Merge "Revert "Added new scheduler filter: AggregateTypeExtraSpecsAffinityFilter""
This commit is contained in:
commit
c8a8963eed
@ -168,24 +168,6 @@ There are many standard filter classes which may be used
|
|||||||
the available metrics are passed.
|
the available metrics are passed.
|
||||||
* |NUMATopologyFilter| - filters hosts based on the NUMA topology requested by the
|
* |NUMATopologyFilter| - filters hosts based on the NUMA topology requested by the
|
||||||
instance, if any.
|
instance, if any.
|
||||||
* |AggregateTypeExtraSpecsAffinityFilter| - filters only hosts aggregated inside
|
|
||||||
host aggregates containing "flavor_extra_spec" metadata. Keys inside this
|
|
||||||
variable must match with the instance extra specs.
|
|
||||||
|
|
||||||
The content of the list will be formatted as follows. The entries in the list
|
|
||||||
will be separated by commas without white space. Each entry will comprise of
|
|
||||||
an instance type extra spec key followed by and equals "=" followed by a
|
|
||||||
value: <key>=<value>.
|
|
||||||
|
|
||||||
Eg. 'flavor_extra_spec=hw:mem_page_size=any,hw:mem_page_size=~,hw:mem_page_size=small'
|
|
||||||
|
|
||||||
Valid sentinel values are:
|
|
||||||
::
|
|
||||||
|
|
||||||
* (asterisk): may be used to specify that any value is valid.
|
|
||||||
~ (tilde): may be used to specify that a key may optionally be omitted.
|
|
||||||
! (exclamation): may be used to specify that the key must not be present.
|
|
||||||
::
|
|
||||||
|
|
||||||
Now we can focus on these standard filter classes in some detail. We'll skip the
|
Now we can focus on these standard filter classes in some detail. We'll skip the
|
||||||
simplest ones, such as |AllHostsFilter|, |CoreFilter| and |RamFilter|,
|
simplest ones, such as |AllHostsFilter|, |CoreFilter| and |RamFilter|,
|
||||||
@ -464,7 +446,6 @@ in :mod:`nova.tests.scheduler`.
|
|||||||
.. |TrustedFilter| replace:: :class:`TrustedFilter <nova.scheduler.filters.trusted_filter.TrustedFilter>`
|
.. |TrustedFilter| replace:: :class:`TrustedFilter <nova.scheduler.filters.trusted_filter.TrustedFilter>`
|
||||||
.. |TypeAffinityFilter| replace:: :class:`TypeAffinityFilter <nova.scheduler.filters.type_filter.TypeAffinityFilter>`
|
.. |TypeAffinityFilter| replace:: :class:`TypeAffinityFilter <nova.scheduler.filters.type_filter.TypeAffinityFilter>`
|
||||||
.. |AggregateTypeAffinityFilter| replace:: :class:`AggregateTypeAffinityFilter <nova.scheduler.filters.type_filter.AggregateTypeAffinityFilter>`
|
.. |AggregateTypeAffinityFilter| replace:: :class:`AggregateTypeAffinityFilter <nova.scheduler.filters.type_filter.AggregateTypeAffinityFilter>`
|
||||||
.. |AggregateTypeExtraSpecsAffinityFilter| replace:: :class:`AggregateTypeExtraSpecsAffinityFilter <nova.scheduler.filters.type_filter.AggregateTypeExtraSpecsAffinityFilter>`
|
|
||||||
.. |ServerGroupAntiAffinityFilter| replace:: :class:`ServerGroupAntiAffinityFilter <nova.scheduler.filters.affinity_filter.ServerGroupAntiAffinityFilter>`
|
.. |ServerGroupAntiAffinityFilter| replace:: :class:`ServerGroupAntiAffinityFilter <nova.scheduler.filters.affinity_filter.ServerGroupAntiAffinityFilter>`
|
||||||
.. |ServerGroupAffinityFilter| replace:: :class:`ServerGroupAffinityFilter <nova.scheduler.filters.affinity_filter.ServerGroupAffinityFilter>`
|
.. |ServerGroupAffinityFilter| replace:: :class:`ServerGroupAffinityFilter <nova.scheduler.filters.affinity_filter.ServerGroupAffinityFilter>`
|
||||||
.. |AggregateInstanceExtraSpecsFilter| replace:: :class:`AggregateInstanceExtraSpecsFilter <nova.scheduler.filters.aggregate_instance_extra_specs.AggregateInstanceExtraSpecsFilter>`
|
.. |AggregateInstanceExtraSpecsFilter| replace:: :class:`AggregateInstanceExtraSpecsFilter <nova.scheduler.filters.aggregate_instance_extra_specs.AggregateInstanceExtraSpecsFilter>`
|
||||||
|
@ -13,16 +13,9 @@
|
|||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
import collections
|
|
||||||
import six
|
|
||||||
|
|
||||||
from nova.i18n import _LI
|
|
||||||
from nova.scheduler import filters
|
from nova.scheduler import filters
|
||||||
from nova.scheduler.filters import utils
|
from nova.scheduler.filters import utils
|
||||||
from oslo_log import log as logging
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class TypeAffinityFilter(filters.BaseHostFilter):
|
class TypeAffinityFilter(filters.BaseHostFilter):
|
||||||
@ -66,136 +59,3 @@ class AggregateTypeAffinityFilter(filters.BaseHostFilter):
|
|||||||
[x.strip() for x in val.split(',')]):
|
[x.strip() for x in val.split(',')]):
|
||||||
return True
|
return True
|
||||||
return not aggregate_vals
|
return not aggregate_vals
|
||||||
|
|
||||||
|
|
||||||
class AggregateTypeExtraSpecsAffinityFilter(filters.BaseHostFilter):
|
|
||||||
"""AggregateTypeExtraSpecsAffinityFilter filters only hosts aggregated
|
|
||||||
inside host aggregates containing "flavor_extra_spec" metadata. Keys inside
|
|
||||||
this variable must match with the instance extra specs.
|
|
||||||
|
|
||||||
To use this filter, the name of this class should be added to the variable
|
|
||||||
``scheduler_default_filters``, in ``nova.conf``:
|
|
||||||
|
|
||||||
| [DEFAULT]
|
|
||||||
| scheduler_default_filters=<list of other filters>, \
|
|
||||||
AggregateTypeExtraSpecsAffinityFilter
|
|
||||||
|
|
||||||
The content of the list will be formatted as follows. The entries in the
|
|
||||||
list will be separated by commas without white space. Each entry will
|
|
||||||
comprise of an instance type extra spec key followed by and equals "="
|
|
||||||
followed by a value: <key>=<value>.
|
|
||||||
|
|
||||||
Eg.
|
|
||||||
Host Aggregate metadata:
|
|
||||||
'flavor_extra_spec=hw:mem_page_size=any,hw:mem_page_size=~,' \
|
|
||||||
'hw:mem_page_size=small'
|
|
||||||
Flavor extra specs:
|
|
||||||
'hw:mem_page_size=small'
|
|
||||||
|
|
||||||
Valid sentinel values are:
|
|
||||||
|
|
||||||
| * (asterisk): may be used to specify that any value is valid.
|
|
||||||
| ~ (tilde): may be used to specify that a key may optionally be omitted.
|
|
||||||
| ! (exclamation): may be used to specify that the key must not be present.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def _parse_extra_spec_key_pairs(self, aggregate_extra_spec):
|
|
||||||
"""Parse and group all key/data from aggregate_extra_spec.
|
|
||||||
|
|
||||||
:param aggregate_extra_spec: string containing a list of required
|
|
||||||
instance flavor extra specs, separated by
|
|
||||||
commas, with format "key=value"
|
|
||||||
:type aggregate_extra_spec: unicode.
|
|
||||||
:return: dictionary with the values parsed and grouped by keys.
|
|
||||||
:type: dict.
|
|
||||||
"""
|
|
||||||
extra_specs = collections.defaultdict(set)
|
|
||||||
kv_list = str(aggregate_extra_spec).split(',')
|
|
||||||
|
|
||||||
# Create a new set entry in a dict for every new key, update the key
|
|
||||||
# value (set object) for every other value.
|
|
||||||
for kv_element in kv_list:
|
|
||||||
key, value = kv_element.split('=', 1)
|
|
||||||
extra_specs[key].add(value)
|
|
||||||
if '=' in value:
|
|
||||||
LOG.info(_LI("Value string has an '=' char: key = '%(key)s', "
|
|
||||||
"value = '%(value)s'. Check if it's malformed"),
|
|
||||||
{'value': value, 'key': key})
|
|
||||||
return extra_specs
|
|
||||||
|
|
||||||
def _instance_is_allowed_in_aggregate(self,
|
|
||||||
aggregate_extra_spec,
|
|
||||||
instance_extra_specs):
|
|
||||||
"""Loop over all aggregate_extra_spec elements parsed and execute the
|
|
||||||
appropriate filter action.
|
|
||||||
|
|
||||||
:param aggregate_extra_spec: dictionary with the values parsed and
|
|
||||||
grouped by keys.
|
|
||||||
:type aggregate_extra_spec: dict.
|
|
||||||
:param instance_extra_specs: dictionary containing the extra specs of
|
|
||||||
the instance to be filtered.
|
|
||||||
:type instance_extra_specs: dict.
|
|
||||||
:return: True if all parameters executed correctly; False is there
|
|
||||||
is any error.
|
|
||||||
:type: boolean.
|
|
||||||
"""
|
|
||||||
for key, value in six.iteritems(aggregate_extra_spec):
|
|
||||||
if '*' in value:
|
|
||||||
if key not in instance_extra_specs:
|
|
||||||
LOG.debug("'flavor_extra_spec' key: %(key)s "
|
|
||||||
"is not present", {'key': key})
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if '!' in value:
|
|
||||||
if key in instance_extra_specs:
|
|
||||||
LOG.debug("'flavor_extra_spec' key: %(key)s "
|
|
||||||
"is present", {'key': key})
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if '~' in value:
|
|
||||||
if key not in instance_extra_specs:
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
value.discard('~')
|
|
||||||
|
|
||||||
for element in value:
|
|
||||||
match = [char for char in ["*", "!", "~"] if char in element]
|
|
||||||
if match:
|
|
||||||
LOG.info(_LI("Value string has '%(chars)s' char(s): "
|
|
||||||
"key = '%(key)s', value = '%(value)s'. "
|
|
||||||
"Check if it's malformed"),
|
|
||||||
{'value': element, 'chars': match, 'key': key})
|
|
||||||
if key not in instance_extra_specs:
|
|
||||||
LOG.debug("'flavor_extra_spec' key: %(key)s "
|
|
||||||
"is not present", {'key': key})
|
|
||||||
return False
|
|
||||||
if instance_extra_specs[key] not in value:
|
|
||||||
LOG.debug("The following 'flavor_extra_spec' "
|
|
||||||
"key=value: %(key)s=%(value)s doesn't "
|
|
||||||
"match", {'key': key, 'value': value})
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def host_passes(self, host_state, spec_obj):
|
|
||||||
instance_type = spec_obj.flavor
|
|
||||||
# If 'extra_specs' is not present or extra_specs are empty then we
|
|
||||||
# need not proceed further
|
|
||||||
if (not instance_type.obj_attr_is_set('extra_specs')
|
|
||||||
or not instance_type.extra_specs):
|
|
||||||
return True
|
|
||||||
|
|
||||||
aggregate_extra_spec_list = utils.aggregate_values_from_key(host_state,
|
|
||||||
'flavor_extra_spec')
|
|
||||||
|
|
||||||
for aggregate_extra_spec in aggregate_extra_spec_list:
|
|
||||||
aggregate_extra_spec = self._parse_extra_spec_key_pairs(
|
|
||||||
aggregate_extra_spec)
|
|
||||||
if not self._instance_is_allowed_in_aggregate(aggregate_extra_spec,
|
|
||||||
instance_type.extra_specs):
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
@ -10,8 +10,6 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
from nova import objects
|
from nova import objects
|
||||||
@ -131,195 +129,3 @@ class TestTypeFilter(test.NoDBTestCase):
|
|||||||
self.assertTrue(self.filt_cls.host_passes(host, spec_obj2))
|
self.assertTrue(self.filt_cls.host_passes(host, spec_obj2))
|
||||||
# False as instance_type is not allowed for aggregate
|
# False as instance_type is not allowed for aggregate
|
||||||
self.assertFalse(self.filt_cls.host_passes(host, spec_obj3))
|
self.assertFalse(self.filt_cls.host_passes(host, spec_obj3))
|
||||||
|
|
||||||
|
|
||||||
class TestAggregateTypeExtraSpecsAffinityFilter(test.NoDBTestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestAggregateTypeExtraSpecsAffinityFilter, self).setUp()
|
|
||||||
self.filt_cls = type_filter.AggregateTypeExtraSpecsAffinityFilter()
|
|
||||||
|
|
||||||
self.aggr_no_extraspecs = objects.Aggregate(context='test')
|
|
||||||
self.aggr_no_extraspecs.metadata = {}
|
|
||||||
|
|
||||||
self.aggr_invalid_extraspecs = objects.Aggregate(context='test')
|
|
||||||
self.aggr_invalid_extraspecs.metadata = {'flavor_extra_spec':
|
|
||||||
'hw:mem_page_size=large'}
|
|
||||||
|
|
||||||
self.aggr_valid_extraspecs = objects.Aggregate(context='test')
|
|
||||||
self.aggr_valid_extraspecs.metadata = {'flavor_extra_spec':
|
|
||||||
'hw:mem_page_size=small'}
|
|
||||||
|
|
||||||
self.aggr_optional_large = objects.Aggregate(context='test')
|
|
||||||
self.aggr_optional_large.metadata = {'flavor_extra_spec':
|
|
||||||
'hw:mem_page_size=large,hw:mem_page_size=~'}
|
|
||||||
|
|
||||||
self.aggr_optional_small = objects.Aggregate(context='test')
|
|
||||||
self.aggr_optional_small.metadata = {'flavor_extra_spec':
|
|
||||||
'hw:mem_page_size=small,hw:mem_page_size=~'}
|
|
||||||
|
|
||||||
self.aggr_optional_first = objects.Aggregate(context='test')
|
|
||||||
self.aggr_optional_first.metadata = {'flavor_extra_spec':
|
|
||||||
'hw:mem_page_size=~,hw:mem_page_size=small,hw:mem_page_size=any'}
|
|
||||||
|
|
||||||
self.aggr_optional_last = objects.Aggregate(context='test')
|
|
||||||
self.aggr_optional_last.metadata = {'flavor_extra_spec':
|
|
||||||
'hw:mem_page_size=any,hw:mem_page_size=small,hw:mem_page_size=~'}
|
|
||||||
|
|
||||||
self.aggr_optional_middle = objects.Aggregate(context='test')
|
|
||||||
self.aggr_optional_middle.metadata = {'flavor_extra_spec':
|
|
||||||
'hw:mem_page_size=any,hw:mem_page_size=~,hw:mem_page_size=small'}
|
|
||||||
|
|
||||||
self.aggr_any = objects.Aggregate(context='test')
|
|
||||||
self.aggr_any.metadata = {'flavor_extra_spec': 'hw:mem_page_size=*'}
|
|
||||||
|
|
||||||
self.aggr_nopresent = objects.Aggregate(context='test')
|
|
||||||
self.aggr_nopresent.metadata = {'flavor_extra_spec':
|
|
||||||
'hw:mem_page_size=!'}
|
|
||||||
|
|
||||||
self.aggr_asterisk_inline = objects.Aggregate(context='test')
|
|
||||||
self.aggr_asterisk_inline.metadata = {'flavor_extra_spec':
|
|
||||||
'hw:mem_page_size=*value'}
|
|
||||||
|
|
||||||
self.aggr_tilde_inline = objects.Aggregate(context='test')
|
|
||||||
self.aggr_tilde_inline.metadata = {'flavor_extra_spec':
|
|
||||||
'hw:mem_page_size=value~'}
|
|
||||||
|
|
||||||
self.aggr_exclamation_inline = objects.Aggregate(context='test')
|
|
||||||
self.aggr_exclamation_inline.metadata = {'flavor_extra_spec':
|
|
||||||
'hw:mem_page_size=va!lue'}
|
|
||||||
|
|
||||||
self.rs_extraspecs = objects.RequestSpec(
|
|
||||||
context=mock.sentinel.ctx,
|
|
||||||
flavor=objects.Flavor(extra_specs={'hw:mem_page_size': 'small'}))
|
|
||||||
self.rs_no_extraspecs = objects.RequestSpec(
|
|
||||||
context=mock.sentinel.ctx,
|
|
||||||
flavor=objects.Flavor())
|
|
||||||
|
|
||||||
@mock.patch.object(logging.LoggerAdapter, 'info')
|
|
||||||
def test_parse_extra_spec_key_pairs(self, mock_logging):
|
|
||||||
mock_logging.return_value = True
|
|
||||||
self.filt_cls._parse_extra_spec_key_pairs("key=value=foobar")
|
|
||||||
mock_logging.assert_called_with(
|
|
||||||
("Value string has an '=' char: key = '%(key)s', value = "
|
|
||||||
"'%(value)s'. Check if it's malformed"),
|
|
||||||
{'value': 'value=foobar', 'key': 'key'})
|
|
||||||
mock_logging.reset_mock()
|
|
||||||
self.filt_cls._parse_extra_spec_key_pairs("key=value")
|
|
||||||
mock_logging.assert_not_called()
|
|
||||||
|
|
||||||
@mock.patch.object(logging.LoggerAdapter, 'info')
|
|
||||||
def test_sentinel_values_inline(self, mock_logging):
|
|
||||||
mock_logging.return_value = True
|
|
||||||
host_state = fakes.FakeHostState('host1', 'compute', {})
|
|
||||||
host_state_aggregates = [self.aggr_asterisk_inline,
|
|
||||||
self.aggr_tilde_inline, self.aggr_exclamation_inline]
|
|
||||||
chars = ['*', '~', '!']
|
|
||||||
values = ['*value', 'value~', 'va!lue']
|
|
||||||
for host_state_aggregate, char, value in zip(host_state_aggregates,
|
|
||||||
chars, values):
|
|
||||||
host_state.aggregates = [host_state_aggregate]
|
|
||||||
self.filt_cls.host_passes(host_state, self.rs_extraspecs)
|
|
||||||
mock_logging.assert_called_with(
|
|
||||||
("Value string has '%(chars)s' char(s): key = '%(key)s', "
|
|
||||||
"value = '%(value)s'. Check if it's malformed"),
|
|
||||||
{'chars': [char], 'value': value, 'key': 'hw:mem_page_size'})
|
|
||||||
mock_logging.reset_mock()
|
|
||||||
|
|
||||||
@mock.patch.object(logging.LoggerAdapter, 'info')
|
|
||||||
def test_no_sentinel_values_inline(self, mock_logging):
|
|
||||||
mock_logging.return_value = True
|
|
||||||
host_state = fakes.FakeHostState('host1', 'compute', {})
|
|
||||||
host_state.aggregates = [self.aggr_valid_extraspecs]
|
|
||||||
self.filt_cls.host_passes(host_state, self.rs_extraspecs)
|
|
||||||
mock_logging.assert_not_called()
|
|
||||||
|
|
||||||
def test_aggregate_type_extra_specs_ha_no_extraspecs(self):
|
|
||||||
host_state = fakes.FakeHostState('host1', 'compute', {})
|
|
||||||
host_state.aggregates.append(self.aggr_no_extraspecs)
|
|
||||||
self.assertTrue(self.filt_cls.host_passes(host_state,
|
|
||||||
self.rs_extraspecs))
|
|
||||||
|
|
||||||
def test_aggregate_type_extra_specs_ha_wrong_extraspecs(self):
|
|
||||||
host_state = fakes.FakeHostState('host1', 'compute', {})
|
|
||||||
host_state.aggregates.append(self.aggr_invalid_extraspecs)
|
|
||||||
self.assertFalse(self.filt_cls.host_passes(host_state,
|
|
||||||
self.rs_extraspecs))
|
|
||||||
|
|
||||||
def test_aggregate_type_extra_specs_ha_right_extraspecs(self):
|
|
||||||
host_state = fakes.FakeHostState('host1', 'compute', {})
|
|
||||||
host_state.aggregates.append(self.aggr_valid_extraspecs)
|
|
||||||
self.assertTrue(self.filt_cls.host_passes(host_state,
|
|
||||||
self.rs_extraspecs))
|
|
||||||
|
|
||||||
def test_aggregate_type_extra_specs_2ha_noextra_wrong(self):
|
|
||||||
host_state = fakes.FakeHostState('host1', 'compute', {})
|
|
||||||
host_state.aggregates.append(self.aggr_no_extraspecs)
|
|
||||||
host_state.aggregates.append(self.aggr_invalid_extraspecs)
|
|
||||||
self.assertFalse(self.filt_cls.host_passes(host_state,
|
|
||||||
self.rs_extraspecs))
|
|
||||||
|
|
||||||
def test_aggregate_type_extra_specs_2ha_noextra_right(self):
|
|
||||||
host_state = fakes.FakeHostState('host1', 'compute', {})
|
|
||||||
host_state.aggregates.append(self.aggr_no_extraspecs)
|
|
||||||
host_state.aggregates.append(self.aggr_valid_extraspecs)
|
|
||||||
self.assertTrue(self.filt_cls.host_passes(host_state,
|
|
||||||
self.rs_extraspecs))
|
|
||||||
|
|
||||||
def test_aggregate_type_extra_specs_2ha_wrong_right(self):
|
|
||||||
host_state = fakes.FakeHostState('host1', 'compute', {})
|
|
||||||
host_state.aggregates.append(self.aggr_invalid_extraspecs)
|
|
||||||
host_state.aggregates.append(self.aggr_valid_extraspecs)
|
|
||||||
self.assertFalse(self.filt_cls.host_passes(host_state,
|
|
||||||
self.rs_extraspecs))
|
|
||||||
|
|
||||||
def test_aggregate_type_extra_specs_any_and_more_large(self):
|
|
||||||
host_state = fakes.FakeHostState('host1', 'compute', {})
|
|
||||||
host_state.aggregates.append(self.aggr_optional_large)
|
|
||||||
self.assertFalse(self.filt_cls.host_passes(host_state,
|
|
||||||
self.rs_extraspecs))
|
|
||||||
|
|
||||||
def test_aggregate_type_extra_specs_any_and_more_small(self):
|
|
||||||
host_state = fakes.FakeHostState('host1', 'compute', {})
|
|
||||||
host_state.aggregates.append(self.aggr_optional_small)
|
|
||||||
self.assertTrue(self.filt_cls.host_passes(host_state,
|
|
||||||
self.rs_extraspecs))
|
|
||||||
|
|
||||||
def test_aggregate_type_extra_specs_any_and_more_no_extraspecs(self):
|
|
||||||
host_state = fakes.FakeHostState('host1', 'compute', {})
|
|
||||||
host_state.aggregates.append(self.aggr_optional_large)
|
|
||||||
self.assertTrue(self.filt_cls.host_passes(host_state,
|
|
||||||
self.rs_no_extraspecs))
|
|
||||||
|
|
||||||
def test_aggregate_type_extra_specs_any_order(self):
|
|
||||||
host_state = fakes.FakeHostState('host1', 'compute', {})
|
|
||||||
for host_state_aggregate in [self.aggr_optional_first,
|
|
||||||
self.aggr_optional_last,
|
|
||||||
self.aggr_optional_middle]:
|
|
||||||
host_state.aggregates = [host_state_aggregate]
|
|
||||||
self.assertTrue(self.filt_cls.host_passes(host_state,
|
|
||||||
self.rs_extraspecs))
|
|
||||||
|
|
||||||
def test_aggregate_type_extra_specs_asterisk(self):
|
|
||||||
host_state = fakes.FakeHostState('host1', 'compute', {})
|
|
||||||
host_state.aggregates.append(self.aggr_any)
|
|
||||||
self.assertTrue(self.filt_cls.host_passes(host_state,
|
|
||||||
self.rs_extraspecs))
|
|
||||||
|
|
||||||
def test_aggregate_type_extra_specs_asterisk_no_extraspecs(self):
|
|
||||||
host_state = fakes.FakeHostState('host1', 'compute', {})
|
|
||||||
host_state.aggregates.append(self.aggr_any)
|
|
||||||
self.assertTrue(self.filt_cls.host_passes(host_state,
|
|
||||||
self.rs_no_extraspecs))
|
|
||||||
|
|
||||||
def test_aggregate_type_extra_specs_nospec(self):
|
|
||||||
host_state = fakes.FakeHostState('host1', 'compute', {})
|
|
||||||
host_state.aggregates.append(self.aggr_nopresent)
|
|
||||||
self.assertFalse(self.filt_cls.host_passes(host_state,
|
|
||||||
self.rs_extraspecs))
|
|
||||||
|
|
||||||
def test_aggregate_type_extra_specs_nospec_no_extraspecs(self):
|
|
||||||
host_state = fakes.FakeHostState('host1', 'compute', {})
|
|
||||||
host_state.aggregates.append(self.aggr_nopresent)
|
|
||||||
self.assertTrue(self.filt_cls.host_passes(host_state,
|
|
||||||
self.rs_no_extraspecs))
|
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
---
|
|
||||||
features:
|
|
||||||
- Add a new scheduler filter, AggregateTypeExtraSpecsAffinityFilter, which
|
|
||||||
filters only hosts aggregated inside host aggregates containing
|
|
||||||
"flavor_extra_spec" metadata. Keys inside this variable must match with the
|
|
||||||
instance extra specs. The entries in the list will be separated by commas
|
|
||||||
without white space. Each entry will comprise of an instance type extra
|
|
||||||
spec key followed by an equals "=" followed by a value (<key>=<value>).
|
|
||||||
Documentation can be find in doc folder (doc/source/filter_scheduler.rst)
|
|
||||||
and online (http://docs.openstack.org/developer/nova/filter_scheduler.html).
|
|
Loading…
x
Reference in New Issue
Block a user