Skip to content

Commit

Permalink
Parse forbidden in extra_specs
Browse files Browse the repository at this point in the history
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
  • Loading branch information
cdent committed Apr 17, 2018
1 parent fb0b785 commit 2c51688
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 14 deletions.
21 changes: 19 additions & 2 deletions doc/source/user/flavors.rst
Expand Up @@ -632,9 +632,26 @@ Required traits

The scheduler will pass required traits to the
``GET /allocation_candidates`` endpoint in the Placement API to include
only resource providers that can satisfy the required traits. Currently
the only valid value is ``required``. Any other value will be considered
only resource providers that can satisfy the required traits. In 17.0.0
the only valid value is ``required``. In 18.0.0 ``forbidden`` is added (see
below). Any other value will be considered
invalid.

The FilterScheduler is currently the only scheduler driver that supports
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.
9 changes: 9 additions & 0 deletions nova/scheduler/client/report.py
Expand Up @@ -339,6 +339,7 @@ def get_allocation_candidates(self, context, resources):
# and traits in the query string (via a new method on ResourceRequest).
res = resources.get_request_group(None).resources
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

resource_query = ",".join(
Expand All @@ -350,6 +351,14 @@ def get_allocation_candidates(self, context, resources):
}
if 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:
# NOTE(danms): In 1.21, placement cannot take an AND'd
# set of aggregates, only an OR'd set. Thus, if we have
Expand Down
15 changes: 9 additions & 6 deletions nova/scheduler/utils.py
Expand Up @@ -85,17 +85,20 @@ def _add_resource(self, groupid, rclass, amount):
self.get_request_group(groupid).resources[rclass] = amount

def _add_trait(self, groupid, trait_name, trait_type):
# Currently the only valid value for a trait entry is 'required'.
trait_vals = ('required',)
# Ensure the value is supported.
if trait_type not in trait_vals:
# Currently the only valid values for a trait entry are 'required'
# and 'forbidden'
trait_vals = ('required', 'forbidden')
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(
"Only (%(tvals)s) traits are supported. Received '%(val)s' "
"for key trait%(groupid)s.",
{"tvals": ', '.join(trait_vals), "groupid": groupid,
"val": trait_type})
return
self.get_request_group(groupid).required_traits.add(trait_name)
return

@classmethod
def from_extra_specs(cls, extra_specs):
Expand Down
12 changes: 8 additions & 4 deletions nova/tests/unit/scheduler/client/test_report.py
Expand Up @@ -1421,14 +1421,18 @@ def test_get_allocation_candidates(self):
'resources1:DISK_GB': '30',
'trait:CUSTOM_TRAIT1': 'required',
'trait:CUSTOM_TRAIT2': 'preferred',
'trait:CUSTOM_TRAIT3': 'forbidden',
'trait:CUSTOM_TRAIT4': 'forbidden',
})
resources.get_request_group(None).member_of = [
('agg1', 'agg2', 'agg3'), ('agg1', 'agg2')]
expected_path = '/allocation_candidates'
expected_query = {'resources': ['MEMORY_MB:1024,VCPU:1'],
'required': ['CUSTOM_TRAIT1'],
'member_of': ['in:agg1,agg2'],
'limit': ['1000']}
expected_query = {
'resources': ['MEMORY_MB:1024,VCPU:1'],
'required': ['CUSTOM_TRAIT1,!CUSTOM_TRAIT3,!CUSTOM_TRAIT4'],
'member_of': ['in:agg1,agg2'],
'limit': ['1000']
}

resp_mock.json.return_value = json_data
self.ks_adap_mock.get.return_value = resp_mock
Expand Down
12 changes: 10 additions & 2 deletions nova/tests/unit/scheduler/test_utils.py
Expand Up @@ -373,6 +373,7 @@ def test_resource_request_from_extra_specs(self):
# Key skipped because no colons
'nocolons': '42',
'trait:CUSTOM_MAGIC': 'required',
'trait:CUSTOM_BRONZE': 'forbidden',
# Resource skipped because invalid resource class name
'resources86:CUTSOM_MISSPELLED': '86',
'resources1:SRIOV_NET_VF': '1',
Expand All @@ -384,6 +385,7 @@ def test_resource_request_from_extra_specs(self):
# Trait skipped because unsupported value
'trait86:CUSTOM_GOLD': 'preferred',
'trait1:CUSTOM_PHYSNET_NET1': 'required',
'trait1:CUSTOM_PHYSNET_NET2': 'forbidden',
'resources2:SRIOV_NET_VF': '1',
'resources2:IPV4_ADDRESS': '2',
'trait2:CUSTOM_PHYSNET_NET2': 'required',
Expand All @@ -405,7 +407,10 @@ def test_resource_request_from_extra_specs(self):
required_traits={
'HW_CPU_X86_AVX',
'CUSTOM_MAGIC',
}
},
forbidden_traits={
'CUSTOM_BRONZE',
},
)
expected._rg_by_id['1'] = plib.RequestGroup(
resources={
Expand All @@ -414,7 +419,10 @@ def test_resource_request_from_extra_specs(self):
},
required_traits={
'CUSTOM_PHYSNET_NET1',
}
},
forbidden_traits={
'CUSTOM_PHYSNET_NET2',
},
)
expected._rg_by_id['2'] = plib.RequestGroup(
resources={
Expand Down
21 changes: 21 additions & 0 deletions releasenotes/notes/forbidden-traits-in-nova-478f1884a06e50e7.yaml
@@ -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.

0 comments on commit 2c51688

Please sign in to comment.