Merge "placement: support traits in allocation candidates API"
This commit is contained in:
@@ -18,6 +18,7 @@ from oslo_log import log as logging
|
|||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
from oslo_utils import encodeutils
|
from oslo_utils import encodeutils
|
||||||
from oslo_utils import timeutils
|
from oslo_utils import timeutils
|
||||||
|
import six
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
from nova.api.openstack.placement import microversion
|
from nova.api.openstack.placement import microversion
|
||||||
@@ -117,9 +118,10 @@ def _transform_allocation_requests_list(alloc_reqs):
|
|||||||
return results
|
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
|
"""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: {
|
RP_UUID_1: {
|
||||||
@@ -132,7 +134,11 @@ def _transform_provider_summaries(p_sums):
|
|||||||
'capacity': 4,
|
'capacity': 4,
|
||||||
'used': 0,
|
'used': 0,
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
'traits': [
|
||||||
|
'HW_CPU_X86_AVX512F',
|
||||||
|
'HW_CPU_X86_AVX512CD'
|
||||||
|
]
|
||||||
},
|
},
|
||||||
RP_UUID_2: {
|
RP_UUID_2: {
|
||||||
'resources': {
|
'resources': {
|
||||||
@@ -144,20 +150,32 @@ def _transform_provider_summaries(p_sums):
|
|||||||
'capacity': 4,
|
'capacity': 4,
|
||||||
'used': 0,
|
'used': 0,
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
'traits': [
|
||||||
|
'HW_NIC_OFFLOAD_TSO',
|
||||||
|
'HW_NIC_OFFLOAD_GRO'
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
return {
|
|
||||||
ps.resource_provider.uuid: {
|
ret = {}
|
||||||
'resources': {
|
|
||||||
psr.resource_class: {
|
for ps in p_sums:
|
||||||
'capacity': psr.capacity,
|
resources = {
|
||||||
'used': psr.used,
|
psr.resource_class: {
|
||||||
} for psr in ps.resources
|
'capacity': psr.capacity,
|
||||||
}
|
'used': psr.used,
|
||||||
} for ps in p_sums
|
} 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):
|
def _transform_allocation_candidates(alloc_cands, want_version):
|
||||||
@@ -175,7 +193,10 @@ def _transform_allocation_candidates(alloc_cands, want_version):
|
|||||||
else:
|
else:
|
||||||
a_reqs = _transform_allocation_requests_list(
|
a_reqs = _transform_allocation_requests_list(
|
||||||
alloc_cands.allocation_requests)
|
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 {
|
return {
|
||||||
'allocation_requests': a_reqs,
|
'allocation_requests': a_reqs,
|
||||||
'provider_summaries': p_sums,
|
'provider_summaries': p_sums,
|
||||||
@@ -195,7 +216,9 @@ def list_allocation_candidates(req):
|
|||||||
context = req.environ['placement.context']
|
context = req.environ['placement.context']
|
||||||
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
||||||
get_schema = schema.GET_SCHEMA_1_10
|
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
|
get_schema = schema.GET_SCHEMA_1_16
|
||||||
util.validate_query_params(req, get_schema)
|
util.validate_query_params(req, get_schema)
|
||||||
|
|
||||||
@@ -213,6 +236,8 @@ def list_allocation_candidates(req):
|
|||||||
raise webob.exc.HTTPBadRequest(
|
raise webob.exc.HTTPBadRequest(
|
||||||
_('Invalid resource class in resources parameter: %(error)s') %
|
_('Invalid resource class in resources parameter: %(error)s') %
|
||||||
{'error': exc})
|
{'error': exc})
|
||||||
|
except exception.TraitNotFound as exc:
|
||||||
|
raise webob.exc.HTTPBadRequest(six.text_type(exc))
|
||||||
|
|
||||||
response = req.response
|
response = req.response
|
||||||
trx_cands = _transform_allocation_candidates(cands, want_version)
|
trx_cands = _transform_allocation_candidates(cands, want_version)
|
||||||
|
@@ -58,6 +58,8 @@ VERSIONS = [
|
|||||||
# representation and 'in_tree' filter on GET /resource_providers
|
# representation and 'in_tree' filter on GET /resource_providers
|
||||||
'1.15', # Include last-modified and cache-control headers
|
'1.15', # Include last-modified and cache-control headers
|
||||||
'1.16', # Add 'limit' query parameter to GET /allocation_candidates
|
'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.
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@@ -214,3 +214,10 @@ header has been added to prevent inadvertent caching of resources.
|
|||||||
Add support for a ``limit`` query parameter when making a
|
Add support for a ``limit`` query parameter when making a
|
||||||
``GET /allocation_candidates`` request. The parameter accepts an integer
|
``GET /allocation_candidates`` request. The parameter accepts an integer
|
||||||
value, `N`, which limits the maximum number of candidates returned.
|
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.
|
||||||
|
@@ -40,3 +40,9 @@ GET_SCHEMA_1_16['properties']['limit'] = {
|
|||||||
"minimum": 1,
|
"minimum": 1,
|
||||||
"minLength": 1
|
"minLength": 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Add required parameter.
|
||||||
|
GET_SCHEMA_1_17 = copy.deepcopy(GET_SCHEMA_1_16)
|
||||||
|
GET_SCHEMA_1_17['properties']['required'] = {
|
||||||
|
"type": ["string"]
|
||||||
|
}
|
||||||
|
@@ -306,8 +306,8 @@ def normalize_traits_qs_param(val):
|
|||||||
"""
|
"""
|
||||||
ret = set(substr.strip() for substr in val.split(','))
|
ret = set(substr.strip() for substr in val.split(','))
|
||||||
if not all(trait for trait in ret):
|
if not all(trait for trait in ret):
|
||||||
msg = _('Malformed traits parameter. Expected query string value '
|
msg = _('Invalid query string parameters: Expected \'required\' '
|
||||||
'of the form: HW_CPU_X86_VMX,CUSTOM_MAGIC. '
|
'parameter value of the form: HW_CPU_X86_VMX,CUSTOM_MAGIC. '
|
||||||
'Got: "%s"') % val
|
'Got: "%s"') % val
|
||||||
raise webob.exc.HTTPBadRequest(msg)
|
raise webob.exc.HTTPBadRequest(msg)
|
||||||
return ret
|
return ret
|
||||||
|
@@ -2265,7 +2265,7 @@ class PowerVMAPIFailed(NovaException):
|
|||||||
|
|
||||||
|
|
||||||
class TraitNotFound(NotFound):
|
class TraitNotFound(NotFound):
|
||||||
msg_fmt = _("No such trait %(name)s.")
|
msg_fmt = _("No such trait(s): %(name)s.")
|
||||||
|
|
||||||
|
|
||||||
class TraitExists(NovaException):
|
class TraitExists(NovaException):
|
||||||
|
@@ -3515,7 +3515,7 @@ class AllocationCandidates(base.NovaObject):
|
|||||||
# Double-check that we found a trait ID for each requested name
|
# Double-check that we found a trait ID for each requested name
|
||||||
if len(trait_map) != len(traits):
|
if len(trait_map) != len(traits):
|
||||||
missing = traits - set(trait_map)
|
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
|
# Contains a set of resource provider IDs that share some inventory for
|
||||||
# each resource class requested. We do this here as an optimization. If
|
# each resource class requested. We do this here as an optimization. If
|
||||||
|
@@ -271,6 +271,10 @@ class SharedStorageFixture(APIFixture):
|
|||||||
inv_list = rp_obj.InventoryList(objects=[vcpu_inv, ram_inv])
|
inv_list = rp_obj.InventoryList(objects=[vcpu_inv, ram_inv])
|
||||||
cn.set_inventory(inv_list)
|
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
|
# Populate shared storage provider with DISK_GB inventory
|
||||||
disk_inv = rp_obj.Inventory(
|
disk_inv = rp_obj.Inventory(
|
||||||
self.context,
|
self.context,
|
||||||
|
@@ -170,3 +170,80 @@ tests:
|
|||||||
openstack-api-version: placement 1.16
|
openstack-api-version: placement 1.16
|
||||||
response_json_paths:
|
response_json_paths:
|
||||||
$.allocation_requests.`len`: 1
|
$.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
|
||||||
|
@@ -39,13 +39,13 @@ tests:
|
|||||||
response_json_paths:
|
response_json_paths:
|
||||||
$.errors[0].title: Not Acceptable
|
$.errors[0].title: Not Acceptable
|
||||||
|
|
||||||
- name: latest microversion is 1.16
|
- name: latest microversion is 1.17
|
||||||
GET: /
|
GET: /
|
||||||
request_headers:
|
request_headers:
|
||||||
openstack-api-version: placement latest
|
openstack-api-version: placement latest
|
||||||
response_headers:
|
response_headers:
|
||||||
vary: /OpenStack-API-Version/
|
vary: /OpenStack-API-Version/
|
||||||
openstack-api-version: placement 1.16
|
openstack-api-version: placement 1.17
|
||||||
|
|
||||||
- name: other accept header bad version
|
- name: other accept header bad version
|
||||||
GET: /
|
GET: /
|
||||||
|
@@ -425,7 +425,7 @@ class AllocationCandidatesTestCase(ProviderDBBase):
|
|||||||
requests = [placement_lib.RequestGroup(
|
requests = [placement_lib.RequestGroup(
|
||||||
use_same_provider=False, resources=self.requested_resources,
|
use_same_provider=False, resources=self.requested_resources,
|
||||||
required_traits=missing)]
|
required_traits=missing)]
|
||||||
self.assertRaises(ValueError,
|
self.assertRaises(exception.TraitNotFound,
|
||||||
rp_obj.AllocationCandidates.get_by_requests,
|
rp_obj.AllocationCandidates.get_by_requests,
|
||||||
self.ctx, requests)
|
self.ctx, requests)
|
||||||
|
|
||||||
|
@@ -31,6 +31,7 @@ Request
|
|||||||
|
|
||||||
- resources: resources_query_required
|
- resources: resources_query_required
|
||||||
- limit: allocation_candidates_limit
|
- limit: allocation_candidates_limit
|
||||||
|
- required: allocation_candidates_required
|
||||||
|
|
||||||
Response (microversions 1.12 - )
|
Response (microversions 1.12 - )
|
||||||
--------------------------------
|
--------------------------------
|
||||||
|
@@ -50,6 +50,16 @@ allocation_candidates_limit:
|
|||||||
description: >
|
description: >
|
||||||
A positive integer used to limit the maximum number of allocation
|
A positive integer used to limit the maximum number of allocation
|
||||||
candidates returned in the response.
|
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:
|
member_of:
|
||||||
type: string
|
type: string
|
||||||
in: query
|
in: query
|
||||||
@@ -241,7 +251,8 @@ provider_summaries:
|
|||||||
required: true
|
required: true
|
||||||
description: >
|
description: >
|
||||||
A dictionary keyed by resource provider UUID,
|
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
|
reserved: &reserved
|
||||||
type: integer
|
type: integer
|
||||||
in: body
|
in: body
|
||||||
|
@@ -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.
|
Reference in New Issue
Block a user