Specific Nova part of the Blazar Reservation Service for OpenStack
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

139 lines
5.5 KiB

# 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.
from blazarnova.i18n import _
from nova.scheduler import filters
from oslo_config import cfg
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
FLAVOR_EXTRA_SPEC = "aggregate_instance_extra_specs:reservation"
opts = [
help='Name of the special aggregate where all hosts '
'are candidate for physical host reservation'),
help='Aggregate metadata value for key matching project_id'),
help='Aggregate metadata key for knowing owner project_id'),
help='Prefix for Availability Zones created by Blazar')
cfg.CONF.register_opts(opts, 'blazar:physical:host')
class BlazarFilter(filters.BaseHostFilter):
"""Blazar Filter for nova-scheduler."""
run_filter_once_per_request = True
def fetch_blazar_pools(self, host_state):
# Get any reservation pools this host is part of
# Note this include possibly the freepool
aggregates = host_state.aggregates
pools = []
for agg in aggregates:
if (str(agg.availability_zone).startswith(
# NOTE(hiro-kobayashi): following 2 lines are for keeping
# backward compatibility
or str(agg.availability_zone).startswith('blazar:')):
if agg.name == (
return pools
def host_reservation_request(self, host_state, spec_obj, requested_pools):
pools = self.fetch_blazar_pools(host_state)
for pool in [p for p in pools if p.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 = spec_obj.project_id
access = pool.metadata.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
owner_project_id = pool.metadata.get(owner)
if owner_project_id == spec_obj.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': spec_obj.project_id})
return False
return False
def host_passes(self, host_state, spec_obj):
"""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)
# Find which Pools the user wants to use (if any)
requested_pools = spec_obj.get_scheduler_hint('reservation')
if isinstance(requested_pools, str):
requested_pools = [requested_pools]
# the request is host reservation
if requested_pools:
return self.host_reservation_request(host_state, spec_obj,
# the request is instance reservation
if FLAVOR_EXTRA_SPEC in spec_obj.flavor.extra_specs.keys():
# Scheduling requests for instance reservation are processed by
# other Nova filters: AggregateInstanceExtraSpecsFilter,
# AggregateMultiTenancyIsolation, and
# ServerGroupAntiAffinityFilter. What BlazarFilter needs to
# do is just pass the host if the request has an instance
# reservation key.
return True
if self.fetch_blazar_pools(host_state):
# Host is in a blazar pool and non reservation request
LOG.info(_("In a user pool or in the freepool"))
return False
return True