diff --git a/nova/scheduler/external.py b/nova/scheduler/external.py new file mode 100644 index 00000000000..4b1cb7ff9a2 --- /dev/null +++ b/nova/scheduler/external.py @@ -0,0 +1,43 @@ +# Copyright (c) 2025 SAP SE +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" + +""" +import requests +from oslo_log import log as logging + +LOG = logging.getLogger(__name__) + + +def call_external_scheduler_api(host_objs, spec_obj, url): + """Reorder and filter hosts using an external scheduler service.""" + if not url or not host_objs: + return host_objs + # TODO: Check utils.request_is_rebuild(spec) here? + json_data = { + "hosts": [ o.obj_to_primitive() for o in host_objs ], + "request_spec": spec_obj.obj_to_primitive(), + } + try: + response = requests.post(url, json=json_data, timeout=10) + response.raise_for_status() + except requests.RequestException as e: + LOG.error("Failed to call external scheduler API: %s", e) + return host_objs + response_json = response.json() + # We expect the service to return an ordered list + # of host names. This list can also be empty. + host_names = response_json.get("hosts", []) + return [ o for o in host_objs if o.host in host_names ] diff --git a/nova/scheduler/manager.py b/nova/scheduler/manager.py index 3d350c5bc63..e238e4fc23e 100644 --- a/nova/scheduler/manager.py +++ b/nova/scheduler/manager.py @@ -38,6 +38,7 @@ from nova import quota from nova import rpc from nova.scheduler.client import report +from nova.scheduler.external import call_external_scheduler_api from nova.scheduler import host_manager from nova.scheduler import request_filter from nova.scheduler import utils @@ -633,6 +634,12 @@ def _get_sorted_hosts(self, spec_obj, host_states, index): # Strip off the WeighedHost wrapper class... weighed_hosts = [h.obj for h in weighed_hosts] + # Call an external service that can modify `weighed_hosts` once more. + # This service may filter out some hosts, or it may re-order them. + if url := CONF.filter_scheduler.external_scheduler_api_url: + weighed_hosts = call_external_scheduler_api( + weighed_hosts, spec_obj, url) + # We randomize the first element in the returned list to alleviate # congestion where the same host is consistently selected among # numerous potential hosts for similar request specs.