Introduce ComputeFilter
* Introduce ComputeFilter & its test cases. * Work with HostState class as well. * Move all fake class to fakes module. Change-Id: I4bcf9d7c51a566997b4f01ad7746993064d36b7c Closes-Bug: #1691587 Closes-Bug: #1690559
This commit is contained in:
parent
b295cf8d38
commit
5a8f531684
@ -29,6 +29,8 @@ There are many standard filter classes which may be used
|
||||
to host the instance are passed.
|
||||
* LabelFilter - filters hosts based on whether host has the CLI specified
|
||||
labels.
|
||||
* ComputeFilter - filters hosts that are operational and enabled. In general,
|
||||
you should always enable this filter.
|
||||
|
||||
Configuring Filters
|
||||
-------------------
|
||||
@ -45,7 +47,7 @@ The default values for these settings in zun.conf are:
|
||||
::
|
||||
|
||||
--filter_scheduler.available_filters=zun.scheduler.filters.all_filters
|
||||
--filter_scheduler.enabled_filters=RamFilter,CPUFilter
|
||||
--filter_scheduler.enabled_filters=RamFilter,CPUFilter,ComputeFilter
|
||||
|
||||
With this configuration, all filters in ``zun.scheduler.filters``
|
||||
would be available, and by default the RamFilter and CPUFilter would be
|
||||
|
@ -72,7 +72,7 @@ zun.database.migration_backend =
|
||||
|
||||
zun.scheduler.driver =
|
||||
chance_scheduler = zun.scheduler.chance_scheduler:ChanceScheduler
|
||||
fake_scheduler = zun.tests.unit.scheduler.fake_scheduler:FakeScheduler
|
||||
fake_scheduler = zun.tests.unit.scheduler.fakes:FakeScheduler
|
||||
filter_scheduler = zun.scheduler.filter_scheduler:FilterScheduler
|
||||
|
||||
zun.image.driver =
|
||||
|
@ -64,7 +64,8 @@ Related options:
|
||||
cfg.ListOpt("enabled_filters",
|
||||
default=[
|
||||
"CPUFilter",
|
||||
"RamFilter"
|
||||
"RamFilter",
|
||||
"ComputeFilter"
|
||||
],
|
||||
help="""
|
||||
Filters that the scheduler will use.
|
||||
|
@ -21,7 +21,6 @@ from zun.common import exception
|
||||
from zun.common.i18n import _
|
||||
import zun.conf
|
||||
from zun import objects
|
||||
from zun.pci import stats as pci_stats
|
||||
from zun.scheduler import driver
|
||||
from zun.scheduler import filters
|
||||
from zun.scheduler.host_state import HostState
|
||||
@ -44,10 +43,11 @@ class FilterScheduler(driver.Scheduler):
|
||||
|
||||
def _schedule(self, context, container, extra_spec):
|
||||
"""Picks a host according to filters."""
|
||||
hosts = self.hosts_up(context)
|
||||
services = self._get_services_by_host(context)
|
||||
nodes = objects.ComputeNode.list(context)
|
||||
hosts = services.keys()
|
||||
nodes = [node for node in nodes if node.hostname in hosts]
|
||||
host_states = self.get_all_host_state(nodes)
|
||||
host_states = self.get_all_host_state(nodes, services)
|
||||
hosts = self.filter_handler.get_filtered_objects(self.enabled_filters,
|
||||
host_states,
|
||||
container,
|
||||
@ -102,17 +102,18 @@ class FilterScheduler(driver.Scheduler):
|
||||
def _load_filters(self):
|
||||
return CONF.scheduler.enabled_filters
|
||||
|
||||
def get_all_host_state(self, nodes):
|
||||
def _get_services_by_host(self, context):
|
||||
"""Get a dict of services indexed by hostname"""
|
||||
return {service.host: service
|
||||
for service in objects.ZunService.list_by_binary(
|
||||
context,
|
||||
'zun-compute')}
|
||||
|
||||
def get_all_host_state(self, nodes, services):
|
||||
host_states = []
|
||||
for node in nodes:
|
||||
host_state = HostState(node.hostname)
|
||||
host_state.mem_total = node.mem_total
|
||||
host_state.mem_used = node.mem_used
|
||||
host_state.cpus = node.cpus
|
||||
host_state.cpu_used = node.cpu_used
|
||||
host_state.numa_topology = node.numa_topology
|
||||
host_state.labels = node.labels
|
||||
host_state.pci_stats = pci_stats.PciDeviceStats(
|
||||
stats=node.pci_device_pools)
|
||||
host_state.update(compute_node=node,
|
||||
service=services.get(node.hostname))
|
||||
host_states.append(host_state)
|
||||
return host_states
|
||||
|
47
zun/scheduler/filters/compute_filter.py
Normal file
47
zun/scheduler/filters/compute_filter.py
Normal file
@ -0,0 +1,47 @@
|
||||
# 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.log import logging
|
||||
|
||||
from zun.api import servicegroup
|
||||
from zun.scheduler import filters
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ComputeFilter(filters.BaseHostFilter):
|
||||
"""Filter on active Compute nodes"""
|
||||
|
||||
def __init__(self):
|
||||
self.servicegroup_api = servicegroup.ServiceGroup()
|
||||
super(ComputeFilter, self).__init__()
|
||||
|
||||
# Host state does not change within a request
|
||||
run_filter_once_per_request = True
|
||||
|
||||
def host_passes(self, host_state, container, extra_spec):
|
||||
"""Returns True for only active compute nodes"""
|
||||
service = host_state.service
|
||||
if service.disabled:
|
||||
LOG.debug('%(host_state)s is disabled, reason: %(reason)s',
|
||||
{'host_state': host_state,
|
||||
'reason': service.disabled_reason or 'Unknow'})
|
||||
return False
|
||||
else:
|
||||
if not self.servicegroup_api.service_is_up(service):
|
||||
LOG.warning('%(host_state)s has not been heard from in '
|
||||
'a while', {'host_state': host_state})
|
||||
return False
|
||||
return True
|
@ -10,6 +10,13 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_log.log import logging
|
||||
|
||||
from zun.common import utils
|
||||
from zun.pci import stats as pci_stats
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HostState(object):
|
||||
"""Mutable and immutable information tracked for a host.
|
||||
@ -28,6 +35,34 @@ class HostState(object):
|
||||
self.cpus = 0
|
||||
self.cpu_used = 0
|
||||
self.numa_topology = None
|
||||
self.labels = None
|
||||
self.pci_stats = None
|
||||
|
||||
# Resource oversubscription values for the compute host:
|
||||
self.limits = {}
|
||||
|
||||
def update(self, compute_node=None, service=None):
|
||||
"""Update information about a host"""
|
||||
@utils.synchronized((self.hostname, compute_node))
|
||||
def _locked_update(self, compute_node, service):
|
||||
if compute_node is not None:
|
||||
LOG.debug('Update host state from compute node: %s',
|
||||
compute_node)
|
||||
self._update_from_compute_node(compute_node)
|
||||
if service is not None:
|
||||
LOG.debug('Update host state with service: %s', service)
|
||||
self.service = service
|
||||
|
||||
return _locked_update(self, compute_node, service)
|
||||
|
||||
def _update_from_compute_node(self, compute_node):
|
||||
"""Update information about a host from a Compute object"""
|
||||
self.mem_total = compute_node.mem_total
|
||||
self.mem_free = compute_node.mem_free
|
||||
self.mem_used = compute_node.mem_used
|
||||
self.cpus = compute_node.cpus
|
||||
self.cpu_used = compute_node.cpu_used
|
||||
self.numa_topology = compute_node.numa_topology
|
||||
self.labels = compute_node.labels
|
||||
self.pci_stats = pci_stats.PciDeviceStats(
|
||||
stats=compute_node.pci_device_pools)
|
||||
|
@ -11,9 +11,26 @@
|
||||
# under the License.
|
||||
|
||||
from zun.scheduler import driver
|
||||
from zun.scheduler import host_state
|
||||
|
||||
|
||||
class FakeScheduler(driver.Scheduler):
|
||||
|
||||
def select_destinations(self, context, containers):
|
||||
return []
|
||||
|
||||
|
||||
class FakeHostState(host_state.HostState):
|
||||
def __init__(self, host, attribute_dict=None):
|
||||
super(FakeHostState, self).__init__(host)
|
||||
if attribute_dict:
|
||||
for (key, val) in attribute_dict.items():
|
||||
setattr(self, key, val)
|
||||
|
||||
|
||||
class FakeService(object):
|
||||
|
||||
def __init__(self, name, host, disabled=False):
|
||||
self.name = name
|
||||
self.host = host
|
||||
self.disabled = disabled
|
68
zun/tests/unit/scheduler/filters/test_compute_filter.py
Normal file
68
zun/tests/unit/scheduler/filters/test_compute_filter.py
Normal file
@ -0,0 +1,68 @@
|
||||
# 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 mock
|
||||
from oslo_utils import timeutils
|
||||
|
||||
from zun.common import context
|
||||
from zun import objects
|
||||
from zun.scheduler.filters import compute_filter
|
||||
from zun.tests import base
|
||||
from zun.tests.unit.scheduler import fakes
|
||||
|
||||
|
||||
@mock.patch('zun.api.servicegroup.ServiceGroup.service_is_up')
|
||||
class TestComputeFilter(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestComputeFilter, self).setUp()
|
||||
self.context = context.RequestContext('fake_user', 'fake_project')
|
||||
|
||||
def test_compute_filter_manual_disable(self, service_up_mock):
|
||||
filt_cls = compute_filter.ComputeFilter()
|
||||
container = objects.Container(self.context)
|
||||
extra_spec = {}
|
||||
service = objects.ZunService(self.context)
|
||||
service.disabled = True
|
||||
service.disabled_reason = 'This is a reason!'
|
||||
host = fakes.FakeHostState('host1',
|
||||
{'service': service})
|
||||
self.assertFalse(filt_cls.host_passes(host, container,
|
||||
extra_spec))
|
||||
self.assertFalse(service_up_mock.called)
|
||||
|
||||
def test_compute_filter_sgapi_passes(self, service_up_mock):
|
||||
filt_cls = compute_filter.ComputeFilter()
|
||||
container = objects.Container(self.context)
|
||||
service = objects.ZunService(self.context)
|
||||
service.disabled = False
|
||||
extra_spec = {}
|
||||
host = fakes.FakeHostState('host2',
|
||||
{'service': service})
|
||||
service_up_mock.return_value = True
|
||||
self.assertTrue(filt_cls.host_passes(host, container,
|
||||
extra_spec))
|
||||
service_up_mock.assert_called_once_with(service)
|
||||
|
||||
def test_compute_filter_sgapi_fails(self, service_up_mock):
|
||||
filts_cls = compute_filter.ComputeFilter()
|
||||
container = objects.Container(self.context)
|
||||
service = objects.ZunService(self.context)
|
||||
service.disabled = False
|
||||
service.updated_at = timeutils.utcnow()
|
||||
extra_spec = {}
|
||||
host = fakes.FakeHostState('host3',
|
||||
{'service': service})
|
||||
service_up_mock.return_value = False
|
||||
self.assertFalse(filts_cls.host_passes(host, container,
|
||||
extra_spec))
|
||||
service_up_mock.assert_called_once_with(service)
|
@ -13,8 +13,8 @@
|
||||
from zun.common import context
|
||||
from zun import objects
|
||||
from zun.scheduler.filters import cpu_filter
|
||||
from zun.scheduler.host_state import HostState
|
||||
from zun.tests import base
|
||||
from zun.tests.unit.scheduler import fakes
|
||||
|
||||
|
||||
class TestCPUFilter(base.TestCase):
|
||||
@ -27,7 +27,7 @@ class TestCPUFilter(base.TestCase):
|
||||
self.filt_cls = cpu_filter.CPUFilter()
|
||||
container = objects.Container(self.context)
|
||||
container.cpu = 5.0
|
||||
host = HostState('testhost')
|
||||
host = fakes.FakeHostState('testhost')
|
||||
host.cpus = 8
|
||||
host.cpu_used = 0.0
|
||||
extra_spec = {}
|
||||
@ -37,7 +37,7 @@ class TestCPUFilter(base.TestCase):
|
||||
self.filt_cls = cpu_filter.CPUFilter()
|
||||
container = objects.Container(self.context)
|
||||
container.cpu = 8.0
|
||||
host = HostState('testhost')
|
||||
host = fakes.FakeHostState('testhost')
|
||||
host.cpus = 5
|
||||
host.cpu_used = 2.0
|
||||
extra_spec = {}
|
||||
|
@ -13,8 +13,8 @@
|
||||
from zun.common import context
|
||||
from zun import objects
|
||||
from zun.scheduler.filters import ram_filter
|
||||
from zun.scheduler.host_state import HostState
|
||||
from zun.tests import base
|
||||
from zun.tests.unit.scheduler import fakes
|
||||
|
||||
|
||||
class TestRamFilter(base.TestCase):
|
||||
@ -27,7 +27,7 @@ class TestRamFilter(base.TestCase):
|
||||
self.filt_cls = ram_filter.RamFilter()
|
||||
container = objects.Container(self.context)
|
||||
container.memory = '1024M'
|
||||
host = HostState('testhost')
|
||||
host = fakes.FakeHostState('testhost')
|
||||
host.mem_total = 1024 * 128
|
||||
host.mem_used = 1024
|
||||
extra_spec = {}
|
||||
@ -37,7 +37,7 @@ class TestRamFilter(base.TestCase):
|
||||
self.filt_cls = ram_filter.RamFilter()
|
||||
container = objects.Container(self.context)
|
||||
container.memory = '4096M'
|
||||
host = HostState('testhost')
|
||||
host = fakes.FakeHostState('testhost')
|
||||
host.mem_total = 1024 * 128
|
||||
host.mem_used = 1024 * 127
|
||||
extra_spec = {}
|
||||
|
@ -17,7 +17,7 @@ from oslo_config import cfg
|
||||
from zun.scheduler import client as scheduler_client
|
||||
from zun.scheduler import filter_scheduler
|
||||
from zun.tests import base
|
||||
from zun.tests.unit.scheduler import fake_scheduler
|
||||
from zun.tests.unit.scheduler import fakes
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
@ -37,7 +37,7 @@ class SchedulerClientTestCase(base.TestCase):
|
||||
def test_init_using_custom_schedulerdriver(self):
|
||||
CONF.set_override('driver', 'fake_scheduler', group='scheduler')
|
||||
driver = self.client_cls().driver
|
||||
self.assertIsInstance(driver, fake_scheduler.FakeScheduler)
|
||||
self.assertIsInstance(driver, fakes.FakeScheduler)
|
||||
|
||||
@mock.patch('zun.scheduler.filter_scheduler.FilterScheduler'
|
||||
'.select_destinations')
|
||||
|
@ -12,19 +12,14 @@
|
||||
|
||||
import mock
|
||||
|
||||
from zun.api import servicegroup
|
||||
from zun.common import context
|
||||
from zun.common import exception
|
||||
from zun import objects
|
||||
from zun.scheduler import filter_scheduler
|
||||
from zun.tests import base
|
||||
from zun.tests.unit.db import utils
|
||||
|
||||
|
||||
class FakeService(object):
|
||||
|
||||
def __init__(self, name, host):
|
||||
self.name = name
|
||||
self.host = host
|
||||
from zun.tests.unit.scheduler.fakes import FakeService
|
||||
|
||||
|
||||
class FilterSchedulerTestCase(base.TestCase):
|
||||
@ -37,11 +32,13 @@ class FilterSchedulerTestCase(base.TestCase):
|
||||
self.context = context.RequestContext('fake_user', 'fake_project')
|
||||
self.driver = self.driver_cls()
|
||||
|
||||
@mock.patch.object(servicegroup.ServiceGroup, 'service_is_up')
|
||||
@mock.patch.object(objects.ComputeNode, 'list')
|
||||
@mock.patch.object(objects.ZunService, 'list_by_binary')
|
||||
@mock.patch('random.choice')
|
||||
def test_select_destinations(self, mock_random_choice,
|
||||
mock_list_by_binary, mock_compute_list):
|
||||
mock_list_by_binary, mock_compute_list,
|
||||
mock_service_is_up):
|
||||
all_services = [FakeService('service1', 'host1'),
|
||||
FakeService('service2', 'host2'),
|
||||
FakeService('service3', 'host3'),
|
||||
@ -60,6 +57,7 @@ class FilterSchedulerTestCase(base.TestCase):
|
||||
node1.cpu_used = 0.0
|
||||
node1.mem_total = 1024 * 128
|
||||
node1.mem_used = 1024 * 4
|
||||
node1.mem_free = 1024 * 124
|
||||
node1.hostname = 'host1'
|
||||
node1.numa_topology = None
|
||||
node1.labels = {}
|
||||
@ -69,6 +67,7 @@ class FilterSchedulerTestCase(base.TestCase):
|
||||
node2.cpu_used = 0.0
|
||||
node2.mem_total = 1024 * 128
|
||||
node2.mem_used = 1024 * 4
|
||||
node2.mem_free = 1024 * 124
|
||||
node2.hostname = 'host2'
|
||||
node2.numa_topology = None
|
||||
node2.labels = {}
|
||||
@ -78,6 +77,7 @@ class FilterSchedulerTestCase(base.TestCase):
|
||||
node3.cpu_used = 0.0
|
||||
node3.mem_total = 1024 * 128
|
||||
node3.mem_used = 1024 * 4
|
||||
node3.mem_free = 1024 * 124
|
||||
node3.hostname = 'host3'
|
||||
node3.numa_topology = None
|
||||
node3.labels = {}
|
||||
@ -87,6 +87,7 @@ class FilterSchedulerTestCase(base.TestCase):
|
||||
node4.cpu_used = 0.0
|
||||
node4.mem_total = 1024 * 128
|
||||
node4.mem_used = 1024 * 4
|
||||
node4.mem_free = 1024 * 124
|
||||
node4.hostname = 'host4'
|
||||
node4.numa_topology = None
|
||||
node4.labels = {}
|
||||
@ -97,6 +98,7 @@ class FilterSchedulerTestCase(base.TestCase):
|
||||
def side_effect(hosts):
|
||||
return hosts[2]
|
||||
mock_random_choice.side_effect = side_effect
|
||||
mock_service_is_up.return_value = True
|
||||
extra_spec = {}
|
||||
dests = self.driver.select_destinations(self.context, containers,
|
||||
extra_spec)
|
||||
|
@ -17,13 +17,13 @@ import mock
|
||||
|
||||
from zun import objects
|
||||
from zun.tests import base
|
||||
from zun.tests.unit.scheduler import fake_scheduler
|
||||
from zun.tests.unit.scheduler import fakes
|
||||
|
||||
|
||||
class SchedulerTestCase(base.TestCase):
|
||||
"""Test case for base scheduler driver class."""
|
||||
|
||||
driver_cls = fake_scheduler.FakeScheduler
|
||||
driver_cls = fakes.FakeScheduler
|
||||
|
||||
def setUp(self):
|
||||
super(SchedulerTestCase, self).setUp()
|
||||
|
Loading…
Reference in New Issue
Block a user