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:
parent
dd3f96e915
commit
0ca6b81758
|
@ -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>`
|
||||
|
|
|
@ -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
|
||||
#
|
||||
|
|
|
@ -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
|
|
@ -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))
|
||||
|
|
Loading…
Reference in New Issue