Climate to Blazar in Nova filter
Renamed Climate to Blazar in the Nova filter. Change-Id: I65a83d2f0dddb21e83b6b74969f7e68466d35797 Partial-Bug: #1311747
This commit is contained in:
parent
cc776e87a8
commit
6815ec9152
|
@ -0,0 +1,146 @@
|
|||
# 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 _
|
||||
from nova.openstack.common import log as logging
|
||||
from nova.scheduler import filters
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
deprecated_group = 'climate:physical:host'
|
||||
opts = [
|
||||
cfg.StrOpt('aggregate_freepool_name',
|
||||
default='freepool',
|
||||
help='Name of the special aggregate where all hosts '
|
||||
'are candidate for physical host reservation',
|
||||
deprecated_group=deprecated_group),
|
||||
cfg.StrOpt('project_id_key',
|
||||
default='blazar:tenant',
|
||||
deprecated_name='tenant_id_key',
|
||||
help='Aggregate metadata value for key matching project_id',
|
||||
deprecated_group=deprecated_group),
|
||||
cfg.StrOpt('blazar_owner',
|
||||
default='blazar:owner',
|
||||
deprecated_name='climate_owner',
|
||||
help='Aggregate metadata key for knowing owner project_id',
|
||||
deprecated_group=deprecated_group),
|
||||
cfg.StrOpt('blazar_az_prefix',
|
||||
default='blazar:',
|
||||
deprecated_name='climate_az_prefix',
|
||||
help='Prefix for Availability Zones created by Blazar',
|
||||
deprecated_group=deprecated_group),
|
||||
]
|
||||
|
||||
cfg.CONF.register_opts(opts, 'blazar:physical:host')
|
||||
|
||||
|
||||
class ClimateFilter(filters.BaseHostFilter):
|
||||
"""Blazar 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 "blazar:owner"
|
||||
|
||||
If the user does not pass "reservation=<id>" as a hint then only
|
||||
hosts which are not in a pool (including freepool) pass.
|
||||
|
||||
If the user does pass "reservation=<id>" 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 :
|
||||
- "blazar:owner=tenant_id (which grants automatically all the
|
||||
users from the tenant from which the request came from)
|
||||
- or, "tenant_id=blazar: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['blazar:physical:host'].blazar_az_prefix)
|
||||
or agg['availability_zone'].startswith('climate:')):
|
||||
pools.append(agg)
|
||||
if agg['name'] == (
|
||||
cfg.CONF['blazar: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 blazar:tenant:{project_id}
|
||||
key = context.project_id
|
||||
access = pool['metadetails'].get(key)
|
||||
if access:
|
||||
return True
|
||||
# NOTE(sbauza): We also need to check the blazar:owner key
|
||||
# until we modify the reservation pool for including the
|
||||
# project_id key as for any other extra project
|
||||
owner = cfg.CONF['blazar:physical:host'].blazar_owner
|
||||
# NOTE(pafuent): climate:owner was the previous default
|
||||
# value.
|
||||
owner_project_id = pool['metadetails'].get(
|
||||
owner, pool['metadetails'].get('climate: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
|
||||
return True
|
||||
|
||||
# For backward compatibility. This should be done in this way due to how Nova
|
||||
# imports the filters
|
||||
BlazarFilter = ClimateFilter
|
|
@ -1,5 +1,4 @@
|
|||
# Copyright (c) 2013 Bull.
|
||||
#
|
||||
# Copyright 2014 Intel Corporation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
|
@ -14,117 +13,5 @@
|
|||
# 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 _
|
||||
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=<id>" as a hint then only
|
||||
hosts which are not in a pool (including freepool) pass.
|
||||
|
||||
If the user does pass "reservation=<id>" 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
|
||||
return True
|
||||
# Only for backward compatibility
|
||||
from blazarnova.scheduler.filters.blazar_filter import * # noqa
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
import mock
|
||||
|
||||
from climatenova.scheduler.filters import climate_filter
|
||||
from blazarnova.scheduler.filters import blazar_filter
|
||||
from nova import context
|
||||
from nova.openstack.common import log as logging
|
||||
from nova import test
|
||||
|
@ -24,17 +24,17 @@ from oslo.config import cfg
|
|||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ClimateFilterTestCase(test.TestCase):
|
||||
class BlazarFilterTestCase(test.TestCase):
|
||||
"""Filter test case.
|
||||
|
||||
This test case provides tests for the schedule filters available
|
||||
on Climate.
|
||||
on Blazar.
|
||||
"""
|
||||
def setUp(self):
|
||||
super(ClimateFilterTestCase, self).setUp()
|
||||
super(BlazarFilterTestCase, self).setUp()
|
||||
|
||||
#Let's have at hand a brand new climate filter
|
||||
self.f = climate_filter.ClimateFilter()
|
||||
#Let's have at hand a brand new blazar filter
|
||||
self.f = blazar_filter.BlazarFilter()
|
||||
|
||||
#A fake host state
|
||||
self.host = fakes.FakeHostState('host1', 'node1', {})
|
||||
|
@ -51,8 +51,8 @@ class ClimateFilterTestCase(test.TestCase):
|
|||
"scheduler_hints": {}
|
||||
}
|
||||
|
||||
@mock.patch('climatenova.scheduler.filters.climate_filter.db')
|
||||
def test_climate_filter_no_pool_available_requested(self, fake_nova_db):
|
||||
@mock.patch('blazarnova.scheduler.filters.blazar_filter.db')
|
||||
def test_blazar_filter_no_pool_available_requested(self, fake_nova_db):
|
||||
|
||||
#Given the host doesn't belong to any pool
|
||||
fake_nova_db.aggregate_get_by_host.return_value = []
|
||||
|
@ -68,8 +68,8 @@ class ClimateFilterTestCase(test.TestCase):
|
|||
#Then the host shall NOT pass
|
||||
self.assertFalse(self.host.passes)
|
||||
|
||||
@mock.patch('climatenova.scheduler.filters.climate_filter.db')
|
||||
def test_climate_filter_no_pool_available_not_requested(
|
||||
@mock.patch('blazarnova.scheduler.filters.blazar_filter.db')
|
||||
def test_blazar_filter_no_pool_available_not_requested(
|
||||
self,
|
||||
fake_nova_db):
|
||||
|
||||
|
@ -86,15 +86,15 @@ class ClimateFilterTestCase(test.TestCase):
|
|||
#Then the host shall pass
|
||||
self.assertTrue(self.host.passes)
|
||||
|
||||
@mock.patch('climatenova.scheduler.filters.climate_filter.db')
|
||||
def test_climate_filter_host_in_freepool_and_none_requested(
|
||||
@mock.patch('blazarnova.scheduler.filters.blazar_filter.db')
|
||||
def test_blazar_filter_host_in_freepool_and_none_requested(
|
||||
self,
|
||||
fake_nova_db):
|
||||
|
||||
#Given the host is in the free pool (named "freepool")
|
||||
fake_nova_db.aggregate_get_by_host.return_value = \
|
||||
[{'name':
|
||||
cfg.CONF['climate:physical:host'].aggregate_freepool_name,
|
||||
cfg.CONF['blazar:physical:host'].aggregate_freepool_name,
|
||||
'availability_zone': 'unknown',
|
||||
'metadetails': {self.fake_context.project_id: True}}]
|
||||
|
||||
|
@ -108,14 +108,14 @@ class ClimateFilterTestCase(test.TestCase):
|
|||
#Then the host shall NOT pass
|
||||
self.assertFalse(self.host.passes)
|
||||
|
||||
@mock.patch('climatenova.scheduler.filters.climate_filter.db')
|
||||
def test_climate_filter_host_in_pool_none_requested(self, fake_nova_db):
|
||||
@mock.patch('blazarnova.scheduler.filters.blazar_filter.db')
|
||||
def test_blazar_filter_host_in_pool_none_requested(self, fake_nova_db):
|
||||
|
||||
#Given the host belongs to the 'r-fakeres' reservation pool
|
||||
fake_nova_db.aggregate_get_by_host.return_value = \
|
||||
[{'name': 'r-fakeres',
|
||||
'availability_zone':
|
||||
cfg.CONF['climate:physical:host'].climate_az_prefix + 'XX',
|
||||
cfg.CONF['blazar:physical:host'].blazar_az_prefix + 'XX',
|
||||
'metadetails': {self.fake_context.project_id: True}}]
|
||||
|
||||
#And the filter doesn't require any pool (using filter as in setup())
|
||||
|
@ -128,8 +128,8 @@ class ClimateFilterTestCase(test.TestCase):
|
|||
#Then the host shall NOT pass
|
||||
self.assertFalse(self.host.passes)
|
||||
|
||||
@mock.patch('climatenova.scheduler.filters.climate_filter.db')
|
||||
def test_climate_filter_host_not_in_ant_pool(self, fake_nova_db):
|
||||
@mock.patch('blazarnova.scheduler.filters.blazar_filter.db')
|
||||
def test_blazar_filter_host_not_in_ant_pool(self, fake_nova_db):
|
||||
|
||||
#Given the host belongs to a pool different to 'r-fakeres'
|
||||
fake_nova_db.aggregate_get_by_host.return_value = \
|
||||
|
@ -148,8 +148,8 @@ class ClimateFilterTestCase(test.TestCase):
|
|||
#Then the host shall NOT pass
|
||||
self.assertFalse(self.host.passes)
|
||||
|
||||
@mock.patch('climatenova.scheduler.filters.climate_filter.db')
|
||||
def test_climate_filter_host_not_auth_in_current_tenant(
|
||||
@mock.patch('blazarnova.scheduler.filters.blazar_filter.db')
|
||||
def test_blazar_filter_host_not_auth_in_current_tenant(
|
||||
self,
|
||||
fake_nova_db):
|
||||
|
||||
|
@ -171,15 +171,15 @@ class ClimateFilterTestCase(test.TestCase):
|
|||
#Then the host shall NOT pass
|
||||
self.assertFalse(self.host.passes)
|
||||
|
||||
@mock.patch('climatenova.scheduler.filters.climate_filter.db')
|
||||
def test_climate_filter_host_auth_in_current_tenant(self, fake_nova_db):
|
||||
@mock.patch('blazarnova.scheduler.filters.blazar_filter.db')
|
||||
def test_blazar_filter_host_auth_in_current_tenant(self, fake_nova_db):
|
||||
|
||||
#Given the host is authorized in the current tenant
|
||||
#And thee pool name is 'r-fakeres'
|
||||
fake_nova_db.aggregate_get_by_host.return_value = \
|
||||
[{'name': 'r-fakeres',
|
||||
'availability_zone':
|
||||
cfg.CONF['climate:physical:host'].climate_az_prefix,
|
||||
cfg.CONF['blazar:physical:host'].blazar_az_prefix,
|
||||
'metadetails': {self.fake_context.project_id: True}}]
|
||||
|
||||
#And the 'r-fakeres' pool is requested in the filter
|
||||
|
@ -193,18 +193,17 @@ class ClimateFilterTestCase(test.TestCase):
|
|||
#Then the host shall pass
|
||||
self.assertTrue(self.host.passes)
|
||||
|
||||
@mock.patch('climatenova.scheduler.filters.climate_filter.db')
|
||||
def test_climate_filter_host_authorized_by_owner(self, fake_nova_db):
|
||||
|
||||
#Given the host climate owner is the current project id
|
||||
@mock.patch('blazarnova.scheduler.filters.blazar_filter.db')
|
||||
def test_blazar_filter_host_authorized_by_owner(self, fake_nova_db):
|
||||
#Given the host blazar owner is the current project id
|
||||
#And thee pool name is 'r-fakeres'
|
||||
fake_nova_db.aggregate_get_by_host.return_value = \
|
||||
[{'name': 'r-fakeres',
|
||||
'availability_zone':
|
||||
cfg.CONF['climate:physical:host'].climate_az_prefix,
|
||||
cfg.CONF['blazar:physical:host'].blazar_az_prefix,
|
||||
'metadetails': {self.fake_context.project_id: False,
|
||||
cfg.CONF['climate:physical:host'].
|
||||
climate_owner: self.fake_context.project_id}}]
|
||||
cfg.CONF['blazar:physical:host'].
|
||||
blazar_owner: self.fake_context.project_id}}]
|
||||
|
||||
#And the 'r-fakeres' pool is requested in the filter
|
||||
self.filter_properties['scheduler_hints']['reservation'] = 'r-fakeres'
|
||||
|
@ -217,18 +216,18 @@ class ClimateFilterTestCase(test.TestCase):
|
|||
#Then the host shall pass
|
||||
self.assertTrue(self.host.passes)
|
||||
|
||||
@mock.patch('climatenova.scheduler.filters.climate_filter.db')
|
||||
def test_climate_filter_host_not_authorized_by_owner(self, fake_nova_db):
|
||||
@mock.patch('blazarnova.scheduler.filters.blazar_filter.db')
|
||||
def test_blazar_filter_host_not_authorized_by_owner(self, fake_nova_db):
|
||||
|
||||
#Given the host climate owner is NOT the current project id
|
||||
#Given the host blazar owner is NOT the current project id
|
||||
#And thee pool name is 'r-fakeres'
|
||||
fake_nova_db.aggregate_get_by_host.return_value = \
|
||||
[{'name': 'r-fakeres',
|
||||
'availability_zone':
|
||||
cfg.CONF['climate:physical:host'].climate_az_prefix,
|
||||
cfg.CONF['blazar:physical:host'].blazar_az_prefix,
|
||||
'metadetails': {self.fake_context.project_id: False,
|
||||
cfg.CONF['climate:physical:host'].
|
||||
climate_owner: 'another_project_id'}}]
|
||||
cfg.CONF['blazar:physical:host'].
|
||||
blazar_owner: 'another_project_id'}}]
|
||||
|
||||
#And the 'r-fakeres' pool is requested in the filter
|
||||
self.filter_properties['scheduler_hints']['reservation'] = 'r-fakeres'
|
||||
|
@ -241,12 +240,12 @@ class ClimateFilterTestCase(test.TestCase):
|
|||
#Then the host shall pass
|
||||
self.assertFalse(self.host.passes)
|
||||
|
||||
@mock.patch('climatenova.scheduler.filters.climate_filter.db')
|
||||
def test_climate_filter_host_not_in_requested_pools(self, fake_nova_db):
|
||||
@mock.patch('blazarnova.scheduler.filters.blazar_filter.db')
|
||||
def test_blazar_filter_host_not_in_requested_pools(self, fake_nova_db):
|
||||
|
||||
#Given the host is in the free pool
|
||||
fake_nova_db.aggregate_get_by_host.return_value = \
|
||||
[{'name': cfg.CONF['climate:physical:host'].
|
||||
[{'name': cfg.CONF['blazar:physical:host'].
|
||||
aggregate_freepool_name,
|
||||
'availability_zone': 'unknown'}]
|
||||
|
||||
|
@ -261,14 +260,14 @@ class ClimateFilterTestCase(test.TestCase):
|
|||
#Then the host shall NOT pass
|
||||
self.assertFalse(self.host.passes)
|
||||
|
||||
@mock.patch('climatenova.scheduler.filters.climate_filter.db')
|
||||
def test_climate_filter_unicode_requested_pool(self, fake_nova_db):
|
||||
@mock.patch('blazarnova.scheduler.filters.blazar_filter.db')
|
||||
def test_blazar_filter_unicode_requested_pool(self, fake_nova_db):
|
||||
|
||||
#Given the host is in a pool with unicode characters
|
||||
fake_nova_db.aggregate_get_by_host.return_value = \
|
||||
[{'name': U'r-fake~es',
|
||||
'availability_zone':
|
||||
cfg.CONF['climate:physical:host'].climate_az_prefix,
|
||||
cfg.CONF['blazar:physical:host'].blazar_az_prefix,
|
||||
'metadetails': {self.fake_context.project_id: True}}]
|
||||
|
||||
#And the filter is requesting for a host with the same name (ucode)
|
Loading…
Reference in New Issue