Merge "Revert "Added new scheduler filter: AggregateTypeExtraSpecsAffinityFilter""

This commit is contained in:
Jenkins 2016-02-01 18:35:53 +00:00 committed by Gerrit Code Review
commit c8a8963eed
4 changed files with 0 additions and 363 deletions

View File

@ -168,24 +168,6 @@ There are many standard filter classes which may be used
the available metrics are passed.
* |NUMATopologyFilter| - filters hosts based on the NUMA topology requested by the
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
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>`
.. |TypeAffinityFilter| replace:: :class:`TypeAffinityFilter <nova.scheduler.filters.type_filter.TypeAffinityFilter>`
.. |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>`
.. |ServerGroupAffinityFilter| replace:: :class:`ServerGroupAffinityFilter <nova.scheduler.filters.affinity_filter.ServerGroupAffinityFilter>`
.. |AggregateInstanceExtraSpecsFilter| replace:: :class:`AggregateInstanceExtraSpecsFilter <nova.scheduler.filters.aggregate_instance_extra_specs.AggregateInstanceExtraSpecsFilter>`

View File

@ -13,16 +13,9 @@
# 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 collections
import six
from nova.i18n import _LI
from nova.scheduler import filters
from nova.scheduler.filters import utils
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
class TypeAffinityFilter(filters.BaseHostFilter):
@ -66,136 +59,3 @@ class AggregateTypeAffinityFilter(filters.BaseHostFilter):
[x.strip() for x in val.split(',')]):
return True
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

View File

@ -10,8 +10,6 @@
# License for the specific language governing permissions and limitations
# under the License.
import logging
import mock
from nova import objects
@ -131,195 +129,3 @@ class TestTypeFilter(test.NoDBTestCase):
self.assertTrue(self.filt_cls.host_passes(host, spec_obj2))
# False as instance_type is not allowed for aggregate
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))

View File

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