Merge "Introduce ComputeFilter"

This commit is contained in:
Zuul 2017-11-24 01:18:38 +00:00 committed by Gerrit Code Review
commit c76752410a
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. to host the instance are passed.
* LabelFilter - filters hosts based on whether host has the CLI specified * LabelFilter - filters hosts based on whether host has the CLI specified
labels. labels.
* ComputeFilter - filters hosts that are operational and enabled. In general,
you should always enable this filter.
Configuring Filters 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.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`` With this configuration, all filters in ``zun.scheduler.filters``
would be available, and by default the RamFilter and CPUFilter would be 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 = zun.scheduler.driver =
chance_scheduler = zun.scheduler.chance_scheduler:ChanceScheduler 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 filter_scheduler = zun.scheduler.filter_scheduler:FilterScheduler
zun.image.driver = zun.image.driver =

View File

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

View File

@ -21,7 +21,6 @@ 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 import objects
from zun.pci import stats as pci_stats
from zun.scheduler import driver from zun.scheduler import driver
from zun.scheduler import filters from zun.scheduler import filters
from zun.scheduler.host_state import HostState from zun.scheduler.host_state import HostState
@ -44,10 +43,11 @@ class FilterScheduler(driver.Scheduler):
def _schedule(self, context, container, extra_spec): def _schedule(self, context, container, extra_spec):
"""Picks a host according to filters.""" """Picks a host according to filters."""
hosts = self.hosts_up(context) services = self._get_services_by_host(context)
nodes = objects.ComputeNode.list(context) nodes = objects.ComputeNode.list(context)
hosts = services.keys()
nodes = [node for node in nodes if node.hostname in hosts] 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, hosts = self.filter_handler.get_filtered_objects(self.enabled_filters,
host_states, host_states,
container, container,
@ -102,17 +102,18 @@ class FilterScheduler(driver.Scheduler):
def _load_filters(self): def _load_filters(self):
return CONF.scheduler.enabled_filters 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 = [] host_states = []
for node in nodes: for node in nodes:
host_state = HostState(node.hostname) host_state = HostState(node.hostname)
host_state.mem_total = node.mem_total host_state.update(compute_node=node,
host_state.mem_used = node.mem_used service=services.get(node.hostname))
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_states.append(host_state) host_states.append(host_state)
return host_states 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 # License for the specific language governing permissions and limitations
# under the License. # 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): class HostState(object):
"""Mutable and immutable information tracked for a host. """Mutable and immutable information tracked for a host.
@ -28,6 +35,34 @@ class HostState(object):
self.cpus = 0 self.cpus = 0
self.cpu_used = 0 self.cpu_used = 0
self.numa_topology = None self.numa_topology = None
self.labels = None
self.pci_stats = None
# Resource oversubscription values for the compute host: # Resource oversubscription values for the compute host:
self.limits = {} 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. # under the License.
from zun.scheduler import driver from zun.scheduler import driver
from zun.scheduler import host_state
class FakeScheduler(driver.Scheduler): class FakeScheduler(driver.Scheduler):
def select_destinations(self, context, containers): def select_destinations(self, context, containers):
return [] 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.common import context
from zun import objects from zun import objects
from zun.scheduler.filters import cpu_filter from zun.scheduler.filters import cpu_filter
from zun.scheduler.host_state import HostState
from zun.tests import base from zun.tests import base
from zun.tests.unit.scheduler import fakes
class TestCPUFilter(base.TestCase): class TestCPUFilter(base.TestCase):
@ -27,7 +27,7 @@ class TestCPUFilter(base.TestCase):
self.filt_cls = cpu_filter.CPUFilter() self.filt_cls = cpu_filter.CPUFilter()
container = objects.Container(self.context) container = objects.Container(self.context)
container.cpu = 5.0 container.cpu = 5.0
host = HostState('testhost') host = fakes.FakeHostState('testhost')
host.cpus = 8 host.cpus = 8
host.cpu_used = 0.0 host.cpu_used = 0.0
extra_spec = {} extra_spec = {}
@ -37,7 +37,7 @@ class TestCPUFilter(base.TestCase):
self.filt_cls = cpu_filter.CPUFilter() self.filt_cls = cpu_filter.CPUFilter()
container = objects.Container(self.context) container = objects.Container(self.context)
container.cpu = 8.0 container.cpu = 8.0
host = HostState('testhost') host = fakes.FakeHostState('testhost')
host.cpus = 5 host.cpus = 5
host.cpu_used = 2.0 host.cpu_used = 2.0
extra_spec = {} extra_spec = {}

View File

@ -13,8 +13,8 @@
from zun.common import context from zun.common import context
from zun import objects from zun import objects
from zun.scheduler.filters import ram_filter from zun.scheduler.filters import ram_filter
from zun.scheduler.host_state import HostState
from zun.tests import base from zun.tests import base
from zun.tests.unit.scheduler import fakes
class TestRamFilter(base.TestCase): class TestRamFilter(base.TestCase):
@ -27,7 +27,7 @@ class TestRamFilter(base.TestCase):
self.filt_cls = ram_filter.RamFilter() self.filt_cls = ram_filter.RamFilter()
container = objects.Container(self.context) container = objects.Container(self.context)
container.memory = '1024M' container.memory = '1024M'
host = HostState('testhost') host = fakes.FakeHostState('testhost')
host.mem_total = 1024 * 128 host.mem_total = 1024 * 128
host.mem_used = 1024 host.mem_used = 1024
extra_spec = {} extra_spec = {}
@ -37,7 +37,7 @@ class TestRamFilter(base.TestCase):
self.filt_cls = ram_filter.RamFilter() self.filt_cls = ram_filter.RamFilter()
container = objects.Container(self.context) container = objects.Container(self.context)
container.memory = '4096M' container.memory = '4096M'
host = HostState('testhost') host = fakes.FakeHostState('testhost')
host.mem_total = 1024 * 128 host.mem_total = 1024 * 128
host.mem_used = 1024 * 127 host.mem_used = 1024 * 127
extra_spec = {} 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 client as scheduler_client
from zun.scheduler import filter_scheduler from zun.scheduler import filter_scheduler
from zun.tests import base from zun.tests import base
from zun.tests.unit.scheduler import fake_scheduler from zun.tests.unit.scheduler import fakes
CONF = cfg.CONF CONF = cfg.CONF
@ -37,7 +37,7 @@ class SchedulerClientTestCase(base.TestCase):
def test_init_using_custom_schedulerdriver(self): def test_init_using_custom_schedulerdriver(self):
CONF.set_override('driver', 'fake_scheduler', group='scheduler') CONF.set_override('driver', 'fake_scheduler', group='scheduler')
driver = self.client_cls().driver driver = self.client_cls().driver
self.assertIsInstance(driver, fake_scheduler.FakeScheduler) self.assertIsInstance(driver, fakes.FakeScheduler)
@mock.patch('zun.scheduler.filter_scheduler.FilterScheduler' @mock.patch('zun.scheduler.filter_scheduler.FilterScheduler'
'.select_destinations') '.select_destinations')

View File

@ -12,19 +12,14 @@
import mock import mock
from zun.api import servicegroup
from zun.common import context 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
from zun.tests import base from zun.tests import base
from zun.tests.unit.db import utils from zun.tests.unit.db import utils
from zun.tests.unit.scheduler.fakes import FakeService
class FakeService(object):
def __init__(self, name, host):
self.name = name
self.host = host
class FilterSchedulerTestCase(base.TestCase): class FilterSchedulerTestCase(base.TestCase):
@ -37,11 +32,13 @@ class FilterSchedulerTestCase(base.TestCase):
self.context = context.RequestContext('fake_user', 'fake_project') self.context = context.RequestContext('fake_user', 'fake_project')
self.driver = self.driver_cls() self.driver = self.driver_cls()
@mock.patch.object(servicegroup.ServiceGroup, 'service_is_up')
@mock.patch.object(objects.ComputeNode, '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(self, mock_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'), all_services = [FakeService('service1', 'host1'),
FakeService('service2', 'host2'), FakeService('service2', 'host2'),
FakeService('service3', 'host3'), FakeService('service3', 'host3'),
@ -60,6 +57,7 @@ class FilterSchedulerTestCase(base.TestCase):
node1.cpu_used = 0.0 node1.cpu_used = 0.0
node1.mem_total = 1024 * 128 node1.mem_total = 1024 * 128
node1.mem_used = 1024 * 4 node1.mem_used = 1024 * 4
node1.mem_free = 1024 * 124
node1.hostname = 'host1' node1.hostname = 'host1'
node1.numa_topology = None node1.numa_topology = None
node1.labels = {} node1.labels = {}
@ -69,6 +67,7 @@ class FilterSchedulerTestCase(base.TestCase):
node2.cpu_used = 0.0 node2.cpu_used = 0.0
node2.mem_total = 1024 * 128 node2.mem_total = 1024 * 128
node2.mem_used = 1024 * 4 node2.mem_used = 1024 * 4
node2.mem_free = 1024 * 124
node2.hostname = 'host2' node2.hostname = 'host2'
node2.numa_topology = None node2.numa_topology = None
node2.labels = {} node2.labels = {}
@ -78,6 +77,7 @@ class FilterSchedulerTestCase(base.TestCase):
node3.cpu_used = 0.0 node3.cpu_used = 0.0
node3.mem_total = 1024 * 128 node3.mem_total = 1024 * 128
node3.mem_used = 1024 * 4 node3.mem_used = 1024 * 4
node3.mem_free = 1024 * 124
node3.hostname = 'host3' node3.hostname = 'host3'
node3.numa_topology = None node3.numa_topology = None
node3.labels = {} node3.labels = {}
@ -87,6 +87,7 @@ class FilterSchedulerTestCase(base.TestCase):
node4.cpu_used = 0.0 node4.cpu_used = 0.0
node4.mem_total = 1024 * 128 node4.mem_total = 1024 * 128
node4.mem_used = 1024 * 4 node4.mem_used = 1024 * 4
node4.mem_free = 1024 * 124
node4.hostname = 'host4' node4.hostname = 'host4'
node4.numa_topology = None node4.numa_topology = None
node4.labels = {} node4.labels = {}
@ -97,6 +98,7 @@ class FilterSchedulerTestCase(base.TestCase):
def side_effect(hosts): def side_effect(hosts):
return hosts[2] return hosts[2]
mock_random_choice.side_effect = side_effect mock_random_choice.side_effect = side_effect
mock_service_is_up.return_value = True
extra_spec = {} extra_spec = {}
dests = self.driver.select_destinations(self.context, containers, dests = self.driver.select_destinations(self.context, containers,
extra_spec) extra_spec)

View File

@ -17,13 +17,13 @@ import mock
from zun import objects from zun import objects
from zun.tests import base from zun.tests import base
from zun.tests.unit.scheduler import fake_scheduler from zun.tests.unit.scheduler import fakes
class SchedulerTestCase(base.TestCase): class SchedulerTestCase(base.TestCase):
"""Test case for base scheduler driver class.""" """Test case for base scheduler driver class."""
driver_cls = fake_scheduler.FakeScheduler driver_cls = fakes.FakeScheduler
def setUp(self): def setUp(self):
super(SchedulerTestCase, self).setUp() super(SchedulerTestCase, self).setUp()