Allow preemptible instances to run on unused hosts

Preemptible instances run on a configurable host aggregate, using the
freepool by default.

Compared to the proposed spec, the use of a separate aggregate allows to
better control scheduling of preemptibles. For example, we can prevent
preemptible instances from being launched on hosts that are about to be
used by reservations. To be used, this feature will need additional
support in the main Blazar service.

Blueprint: blazar-preemptible-instances
Change-Id: I294c22b22a1d9e3df01c56a518476a33782d1196
This commit is contained in:
Pierre Riteau 2022-02-03 17:35:25 +01:00
parent 63560cf765
commit 3d01888c14
3 changed files with 119 additions and 7 deletions

View File

@ -19,16 +19,26 @@ from blazarnova.i18n import _
from nova.scheduler import filters
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils.strutils import bool_from_string
LOG = logging.getLogger(__name__)
FLAVOR_EXTRA_SPEC = "aggregate_instance_extra_specs:reservation"
FLAVOR_PREEMPTIBLE = "blazar:preemptible"
opts = [
cfg.StrOpt('aggregate_freepool_name',
default='freepool',
help='Name of the special aggregate where all hosts '
'are candidate for physical host reservation'),
cfg.BoolOpt('allow_preemptibles',
default=False,
help='Whether to allow preemptible instances to be scheduled '
'on hosts in the preemptible aggregate'),
cfg.StrOpt('preemptible_aggregate',
default='freepool',
help='Name of the aggregate where hosts can run preemptible '
'instances'),
cfg.StrOpt('project_id_key',
default='blazar:tenant',
help='Aggregate metadata value for key matching project_id'),
@ -54,14 +64,17 @@ class BlazarFilter(filters.BaseHostFilter):
aggregates = host_state.aggregates
pools = []
for agg in aggregates:
if (str(agg.availability_zone).startswith(
cfg.CONF['blazar:physical:host'].blazar_az_prefix)
if (agg.availability_zone and
str(agg.availability_zone).startswith(
cfg.CONF['blazar:physical:host'].blazar_az_prefix)
# NOTE(hiro-kobayashi): following 2 lines are for keeping
# backward compatibility
or str(agg.availability_zone).startswith('blazar:')):
pools.append(agg)
if agg.name == (
cfg.CONF['blazar:physical:host'].aggregate_freepool_name):
if agg.name in [
cfg.CONF['blazar:physical:host'].aggregate_freepool_name,
cfg.CONF['blazar:physical:host'].preemptible_aggregate]:
pools.append(agg)
return pools
@ -121,8 +134,9 @@ class BlazarFilter(filters.BaseHostFilter):
return self.host_reservation_request(host_state, spec_obj,
requested_pools)
extra_specs = spec_obj.flavor.extra_specs
# the request is instance reservation
if FLAVOR_EXTRA_SPEC in spec_obj.flavor.extra_specs.keys():
if FLAVOR_EXTRA_SPEC in extra_specs.keys():
# Scheduling requests for instance reservation are processed by
# other Nova filters: AggregateInstanceExtraSpecsFilter,
# AggregateMultiTenancyIsolation, and
@ -131,9 +145,22 @@ class BlazarFilter(filters.BaseHostFilter):
# reservation key.
return True
if self.fetch_blazar_pools(host_state):
allow_preempt = cfg.CONF['blazar:physical:host'].allow_preemptibles
preemptible = bool_from_string(
extra_specs.get(FLAVOR_PREEMPTIBLE, False))
blazar_pools = self.fetch_blazar_pools(host_state)
# If the request is for a preemptible instance and they are allowed
if allow_preempt and preemptible:
if (len(blazar_pools) == 1 and blazar_pools[0].name ==
cfg.CONF['blazar:physical:host'].preemptible_aggregate):
# Pass host if it only belongs to the preemptibles aggregate
LOG.info("Host %s allowed for preemptibles" % host_state)
return True
return False
if blazar_pools:
# Host is in a blazar pool and non reservation request
LOG.info(_("In a user pool or in the freepool"))
LOG.info("Host is in a reservation aggregate or in the freepool")
return False
return True

View File

@ -284,3 +284,81 @@ class BlazarFilterTestCase(test.TestCase):
self.host.passes = self.f.host_passes(self.host, self.spec_obj)
self.assertTrue(self.host.passes)
def test_blazar_filter_host_in_freepool_for_preemptibles(self):
# Given preemptibles are allowed
cfg.CONF.set_override('allow_preemptibles', True,
group='blazar:physical:host')
self.addCleanup(cfg.CONF.clear_override, 'allow_preemptibles',
group='blazar:physical:host')
# Given the host is in the free pool
self.host.aggregates = [
objects.Aggregate(
name='freepool',
metadata={'availability_zone': ''})]
# And the instance is launched with a flavor marked as preemptible
self.spec_obj.flavor.extra_specs = {'blazar:preemptible': 'true'}
# When the host goes through the filter
self.host.passes = self.f.host_passes(self.host, self.spec_obj)
# Then the host shall pass
self.assertTrue(self.host.passes)
def test_blazar_filter_host_in_preemptibles(self):
# Given preemptibles are allowed and dedicated aggregate is used
cfg.CONF.set_override('allow_preemptibles', True,
group='blazar:physical:host')
self.addCleanup(cfg.CONF.clear_override, 'allow_preemptibles',
group='blazar:physical:host')
cfg.CONF.set_override('preemptible_aggregate', 'preemptibles',
group='blazar:physical:host')
self.addCleanup(cfg.CONF.clear_override, 'preemptible_aggregate',
group='blazar:physical:host')
# Given the host is in the preemptibles aggregate
self.host.aggregates = [
objects.Aggregate(
name='preemptibles',
metadata={'availability_zone': ''})]
# And the instance is launched with a flavor marked as preemptible
self.spec_obj.flavor.extra_specs = {'blazar:preemptible': 'true'}
# When the host goes through the filter
self.host.passes = self.f.host_passes(self.host, self.spec_obj)
# Then the host shall pass
self.assertTrue(self.host.passes)
def test_blazar_filter_host_not_in_preemptibles(self):
# Given preemptibles are allowed and dedicated aggregate is used
cfg.CONF.set_override('allow_preemptibles', True,
group='blazar:physical:host')
self.addCleanup(cfg.CONF.clear_override, 'allow_preemptibles',
group='blazar:physical:host')
cfg.CONF.set_override('preemptible_aggregate', 'preemptibles',
group='blazar:physical:host')
self.addCleanup(cfg.CONF.clear_override, 'preemptible_aggregate',
group='blazar:physical:host')
# Given the host is in the free pool
self.host.aggregates = [
objects.Aggregate(
name=cfg.CONF['blazar:physical:host'].aggregate_freepool_name,
metadata={'availability_zone': 'unknown',
self.spec_obj.project_id: True})]
# And the instance is launched with a flavor marked as preemptible
self.spec_obj.flavor.extra_specs = {'blazar:preemptible': 'true'}
# When the host goes through the filter
self.host.passes = self.f.host_passes(self.host, self.spec_obj)
# Then the host shall NOT pass
self.assertFalse(self.host.passes)

View File

@ -0,0 +1,7 @@
---
features:
- |
Adds support for running preemptible instances on unreserved Blazar hosts.
This functionality is disabled by default and can be enabled using
``[blazar:physical:host]/allow_preemptibles``. It also needs to be enabled
in the Blazar service.