Placement: allow to set reserved value equal to total for inventory

This is needed for ironic use case, when during cleaning, resources
are reserved by ironic itself. Cyborg will also benefit from this
during FPGA programming.

blueprint: allow-reserved-equal-total-inventory
Change-Id: I037d9b8c1bb554c3437434cc9a57ddb630dd62f0
This commit is contained in:
Vladyslav Drok 2018-04-27 19:45:55 +03:00 committed by Eric Fried
parent 549e5a2226
commit 7f79758dd9
9 changed files with 134 additions and 42 deletions

View File

@ -99,6 +99,12 @@ class InvalidInventoryCapacity(InvalidInventory):
"The reserved value is greater than or equal to total.")
class InvalidInventoryCapacityReservedCanBeTotal(InvalidInventoryCapacity):
msg_fmt = _("Invalid inventory for '%(resource_class)s' on "
"resource provider '%(resource_provider)s'. "
"The reserved value is greater than total.")
# An exception with this name is used on both sides of the placement/
# nova interaction.
class InventoryInUse(InvalidInventory):

View File

@ -12,6 +12,7 @@
"""Inventory handlers for Placement API."""
import copy
import operator
from oslo_db import exception as db_exc
from oslo_serialization import jsonutils
@ -147,6 +148,31 @@ def _serialize_inventories(inventories, generation):
'inventories': inventories_dict}, last_modified)
def _validate_inventory_capacity(version, inventories):
"""Validate inventory capacity.
:param version: request microversion.
:param inventories: Inventory or InventoryList to validate capacities of.
:raises: exception.InvalidInventoryCapacityReservedCanBeTotal if request
microversion is 1.26 or higher and any inventory has capacity < 0.
:raises: exception.InvalidInventoryCapacity if request
microversion is lower than 1.26 and any inventory has capacity <= 0.
"""
if not version.matches((1, 26)):
op = operator.le
exc_class = exception.InvalidInventoryCapacity
else:
op = operator.lt
exc_class = exception.InvalidInventoryCapacityReservedCanBeTotal
if isinstance(inventories, rp_obj.Inventory):
inventories = rp_obj.InventoryList(objects=[inventories])
for inventory in inventories:
if op(inventory.capacity, 0):
raise exc_class(
resource_class=inventory.resource_class,
resource_provider=inventory.resource_provider.uuid)
@wsgi_wrapper.PlacementWsgify
@util.require_content('application/json')
def create_inventory(req):
@ -168,6 +194,8 @@ def create_inventory(req):
**data)
try:
_validate_inventory_capacity(
req.environ[microversion.MICROVERSION_ENVIRON], inventory)
resource_provider.add_inventory(inventory)
except (exception.ConcurrentUpdateDetected,
db_exc.DBDuplicateEntry) as exc:
@ -305,6 +333,8 @@ def set_inventories(req):
inventories = rp_obj.InventoryList(objects=inv_list)
try:
_validate_inventory_capacity(
req.environ[microversion.MICROVERSION_ENVIRON], inventories)
resource_provider.set_inventory(inventories)
except exception.ResourceClassNotFound as exc:
raise webob.exc.HTTPBadRequest(
@ -401,6 +431,8 @@ def update_inventory(req):
**data)
try:
_validate_inventory_capacity(
req.environ[microversion.MICROVERSION_ENVIRON], inventory)
resource_provider.update_inventory(inventory)
except (exception.ConcurrentUpdateDetected,
db_exc.DBDuplicateEntry) as exc:

View File

@ -68,6 +68,8 @@ VERSIONS = [
# GET /resource_providers
'1.25', # Adds support for granular resource requests via numbered
# querystring groups in GET /allocation_candidates
'1.26', # Add ability to specify inventory with reserved value equal to
# total.
]

View File

@ -201,10 +201,6 @@ def _add_inventory_to_provider(ctx, rp, inv_list, to_add):
for rc_id in to_add:
rc_str = _RC_CACHE.string_from_id(rc_id)
inv_record = inv_list.find(rc_str)
if inv_record.capacity <= 0:
raise exception.InvalidInventoryCapacity(
resource_class=rc_str,
resource_provider=rp.uuid)
ins_stmt = _INV_TBL.insert().values(
resource_provider_id=rp.id,
resource_class_id=rc_id,
@ -232,10 +228,6 @@ def _update_inventory_for_provider(ctx, rp, inv_list, to_update):
for rc_id in to_update:
rc_str = _RC_CACHE.string_from_id(rc_id)
inv_record = inv_list.find(rc_str)
if inv_record.capacity <= 0:
raise exception.InvalidInventoryCapacity(
resource_class=rc_str,
resource_provider=rp.uuid)
allocation_query = sa.select(
[func.sum(_ALLOC_TBL.c.used).label('usage')]).\
where(sa.and_(

View File

@ -323,3 +323,9 @@ The semantic of the (unnumbered) ``resources``, ``required``, and ``member_of``
query parameters is unchanged: the resources, traits, and aggregate
associations specified thereby may be satisfied by any provider in the same
non-sharing tree or associated via the specified aggregate(s).
1.26 Allow inventories to have reserved value equal to total
------------------------------------------------------------
Starting with this version, it is allowed to set the reserved value of the
resource provider inventory to be equal to total.

View File

@ -584,22 +584,6 @@ class ResourceProviderTestCase(tb.PlacementDbBaseTestCase):
self.assertEqual(1, len(new_inv_list))
self.assertEqual(2048, new_inv_list[0].total)
# fail when inventory bad
disk_inv = rp_obj.Inventory(
resource_provider=rp,
resource_class=fields.ResourceClass.DISK_GB,
total=2048,
reserved=2048)
disk_inv.obj_set_defaults()
error = self.assertRaises(exception.InvalidInventoryCapacity,
rp.update_inventory, disk_inv)
self.assertIn("Invalid inventory for '%s'"
% fields.ResourceClass.DISK_GB, str(error))
self.assertIn("on resource provider '%s'." % rp.uuid, str(error))
# generation has not bumped
self.assertEqual(saved_generation, rp.generation)
# delete inventory
rp.delete_inventory(fields.ResourceClass.DISK_GB)
@ -693,17 +677,6 @@ class ResourceProviderTestCase(tb.PlacementDbBaseTestCase):
mock_log.warning.assert_called_once_with(
mock.ANY, {'uuid': rp.uuid, 'resource': 'DISK_GB'})
def test_add_invalid_inventory(self):
rp = self._create_provider(uuidsentinel.rp_name)
error = self.assertRaises(
exception.InvalidInventoryCapacity,
tb.add_inventory, rp, fields.ResourceClass.DISK_GB, 1024,
reserved=2048)
self.assertIn("Invalid inventory for '%s'"
% fields.ResourceClass.DISK_GB, str(error))
self.assertIn("on resource provider '%s'."
% rp.uuid, str(error))
def test_add_allocation_increments_generation(self):
rp = self._create_provider(name='foo')
tb.add_inventory(rp, DISK_INVENTORY['resource_class'],

View File

@ -649,9 +649,84 @@ tests:
status: 400
response_strings:
- Unable to update inventory
- greater than or equal to total
response_json_paths:
$.errors[0].title: Bad Request
- name: put all inventory zero capacity old microversion
PUT: $LAST_URL
request_headers:
content-type: application/json
data:
resource_provider_generation: 6
inventories:
IPV4_ADDRESS:
total: 253
reserved: 253
status: 400
response_strings:
- Unable to update inventory
- greater than or equal to total
response_json_paths:
$.errors[0].title: Bad Request
- name: put inventory with reserved equal to total
PUT: $LAST_URL
request_headers:
content-type: application/json
openstack-api-version: placement 1.26
data:
resource_provider_generation: 6
inventories:
IPV4_ADDRESS:
total: 253
reserved: 253
status: 200
- name: put all inventory bad capacity in new microversion
PUT: $LAST_URL
request_headers:
content-type: application/json
openstack-api-version: placement 1.26
data:
resource_provider_generation: 7
inventories:
IPV4_ADDRESS:
total: 253
reserved: 512
status: 400
response_strings:
- Unable to update inventory
- greater than total
response_json_paths:
$.errors[0].title: Bad Request
- name: put one inventory zero capacity old microversion
PUT: /resource_providers/$ENVIRON['RP_UUID']/inventories/IPV4_ADDRESS
request_headers:
content-type: application/json
data:
resource_provider_generation: 7
total: 253
reserved: 253
status: 400
response_strings:
- Unable to update inventory
- greater than or equal to total
response_json_paths:
$.errors[0].title: Bad Request
- name: put one inventory with reserved equal to total new microversion
PUT: $LAST_URL
request_headers:
content-type: application/json
openstack-api-version: placement 1.26
data:
resource_provider_generation: 7
total: 512
reserved: 512
status: 200
- name: delete all inventory bad generation
PUT: /resource_providers/$ENVIRON['RP_UUID']/inventories
request_headers:
@ -680,7 +755,7 @@ tests:
- name: get inventories after deletions
GET: /resource_providers/$ENVIRON['RP_UUID']/inventories
response_json_paths:
$.resource_provider_generation: 8
$.resource_provider_generation: 10
$.inventories: {}
- name: post an inventory again
@ -699,7 +774,7 @@ tests:
response_headers:
location: $SCHEME://$NETLOC/resource_providers/$ENVIRON['RP_UUID']/inventories/DISK_GB
response_json_paths:
$.resource_provider_generation: 9
$.resource_provider_generation: 11
$.total: 2048
$.reserved: 512
@ -709,17 +784,17 @@ tests:
content-type: application/json
openstack-api-version: placement 1.4
data:
resource_provider_generation: 9
resource_provider_generation: 11
inventories: {}
response_json_paths:
$.resource_provider_generation: 10
$.resource_provider_generation: 12
$.inventories: {}
status: 200
- name: get generation after deletion
GET: /resource_providers/$ENVIRON['RP_UUID']/inventories
response_json_paths:
$.resource_provider_generation: 10
$.resource_provider_generation: 12
$.inventories: {}
- name: delete inventories earlier version

View File

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

View File

@ -0,0 +1,6 @@
---
features:
- |
Introduces new placement API version ``1.26``. Starting with this version
it is allowed to define resource provider inventories with reserved value
equal to total.