Parse forbidden in extra_specs
Accept forbidden traits in the processing of extra_specs, with the format of: trait:CUSTOM_MAGIC=forbidden This will be transformed into required=!CUSTOM_MAGIC when the traits are assembled into a request to GET /allocation_candidates. Implements blueprint forbidden-traits-in-nova Change-Id: I31e609aef47d2fea03f279e4bfdd30f072d062b4
This commit is contained in:
parent
fb0b785169
commit
2c51688558
@ -632,9 +632,26 @@ Required traits
|
|||||||
|
|
||||||
The scheduler will pass required traits to the
|
The scheduler will pass required traits to the
|
||||||
``GET /allocation_candidates`` endpoint in the Placement API to include
|
``GET /allocation_candidates`` endpoint in the Placement API to include
|
||||||
only resource providers that can satisfy the required traits. Currently
|
only resource providers that can satisfy the required traits. In 17.0.0
|
||||||
the only valid value is ``required``. Any other value will be considered
|
the only valid value is ``required``. In 18.0.0 ``forbidden`` is added (see
|
||||||
|
below). Any other value will be considered
|
||||||
invalid.
|
invalid.
|
||||||
|
|
||||||
The FilterScheduler is currently the only scheduler driver that supports
|
The FilterScheduler is currently the only scheduler driver that supports
|
||||||
this feature.
|
this feature.
|
||||||
|
|
||||||
|
Forbidden traits
|
||||||
|
Added in the 18.0.0 Rocky release.
|
||||||
|
|
||||||
|
Forbidden traits are similar to required traits, described above, but
|
||||||
|
instead of specifying the set of traits that must be satisfied by a compute
|
||||||
|
node, forbidden traits must **not** be present.
|
||||||
|
|
||||||
|
The syntax of the extra spec is ``trait:<trait_name>=forbidden``, for
|
||||||
|
example:
|
||||||
|
|
||||||
|
- trait:HW_CPU_X86_AVX2=forbidden
|
||||||
|
- trait:STORAGE_DISK_SSD=forbidden
|
||||||
|
|
||||||
|
The FilterScheduler is currently the only scheduler driver that supports
|
||||||
|
this feature.
|
||||||
|
@ -339,6 +339,7 @@ class SchedulerReportClient(object):
|
|||||||
# and traits in the query string (via a new method on ResourceRequest).
|
# and traits in the query string (via a new method on ResourceRequest).
|
||||||
res = resources.get_request_group(None).resources
|
res = resources.get_request_group(None).resources
|
||||||
required_traits = resources.get_request_group(None).required_traits
|
required_traits = resources.get_request_group(None).required_traits
|
||||||
|
forbidden_traits = resources.get_request_group(None).forbidden_traits
|
||||||
aggregates = resources.get_request_group(None).member_of
|
aggregates = resources.get_request_group(None).member_of
|
||||||
|
|
||||||
resource_query = ",".join(
|
resource_query = ",".join(
|
||||||
@ -350,6 +351,14 @@ class SchedulerReportClient(object):
|
|||||||
}
|
}
|
||||||
if required_traits:
|
if required_traits:
|
||||||
qs_params['required'] = ",".join(required_traits)
|
qs_params['required'] = ",".join(required_traits)
|
||||||
|
if forbidden_traits:
|
||||||
|
# Sorted to make testing easier to manage and for
|
||||||
|
# predictability.
|
||||||
|
forbiddens = ',!'.join(sorted(forbidden_traits))
|
||||||
|
if qs_params['required']:
|
||||||
|
qs_params['required'] += ',!' + forbiddens
|
||||||
|
else:
|
||||||
|
qs_params['required'] = '!' + forbiddens
|
||||||
if aggregates:
|
if aggregates:
|
||||||
# NOTE(danms): In 1.21, placement cannot take an AND'd
|
# NOTE(danms): In 1.21, placement cannot take an AND'd
|
||||||
# set of aggregates, only an OR'd set. Thus, if we have
|
# set of aggregates, only an OR'd set. Thus, if we have
|
||||||
|
@ -85,17 +85,20 @@ class ResourceRequest(object):
|
|||||||
self.get_request_group(groupid).resources[rclass] = amount
|
self.get_request_group(groupid).resources[rclass] = amount
|
||||||
|
|
||||||
def _add_trait(self, groupid, trait_name, trait_type):
|
def _add_trait(self, groupid, trait_name, trait_type):
|
||||||
# Currently the only valid value for a trait entry is 'required'.
|
# Currently the only valid values for a trait entry are 'required'
|
||||||
trait_vals = ('required',)
|
# and 'forbidden'
|
||||||
# Ensure the value is supported.
|
trait_vals = ('required', 'forbidden')
|
||||||
if trait_type not in trait_vals:
|
if trait_type == 'required':
|
||||||
|
self.get_request_group(groupid).required_traits.add(trait_name)
|
||||||
|
elif trait_type == 'forbidden':
|
||||||
|
self.get_request_group(groupid).forbidden_traits.add(trait_name)
|
||||||
|
else:
|
||||||
LOG.warning(
|
LOG.warning(
|
||||||
"Only (%(tvals)s) traits are supported. Received '%(val)s' "
|
"Only (%(tvals)s) traits are supported. Received '%(val)s' "
|
||||||
"for key trait%(groupid)s.",
|
"for key trait%(groupid)s.",
|
||||||
{"tvals": ', '.join(trait_vals), "groupid": groupid,
|
{"tvals": ', '.join(trait_vals), "groupid": groupid,
|
||||||
"val": trait_type})
|
"val": trait_type})
|
||||||
return
|
return
|
||||||
self.get_request_group(groupid).required_traits.add(trait_name)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_extra_specs(cls, extra_specs):
|
def from_extra_specs(cls, extra_specs):
|
||||||
|
@ -1421,14 +1421,18 @@ class TestProviderOperations(SchedulerReportClientTestCase):
|
|||||||
'resources1:DISK_GB': '30',
|
'resources1:DISK_GB': '30',
|
||||||
'trait:CUSTOM_TRAIT1': 'required',
|
'trait:CUSTOM_TRAIT1': 'required',
|
||||||
'trait:CUSTOM_TRAIT2': 'preferred',
|
'trait:CUSTOM_TRAIT2': 'preferred',
|
||||||
|
'trait:CUSTOM_TRAIT3': 'forbidden',
|
||||||
|
'trait:CUSTOM_TRAIT4': 'forbidden',
|
||||||
})
|
})
|
||||||
resources.get_request_group(None).member_of = [
|
resources.get_request_group(None).member_of = [
|
||||||
('agg1', 'agg2', 'agg3'), ('agg1', 'agg2')]
|
('agg1', 'agg2', 'agg3'), ('agg1', 'agg2')]
|
||||||
expected_path = '/allocation_candidates'
|
expected_path = '/allocation_candidates'
|
||||||
expected_query = {'resources': ['MEMORY_MB:1024,VCPU:1'],
|
expected_query = {
|
||||||
'required': ['CUSTOM_TRAIT1'],
|
'resources': ['MEMORY_MB:1024,VCPU:1'],
|
||||||
'member_of': ['in:agg1,agg2'],
|
'required': ['CUSTOM_TRAIT1,!CUSTOM_TRAIT3,!CUSTOM_TRAIT4'],
|
||||||
'limit': ['1000']}
|
'member_of': ['in:agg1,agg2'],
|
||||||
|
'limit': ['1000']
|
||||||
|
}
|
||||||
|
|
||||||
resp_mock.json.return_value = json_data
|
resp_mock.json.return_value = json_data
|
||||||
self.ks_adap_mock.get.return_value = resp_mock
|
self.ks_adap_mock.get.return_value = resp_mock
|
||||||
|
@ -373,6 +373,7 @@ class TestUtils(test.NoDBTestCase):
|
|||||||
# Key skipped because no colons
|
# Key skipped because no colons
|
||||||
'nocolons': '42',
|
'nocolons': '42',
|
||||||
'trait:CUSTOM_MAGIC': 'required',
|
'trait:CUSTOM_MAGIC': 'required',
|
||||||
|
'trait:CUSTOM_BRONZE': 'forbidden',
|
||||||
# Resource skipped because invalid resource class name
|
# Resource skipped because invalid resource class name
|
||||||
'resources86:CUTSOM_MISSPELLED': '86',
|
'resources86:CUTSOM_MISSPELLED': '86',
|
||||||
'resources1:SRIOV_NET_VF': '1',
|
'resources1:SRIOV_NET_VF': '1',
|
||||||
@ -384,6 +385,7 @@ class TestUtils(test.NoDBTestCase):
|
|||||||
# Trait skipped because unsupported value
|
# Trait skipped because unsupported value
|
||||||
'trait86:CUSTOM_GOLD': 'preferred',
|
'trait86:CUSTOM_GOLD': 'preferred',
|
||||||
'trait1:CUSTOM_PHYSNET_NET1': 'required',
|
'trait1:CUSTOM_PHYSNET_NET1': 'required',
|
||||||
|
'trait1:CUSTOM_PHYSNET_NET2': 'forbidden',
|
||||||
'resources2:SRIOV_NET_VF': '1',
|
'resources2:SRIOV_NET_VF': '1',
|
||||||
'resources2:IPV4_ADDRESS': '2',
|
'resources2:IPV4_ADDRESS': '2',
|
||||||
'trait2:CUSTOM_PHYSNET_NET2': 'required',
|
'trait2:CUSTOM_PHYSNET_NET2': 'required',
|
||||||
@ -405,7 +407,10 @@ class TestUtils(test.NoDBTestCase):
|
|||||||
required_traits={
|
required_traits={
|
||||||
'HW_CPU_X86_AVX',
|
'HW_CPU_X86_AVX',
|
||||||
'CUSTOM_MAGIC',
|
'CUSTOM_MAGIC',
|
||||||
}
|
},
|
||||||
|
forbidden_traits={
|
||||||
|
'CUSTOM_BRONZE',
|
||||||
|
},
|
||||||
)
|
)
|
||||||
expected._rg_by_id['1'] = plib.RequestGroup(
|
expected._rg_by_id['1'] = plib.RequestGroup(
|
||||||
resources={
|
resources={
|
||||||
@ -414,7 +419,10 @@ class TestUtils(test.NoDBTestCase):
|
|||||||
},
|
},
|
||||||
required_traits={
|
required_traits={
|
||||||
'CUSTOM_PHYSNET_NET1',
|
'CUSTOM_PHYSNET_NET1',
|
||||||
}
|
},
|
||||||
|
forbidden_traits={
|
||||||
|
'CUSTOM_PHYSNET_NET2',
|
||||||
|
},
|
||||||
)
|
)
|
||||||
expected._rg_by_id['2'] = plib.RequestGroup(
|
expected._rg_by_id['2'] = plib.RequestGroup(
|
||||||
resources={
|
resources={
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Added support for forbidden traits to the scheduler. A flavor extra spec
|
||||||
|
is extended to support specifying the forbidden traits. The syntax of
|
||||||
|
extra spec is ``trait:<trait_name>=forbidden``, for example:
|
||||||
|
|
||||||
|
- trait:HW_CPU_X86_AVX2=forbidden
|
||||||
|
- trait:STORAGE_DISK_SSD=forbidden
|
||||||
|
|
||||||
|
The scheduler will pass the forbidden traits to the
|
||||||
|
``GET /allocation_candidates`` endpoint in the Placement API to include
|
||||||
|
only resource providers that do not include the forbidden traits. Currently
|
||||||
|
the only valid values are ``required`` and ``forbidden``. Any other values
|
||||||
|
will be considered invalid.
|
||||||
|
|
||||||
|
This requires that the Placement API version 1.22 is available before
|
||||||
|
the ``nova-scheduler`` service can use this feature.
|
||||||
|
|
||||||
|
The FilterScheduler is currently the only scheduler driver that supports
|
||||||
|
this feature.
|
Loading…
x
Reference in New Issue
Block a user