Aggregate: Hosts isolation based on image properties

Isolates hosts based on image properties and aggregate metadata
 - If a host doesn't belong to any aggregate it can create instances
   from all images.
 - if a host belongs to an aggregate and if this aggregate defines
   metadata that match with the image properties then the host
   is a candidate to boot the instance.

DocImpact
Change-Id: I3b9325e2e103f3bb6eed66789ac2c82941e94397
Implements: blueprint aggregate-host-isolation-based-image-properties
This commit is contained in:
Sahid Orentino Ferdjaoui 2013-11-14 16:53:18 +00:00
parent dd3f96e915
commit 0ca6b81758
4 changed files with 186 additions and 1 deletions

View File

@ -107,6 +107,8 @@ There are some standard filter classes to use (:mod:`nova.scheduler.filters`):
* |GroupAffinityFilter| - ensures that each instance in group is on a same
host with one of the instance host in a group.
* |AggregateMultiTenancyIsolation| - isolate tenants in specific aggregates.
* |AggregateImagePropertiesIsolation| - isolates hosts based on image
properties and aggregate metadata.
Now we can focus on these standard filter classes in details. I will pass the
simplest ones, such as |AllHostsFilter|, |CoreFilter| and |RamFilter| are,
@ -330,5 +332,5 @@ in :mod:``nova.tests.scheduler``.
.. |AggregateTypeAffinityFilter| replace:: :class:`AggregateTypeAffinityFilter <nova.scheduler.filters.type_filter.AggregateTypeAffinityFilter>`
.. |AggregateInstanceExtraSpecsFilter| replace:: :class:`AggregateInstanceExtraSpecsFilter <nova.scheduler.filters.aggregate_instance_extra_specs.AggregateInstanceExtraSpecsFilter>`
.. |AggregateMultiTenancyIsolation| replace:: :class:`AggregateMultiTenancyIsolation <nova.scheduler.filters.aggregate_multitenancy_isolation.AggregateMultiTenancyIsolation>`
.. |RamWeigher| replace:: :class:`RamWeigher <nova.scheduler.weights.all_weighers.RamWeigher>`
.. |AggregateImagePropertiesIsolation| replace:: :class:`AggregateImagePropertiesIsolation <nova.scheduler.filters.aggregate_image_properties_isolation.AggregateImagePropertiesIsolation>`

View File

@ -1735,6 +1735,19 @@
#scheduler_host_subset_size=1
#
# Options defined in nova.scheduler.filters.aggregate_image_properties_isolation
#
# Force the filter to consider only keys matching the given
# namespace. (string value)
#aggregate_image_properties_isolation_namespace=<None>
# The separator used between the namespace and keys (string
# value)
#aggregate_image_properties_isolation_separator=.
#
# Options defined in nova.scheduler.filters.core_filter
#

View File

@ -0,0 +1,68 @@
# Copyright (c) 2013 Cloudwatt
# 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.config import cfg
from nova import db
from nova.openstack.common.gettextutils import _
from nova.openstack.common import log as logging
from nova.scheduler import filters
opts = [
cfg.StrOpt('aggregate_image_properties_isolation_namespace',
help='Force the filter to consider only keys matching '
'the given namespace.'),
cfg.StrOpt('aggregate_image_properties_isolation_separator',
default=".",
help='The separator used between the namespace and keys'),
]
CONF = cfg.CONF
CONF.register_opts(opts)
LOG = logging.getLogger(__name__)
class AggregateImagePropertiesIsolation(filters.BaseHostFilter):
"""AggregateImagePropertiesIsolation works with image properties."""
# Aggregate data and instance type does not change within a request
run_filter_once_per_request = True
def host_passes(self, host_state, filter_properties):
"""Checks a host in an aggregate that metadata key/value match
with image properties.
"""
cfg_namespace = CONF.aggregate_image_properties_isolation_namespace
cfg_separator = CONF.aggregate_image_properties_isolation_separator
spec = filter_properties.get('request_spec', {})
image_props = spec.get('image', {}).get('properties', {})
context = filter_properties['context'].elevated()
metadata = db.aggregate_metadata_get_by_host(context, host_state.host)
for key, options in metadata.iteritems():
if (cfg_namespace and
not key.startswith(cfg_namespace + cfg_separator)):
continue
prop = image_props.get(key)
if prop and prop not in options:
LOG.debug(_("%(host_state)s fails image aggregate properties "
"requirements. Property %(prop)s does not "
"match %(options)s."),
{'host_state': host_state,
'prop': prop,
'options': options})
return False
return True

View File

@ -1643,3 +1643,105 @@ class HostFiltersTestCase(test.NoDBTestCase):
self.pci_request_result = True
self.assertRaises(AttributeError, filt_cls.host_passes,
host, filter_properties)
def test_aggregate_image_properties_isolation_passes(self):
self._stub_service_is_up(True)
filt_cls = self.class_map['AggregateImagePropertiesIsolation']()
aggr_meta = {'foo': 'bar'}
self._create_aggregate_with_host(name='fake1',
metadata=aggr_meta,
hosts=['host1'])
filter_properties = {'context': self.context,
'request_spec': {
'image': {
'properties': {'foo': 'bar'}}}}
host = fakes.FakeHostState('host1', 'compute', {})
self.assertTrue(filt_cls.host_passes(host, filter_properties))
def test_aggregate_image_properties_isolation_multi_props_passes(self):
self._stub_service_is_up(True)
filt_cls = self.class_map['AggregateImagePropertiesIsolation']()
aggr_meta = {'foo': 'bar', 'foo2': 'bar2'}
self._create_aggregate_with_host(name='fake1',
metadata=aggr_meta,
hosts=['host1'])
filter_properties = {'context': self.context,
'request_spec': {
'image': {
'properties': {'foo': 'bar',
'foo2': 'bar2'}}}}
host = fakes.FakeHostState('host1', 'compute', {})
self.assertTrue(filt_cls.host_passes(host, filter_properties))
def test_aggregate_image_properties_isolation_props_with_meta_passes(self):
self._stub_service_is_up(True)
filt_cls = self.class_map['AggregateImagePropertiesIsolation']()
aggr_meta = {'foo': 'bar'}
self._create_aggregate_with_host(name='fake1',
metadata=aggr_meta,
hosts=['host1'])
filter_properties = {'context': self.context,
'request_spec': {
'image': {
'properties': {}}}}
host = fakes.FakeHostState('host1', 'compute', {})
self.assertTrue(filt_cls.host_passes(host, filter_properties))
def test_aggregate_image_properties_isolation_props_imgprops_passes(self):
self._stub_service_is_up(True)
filt_cls = self.class_map['AggregateImagePropertiesIsolation']()
aggr_meta = {}
self._create_aggregate_with_host(name='fake1',
metadata=aggr_meta,
hosts=['host1'])
filter_properties = {'context': self.context,
'request_spec': {
'image': {
'properties': {'foo': 'bar'}}}}
host = fakes.FakeHostState('host1', 'compute', {})
self.assertTrue(filt_cls.host_passes(host, filter_properties))
def test_aggregate_image_properties_isolation_props_not_match_fails(self):
self._stub_service_is_up(True)
filt_cls = self.class_map['AggregateImagePropertiesIsolation']()
aggr_meta = {'foo': 'bar'}
self._create_aggregate_with_host(name='fake1',
metadata=aggr_meta,
hosts=['host1'])
filter_properties = {'context': self.context,
'request_spec': {
'image': {
'properties': {'foo': 'no-bar'}}}}
host = fakes.FakeHostState('host1', 'compute', {})
self.assertFalse(filt_cls.host_passes(host, filter_properties))
def test_aggregate_image_properties_isolation_props_not_match2_fails(self):
self._stub_service_is_up(True)
filt_cls = self.class_map['AggregateImagePropertiesIsolation']()
aggr_meta = {'foo': 'bar', 'foo2': 'bar2'}
self._create_aggregate_with_host(name='fake1',
metadata=aggr_meta,
hosts=['host1'])
filter_properties = {'context': self.context,
'request_spec': {
'image': {
'properties': {'foo': 'bar',
'foo2': 'bar3'}}}}
host = fakes.FakeHostState('host1', 'compute', {})
self.assertFalse(filt_cls.host_passes(host, filter_properties))
def test_aggregate_image_properties_isolation_props_namespace(self):
self._stub_service_is_up(True)
filt_cls = self.class_map['AggregateImagePropertiesIsolation']()
self.flags(aggregate_image_properties_isolation_namespace="np")
aggr_meta = {'np.foo': 'bar', 'foo2': 'bar2'}
self._create_aggregate_with_host(name='fake1',
metadata=aggr_meta,
hosts=['host1'])
filter_properties = {'context': self.context,
'request_spec': {
'image': {
'properties': {'np.foo': 'bar',
'foo2': 'bar3'}}}}
host = fakes.FakeHostState('host1', 'compute', {})
self.assertTrue(filt_cls.host_passes(host, filter_properties))