Add CPUFilter for filter scheduler
Add CPUFilter for filter scheduler Change-Id: I52091780cb5dff8b40666e90fed90f0ae5b18ebf Partially-Implements: blueprint import-nova-filter-scheduler
This commit is contained in:
@@ -64,6 +64,7 @@ Related options:
|
|||||||
cfg.ListOpt("enabled_filters",
|
cfg.ListOpt("enabled_filters",
|
||||||
default=[
|
default=[
|
||||||
"NoopFilter",
|
"NoopFilter",
|
||||||
|
"CPUFilter"
|
||||||
],
|
],
|
||||||
help="""
|
help="""
|
||||||
Filters that the scheduler will use.
|
Filters that the scheduler will use.
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import random
|
|||||||
from zun.common import exception
|
from zun.common import exception
|
||||||
from zun.common.i18n import _
|
from zun.common.i18n import _
|
||||||
import zun.conf
|
import zun.conf
|
||||||
|
from zun import objects
|
||||||
from zun.scheduler import driver
|
from zun.scheduler import driver
|
||||||
from zun.scheduler import filters
|
from zun.scheduler import filters
|
||||||
|
|
||||||
@@ -42,21 +43,23 @@ class FilterScheduler(driver.Scheduler):
|
|||||||
def _schedule(self, context, container):
|
def _schedule(self, context, container):
|
||||||
"""Picks a host according to filters."""
|
"""Picks a host according to filters."""
|
||||||
hosts = self.hosts_up(context)
|
hosts = self.hosts_up(context)
|
||||||
hosts = self.filter_handler.get_filtered_objects(self.enabled_filters,
|
nodes = objects.ComputeNode.list(context)
|
||||||
hosts,
|
nodes = [node for node in nodes if node.hostname in hosts]
|
||||||
|
nodes = self.filter_handler.get_filtered_objects(self.enabled_filters,
|
||||||
|
nodes,
|
||||||
container)
|
container)
|
||||||
if not hosts:
|
if not nodes:
|
||||||
msg = _("Is the appropriate service running?")
|
msg = _("Is the appropriate service running?")
|
||||||
raise exception.NoValidHost(reason=msg)
|
raise exception.NoValidHost(reason=msg)
|
||||||
|
|
||||||
return random.choice(hosts)
|
return random.choice(nodes)
|
||||||
|
|
||||||
def select_destinations(self, context, containers):
|
def select_destinations(self, context, containers):
|
||||||
"""Selects destinations by filters."""
|
"""Selects destinations by filters."""
|
||||||
dests = []
|
dests = []
|
||||||
for container in containers:
|
for container in containers:
|
||||||
host = self._schedule(context, container)
|
node = self._schedule(context, container)
|
||||||
host_state = dict(host=host, nodename=None, limits=None)
|
host_state = dict(host=node.hostname, nodename=None, limits=None)
|
||||||
dests.append(host_state)
|
dests.append(host_state)
|
||||||
|
|
||||||
if len(dests) < 1:
|
if len(dests) < 1:
|
||||||
|
|||||||
38
zun/scheduler/filters/cpu_filter.py
Normal file
38
zun/scheduler/filters/cpu_filter.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# Copyright (c) 2017 OpenStack Foundation
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from zun.scheduler import filters
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class CPUFilter(filters.BaseHostFilter):
|
||||||
|
"""Filter the containers by cpu request"""
|
||||||
|
|
||||||
|
run_filter_once_per_request = True
|
||||||
|
|
||||||
|
def host_passes(self, host_state, container):
|
||||||
|
cpu_free = host_state.cpus - host_state.cpu_used
|
||||||
|
if cpu_free < container.cpu:
|
||||||
|
LOG.debug("%(host_state)s does not have %(container_vcpus).2f "
|
||||||
|
"usable vcpus, it only has %(free_vcpus).2f usable "
|
||||||
|
"vcpus",
|
||||||
|
{'host_state': host_state,
|
||||||
|
'container_vcpus': container.cpu,
|
||||||
|
'free_vcpus': cpu_free})
|
||||||
|
return False
|
||||||
|
return True
|
||||||
0
zun/tests/unit/scheduler/filters/__init__.py
Normal file
0
zun/tests/unit/scheduler/filters/__init__.py
Normal file
41
zun/tests/unit/scheduler/filters/test_cpu_filter.py
Normal file
41
zun/tests/unit/scheduler/filters/test_cpu_filter.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# 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 zun.common import context
|
||||||
|
from zun import objects
|
||||||
|
from zun.scheduler.filters import cpu_filter
|
||||||
|
from zun.tests import base
|
||||||
|
|
||||||
|
|
||||||
|
class TestCPUFilter(base.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestCPUFilter, self).setUp()
|
||||||
|
self.context = context.RequestContext('fake_user', 'fake_project')
|
||||||
|
|
||||||
|
def test_cpu_filter_pass(self):
|
||||||
|
self.filt_cls = cpu_filter.CPUFilter()
|
||||||
|
container = objects.Container(self.context)
|
||||||
|
container.cpu = 5.0
|
||||||
|
host = objects.ComputeNode(self.context)
|
||||||
|
host.cpus = 8
|
||||||
|
host.cpu_used = 0.0
|
||||||
|
self.assertTrue(self.filt_cls.host_passes(host, container))
|
||||||
|
|
||||||
|
def test_cpu_filter_fail(self):
|
||||||
|
self.filt_cls = cpu_filter.CPUFilter()
|
||||||
|
container = objects.Container(self.context)
|
||||||
|
container.cpu = 8.0
|
||||||
|
host = objects.ComputeNode(self.context)
|
||||||
|
host.cpus = 5
|
||||||
|
host.cpu_used = 2.0
|
||||||
|
self.assertFalse(self.filt_cls.host_passes(host, container))
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
|
from zun.common import context
|
||||||
from zun.common import exception
|
from zun.common import exception
|
||||||
from zun import objects
|
from zun import objects
|
||||||
from zun.scheduler import filter_scheduler
|
from zun.scheduler import filter_scheduler
|
||||||
@@ -33,27 +34,46 @@ class FilterSchedulerTestCase(base.TestCase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(FilterSchedulerTestCase, self).setUp()
|
super(FilterSchedulerTestCase, self).setUp()
|
||||||
|
self.context = context.RequestContext('fake_user', 'fake_project')
|
||||||
self.driver = self.driver_cls()
|
self.driver = self.driver_cls()
|
||||||
|
|
||||||
|
@mock.patch.object(objects.ComputeNode, 'list')
|
||||||
@mock.patch.object(objects.ZunService, 'list_by_binary')
|
@mock.patch.object(objects.ZunService, 'list_by_binary')
|
||||||
@mock.patch('random.choice')
|
@mock.patch('random.choice')
|
||||||
def test_select_destinations(self, mock_random_choice,
|
def test_select_destinations(self, mock_random_choice,
|
||||||
mock_list_by_binary):
|
mock_list_by_binary, mock_compute_list):
|
||||||
all_services = [FakeService('service1', 'host1'),
|
all_services = [FakeService('service1', 'host1'),
|
||||||
FakeService('service2', 'host2'),
|
FakeService('service2', 'host2'),
|
||||||
FakeService('service3', 'host3'),
|
FakeService('service3', 'host3'),
|
||||||
FakeService('service4', 'host4')]
|
FakeService('service4', 'host4')]
|
||||||
all_hosts = ['host1', 'host2', 'host3', 'host4']
|
|
||||||
|
|
||||||
def _return_services(*args, **kwargs):
|
def _return_services(*args, **kwargs):
|
||||||
return all_services
|
return all_services
|
||||||
|
|
||||||
mock_random_choice.side_effect = ['host3']
|
|
||||||
self.driver.servicegroup_api.service_is_up = mock.Mock(
|
self.driver.servicegroup_api.service_is_up = mock.Mock(
|
||||||
return_value=True)
|
return_value=True)
|
||||||
mock_list_by_binary.side_effect = _return_services
|
mock_list_by_binary.side_effect = _return_services
|
||||||
test_container = utils.get_test_container()
|
test_container = utils.get_test_container()
|
||||||
containers = [objects.Container(self.context, **test_container)]
|
containers = [objects.Container(self.context, **test_container)]
|
||||||
|
node1 = objects.ComputeNode(self.context)
|
||||||
|
node1.cpus = 48
|
||||||
|
node1.cpu_used = 0.0
|
||||||
|
node1.hostname = 'host1'
|
||||||
|
node2 = objects.ComputeNode(self.context)
|
||||||
|
node2.cpus = 48
|
||||||
|
node2.cpu_used = 0.0
|
||||||
|
node2.hostname = 'host2'
|
||||||
|
node3 = objects.ComputeNode(self.context)
|
||||||
|
node3.cpus = 48
|
||||||
|
node3.cpu_used = 0.0
|
||||||
|
node3.hostname = 'host3'
|
||||||
|
node4 = objects.ComputeNode(self.context)
|
||||||
|
node4.cpus = 48
|
||||||
|
node4.cpu_used = 0.0
|
||||||
|
node4.hostname = 'host4'
|
||||||
|
nodes = [node1, node2, node3, node4]
|
||||||
|
mock_compute_list.return_value = nodes
|
||||||
|
mock_random_choice.side_effect = [node3]
|
||||||
dests = self.driver.select_destinations(self.context, containers)
|
dests = self.driver.select_destinations(self.context, containers)
|
||||||
|
|
||||||
self.assertEqual(1, len(dests))
|
self.assertEqual(1, len(dests))
|
||||||
@@ -61,13 +81,15 @@ class FilterSchedulerTestCase(base.TestCase):
|
|||||||
self.assertEqual('host3', host)
|
self.assertEqual('host3', host)
|
||||||
self.assertIsNone(node)
|
self.assertIsNone(node)
|
||||||
|
|
||||||
calls = [mock.call(all_hosts)]
|
calls = [mock.call(nodes)]
|
||||||
self.assertEqual(calls, mock_random_choice.call_args_list)
|
self.assertEqual(calls, mock_random_choice.call_args_list)
|
||||||
|
|
||||||
|
@mock.patch.object(objects.ComputeNode, 'list')
|
||||||
@mock.patch.object(objects.ZunService, 'list_by_binary')
|
@mock.patch.object(objects.ZunService, 'list_by_binary')
|
||||||
@mock.patch('random.choice')
|
@mock.patch('random.choice')
|
||||||
def test_select_destinations_no_valid_host(self, mock_random_choice,
|
def test_select_destinations_no_valid_host(self, mock_random_choice,
|
||||||
mock_list_by_binary):
|
mock_list_by_binary,
|
||||||
|
mock_compute_list):
|
||||||
|
|
||||||
def _return_services(*args, **kwargs):
|
def _return_services(*args, **kwargs):
|
||||||
return []
|
return []
|
||||||
|
|||||||
Reference in New Issue
Block a user