271 lines
10 KiB
Python
271 lines
10 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.
|
|
|
|
"""An object describing a tree of resource providers and their inventories.
|
|
|
|
This object is not stored in the Nova API or cell databases; rather, this
|
|
object is constructed and used by the scheduler report client to track state
|
|
changes for resources on the hypervisor or baremetal node. As such, there are
|
|
no remoteable methods nor is there any interaction with the nova.db modules.
|
|
"""
|
|
|
|
import copy
|
|
|
|
from oslo_concurrency import lockutils
|
|
from oslo_log import log as logging
|
|
from oslo_utils import uuidutils
|
|
|
|
from nova.i18n import _
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
_LOCK_NAME = 'provider-tree-lock'
|
|
|
|
|
|
class _Provider(object):
|
|
"""Represents a resource provider in the tree. All operations against the
|
|
tree should be done using the ProviderTree interface, since it controls
|
|
thread-safety.
|
|
"""
|
|
def __init__(self, name, uuid=None, generation=None, parent_uuid=None):
|
|
if uuid is None:
|
|
uuid = uuidutils.generate_uuid()
|
|
self.uuid = uuid
|
|
self.name = name
|
|
self.generation = generation
|
|
self.parent_uuid = parent_uuid
|
|
# Contains a dict, keyed by uuid of child resource providers having
|
|
# this provider as a parent
|
|
self.children = {}
|
|
# dict of inventory records, keyed by resource class
|
|
self.inventory = {}
|
|
|
|
def get_provider_uuids(self):
|
|
"""Returns a set of UUIDs of this provider and all its descendants."""
|
|
ret = set([self.uuid])
|
|
for child in self.children.values():
|
|
ret |= child.get_provider_uuids()
|
|
return ret
|
|
|
|
def find(self, search):
|
|
if self.name == search or self.uuid == search:
|
|
return self
|
|
if search in self.children:
|
|
return self.children[search]
|
|
if self.children:
|
|
for child in self.children.values():
|
|
# We already searched for the child by UUID above, so here we
|
|
# just check for a child name match
|
|
if child.name == search:
|
|
return child
|
|
subchild = child.find(search)
|
|
if subchild:
|
|
return subchild
|
|
return None
|
|
|
|
def add_child(self, provider):
|
|
self.children[provider.uuid] = provider
|
|
|
|
def remove_child(self, provider):
|
|
if provider.uuid in self.children:
|
|
del self.children[provider.uuid]
|
|
|
|
def has_inventory(self):
|
|
"""Returns whether the provider has any inventory records at all. """
|
|
return self.inventory != {}
|
|
|
|
def has_inventory_changed(self, new):
|
|
"""Returns whether the inventory has changed for the provider."""
|
|
cur = self.inventory
|
|
if set(cur) != set(new):
|
|
return True
|
|
for key, cur_rec in cur.items():
|
|
new_rec = new[key]
|
|
for rec_key, cur_val in cur_rec.items():
|
|
if rec_key not in new_rec:
|
|
# Deliberately don't want to compare missing keys in the
|
|
# inventory record. For instance, we will be passing in
|
|
# fields like allocation_ratio in the current dict but the
|
|
# resource tracker may only pass in the total field. We
|
|
# want to return that inventory didn't change when the
|
|
# total field values are the same even if the
|
|
# allocation_ratio field is missing from the new record.
|
|
continue
|
|
if new_rec[rec_key] != cur_val:
|
|
return True
|
|
return False
|
|
|
|
def update_inventory(self, inventory, generation):
|
|
"""Update the stored inventory for the provider along with a resource
|
|
provider generation to set the provider to. The method returns whether
|
|
the inventory has changed.
|
|
"""
|
|
if generation != self.generation:
|
|
msg_args = {
|
|
'rp_uuid': self.uuid,
|
|
'old': self.generation,
|
|
'new': generation,
|
|
}
|
|
LOG.debug("Updating resource provider %(rp_uuid)s generation "
|
|
"from %(old)s to %(new)s", msg_args)
|
|
self.generation = generation
|
|
if self.has_inventory_changed(inventory):
|
|
self.inventory = copy.deepcopy(inventory)
|
|
return True
|
|
return False
|
|
|
|
|
|
class ProviderTree(object):
|
|
|
|
def __init__(self, cns=None):
|
|
"""Create a provider tree from an `objects.ComputeNodeList` object."""
|
|
self.lock = lockutils.internal_lock(_LOCK_NAME)
|
|
self.roots = []
|
|
|
|
if cns:
|
|
for cn in cns:
|
|
# By definition, all compute nodes are root providers...
|
|
p = _Provider(cn.hypervisor_hostname, cn.uuid)
|
|
self.roots.append(p)
|
|
|
|
def get_provider_uuids(self, name_or_uuid=None):
|
|
"""Return a set of the UUIDs of all providers (in a subtree).
|
|
|
|
:param name_or_uuid: Provider name or UUID representing the root of a
|
|
subtree for which to return UUIDs. If not
|
|
specified, the method returns all UUIDs in the
|
|
ProviderTree.
|
|
"""
|
|
if name_or_uuid is not None:
|
|
with self.lock:
|
|
return self._find_with_lock(name_or_uuid).get_provider_uuids()
|
|
|
|
# If no name_or_uuid, get UUIDs for all providers recursively.
|
|
ret = set()
|
|
with self.lock:
|
|
for root in self.roots:
|
|
ret |= root.get_provider_uuids()
|
|
return ret
|
|
|
|
def remove(self, name_or_uuid):
|
|
"""Safely removes the provider identified by the supplied name_or_uuid
|
|
parameter and all of its children from the tree.
|
|
|
|
:raises ValueError if name_or_uuid points to a non-existing provider.
|
|
:param name_or_uuid: Either name or UUID of the resource provider to
|
|
remove from the tree.
|
|
"""
|
|
with self.lock:
|
|
found = self._find_with_lock(name_or_uuid)
|
|
if found.parent_uuid:
|
|
parent = self._find_with_lock(found.parent_uuid)
|
|
parent.remove_child(found)
|
|
else:
|
|
self.roots.remove(found)
|
|
|
|
def new_root(self, name, uuid, generation):
|
|
"""Adds a new root provider to the tree, returning its UUID."""
|
|
|
|
with self.lock:
|
|
exists = True
|
|
try:
|
|
self._find_with_lock(uuid)
|
|
except ValueError:
|
|
exists = False
|
|
|
|
if exists:
|
|
err = _("Provider %s already exists as a root.")
|
|
raise ValueError(err % uuid)
|
|
|
|
p = _Provider(name, uuid=uuid, generation=generation)
|
|
self.roots.append(p)
|
|
return p.uuid
|
|
|
|
def _find_with_lock(self, name_or_uuid):
|
|
for root in self.roots:
|
|
found = root.find(name_or_uuid)
|
|
if found:
|
|
return found
|
|
raise ValueError(_("No such provider %s") % name_or_uuid)
|
|
|
|
def exists(self, name_or_uuid):
|
|
"""Given either a name or a UUID, return True if the tree contains the
|
|
provider, False otherwise.
|
|
"""
|
|
with self.lock:
|
|
try:
|
|
self._find_with_lock(name_or_uuid)
|
|
return True
|
|
except ValueError:
|
|
return False
|
|
|
|
def new_child(self, name, parent_uuid, uuid=None, generation=None):
|
|
"""Creates a new child provider with the given name and uuid under the
|
|
given parent.
|
|
|
|
:returns: the UUID of the new provider
|
|
|
|
:raises ValueError if parent_uuid points to a non-existing provider.
|
|
"""
|
|
with self.lock:
|
|
parent = self._find_with_lock(parent_uuid)
|
|
p = _Provider(name, uuid, generation, parent_uuid)
|
|
parent.add_child(p)
|
|
return p.uuid
|
|
|
|
def has_inventory(self, name_or_uuid):
|
|
"""Returns True if the provider identified by name_or_uuid has any
|
|
inventory records at all.
|
|
|
|
:raises: ValueError if a provider with uuid was not found in the tree.
|
|
:param name_or_uuid: Either name or UUID of the resource provider
|
|
"""
|
|
with self.lock:
|
|
p = self._find_with_lock(name_or_uuid)
|
|
return p.has_inventory()
|
|
|
|
def has_inventory_changed(self, name_or_uuid, inventory):
|
|
"""Returns True if the supplied inventory is different for the provider
|
|
with the supplied name or UUID.
|
|
|
|
:raises: ValueError if a provider with name_or_uuid was not found in
|
|
the tree.
|
|
:param name_or_uuid: Either name or UUID of the resource provider to
|
|
update inventory for.
|
|
:param inventory: dict, keyed by resource class, of inventory
|
|
information.
|
|
"""
|
|
with self.lock:
|
|
provider = self._find_with_lock(name_or_uuid)
|
|
return provider.has_inventory_changed(inventory)
|
|
|
|
def update_inventory(self, name_or_uuid, inventory, generation):
|
|
"""Given a name or UUID of a provider and a dict of inventory resource
|
|
records, update the provider's inventory and set the provider's
|
|
generation.
|
|
|
|
:returns: True if the inventory has changed.
|
|
|
|
:note: The provider's generation is always set to the supplied
|
|
generation, even if there were no changes to the inventory.
|
|
|
|
:raises: ValueError if a provider with name_or_uuid was not found in
|
|
the tree.
|
|
:param name_or_uuid: Either name or UUID of the resource provider to
|
|
update inventory for.
|
|
:param inventory: dict, keyed by resource class, of inventory
|
|
information.
|
|
:param generation: The resource provider generation to set
|
|
"""
|
|
with self.lock:
|
|
provider = self._find_with_lock(name_or_uuid)
|
|
return provider.update_inventory(inventory, generation)
|