Skip to content

Commit

Permalink
SAP: Introduce an external scheduler filter
Browse files Browse the repository at this point in the history
This allows us to rely on an external http service to do the filtering,
on a potentially more complex rule-set.
Instead of filtering each host one-by-one, we want to filter them in
a single requests and therefor override the `BaseFilter.filter_all`
  • Loading branch information
auhlig committed Jul 11, 2024
1 parent dc4a02a commit 0b35204
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 0 deletions.
7 changes: 7 additions & 0 deletions nova/conf/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -947,6 +947,13 @@
Related options:
* ``[DEFAULT] bigvm_mb``
"""),

# External scheduler filter.
cfg.StrOpt("external_scheduler_api_url",
default="",
help="""
The API URL of the external scheduler. If not provided, the filter is skipped.
""")
]

Expand Down
78 changes: 78 additions & 0 deletions nova/scheduler/filters/external_scheduler_filter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Copyright 2024 SAP SE or an SAP affiliate company.
#
# 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

import nova.conf
from nova.scheduler import filters
from oslo_log import log as logging

LOG = logging.getLogger(__name__)
CONF = nova.conf.CONF


class ExternalSchedulerFilter(filters.BaseHostFilter):
"""Filter using an out of tree scheduler via a REST API."""

RUN_ON_REBUILD = False

def __init__(self):
super().__init__()
self.api_url = CONF.filter_scheduler.external_scheduler_api_url

def filter_all(self, filter_obj_list, spec_obj):
"""Yield objects that pass the filter."""
if not self.api_url:
LOG.debug(
"external_scheduler_api_url not configured. skipping filter"
)
return filter_obj_list

hosts_data = []
for obj in filter_obj_list:
hosts_data.append({
"name": obj.host,
"status": obj.status,
"vcpus_total": obj.vcpus_total,
"vcpus_used": obj.vcpus_used,
"memory_mb": obj.memory_mb,
"memory_mb_used": obj.memory_mb_used
})

spec_data = {
"vcpus": spec_obj.vcpus,
"memory_mb": spec_obj.memory_mb,
}

json_data = {
"hosts": hosts_data,
"request_spec": spec_data
}

try:
response = requests.post(
self.api_url, json=json_data, timeout=10
)
response.raise_for_status()
response_json = response.json()
valid_hosts = response_json.get('valid_hosts', [])
for obj in filter_obj_list:
if obj.host in valid_hosts:
yield obj

except requests.RequestException as e:
# Log an error if the request fails
LOG.error("Failed to query the external API: %s", e)
return filter_obj_list
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Copyright 2024 SAP SE or an SAP affiliate company.
#
# 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.

# Tests for external scheduler filter.

from unittest.mock import MagicMock
from unittest.mock import patch
from unittest.mock import sentinel

import nova.conf
from nova import objects
from nova.scheduler.filters import external_scheduler_filter
from nova import test
from nova.tests.unit.scheduler import fakes


CONF = nova.conf.CONF


class ExternalSchedulerFilterTestCase(test.NoDBTestCase):

def setUp(self):
super(test.NoDBTestCase, self).setUp()
CONF.set_override('external_scheduler_api_url',
'http://127.0.0.1:1234', 'filter_scheduler')

@patch('requests.post')
def test_filter_all(self, mock_post):
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = {
'valid_hosts': ['host1', 'host3']
}
mock_post.return_value = mock_response

host_attributes = {
"status": "up",
"vcpus_total": 100,
"vcpus_used": 10,
"memory_mb": 1000,
"memory_mb_used": 10,
}
all_hosts = [
fakes.FakeHostState('host1', 'node1', host_attributes),
fakes.FakeHostState('host2', 'node2', host_attributes),
fakes.FakeHostState('host3', 'node3', host_attributes)
]
instance_request_spec = objects.RequestSpec(
context=sentinel.ctx,
flavor=objects.Flavor(
name="small",
vcpus=4,
memory_mb=1024
),
)

f = external_scheduler_filter.ExternalSchedulerFilter()
valid_hosts = f.filter_all(all_hosts, instance_request_spec)
self.assertEqual(
['host1', 'host3'],
[h.host for h in valid_hosts]
)

0 comments on commit 0b35204

Please sign in to comment.