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:
parent
63560cf765
commit
3d01888c14
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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.
|
Loading…
Reference in New Issue
Block a user