Files
placement/placement/objects/reshaper.py
Chris Dent f23137beee Remove InventoryList class
The two methods from the class, 'find' and
'get_all_by_resource_provider' are made module-level and tests
and callers updated as required. The signature on find changes to
take a list of Inventory objects as its first arg.

Change-Id: I50e69aececf0d1afd3218bb2d63407b273757b29
2019-03-13 21:24:34 +00:00

114 lines
5.8 KiB
Python

# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_log import log as logging
from placement import db_api
from placement.objects import allocation as alloc_obj
from placement.objects import inventory as inv_obj
LOG = logging.getLogger(__name__)
@db_api.placement_context_manager.writer
def reshape(ctx, inventories, allocations):
"""The 'replace the world' strategy that is executed when we want to
completely replace a set of provider inventory, allocation and consumer
information in a single transaction.
:note: The reason this has to be done in a single monolithic function is so
we have a single top-level function on which to decorate with the
@db_api.placement_context_manager.writer transaction context
manager. Each time a top-level function that is decorated with this
exits, the transaction is either COMMIT'd or ROLLBACK'd. We need to
avoid calling two functions that are already decorated with a
transaction context manager from a function that *isn't* decorated
with the transaction context manager if we want all changes involved
in the sub-functions to operate within a single DB transaction.
:param ctx: `placement.context.RequestContext` object
containing the DB transaction context.
:param inventories: dict, keyed by ResourceProvider, of lists of
`Inventory` objects representing the replaced inventory
information for the provider.
:param allocations: `AllocationList` object containing all allocations for
all consumers being modified by the reshape operation.
:raises: `exception.ConcurrentUpdateDetected` when any resource provider or
consumer generation increment fails due to concurrent changes to
the same objects.
"""
# The resource provider objects, keyed by provider UUID, that are involved
# in this transaction. We keep a cache of these because as we perform the
# various operations on the providers, their generations increment and we
# want to "inject" the changed resource provider objects into the
# AllocationList's objects before calling AllocationList.replace_all().
# We start with the providers in the allocation objects, but only use one
# if we don't find it in the inventories.
affected_providers = {alloc.resource_provider.uuid: alloc.resource_provider
for alloc in allocations}
# We have to do the inventory changes in two steps because:
# - we can't delete inventories with allocations; and
# - we can't create allocations on nonexistent inventories.
# So in the first step we create a kind of "union" inventory for each
# provider. It contains all the inventories that the request wishes to
# exist in the end, PLUS any inventories that the request wished to remove
# (in their original form).
# Note that this can cause us to end up with an interim situation where we
# have modified an inventory to have less capacity than is currently
# allocated, but that's allowed by the code. If the final picture is
# overcommitted, we'll get an appropriate exception when we replace the
# allocations at the end.
for rp, new_inv_list in inventories.items():
LOG.debug("reshaping: *interim* inventory replacement for provider %s",
rp.uuid)
# Update the cache. This may be replacing an entry that came from
# allocations, or adding a new entry from inventories.
affected_providers[rp.uuid] = rp
# Optimization: If the new inventory is empty, the below would be
# replacing it with itself (and incrementing the generation)
# unnecessarily.
if not new_inv_list:
continue
# A dict, keyed by resource class, of the Inventory objects. We start
# with the original inventory list.
inv_by_rc = {
inv.resource_class: inv for inv in
inv_obj.get_all_by_resource_provider(ctx, rp)}
# Now add each inventory in the new inventory list. If an inventory for
# that resource class existed in the original inventory list, it is
# overwritten.
for inv in new_inv_list:
inv_by_rc[inv.resource_class] = inv
# Set the interim inventory structure.
rp.set_inventory(list(inv_by_rc.values()))
# NOTE(jaypipes): The above inventory replacements will have
# incremented the resource provider generations, so we need to look in
# the AllocationList and swap the resource provider object with the one we
# saved above that has the updated provider generation in it.
for alloc in allocations:
rp_uuid = alloc.resource_provider.uuid
if rp_uuid in affected_providers:
alloc.resource_provider = affected_providers[rp_uuid]
# Now we can replace all the allocations
LOG.debug("reshaping: attempting allocation replacement")
alloc_obj.replace_all(ctx, allocations)
# And finally, we can set the inventories to their actual desired state.
for rp, new_inv_list in inventories.items():
LOG.debug("reshaping: *final* inventory replacement for provider %s",
rp.uuid)
rp.set_inventory(new_inv_list)