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
114 lines
5.8 KiB
Python
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)
|