diff --git a/nova/compute/api.py b/nova/compute/api.py index b9b24b9919c2..974470c9131c 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -2941,16 +2941,27 @@ class API(base.Base): # through the scheduler again, but we want the instance to be # rebuilt on the same host it's already on. if orig_image_ref != image_href: - request_spec.requested_destination = objects.Destination( - host=instance.host, - node=instance.node) # We have to modify the request spec that goes to the scheduler # to contain the new image. We persist this since we've already # changed the instance.image_ref above so we're being # consistent. request_spec.image = objects.ImageMeta.from_dict(image) request_spec.save() - host = None # This tells conductor to call the scheduler. + if 'scheduler_hints' not in request_spec: + request_spec.scheduler_hints = {} + # Nuke the id on this so we can't accidentally save + # this hint hack later + del request_spec.id + + # NOTE(danms): Passing host=None tells conductor to + # call the scheduler. The _nova_check_type hint + # requires that the scheduler returns only the same + # host that we are currently on and only checks + # rebuild-related filters. + request_spec.scheduler_hints['_nova_check_type'] = ['rebuild'] + request_spec.force_hosts = [instance.host] + request_spec.force_nodes = [instance.node] + host = None except exception.RequestSpecNotFound: # Some old instances can still have no RequestSpec object attached # to them, we need to support the old way diff --git a/nova/scheduler/filters/__init__.py b/nova/scheduler/filters/__init__.py index cbc311cbb355..44f283f7acfa 100644 --- a/nova/scheduler/filters/__init__.py +++ b/nova/scheduler/filters/__init__.py @@ -21,9 +21,27 @@ from nova import filters class BaseHostFilter(filters.BaseFilter): """Base class for host filters.""" - def _filter_one(self, obj, filter_properties): + + # This is set to True if this filter should be run for rebuild. + # For example, with rebuild, we need to ask the scheduler if the + # existing host is still legit for a rebuild with the new image and + # other parameters. We care about running policy filters (i.e. + # ImagePropertiesFilter) but not things that check usage on the + # existing compute node, etc. + RUN_ON_REBUILD = False + + def _filter_one(self, obj, spec): """Return True if the object passes the filter, otherwise False.""" - return self.host_passes(obj, filter_properties) + # Do this here so we don't get scheduler.filters.utils + from nova.scheduler import utils + if not self.RUN_ON_REBUILD and utils.request_is_rebuild(spec): + # If we don't filter, default to passing the host. + return True + else: + # We are either a rebuild filter, in which case we always run, + # or this request is not rebuild in which case all filters + # should run. + return self.host_passes(obj, spec) def host_passes(self, host_state, filter_properties): """Return True if the HostState passes the filter, otherwise False. diff --git a/nova/scheduler/filters/affinity_filter.py b/nova/scheduler/filters/affinity_filter.py index 7eb57eb3ac0a..f8aa47ee039a 100644 --- a/nova/scheduler/filters/affinity_filter.py +++ b/nova/scheduler/filters/affinity_filter.py @@ -29,6 +29,8 @@ class DifferentHostFilter(filters.BaseHostFilter): # The hosts the instances are running on doesn't change within a request run_filter_once_per_request = True + RUN_ON_REBUILD = False + def host_passes(self, host_state, spec_obj): affinity_uuids = spec_obj.get_scheduler_hint('different_host') if affinity_uuids: @@ -45,6 +47,8 @@ class SameHostFilter(filters.BaseHostFilter): # The hosts the instances are running on doesn't change within a request run_filter_once_per_request = True + RUN_ON_REBUILD = False + def host_passes(self, host_state, spec_obj): affinity_uuids = spec_obj.get_scheduler_hint('same_host') if affinity_uuids: @@ -59,6 +63,8 @@ class SimpleCIDRAffinityFilter(filters.BaseHostFilter): # The address of a host doesn't change within a request run_filter_once_per_request = True + RUN_ON_REBUILD = False + def host_passes(self, host_state, spec_obj): affinity_cidr = spec_obj.get_scheduler_hint('cidr', '/24') affinity_host_addr = spec_obj.get_scheduler_hint('build_near_host_ip') @@ -77,6 +83,9 @@ class _GroupAntiAffinityFilter(filters.BaseHostFilter): """Schedule the instance on a different host from a set of group hosts. """ + + RUN_ON_REBUILD = False + def host_passes(self, host_state, spec_obj): # Only invoke the filter if 'anti-affinity' is configured policies = (spec_obj.instance_group.policies @@ -110,6 +119,9 @@ class ServerGroupAntiAffinityFilter(_GroupAntiAffinityFilter): class _GroupAffinityFilter(filters.BaseHostFilter): """Schedule the instance on to host from a set of group hosts. """ + + RUN_ON_REBUILD = False + def host_passes(self, host_state, spec_obj): # Only invoke the filter if 'affinity' is configured policies = (spec_obj.instance_group.policies diff --git a/nova/scheduler/filters/aggregate_image_properties_isolation.py b/nova/scheduler/filters/aggregate_image_properties_isolation.py index 600c8746fa8c..3649d1d1afb9 100644 --- a/nova/scheduler/filters/aggregate_image_properties_isolation.py +++ b/nova/scheduler/filters/aggregate_image_properties_isolation.py @@ -32,6 +32,8 @@ class AggregateImagePropertiesIsolation(filters.BaseHostFilter): # Aggregate data and instance type does not change within a request run_filter_once_per_request = True + RUN_ON_REBUILD = True + def host_passes(self, host_state, spec_obj): """Checks a host in an aggregate that metadata key/value match with image properties. diff --git a/nova/scheduler/filters/aggregate_instance_extra_specs.py b/nova/scheduler/filters/aggregate_instance_extra_specs.py index e758bb2ae160..d2e2d15207ed 100644 --- a/nova/scheduler/filters/aggregate_instance_extra_specs.py +++ b/nova/scheduler/filters/aggregate_instance_extra_specs.py @@ -33,6 +33,8 @@ class AggregateInstanceExtraSpecsFilter(filters.BaseHostFilter): # Aggregate data and instance type does not change within a request run_filter_once_per_request = True + RUN_ON_REBUILD = False + def host_passes(self, host_state, spec_obj): """Return a list of hosts that can create instance_type diff --git a/nova/scheduler/filters/aggregate_multitenancy_isolation.py b/nova/scheduler/filters/aggregate_multitenancy_isolation.py index 2e2278741420..e74294ad7f6c 100644 --- a/nova/scheduler/filters/aggregate_multitenancy_isolation.py +++ b/nova/scheduler/filters/aggregate_multitenancy_isolation.py @@ -28,6 +28,8 @@ class AggregateMultiTenancyIsolation(filters.BaseHostFilter): # Aggregate data and tenant do not change within a request run_filter_once_per_request = True + RUN_ON_REBUILD = False + def host_passes(self, host_state, spec_obj): """If a host is in an aggregate that has the metadata key "filter_tenant_id" it can only create instances from that tenant(s). diff --git a/nova/scheduler/filters/all_hosts_filter.py b/nova/scheduler/filters/all_hosts_filter.py index 07df2c05e772..1556d2cfb8be 100644 --- a/nova/scheduler/filters/all_hosts_filter.py +++ b/nova/scheduler/filters/all_hosts_filter.py @@ -23,5 +23,7 @@ class AllHostsFilter(filters.BaseHostFilter): # list of hosts doesn't change within a request run_filter_once_per_request = True + RUN_ON_REBUILD = False + def host_passes(self, host_state, spec_obj): return True diff --git a/nova/scheduler/filters/availability_zone_filter.py b/nova/scheduler/filters/availability_zone_filter.py index 58b0e9dd37e7..f48a9f357106 100644 --- a/nova/scheduler/filters/availability_zone_filter.py +++ b/nova/scheduler/filters/availability_zone_filter.py @@ -35,6 +35,8 @@ class AvailabilityZoneFilter(filters.BaseHostFilter): # Availability zones do not change within a request run_filter_once_per_request = True + RUN_ON_REBUILD = False + def host_passes(self, host_state, spec_obj): availability_zone = spec_obj.availability_zone diff --git a/nova/scheduler/filters/compute_capabilities_filter.py b/nova/scheduler/filters/compute_capabilities_filter.py index 24fd05ebd7a2..72ac579b8b58 100644 --- a/nova/scheduler/filters/compute_capabilities_filter.py +++ b/nova/scheduler/filters/compute_capabilities_filter.py @@ -30,6 +30,8 @@ class ComputeCapabilitiesFilter(filters.BaseHostFilter): # Instance type and host capabilities do not change within a request run_filter_once_per_request = True + RUN_ON_REBUILD = False + def _get_capabilities(self, host_state, scope): cap = host_state for index in range(0, len(scope)): diff --git a/nova/scheduler/filters/compute_filter.py b/nova/scheduler/filters/compute_filter.py index ae124a94d7c8..259faf4d4176 100644 --- a/nova/scheduler/filters/compute_filter.py +++ b/nova/scheduler/filters/compute_filter.py @@ -25,6 +25,8 @@ LOG = logging.getLogger(__name__) class ComputeFilter(filters.BaseHostFilter): """Filter on active Compute nodes.""" + RUN_ON_REBUILD = False + def __init__(self): self.servicegroup_api = servicegroup.API() diff --git a/nova/scheduler/filters/core_filter.py b/nova/scheduler/filters/core_filter.py index 15cd4da5eac8..30d816fdcc27 100644 --- a/nova/scheduler/filters/core_filter.py +++ b/nova/scheduler/filters/core_filter.py @@ -26,6 +26,8 @@ LOG = logging.getLogger(__name__) class BaseCoreFilter(filters.BaseHostFilter): + RUN_ON_REBUILD = False + def _get_cpu_allocation_ratio(self, host_state, spec_obj): raise NotImplementedError diff --git a/nova/scheduler/filters/disk_filter.py b/nova/scheduler/filters/disk_filter.py index d6323ea59a04..1c478d69dfa8 100644 --- a/nova/scheduler/filters/disk_filter.py +++ b/nova/scheduler/filters/disk_filter.py @@ -28,6 +28,8 @@ CONF = nova.conf.CONF class DiskFilter(filters.BaseHostFilter): """Disk Filter with over subscription flag.""" + RUN_ON_REBUILD = False + def _get_disk_allocation_ratio(self, host_state, spec_obj): return host_state.disk_allocation_ratio @@ -80,6 +82,8 @@ class AggregateDiskFilter(DiskFilter): found. """ + RUN_ON_REBUILD = False + def _get_disk_allocation_ratio(self, host_state, spec_obj): aggregate_vals = utils.aggregate_values_from_key( host_state, diff --git a/nova/scheduler/filters/exact_core_filter.py b/nova/scheduler/filters/exact_core_filter.py index f9b20f997000..6f79f25cb74f 100644 --- a/nova/scheduler/filters/exact_core_filter.py +++ b/nova/scheduler/filters/exact_core_filter.py @@ -25,6 +25,8 @@ LOG = logging.getLogger(__name__) class ExactCoreFilter(filters.BaseHostFilter): """Exact Core Filter.""" + RUN_ON_REBUILD = False + def __init__(self, *args, **kwargs): super(ExactCoreFilter, self).__init__(*args, **kwargs) LOG.warning('ExactCoreFilter is deprecated in Pike and will be ' diff --git a/nova/scheduler/filters/exact_disk_filter.py b/nova/scheduler/filters/exact_disk_filter.py index e382a70e6545..519dd8ca8af6 100644 --- a/nova/scheduler/filters/exact_disk_filter.py +++ b/nova/scheduler/filters/exact_disk_filter.py @@ -23,6 +23,8 @@ LOG = logging.getLogger(__name__) class ExactDiskFilter(filters.BaseHostFilter): """Exact Disk Filter.""" + RUN_ON_REBUILD = False + def __init__(self, *args, **kwargs): super(ExactDiskFilter, self).__init__(*args, **kwargs) LOG.warning('ExactDiskFilter is deprecated in Pike and will be ' diff --git a/nova/scheduler/filters/exact_ram_filter.py b/nova/scheduler/filters/exact_ram_filter.py index d09b5d5ef1cb..123a1e11a856 100644 --- a/nova/scheduler/filters/exact_ram_filter.py +++ b/nova/scheduler/filters/exact_ram_filter.py @@ -23,6 +23,8 @@ LOG = logging.getLogger(__name__) class ExactRamFilter(filters.BaseHostFilter): """Exact RAM Filter.""" + RUN_ON_REBUILD = False + def __init__(self, *args, **kwargs): super(ExactRamFilter, self).__init__(*args, **kwargs) LOG.warning('ExactRamFilter is deprecated in Pike and will be ' diff --git a/nova/scheduler/filters/image_props_filter.py b/nova/scheduler/filters/image_props_filter.py index 1bf241a487db..f6977dffb244 100644 --- a/nova/scheduler/filters/image_props_filter.py +++ b/nova/scheduler/filters/image_props_filter.py @@ -35,6 +35,8 @@ class ImagePropertiesFilter(filters.BaseHostFilter): contained in the image dictionary in the request_spec. """ + RUN_ON_REBUILD = True + # Image Properties and Compute Capabilities do not change within # a request run_filter_once_per_request = True diff --git a/nova/scheduler/filters/io_ops_filter.py b/nova/scheduler/filters/io_ops_filter.py index 9b4a16cbb602..0d2190c4ebb2 100644 --- a/nova/scheduler/filters/io_ops_filter.py +++ b/nova/scheduler/filters/io_ops_filter.py @@ -28,6 +28,8 @@ CONF = nova.conf.CONF class IoOpsFilter(filters.BaseHostFilter): """Filter out hosts with too many concurrent I/O operations.""" + RUN_ON_REBUILD = False + def _get_max_io_ops_per_host(self, host_state, spec_obj): return CONF.filter_scheduler.max_io_ops_per_host diff --git a/nova/scheduler/filters/isolated_hosts_filter.py b/nova/scheduler/filters/isolated_hosts_filter.py index 8b3677aea632..86ab1c4ce901 100644 --- a/nova/scheduler/filters/isolated_hosts_filter.py +++ b/nova/scheduler/filters/isolated_hosts_filter.py @@ -25,6 +25,8 @@ class IsolatedHostsFilter(filters.BaseHostFilter): # The configuration values do not change within a request run_filter_once_per_request = True + RUN_ON_REBUILD = True + def host_passes(self, host_state, spec_obj): """Result Matrix with 'restrict_isolated_hosts_to_isolated_images' set to True:: diff --git a/nova/scheduler/filters/json_filter.py b/nova/scheduler/filters/json_filter.py index 70b96ad9d944..1afabfa33bde 100644 --- a/nova/scheduler/filters/json_filter.py +++ b/nova/scheduler/filters/json_filter.py @@ -26,6 +26,9 @@ class JsonFilter(filters.BaseHostFilter): """Host Filter to allow simple JSON-based grammar for selecting hosts. """ + + RUN_ON_REBUILD = False + def _op_compare(self, args, op): """Returns True if the specified operator can successfully compare the first item in the args with all the rest. Will diff --git a/nova/scheduler/filters/metrics_filter.py b/nova/scheduler/filters/metrics_filter.py index 352b6e66c2aa..9b4d6307f156 100644 --- a/nova/scheduler/filters/metrics_filter.py +++ b/nova/scheduler/filters/metrics_filter.py @@ -32,6 +32,8 @@ class MetricsFilter(filters.BaseHostFilter): these hosts. """ + RUN_ON_REBUILD = False + def __init__(self): super(MetricsFilter, self).__init__() opts = utils.parse_options(CONF.metrics.weight_setting, diff --git a/nova/scheduler/filters/num_instances_filter.py b/nova/scheduler/filters/num_instances_filter.py index 115b31c2be47..52946fdbc112 100644 --- a/nova/scheduler/filters/num_instances_filter.py +++ b/nova/scheduler/filters/num_instances_filter.py @@ -28,6 +28,8 @@ CONF = nova.conf.CONF class NumInstancesFilter(filters.BaseHostFilter): """Filter out hosts with too many instances.""" + RUN_ON_REBUILD = False + def _get_max_instances_per_host(self, host_state, spec_obj): return CONF.filter_scheduler.max_instances_per_host diff --git a/nova/scheduler/filters/numa_topology_filter.py b/nova/scheduler/filters/numa_topology_filter.py index 55bb99246f49..10079ce2f15a 100644 --- a/nova/scheduler/filters/numa_topology_filter.py +++ b/nova/scheduler/filters/numa_topology_filter.py @@ -23,6 +23,8 @@ LOG = logging.getLogger(__name__) class NUMATopologyFilter(filters.BaseHostFilter): """Filter on requested NUMA topology.""" + RUN_ON_REBUILD = True + def _satisfies_cpu_policy(self, host_state, extra_specs, image_props): """Check that the host_state provided satisfies any available CPU policy requirements. diff --git a/nova/scheduler/filters/pci_passthrough_filter.py b/nova/scheduler/filters/pci_passthrough_filter.py index 0db453d53260..f08899586ad0 100644 --- a/nova/scheduler/filters/pci_passthrough_filter.py +++ b/nova/scheduler/filters/pci_passthrough_filter.py @@ -40,6 +40,8 @@ class PciPassthroughFilter(filters.BaseHostFilter): """ + RUN_ON_REBUILD = False + def host_passes(self, host_state, spec_obj): """Return true if the host has the required PCI devices.""" pci_requests = spec_obj.pci_requests diff --git a/nova/scheduler/filters/ram_filter.py b/nova/scheduler/filters/ram_filter.py index ae7e8695277f..8830b709d219 100644 --- a/nova/scheduler/filters/ram_filter.py +++ b/nova/scheduler/filters/ram_filter.py @@ -25,6 +25,8 @@ LOG = logging.getLogger(__name__) class BaseRamFilter(filters.BaseHostFilter): + RUN_ON_REBUILD = False + def _get_ram_allocation_ratio(self, host_state, spec_obj): raise NotImplementedError diff --git a/nova/scheduler/filters/retry_filter.py b/nova/scheduler/filters/retry_filter.py index 4c2f0a163534..61ac2afefd66 100644 --- a/nova/scheduler/filters/retry_filter.py +++ b/nova/scheduler/filters/retry_filter.py @@ -26,6 +26,10 @@ class RetryFilter(filters.BaseHostFilter): purposes """ + # NOTE(danms): This does not affect _where_ an instance lands, so not + # related to rebuild. + RUN_ON_REBUILD = False + def host_passes(self, host_state, spec_obj): """Skip nodes that have already been attempted.""" retry = spec_obj.retry diff --git a/nova/scheduler/filters/trusted_filter.py b/nova/scheduler/filters/trusted_filter.py index 059b6d8030e6..a0ecd3e95a97 100644 --- a/nova/scheduler/filters/trusted_filter.py +++ b/nova/scheduler/filters/trusted_filter.py @@ -230,6 +230,8 @@ class ComputeAttestation(object): class TrustedFilter(filters.BaseHostFilter): """Trusted filter to support Trusted Compute Pools.""" + RUN_ON_REBUILD = False + def __init__(self): self.compute_attestation = ComputeAttestation() msg = _LW('The TrustedFilter is deprecated as it has been marked ' diff --git a/nova/scheduler/filters/type_filter.py b/nova/scheduler/filters/type_filter.py index 7b9a0ad54fba..4248d50b6146 100644 --- a/nova/scheduler/filters/type_filter.py +++ b/nova/scheduler/filters/type_filter.py @@ -30,6 +30,8 @@ class TypeAffinityFilter(filters.BaseHostFilter): (spread) set to 1 (default). """ + RUN_ON_REBUILD = False + def __init__(self): super(TypeAffinityFilter, self).__init__() LOG.warning('TypeAffinityFilter is deprecated for removal in the ' @@ -64,6 +66,8 @@ class AggregateTypeAffinityFilter(filters.BaseHostFilter): # Aggregate data does not change within a request run_filter_once_per_request = True + RUN_ON_REBUILD = False + def host_passes(self, host_state, spec_obj): instance_type = spec_obj.flavor diff --git a/nova/scheduler/host_manager.py b/nova/scheduler/host_manager.py index 6a154ffc57fc..e81426d38658 100644 --- a/nova/scheduler/host_manager.py +++ b/nova/scheduler/host_manager.py @@ -577,8 +577,13 @@ class HostManager(object): _match_forced_hosts(name_to_cls_map, force_hosts) if force_nodes: _match_forced_nodes(name_to_cls_map, force_nodes) - if force_hosts or force_nodes: - # NOTE(deva): Skip filters when forcing host or node + check_type = ('scheduler_hints' in spec_obj and + spec_obj.scheduler_hints.get('_nova_check_type')) + if not check_type and (force_hosts or force_nodes): + # NOTE(deva,dansmith): Skip filters when forcing host or node + # unless we've declared the internal check type flag, in which + # case we're asking for a specific host and for filtering to + # be done. if name_to_cls_map: return name_to_cls_map.values() else: diff --git a/nova/scheduler/utils.py b/nova/scheduler/utils.py index 7b9b9658af5d..e48868b6164c 100644 --- a/nova/scheduler/utils.py +++ b/nova/scheduler/utils.py @@ -736,3 +736,16 @@ def retry_on_timeout(retries=1): return outer retry_select_destinations = retry_on_timeout(CONF.scheduler.max_attempts - 1) + + +def request_is_rebuild(spec_obj): + """Returns True if request is for a rebuild. + + :param spec_obj: An objects.RequestSpec to examine (or None). + """ + if not spec_obj: + return False + if 'scheduler_hints' not in spec_obj: + return False + check_type = spec_obj.scheduler_hints.get('_nova_check_type') + return check_type == ['rebuild'] diff --git a/nova/tests/functional/test_servers.py b/nova/tests/functional/test_servers.py index 6378d59fc96c..4d9c740a6d06 100644 --- a/nova/tests/functional/test_servers.py +++ b/nova/tests/functional/test_servers.py @@ -1066,6 +1066,16 @@ class ServerRebuildTestCase(integrated_helpers._IntegratedTestBase, # can use to update image metadata via our compute images proxy API. microversion = '2.38' + def _disable_compute_for(self, server): + # Refresh to get its host + server = self.api.get_server(server['id']) + host = server['OS-EXT-SRV-ATTR:host'] + + # Disable the service it is on + self.api_fixture.admin_api.put_service('disable', + {'host': host, + 'binary': 'nova-compute'}) + def test_rebuild_with_image_novalidhost(self): """Creates a server with an image that is valid for the single compute that we have. Then rebuilds the server, passing in an image with @@ -1073,6 +1083,12 @@ class ServerRebuildTestCase(integrated_helpers._IntegratedTestBase, a NoValidHost error. The ImagePropertiesFilter filter is enabled by default so that should filter out the host based on the image meta. """ + + fake.set_nodes(['host2']) + self.addCleanup(fake.restore_nodes) + self.flags(host='host2') + self.compute2 = self.start_service('compute', host='host2') + server_req_body = { 'server': { # We hard-code from a fake image since we can't get images @@ -1087,6 +1103,11 @@ class ServerRebuildTestCase(integrated_helpers._IntegratedTestBase, } server = self.api.post_server(server_req_body) self._wait_for_state_change(self.api, server, 'ACTIVE') + + # Disable the host we're on so ComputeFilter would have ruled it out + # normally + self._disable_compute_for(server) + # Now update the image metadata to be something that won't work with # the fake compute driver we're using since the fake driver has an # "x86_64" architecture. diff --git a/nova/tests/unit/compute/test_compute_api.py b/nova/tests/unit/compute/test_compute_api.py index 2b0001ea1b21..edc07430bcce 100644 --- a/nova/tests/unit/compute/test_compute_api.py +++ b/nova/tests/unit/compute/test_compute_api.py @@ -3218,6 +3218,7 @@ class _ComputeAPIUnitTestMixIn(object): system_metadata=orig_system_metadata, expected_attrs=['system_metadata'], image_ref=orig_image_href, + node='node', vm_mode=fields_obj.VMMode.HVM) flavor = instance.get_flavor() @@ -3229,7 +3230,7 @@ class _ComputeAPIUnitTestMixIn(object): _get_image.side_effect = get_image bdm_get_by_instance_uuid.return_value = bdms - fake_spec = objects.RequestSpec() + fake_spec = objects.RequestSpec(id=1) req_spec_get_by_inst_uuid.return_value = fake_spec with mock.patch.object(self.compute_api.compute_task_api, @@ -3247,10 +3248,9 @@ class _ComputeAPIUnitTestMixIn(object): # assert the request spec was modified so the scheduler picks # the existing instance host/node req_spec_save.assert_called_once_with() - self.assertIn('requested_destination', fake_spec) - requested_destination = fake_spec.requested_destination - self.assertEqual(instance.host, requested_destination.host) - self.assertEqual(instance.node, requested_destination.node) + self.assertIn('_nova_check_type', fake_spec.scheduler_hints) + self.assertEqual('rebuild', + fake_spec.scheduler_hints['_nova_check_type'][0]) _check_auto_disk_config.assert_called_once_with(image=new_image) _checks_for_create_and_rebuild.assert_called_once_with(self.context, diff --git a/releasenotes/notes/bug-1664931-refine-validate-image-rebuild-6d730042438eec10.yaml b/releasenotes/notes/bug-1664931-refine-validate-image-rebuild-6d730042438eec10.yaml new file mode 100644 index 000000000000..1c9bc6113f37 --- /dev/null +++ b/releasenotes/notes/bug-1664931-refine-validate-image-rebuild-6d730042438eec10.yaml @@ -0,0 +1,20 @@ +--- +fixes: + - | + The fix for `OSSA-2017-005`_ (CVE-2017-16239) was too far-reaching in that + rebuilds can now fail based on scheduling filters that should not apply + to rebuild. For example, a rebuild of an instance on a disabled compute + host could fail whereas it would not before the fix for CVE-2017-16239. + Similarly, rebuilding an instance on a host that is at capacity for vcpu, + memory or disk could fail since the scheduler filters would treat it as a + new build request even though the rebuild is not claiming *new* resources. + + Therefore this release contains a fix for those regressions in scheduling + behavior on rebuild while maintaining the original fix for CVE-2017-16239. + + .. note:: The fix relies on a ``RUN_ON_REBUILD`` variable which is checked + for all scheduler filters during a rebuild. The reasoning behind + the value for that variable depends on each filter. If you have + out-of-tree scheduler filters, you will likely need to assess + whether or not they need to override the default value (False) + for the new variable.