diff --git a/nova/filters.py b/nova/filters.py index d4bbe1851c72..4793e229c770 100644 --- a/nova/filters.py +++ b/nova/filters.py @@ -27,13 +27,13 @@ LOG = logging.getLogger(__name__) class BaseFilter(object): """Base class for all filter classes.""" - def _filter_one(self, obj, filter_properties): + def _filter_one(self, obj, spec_obj): """Return True if it passes the filter, False otherwise. Override this in a subclass. """ return True - def filter_all(self, filter_obj_list, filter_properties): + def filter_all(self, filter_obj_list, spec_obj): """Yield objects that pass the filter. Can be overridden in a subclass, if you need to base filtering @@ -41,7 +41,7 @@ class BaseFilter(object): _filter_one() to filter a single object. """ for obj in filter_obj_list: - if self._filter_one(obj, filter_properties): + if self._filter_one(obj, spec_obj): yield obj # Set to true in a subclass if a filter only needs to be run once @@ -65,7 +65,7 @@ class BaseFilterHandler(loadables.BaseLoader): This class should be subclassed where one needs to use filters. """ - def get_filtered_objects(self, filters, objs, filter_properties, index=0): + def get_filtered_objects(self, filters, objs, spec_obj, index=0): list_objs = list(objs) LOG.debug("Starting with %d host(s)", len(list_objs)) # Track the hosts as they are removed. The 'full_filter_results' list @@ -82,7 +82,7 @@ class BaseFilterHandler(loadables.BaseLoader): if filter_.run_filter_for_index(index): cls_name = filter_.__class__.__name__ start_count = len(list_objs) - objs = filter_.filter_all(list_objs, filter_properties) + objs = filter_.filter_all(list_objs, spec_obj) if objs is None: LOG.debug("Filter %s says to stop filtering", cls_name) return @@ -104,9 +104,17 @@ class BaseFilterHandler(loadables.BaseLoader): {'cls_name': cls_name, 'obj_len': len(list_objs)}) if not list_objs: # Log the filtration history - rspec = filter_properties.get("request_spec", {}) - inst_props = rspec.get("instance_properties", {}) - msg_dict = {"inst_uuid": inst_props.get("uuid", ""), + # NOTE(sbauza): Since the Cells scheduler still provides a legacy + # dictionary for filter_props, and since we agreed on not modifying + # the Cells scheduler to support that because of Cells v2, we + # prefer to define a compatible way to address both types + if isinstance(spec_obj, dict): + rspec = spec_obj.get("request_spec", {}) + inst_props = rspec.get("instance_properties", {}) + inst_uuid = inst_props.get("uuid", "") + else: + inst_uuid = spec_obj.instance_uuid + msg_dict = {"inst_uuid": inst_uuid, "str_results": str(full_filter_results), } full_msg = ("Filtering removed all hosts for the request with " diff --git a/nova/scheduler/filter_scheduler.py b/nova/scheduler/filter_scheduler.py index 378496254c9b..9e470cd49bb9 100644 --- a/nova/scheduler/filter_scheduler.py +++ b/nova/scheduler/filter_scheduler.py @@ -127,23 +127,12 @@ class FilterScheduler(driver.Scheduler): selected_hosts = [] num_instances = spec_obj.num_instances - # TODO(sbauza): Modify the interfaces for HostManager and filters to - # accept the RequestSpec object directly (in a later patch hopefully) - filter_properties = spec_obj.to_legacy_filter_properties_dict() - # NOTE(sbauza): Adding temporarly some keys since filters are - # directly using it - until we provide directly RequestSpec - filter_properties.update( - {'request_spec': spec_obj.to_legacy_request_spec_dict(), - 'instance_type': spec_obj.flavor}) - # TODO(sbauza): Adding two keys not used in-tree but which will be - # provided as non-fields for the RequestSpec once we provide it to the - # filters - filter_properties.update({'context': context, - 'config_options': config_options}) + # NOTE(sbauza): Adding one field for any out-of-tree need + spec_obj.config_options = config_options for num in range(num_instances): # Filter local hosts based on requirements ... hosts = self.host_manager.get_filtered_hosts(hosts, - filter_properties, index=num) + spec_obj, index=num) if not hosts: # Can't get any more locally. break @@ -151,7 +140,7 @@ class FilterScheduler(driver.Scheduler): LOG.debug("Filtered %(hosts)s", {'hosts': hosts}) weighed_hosts = self.host_manager.get_weighed_hosts(hosts, - filter_properties) + spec_obj) LOG.debug("Weighed %(hosts)s", {'hosts': weighed_hosts}) @@ -169,8 +158,10 @@ class FilterScheduler(driver.Scheduler): # Now consume the resources so the filter/weights # will change for the next instance. chosen_host.obj.consume_from_request(spec_obj) - if filter_properties.get('group_updated') is True: - filter_properties['group_hosts'].add(chosen_host.obj.host) + if spec_obj.instance_group is not None: + spec_obj.instance_group.hosts.append(chosen_host.obj.host) + # hosts has to be not part of the updates when saving + spec_obj.instance_group.obj_reset_changes(['hosts']) return selected_hosts def _get_all_host_states(self, context): diff --git a/nova/scheduler/filters/__init__.py b/nova/scheduler/filters/__init__.py index 1359913e1b92..c82729c95776 100644 --- a/nova/scheduler/filters/__init__.py +++ b/nova/scheduler/filters/__init__.py @@ -16,8 +16,10 @@ """ Scheduler host filters """ +import functools from nova import filters +from nova import objects class BaseHostFilter(filters.BaseFilter): @@ -45,3 +47,31 @@ def all_filters(): and should return a list of all filter classes available. """ return HostFilterHandler().get_all_classes() + + +# TODO(sbauza): Remove that decorator once all filters are using RequestSpec +# object directly. +def compat_legacy_props(function): + """Decorator for returning a legacy filter_properties dictionary. + + This is used for keeping unchanged the existing filters without yet using + the RequestSpec fields by returning a legacy dictionary. + """ + + @functools.wraps(function) + def decorated_host_passes(self, host_state, filter_properties): + if isinstance(filter_properties, objects.RequestSpec): + legacy_props = filter_properties.to_legacy_filter_properties_dict() + legacy_props.update({'request_spec': ( + filter_properties.to_legacy_request_spec_dict()), + 'instance_type': filter_properties.flavor}) + # TODO(sbauza): Adding two keys not used in-tree but which will be + # provided as non-fields for the RequestSpec once we provide it to + # the filters + legacy_props.update( + {'context': filter_properties._context, + 'config_options': filter_properties.config_options}) + filter_properties = legacy_props + return function(self, host_state, filter_properties) + + return decorated_host_passes diff --git a/nova/scheduler/filters/affinity_filter.py b/nova/scheduler/filters/affinity_filter.py index 3470e532c7ed..22fc508ba62d 100644 --- a/nova/scheduler/filters/affinity_filter.py +++ b/nova/scheduler/filters/affinity_filter.py @@ -30,6 +30,7 @@ class DifferentHostFilter(filters.BaseHostFilter): # The hosts the instances are running on doesn't change within a request run_filter_once_per_request = True + @filters.compat_legacy_props def host_passes(self, host_state, filter_properties): scheduler_hints = filter_properties.get('scheduler_hints') or {} @@ -49,6 +50,7 @@ class SameHostFilter(filters.BaseHostFilter): # The hosts the instances are running on doesn't change within a request run_filter_once_per_request = True + @filters.compat_legacy_props def host_passes(self, host_state, filter_properties): scheduler_hints = filter_properties.get('scheduler_hints') or {} @@ -67,6 +69,7 @@ class SimpleCIDRAffinityFilter(filters.BaseHostFilter): # The address of a host doesn't change within a request run_filter_once_per_request = True + @filters.compat_legacy_props def host_passes(self, host_state, filter_properties): scheduler_hints = filter_properties.get('scheduler_hints') or {} @@ -87,6 +90,7 @@ class _GroupAntiAffinityFilter(filters.BaseHostFilter): """Schedule the instance on a different host from a set of group hosts. """ + @filters.compat_legacy_props def host_passes(self, host_state, filter_properties): # Only invoke the filter is 'anti-affinity' is configured policies = filter_properties.get('group_policies', []) @@ -113,6 +117,7 @@ class ServerGroupAntiAffinityFilter(_GroupAntiAffinityFilter): class _GroupAffinityFilter(filters.BaseHostFilter): """Schedule the instance on to host from a set of group hosts. """ + @filters.compat_legacy_props def host_passes(self, host_state, filter_properties): # Only invoke the filter is 'affinity' is configured policies = filter_properties.get('group_policies', []) diff --git a/nova/scheduler/filters/aggregate_image_properties_isolation.py b/nova/scheduler/filters/aggregate_image_properties_isolation.py index c56ecb8229ca..623c5db5206f 100644 --- a/nova/scheduler/filters/aggregate_image_properties_isolation.py +++ b/nova/scheduler/filters/aggregate_image_properties_isolation.py @@ -40,6 +40,7 @@ class AggregateImagePropertiesIsolation(filters.BaseHostFilter): # Aggregate data and instance type does not change within a request run_filter_once_per_request = True + @filters.compat_legacy_props def host_passes(self, host_state, filter_properties): """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 cd8983efd1f4..5c283bda7af7 100644 --- a/nova/scheduler/filters/aggregate_instance_extra_specs.py +++ b/nova/scheduler/filters/aggregate_instance_extra_specs.py @@ -33,6 +33,7 @@ class AggregateInstanceExtraSpecsFilter(filters.BaseHostFilter): # Aggregate data and instance type does not change within a request run_filter_once_per_request = True + @filters.compat_legacy_props def host_passes(self, host_state, filter_properties): """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 385a17acf1fa..32124f76cf71 100644 --- a/nova/scheduler/filters/aggregate_multitenancy_isolation.py +++ b/nova/scheduler/filters/aggregate_multitenancy_isolation.py @@ -28,6 +28,7 @@ class AggregateMultiTenancyIsolation(filters.BaseHostFilter): # Aggregate data and tenant do not change within a request run_filter_once_per_request = True + @filters.compat_legacy_props def host_passes(self, host_state, filter_properties): """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 71d136f4317c..190e46dd6944 100644 --- a/nova/scheduler/filters/all_hosts_filter.py +++ b/nova/scheduler/filters/all_hosts_filter.py @@ -23,5 +23,6 @@ class AllHostsFilter(filters.BaseHostFilter): # list of hosts doesn't change within a request run_filter_once_per_request = True + @filters.compat_legacy_props def host_passes(self, host_state, filter_properties): return True diff --git a/nova/scheduler/filters/availability_zone_filter.py b/nova/scheduler/filters/availability_zone_filter.py index f3eec3f7928e..03800ab00c55 100644 --- a/nova/scheduler/filters/availability_zone_filter.py +++ b/nova/scheduler/filters/availability_zone_filter.py @@ -36,6 +36,7 @@ class AvailabilityZoneFilter(filters.BaseHostFilter): # Availability zones do not change within a request run_filter_once_per_request = True + @filters.compat_legacy_props def host_passes(self, host_state, filter_properties): spec = filter_properties.get('request_spec', {}) props = spec.get('instance_properties', {}) diff --git a/nova/scheduler/filters/compute_capabilities_filter.py b/nova/scheduler/filters/compute_capabilities_filter.py index 51a4381d0753..bef34f66835f 100644 --- a/nova/scheduler/filters/compute_capabilities_filter.py +++ b/nova/scheduler/filters/compute_capabilities_filter.py @@ -92,6 +92,7 @@ class ComputeCapabilitiesFilter(filters.BaseHostFilter): return False return True + @filters.compat_legacy_props def host_passes(self, host_state, filter_properties): """Return a list of hosts that can create instance_type.""" instance_type = filter_properties.get('instance_type') diff --git a/nova/scheduler/filters/compute_filter.py b/nova/scheduler/filters/compute_filter.py index ec754c098a66..bbfd66095b72 100644 --- a/nova/scheduler/filters/compute_filter.py +++ b/nova/scheduler/filters/compute_filter.py @@ -34,6 +34,7 @@ class ComputeFilter(filters.BaseHostFilter): # Host state does not change within a request run_filter_once_per_request = True + @filters.compat_legacy_props def host_passes(self, host_state, filter_properties): """Returns True for only active compute nodes.""" service = host_state.service diff --git a/nova/scheduler/filters/core_filter.py b/nova/scheduler/filters/core_filter.py index 1fed2c034005..c323dee3caa3 100644 --- a/nova/scheduler/filters/core_filter.py +++ b/nova/scheduler/filters/core_filter.py @@ -29,6 +29,7 @@ class BaseCoreFilter(filters.BaseHostFilter): def _get_cpu_allocation_ratio(self, host_state, filter_properties): raise NotImplementedError + @filters.compat_legacy_props def host_passes(self, host_state, filter_properties): """Return True if host has sufficient CPU cores.""" instance_type = filter_properties.get('instance_type') diff --git a/nova/scheduler/filters/disk_filter.py b/nova/scheduler/filters/disk_filter.py index d87baaede5e3..e623616733a0 100644 --- a/nova/scheduler/filters/disk_filter.py +++ b/nova/scheduler/filters/disk_filter.py @@ -35,6 +35,7 @@ class DiskFilter(filters.BaseHostFilter): def _get_disk_allocation_ratio(self, host_state, filter_properties): return CONF.disk_allocation_ratio + @filters.compat_legacy_props def host_passes(self, host_state, filter_properties): """Filter based on disk usage.""" instance_type = filter_properties.get('instance_type') diff --git a/nova/scheduler/filters/exact_core_filter.py b/nova/scheduler/filters/exact_core_filter.py index 800ab8a7ece2..43150509e6e2 100644 --- a/nova/scheduler/filters/exact_core_filter.py +++ b/nova/scheduler/filters/exact_core_filter.py @@ -25,6 +25,7 @@ LOG = logging.getLogger(__name__) class ExactCoreFilter(filters.BaseHostFilter): """Exact Core Filter.""" + @filters.compat_legacy_props def host_passes(self, host_state, filter_properties): """Return True if host has the exact number of CPU cores.""" instance_type = filter_properties.get('instance_type') diff --git a/nova/scheduler/filters/exact_disk_filter.py b/nova/scheduler/filters/exact_disk_filter.py index 7a8c5b5889f0..3b5e56279450 100644 --- a/nova/scheduler/filters/exact_disk_filter.py +++ b/nova/scheduler/filters/exact_disk_filter.py @@ -23,6 +23,7 @@ LOG = logging.getLogger(__name__) class ExactDiskFilter(filters.BaseHostFilter): """Exact Disk Filter.""" + @filters.compat_legacy_props def host_passes(self, host_state, filter_properties): """Return True if host has the exact amount of disk available.""" instance_type = filter_properties.get('instance_type') diff --git a/nova/scheduler/filters/exact_ram_filter.py b/nova/scheduler/filters/exact_ram_filter.py index 71aab99cd744..3d7baf4794e7 100644 --- a/nova/scheduler/filters/exact_ram_filter.py +++ b/nova/scheduler/filters/exact_ram_filter.py @@ -23,6 +23,7 @@ LOG = logging.getLogger(__name__) class ExactRamFilter(filters.BaseHostFilter): """Exact RAM Filter.""" + @filters.compat_legacy_props def host_passes(self, host_state, filter_properties): """Return True if host has the exact amount of RAM available.""" instance_type = filter_properties.get('instance_type') diff --git a/nova/scheduler/filters/image_props_filter.py b/nova/scheduler/filters/image_props_filter.py index b43d33588d66..57c01f2a94c2 100644 --- a/nova/scheduler/filters/image_props_filter.py +++ b/nova/scheduler/filters/image_props_filter.py @@ -95,6 +95,7 @@ class ImagePropertiesFilter(filters.BaseHostFilter): 'hypervisor_version': hypervisor_version}) return False + @filters.compat_legacy_props def host_passes(self, host_state, filter_properties): """Check if host passes specified image properties. diff --git a/nova/scheduler/filters/io_ops_filter.py b/nova/scheduler/filters/io_ops_filter.py index 7e6c7d93ff73..5fca44c42fbb 100644 --- a/nova/scheduler/filters/io_ops_filter.py +++ b/nova/scheduler/filters/io_ops_filter.py @@ -39,6 +39,7 @@ class IoOpsFilter(filters.BaseHostFilter): def _get_max_io_ops_per_host(self, host_state, filter_properties): return CONF.max_io_ops_per_host + @filters.compat_legacy_props def host_passes(self, host_state, filter_properties): """Use information about current vm and task states collected from compute node statistics to decide whether to filter. diff --git a/nova/scheduler/filters/isolated_hosts_filter.py b/nova/scheduler/filters/isolated_hosts_filter.py index a77cd2f9196e..5b93634f70b2 100644 --- a/nova/scheduler/filters/isolated_hosts_filter.py +++ b/nova/scheduler/filters/isolated_hosts_filter.py @@ -39,6 +39,7 @@ class IsolatedHostsFilter(filters.BaseHostFilter): # The configuration values do not change within a request run_filter_once_per_request = True + @filters.compat_legacy_props def host_passes(self, host_state, filter_properties): """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 e3f59b2be0de..95ce0eabd4e6 100644 --- a/nova/scheduler/filters/json_filter.py +++ b/nova/scheduler/filters/json_filter.py @@ -126,6 +126,7 @@ class JsonFilter(filters.BaseHostFilter): result = method(self, cooked_args) return result + @filters.compat_legacy_props def host_passes(self, host_state, filter_properties): """Return a list of hosts that can fulfill the requirements specified in the query. diff --git a/nova/scheduler/filters/metrics_filter.py b/nova/scheduler/filters/metrics_filter.py index 92b92ac96554..7eb8e3ec672d 100644 --- a/nova/scheduler/filters/metrics_filter.py +++ b/nova/scheduler/filters/metrics_filter.py @@ -43,6 +43,7 @@ class MetricsFilter(filters.BaseHostFilter): name="metrics.weight_setting") self.keys = set([x[0] for x in opts]) + @filters.compat_legacy_props def host_passes(self, host_state, filter_properties): metrics_on_host = set(m.name for m in host_state.metrics) if not self.keys.issubset(metrics_on_host): diff --git a/nova/scheduler/filters/num_instances_filter.py b/nova/scheduler/filters/num_instances_filter.py index 603bf6542934..aa9856e585be 100644 --- a/nova/scheduler/filters/num_instances_filter.py +++ b/nova/scheduler/filters/num_instances_filter.py @@ -36,6 +36,7 @@ class NumInstancesFilter(filters.BaseHostFilter): def _get_max_instances_per_host(self, host_state, filter_properties): return CONF.max_instances_per_host + @filters.compat_legacy_props def host_passes(self, host_state, filter_properties): num_instances = host_state.num_instances max_instances = self._get_max_instances_per_host( diff --git a/nova/scheduler/filters/numa_topology_filter.py b/nova/scheduler/filters/numa_topology_filter.py index ee83e62c0dd3..43cec742984c 100644 --- a/nova/scheduler/filters/numa_topology_filter.py +++ b/nova/scheduler/filters/numa_topology_filter.py @@ -18,6 +18,7 @@ from nova.virt import hardware class NUMATopologyFilter(filters.BaseHostFilter): """Filter on requested NUMA topology.""" + @filters.compat_legacy_props def host_passes(self, host_state, filter_properties): ram_ratio = host_state.ram_allocation_ratio cpu_ratio = host_state.cpu_allocation_ratio diff --git a/nova/scheduler/filters/pci_passthrough_filter.py b/nova/scheduler/filters/pci_passthrough_filter.py index e3a366085ec1..266f52840ad8 100644 --- a/nova/scheduler/filters/pci_passthrough_filter.py +++ b/nova/scheduler/filters/pci_passthrough_filter.py @@ -40,6 +40,7 @@ class PciPassthroughFilter(filters.BaseHostFilter): """ + @filters.compat_legacy_props def host_passes(self, host_state, filter_properties): """Return true if the host has the required PCI devices.""" request_spec = filter_properties.get('request_spec', {}) diff --git a/nova/scheduler/filters/ram_filter.py b/nova/scheduler/filters/ram_filter.py index 3e7fbf507401..735215dbbdc1 100644 --- a/nova/scheduler/filters/ram_filter.py +++ b/nova/scheduler/filters/ram_filter.py @@ -28,6 +28,7 @@ class BaseRamFilter(filters.BaseHostFilter): def _get_ram_allocation_ratio(self, host_state, filter_properties): raise NotImplementedError + @filters.compat_legacy_props def host_passes(self, host_state, filter_properties): """Only return hosts with sufficient available RAM.""" instance_type = filter_properties.get('instance_type') diff --git a/nova/scheduler/filters/retry_filter.py b/nova/scheduler/filters/retry_filter.py index d2f715e75b7a..0a9692ef7f40 100644 --- a/nova/scheduler/filters/retry_filter.py +++ b/nova/scheduler/filters/retry_filter.py @@ -25,6 +25,7 @@ class RetryFilter(filters.BaseHostFilter): purposes """ + @filters.compat_legacy_props def host_passes(self, host_state, filter_properties): """Skip nodes that have already been attempted.""" retry = filter_properties.get('retry', None) diff --git a/nova/scheduler/filters/trusted_filter.py b/nova/scheduler/filters/trusted_filter.py index 438b8da524ac..b9acad6da59f 100644 --- a/nova/scheduler/filters/trusted_filter.py +++ b/nova/scheduler/filters/trusted_filter.py @@ -265,6 +265,7 @@ class TrustedFilter(filters.BaseHostFilter): # The hosts the instances are running on doesn't change within a request run_filter_once_per_request = True + @filters.compat_legacy_props def host_passes(self, host_state, filter_properties): instance_type = filter_properties.get('instance_type', {}) extra = instance_type.get('extra_specs', {}) diff --git a/nova/scheduler/filters/type_filter.py b/nova/scheduler/filters/type_filter.py index e593cf071a59..9170f4190459 100644 --- a/nova/scheduler/filters/type_filter.py +++ b/nova/scheduler/filters/type_filter.py @@ -25,6 +25,7 @@ class TypeAffinityFilter(filters.BaseHostFilter): (spread) set to 1 (default). """ + @filters.compat_legacy_props def host_passes(self, host_state, filter_properties): """Dynamically limits hosts to one instance type @@ -48,6 +49,7 @@ class AggregateTypeAffinityFilter(filters.BaseHostFilter): # Aggregate data does not change within a request run_filter_once_per_request = True + @filters.compat_legacy_props def host_passes(self, host_state, filter_properties): instance_type = filter_properties.get('instance_type') diff --git a/nova/scheduler/host_manager.py b/nova/scheduler/host_manager.py index b8ab26dcab1c..7c8d31818a4f 100644 --- a/nova/scheduler/host_manager.py +++ b/nova/scheduler/host_manager.py @@ -449,7 +449,7 @@ class HostManager(object): raise exception.SchedulerHostFilterNotFound(filter_name=msg) return good_filters - def get_filtered_hosts(self, hosts, filter_properties, + def get_filtered_hosts(self, hosts, spec_obj, filter_class_names=None, index=0): """Filter hosts and return only ones passing all filters.""" @@ -499,9 +499,9 @@ class HostManager(object): filters = self.default_filters else: filters = self._choose_host_filters(filter_class_names) - ignore_hosts = filter_properties.get('ignore_hosts', []) - force_hosts = filter_properties.get('force_hosts', []) - force_nodes = filter_properties.get('force_nodes', []) + ignore_hosts = spec_obj.ignore_hosts or [] + force_hosts = spec_obj.force_hosts or [] + force_nodes = spec_obj.force_nodes or [] if ignore_hosts or force_hosts or force_nodes: # NOTE(deva): we can't assume "host" is unique because @@ -523,12 +523,12 @@ class HostManager(object): hosts = six.itervalues(name_to_cls_map) return self.filter_handler.get_filtered_objects(filters, - hosts, filter_properties, index) + hosts, spec_obj, index) - def get_weighed_hosts(self, hosts, weight_properties): + def get_weighed_hosts(self, hosts, spec_obj): """Weigh the hosts.""" return self.weight_handler.get_weighed_objects(self.weighers, - hosts, weight_properties) + hosts, spec_obj) def get_all_host_states(self, context): """Returns a list of HostStates that represents all the hosts diff --git a/nova/tests/unit/scheduler/test_filter_scheduler.py b/nova/tests/unit/scheduler/test_filter_scheduler.py index 99ec63a5ef35..261b44af7cbe 100644 --- a/nova/tests/unit/scheduler/test_filter_scheduler.py +++ b/nova/tests/unit/scheduler/test_filter_scheduler.py @@ -74,7 +74,8 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase): os_type='Linux', uuid='fake-uuid', pci_requests=None, - numa_topology=None) + numa_topology=None, + instance_group=None) self.mox.ReplayAll() weighed_hosts = self.driver._schedule(self.context, spec_obj) self.assertEqual(len(weighed_hosts), 10) @@ -143,7 +144,8 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase): ephemeral_gb=0, vcpus=1), pci_requests=None, - numa_topology=None) + numa_topology=None, + instance_group=None) self.mox.ReplayAll() hosts = self.driver._schedule(self.context, spec_obj) @@ -178,7 +180,8 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase): ephemeral_gb=0, vcpus=1), pci_requests=None, - numa_topology=None) + numa_topology=None, + instance_group=None) self.mox.ReplayAll() hosts = self.driver._schedule(self.context, spec_obj) @@ -222,7 +225,8 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase): ephemeral_gb=0, vcpus=1), pci_requests=None, - numa_topology=None) + numa_topology=None, + instance_group=None) self.stubs.Set(weights.HostWeightHandler, 'get_weighed_objects', _fake_weigh_objects) diff --git a/nova/tests/unit/scheduler/test_filters.py b/nova/tests/unit/scheduler/test_filters.py index f4ec41a9d1d4..7f87b25d4bdc 100644 --- a/nova/tests/unit/scheduler/test_filters.py +++ b/nova/tests/unit/scheduler/test_filters.py @@ -23,6 +23,7 @@ from six.moves import range from nova import filters from nova import loadables +from nova import objects from nova import test @@ -46,18 +47,18 @@ class FiltersTestCase(test.NoDBTestCase): def test_filter_all(self): filter_obj_list = ['obj1', 'obj2', 'obj3'] - filter_properties = 'fake_filter_properties' + spec_obj = objects.RequestSpec() base_filter = filters.BaseFilter() self.mox.StubOutWithMock(base_filter, '_filter_one') - base_filter._filter_one('obj1', filter_properties).AndReturn(True) - base_filter._filter_one('obj2', filter_properties).AndReturn(False) - base_filter._filter_one('obj3', filter_properties).AndReturn(True) + base_filter._filter_one('obj1', spec_obj).AndReturn(True) + base_filter._filter_one('obj2', spec_obj).AndReturn(False) + base_filter._filter_one('obj3', spec_obj).AndReturn(True) self.mox.ReplayAll() - result = base_filter.filter_all(filter_obj_list, filter_properties) + result = base_filter.filter_all(filter_obj_list, spec_obj) self.assertTrue(inspect.isgenerator(result)) self.assertEqual(['obj1', 'obj3'], list(result)) @@ -67,7 +68,7 @@ class FiltersTestCase(test.NoDBTestCase): # call filter_all() with generators returned from previous calls # to filter_all(). filter_obj_list = ['obj1', 'obj2', 'obj3'] - filter_properties = 'fake_filter_properties' + spec_obj = objects.RequestSpec() base_filter = filters.BaseFilter() self.mox.StubOutWithMock(base_filter, '_filter_one') @@ -83,16 +84,16 @@ class FiltersTestCase(test.NoDBTestCase): # After that, 'obj3' gets yielded 'total_iterations' number of # times. for x in range(total_iterations): - base_filter._filter_one('obj1', filter_properties).AndReturn(True) - base_filter._filter_one('obj2', filter_properties).AndReturn(False) + base_filter._filter_one('obj1', spec_obj).AndReturn(True) + base_filter._filter_one('obj2', spec_obj).AndReturn(False) for x in range(total_iterations): - base_filter._filter_one('obj3', filter_properties).AndReturn(True) + base_filter._filter_one('obj3', spec_obj).AndReturn(True) self.mox.ReplayAll() objs = iter(filter_obj_list) for x in range(total_iterations): # Pass in generators returned from previous calls. - objs = base_filter.filter_all(objs, filter_properties) + objs = base_filter.filter_all(objs, spec_obj) self.assertTrue(inspect.isgenerator(objs)) self.assertEqual(['obj1', 'obj3'], list(objs)) @@ -100,7 +101,7 @@ class FiltersTestCase(test.NoDBTestCase): filter_objs_initial = ['initial', 'filter1', 'objects1'] filter_objs_second = ['second', 'filter2', 'objects2'] filter_objs_last = ['last', 'filter3', 'objects3'] - filter_properties = 'fake_filter_properties' + spec_obj = objects.RequestSpec() def _fake_base_loader_init(*args, **kwargs): pass @@ -122,10 +123,10 @@ class FiltersTestCase(test.NoDBTestCase): filt1_mock.run_filter_for_index(0).AndReturn(True) filt1_mock.filter_all(filter_objs_initial, - filter_properties).AndReturn(filter_objs_second) + spec_obj).AndReturn(filter_objs_second) filt2_mock.run_filter_for_index(0).AndReturn(True) filt2_mock.filter_all(filter_objs_second, - filter_properties).AndReturn(filter_objs_last) + spec_obj).AndReturn(filter_objs_last) self.mox.ReplayAll() @@ -133,7 +134,7 @@ class FiltersTestCase(test.NoDBTestCase): filter_mocks = [filt1_mock, filt2_mock] result = filter_handler.get_filtered_objects(filter_mocks, filter_objs_initial, - filter_properties) + spec_obj) self.assertEqual(filter_objs_last, result) def test_get_filtered_objects_for_index(self): @@ -142,7 +143,7 @@ class FiltersTestCase(test.NoDBTestCase): """ filter_objs_initial = ['initial', 'filter1', 'objects1'] filter_objs_second = ['second', 'filter2', 'objects2'] - filter_properties = 'fake_filter_properties' + spec_obj = objects.RequestSpec() def _fake_base_loader_init(*args, **kwargs): pass @@ -164,7 +165,7 @@ class FiltersTestCase(test.NoDBTestCase): filt1_mock.run_filter_for_index(0).AndReturn(True) filt1_mock.filter_all(filter_objs_initial, - filter_properties).AndReturn(filter_objs_second) + spec_obj).AndReturn(filter_objs_second) # return false so filter_all will not be called filt2_mock.run_filter_for_index(0).AndReturn(False) @@ -174,11 +175,11 @@ class FiltersTestCase(test.NoDBTestCase): filter_mocks = [filt1_mock, filt2_mock] filter_handler.get_filtered_objects(filter_mocks, filter_objs_initial, - filter_properties) + spec_obj) def test_get_filtered_objects_none_response(self): filter_objs_initial = ['initial', 'filter1', 'objects1'] - filter_properties = 'fake_filter_properties' + spec_obj = objects.RequestSpec() def _fake_base_loader_init(*args, **kwargs): pass @@ -200,26 +201,86 @@ class FiltersTestCase(test.NoDBTestCase): filt1_mock.run_filter_for_index(0).AndReturn(True) filt1_mock.filter_all(filter_objs_initial, - filter_properties).AndReturn(None) + spec_obj).AndReturn(None) self.mox.ReplayAll() filter_handler = filters.BaseFilterHandler(filters.BaseFilter) filter_mocks = [filt1_mock, filt2_mock] result = filter_handler.get_filtered_objects(filter_mocks, filter_objs_initial, - filter_properties) + spec_obj) self.assertIsNone(result) def test_get_filtered_objects_info_log_none_returned(self): LOG = filters.LOG class FilterA(filters.BaseFilter): - def filter_all(self, list_objs, filter_properties): + def filter_all(self, list_objs, spec_obj): # return all but the first object return list_objs[1:] class FilterB(filters.BaseFilter): - def filter_all(self, list_objs, filter_properties): + def filter_all(self, list_objs, spec_obj): + # return an empty list + return [] + + filter_a = FilterA() + filter_b = FilterB() + all_filters = [filter_a, filter_b] + hosts = ["Host0", "Host1", "Host2"] + fake_uuid = "uuid" + spec_obj = objects.RequestSpec(instance_uuid=fake_uuid) + with mock.patch.object(LOG, "info") as mock_log: + result = self.filter_handler.get_filtered_objects( + all_filters, hosts, spec_obj) + self.assertFalse(result) + # FilterA should leave Host1 and Host2; FilterB should leave None. + exp_output = ("['FilterA: (start: 3, end: 2)', " + "'FilterB: (start: 2, end: 0)']") + cargs = mock_log.call_args[0][0] + self.assertIn("with instance ID '%s'" % fake_uuid, cargs) + self.assertIn(exp_output, cargs) + + def test_get_filtered_objects_debug_log_none_returned(self): + LOG = filters.LOG + + class FilterA(filters.BaseFilter): + def filter_all(self, list_objs, spec_obj): + # return all but the first object + return list_objs[1:] + + class FilterB(filters.BaseFilter): + def filter_all(self, list_objs, spec_obj): + # return an empty list + return [] + + filter_a = FilterA() + filter_b = FilterB() + all_filters = [filter_a, filter_b] + hosts = ["Host0", "Host1", "Host2"] + fake_uuid = "uuid" + spec_obj = objects.RequestSpec(instance_uuid=fake_uuid) + with mock.patch.object(LOG, "debug") as mock_log: + result = self.filter_handler.get_filtered_objects( + all_filters, hosts, spec_obj) + self.assertFalse(result) + # FilterA should leave Host1 and Host2; FilterB should leave None. + exp_output = ("[('FilterA', [('Host1', ''), ('Host2', '')]), " + + "('FilterB', None)]") + cargs = mock_log.call_args[0][0] + self.assertIn("with instance ID '%s'" % fake_uuid, cargs) + self.assertIn(exp_output, cargs) + + def test_get_filtered_objects_compatible_with_filt_props_dicts(self): + LOG = filters.LOG + + class FilterA(filters.BaseFilter): + def filter_all(self, list_objs, spec_obj): + # return all but the first object + return list_objs[1:] + + class FilterB(filters.BaseFilter): + def filter_all(self, list_objs, spec_obj): # return an empty list return [] @@ -240,34 +301,3 @@ class FiltersTestCase(test.NoDBTestCase): cargs = mock_log.call_args[0][0] self.assertIn("with instance ID '%s'" % fake_uuid, cargs) self.assertIn(exp_output, cargs) - - def test_get_filtered_objects_debug_log_none_returned(self): - LOG = filters.LOG - - class FilterA(filters.BaseFilter): - def filter_all(self, list_objs, filter_properties): - # return all but the first object - return list_objs[1:] - - class FilterB(filters.BaseFilter): - def filter_all(self, list_objs, filter_properties): - # return an empty list - return [] - - filter_a = FilterA() - filter_b = FilterB() - all_filters = [filter_a, filter_b] - hosts = ["Host0", "Host1", "Host2"] - fake_uuid = "uuid" - filt_props = {"request_spec": {"instance_properties": { - "uuid": fake_uuid}}} - with mock.patch.object(LOG, "debug") as mock_log: - result = self.filter_handler.get_filtered_objects( - all_filters, hosts, filt_props) - self.assertFalse(result) - # FilterA should leave Host1 and Host2; FilterB should leave None. - exp_output = ("[('FilterA', [('Host1', ''), ('Host2', '')]), " + - "('FilterB', None)]") - cargs = mock_log.call_args[0][0] - self.assertIn("with instance ID '%s'" % fake_uuid, cargs) - self.assertIn(exp_output, cargs) diff --git a/nova/tests/unit/scheduler/test_host_filters.py b/nova/tests/unit/scheduler/test_host_filters.py index caed938aa31b..682d3a4e6ccb 100644 --- a/nova/tests/unit/scheduler/test_host_filters.py +++ b/nova/tests/unit/scheduler/test_host_filters.py @@ -14,7 +14,9 @@ """ Tests For Scheduler Host Filters. """ +import mock +from nova import objects from nova.scheduler import filters from nova.scheduler.filters import all_hosts_filter from nova.scheduler.filters import compute_filter @@ -36,3 +38,27 @@ class HostFiltersTestCase(test.NoDBTestCase): filt_cls = all_hosts_filter.AllHostsFilter() host = fakes.FakeHostState('host1', 'node1', {}) self.assertTrue(filt_cls.host_passes(host, {})) + + @mock.patch.object(objects.RequestSpec, 'to_legacy_request_spec_dict') + @mock.patch.object(objects.RequestSpec, 'to_legacy_filter_properties_dict') + def test_compat_legacy_props(self, to_props, to_spec): + fake_flavor = objects.Flavor() + fake_context = mock.Mock() + fake_spec = objects.RequestSpec(context=fake_context, + flavor=fake_flavor) + fake_spec.config_options = None + to_props.return_value = {'prop1': 'val1'} + to_spec.return_value = {'spec1': 'val2'} + + @filters.compat_legacy_props + def fake_host_passes(self, host_state, filter_properties): + # NOTE(sbauza): Convenient way to verify the passed properties + return filter_properties + + expected = {'prop1': 'val1', + 'request_spec': {'spec1': 'val2'}, + 'instance_type': fake_flavor, + 'context': fake_context, + 'config_options': None} + self.assertEqual(expected, + fake_host_passes('self', 'host_state', fake_spec)) diff --git a/nova/tests/unit/scheduler/test_host_manager.py b/nova/tests/unit/scheduler/test_host_manager.py index 99146352fc3d..b3bfa8dcde79 100644 --- a/nova/tests/unit/scheduler/test_host_manager.py +++ b/nova/tests/unit/scheduler/test_host_manager.py @@ -207,7 +207,10 @@ class HostManagerTestCase(test.NoDBTestCase): self.assertEqual(set(info['expected_objs']), set(result)) def test_get_filtered_hosts(self): - fake_properties = {'moo': 1, 'cow': 2} + fake_properties = objects.RequestSpec(ignore_hosts=[], + instance_uuid='fake-uuid1', + force_hosts=[], + force_nodes=[]) info = {'expected_objs': self.fake_hosts, 'expected_fprops': fake_properties} @@ -220,7 +223,10 @@ class HostManagerTestCase(test.NoDBTestCase): @mock.patch.object(FakeFilterClass2, '_filter_one', return_value=True) def test_get_filtered_hosts_with_specified_filters(self, mock_filter_one): - fake_properties = {'moo': 1, 'cow': 2} + fake_properties = objects.RequestSpec(ignore_hosts=[], + instance_uuid='fake-uuid1', + force_hosts=[], + force_nodes=[]) specified_filters = ['FakeFilterClass1', 'FakeFilterClass2'] info = {'expected_objs': self.fake_hosts, @@ -232,8 +238,12 @@ class HostManagerTestCase(test.NoDBTestCase): self._verify_result(info, result) def test_get_filtered_hosts_with_ignore(self): - fake_properties = {'ignore_hosts': ['fake_host1', 'fake_host3', - 'fake_host5', 'fake_multihost']} + fake_properties = objects.RequestSpec( + instance_uuid='fake-uuid1', + ignore_hosts=['fake_host1', 'fake_host3', + 'fake_host5', 'fake_multihost'], + force_hosts=[], + force_nodes=[]) # [1] and [3] are host2 and host4 info = {'expected_objs': [self.fake_hosts[1], self.fake_hosts[3]], @@ -245,8 +255,11 @@ class HostManagerTestCase(test.NoDBTestCase): self._verify_result(info, result) def test_get_filtered_hosts_with_force_hosts(self): - fake_properties = {'force_hosts': ['fake_host1', 'fake_host3', - 'fake_host5']} + fake_properties = objects.RequestSpec( + instance_uuid='fake-uuid1', + ignore_hosts=[], + force_hosts=['fake_host1', 'fake_host3', 'fake_host5'], + force_nodes=[]) # [0] and [2] are host1 and host3 info = {'expected_objs': [self.fake_hosts[0], self.fake_hosts[2]], @@ -258,7 +271,11 @@ class HostManagerTestCase(test.NoDBTestCase): self._verify_result(info, result, False) def test_get_filtered_hosts_with_no_matching_force_hosts(self): - fake_properties = {'force_hosts': ['fake_host5', 'fake_host6']} + fake_properties = objects.RequestSpec( + instance_uuid='fake-uuid1', + ignore_hosts=[], + force_hosts=['fake_host5', 'fake_host6'], + force_nodes=[]) info = {'expected_objs': [], 'expected_fprops': fake_properties} @@ -270,8 +287,11 @@ class HostManagerTestCase(test.NoDBTestCase): def test_get_filtered_hosts_with_ignore_and_force_hosts(self): # Ensure ignore_hosts processed before force_hosts in host filters. - fake_properties = {'force_hosts': ['fake_host3', 'fake_host1'], - 'ignore_hosts': ['fake_host1']} + fake_properties = objects.RequestSpec( + instance_uuid='fake-uuid1', + ignore_hosts=['fake_host1'], + force_hosts=['fake_host3', 'fake_host1'], + force_nodes=[]) # only fake_host3 should be left. info = {'expected_objs': [self.fake_hosts[2]], @@ -284,7 +304,11 @@ class HostManagerTestCase(test.NoDBTestCase): def test_get_filtered_hosts_with_force_host_and_many_nodes(self): # Ensure all nodes returned for a host with many nodes - fake_properties = {'force_hosts': ['fake_multihost']} + fake_properties = objects.RequestSpec( + instance_uuid='fake-uuid1', + ignore_hosts=[], + force_hosts=['fake_multihost'], + force_nodes=[]) info = {'expected_objs': [self.fake_hosts[4], self.fake_hosts[5], self.fake_hosts[6], self.fake_hosts[7]], @@ -296,8 +320,11 @@ class HostManagerTestCase(test.NoDBTestCase): self._verify_result(info, result, False) def test_get_filtered_hosts_with_force_nodes(self): - fake_properties = {'force_nodes': ['fake-node2', 'fake-node4', - 'fake-node9']} + fake_properties = objects.RequestSpec( + instance_uuid='fake-uuid1', + ignore_hosts=[], + force_hosts=[], + force_nodes=['fake-node2', 'fake-node4', 'fake-node9']) # [5] is fake-node2, [7] is fake-node4 info = {'expected_objs': [self.fake_hosts[5], self.fake_hosts[7]], @@ -310,8 +337,11 @@ class HostManagerTestCase(test.NoDBTestCase): def test_get_filtered_hosts_with_force_hosts_and_nodes(self): # Ensure only overlapping results if both force host and node - fake_properties = {'force_hosts': ['fake_host1', 'fake_multihost'], - 'force_nodes': ['fake-node2', 'fake-node9']} + fake_properties = objects.RequestSpec( + instance_uuid='fake-uuid1', + ignore_hosts=[], + force_hosts=['fake-host1', 'fake_multihost'], + force_nodes=['fake-node2', 'fake-node9']) # [5] is fake-node2 info = {'expected_objs': [self.fake_hosts[5]], @@ -324,8 +354,11 @@ class HostManagerTestCase(test.NoDBTestCase): def test_get_filtered_hosts_with_force_hosts_and_wrong_nodes(self): # Ensure non-overlapping force_node and force_host yield no result - fake_properties = {'force_hosts': ['fake_multihost'], - 'force_nodes': ['fake-node']} + fake_properties = objects.RequestSpec( + instance_uuid='fake-uuid1', + ignore_hosts=[], + force_hosts=['fake_multihost'], + force_nodes=['fake-node']) info = {'expected_objs': [], 'expected_fprops': fake_properties} @@ -337,8 +370,11 @@ class HostManagerTestCase(test.NoDBTestCase): def test_get_filtered_hosts_with_ignore_hosts_and_force_nodes(self): # Ensure ignore_hosts can coexist with force_nodes - fake_properties = {'force_nodes': ['fake-node4', 'fake-node2'], - 'ignore_hosts': ['fake_host1', 'fake_host2']} + fake_properties = objects.RequestSpec( + instance_uuid='fake-uuid1', + ignore_hosts=['fake_host1', 'fake_host2'], + force_hosts=[], + force_nodes=['fake-node4', 'fake-node2']) info = {'expected_objs': [self.fake_hosts[5], self.fake_hosts[7]], 'expected_fprops': fake_properties} @@ -350,8 +386,11 @@ class HostManagerTestCase(test.NoDBTestCase): def test_get_filtered_hosts_with_ignore_hosts_and_force_same_nodes(self): # Ensure ignore_hosts is processed before force_nodes - fake_properties = {'force_nodes': ['fake_node4', 'fake_node2'], - 'ignore_hosts': ['fake_multihost']} + fake_properties = objects.RequestSpec( + instance_uuid='fake-uuid1', + ignore_hosts=['fake_multihost'], + force_hosts=[], + force_nodes=['fake_node4', 'fake_node2']) info = {'expected_objs': [], 'expected_fprops': fake_properties} @@ -885,9 +924,9 @@ class HostStateTestCase(test.NoDBTestCase): numa_fit_mock.return_value = fake_numa_topology instance_init_mock.return_value = fake_instance spec_obj = objects.RequestSpec( + instance_uuid='fake-uuid', flavor=objects.Flavor(root_gb=0, ephemeral_gb=0, memory_mb=0, vcpus=0), - uuid='fake-uuid', numa_topology=fake_numa_topology, pci_requests=objects.InstancePCIRequests(requests=[])) host = host_manager.HostState("fakehost", "fakenode") @@ -905,9 +944,9 @@ class HostStateTestCase(test.NoDBTestCase): second_numa_topology = objects.InstanceNUMATopology( cells=[objects.InstanceNUMACell()]) spec_obj = objects.RequestSpec( + instance_uuid='fake-uuid', flavor=objects.Flavor(root_gb=0, ephemeral_gb=0, memory_mb=0, vcpus=0), - uuid='fake-uuid', numa_topology=second_numa_topology, pci_requests=objects.InstancePCIRequests(requests=[])) second_host_numa_topology = mock.Mock() @@ -936,6 +975,7 @@ class HostStateTestCase(test.NoDBTestCase): for r in fake_requests], instance_uuid='fake-uuid') req_spec = objects.RequestSpec( + instance_uuid='fake-uuid', project_id='12345', numa_topology=inst_topology, pci_requests=fake_requests_obj, @@ -968,6 +1008,7 @@ class HostStateTestCase(test.NoDBTestCase): for r in fake_requests], instance_uuid='fake-uuid') req_spec = objects.RequestSpec( + instance_uuid='fake-uuid', project_id='12345', numa_topology=None, pci_requests=fake_requests_obj, diff --git a/nova/tests/unit/scheduler/test_ironic_host_manager.py b/nova/tests/unit/scheduler/test_ironic_host_manager.py index 04f7d12b5cb8..631710a429ed 100644 --- a/nova/tests/unit/scheduler/test_ironic_host_manager.py +++ b/nova/tests/unit/scheduler/test_ironic_host_manager.py @@ -329,7 +329,11 @@ class IronicHostManagerTestFilters(test.NoDBTestCase): self.assertEqual(set(info['expected_objs']), set(result)) def test_get_filtered_hosts(self): - fake_properties = {'moo': 1, 'cow': 2} + fake_properties = objects.RequestSpec( + instance_uuid='fake-uuid', + ignore_hosts=[], + force_hosts=[], + force_nodes=[]) info = {'expected_objs': self.fake_hosts, 'expected_fprops': fake_properties} @@ -342,7 +346,11 @@ class IronicHostManagerTestFilters(test.NoDBTestCase): @mock.patch.object(FakeFilterClass2, '_filter_one', return_value=True) def test_get_filtered_hosts_with_specified_filters(self, mock_filter_one): - fake_properties = {'moo': 1, 'cow': 2} + fake_properties = objects.RequestSpec( + instance_uuid='fake-uuid', + ignore_hosts=[], + force_hosts=[], + force_nodes=[]) specified_filters = ['FakeFilterClass1', 'FakeFilterClass2'] info = {'expected_objs': self.fake_hosts, @@ -354,8 +362,12 @@ class IronicHostManagerTestFilters(test.NoDBTestCase): self._verify_result(info, result) def test_get_filtered_hosts_with_ignore(self): - fake_properties = {'ignore_hosts': ['fake_host1', 'fake_host3', - 'fake_host5', 'fake_multihost']} + fake_properties = objects.RequestSpec( + instance_uuid='fake-uuid', + ignore_hosts=['fake_host1', 'fake_host3', + 'fake_host5', 'fake_multihost'], + force_hosts=[], + force_nodes=[]) # [1] and [3] are host2 and host4 info = {'expected_objs': [self.fake_hosts[1], self.fake_hosts[3]], @@ -367,8 +379,11 @@ class IronicHostManagerTestFilters(test.NoDBTestCase): self._verify_result(info, result) def test_get_filtered_hosts_with_force_hosts(self): - fake_properties = {'force_hosts': ['fake_host1', 'fake_host3', - 'fake_host5']} + fake_properties = objects.RequestSpec( + instance_uuid='fake-uuid', + ignore_hosts=[], + force_hosts=['fake_host1', 'fake_host3', 'fake_host5'], + force_nodes=[]) # [0] and [2] are host1 and host3 info = {'expected_objs': [self.fake_hosts[0], self.fake_hosts[2]], @@ -380,7 +395,11 @@ class IronicHostManagerTestFilters(test.NoDBTestCase): self._verify_result(info, result, False) def test_get_filtered_hosts_with_no_matching_force_hosts(self): - fake_properties = {'force_hosts': ['fake_host5', 'fake_host6']} + fake_properties = objects.RequestSpec( + instance_uuid='fake-uuid', + ignore_hosts=[], + force_hosts=['fake_host5', 'fake_host6'], + force_nodes=[]) info = {'expected_objs': [], 'expected_fprops': fake_properties} @@ -392,8 +411,11 @@ class IronicHostManagerTestFilters(test.NoDBTestCase): def test_get_filtered_hosts_with_ignore_and_force_hosts(self): # Ensure ignore_hosts processed before force_hosts in host filters. - fake_properties = {'force_hosts': ['fake_host3', 'fake_host1'], - 'ignore_hosts': ['fake_host1']} + fake_properties = objects.RequestSpec( + instance_uuid='fake-uuid', + ignore_hosts=['fake_host1'], + force_hosts=['fake_host3', 'fake_host1'], + force_nodes=[]) # only fake_host3 should be left. info = {'expected_objs': [self.fake_hosts[2]], @@ -406,7 +428,11 @@ class IronicHostManagerTestFilters(test.NoDBTestCase): def test_get_filtered_hosts_with_force_host_and_many_nodes(self): # Ensure all nodes returned for a host with many nodes - fake_properties = {'force_hosts': ['fake_multihost']} + fake_properties = objects.RequestSpec( + instance_uuid='fake-uuid', + ignore_hosts=[], + force_hosts=['fake_multihost'], + force_nodes=[]) info = {'expected_objs': [self.fake_hosts[4], self.fake_hosts[5], self.fake_hosts[6], self.fake_hosts[7]], @@ -418,8 +444,11 @@ class IronicHostManagerTestFilters(test.NoDBTestCase): self._verify_result(info, result, False) def test_get_filtered_hosts_with_force_nodes(self): - fake_properties = {'force_nodes': ['fake-node2', 'fake-node4', - 'fake-node9']} + fake_properties = objects.RequestSpec( + instance_uuid='fake-uuid', + ignore_hosts=[], + force_hosts=[], + force_nodes=['fake-node2', 'fake-node4', 'fake-node9']) # [5] is fake-node2, [7] is fake-node4 info = {'expected_objs': [self.fake_hosts[5], self.fake_hosts[7]], @@ -432,8 +461,11 @@ class IronicHostManagerTestFilters(test.NoDBTestCase): def test_get_filtered_hosts_with_force_hosts_and_nodes(self): # Ensure only overlapping results if both force host and node - fake_properties = {'force_hosts': ['fake_host1', 'fake_multihost'], - 'force_nodes': ['fake-node2', 'fake-node9']} + fake_properties = objects.RequestSpec( + instance_uuid='fake-uuid', + ignore_hosts=[], + force_hosts=['fake_host1', 'fake_multihost'], + force_nodes=['fake-node2', 'fake-node9']) # [5] is fake-node2 info = {'expected_objs': [self.fake_hosts[5]], @@ -446,8 +478,11 @@ class IronicHostManagerTestFilters(test.NoDBTestCase): def test_get_filtered_hosts_with_force_hosts_and_wrong_nodes(self): # Ensure non-overlapping force_node and force_host yield no result - fake_properties = {'force_hosts': ['fake_multihost'], - 'force_nodes': ['fake-node']} + fake_properties = objects.RequestSpec( + instance_uuid='fake-uuid', + ignore_hosts=[], + force_hosts=['fake_multihost'], + force_nodes=['fake-node']) info = {'expected_objs': [], 'expected_fprops': fake_properties} @@ -459,8 +494,11 @@ class IronicHostManagerTestFilters(test.NoDBTestCase): def test_get_filtered_hosts_with_ignore_hosts_and_force_nodes(self): # Ensure ignore_hosts can coexist with force_nodes - fake_properties = {'force_nodes': ['fake-node4', 'fake-node2'], - 'ignore_hosts': ['fake_host1', 'fake_host2']} + fake_properties = objects.RequestSpec( + instance_uuid='fake-uuid', + ignore_hosts=['fake_host1', 'fake_host2'], + force_hosts=[], + force_nodes=['fake-node4', 'fake-node2']) info = {'expected_objs': [self.fake_hosts[5], self.fake_hosts[7]], 'expected_fprops': fake_properties} @@ -472,8 +510,11 @@ class IronicHostManagerTestFilters(test.NoDBTestCase): def test_get_filtered_hosts_with_ignore_hosts_and_force_same_nodes(self): # Ensure ignore_hosts is processed before force_nodes - fake_properties = {'force_nodes': ['fake_node4', 'fake_node2'], - 'ignore_hosts': ['fake_multihost']} + fake_properties = objects.RequestSpec( + instance_uuid='fake-uuid', + ignore_hosts=['fake_multihost'], + force_hosts=[], + force_nodes=['fake_node4', 'fake_node2']) info = {'expected_objs': [], 'expected_fprops': fake_properties} diff --git a/releasenotes/notes/filters_use_reqspec-9f92b9c0ead76093.yaml b/releasenotes/notes/filters_use_reqspec-9f92b9c0ead76093.yaml new file mode 100644 index 000000000000..297c2f828f92 --- /dev/null +++ b/releasenotes/notes/filters_use_reqspec-9f92b9c0ead76093.yaml @@ -0,0 +1,9 @@ +--- +upgrade: + - | + Filters internal interface changed using now the RequestSpec NovaObject + instead of an old filter_properties dictionary. + In case you run out-of-tree filters, you need to modify the host_passes() + method to accept a new RequestSpec object and modify the filter internals + to use that new object. You can see other in-tree filters for getting the + logic or ask for help in #openstack-nova IRC channel.