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
This commit is contained in:
parent
d62205f316
commit
f619da2405
@ -93,6 +93,7 @@ There are some standard filter classes to use (:mod:`nova.scheduler.filters`):
|
|||||||
* |AggregateTypeAffinityFilter| - limits instance_type by aggregate.
|
* |AggregateTypeAffinityFilter| - limits instance_type by aggregate.
|
||||||
* |GroupAntiAffinityFilter| - ensures that each instance in group is on a
|
* |GroupAntiAffinityFilter| - ensures that each instance in group is on a
|
||||||
different host.
|
different host.
|
||||||
|
* |AggregateMultiTenancyIsolation| - isolate tenants in specific aggregates.
|
||||||
|
|
||||||
Now we can focus on these standard filter classes in details. I will pass the
|
Now we can focus on these standard filter classes in details. I will pass the
|
||||||
simplest ones, such as |AllHostsFilter|, |CoreFilter| and |RamFilter| are,
|
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>`
|
.. |TypeAffinityFilter| replace:: :class:`TypeAffinityFilter <nova.scheduler.filters.type_filter.TypeAffinityFilter>`
|
||||||
.. |AggregateTypeAffinityFilter| replace:: :class:`AggregateTypeAffinityFilter <nova.scheduler.filters.type_filter.AggregateTypeAffinityFilter>`
|
.. |AggregateTypeAffinityFilter| replace:: :class:`AggregateTypeAffinityFilter <nova.scheduler.filters.type_filter.AggregateTypeAffinityFilter>`
|
||||||
.. |AggregateInstanceExtraSpecsFilter| replace:: :class:`AggregateInstanceExtraSpecsFilter <nova.scheduler.filters.aggregate_instance_extra_specs.AggregateInstanceExtraSpecsFilter>`
|
.. |AggregateInstanceExtraSpecsFilter| replace:: :class:`AggregateInstanceExtraSpecsFilter <nova.scheduler.filters.aggregate_instance_extra_specs.AggregateInstanceExtraSpecsFilter>`
|
||||||
|
.. |AggregateMultiTenancyIsolation| replace:: :class:`AggregateMultiTenancyIsolation <nova.scheduler.filters.aggregate_multitenancy_isolation.AggregateMultiTenancyIsolation>`
|
||||||
|
47
nova/scheduler/filters/aggregate_multitenancy_isolation.py
Normal file
47
nova/scheduler/filters/aggregate_multitenancy_isolation.py
Normal file
@ -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
|
@ -1414,3 +1414,42 @@ class HostFiltersTestCase(test.TestCase):
|
|||||||
host = fakes.FakeHostState('host1', 'node1', {})
|
host = fakes.FakeHostState('host1', 'node1', {})
|
||||||
filter_properties = {'group_hosts': ['host1']}
|
filter_properties = {'group_hosts': ['host1']}
|
||||||
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
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))
|
||||||
|
Loading…
Reference in New Issue
Block a user