placement: support traits in allocation candidates API

This patch add new query parameter `required` to the
`GET /allocation_candidates` API, which is used to filter candidates
with required traits.  The candidate attached traits return in the
provider summary also. Those API changes are added by new microversion.

Also using specific exception TraitNotFound instead of the generic
exception ValueError when invalid traits in the request.

Change-Id: Id821b5b2768dcc698695ba6570c6201e1e9a8233
Implement blueprint add-trait-support-in-allocation-candidates
This commit is contained in:
He Jie Xu 2018-01-19 15:38:26 +08:00
parent bf55576e0b
commit 8a307bbdf4
14 changed files with 167 additions and 24 deletions

View File

@ -18,6 +18,7 @@ from oslo_log import log as logging
from oslo_serialization import jsonutils
from oslo_utils import encodeutils
from oslo_utils import timeutils
import six
import webob
from nova.api.openstack.placement import microversion
@ -117,9 +118,10 @@ def _transform_allocation_requests_list(alloc_reqs):
return results
def _transform_provider_summaries(p_sums):
def _transform_provider_summaries(p_sums, include_traits=False):
"""Turn supplied list of ProviderSummary objects into a dict, keyed by
resource provider UUID, of dicts of provider and inventory information.
resource provider UUID, of dicts of provider and inventory information. The
traits only show up when `include_traits` is `True`.
{
RP_UUID_1: {
@ -132,7 +134,11 @@ def _transform_provider_summaries(p_sums):
'capacity': 4,
'used': 0,
}
}
},
'traits': [
'HW_CPU_X86_AVX512F',
'HW_CPU_X86_AVX512CD'
]
},
RP_UUID_2: {
'resources': {
@ -144,20 +150,32 @@ def _transform_provider_summaries(p_sums):
'capacity': 4,
'used': 0,
}
}
},
'traits': [
'HW_NIC_OFFLOAD_TSO',
'HW_NIC_OFFLOAD_GRO'
]
}
}
"""
return {
ps.resource_provider.uuid: {
'resources': {
psr.resource_class: {
'capacity': psr.capacity,
'used': psr.used,
} for psr in ps.resources
}
} for ps in p_sums
}
ret = {}
for ps in p_sums:
resources = {
psr.resource_class: {
'capacity': psr.capacity,
'used': psr.used,
} for psr in ps.resources
}
ret[ps.resource_provider.uuid] = {'resources': resources}
if include_traits:
ret[ps.resource_provider.uuid]['traits'] = [
t.name for t in ps.traits]
return ret
def _transform_allocation_candidates(alloc_cands, want_version):
@ -175,7 +193,10 @@ def _transform_allocation_candidates(alloc_cands, want_version):
else:
a_reqs = _transform_allocation_requests_list(
alloc_cands.allocation_requests)
p_sums = _transform_provider_summaries(alloc_cands.provider_summaries)
include_traits = want_version.matches((1, 17))
p_sums = _transform_provider_summaries(alloc_cands.provider_summaries,
include_traits)
return {
'allocation_requests': a_reqs,
'provider_summaries': p_sums,
@ -195,7 +216,9 @@ def list_allocation_candidates(req):
context = req.environ['placement.context']
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
get_schema = schema.GET_SCHEMA_1_10
if want_version.matches((1, 16)):
if want_version.matches((1, 17)):
get_schema = schema.GET_SCHEMA_1_17
elif want_version.matches((1, 16)):
get_schema = schema.GET_SCHEMA_1_16
util.validate_query_params(req, get_schema)
@ -213,6 +236,8 @@ def list_allocation_candidates(req):
raise webob.exc.HTTPBadRequest(
_('Invalid resource class in resources parameter: %(error)s') %
{'error': exc})
except exception.TraitNotFound as exc:
raise webob.exc.HTTPBadRequest(six.text_type(exc))
response = req.response
trx_cands = _transform_allocation_candidates(cands, want_version)

View File

@ -58,6 +58,8 @@ VERSIONS = [
# representation and 'in_tree' filter on GET /resource_providers
'1.15', # Include last-modified and cache-control headers
'1.16', # Add 'limit' query parameter to GET /allocation_candidates
'1.17', # Add 'required' query parameter to GET /allocation_candidates and
# return traits in the provider summary.
]

View File

@ -214,3 +214,10 @@ header has been added to prevent inadvertent caching of resources.
Add support for a ``limit`` query parameter when making a
``GET /allocation_candidates`` request. The parameter accepts an integer
value, `N`, which limits the maximum number of candidates returned.
1.17 Add 'required' parameter to the allocation candidates
----------------------------------------------------------
Add the `required` parameter to the `GET /allocation_candidates` API. It
accepts a list of traits separated by `,`. The provider summary in the response
will include the attached traits also.

View File

@ -40,3 +40,9 @@ GET_SCHEMA_1_16['properties']['limit'] = {
"minimum": 1,
"minLength": 1
}
# Add required parameter.
GET_SCHEMA_1_17 = copy.deepcopy(GET_SCHEMA_1_16)
GET_SCHEMA_1_17['properties']['required'] = {
"type": ["string"]
}

View File

@ -306,8 +306,8 @@ def normalize_traits_qs_param(val):
"""
ret = set(substr.strip() for substr in val.split(','))
if not all(trait for trait in ret):
msg = _('Malformed traits parameter. Expected query string value '
'of the form: HW_CPU_X86_VMX,CUSTOM_MAGIC. '
msg = _('Invalid query string parameters: Expected \'required\' '
'parameter value of the form: HW_CPU_X86_VMX,CUSTOM_MAGIC. '
'Got: "%s"') % val
raise webob.exc.HTTPBadRequest(msg)
return ret

View File

@ -2216,7 +2216,7 @@ class PowerVMAPIFailed(NovaException):
class TraitNotFound(NotFound):
msg_fmt = _("No such trait %(name)s.")
msg_fmt = _("No such trait(s): %(name)s.")
class TraitExists(NovaException):

View File

@ -3515,7 +3515,7 @@ class AllocationCandidates(base.NovaObject):
# Double-check that we found a trait ID for each requested name
if len(trait_map) != len(traits):
missing = traits - set(trait_map)
raise ValueError(_("Unknown traits requested: %s") % missing)
raise exception.TraitNotFound(name=', '.join(missing))
# Contains a set of resource provider IDs that share some inventory for
# each resource class requested. We do this here as an optimization. If

View File

@ -271,6 +271,10 @@ class SharedStorageFixture(APIFixture):
inv_list = rp_obj.InventoryList(objects=[vcpu_inv, ram_inv])
cn.set_inventory(inv_list)
t_avx_sse = rp_obj.Trait.get_by_name(self.context, "HW_CPU_X86_SSE")
t_avx_sse2 = rp_obj.Trait.get_by_name(self.context, "HW_CPU_X86_SSE2")
cn1.set_traits(rp_obj.TraitList(objects=[t_avx_sse, t_avx_sse2]))
# Populate shared storage provider with DISK_GB inventory
disk_inv = rp_obj.Inventory(
self.context,

View File

@ -170,3 +170,80 @@ tests:
openstack-api-version: placement 1.16
response_json_paths:
$.allocation_requests.`len`: 1
- name: get allocation candidates with required traits in old version
GET: /allocation_candidates?resources=VCPU:1,MEMORY_MB:1024,DISK_GB:100&required=HW_CPU_X86_SSE
status: 400
request_headers:
openstack-api-version: placement 1.16
response_strings:
- Invalid query string parameters
- "'required' was unexpected"
- name: get allocation candidates without traits summary in old version
GET: /allocation_candidates?resources=VCPU:1,MEMORY_MB:1024,DISK_GB:100
status: 200
request_headers:
openstack-api-version: placement 1.16
response_json_paths:
$.provider_summaries["$ENVIRON['CN1_UUID']"].`len`: 1
$.provider_summaries["$ENVIRON['CN2_UUID']"].`len`: 1
- name: get allocation candidates with invalid trait
GET: /allocation_candidates?resources=VCPU:1,MEMORY_MB:1024,DISK_GB:100&required=INVALID_TRAIT
status: 400
request_headers:
openstack-api-version: placement 1.17
response_strings:
- No such trait(s)
- name: get allocation candidates with empty required value
GET: /allocation_candidates?resources=VCPU:1,MEMORY_MB:1024,DISK_GB:100&required=
status: 400
request_headers:
openstack-api-version: placement 1.17
response_strings:
- "Invalid query string parameters: Expected 'required' parameter value of the form: HW_CPU_X86_VMX,CUSTOM_MAGIC."
- name: get allocation candidates with invalid required value
GET: /allocation_candidates?resources=VCPU:1,MEMORY_MB:1024,DISK_GB:100&required=,,
status: 400
request_headers:
openstack-api-version: placement 1.17
response_strings:
- "Invalid query string parameters: Expected 'required' parameter value of the form: HW_CPU_X86_VMX,CUSTOM_MAGIC."
- name: get allocation candidates with required trait
GET: /allocation_candidates?resources=VCPU:1,MEMORY_MB:1024,DISK_GB:100&required=HW_CPU_X86_SSE
status: 200
request_headers:
openstack-api-version: placement 1.17
response_json_paths:
$.allocation_requests.`len`: 1
$.provider_summaries.`len`: 2
$.provider_summaries["$ENVIRON['CN1_UUID']"].`len`: 2
$.provider_summaries["$ENVIRON['CN1_UUID']"].traits.`sorted`:
- HW_CPU_X86_SSE
- HW_CPU_X86_SSE2
- name: get allocation candidates with multiple required traits
GET: /allocation_candidates?resources=VCPU:1,MEMORY_MB:1024,DISK_GB:100&required=HW_CPU_X86_SSE,HW_CPU_X86_SSE2
status: 200
request_headers:
openstack-api-version: placement 1.17
response_json_paths:
$.allocation_requests.`len`: 1
$.provider_summaries.`len`: 2
$.provider_summaries["$ENVIRON['CN1_UUID']"].`len`: 2
$.provider_summaries["$ENVIRON['CN1_UUID']"].traits.`sorted`:
- HW_CPU_X86_SSE
- HW_CPU_X86_SSE2
- name: get allocation candidates with required trait and no matching
GET: /allocation_candidates?resources=VCPU:1,MEMORY_MB:1024,DISK_GB:100&required=HW_CPU_X86_SSE3
status: 200
request_headers:
openstack-api-version: placement 1.17
response_json_paths:
$.allocation_requests.`len`: 0
$.provider_summaries.`len`: 0

View File

@ -39,13 +39,13 @@ tests:
response_json_paths:
$.errors[0].title: Not Acceptable
- name: latest microversion is 1.16
- name: latest microversion is 1.17
GET: /
request_headers:
openstack-api-version: placement latest
response_headers:
vary: /OpenStack-API-Version/
openstack-api-version: placement 1.16
openstack-api-version: placement 1.17
- name: other accept header bad version
GET: /

View File

@ -425,7 +425,7 @@ class AllocationCandidatesTestCase(ProviderDBBase):
requests = [placement_lib.RequestGroup(
use_same_provider=False, resources=self.requested_resources,
required_traits=missing)]
self.assertRaises(ValueError,
self.assertRaises(exception.TraitNotFound,
rp_obj.AllocationCandidates.get_by_requests,
self.ctx, requests)

View File

@ -31,6 +31,7 @@ Request
- resources: resources_query_required
- limit: allocation_candidates_limit
- required: allocation_candidates_required
Response (microversions 1.12 - )
--------------------------------

View File

@ -50,6 +50,16 @@ allocation_candidates_limit:
description: >
A positive integer used to limit the maximum number of allocation
candidates returned in the response.
allocation_candidates_required:
type: string
in: query
required: false
min_version: 1.17
description: >
Accepts a list of traits separated by `,`. Allocation requests in the
response will be for resource providers that have capacity for all
requested resources and the set of those resource providers will
*collectively* contain all of the required traits.
member_of:
type: string
in: query
@ -241,7 +251,8 @@ provider_summaries:
required: true
description: >
A dictionary keyed by resource provider UUID,
of dictionaries of inventory/capacity information.
of dictionaries of inventory/capacity information. The list of traits
the resource provider has associated with it is included in version `1.17`.
reserved: &reserved
type: integer
in: body

View File

@ -0,0 +1,10 @@
---
features:
- |
Add ``required`` query parameter to the ``GET /allocation_candidates`` API
in new placement microversion 1.17. The parameter accepts a list of traits
separated by ``,``, which is used to further limit the list of allocation
requests to resource providers that have the capacity to fulfill the
requested resources AND *collectively* have all of the required traits
associated with them. In the same microversion, the candidate attached
traits returned in the provider summary.