From f619da2405f8bb510a8ae2a88f6e4fcddb424ada Mon Sep 17 00:00:00 2001 From: Belmiro Moreira Date: Sun, 27 Jan 2013 17:57:31 +0100 Subject: [PATCH] Multi-tenancy isolation with aggregates A new scheduler filter that allows the creation of instances from specific tenants in selected aggregates. With this filter is possible to isolate tenants in a specific set of compute nodes (aggregates). 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. Implements: blueprint multi-tenancy-aggregates DocImpact Change-Id: I119c809c54da9e9dc3ac506c02203d2d4422b06e --- doc/source/devref/filter_scheduler.rst | 2 + .../aggregate_multitenancy_isolation.py | 47 +++++++++++++++++++ nova/tests/scheduler/test_host_filters.py | 39 +++++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 nova/scheduler/filters/aggregate_multitenancy_isolation.py diff --git a/doc/source/devref/filter_scheduler.rst b/doc/source/devref/filter_scheduler.rst index 31dcfde77c8e..a1175ddc73b1 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 ` .. |AggregateTypeAffinityFilter| replace:: :class:`AggregateTypeAffinityFilter ` .. |AggregateInstanceExtraSpecsFilter| replace:: :class:`AggregateInstanceExtraSpecsFilter ` +.. |AggregateMultiTenancyIsolation| replace:: :class:`AggregateMultiTenancyIsolation ` diff --git a/nova/scheduler/filters/aggregate_multitenancy_isolation.py b/nova/scheduler/filters/aggregate_multitenancy_isolation.py new file mode 100644 index 000000000000..539da37d1402 --- /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 53e51668c6a8..0a5f0da4002e 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))