Placement report
1. Create resource provider, resource class, traits. 2. Nested resource provider is supported in this patch. 3. According to the comments and reply in: https://review.opendev.org/#/c/626057/10/cyborg/common/placement_client.py, we should make a class to invoke Placement API. This has also been done in this patch. 4. Convert cyborg/objects/driver_objects/driver_device.py file type from dos to unix, to avoid ^M at the end of each line. 5. Get rid of legacy: ProviderTree class. 6. More info for driver developers: each driver should report resource class and traits in driver_attribute field in order that cyborg-conductor can parse it and report to placement. 7. update deployable rp_uuid field after getting rp_uuid from placement. Change-Id: Ib8f682255d2ae60eb615c90de0a0f7c83d1af54d
This commit is contained in:
parent
0f87dca124
commit
07efd624b8
@ -24,7 +24,6 @@ import re
|
|||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
|
|
||||||
from cyborg.accelerator.common import utils
|
from cyborg.accelerator.common import utils
|
||||||
from cyborg.agent import rc_fields
|
|
||||||
from cyborg.objects.driver_objects import driver_deployable, driver_device,\
|
from cyborg.objects.driver_objects import driver_deployable, driver_device,\
|
||||||
driver_attach_handle, driver_controlpath_id, driver_attribute
|
driver_attach_handle, driver_controlpath_id, driver_attribute
|
||||||
from cyborg.common import constants
|
from cyborg.common import constants
|
||||||
@ -32,8 +31,8 @@ from cyborg.common import constants
|
|||||||
|
|
||||||
PCI_DEVICES_PATH = "/sys/bus/pci/devices"
|
PCI_DEVICES_PATH = "/sys/bus/pci/devices"
|
||||||
PCI_DEVICES_PATH_PATTERN = "/sys/bus/pci/devices/*"
|
PCI_DEVICES_PATH_PATTERN = "/sys/bus/pci/devices/*"
|
||||||
# TODO(shaohe) The KNOW_FPGAS can be configurable.
|
# TODO(shaohe) The KNOWN_FPGAS can be configurable.
|
||||||
KNOW_FPGAS = [("0x8086", "0x09c4")]
|
KNOWN_FPGAS = [("0x8086", "0x09c4")]
|
||||||
|
|
||||||
INTEL_FPGA_DEV_PREFIX = "intel-fpga-dev"
|
INTEL_FPGA_DEV_PREFIX = "intel-fpga-dev"
|
||||||
SYS_FPGA = "/sys/class/fpga"
|
SYS_FPGA = "/sys/class/fpga"
|
||||||
@ -48,12 +47,7 @@ DEVICE_FILE_MAP = {"vendor": "vendor",
|
|||||||
DEVICE_FILE_HANDLER = {}
|
DEVICE_FILE_HANDLER = {}
|
||||||
DEVICE_EXPOSED = ["vendor", "device"]
|
DEVICE_EXPOSED = ["vendor", "device"]
|
||||||
|
|
||||||
RC_FPGA = rc_fields.ResourceClass.normalize_name(
|
PRODUCT_MAP = {"0x09c4": "PAC_ARRIA10"}
|
||||||
rc_fields.ResourceClass.FPGA)
|
|
||||||
|
|
||||||
RESOURCES = {
|
|
||||||
"fpga": RC_FPGA
|
|
||||||
}
|
|
||||||
|
|
||||||
DRIVER_NAME = "intel"
|
DRIVER_NAME = "intel"
|
||||||
|
|
||||||
@ -66,7 +60,7 @@ def read_line(filename):
|
|||||||
def is_fpga(p):
|
def is_fpga(p):
|
||||||
infos = (read_line(os.path.join(p, "vendor")),
|
infos = (read_line(os.path.join(p, "vendor")),
|
||||||
read_line(os.path.join(p, "device")))
|
read_line(os.path.join(p, "device")))
|
||||||
if infos in KNOW_FPGAS:
|
if infos in KNOWN_FPGAS:
|
||||||
return os.path.realpath(p)
|
return os.path.realpath(p)
|
||||||
|
|
||||||
|
|
||||||
@ -80,7 +74,7 @@ def find_fpgas_by_know_list():
|
|||||||
lambda p: (
|
lambda p: (
|
||||||
read_line(os.path.join(p, "vendor")),
|
read_line(os.path.join(p, "vendor")),
|
||||||
read_line(os.path.join(p, "device"))
|
read_line(os.path.join(p, "device"))
|
||||||
) in KNOW_FPGAS,
|
) in KNOWN_FPGAS,
|
||||||
glob.glob(PCI_DEVICES_PATH_PATTERN))
|
glob.glob(PCI_DEVICES_PATH_PATTERN))
|
||||||
|
|
||||||
|
|
||||||
@ -165,23 +159,46 @@ def get_pf_bdf(bdf):
|
|||||||
return bdf
|
return bdf
|
||||||
|
|
||||||
|
|
||||||
def get_afu_ids(name):
|
def get_afu_ids(device_name):
|
||||||
return map(
|
return map(
|
||||||
read_line,
|
read_line,
|
||||||
glob.glob(
|
glob.glob(
|
||||||
os.path.join(
|
os.path.join(
|
||||||
PCI_DEVICES_PATH_PATTERN, "fpga",
|
PCI_DEVICES_PATH_PATTERN, "fpga",
|
||||||
name, "intel-fpga-port.*", "afu_id")
|
device_name, "intel-fpga-port.*", "afu_id")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_traits(name, product_id):
|
def get_region_ids(device_name):
|
||||||
|
return map(
|
||||||
|
read_line,
|
||||||
|
glob.glob(
|
||||||
|
os.path.join(
|
||||||
|
SYS_FPGA, device_name, "device/physfn/fpga",
|
||||||
|
"intel-fpga-dev.*", "intel-fpga-fme.*", "pr/interface_id")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_traits(device_name, product_id, vf=True):
|
||||||
|
"""Generate traits for devices.
|
||||||
|
: param devices_name: name of PF/VF, for example, "intel-fpga-dev.0".
|
||||||
|
: param product_id: product id of PF/VF, for example, "0x09c4".
|
||||||
|
: param vf: True if device_name is a VF, otherwise False.
|
||||||
|
"""
|
||||||
# "region_id" not support at present, "CUSTOM_FPGA_REGION_INTEL_UUID"
|
# "region_id" not support at present, "CUSTOM_FPGA_REGION_INTEL_UUID"
|
||||||
# "CUSTOM_PROGRAMMABLE" not support at present
|
# "CUSTOM_PROGRAMMABLE" not support at present
|
||||||
traits = ["CUSTOM_FPGA_INTEL"]
|
traits = []
|
||||||
for i in get_afu_ids(name):
|
if not vf:
|
||||||
l = "CUSTOM_FPGA_INTEL_FUNCTION_" + i.upper()
|
traits.append("CUSTOM_FPGA_INTEL")
|
||||||
|
traits.append("CUSTOM_FPGA_INTEL_" + PRODUCT_MAP.get(product_id))
|
||||||
|
else:
|
||||||
|
for i in get_afu_ids(device_name):
|
||||||
|
l = "CUSTOM_FPGA_FUNCTION_ID_INTEL_" + i.upper()
|
||||||
|
traits.append(l)
|
||||||
|
for i in get_region_ids(device_name):
|
||||||
|
l = "CUSTOM_FPGA_REGION_INTEL_" + i.upper()
|
||||||
traits.append(l)
|
traits.append(l)
|
||||||
return {"traits": traits}
|
return {"traits": traits}
|
||||||
|
|
||||||
@ -216,9 +233,9 @@ def fpga_tree():
|
|||||||
if names:
|
if names:
|
||||||
name = names[0]
|
name = names[0]
|
||||||
fpga["stub"] = False
|
fpga["stub"] = False
|
||||||
traits = get_traits(name, fpga["product_id"])
|
traits = get_traits(name, fpga["product_id"], vf)
|
||||||
fpga.update(traits)
|
fpga.update(traits)
|
||||||
fpga["rc"] = RESOURCES["fpga"]
|
fpga["rc"] = constants.RESOURCES["FPGA"]
|
||||||
return fpga
|
return fpga
|
||||||
|
|
||||||
devs = []
|
devs = []
|
||||||
@ -294,17 +311,28 @@ def _generate_attach_handle(fpga):
|
|||||||
|
|
||||||
def _generate_attribute_list(fpga):
|
def _generate_attribute_list(fpga):
|
||||||
attr_list = []
|
attr_list = []
|
||||||
|
index = 0
|
||||||
for k, v in fpga.items():
|
for k, v in fpga.items():
|
||||||
if k == "rc":
|
if k == "rc":
|
||||||
driver_attr = driver_attribute.DriverAttribute()
|
driver_attr = driver_attribute.DriverAttribute()
|
||||||
driver_attr.key = k
|
driver_attr.key, driver_attr.value = k, v
|
||||||
driver_attr.value = fpga.get(k, None)
|
|
||||||
attr_list.append(driver_attr)
|
attr_list.append(driver_attr)
|
||||||
if k == "traits":
|
if k == "traits":
|
||||||
values = fpga.get(k, None)
|
values = fpga.get(k, None)
|
||||||
for val in values:
|
for val in values:
|
||||||
driver_attr = driver_attribute.DriverAttribute()
|
driver_attr = driver_attribute.DriverAttribute()
|
||||||
driver_attr.key = "trait" + str(values.index(val))
|
driver_attr.key = "trait" + str(index)
|
||||||
|
index = index + 1
|
||||||
driver_attr.value = val
|
driver_attr.value = val
|
||||||
attr_list.append(driver_attr)
|
attr_list.append(driver_attr)
|
||||||
|
if fpga.get("regions"):
|
||||||
|
for vf in fpga["regions"]:
|
||||||
|
for k, values in vf.items():
|
||||||
|
if k == "traits":
|
||||||
|
for val in values:
|
||||||
|
driver_attr = driver_attribute.DriverAttribute(
|
||||||
|
key="trait" + str(index), value=val)
|
||||||
|
index = index + 1
|
||||||
|
# driver_attr.value = "CUSTOM_UPDATED_TRAITS2"
|
||||||
|
attr_list.append(driver_attr)
|
||||||
return attr_list
|
return attr_list
|
||||||
|
@ -1,670 +0,0 @@
|
|||||||
# 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 collections
|
|
||||||
import copy
|
|
||||||
|
|
||||||
import os_traits
|
|
||||||
from oslo_concurrency import lockutils
|
|
||||||
from oslo_log import log as logging
|
|
||||||
from oslo_utils import uuidutils
|
|
||||||
|
|
||||||
from cyborg.common.i18n import _
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
_LOCK_NAME = 'provider-tree-lock'
|
|
||||||
|
|
||||||
# Point-in-time representation of a resource provider in the tree.
|
|
||||||
# Note that, whereas namedtuple enforces read-only-ness of instances as a
|
|
||||||
# whole, nothing prevents modification of the internals of attributes of
|
|
||||||
# complex types (children/inventory/traits/aggregates). However, any such
|
|
||||||
# modifications still have no effect on the ProviderTree the instance came
|
|
||||||
# from. Like, you can Sharpie a moustache on a Polaroid of my face, but that
|
|
||||||
# doesn't make a moustache appear on my actual face.
|
|
||||||
ProviderData = collections.namedtuple(
|
|
||||||
'ProviderData', ['uuid', 'name', 'generation', 'parent_uuid', 'inventory',
|
|
||||||
'traits', 'aggregates'])
|
|
||||||
|
|
||||||
|
|
||||||
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 = {}
|
|
||||||
# Set of trait names
|
|
||||||
self.traits = set()
|
|
||||||
# Set of aggregate UUIDs
|
|
||||||
self.aggregates = set()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_dict(cls, pdict):
|
|
||||||
"""Factory method producing a _Provider based on a dict with
|
|
||||||
appropriate keys.
|
|
||||||
|
|
||||||
:param pdict: Dictionary representing a provider, with keys 'name',
|
|
||||||
'uuid', 'generation', 'parent_provider_uuid'. Of these,
|
|
||||||
only 'name' is mandatory.
|
|
||||||
"""
|
|
||||||
return cls(pdict['name'], uuid=pdict.get('uuid'),
|
|
||||||
generation=pdict.get('generation'),
|
|
||||||
parent_uuid=pdict.get('parent_provider_uuid'))
|
|
||||||
|
|
||||||
def data(self):
|
|
||||||
"""A collection of all informations of a provider.
|
|
||||||
|
|
||||||
:Return: a collections.namedtuple
|
|
||||||
include inventory, traits, aggregates, uuid, name, generation,
|
|
||||||
and parent_uuid.
|
|
||||||
"""
|
|
||||||
inventory = copy.deepcopy(self.inventory)
|
|
||||||
traits = copy.copy(self.traits)
|
|
||||||
aggregates = copy.copy(self.aggregates)
|
|
||||||
return ProviderData(
|
|
||||||
self.uuid, self.name, self.generation, self.parent_uuid,
|
|
||||||
inventory, traits, aggregates)
|
|
||||||
|
|
||||||
def get_provider_uuids(self):
|
|
||||||
"""Returns a list, in top-down traversal order, of UUIDs of this
|
|
||||||
provider and all its descendants.
|
|
||||||
"""
|
|
||||||
ret = [self.uuid]
|
|
||||||
for child in self.children.values():
|
|
||||||
ret.extend(child.get_provider_uuids())
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def find(self, search):
|
|
||||||
"""Find an expect one in the provider tree by match the serach.
|
|
||||||
|
|
||||||
:param search: it can be the either name or uuid of an expect provider.
|
|
||||||
:return: the expect _Provider object or None.
|
|
||||||
"""
|
|
||||||
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]
|
|
||||||
# If the new record contains new fields (e.g. we're adding on
|
|
||||||
# `reserved` or `allocation_ratio`) we want to make sure to pick
|
|
||||||
# them up
|
|
||||||
if set(new_rec) - set(cur_rec):
|
|
||||||
return True
|
|
||||||
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
|
|
||||||
# *new* 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_generation(self, generation):
|
|
||||||
if generation is not None and 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
|
|
||||||
|
|
||||||
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.
|
|
||||||
"""
|
|
||||||
self._update_generation(generation)
|
|
||||||
if self.has_inventory_changed(inventory):
|
|
||||||
self.inventory = copy.deepcopy(inventory)
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def have_traits_changed(self, new):
|
|
||||||
"""Returns whether the provider's traits have changed."""
|
|
||||||
return set(new) != self.traits
|
|
||||||
|
|
||||||
def update_traits(self, new, generation=None):
|
|
||||||
"""Update the stored traits for the provider along with a resource
|
|
||||||
provider generation to set the provider to. The method returns whether
|
|
||||||
the traits have changed.
|
|
||||||
"""
|
|
||||||
self._update_generation(generation)
|
|
||||||
if self.have_traits_changed(new):
|
|
||||||
self.traits = set(new) # create a copy of the new traits
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def has_traits(self, traits):
|
|
||||||
"""Query whether the provider has certain traits.
|
|
||||||
|
|
||||||
:param traits: Iterable of string trait names to look for.
|
|
||||||
:return: True if this provider has *all* of the specified traits; False
|
|
||||||
if any of the specified traits are absent. Returns True if
|
|
||||||
the traits parameter is empty.
|
|
||||||
"""
|
|
||||||
return not bool(set(traits) - self.traits)
|
|
||||||
|
|
||||||
def have_aggregates_changed(self, new):
|
|
||||||
"""Returns whether the provider's aggregates have changed."""
|
|
||||||
return set(new) != self.aggregates
|
|
||||||
|
|
||||||
def update_aggregates(self, new, generation=None):
|
|
||||||
"""Update the stored aggregates for the provider along with a resource
|
|
||||||
provider generation to set the provider to. The method returns whether
|
|
||||||
the aggregates have changed.
|
|
||||||
"""
|
|
||||||
self._update_generation(generation)
|
|
||||||
if self.have_aggregates_changed(new):
|
|
||||||
self.aggregates = set(new) # create a copy of the new aggregates
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def in_aggregates(self, aggregates):
|
|
||||||
"""Query whether the provider is a member of certain aggregates.
|
|
||||||
|
|
||||||
:param aggregates: Iterable of string aggregate UUIDs to look for.
|
|
||||||
:return: True if this provider is a member of *all* of the specified
|
|
||||||
aggregates; False if any of the specified aggregates are
|
|
||||||
absent. Returns True if the aggregates parameter is empty.
|
|
||||||
"""
|
|
||||||
return not bool(set(aggregates) - self.aggregates)
|
|
||||||
|
|
||||||
|
|
||||||
class ProviderTree(object):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
"""Create an empty provider tree."""
|
|
||||||
self.lock = lockutils.internal_lock(_LOCK_NAME)
|
|
||||||
self.roots = []
|
|
||||||
|
|
||||||
def get_provider_uuids(self, name_or_uuid=None):
|
|
||||||
"""Return a list, in top-down traversable order, 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 = []
|
|
||||||
with self.lock:
|
|
||||||
for root in self.roots:
|
|
||||||
ret.extend(root.get_provider_uuids())
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def populate_from_iterable(self, provider_dicts):
|
|
||||||
"""Populates this ProviderTree from an iterable of provider dicts.
|
|
||||||
|
|
||||||
This method will ADD providers to the tree if provider_dicts contains
|
|
||||||
providers that do not exist in the tree already and will REPLACE
|
|
||||||
providers in the tree if provider_dicts contains providers that are
|
|
||||||
already in the tree. This method will NOT remove providers from the
|
|
||||||
tree that are not in provider_dicts. But if a parent provider is in
|
|
||||||
provider_dicts and the descendents are not, this method will remove the
|
|
||||||
descendents from the tree.
|
|
||||||
|
|
||||||
:param provider_dicts: An iterable of dicts of resource provider
|
|
||||||
information. If a provider is present in
|
|
||||||
provider_dicts, all its descendants must also be
|
|
||||||
present.
|
|
||||||
:raises: ValueError if any provider in provider_dicts has a parent that
|
|
||||||
is not in this ProviderTree or elsewhere in provider_dicts.
|
|
||||||
"""
|
|
||||||
if not provider_dicts:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Map of provider UUID to provider dict for the providers we're
|
|
||||||
# *adding* via this method.
|
|
||||||
to_add_by_uuid = {pd['uuid']: pd for pd in provider_dicts}
|
|
||||||
|
|
||||||
with self.lock:
|
|
||||||
# Sanity check for orphans. Every parent UUID must either be None
|
|
||||||
# (the provider is a root), or be in the tree already, or exist as
|
|
||||||
# a key in to_add_by_uuid (we're adding it).
|
|
||||||
all_parents = set([None]) | set(to_add_by_uuid)
|
|
||||||
# NOTE(efried): Can't use get_provider_uuids directly because we're
|
|
||||||
# already under lock.
|
|
||||||
for root in self.roots:
|
|
||||||
all_parents |= set(root.get_provider_uuids())
|
|
||||||
missing_parents = set()
|
|
||||||
for pd in to_add_by_uuid.values():
|
|
||||||
parent_uuid = pd.get('parent_provider_uuid')
|
|
||||||
if parent_uuid not in all_parents:
|
|
||||||
missing_parents.add(parent_uuid)
|
|
||||||
if missing_parents:
|
|
||||||
raise ValueError(
|
|
||||||
_("The following parents were not found: %s") %
|
|
||||||
', '.join(missing_parents))
|
|
||||||
|
|
||||||
# Ready to do the work.
|
|
||||||
# Use to_add_by_uuid to keep track of which providers are left to
|
|
||||||
# be added.
|
|
||||||
while to_add_by_uuid:
|
|
||||||
# Find a provider that's suitable to inject.
|
|
||||||
for uuid, pd in to_add_by_uuid.items():
|
|
||||||
# Roots are always okay to inject (None won't be a key in
|
|
||||||
# to_add_by_uuid). Otherwise, we have to make sure we
|
|
||||||
# already added the parent (and, by recursion, all
|
|
||||||
# ancestors) if present in the input.
|
|
||||||
parent_uuid = pd.get('parent_provider_uuid')
|
|
||||||
if parent_uuid not in to_add_by_uuid:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
# This should never happen - we already ensured all parents
|
|
||||||
# exist in the tree, which means we can't have any branches
|
|
||||||
# that don't wind up at the root, which means we can't have
|
|
||||||
# cycles. But to quell the paranoia...
|
|
||||||
raise ValueError(
|
|
||||||
_("Unexpectedly failed to find parents already in the"
|
|
||||||
"tree for any of the following: %s") %
|
|
||||||
','.join(set(to_add_by_uuid)))
|
|
||||||
|
|
||||||
# Add or replace the provider, either as a root or under its
|
|
||||||
# parent
|
|
||||||
try:
|
|
||||||
self._remove_with_lock(uuid)
|
|
||||||
except ValueError:
|
|
||||||
# Wasn't there in the first place - fine.
|
|
||||||
pass
|
|
||||||
|
|
||||||
provider = _Provider.from_dict(pd)
|
|
||||||
if parent_uuid is None:
|
|
||||||
self.roots.append(provider)
|
|
||||||
else:
|
|
||||||
parent = self._find_with_lock(parent_uuid)
|
|
||||||
parent.add_child(provider)
|
|
||||||
|
|
||||||
# Remove this entry to signify we're done with it.
|
|
||||||
to_add_by_uuid.pop(uuid)
|
|
||||||
|
|
||||||
def _remove_with_lock(self, name_or_uuid):
|
|
||||||
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 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:
|
|
||||||
self._remove_with_lock(name_or_uuid)
|
|
||||||
|
|
||||||
def new_root(self, name, uuid, generation=None):
|
|
||||||
"""Adds a new root provider to the tree, returning its UUID.
|
|
||||||
|
|
||||||
:param name: The name of the new root provider
|
|
||||||
:param uuid: The UUID of the new root provider
|
|
||||||
:param generation: Generation to set for the new root provider
|
|
||||||
:returns: the UUID of the new provider
|
|
||||||
:raises: ValueError if a provider with the specified uuid already
|
|
||||||
exists in the tree.
|
|
||||||
"""
|
|
||||||
|
|
||||||
with self.lock:
|
|
||||||
exists = True
|
|
||||||
try:
|
|
||||||
self._find_with_lock(uuid)
|
|
||||||
except ValueError:
|
|
||||||
exists = False
|
|
||||||
|
|
||||||
if exists:
|
|
||||||
err = _("Provider %s already exists.")
|
|
||||||
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 data(self, name_or_uuid):
|
|
||||||
"""Return a point-in-time copy of the specified provider's data.
|
|
||||||
|
|
||||||
:param name_or_uuid: Either name or UUID of the resource provider whose
|
|
||||||
data is to be returned.
|
|
||||||
:return: ProviderData object representing the specified provider.
|
|
||||||
:raises: ValueError if a provider with name_or_uuid was not found in
|
|
||||||
the tree.
|
|
||||||
"""
|
|
||||||
with self.lock:
|
|
||||||
return self._find_with_lock(name_or_uuid).data()
|
|
||||||
|
|
||||||
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=None, generation=None):
|
|
||||||
"""Creates a new child provider with the given name and uuid under the
|
|
||||||
given parent.
|
|
||||||
|
|
||||||
:param name: The name of the new child provider
|
|
||||||
:param parent: Either name or UUID of the parent provider
|
|
||||||
:param uuid: The UUID of the new child provider
|
|
||||||
:param generation: Generation to set for the new child provider
|
|
||||||
:returns: the UUID of the new provider
|
|
||||||
|
|
||||||
:raises ValueError if a provider with the specified uuid or name
|
|
||||||
already exists; or if parent_uuid points to a nonexistent
|
|
||||||
provider.
|
|
||||||
"""
|
|
||||||
with self.lock:
|
|
||||||
try:
|
|
||||||
self._find_with_lock(uuid or name)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
err = _("Provider %s already exists.")
|
|
||||||
raise ValueError(err % (uuid or name))
|
|
||||||
|
|
||||||
parent_node = self._find_with_lock(parent)
|
|
||||||
p = _Provider(name, uuid, generation, parent_node.uuid)
|
|
||||||
parent_node.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
|
|
||||||
query 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=None):
|
|
||||||
"""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. If not
|
|
||||||
specified, the provider's generation is not changed.
|
|
||||||
"""
|
|
||||||
with self.lock:
|
|
||||||
provider = self._find_with_lock(name_or_uuid)
|
|
||||||
return provider.update_inventory(inventory, generation)
|
|
||||||
|
|
||||||
def has_sharing_provider(self, resource_class):
|
|
||||||
"""Returns whether the specified provider_tree contains any sharing
|
|
||||||
providers of inventory of the specified resource_class.
|
|
||||||
"""
|
|
||||||
for rp_uuid in self.get_provider_uuids():
|
|
||||||
pdata = self.data(rp_uuid)
|
|
||||||
has_rc = resource_class in pdata.inventory
|
|
||||||
is_sharing = os_traits.MISC_SHARES_VIA_AGGREGATE in pdata.traits
|
|
||||||
if has_rc and is_sharing:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def has_traits(self, name_or_uuid, traits):
|
|
||||||
"""Given a name or UUID of a provider, query whether that provider has
|
|
||||||
*all* of the specified traits.
|
|
||||||
|
|
||||||
: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
|
|
||||||
query for traits.
|
|
||||||
:param traits: Iterable of string trait names to search for.
|
|
||||||
:return: True if this provider has *all* of the specified traits; False
|
|
||||||
if any of the specified traits are absent. Returns True if
|
|
||||||
the traits parameter is empty, even if the provider has no
|
|
||||||
traits.
|
|
||||||
"""
|
|
||||||
with self.lock:
|
|
||||||
provider = self._find_with_lock(name_or_uuid)
|
|
||||||
return provider.has_traits(traits)
|
|
||||||
|
|
||||||
def have_traits_changed(self, name_or_uuid, traits):
|
|
||||||
"""Returns True if the specified traits list is different for the
|
|
||||||
provider with the specified 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
|
|
||||||
query traits for.
|
|
||||||
:param traits: Iterable of string trait names to compare against the
|
|
||||||
provider's traits.
|
|
||||||
"""
|
|
||||||
with self.lock:
|
|
||||||
provider = self._find_with_lock(name_or_uuid)
|
|
||||||
return provider.have_traits_changed(traits)
|
|
||||||
|
|
||||||
def update_traits(self, name_or_uuid, traits, generation=None):
|
|
||||||
"""Given a name or UUID of a provider and an iterable of string trait
|
|
||||||
names, update the provider's traits and set the provider's generation.
|
|
||||||
|
|
||||||
:returns: True if the traits list has changed.
|
|
||||||
|
|
||||||
:note: The provider's generation is always set to the supplied
|
|
||||||
generation, even if there were no changes to the traits.
|
|
||||||
|
|
||||||
: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 traits for.
|
|
||||||
:param traits: Iterable of string trait names to set.
|
|
||||||
:param generation: The resource provider generation to set. If None,
|
|
||||||
the provider's generation is not changed.
|
|
||||||
"""
|
|
||||||
with self.lock:
|
|
||||||
provider = self._find_with_lock(name_or_uuid)
|
|
||||||
return provider.update_traits(traits, generation=generation)
|
|
||||||
|
|
||||||
def add_traits(self, name_or_uuid, *traits):
|
|
||||||
"""Set traits on a provider, without affecting existing traits.
|
|
||||||
|
|
||||||
:param name_or_uuid: The name or UUID of the provider whose traits are
|
|
||||||
to be affected.
|
|
||||||
:param traits: String names of traits to be added.
|
|
||||||
"""
|
|
||||||
with self.lock:
|
|
||||||
provider = self._find_with_lock(name_or_uuid)
|
|
||||||
final_traits = provider.traits | set(traits)
|
|
||||||
provider.update_traits(final_traits)
|
|
||||||
|
|
||||||
def remove_traits(self, name_or_uuid, *traits):
|
|
||||||
"""Unset traits on a provider, without affecting other existing traits.
|
|
||||||
|
|
||||||
:param name_or_uuid: The name or UUID of the provider whose traits are
|
|
||||||
to be affected.
|
|
||||||
:param traits: String names of traits to be removed.
|
|
||||||
"""
|
|
||||||
with self.lock:
|
|
||||||
provider = self._find_with_lock(name_or_uuid)
|
|
||||||
final_traits = provider.traits - set(traits)
|
|
||||||
provider.update_traits(final_traits)
|
|
||||||
|
|
||||||
def in_aggregates(self, name_or_uuid, aggregates):
|
|
||||||
"""Given a name or UUID of a provider, query whether that provider is a
|
|
||||||
member of *all* the specified aggregates.
|
|
||||||
|
|
||||||
: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
|
|
||||||
query for aggregates.
|
|
||||||
:param aggregates: Iterable of string aggregate UUIDs to search for.
|
|
||||||
:return: True if this provider is associated with *all* of the
|
|
||||||
specified aggregates; False if any of the specified aggregates
|
|
||||||
are absent. Returns True if the aggregates parameter is
|
|
||||||
empty, even if the provider has no aggregate associations.
|
|
||||||
"""
|
|
||||||
with self.lock:
|
|
||||||
provider = self._find_with_lock(name_or_uuid)
|
|
||||||
return provider.in_aggregates(aggregates)
|
|
||||||
|
|
||||||
def have_aggregates_changed(self, name_or_uuid, aggregates):
|
|
||||||
"""Returns True if the specified aggregates list is different for the
|
|
||||||
provider with the specified 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
|
|
||||||
query aggregates for.
|
|
||||||
:param aggregates: Iterable of string aggregate UUIDs to compare
|
|
||||||
against the provider's aggregates.
|
|
||||||
"""
|
|
||||||
with self.lock:
|
|
||||||
provider = self._find_with_lock(name_or_uuid)
|
|
||||||
return provider.have_aggregates_changed(aggregates)
|
|
||||||
|
|
||||||
def update_aggregates(self, name_or_uuid, aggregates, generation=None):
|
|
||||||
"""Given a name or UUID of a provider and an iterable of string
|
|
||||||
aggregate UUIDs, update the provider's aggregates and set the
|
|
||||||
provider's generation.
|
|
||||||
|
|
||||||
:returns: True if the aggregates list has changed.
|
|
||||||
|
|
||||||
:note: The provider's generation is always set to the supplied
|
|
||||||
generation, even if there were no changes to the aggregates.
|
|
||||||
|
|
||||||
: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 aggregates for.
|
|
||||||
:param aggregates: Iterable of string aggregate UUIDs to set.
|
|
||||||
:param generation: The resource provider generation to set. If None,
|
|
||||||
the provider's generation is not changed.
|
|
||||||
"""
|
|
||||||
with self.lock:
|
|
||||||
provider = self._find_with_lock(name_or_uuid)
|
|
||||||
return provider.update_aggregates(aggregates,
|
|
||||||
generation=generation)
|
|
||||||
|
|
||||||
def add_aggregates(self, name_or_uuid, *aggregates):
|
|
||||||
"""Set aggregates on a provider, without affecting existing aggregates.
|
|
||||||
|
|
||||||
:param name_or_uuid: The name or UUID of the provider whose aggregates
|
|
||||||
are to be affected.
|
|
||||||
:param aggregates: String UUIDs of aggregates to be added.
|
|
||||||
"""
|
|
||||||
with self.lock:
|
|
||||||
provider = self._find_with_lock(name_or_uuid)
|
|
||||||
final_aggs = provider.aggregates | set(aggregates)
|
|
||||||
provider.update_aggregates(final_aggs)
|
|
||||||
|
|
||||||
def remove_aggregates(self, name_or_uuid, *aggregates):
|
|
||||||
"""Unset aggregates on a provider, without affecting other existing
|
|
||||||
aggregates.
|
|
||||||
|
|
||||||
:param name_or_uuid: The name or UUID of the provider whose aggregates
|
|
||||||
are to be affected.
|
|
||||||
:param aggregates: String UUIDs of aggregates to be removed.
|
|
||||||
"""
|
|
||||||
with self.lock:
|
|
||||||
provider = self._find_with_lock(name_or_uuid)
|
|
||||||
final_aggs = provider.aggregates - set(aggregates)
|
|
||||||
provider.update_aggregates(final_aggs)
|
|
@ -1,71 +0,0 @@
|
|||||||
#
|
|
||||||
# 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.
|
|
||||||
"""Standard Resource Class Fields."""
|
|
||||||
|
|
||||||
# NOTE(cdent): This is kept as its own independent file as it is used by
|
|
||||||
# both the placement and nova sides of the placement interaction. On the
|
|
||||||
# placement side we don't want to import all the nova fields, nor all the
|
|
||||||
# nova objects (which are automatically loaded and registered if the
|
|
||||||
# nova.objects package is imported).
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from oslo_versionedobjects import fields
|
|
||||||
|
|
||||||
|
|
||||||
class ResourceClass(fields.StringField):
|
|
||||||
"""Classes of resources provided to consumers."""
|
|
||||||
|
|
||||||
CUSTOM_NAMESPACE = 'CUSTOM_'
|
|
||||||
"""All non-standard resource classes must begin with this string."""
|
|
||||||
|
|
||||||
VCPU = 'VCPU'
|
|
||||||
MEMORY_MB = 'MEMORY_MB'
|
|
||||||
DISK_GB = 'DISK_GB'
|
|
||||||
PCI_DEVICE = 'PCI_DEVICE'
|
|
||||||
SRIOV_NET_VF = 'SRIOV_NET_VF'
|
|
||||||
NUMA_SOCKET = 'NUMA_SOCKET'
|
|
||||||
NUMA_CORE = 'NUMA_CORE'
|
|
||||||
NUMA_THREAD = 'NUMA_THREAD'
|
|
||||||
NUMA_MEMORY_MB = 'NUMA_MEMORY_MB'
|
|
||||||
IPV4_ADDRESS = 'IPV4_ADDRESS'
|
|
||||||
VGPU = 'VGPU'
|
|
||||||
VGPU_DISPLAY_HEAD = 'VGPU_DISPLAY_HEAD'
|
|
||||||
FPGA = 'ACCELERATOR_FPGA'
|
|
||||||
|
|
||||||
# The ordering here is relevant. If you must add a value, only
|
|
||||||
# append.
|
|
||||||
STANDARD = (VCPU, MEMORY_MB, DISK_GB, PCI_DEVICE, SRIOV_NET_VF,
|
|
||||||
NUMA_SOCKET, NUMA_CORE, NUMA_THREAD, NUMA_MEMORY_MB,
|
|
||||||
IPV4_ADDRESS, VGPU, VGPU_DISPLAY_HEAD)
|
|
||||||
|
|
||||||
# This is the set of standard resource classes that existed before
|
|
||||||
# we opened up for custom resource classes in version 1.1 of various
|
|
||||||
# objects in nova/objects/resource_provider.py
|
|
||||||
V1_0 = (VCPU, MEMORY_MB, DISK_GB, PCI_DEVICE, SRIOV_NET_VF, NUMA_SOCKET,
|
|
||||||
NUMA_CORE, NUMA_THREAD, NUMA_MEMORY_MB, IPV4_ADDRESS)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def normalize_name(cls, rc_name):
|
|
||||||
if rc_name is None:
|
|
||||||
return None
|
|
||||||
# Replace non-alphanumeric characters with underscores
|
|
||||||
norm_name = re.sub('[^0-9A-Za-z]+', '_', rc_name)
|
|
||||||
# Bug #1762789: Do .upper after replacing non alphanumerics.
|
|
||||||
norm_name = norm_name.upper()
|
|
||||||
norm_name = cls.CUSTOM_NAMESPACE + norm_name
|
|
||||||
return norm_name
|
|
||||||
|
|
||||||
|
|
||||||
class ResourceClassField(fields.AutoTypedField):
|
|
||||||
AUTO_TYPE = ResourceClass()
|
|
@ -12,7 +12,7 @@
|
|||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
import os_resource_classes as orc
|
||||||
|
|
||||||
CONDUCTOR_TOPIC = 'cyborg-conductor'
|
CONDUCTOR_TOPIC = 'cyborg-conductor'
|
||||||
AGENT_TOPIC = 'cyborg-agent'
|
AGENT_TOPIC = 'cyborg-agent'
|
||||||
@ -31,3 +31,8 @@ DEVICE_TYPE = (DEVICE_GPU, DEVICE_FPGA, DEVICE_AICHIP)
|
|||||||
# 'TEST_PCI': used by fake driver, ignored by Nova virt driver.
|
# 'TEST_PCI': used by fake driver, ignored by Nova virt driver.
|
||||||
ATTACH_HANDLE_TYPES = (AH_TYPE_PCI, AH_TYPE_MDEV, AH_TYPE_TEST_PCI) = (
|
ATTACH_HANDLE_TYPES = (AH_TYPE_PCI, AH_TYPE_MDEV, AH_TYPE_TEST_PCI) = (
|
||||||
"PCI", "MDEV", "TEST_PCI")
|
"PCI", "MDEV", "TEST_PCI")
|
||||||
|
|
||||||
|
# Resource Class
|
||||||
|
RESOURCES = {
|
||||||
|
"FPGA": orc.FPGA
|
||||||
|
}
|
||||||
|
@ -281,6 +281,10 @@ class AttributeAlreadyExists(CyborgException):
|
|||||||
|
|
||||||
# An exception with this name is used on both sides of the placement/
|
# An exception with this name is used on both sides of the placement/
|
||||||
# cyborg interaction.
|
# cyborg interaction.
|
||||||
|
class ResourceClassNotFound(NotFound):
|
||||||
|
msg_fmt = _("No such resource class %(name_or_uuid)s.")
|
||||||
|
|
||||||
|
|
||||||
class ResourceProviderInUse(CyborgException):
|
class ResourceProviderInUse(CyborgException):
|
||||||
msg_fmt = _("Resource provider has allocations.")
|
msg_fmt = _("Resource provider has allocations.")
|
||||||
|
|
||||||
@ -341,6 +345,14 @@ class ResourceProviderUpdateConflict(PlacementAPIConflict):
|
|||||||
"provider %(uuid)s (generation %(generation)d): %(error)s")
|
"provider %(uuid)s (generation %(generation)d): %(error)s")
|
||||||
|
|
||||||
|
|
||||||
|
class TraitCreationFailed(CyborgException):
|
||||||
|
msg_fmt = _("Failed to create trait %(name)s: %(error)s")
|
||||||
|
|
||||||
|
|
||||||
|
class TraitRetrievalFailed(CyborgException):
|
||||||
|
msg_fmt = _("Failed to retrieve traits from the placement API: %(error)s")
|
||||||
|
|
||||||
|
|
||||||
class InvalidResourceClass(Invalid):
|
class InvalidResourceClass(Invalid):
|
||||||
msg_fmt = _("Resource class '%(resource_class)s' invalid.")
|
msg_fmt = _("Resource class '%(resource_class)s' invalid.")
|
||||||
|
|
||||||
|
@ -13,38 +13,58 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
from cyborg.common import exception
|
||||||
|
from cyborg.conf import CONF
|
||||||
|
from keystoneauth1 import exceptions as ks_exc
|
||||||
|
from oslo_log import log as logging
|
||||||
|
from oslo_middleware import request_id
|
||||||
|
|
||||||
from openstack import connection
|
from openstack import connection
|
||||||
|
|
||||||
from oslo_log import log as logging
|
|
||||||
|
|
||||||
from cyborg.conf import CONF
|
|
||||||
|
|
||||||
_CONN = None
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
NESTED_PROVIDER_API_VERSION = '1.14'
|
||||||
|
POST_RPS_RETURNS_PAYLOAD_API_VERSION = '1.20'
|
||||||
|
PLACEMENT_CLIENT_SEMAPHORE = 'placement_client'
|
||||||
|
_CONN = None
|
||||||
|
|
||||||
|
|
||||||
def get_placement():
|
class PlacementClient(object):
|
||||||
return _PlacementClient()
|
"""Client class for reporting to placement."""
|
||||||
|
|
||||||
|
|
||||||
class _PlacementClient(object):
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
global _CONN
|
global _CONN
|
||||||
if _CONN is None:
|
if _CONN is None:
|
||||||
default_user = 'devstack-admin'
|
default_user = 'devstack-admin'
|
||||||
try:
|
try:
|
||||||
# TODO() CONF access fails.
|
|
||||||
auth_user = CONF.placement.username or default_user
|
auth_user = CONF.placement.username or default_user
|
||||||
except Exception:
|
except Exception:
|
||||||
auth_user = default_user
|
auth_user = default_user
|
||||||
_CONN = connection.Connection(cloud=auth_user)
|
_CONN = connection.Connection(cloud=auth_user)
|
||||||
self._client = _CONN.placement
|
self._client = _CONN.placement
|
||||||
|
|
||||||
|
def get(self, url, version=None, global_request_id=None):
|
||||||
|
return self._client.get(url, microversion=version,
|
||||||
|
global_request_id=global_request_id)
|
||||||
|
|
||||||
|
def post(self, url, data, version=None, global_request_id=None):
|
||||||
|
return self._client.post(url, json=data, microversion=version,
|
||||||
|
global_request_id=global_request_id)
|
||||||
|
|
||||||
|
def put(self, url, data, version=None, global_request_id=None):
|
||||||
|
kwargs = {}
|
||||||
|
if data is not None:
|
||||||
|
kwargs['json'] = data
|
||||||
|
return self._client.put(url, microversion=version,
|
||||||
|
global_request_id=global_request_id,
|
||||||
|
**kwargs)
|
||||||
|
|
||||||
|
def delete(self, url, version=None, global_request_id=None):
|
||||||
|
return self._client.delete(url, microversion=version,
|
||||||
|
global_request_id=global_request_id)
|
||||||
|
|
||||||
def _get_rp_traits(self, rp_uuid):
|
def _get_rp_traits(self, rp_uuid):
|
||||||
placement = self._client
|
resp = self.get("/resource_providers/%s/traits" % rp_uuid,
|
||||||
resp = placement.get("/resource_providers/%s/traits" % rp_uuid,
|
version='1.6')
|
||||||
microversion='1.6')
|
|
||||||
if resp.status_code != 200:
|
if resp.status_code != 200:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"Failed to get traits for rp %s: HTTP %d: %s" %
|
"Failed to get traits for rp %s: HTTP %d: %s" %
|
||||||
@ -52,9 +72,10 @@ class _PlacementClient(object):
|
|||||||
return resp.json()
|
return resp.json()
|
||||||
|
|
||||||
def _ensure_traits(self, trait_names):
|
def _ensure_traits(self, trait_names):
|
||||||
placement = self._client
|
# TODO(Xinran): maintain a reference count of how many RPs use
|
||||||
|
# this trait and do the deletion only when the last RP is deleted.
|
||||||
for trait in trait_names:
|
for trait in trait_names:
|
||||||
resp = placement.put('/traits/' + trait, microversion='1.6')
|
resp = self.put("/traits/%s" % trait, None, version='1.6')
|
||||||
if resp.status_code == 201:
|
if resp.status_code == 201:
|
||||||
LOG.info("Created trait %(trait)s", {"trait": trait})
|
LOG.info("Created trait %(trait)s", {"trait": trait})
|
||||||
elif resp.status_code == 204:
|
elif resp.status_code == 204:
|
||||||
@ -65,9 +86,14 @@ class _PlacementClient(object):
|
|||||||
(trait, resp.status_code, resp.text))
|
(trait, resp.status_code, resp.text))
|
||||||
|
|
||||||
def _put_rp_traits(self, rp_uuid, traits_json):
|
def _put_rp_traits(self, rp_uuid, traits_json):
|
||||||
placement = self._client
|
generation = self.get_resource_provider(
|
||||||
resp = placement.put("/resource_providers/%s/traits" % rp_uuid,
|
resource_provider_uuid=rp_uuid)['generation']
|
||||||
json=traits_json, microversion='1.6')
|
payload = {
|
||||||
|
'resource_provider_generation': generation,
|
||||||
|
'traits': traits_json["traits"],
|
||||||
|
}
|
||||||
|
resp = self.put(
|
||||||
|
"/resource_providers/%s/traits" % rp_uuid, payload, version='1.6')
|
||||||
if resp.status_code != 200:
|
if resp.status_code != 200:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"Failed to set traits to %s for rp %s: HTTP %d: %s" %
|
"Failed to set traits to %s for rp %s: HTTP %d: %s" %
|
||||||
@ -79,8 +105,15 @@ class _PlacementClient(object):
|
|||||||
traits = list(set(traits_json['traits'] + trait_names))
|
traits = list(set(traits_json['traits'] + trait_names))
|
||||||
traits_json['traits'] = traits
|
traits_json['traits'] = traits
|
||||||
self._put_rp_traits(rp_uuid, traits_json)
|
self._put_rp_traits(rp_uuid, traits_json)
|
||||||
LOG.info('Added traits %(traits)s to RP %(rp_uuid)s',
|
|
||||||
{"traits": traits, "rp_uuid": rp_uuid})
|
def delete_trait_by_name(self, rp_uuid, trait_name):
|
||||||
|
traits_json = self._get_rp_traits(rp_uuid)
|
||||||
|
traits = [
|
||||||
|
trait for trait in traits_json['traits']
|
||||||
|
if trait != trait_name
|
||||||
|
]
|
||||||
|
traits_json['traits'] = traits
|
||||||
|
self._put_rp_traits(rp_uuid, traits_json)
|
||||||
|
|
||||||
def delete_traits_with_prefixes(self, rp_uuid, trait_prefixes):
|
def delete_traits_with_prefixes(self, rp_uuid, trait_prefixes):
|
||||||
traits_json = self._get_rp_traits(rp_uuid)
|
traits_json = self._get_rp_traits(rp_uuid)
|
||||||
@ -90,5 +123,174 @@ class _PlacementClient(object):
|
|||||||
for prefix in trait_prefixes)]
|
for prefix in trait_prefixes)]
|
||||||
traits_json['traits'] = traits
|
traits_json['traits'] = traits
|
||||||
self._put_rp_traits(rp_uuid, traits_json)
|
self._put_rp_traits(rp_uuid, traits_json)
|
||||||
LOG.info('Deleted traits %(traits)s to RP %(rp_uuid)s',
|
|
||||||
{"traits": traits, "rp_uuid": rp_uuid})
|
def get_placement_request_id(self, response):
|
||||||
|
if response is not None:
|
||||||
|
return response.headers.get(request_id.HTTP_RESP_HEADER_REQUEST_ID)
|
||||||
|
|
||||||
|
def _update_inventory(
|
||||||
|
self, resource_provider_uuid, inventories,
|
||||||
|
resource_provider_generation=None):
|
||||||
|
if resource_provider_generation is None:
|
||||||
|
resource_provider_generation = self.get_resource_provider(
|
||||||
|
resource_provider_uuid=resource_provider_uuid)['generation']
|
||||||
|
url = '/resource_providers/%s/inventories' % resource_provider_uuid
|
||||||
|
body = {
|
||||||
|
'resource_provider_generation': resource_provider_generation,
|
||||||
|
'inventories': inventories
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
return self.put(url, body).json()
|
||||||
|
except ks_exc.NotFound:
|
||||||
|
raise exception.PlacementResourceProviderNotFound(
|
||||||
|
resource_provider=resource_provider_uuid)
|
||||||
|
|
||||||
|
def get_resource_provider(self, resource_provider_uuid):
|
||||||
|
"""Get resource provider by UUID.
|
||||||
|
|
||||||
|
:param resource_provider_uuid: UUID of the resource provider.
|
||||||
|
:raises PlacementResourceProviderNotFound: For failure to find resource
|
||||||
|
:returns: The Resource Provider matching the UUID.
|
||||||
|
"""
|
||||||
|
url = '/resource_providers/%s' % resource_provider_uuid
|
||||||
|
try:
|
||||||
|
return self.get(url).json()
|
||||||
|
except ks_exc.NotFound:
|
||||||
|
raise exception.PlacementResourceProviderNotFound(
|
||||||
|
resource_provider=resource_provider_uuid)
|
||||||
|
|
||||||
|
def _create_resource_provider(self, context, uuid, name,
|
||||||
|
parent_provider_uuid=None):
|
||||||
|
"""Calls the placement API to create a new resource provider record.
|
||||||
|
|
||||||
|
:param context: The security context
|
||||||
|
:param uuid: UUID of the new resource provider
|
||||||
|
:param name: Name of the resource provider
|
||||||
|
:param parent_provider_uuid: Optional UUID of the immediate parent
|
||||||
|
:return: A dict of resource provider information object representing
|
||||||
|
the newly-created resource provider.
|
||||||
|
:raise: ResourceProviderCreationFailed or
|
||||||
|
ResourceProviderRetrievalFailed on error.
|
||||||
|
"""
|
||||||
|
url = "/resource_providers"
|
||||||
|
payload = {
|
||||||
|
'uuid': uuid,
|
||||||
|
'name': name,
|
||||||
|
}
|
||||||
|
if parent_provider_uuid is not None:
|
||||||
|
payload['parent_provider_uuid'] = parent_provider_uuid
|
||||||
|
|
||||||
|
# Bug #1746075: First try the microversion that returns the new
|
||||||
|
# provider's payload.
|
||||||
|
resp = self.post(url, payload,
|
||||||
|
version=POST_RPS_RETURNS_PAYLOAD_API_VERSION,
|
||||||
|
global_request_id=context.global_id)
|
||||||
|
|
||||||
|
placement_req_id = self.get_placement_request_id(resp)
|
||||||
|
|
||||||
|
if resp:
|
||||||
|
msg = ("[%(placement_req_id)s] Created resource provider record "
|
||||||
|
"via placement API for resource provider with UUID "
|
||||||
|
"%(uuid)s and name %(name)s.")
|
||||||
|
args = {
|
||||||
|
'uuid': uuid,
|
||||||
|
'name': name,
|
||||||
|
'placement_req_id': placement_req_id,
|
||||||
|
}
|
||||||
|
LOG.info(msg, args)
|
||||||
|
return resp.json()
|
||||||
|
|
||||||
|
def ensure_resource_provider(self, context, uuid, name=None,
|
||||||
|
parent_provider_uuid=None):
|
||||||
|
resp = self.get("/resource_providers/%s" % uuid, version='1.6')
|
||||||
|
if resp.status_code == 200:
|
||||||
|
LOG.info("Resource Provider %(uuid)s already exists",
|
||||||
|
{"uuid": uuid})
|
||||||
|
else:
|
||||||
|
LOG.info("Creating resource provider %(provider)s",
|
||||||
|
{"provider": name or uuid})
|
||||||
|
try:
|
||||||
|
resp = self._create_resource_provider(context, uuid, name,
|
||||||
|
parent_provider_uuid)
|
||||||
|
except Exception:
|
||||||
|
raise exception.ResourceProviderCreationFailed(
|
||||||
|
name=name or uuid)
|
||||||
|
return uuid
|
||||||
|
|
||||||
|
def ensure_resource_classes(self, context, names):
|
||||||
|
"""Make sure resource classes exist."""
|
||||||
|
version = '1.7'
|
||||||
|
to_ensure = set(names)
|
||||||
|
for name in to_ensure:
|
||||||
|
# no payload on the put request
|
||||||
|
resp = self.put(
|
||||||
|
"/resource_classes/%s" % name, None, version=version,
|
||||||
|
global_request_id=context.global_id)
|
||||||
|
if not resp:
|
||||||
|
msg = ("Failed to ensure resource class record with placement "
|
||||||
|
"API for resource class %(rc_name)s. Got "
|
||||||
|
"%(status_code)d: %(err_text)s.")
|
||||||
|
args = {
|
||||||
|
'rc_name': name,
|
||||||
|
'status_code': resp.status_code,
|
||||||
|
'err_text': resp.text,
|
||||||
|
}
|
||||||
|
LOG.error(msg, args)
|
||||||
|
raise exception.InvalidResourceClass(resource_class=name)
|
||||||
|
|
||||||
|
def _get_providers_in_tree(self, context, uuid):
|
||||||
|
"""Queries the placement API for a list of the resource providers in
|
||||||
|
the tree associated with the specified UUID.
|
||||||
|
|
||||||
|
:param context: The security context
|
||||||
|
:param uuid: UUID identifier for the resource provider to look up
|
||||||
|
:return: A list of dicts of resource provider information, which may be
|
||||||
|
empty if no provider exists with the specified UUID.
|
||||||
|
:raise: ResourceProviderRetrievalFailed on error.
|
||||||
|
"""
|
||||||
|
resp = self.get("/resource_providers?in_tree=%s" % uuid,
|
||||||
|
version=NESTED_PROVIDER_API_VERSION,
|
||||||
|
global_request_id=context.global_id)
|
||||||
|
|
||||||
|
if resp.status_code == 200:
|
||||||
|
return resp.json()['resource_providers']
|
||||||
|
|
||||||
|
# Some unexpected error
|
||||||
|
placement_req_id = self.get_placement_request_id(resp)
|
||||||
|
msg = ("[%(placement_req_id)s] Failed to retrieve resource provider "
|
||||||
|
"tree from placement API for UUID %(uuid)s. Got "
|
||||||
|
"%(status_code)d: %(err_text)s.")
|
||||||
|
args = {
|
||||||
|
'uuid': uuid,
|
||||||
|
'status_code': resp.status_code,
|
||||||
|
'err_text': resp.text,
|
||||||
|
'placement_req_id': placement_req_id,
|
||||||
|
}
|
||||||
|
LOG.error(msg, args)
|
||||||
|
raise exception.ResourceProviderRetrievalFailed(uuid=uuid)
|
||||||
|
|
||||||
|
def _delete_provider(self, rp_uuid, global_request_id=None):
|
||||||
|
resp = self.delete('/resource_providers/%s' % rp_uuid,
|
||||||
|
global_request_id=global_request_id)
|
||||||
|
# Check for 404 since we don't need to warn/raise if we tried to delete
|
||||||
|
# something which doesn"t actually exist.
|
||||||
|
if resp.ok:
|
||||||
|
LOG.info("Deleted resource provider %s", rp_uuid)
|
||||||
|
return
|
||||||
|
|
||||||
|
msg = ("[%(placement_req_id)s] Failed to delete resource provider "
|
||||||
|
"with UUID %(uuid)s from the placement API. Got "
|
||||||
|
"%(status_code)d: %(err_text)s.")
|
||||||
|
args = {
|
||||||
|
'placement_req_id': self.get_placement_request_id(resp),
|
||||||
|
'uuid': rp_uuid,
|
||||||
|
'status_code': resp.status_code,
|
||||||
|
'err_text': resp.text
|
||||||
|
}
|
||||||
|
LOG.error(msg, args)
|
||||||
|
# On conflict, the caller may wish to delete allocations and
|
||||||
|
# redrive. (Note that this is not the same as a
|
||||||
|
# PlacementAPIConflict case.)
|
||||||
|
if resp.status_code == 409:
|
||||||
|
raise exception.ResourceProviderInUse()
|
||||||
|
raise exception.ResourceProviderDeletionFailed(uuid=rp_uuid)
|
||||||
|
@ -15,7 +15,11 @@
|
|||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
import oslo_messaging as messaging
|
import oslo_messaging as messaging
|
||||||
|
from oslo_utils import encodeutils
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from cyborg.common import exception
|
||||||
|
from cyborg.common import placement_client
|
||||||
from cyborg.conf import CONF
|
from cyborg.conf import CONF
|
||||||
from cyborg import objects
|
from cyborg import objects
|
||||||
from cyborg.objects.attach_handle import AttachHandle
|
from cyborg.objects.attach_handle import AttachHandle
|
||||||
@ -23,6 +27,7 @@ from cyborg.objects.attribute import Attribute
|
|||||||
from cyborg.objects.control_path import ControlpathID
|
from cyborg.objects.control_path import ControlpathID
|
||||||
from cyborg.objects.deployable import Deployable
|
from cyborg.objects.deployable import Deployable
|
||||||
from cyborg.objects.device import Device
|
from cyborg.objects.device import Device
|
||||||
|
from cyborg.objects.driver_objects.driver_device import DriverDeployable
|
||||||
from cyborg.objects.driver_objects.driver_device import DriverDevice
|
from cyborg.objects.driver_objects.driver_device import DriverDevice
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@ -38,6 +43,7 @@ class ConductorManager(object):
|
|||||||
super(ConductorManager, self).__init__()
|
super(ConductorManager, self).__init__()
|
||||||
self.topic = topic
|
self.topic = topic
|
||||||
self.host = host or CONF.host
|
self.host = host or CONF.host
|
||||||
|
self.placement_client = placement_client.PlacementClient()
|
||||||
|
|
||||||
def periodic_tasks(self, context, raise_on_error=False):
|
def periodic_tasks(self, context, raise_on_error=False):
|
||||||
pass
|
pass
|
||||||
@ -133,11 +139,10 @@ class ConductorManager(object):
|
|||||||
old_driver_device_list = DriverDevice.list(context, hostname)
|
old_driver_device_list = DriverDevice.list(context, hostname)
|
||||||
# TODO(wangzhh): Remove invalid driver_devices without controlpath_id.
|
# TODO(wangzhh): Remove invalid driver_devices without controlpath_id.
|
||||||
# Then diff two driver device list.
|
# Then diff two driver device list.
|
||||||
self.drv_device_make_diff(context, hostname, old_driver_device_list,
|
self.drv_device_make_diff(context, hostname,
|
||||||
driver_device_list)
|
old_driver_device_list, driver_device_list)
|
||||||
|
|
||||||
@classmethod
|
def drv_device_make_diff(self, context, host, old_driver_device_list,
|
||||||
def drv_device_make_diff(cls, context, host, old_driver_device_list,
|
|
||||||
new_driver_device_list):
|
new_driver_device_list):
|
||||||
"""Compare new driver-side device object list with the old one in
|
"""Compare new driver-side device object list with the old one in
|
||||||
one host.
|
one host.
|
||||||
@ -155,6 +160,7 @@ class ConductorManager(object):
|
|||||||
same = set(new_cpid_list) & set(old_cpid_list) - set(stub_cpid_list)
|
same = set(new_cpid_list) & set(old_cpid_list) - set(stub_cpid_list)
|
||||||
added = set(new_cpid_list) - same - set(stub_cpid_list)
|
added = set(new_cpid_list) - same - set(stub_cpid_list)
|
||||||
deleted = set(old_cpid_list) - same - set(stub_cpid_list)
|
deleted = set(old_cpid_list) - same - set(stub_cpid_list)
|
||||||
|
host_rp = self._get_root_provider(context, host)
|
||||||
for s in same:
|
for s in same:
|
||||||
# get the driver_dev_obj, diff the driver_device layer
|
# get the driver_dev_obj, diff the driver_device layer
|
||||||
new_driver_dev_obj = new_driver_device_list[new_cpid_list.index(s)]
|
new_driver_dev_obj = new_driver_device_list[new_cpid_list.index(s)]
|
||||||
@ -179,21 +185,29 @@ class ConductorManager(object):
|
|||||||
setattr(dev_obj, c_k, getattr(new_driver_dev_obj, c_k))
|
setattr(dev_obj, c_k, getattr(new_driver_dev_obj, c_k))
|
||||||
dev_obj.save(context)
|
dev_obj.save(context)
|
||||||
# diff the internal layer: driver_deployable
|
# diff the internal layer: driver_deployable
|
||||||
cls.drv_deployable_make_diff(context, dev_obj.id, cpid_obj.id,
|
self.drv_deployable_make_diff(context, dev_obj.id, cpid_obj.id,
|
||||||
old_driver_dev_obj.deployable_list,
|
old_driver_dev_obj.deployable_list,
|
||||||
new_driver_dev_obj.deployable_list)
|
new_driver_dev_obj.deployable_list,
|
||||||
|
host_rp)
|
||||||
# device is deleted.
|
# device is deleted.
|
||||||
for d in deleted:
|
for d in deleted:
|
||||||
old_driver_dev_obj = old_driver_device_list[old_cpid_list.index(d)]
|
old_driver_dev_obj = old_driver_device_list[old_cpid_list.index(d)]
|
||||||
|
for driver_dep_obj in old_driver_dev_obj.deployable_list:
|
||||||
|
rp_uuid = self.get_pr_uuid_from_obj(driver_dep_obj)
|
||||||
|
self._delete_provider_and_sub_providers(context, rp_uuid)
|
||||||
old_driver_dev_obj.destroy(context, host)
|
old_driver_dev_obj.destroy(context, host)
|
||||||
# device is added
|
# device is added
|
||||||
for a in added:
|
for a in added:
|
||||||
new_driver_dev_obj = new_driver_device_list[new_cpid_list.index(a)]
|
new_driver_dev_obj = new_driver_device_list[new_cpid_list.index(a)]
|
||||||
new_driver_dev_obj.create(context, host)
|
new_driver_dev_obj.create(context, host)
|
||||||
|
for driver_dep_obj in new_driver_dev_obj.deployable_list:
|
||||||
|
self.get_placement_needed_info_and_report(context,
|
||||||
|
driver_dep_obj,
|
||||||
|
host_rp)
|
||||||
|
|
||||||
@classmethod
|
def drv_deployable_make_diff(self, context, device_id, cpid_id,
|
||||||
def drv_deployable_make_diff(cls, context, device_id, cpid_id,
|
old_driver_dep_list, new_driver_dep_list,
|
||||||
old_driver_dep_list, new_driver_dep_list):
|
host_rp):
|
||||||
"""Compare new driver-side deployable object list with the old one in
|
"""Compare new driver-side deployable object list with the old one in
|
||||||
one host.
|
one host.
|
||||||
"""
|
"""
|
||||||
@ -216,31 +230,45 @@ class ConductorManager(object):
|
|||||||
if dep_obj.num_accelerators != new_driver_dep_obj.num_accelerators:
|
if dep_obj.num_accelerators != new_driver_dep_obj.num_accelerators:
|
||||||
dep_obj.num_accelerators = new_driver_dep_obj.num_accelerators
|
dep_obj.num_accelerators = new_driver_dep_obj.num_accelerators
|
||||||
dep_obj.save(context)
|
dep_obj.save(context)
|
||||||
|
rp_uuid = self.get_pr_uuid_from_obj(new_driver_dep_obj)
|
||||||
|
rc = new_driver_dep_obj.name
|
||||||
|
inv_date = \
|
||||||
|
self._gen_resource_inventory(
|
||||||
|
rc, total=dep_obj.num_accelerators)
|
||||||
|
self.placement_client._placement_client._update_inventory(
|
||||||
|
rp_uuid, inv_date)
|
||||||
# diff the internal layer: driver_attribute_list
|
# diff the internal layer: driver_attribute_list
|
||||||
new_attribute_list = []
|
new_attribute_list = []
|
||||||
if hasattr(new_driver_dep_obj, 'attribute_list'):
|
if hasattr(new_driver_dep_obj, 'attribute_list'):
|
||||||
new_attribute_list = new_driver_dep_obj.attribute_list
|
new_attribute_list = new_driver_dep_obj.attribute_list
|
||||||
cls.drv_attr_make_diff(context, dep_obj.id,
|
self.drv_attr_make_diff(context, dep_obj.id,
|
||||||
old_driver_dep_obj.attribute_list,
|
old_driver_dep_obj.attribute_list,
|
||||||
new_attribute_list)
|
new_attribute_list)
|
||||||
# diff the internal layer: driver_attach_hanle_list
|
# diff the internal layer: driver_attach_hanle_list
|
||||||
cls.drv_ah_make_diff(context, dep_obj.id, cpid_id,
|
self.drv_ah_make_diff(context, dep_obj.id, cpid_id,
|
||||||
old_driver_dep_obj.attach_handle_list,
|
old_driver_dep_obj.attach_handle_list,
|
||||||
new_driver_dep_obj.attach_handle_list)
|
new_driver_dep_obj.attach_handle_list)
|
||||||
# name is deleted.
|
# name is deleted.
|
||||||
for d in deleted:
|
for d in deleted:
|
||||||
old_driver_dep_obj = old_driver_dep_list[old_name_list.index(d)]
|
old_driver_dep_obj = old_driver_dep_list[old_name_list.index(d)]
|
||||||
|
rp_uuid = self.get_pr_uuid_from_obj(old_driver_dep_obj)
|
||||||
old_driver_dep_obj.destroy(context, device_id)
|
old_driver_dep_obj.destroy(context, device_id)
|
||||||
|
self._delete_provider_and_sub_providers(context, rp_uuid)
|
||||||
# name is added.
|
# name is added.
|
||||||
for a in added:
|
for a in added:
|
||||||
new_driver_dep_obj = new_driver_dep_list[new_name_list.index(a)]
|
new_driver_dep_obj = new_driver_dep_list[new_name_list.index(a)]
|
||||||
new_driver_dep_obj.create(context, device_id, cpid_id)
|
new_driver_dep_obj.create(context, device_id, cpid_id)
|
||||||
|
self.get_placement_needed_info_and_report(context,
|
||||||
|
new_driver_dep_obj,
|
||||||
|
host_rp)
|
||||||
|
|
||||||
@classmethod
|
def drv_attr_make_diff(self, context, dep_id, old_driver_attr_list,
|
||||||
def drv_attr_make_diff(cls, context, dep_id, old_driver_attr_list,
|
|
||||||
new_driver_attr_list):
|
new_driver_attr_list):
|
||||||
"""Diff new dirver-side Attribute Object lists with the old one."""
|
"""Diff new dirver-side Attribute Object lists with the old one."""
|
||||||
LOG.info("Start differing attributes.")
|
LOG.info("Start differing attributes.")
|
||||||
|
dep_obj = Deployable.get_by_id(context, dep_id)
|
||||||
|
driver_dep = DriverDeployable.get_by_name(context, dep_obj.name)
|
||||||
|
rp_uuid = self.get_pr_uuid_from_obj(driver_dep)
|
||||||
new_key_list = [driver_attr_obj.key for driver_attr_obj in
|
new_key_list = [driver_attr_obj.key for driver_attr_obj in
|
||||||
new_driver_attr_list]
|
new_driver_attr_list]
|
||||||
old_key_list = [driver_attr_obj.key for driver_attr_obj in
|
old_key_list = [driver_attr_obj.key for driver_attr_obj in
|
||||||
@ -255,17 +283,26 @@ class ConductorManager(object):
|
|||||||
attr_obj = Attribute.get_by_dep_key(context, dep_id, s)
|
attr_obj = Attribute.get_by_dep_key(context, dep_id, s)
|
||||||
attr_obj.value = new_driver_attr_obj.value
|
attr_obj.value = new_driver_attr_obj.value
|
||||||
attr_obj.save(context)
|
attr_obj.save(context)
|
||||||
|
# Update traits here.
|
||||||
|
if new_driver_attr_obj.key.startswith("trait"):
|
||||||
|
self.placement_client.delete_trait_by_name(
|
||||||
|
rp_uuid, old_driver_attr_obj.value)
|
||||||
|
self.placement_client.add_traits_to_rp(
|
||||||
|
rp_uuid, [new_driver_attr_obj.value])
|
||||||
# key is deleted.
|
# key is deleted.
|
||||||
deleted = set(old_key_list) - same
|
deleted = set(old_key_list) - same
|
||||||
for d in deleted:
|
for d in deleted:
|
||||||
old_driver_attr_obj = old_driver_attr_list[
|
old_driver_attr_obj = old_driver_attr_list[old_key_list.index(d)]
|
||||||
old_key_list.index(d)]
|
self.placement_client.delete_trait_by_name(
|
||||||
old_driver_attr_obj.destroy(context, dep_id)
|
rp_uuid, old_driver_attr_obj.value)
|
||||||
|
old_driver_attr_obj.delete_by_key(context, dep_id, d)
|
||||||
# key is added.
|
# key is added.
|
||||||
added = set(new_key_list) - same
|
added = set(new_key_list) - same
|
||||||
for a in added:
|
for a in added:
|
||||||
new_driver_attr_obj = new_driver_attr_list[new_key_list.index(a)]
|
new_driver_attr_obj = new_driver_attr_list[new_key_list.index(a)]
|
||||||
new_driver_attr_obj.create(context, dep_id)
|
new_driver_attr_obj.create(context, dep_id)
|
||||||
|
self.placement_client.add_traits_to_rp(
|
||||||
|
rp_uuid, [new_driver_attr_obj.value])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def drv_ah_make_diff(cls, context, dep_id, cpid_id, old_driver_ah_list,
|
def drv_ah_make_diff(cls, context, dep_id, cpid_id, old_driver_ah_list,
|
||||||
@ -302,3 +339,91 @@ class ConductorManager(object):
|
|||||||
for a in added:
|
for a in added:
|
||||||
new_driver_ah_obj = new_driver_ah_list[new_info_list.index(a)]
|
new_driver_ah_obj = new_driver_ah_list[new_info_list.index(a)]
|
||||||
new_driver_ah_obj.create(context, dep_id, cpid_id)
|
new_driver_ah_obj.create(context, dep_id, cpid_id)
|
||||||
|
|
||||||
|
def _get_root_provider(self, context, hostname):
|
||||||
|
try:
|
||||||
|
provider = self.placement_client.get(
|
||||||
|
"resource_providers?name=" + hostname).json()
|
||||||
|
pr_uuid = provider["resource_providers"][0]["uuid"]
|
||||||
|
return pr_uuid
|
||||||
|
except IndexError:
|
||||||
|
LOG.error("Error, provider %(hostname)s can not be found",
|
||||||
|
{"hostname": hostname})
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error("Error, could not access placement. Details: %(info)s",
|
||||||
|
{"info": e})
|
||||||
|
return
|
||||||
|
|
||||||
|
def _get_sub_provider(self, context, parent, name):
|
||||||
|
name = encodeutils.safe_encode(name)
|
||||||
|
old_sub_pr_uuid = str(uuid.uuid3(uuid.NAMESPACE_DNS, name))
|
||||||
|
new_sub_pr_uuid = self.placement_client.ensure_resource_provider(
|
||||||
|
context, old_sub_pr_uuid,
|
||||||
|
name=name, parent_provider_uuid=parent)
|
||||||
|
if old_sub_pr_uuid == new_sub_pr_uuid:
|
||||||
|
return new_sub_pr_uuid
|
||||||
|
else:
|
||||||
|
raise exception.Conflict()
|
||||||
|
|
||||||
|
def provider_report(self, context, name, resource_class, traits, total,
|
||||||
|
parent):
|
||||||
|
try:
|
||||||
|
self.placement_client.get("/resource_classes/%s" %
|
||||||
|
resource_class,
|
||||||
|
version='1.26')
|
||||||
|
except Exception as e:
|
||||||
|
self.placement_client.ensure_resource_classes(context,
|
||||||
|
[resource_class])
|
||||||
|
LOG.error("Error, could not access resource_classes."
|
||||||
|
"Details: %(info)s", {"info": e})
|
||||||
|
|
||||||
|
sub_pr_uuid = self._get_sub_provider(
|
||||||
|
context, parent, name)
|
||||||
|
result = self._gen_resource_inventory(resource_class, total)
|
||||||
|
self.placement_client._update_inventory(sub_pr_uuid, result)
|
||||||
|
# traits = ["CUSTOM_FPGA_INTEL", "CUSTOM_FPGA_INTEL_ARRIA10",
|
||||||
|
# "CUSTOM_FPGA_INTEL_REGION_UUID",
|
||||||
|
# "CUSTOM_FPGA_FUNCTION_ID_INTEL_UUID",
|
||||||
|
# "CUSTOM_PROGRAMMABLE",
|
||||||
|
# "CUSTOM_FPGA_NETWORK"]
|
||||||
|
self.placement_client.add_traits_to_rp(sub_pr_uuid, traits)
|
||||||
|
return sub_pr_uuid
|
||||||
|
|
||||||
|
def get_placement_needed_info_and_report(self, context, obj,
|
||||||
|
parent_uuid=None):
|
||||||
|
pr_name = obj.name
|
||||||
|
attrs = obj.attribute_list
|
||||||
|
resource_class = [i.value for i in attrs if i.key == 'rc'][0]
|
||||||
|
traits = [i.value for i in attrs
|
||||||
|
if encodeutils.safe_encode(i.key).startswith("trait")]
|
||||||
|
total = obj.num_accelerators
|
||||||
|
rp_uuid = self.provider_report(context, pr_name, resource_class,
|
||||||
|
traits, total, parent_uuid)
|
||||||
|
dep_obj = Deployable.get_by_name(context, pr_name)
|
||||||
|
dep_obj["rp_uuid"] = rp_uuid
|
||||||
|
dep_obj.save(context)
|
||||||
|
|
||||||
|
def _gen_resource_inventory(self, name, total=0, max=1, min=1, step=1):
|
||||||
|
result = {}
|
||||||
|
result[name] = {
|
||||||
|
'total': total,
|
||||||
|
'min_unit': min,
|
||||||
|
'max_unit': max,
|
||||||
|
'step_size': step,
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
|
||||||
|
def get_pr_uuid_from_obj(self, obj):
|
||||||
|
pr_name = encodeutils.safe_encode(obj.name)
|
||||||
|
return str(uuid.uuid3(uuid.NAMESPACE_DNS, pr_name))
|
||||||
|
|
||||||
|
def _delete_provider_and_sub_providers(self, context, rp_uuid):
|
||||||
|
rp_in_tree = self.placement_client._get_providers_in_tree(context,
|
||||||
|
rp_uuid)
|
||||||
|
for rp in rp_in_tree[::-1]:
|
||||||
|
if rp["parent_provider_uuid"] == rp_uuid or rp["uuid"] == rp_uuid:
|
||||||
|
self.placement_client._delete_provider(rp["uuid"])
|
||||||
|
LOG.info("Sucessfully delete resource provider %(rp_uuid)s",
|
||||||
|
{"rp_uuid": rp["uuid"]})
|
||||||
|
if rp["uuid"] == rp_uuid:
|
||||||
|
break
|
||||||
|
@ -106,7 +106,6 @@ def register_placement_opts(cfg=cfg.CONF):
|
|||||||
|
|
||||||
|
|
||||||
DEFAULT_OPTS = (exc_log_opts + service_opts + path_opts)
|
DEFAULT_OPTS = (exc_log_opts + service_opts + path_opts)
|
||||||
PLACEMENT_OPTS = (placement_opts)
|
|
||||||
|
|
||||||
|
|
||||||
def list_opts():
|
def list_opts():
|
||||||
|
@ -115,6 +115,12 @@ class Deployable(base.CyborgObject, object_base.VersionedObjectDictCompat):
|
|||||||
def save(self, context):
|
def save(self, context):
|
||||||
"""Update a Deployable record in the DB."""
|
"""Update a Deployable record in the DB."""
|
||||||
updates = self.obj_get_changes()
|
updates = self.obj_get_changes()
|
||||||
|
# TODO(Xinran): Will remove this if find some better way.
|
||||||
|
updates.pop("uuid", None)
|
||||||
|
updates.pop("created_at", None)
|
||||||
|
if "updated_at" in updates.keys() and \
|
||||||
|
updates["updated_at"] is not None:
|
||||||
|
updates["updated_at"] = updates["updated_at"].replace(tzinfo=None)
|
||||||
db_dep = self.dbapi.deployable_update(context, self.uuid, updates)
|
db_dep = self.dbapi.deployable_update(context, self.uuid, updates)
|
||||||
self.obj_reset_changes()
|
self.obj_reset_changes()
|
||||||
self._from_db_object(self, db_dep)
|
self._from_db_object(self, db_dep)
|
||||||
@ -213,3 +219,12 @@ class Deployable(base.CyborgObject, object_base.VersionedObjectDictCompat):
|
|||||||
return dep_obj_list[0]
|
return dep_obj_list[0]
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_by_name(cls, context, name):
|
||||||
|
dep_filter = {'name': name}
|
||||||
|
dep_obj_list = Deployable.list(context, dep_filter)
|
||||||
|
if len(dep_obj_list) != 0:
|
||||||
|
return dep_obj_list[0]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
@ -94,3 +94,11 @@ class Device(base.CyborgObject, object_base.VersionedObjectDictCompat):
|
|||||||
dev_filter = {'hostname': hostname}
|
dev_filter = {'hostname': hostname}
|
||||||
device_obj_list = Device.list(context, dev_filter)
|
device_obj_list = Device.list(context, dev_filter)
|
||||||
return device_obj_list
|
return device_obj_list
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_by_device_id(cls, context, device_id):
|
||||||
|
"""get device object list from the device ID. return [] if not
|
||||||
|
exist."""
|
||||||
|
dev_filter = {'device_id': device_id}
|
||||||
|
device_obj_list = Device.list(context, dev_filter)
|
||||||
|
return device_obj_list
|
||||||
|
@ -45,6 +45,14 @@ class DriverAttribute(base.DriverObjectBase,
|
|||||||
for attr_obj in attr_obj_list:
|
for attr_obj in attr_obj_list:
|
||||||
attr_obj.destroy(context)
|
attr_obj.destroy(context)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def delete_by_key(cls, context, deployable_id, key):
|
||||||
|
"""Delete driver-side attribute list from the DB."""
|
||||||
|
attr_obj_list = Attribute.get_by_deployable_id(context, deployable_id)
|
||||||
|
for attr_obj in attr_obj_list:
|
||||||
|
if key == attr_obj.key:
|
||||||
|
attr_obj.destroy(context)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def list(cls, context, deployable_id):
|
def list(cls, context, deployable_id):
|
||||||
"""Form driver-side attribute list for one deployable."""
|
"""Form driver-side attribute list for one deployable."""
|
||||||
|
@ -103,3 +103,17 @@ class DriverDeployable(base.DriverObjectBase,
|
|||||||
attach_handle_list=driver_ah_obj_list)
|
attach_handle_list=driver_ah_obj_list)
|
||||||
driver_dep_obj_list.append(driver_dep_obj)
|
driver_dep_obj_list.append(driver_dep_obj)
|
||||||
return driver_dep_obj_list
|
return driver_dep_obj_list
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_by_name(cls, context, name):
|
||||||
|
"""Form driver-side Deployable object list from DB for one device."""
|
||||||
|
# get deployable_obj_list for one device_id
|
||||||
|
dep_obj = Deployable.get_by_name(context, name)
|
||||||
|
driver_ah_obj_list = DriverAttachHandle.list(context, dep_obj.id)
|
||||||
|
# get driver_attr_obj_list fro this dep_obj
|
||||||
|
driver_attr_obj_list = DriverAttribute.list(context, dep_obj.id)
|
||||||
|
driver_dep_obj = cls(context=context, name=dep_obj.name,
|
||||||
|
num_accelerators=dep_obj.num_accelerators,
|
||||||
|
attribute_list=driver_attr_obj_list,
|
||||||
|
attach_handle_list=driver_ah_obj_list)
|
||||||
|
return driver_dep_obj
|
||||||
|
@ -134,3 +134,19 @@ class DriverDevice(base.DriverObjectBase,
|
|||||||
)
|
)
|
||||||
driver_dev_obj_list.append(driver_dev_obj)
|
driver_dev_obj_list.append(driver_dev_obj)
|
||||||
return driver_dev_obj_list
|
return driver_dev_obj_list
|
||||||
|
|
||||||
|
def get_device_obj_by_device_id(self, context, device_id):
|
||||||
|
"""
|
||||||
|
:param context: requested context.
|
||||||
|
:param host: hostname of the node.
|
||||||
|
:return: a device object of current driver device object. It will
|
||||||
|
return on value because it has controlpath_id.
|
||||||
|
"""
|
||||||
|
# get dev_obj_list from hostname
|
||||||
|
device_obj = Device.get_by_device_id(context, device_id)
|
||||||
|
# use controlpath_id.cpid_info to identiy one Device.
|
||||||
|
# get cpid_obj, could be empty or only one value.
|
||||||
|
cpid_obj = ControlpathID.get_by_device_id_cpidinfo(
|
||||||
|
context, device_obj.id, self.controlpath_id.cpid_info)
|
||||||
|
# find the one cpid_obj with cpid_info
|
||||||
|
return device_obj
|
||||||
|
@ -42,7 +42,7 @@ PGFA_DEVICE_COMMON_CONTENT = {
|
|||||||
"config": "",
|
"config": "",
|
||||||
"consistent_dma_mask_bits": "64",
|
"consistent_dma_mask_bits": "64",
|
||||||
"d3cold_allowed": "1",
|
"d3cold_allowed": "1",
|
||||||
"device": "0xbcc0",
|
"device": "0x09c4",
|
||||||
"dma_mask_bits": "64",
|
"dma_mask_bits": "64",
|
||||||
"driver_override": "(null)",
|
"driver_override": "(null)",
|
||||||
"enable": "1",
|
"enable": "1",
|
||||||
@ -117,7 +117,7 @@ PGFA_DEVICES_SPECIAL_COMMON_CONTENT = {
|
|||||||
},
|
},
|
||||||
"dev.2": {
|
"dev.2": {
|
||||||
"d3cold_allowed": "0",
|
"d3cold_allowed": "0",
|
||||||
"device": "0xbcc1",
|
"device": "0x09c4",
|
||||||
"modalias": "pci:v00008086d0000BCC0sv00000000sd00000000bc12sc00i00",
|
"modalias": "pci:v00008086d0000BCC0sv00000000sd00000000bc12sc00i00",
|
||||||
"irq": "0",
|
"irq": "0",
|
||||||
"resource": [
|
"resource": [
|
||||||
|
@ -16,3 +16,4 @@ testtools>=0.9.32 # MIT
|
|||||||
tempest>=17.1.0 # Apache-2.0
|
tempest>=17.1.0 # Apache-2.0
|
||||||
doc8>=0.6.0 # Apache-2.0
|
doc8>=0.6.0 # Apache-2.0
|
||||||
Pygments>=2.2.0 # BSD license
|
Pygments>=2.2.0 # BSD license
|
||||||
|
os-resource-classes>=0.5.0 # Apache-2.0
|
||||||
|
Loading…
Reference in New Issue
Block a user