diff --git a/climatenova/filters/host_reservation.py b/climatenova/filters/host_reservation.py deleted file mode 100644 index ea21890..0000000 --- a/climatenova/filters/host_reservation.py +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright (c) 2013 Julien Danjou -# Copyright (c) 2013 Swann Croiset -# -# 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.openstack.common import log as logging -from nova.scheduler import filters - -LOG = logging.getLogger(__name__) - - -class ClimateFilter(filters.BaseHostFilter): - """Climate Filter for nova-scheduler.""" - - run_filter_once_per_request = True - - def host_passes(self, host_state, filter_properties): - """Filter based on Climate.""" - # scheduler_hints = filter_properties.get('scheduler_hints') or {} - # XXX send host_state + to Climate API - return True diff --git a/climatenova/filters/__init__.py b/climatenova/scheduler/__init__.py similarity index 100% rename from climatenova/filters/__init__.py rename to climatenova/scheduler/__init__.py diff --git a/climatenova/scheduler/filters/__init__.py b/climatenova/scheduler/filters/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/climatenova/scheduler/filters/climate_filter.py b/climatenova/scheduler/filters/climate_filter.py new file mode 100644 index 0000000..ba482cd --- /dev/null +++ b/climatenova/scheduler/filters/climate_filter.py @@ -0,0 +1,131 @@ +# Copyright (c) 2013 Bull. +# +# 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. + +import six + +from oslo.config import cfg + +from nova import db +from nova.openstack.common.gettextutils import _ # noqa +from nova.openstack.common import log as logging +from nova.scheduler import filters + +LOG = logging.getLogger(__name__) + +opts = [ + cfg.StrOpt('aggregate_freepool_name', + default='freepool', + help='Name of the special aggregate where all hosts ' + 'are candidate for physical host reservation'), + cfg.StrOpt('tenant_id_key', + default='climate:tenant', + help='Aggregate metadata value for key matching tenant_id'), + cfg.StrOpt('climate_owner', + default='climate:owner', + help='Aggregate metadata key for knowing owner tenant_id'), + cfg.StrOpt('climate_az_prefix', + default='climate:', + help='Prefix for Availability Zones created by Climate'), +] + +cfg.CONF.register_opts(opts, 'climate:physical:host') + + +class ClimateFilter(filters.BaseHostFilter): + """Climate Filter for nova-scheduler.""" + + run_filter_once_per_request = True + + def host_passes(self, host_state, filter_properties): + """Check if a host in a pool can be used for a request + + A host is in a pool if it is a member of an aggregate that has + a metadata item with a key value of "climate:owner" + + If the user does not pass "reservation=" as a hint then only + hosts which are not in a pool (including freepool) pass. + + If the user does pass "reservation=" as a hint then the host only + passes if it is a member of the specified pool and that pool + has a metadata key of either : + - "climate:owner=tenant_id (which grants automatically all the + users from the tenant from which the request came from) + - or, "tenant_id=climate:tenant" (which grants extra tenants for + the reservation) + """ + + context = filter_properties['context'].elevated() + + # Find which Pools the user wants to use (if any) + scheduler_hints = filter_properties.get('scheduler_hints') or {} + requested_pools = scheduler_hints.get('reservation', []) + if isinstance(requested_pools, six.text_type): + requested_pools = [requested_pools] + + # Get any reservation pools this host is part of + # Note this include possibly the freepool + + aggregates = db.aggregate_get_by_host(context, host_state.host) + pools = [] + for agg in aggregates: + if agg['availability_zone'].startswith( + cfg.CONF['climate:physical:host'].climate_az_prefix): + pools.append(agg) + if agg['name'] == \ + cfg.CONF['climate:physical:host'].aggregate_freepool_name: + pools.append(agg) + + if pools: + if not requested_pools: + # Host is in a Pool and none requested + LOG.audit("In a user pool or in the freepool") + return False + + # Find aggregate which matches Pool + for pool in pools: + if pool['name'] in requested_pools: + + # Check tenant is allowed to use this Pool + + # NOTE(sbauza): Currently, the key is only the project_id, + # but later will possibly be climate:tenant:{project_id} + key = context.project_id + access = pool['metadetails'].get(key) + if access: + return True + # NOTE(sbauza): We also need to check the climate:owner key + # until we modify the reservation pool for including the + # project_id key as for any other extra project + owner = cfg.CONF['climate:physical:host'].climate_owner + owner_project_id = pool['metadetails'].get(owner) + if owner_project_id == context.project_id: + return True + LOG.info(_("Unauthorized request to use Pool " + "%(pool_id)s by tenant %(tenant_id)s"), + {'pool_id': pool['name'], + 'tenant_id': context.project_id}) + return False + + # Host not in requested Pools + return False + + else: + + if requested_pools: + # Host is not in a Pool and Pool requested + return False + else: + return True diff --git a/climatenova/tests/scheduler/__init__.py b/climatenova/tests/scheduler/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/climatenova/tests/scheduler/filters/__init__.py b/climatenova/tests/scheduler/filters/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/climatenova/tests/test_climate_scheduler.py b/climatenova/tests/scheduler/filters/test_climate_filter.py similarity index 61% rename from climatenova/tests/test_climate_scheduler.py rename to climatenova/tests/scheduler/filters/test_climate_filter.py index b9b8a2a..6b5b1c1 100644 --- a/climatenova/tests/test_climate_scheduler.py +++ b/climatenova/tests/scheduler/filters/test_climate_filter.py @@ -14,14 +14,20 @@ from nova.tests.scheduler import fakes -from climatenova.filters import host_reservation -from climatenova import tests +from climatenova.scheduler.filters import climate_filter +from nova import context +from nova import test -class ClimateSchedulerTestCase(tests.TestCase): +class ClimateFilterTestCase(test.TestCase): - def test_climate_scheduler(self): - f = host_reservation.ClimateFilter() + def setUp(self): + super(ClimateFilterTestCase, self).setUp() + + def test_climate_filter(self): + f = climate_filter.ClimateFilter() host = fakes.FakeHostState('host1', 'node1', {}) - filter_properties = {"scheduler_hints": {"foo": "bar"}} + fake_context = context.RequestContext('fake', 'fake') + filter_properties = {"scheduler_hints": {"foo": "bar"}, + "context": fake_context} self.assertTrue(f.host_passes(host, filter_properties)) diff --git a/etc/nova.conf.example b/etc/nova.conf.example new file mode 100644 index 0000000..c618060 --- /dev/null +++ b/etc/nova.conf.example @@ -0,0 +1,4 @@ +[DEFAULT] +scheduler_available_filters = nova.scheduler.filters.all_filters +scheduler_available_filters = climatenova.scheduler.filters.climate_filter.ClimateFilter +scheduler_default_filters=RetryFilter,AvailabilityZoneFilter,RamFilter,ComputeFilter,ComputeCapabilitiesFilter,ImagePropertiesFilter,ClimateFilter