Add capabilities filter for Nova

It turns out that the ComputeCapabilitiesFilter built-in to Nova
doesn't respect capabilities passed in scheduler_hints, so we can't
use it for predictable placement.  Adding this filter to the
undercloud Nova filter list will allow us to do so.

Instead of pulling in all of Nova as a test requirement, I've added
a fake_nova module to the source tree, which is injected as 'nova'
when unit tests are being run.  A check is included to make sure
nova isn't being imported for real, as well as a README explaining
the reasoning behind the fake_nova module.

Change-Id: I0618a3b9e3c33af7cdc78db4b6994d463b8aeda9
This commit is contained in:
Ben Nemec 2016-03-03 19:45:32 +00:00
parent 194d5171dd
commit f1c6ac9f2e
8 changed files with 152 additions and 0 deletions

View File

View File

@ -0,0 +1,35 @@
# Copyright 2016 Red Hat, Inc.
#
# 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 nova.scheduler import filters
class TripleOCapabilitiesFilter(filters.BaseHostFilter):
"""Filter hosts based on capabilities in boot request
The standard Nova ComputeCapabilitiesFilter does not respect capabilities
requested in the scheduler_hints field, so we need a custom one in order
to be able to do predictable placement of nodes.
"""
# list of hosts doesn't change within a request
run_filter_once_per_request = True
def host_passes(self, host_state, spec_obj):
host_node = host_state.stats.get('node')
instance_node = spec_obj.scheduler_hints.get('capabilities:node')
# The instance didn't request a specific node
if not instance_node:
return True
return host_node == instance_node[0]

View File

@ -0,0 +1,27 @@
# Copyright 2016 Red Hat, Inc.
#
# 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 nova
from tripleo_common.filters import capabilities_filter
def tripleo_filters():
"""Return a list of filter classes for TripleO
This is a wrapper around the Nova all_filters function so we can add our
filters to the resulting list.
"""
nova_filters = nova.scheduler.filters.all_filters()
return (nova_filters + [capabilities_filter.TripleOCapabilitiesFilter])

View File

@ -0,0 +1,4 @@
We don't want to pull in all of Nova and, more importantly, all of its
numerous dependencies just for the sake of having one class to inherit
from in our custom filter. Instead, this module will be injected into
sys.modules as 'nova' when we run unit tests that rely on it.

View File

@ -0,0 +1,18 @@
# Copyright 2016 Red Hat, Inc.
#
# 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.
class BaseHostFilter(object):
pass

View File

@ -0,0 +1,68 @@
# Copyright 2016 Red Hat, Inc.
#
# 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 sys
import mock
from tripleo_common.tests import base
from tripleo_common.tests import fake_nova
# See the README file in the fake_nova module directory for details on why
# this is being done.
if 'nova' not in sys.modules:
sys.modules['nova'] = fake_nova
else:
raise RuntimeError('nova module already found in sys.modules. The '
'fake_nova injection should be removed.')
from tripleo_common.filters import capabilities_filter
class TestCapabilitiesFilter(base.TestCase):
def test_no_requested_node(self):
instance = capabilities_filter.TripleOCapabilitiesFilter()
host_state = mock.Mock()
host_state.stats.get.return_value = ''
spec_obj = mock.Mock()
spec_obj.scheduler_hints.get.return_value = []
self.assertTrue(instance.host_passes(host_state, spec_obj))
def test_requested_node_matches(self):
def mock_host_get(key):
if key == 'node':
return 'compute-0'
else:
self.fail('Unexpected key requested by filter')
def mock_spec_get(key):
if key == 'capabilities:node':
return ['compute-0']
else:
self.fail('Unexpected key requested by filter')
instance = capabilities_filter.TripleOCapabilitiesFilter()
host_state = mock.Mock()
host_state.stats.get.side_effect = mock_host_get
spec_obj = mock.Mock()
spec_obj.scheduler_hints.get.side_effect = mock_spec_get
self.assertTrue(instance.host_passes(host_state, spec_obj))
def test_requested_node_no_match(self):
instance = capabilities_filter.TripleOCapabilitiesFilter()
host_state = mock.Mock()
host_state.stats.get.return_value = 'controller-0'
spec_obj = mock.Mock()
spec_obj.scheduler_hints.get.return_value = ['compute-0']
self.assertFalse(instance.host_passes(host_state, spec_obj))