summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/source/devref/filter_scheduler.rst2
-rw-r--r--nova/scheduler/filters/aggregate_multitenancy_isolation.py47
-rw-r--r--nova/tests/scheduler/test_host_filters.py39
3 files changed, 88 insertions, 0 deletions
diff --git a/doc/source/devref/filter_scheduler.rst b/doc/source/devref/filter_scheduler.rst
index 31dcfde77..a1175ddc7 100644
--- a/doc/source/devref/filter_scheduler.rst
+++ b/doc/source/devref/filter_scheduler.rst
@@ -93,6 +93,7 @@ There are some standard filter classes to use (:mod:`nova.scheduler.filters`):
* |AggregateTypeAffinityFilter| - limits instance_type by aggregate.
* |GroupAntiAffinityFilter| - ensures that each instance in group is on a
different host.
+* |AggregateMultiTenancyIsolation| - isolate tenants in specific aggregates.
Now we can focus on these standard filter classes in details. I will pass the
simplest ones, such as |AllHostsFilter|, |CoreFilter| and |RamFilter| are,
@@ -350,3 +351,4 @@ in :mod:`nova.tests.scheduler`.
.. |TypeAffinityFilter| replace:: :class:`TypeAffinityFilter <nova.scheduler.filters.type_filter.TypeAffinityFilter>`
.. |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>`
diff --git a/nova/scheduler/filters/aggregate_multitenancy_isolation.py b/nova/scheduler/filters/aggregate_multitenancy_isolation.py
new file mode 100644
index 000000000..539da37d1
--- /dev/null
+++ b/nova/scheduler/filters/aggregate_multitenancy_isolation.py
@@ -0,0 +1,47 @@
+# Copyright (c) 2011-2013 OpenStack, LLC.
+# 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 nova import db
+from nova.openstack.common import log as logging
+from nova.scheduler import filters
+
+LOG = logging.getLogger(__name__)
+
+
+class AggregateMultiTenancyIsolation(filters.BaseHostFilter):
+ """Isolate tenants in specific aggregates."""
+
+ def host_passes(self, host_state, filter_properties):
+ """If a host is in an aggregate that has the metadata key
+ "filter_tenant_id" it can only create instances from that tenant(s).
+ A host can be in different aggregates.
+
+ If a host doesn't belong to an aggregate with the metadata key
+ "filter_tenant_id" it can create instances from all tenants.
+ """
+ spec = filter_properties.get('request_spec', {})
+ props = spec.get('instance_properties', {})
+ tenant_id = props.get('project_id')
+
+ context = filter_properties['context'].elevated()
+ metadata = db.aggregate_metadata_get_by_host(context, host_state.host,
+ key="filter_tenant_id")
+
+ if metadata != {}:
+ if tenant_id not in metadata["filter_tenant_id"]:
+ LOG.debug(_("%(host_state)s fails tenant id on "
+ "aggregate"), locals())
+ return False
+ return True
diff --git a/nova/tests/scheduler/test_host_filters.py b/nova/tests/scheduler/test_host_filters.py
index 53e51668c..0a5f0da40 100644
--- a/nova/tests/scheduler/test_host_filters.py
+++ b/nova/tests/scheduler/test_host_filters.py
@@ -1414,3 +1414,42 @@ class HostFiltersTestCase(test.TestCase):
host = fakes.FakeHostState('host1', 'node1', {})
filter_properties = {'group_hosts': ['host1']}
self.assertFalse(filt_cls.host_passes(host, filter_properties))
+
+ def test_aggregate_multi_tenancy_isolation_with_meta_passes(self):
+ self._stub_service_is_up(True)
+ filt_cls = self.class_map['AggregateMultiTenancyIsolation']()
+ aggr_meta = {'filter_tenant_id': 'my_tenantid'}
+ self._create_aggregate_with_host(name='fake1', metadata=aggr_meta,
+ hosts=['host1'])
+ filter_properties = {'context': self.context,
+ 'request_spec': {
+ 'instance_properties': {
+ 'project_id': 'my_tenantid'}}}
+ host = fakes.FakeHostState('host1', 'compute', {})
+ self.assertTrue(filt_cls.host_passes(host, filter_properties))
+
+ def test_aggregate_multi_tenancy_isolation_fails(self):
+ self._stub_service_is_up(True)
+ filt_cls = self.class_map['AggregateMultiTenancyIsolation']()
+ aggr_meta = {'filter_tenant_id': 'other_tenantid'}
+ self._create_aggregate_with_host(name='fake1', metadata=aggr_meta,
+ hosts=['host1'])
+ filter_properties = {'context': self.context,
+ 'request_spec': {
+ 'instance_properties': {
+ 'project_id': 'my_tenantid'}}}
+ host = fakes.FakeHostState('host1', 'compute', {})
+ self.assertFalse(filt_cls.host_passes(host, filter_properties))
+
+ def test_aggregate_multi_tenancy_isolation_no_meta_passes(self):
+ self._stub_service_is_up(True)
+ filt_cls = self.class_map['AggregateMultiTenancyIsolation']()
+ aggr_meta = {}
+ self._create_aggregate_with_host(name='fake1', metadata=aggr_meta,
+ hosts=['host1'])
+ filter_properties = {'context': self.context,
+ 'request_spec': {
+ 'instance_properties': {
+ 'project_id': 'my_tenantid'}}}
+ host = fakes.FakeHostState('host1', 'compute', {})
+ self.assertTrue(filt_cls.host_passes(host, filter_properties))