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:
Kien Nguyen 2017-11-09 15:37:08 +07:00
parent b295cf8d38
commit 5a8f531684
13 changed files with 206 additions and 33 deletions

View File

@ -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

View File

@ -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 =

View File

@ -64,7 +64,8 @@ Related options:
cfg.ListOpt("enabled_filters",
default=[
"CPUFilter",
"RamFilter"
"RamFilter",
"ComputeFilter"
],
help="""
Filters that the scheduler will use.

View File

@ -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

View 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

View File

@ -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)

View File

@ -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

View 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)

View File

@ -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 = {}

View File

@ -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 = {}

View File

@ -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')

View File

@ -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)

View File

@ -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()