Migrate to ruff-format + ruff lint; drop hacking plugin

Replace autopep8 with ruff-format in pre-commit. Add G and LOG
rule-sets to ruff lint so logging-format violations (e.g. G003
string concatenation in log calls) are caught without hacking.
Add external = ["H"] so ruff recognises H-codes in noqa comments.
Add [tool.ruff.format] with quote-style = "preserve" to avoid
mass quote conversion on first run (oslo.utils pattern).

Remove the local cyborg/hacking/ plugin (M310/M322/M336/M340
checks) and its test file; register only the upstream hacking
package (rev 8.0.0) via pre-commit. Simplify [flake8] to
select = H only, matching oslo.utils. Remove [flake8:local-plugins].

Drop bandit: remove tox bandit env, cyborg-tox-bandit Zuul job
and its check reference, and bandit from test-requirements.txt.
Hacking is also removed from test-requirements.txt; it is now
consumed solely via the pre-commit hook.

Rewrite tox pep8 env to run `pre-commit run -a` (oslo.utils
pattern); remove fast8 env and tools/flake8wrap.sh. Remove
autopep8 mention from HACKING.rst.

Apply ruff-format to all 214 reformatted source files as part
of this migration commit.

Generated-By: claude-code sonnet 4.6
Change-Id: Id70f611dd571e156264838d4054ff62577028671
Signed-off-by: Sean Mooney <work@seanmooney.info>
This commit is contained in:
Sean Mooney
2026-02-24 21:50:39 +00:00
parent 8ecf37d809
commit 8eb3a5a4b2
224 changed files with 7114 additions and 5078 deletions

View File

@@ -35,17 +35,12 @@ repos:
hooks:
- id: ruff-check
args: ['--fix', '--unsafe-fixes']
- repo: https://github.com/hhatto/autopep8
rev: v2.3.2
hooks:
- id: autopep8
files: '^.*\.py$'
- id: ruff-format
- repo: https://opendev.org/openstack/hacking
rev: 7.0.0
rev: 8.0.0
hooks:
- id: hacking
additional_dependencies: []
exclude: '^(doc|releasenotes|tools)/.*$'
- repo: https://github.com/codespell-project/codespell
rev: v2.4.1
hooks:

View File

@@ -1,26 +1,3 @@
- job:
name: cyborg-tox-bandit
parent: openstack-tox
timeout: 2400
vars:
tox_envlist: bandit
required-projects:
- openstack/requirements
irrelevant-files: &gate-irrelevant-files
- ^(test-|)requirements.txt$
- ^.*\.rst$
- ^api-ref/.*$
- ^cyborg/cmd/status\.py$
- ^cyborg/hacking/.*$
- ^cyborg/tests/functional.*$
- ^cyborg/tests/unit.*$
- ^doc/.*$
- ^etc/.*$
- ^releasenotes/.*$
- ^setup.cfg$
- ^tools/.*$
- ^tox.ini$
- project:
templates:
- openstack-cover-jobs
@@ -32,7 +9,6 @@
jobs:
- cyborg-tempest
- cyborg-tempest-ipv6-only
- cyborg-tox-bandit
gate:
jobs:
- cyborg-tempest

View File

@@ -7,8 +7,7 @@ Before you commit your code run tox against your patch using the command.
tox .
If any of the tests fail correct the error and try again. If your code is valid
Python but not valid pep8 you may find autopep8 from pip useful.
If any of the tests fail correct the error and try again.
Once you submit a patch integration tests will run and those may fail,
-1'ing your patch you can make a gerrit comment 'recheck ci' if you have

View File

@@ -68,6 +68,11 @@ html_theme_options = {
# (source start file, target name, title, author, documentclass
# [howto/manual]).
latex_documents = [
('index', 'Cyborg.tex', 'OpenStack Acceleration API Documentation',
'OpenStack Foundation', 'manual'),
(
'index',
'Cyborg.tex',
'OpenStack Acceleration API Documentation',
'OpenStack Foundation',
'manual',
),
]

View File

@@ -1,4 +1,3 @@
# 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
@@ -14,5 +13,4 @@
import pbr.version
__version__ = pbr.version.VersionInfo(
'openstack-cyborg').version_string()
__version__ = pbr.version.VersionInfo('openstack-cyborg').version_string()

View File

@@ -1,4 +1,3 @@
# 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
@@ -14,5 +13,4 @@
import pbr.version
__version__ = pbr.version.VersionInfo(
'openstack-cyborg').version_string()
__version__ = pbr.version.VersionInfo('openstack-cyborg').version_string()

View File

@@ -1,4 +1,3 @@
# Copyright 2016-2017 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -15,6 +14,7 @@
from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()

View File

@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
"""Accelerator base exception handling. """
"""Accelerator base exception handling."""
import collections
from http import HTTPStatus
@@ -38,8 +38,10 @@ def _ensure_exception_kwargs_serializable(exc_class_name, kwargs):
constructor.
:returns: a dictionary of serializable keyword arguments.
"""
serializers = [(jsonutils.dumps, _('when converting to JSON')),
(str, _('when converting to string'))]
serializers = [
(jsonutils.dumps, _('when converting to JSON')),
(str, _('when converting to string')),
]
exceptions = collections.defaultdict(list)
serializable_kwargs = {}
for k, v in kwargs.items():
@@ -50,20 +52,29 @@ def _ensure_exception_kwargs_serializable(exc_class_name, kwargs):
break
except Exception as e:
exceptions[k].append(
'(%(serializer_type)s) %(e_type)s: %(e_contents)s' %
{'serializer_type': msg, 'e_contents': e,
'e_type': e.__class__.__name__})
'(%(serializer_type)s) %(e_type)s: %(e_contents)s'
% {
'serializer_type': msg,
'e_contents': e,
'e_type': e.__class__.__name__,
}
)
if exceptions:
LOG.error("One or more arguments passed to the %(exc_class)s "
"constructor as kwargs can not be serialized. The "
"serialized arguments: %(serialized)s. These "
"unserialized kwargs were dropped because of the "
"exceptions encountered during their "
"serialization:\n%(errors)s",
dict(errors=';\n'.join("%s: %s" % (k, '; '.join(v))
for k, v in exceptions.items()),
exc_class=exc_class_name,
serialized=serializable_kwargs))
LOG.error(
"One or more arguments passed to the %(exc_class)s "
"constructor as kwargs can not be serialized. The "
"serialized arguments: %(serialized)s. These "
"unserialized kwargs were dropped because of the "
"exceptions encountered during their "
"serialization:\n%(errors)s",
dict(
errors=';\n'.join(
"%s: %s" % (k, '; '.join(v)) for k, v in exceptions.items()
),
exc_class=exc_class_name,
serialized=serializable_kwargs,
),
)
# We might be able to actually put the following keys' values into
# format string, but there is no guarantee, drop it just in case.
for k in exceptions:
@@ -81,15 +92,16 @@ class AcceleratorException(Exception):
If you need to access the message from an exception you should use
str(exc).
"""
_msg_fmt = _("An unknown exception occurred.")
code = HTTPStatus.INTERNAL_SERVER_ERROR
headers = {}
safe = False
def __init__(self, message=None, **kwargs):
self.kwargs = _ensure_exception_kwargs_serializable(
self.__class__.__name__, kwargs)
self.__class__.__name__, kwargs
)
if 'code' not in self.kwargs:
try:

View File

@@ -21,9 +21,9 @@ from oslo_serialization import jsonutils
from cyborg.common import exception
_PCI_ADDRESS_PATTERN = ("^(hex{4}):(hex{2}):(hex{2}).(oct{1})$".
replace("hex", r"[\da-fA-F]").
replace("oct", "[0-7]"))
_PCI_ADDRESS_PATTERN = "^(hex{4}):(hex{2}):(hex{2}).(oct{1})$".replace(
"hex", r"[\da-fA-F]"
).replace("oct", "[0-7]")
_PCI_ADDRESS_REGEX = re.compile(_PCI_ADDRESS_PATTERN)
@@ -93,11 +93,18 @@ def parse_mappings(mapping_list):
raise ValueError(("Missing key in mapping: '%s'") % dev_mapping)
if physnet_or_function in mapping:
raise ValueError(
("Key %(physnet_or_function)s in mapping: %(mapping)s "
"not unique") % {'physnet_or_function': physnet_or_function,
'mapping': dev_mapping})
mapping[physnet_or_function] = set(dev.strip() for dev in
devices.split("|") if dev.strip())
(
"Key %(physnet_or_function)s in mapping: %(mapping)s "
"not unique"
)
% {
'physnet_or_function': physnet_or_function,
'mapping': dev_mapping,
}
)
mapping[physnet_or_function] = set(
dev.strip() for dev in devices.split("|") if dev.strip()
)
return mapping
@@ -106,13 +113,14 @@ def get_vendor_maps():
:return: vendor maps dict
"""
return {"10de": "nvidia",
"102b": "matrox",
"1bd4": "inspur",
"8086": "intel",
"1099": "samsung",
"1cf2": "zte"
}
return {
"10de": "nvidia",
"102b": "matrox",
"1bd4": "inspur",
"8086": "intel",
"1099": "samsung",
"1cf2": "zte",
}
def mdev_str_to_json(pci_address, asked_type, vgpu_mark):

View File

@@ -139,8 +139,9 @@ class Configuration:
"""
self.config_group = config_group
if config_group:
self.conf = BackendGroupConfiguration(accelerator_opts,
config_group)
self.conf = BackendGroupConfiguration(
accelerator_opts, config_group
)
else:
self.conf = DefaultGroupConfiguration()

View File

@@ -24,12 +24,14 @@ from cyborg.objects.driver_objects import driver_deployable
from cyborg.objects.driver_objects import driver_device
import cyborg.privsep
PCI_INFO_PATTERN = re.compile(r"(?P<slot>[0-9a-f]{4}:[0-9a-f]{2}:"
r"[0-9a-f]{2}\.[0-9a-f]) "
r"(?P<class>.*) [\[].*]: (?P<device>.*) .*"
r"[\[](?P<vendor_id>[0-9a-fA-F]"
r"{4}):(?P<device_id>[0-9a-fA-F]{4})].*"
r"[(rev ](?P<revision>[0-9a-f]{2})")
PCI_INFO_PATTERN = re.compile(
r"(?P<slot>[0-9a-f]{4}:[0-9a-f]{2}:"
r"[0-9a-f]{2}\.[0-9a-f]) "
r"(?P<class>.*) [\[].*]: (?P<device>.*) .*"
r"[\[](?P<vendor_id>[0-9a-fA-F]"
r"{4}):(?P<device_id>[0-9a-fA-F]{4})].*"
r"[(rev ](?P<revision>[0-9a-f]{2})"
)
@cyborg.privsep.sys_admin_pctxt.entrypoint
@@ -41,8 +43,9 @@ def lspci_privileged():
class AscendDriver(GenericDriver):
"""The class for Ascend AI Chip drivers.
This is the Huawei Ascend AI Chip drivers.
This is the Huawei Ascend AI Chip drivers.
"""
VENDOR = "huawei"
# TODO(yikun): can be extracted into PCIDeviceDriver
@@ -106,8 +109,10 @@ class AscendDriver(GenericDriver):
device.stub = False
device.vendor = pci_dict["vendor_id"]
device.model = pci_dict.get('model', '')
std_board_info = {'device_id': pci_dict.get('device_id', None),
'class': pci_dict.get('class', None)}
std_board_info = {
'device_id': pci_dict.get('device_id', None),
'class': pci_dict.get('class', None),
}
device.std_board_info = jsonutils.dumps(std_board_info)
device.vendor_board_info = ''
device.type = constants.DEVICE_AICHIP

View File

@@ -15,7 +15,6 @@ import abc
class GenericDriver(metaclass=abc.ABCMeta):
@abc.abstractmethod
def discover(self):
"""Discover a specified accelerator.

View File

@@ -29,8 +29,9 @@ from cyborg.objects.driver_objects import driver_device
class FakeDriver(GenericDriver):
"""Base class for Fake drivers.
This is just a Fake drivers interface.
This is just a Fake drivers interface.
"""
VENDOR = "fake"
NUM_ACCELERATORS = 16
@@ -76,7 +77,8 @@ class FakeDriver(GenericDriver):
def _generate_dep_list(self, pci):
driver_dep = driver_deployable.DriverDeployable()
driver_dep.attach_handle_list = self._generate_attach_handles(
pci, self.NUM_ACCELERATORS)
pci, self.NUM_ACCELERATORS
)
# NOTE(sean-k-mooney): we need to prepend the host name to the
# device name as this is used to generate the RP name and uuid in
# the cyborg conductor when updating placement. As such this needs
@@ -95,17 +97,19 @@ class FakeDriver(GenericDriver):
fpga_list = []
pci_addr = '{"domain":"0000","bus":"0c","device":"00","function":"0"}'
pci_dict = {
'slot': pci_addr, # PCI slot address
'device': 'FakeDevice', # Name of the device
'vendor_id': '0xABCD', # ID of the vendor
'class': 'Fake class', # Name of the class
'device_id': '0xabcd' # ID of the device
'slot': pci_addr, # PCI slot address
'device': 'FakeDevice', # Name of the device
'vendor_id': '0xABCD', # ID of the vendor
'class': 'Fake class', # Name of the class
'device_id': '0xabcd', # ID of the device
}
device = driver_device.DriverDevice()
device.vendor = pci_dict["vendor_id"]
device.model = pci_dict.get('model', 'miss model info')
std_board_info = {'device_id': pci_dict.get('device_id'),
'class': pci_dict.get('class')}
std_board_info = {
'device_id': pci_dict.get('device_id'),
'class': pci_dict.get('class'),
}
device.std_board_info = jsonutils.dumps(std_board_info)
device.vendor_board_info = 'fake_vendor_info'
device.type = constants.DEVICE_FPGA

View File

@@ -20,15 +20,14 @@ Cyborg FPGA driver implementation.
from cyborg.accelerator.drivers.fpga import utils
VENDOR_MAPS = {"0x8086": "intel",
"1bd4": 'inspur'}
VENDOR_MAPS = {"0x8086": "intel", "1bd4": 'inspur'}
class FPGADriver:
"""Base class for FPGA drivers.
This is just a virtual FPGA drivers interface.
Vendor should implement their specific drivers.
This is just a virtual FPGA drivers interface.
Vendor should implement their specific drivers.
"""
@classmethod

View File

@@ -27,8 +27,9 @@ LOG = logging.getLogger(__name__)
class InspurFPGADriver(FPGADriver):
"""Class for Inspur FPGA drivers.
Vendor should implement their specific drivers in this class.
Vendor should implement their specific drivers in this class.
"""
VENDOR = "inspur"
def __init__(self, *args, **kwargs):

View File

@@ -32,14 +32,17 @@ from cyborg.objects.driver_objects import driver_deployable
from cyborg.objects.driver_objects import driver_device
import cyborg.privsep
INSPUR_FPGA_FLAGS = ["Inspur Electronic Information Industry Co., Ltd.",
"Processing accelerators"]
INSPUR_FPGA_FLAGS = [
"Inspur Electronic Information Industry Co., Ltd.",
"Processing accelerators",
]
INSPUR_FPGA_INFO_PATTERN = re.compile(
r"(?P<devices>[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:"
r"[0-9a-fA-F]{2}\.[0-9a-fA-F]) "
r"(?P<controller>.*) [\[].*]: (?P<model>.*) .*"
r"[\[](?P<vendor_id>[0-9a-fA-F]"
r"{4}):(?P<product_id>[0-9a-fA-F]{4})].*")
r"{4}):(?P<product_id>[0-9a-fA-F]{4})].*"
)
VENDOR_ID = "1bd4"
VENDOR_MAPS = {"1bd4": "inspur"}
@@ -88,7 +91,8 @@ def fpga_tree():
fpga_dict = m.groupdict()
# generate traits info
traits = get_traits(
fpga_dict["vendor_id"], fpga_dict["product_id"])
fpga_dict["vendor_id"], fpga_dict["product_id"]
)
fpga_dict["rc"] = constants.RESOURCES["FPGA"]
fpga_dict.update(traits)
fpga_list.append(_generate_driver_device(fpga_dict))
@@ -99,10 +103,13 @@ def _generate_driver_device(fpga):
driver_device_obj = driver_device.DriverDevice()
driver_device_obj.vendor = fpga["vendor_id"]
driver_device_obj.model = fpga.get('model', 'miss model info')
std_board_info = {'product_id': fpga.get('product_id'),
'controller': fpga.get('controller')}
std_board_info = {
'product_id': fpga.get('product_id'),
'controller': fpga.get('controller'),
}
vendor_board_info = {
'vendor_info': fpga.get('vendor_info', 'fpga_vb_info')}
'vendor_info': fpga.get('vendor_info', 'fpga_vb_info')
}
driver_device_obj.std_board_info = jsonutils.dumps(std_board_info)
driver_device_obj.vendor_board_info = jsonutils.dumps(vendor_board_info)
driver_device_obj.type = constants.DEVICE_FPGA
@@ -143,7 +150,8 @@ def _generate_attribute_list(fpga):
if k == "traits":
for index, val in enumerate(v):
driver_attr = driver_attribute.DriverAttribute(
key="trait" + str(index), value=val)
key="trait" + str(index), value=val
)
attr_list.append(driver_attr)
return attr_list

View File

@@ -45,8 +45,9 @@ def _fpga_program_privileged(cmd_args):
class IntelFPGADriver(FPGADriver):
"""Class for Intel FPGA drivers.
Vendor should implement their specific drivers in this class.
Vendor should implement their specific drivers in this class.
"""
VENDOR = "intel"
def __init__(self, *args, **kwargs):
@@ -58,23 +59,27 @@ class IntelFPGADriver(FPGADriver):
def program(self, controlpath_id, image_file_path):
"""Program the FPGA with the provided bitstream image.
TODO(Sundar): Need to handle retries.
TODO(Sundar): Need to handle retries.
:param: controlpath_id
Controlpath_id OVO
:param: image_file_path
String with the file path
:returns: True on success, False on failure
:param: controlpath_id
Controlpath_id OVO
:param: image_file_path
String with the file path
:returns: True on success, False on failure
"""
if controlpath_id['cpid_type'] != "PCI":
raise exception.InvalidType(obj='controlpath_id',
type=controlpath_id['cpid_type'],
expected='PCI')
raise exception.InvalidType(
obj='controlpath_id',
type=controlpath_id['cpid_type'],
expected='PCI',
)
cmd_args = []
bdf_dict = controlpath_id['cpid_info']
# fitting format to the OPAE command.
bdf = ['0x' + s for s in map(lambda x: bdf_dict[x],
["bus", "device", "function"])]
bdf = [
'0x' + s
for s in map(lambda x: bdf_dict[x], ["bus", "device", "function"])
]
for i in zip(["--bus", "--device", "--function"], bdf):
cmd_args.extend(i)
cmd_args.append(image_file_path)

View File

@@ -17,7 +17,6 @@
Cyborg Intel FPGA driver implementation.
"""
import glob
import os
import re
@@ -44,10 +43,10 @@ DEVICE = "device"
PF = "physfn"
VF = "virtfn*"
BDF_PATTERN = re.compile(
r"^[a-fA-F\d]{4}:[a-fA-F\d]{2}:[a-fA-F\d]{2}\.[a-fA-F\d]$")
r"^[a-fA-F\d]{4}:[a-fA-F\d]{2}:[a-fA-F\d]{2}\.[a-fA-F\d]$"
)
DEVICE_FILE_MAP = {"vendor": "vendor",
"device": "product_id"}
DEVICE_FILE_MAP = {"vendor": "vendor", "device": "product_id"}
DEVICE_FILE_HANDLER = {}
DEVICE_EXPOSED = ["vendor", "device"]
@@ -62,15 +61,16 @@ def read_line(filename):
def is_fpga(p):
infos = (read_line(os.path.join(p, "vendor")),
read_line(os.path.join(p, "device")))
infos = (
read_line(os.path.join(p, "vendor")),
read_line(os.path.join(p, "device")),
)
if infos in KNOWN_FPGAS:
return os.path.realpath(p)
def link_real_path(p):
return os.path.realpath(
os.path.join(os.path.dirname(p), os.readlink(p)))
return os.path.realpath(os.path.join(os.path.dirname(p), os.readlink(p)))
# TODO(s_shogo) This function name should be reconsidered in py3
@@ -79,34 +79,43 @@ def find_fpgas_by_know_list():
return filter(
lambda p: (
read_line(os.path.join(p, "vendor")),
read_line(os.path.join(p, "device"))
) in KNOWN_FPGAS,
glob.glob(PCI_DEVICES_PATH_PATTERN))
read_line(os.path.join(p, "device")),
)
in KNOWN_FPGAS,
glob.glob(PCI_DEVICES_PATH_PATTERN),
)
def get_link_targets(links):
return map(
lambda p:
os.path.realpath(
os.path.join(os.path.dirname(p), os.readlink(p))),
links)
lambda p: os.path.realpath(
os.path.join(os.path.dirname(p), os.readlink(p))
),
links,
)
def all_fpgas():
# glob.glob("/sys/class/fpga", "*")
return set(get_link_targets(find_fpgas_by_know_list())) | set(
map(lambda p: p.rsplit("/", 2)[0],
get_link_targets(glob.glob(os.path.join(SYS_FPGA, "*")))))
map(
lambda p: p.rsplit("/", 2)[0],
get_link_targets(glob.glob(os.path.join(SYS_FPGA, "*"))),
)
)
def all_vf_fpgas():
return [dev.rsplit("/", 2)[0] for dev in
glob.glob(os.path.join(SYS_FPGA, "*/device/physfn"))]
return [
dev.rsplit("/", 2)[0]
for dev in glob.glob(os.path.join(SYS_FPGA, "*/device/physfn"))
]
def all_pfs_have_vf():
return list(filter(lambda p: glob.glob(os.path.join(p, "virtfn0")),
all_fpgas()))
return list(
filter(lambda p: glob.glob(os.path.join(p, "virtfn0")), all_fpgas())
)
def target_symbolic_map():
@@ -121,13 +130,13 @@ def bdf_path_map():
def all_vfs_in_pf_fpgas(pf_path):
return get_link_targets(
glob.glob(os.path.join(pf_path, "virtfn*")))
return get_link_targets(glob.glob(os.path.join(pf_path, "virtfn*")))
def all_pf_fpgas():
return filter(lambda p: glob.glob(os.path.join(p, "sriov_totalvfs")),
all_fpgas())
return filter(
lambda p: glob.glob(os.path.join(p, "sriov_totalvfs")), all_fpgas()
)
def is_bdf(bdf):
@@ -146,9 +155,13 @@ def get_afu_ids(device_name):
read_line,
glob.glob(
os.path.join(
PCI_DEVICES_PATH_PATTERN, "fpga",
device_name, "intel-fpga-port.*", "afu_id")
)
PCI_DEVICES_PATH_PATTERN,
"fpga",
device_name,
"intel-fpga-port.*",
"afu_id",
)
),
)
@@ -157,9 +170,9 @@ def get_region_ids(device_name):
read_line,
glob.glob(
os.path.join(
SYS_FPGA, device_name,
"intel-fpga-fme.*", "pr/interface_id")
)
SYS_FPGA, device_name, "intel-fpga-fme.*", "pr/interface_id"
)
),
)
@@ -187,14 +200,16 @@ def fpga_device(path):
infos = {}
# NOTE "In 3.x, os.path.walk is removed in favor of os.walk."
for (dirpath, dirnames, filenames) in os.walk(path):
for dirpath, dirnames, filenames in os.walk(path):
for filename in filenames:
if filename in DEVICE_EXPOSED:
key = DEVICE_FILE_MAP.get(filename) or filename
if key in DEVICE_FILE_HANDLER and callable(
DEVICE_FILE_HANDLER(key)):
DEVICE_FILE_HANDLER(key)
):
infos[key] = DEVICE_FILE_HANDLER(key)(
os.path.join(dirpath, filename))
os.path.join(dirpath, filename)
)
else:
infos[key] = read_line(os.path.join(dirpath, filename))
return infos
@@ -204,9 +219,12 @@ def fpga_tree():
def gen_fpga_infos(path, vf=True):
bdf = get_bdf_by_path(path)
names = glob.glob1(os.path.join(path, "fpga"), "*")
fpga = {"type": constants.DEVICE_FPGA,
"devices": bdf, "stub": True,
"name": "_".join((socket.gethostname(), bdf))}
fpga = {
"type": constants.DEVICE_FPGA,
"devices": bdf,
"stub": True,
"name": "_".join((socket.gethostname(), bdf)),
}
d_info = fpga_device(path)
fpga.update(d_info)
if names:
@@ -238,8 +256,9 @@ def _generate_driver_device(fpga, pf_has_vf):
driver_device_obj.vendor = fpga["vendor"]
driver_device_obj.stub = fpga["stub"]
driver_device_obj.model = fpga.get('model', "miss_model_info")
driver_device_obj.vendor_board_info = fpga.get('vendor_board_info',
"miss_vb_info")
driver_device_obj.vendor_board_info = fpga.get(
'vendor_board_info', "miss_vb_info"
)
std_board_info = {'product_id': fpga.get('product_id')}
driver_device_obj.std_board_info = jsonutils.dumps(std_board_info)
driver_device_obj.type = fpga["type"]
@@ -262,8 +281,7 @@ def _generate_dep_list(fpga, pf_has_vf):
# pf without sriov enabled.
if not pf_has_vf:
driver_dep.num_accelerators = 1
driver_dep.attach_handle_list = \
[_generate_attach_handle(fpga)]
driver_dep.attach_handle_list = [_generate_attach_handle(fpga)]
driver_dep.name = fpga["name"]
driver_dep.driver_name = DRIVER_NAME
# pf with sriov enabled, may have several regions and several vfs.
@@ -272,8 +290,7 @@ def _generate_dep_list(fpga, pf_has_vf):
driver_dep.num_accelerators = len(fpga["regions"])
for vf in fpga["regions"]:
# Only vfs in regions can be attach, no pf.
driver_dep.attach_handle_list.append(
_generate_attach_handle(vf))
driver_dep.attach_handle_list.append(_generate_attach_handle(vf))
driver_dep.name = vf["name"]
driver_dep.driver_name = DRIVER_NAME
return [driver_dep]

View File

@@ -16,6 +16,7 @@
"""
Cyborg Xilinx FPGA driver implementation.
"""
from oslo_concurrency import processutils
from cyborg.accelerator.drivers.fpga.base import FPGADriver
@@ -35,8 +36,9 @@ def _fpga_program_privileged(cmd_args):
class XilinxFPGADriver(FPGADriver):
"""Class for Xilinx FPGA drivers.
Vendor should implement their specific drivers in this class.
Vendor should implement their specific drivers in this class.
"""
VENDOR = "xilinx"
def __init__(self, *args, **kwargs):
@@ -53,15 +55,24 @@ class XilinxFPGADriver(FPGADriver):
:returns: True on success, False on failure
"""
if controlpath_id['cpid_type'] != "PCI":
raise exception.InvalidType(obj='controlpath_id',
type=controlpath_id['cpid_type'],
expected='PCI')
raise exception.InvalidType(
obj='controlpath_id',
type=controlpath_id['cpid_type'],
expected='PCI',
)
cmd_args = ['program']
cmd_args.append('--device')
bdf_dict = controlpath_id['cpid_info']
# BDF format: domain:bus:device:function
bdf = ':'.join([s for s in map(lambda x: bdf_dict[x],
['domain', 'bus', 'device', 'function'])])
bdf = ':'.join(
[
s
for s in map(
lambda x: bdf_dict[x],
['domain', 'bus', 'device', 'function'],
)
]
)
cmd_args.append(bdf)
cmd_args.append('--base')
cmd_args.append('--image')

View File

@@ -34,15 +34,15 @@ from cyborg.privsep import sys_admin_pctxt
LOG = logging.getLogger(__name__)
XILINX_FPGA_FLAGS = ["Xilinx Corporation Device",
"Processing accelerators"]
XILINX_FPGA_FLAGS = ["Xilinx Corporation Device", "Processing accelerators"]
XILINX_FPGA_INFO_PATTERN = re.compile(
r"(?P<pci_addr>[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:"
r"[0-9a-fA-F]{2}\.[0-9a-fA-F]) "
r"(?P<controller>.*) [\[].*]: (?P<model>.*) .*"
r"[\[](?P<vendor_id>[0-9a-fA-F]"
r"{4}):(?P<product_id>[0-9a-fA-F]{4})].*")
r"{4}):(?P<product_id>[0-9a-fA-F]{4})].*"
)
XILINX_PF_MAPS = {"mgmt": "xclmgmt", "user": "xocl"}
@@ -101,13 +101,16 @@ def _combine_device_by_pci_func(pci_devices):
for fpga in fpga_devices:
existed_addr = fpga.get('pci_addr')[0]
# compare domain:bus:slot
if existed_addr and \
new_addr.split('.')[0] == existed_addr.split('.')[0]:
if (
existed_addr
and new_addr.split('.')[0] == existed_addr.split('.')[0]
):
fpga.update({'pci_addr': [existed_addr, new_addr]})
is_existed = True
if not is_existed:
traits = _generate_traits(pci_dict["vendor_id"],
pci_dict["product_id"])
traits = _generate_traits(
pci_dict["vendor_id"], pci_dict["product_id"]
)
pci_dict["rc"] = constants.RESOURCES["FPGA"]
pci_dict.update(traits)
pci_dict.update({'pci_addr': [new_addr]})
@@ -144,7 +147,8 @@ def _generate_attribute_list(fpga):
values = fpga.get(k, [])
for index, val in enumerate(values):
driver_attr = driver_attribute.DriverAttribute(
key="trait" + str(index), value=val)
key="trait" + str(index), value=val
)
attr_list.append(driver_attr)
return attr_list
@@ -171,13 +175,17 @@ def fpga_tree():
driver_device_obj = driver_device.DriverDevice()
driver_device_obj.vendor = fpga["vendor_id"]
driver_device_obj.model = fpga.get('model', 'miss model info')
std_board_info = {'product_id': fpga.get('product_id'),
'controller': fpga.get('controller')}
std_board_info = {
'product_id': fpga.get('product_id'),
'controller': fpga.get('controller'),
}
vendor_board_info = {
'vendor_info': fpga.get('vendor_info', 'fpga_vb_info')}
'vendor_info': fpga.get('vendor_info', 'fpga_vb_info')
}
driver_device_obj.std_board_info = jsonutils.dumps(std_board_info)
driver_device_obj.vendor_board_info = \
jsonutils.dumps(vendor_board_info)
driver_device_obj.vendor_board_info = jsonutils.dumps(
vendor_board_info
)
driver_device_obj.type = constants.DEVICE_FPGA
driver_device_obj.stub = fpga.get('stub', False)
driver_device_obj.controlpath_id = _generate_controlpath_id(fpga)

View File

@@ -16,6 +16,7 @@
"""
Cyborg GPU driver implementation.
"""
from oslo_log import log as logging
from cyborg.accelerator.drivers.gpu import utils
@@ -29,8 +30,8 @@ VENDOR_MAPS = {"10de": "nvidia", "102b": "matrox"}
class GPUDriver:
"""Base class for GPU drivers.
This is just a virtual GPU drivers interface.
Vendor should implement their specific drivers.
This is just a virtual GPU drivers interface.
Vendor should implement their specific drivers.
"""
@classmethod

View File

@@ -24,8 +24,9 @@ from cyborg.accelerator.drivers.gpu.nvidia import sysinfo
class NVIDIAGPUDriver(GPUDriver):
"""Class for Nvidia GPU drivers.
Vendor should implement their specific drivers in this class.
Vendor should implement their specific drivers in this class.
"""
VENDOR = "nvidia"
VENDOR_ID = "10de"

View File

@@ -17,6 +17,7 @@
"""
Cyborg NVIDIA GPU driver implementation.
"""
from oslo_log import log as logging
from oslo_serialization import jsonutils
@@ -52,8 +53,12 @@ def _get_traits(vendor_id, product_id, vgpu_type_name=None):
traits = ["OWNER_CYBORG"]
# PGPU trait
gpu_trait = "_".join(
('CUSTOM', gpu_utils.VENDOR_MAPS.get(vendor_id, "").upper(),
product_id.upper()))
(
'CUSTOM',
gpu_utils.VENDOR_MAPS.get(vendor_id, "").upper(),
product_id.upper(),
)
)
# VGPU trait
if vgpu_type_name:
gpu_trait = "_".join((gpu_trait, vgpu_type_name.upper()))
@@ -73,7 +78,8 @@ def _generate_attribute_list(gpu):
values = gpu.get(k, [])
for val in values:
driver_attr = driver_attribute.DriverAttribute(
key="trait" + str(index), value=val)
key="trait" + str(index), value=val
)
index = index + 1
attr_list.append(driver_attr)
return attr_list
@@ -89,7 +95,8 @@ def _generate_attach_handle(gpu, num=None):
vgpu_mark = gpu["vGPU_type"] + '_' + str(num)
driver_ah.attach_type = constants.AH_TYPE_MDEV
driver_ah.attach_info = utils.mdev_str_to_json(
gpu["devices"], gpu["vGPU_type"], vgpu_mark)
gpu["devices"], gpu["vGPU_type"], vgpu_mark
)
return driver_ah
@@ -103,19 +110,21 @@ def _generate_dep_list(gpu):
# NOTE(yumeng) Since Wallaby release, the deplpyable_name is named as
# <Compute_hostname>_<Device_address>
driver_dep.name = gpu.get('hostname', '') + '_' + gpu["devices"]
driver_dep.driver_name = \
gpu_utils.VENDOR_MAPS.get(gpu["vendor_id"], '').upper()
driver_dep.driver_name = gpu_utils.VENDOR_MAPS.get(
gpu["vendor_id"], ''
).upper()
# if is pGPU, num_accelerators = 1
if gpu["rc"] == "PGPU":
driver_dep.num_accelerators = 1
driver_dep.attach_handle_list = \
[_generate_attach_handle(gpu)]
driver_dep.attach_handle_list = [_generate_attach_handle(gpu)]
else:
# if is vGPU, num_accelerators is the total vGPU capability of
# the asked vGPU type
vGPU_path = os.path.expandvars(
'/sys/bus/pci/devices/{}/mdev_supported_types/{}/'
.format(gpu["devices"], gpu["vGPU_type"]))
'/sys/bus/pci/devices/{}/mdev_supported_types/{}/'.format(
gpu["devices"], gpu["vGPU_type"]
)
)
num_available = 0
with open(vGPU_path + 'available_instances') as f:
num_available = int(f.read().strip())
@@ -128,7 +137,8 @@ def _generate_dep_list(gpu):
# example: echo "attach_handle_uuid" > nvidia-223/create
for num in range(driver_dep.num_accelerators):
driver_dep.attach_handle_list.append(
_generate_attach_handle(gpu, num))
_generate_attach_handle(gpu, num)
)
return [driver_dep]
@@ -143,13 +153,13 @@ def _generate_driver_device(gpu):
driver_device_obj = driver_device.DriverDevice()
driver_device_obj.vendor = gpu['vendor_id']
driver_device_obj.model = gpu.get('model', 'miss model info')
std_board_info = {'product_id': gpu.get('product_id'),
'controller': gpu.get('controller'), }
vendor_board_info = {'vendor_info': gpu.get('vendor_info',
'gpu_vb_info')}
std_board_info = {
'product_id': gpu.get('product_id'),
'controller': gpu.get('controller'),
}
vendor_board_info = {'vendor_info': gpu.get('vendor_info', 'gpu_vb_info')}
driver_device_obj.std_board_info = jsonutils.dumps(std_board_info)
driver_device_obj.vendor_board_info = jsonutils.dumps(
vendor_board_info)
driver_device_obj.vendor_board_info = jsonutils.dumps(vendor_board_info)
driver_device_obj.type = constants.DEVICE_GPU
driver_device_obj.stub = gpu.get('stub', False)
driver_device_obj.controlpath_id = _generate_controlpath_id(gpu)
@@ -204,8 +214,9 @@ def _get_supported_vgpu_types():
return CONF.gpu_devices.enabled_vgpu_types, pgpu_type_mapping
def _get_vgpu_type_per_pgpu(device_address, supported_vgpu_types,
pgpu_type_mapping):
def _get_vgpu_type_per_pgpu(
device_address, supported_vgpu_types, pgpu_type_mapping
):
"""Provides the vGPU type the pGPU supports.
:param device_address: the PCI device address in config,
@@ -214,9 +225,11 @@ def _get_vgpu_type_per_pgpu(device_address, supported_vgpu_types,
supported_vgpu_types, pgpu_type_mapping = _get_supported_vgpu_types()
# Bail out quickly if we don't support vGPUs
if not supported_vgpu_types:
LOG.warning('Unable to load vGPU_type from [gpu_devices] '
'Ensure "enabled_vgpu_types" is set if the gpu'
'is virtualized.')
LOG.warning(
'Unable to load vGPU_type from [gpu_devices] '
'Ensure "enabled_vgpu_types" is set if the gpu'
'is virtualized.'
)
return
try:
@@ -224,8 +237,10 @@ def _get_vgpu_type_per_pgpu(device_address, supported_vgpu_types,
utils.parse_address(device_address)
except (exception.PciDeviceWrongAddressFormat, IndexError):
# this is not a valid PCI address
LOG.warning("The PCI address %s was invalid for getting the"
"related vGPU type", device_address)
LOG.warning(
"The PCI address %s was invalid for getting therelated vGPU type",
device_address,
)
return
return pgpu_type_mapping.get(device_address)
@@ -240,14 +255,17 @@ def _is_vf(pci_address):
try:
return os.path.exists(physfn_path)
except OSError:
LOG.warning('Failed to check VF status for device %s via %s, '
'assuming it is not a VF.', pci_address, physfn_path)
LOG.warning(
'Failed to check VF status for device %s via %s, '
'assuming it is not a VF.',
pci_address,
physfn_path,
)
return False
def _discover_gpus(vendor_id):
"""param: vendor_id=VENDOR_ID means only discover Nvidia GPU on the host
"""
"""param: vendor_id=VENDOR_ID means only discover Nvidia GPU on the host"""
# init vGPU conf
cyborg.conf.devices.register_dynamic_opts(CONF)
supported_vgpu_types, pgpu_type_mapping = _get_supported_vgpu_types()
@@ -280,46 +298,56 @@ def _discover_gpus(vendor_id):
LOG.warning(
'Unable to determine VF status for '
'device %s, assuming it is not a VF.',
gpu_dict["devices"])
gpu_dict["devices"],
)
is_vf = False
if is_vf:
LOG.info(
'Skipping VF device %s, only PFs and'
' mediated devices are reported.',
gpu_dict["devices"])
gpu_dict["devices"],
)
continue
# get hostname for deployable_name usage
gpu_dict['hostname'] = CONF.host
# get vgpu_type from cyborg.conf, otherwise vgpu_type=None
vgpu_type = _get_vgpu_type_per_pgpu(
gpu_dict["devices"], supported_vgpu_types, pgpu_type_mapping)
gpu_dict["devices"], supported_vgpu_types, pgpu_type_mapping
)
# generate rc and trait for pGPU
if not vgpu_type:
gpu_dict["rc"] = constants.RESOURCES["PGPU"]
traits = _get_traits(gpu_dict["vendor_id"],
gpu_dict["product_id"])
traits = _get_traits(
gpu_dict["vendor_id"], gpu_dict["product_id"]
)
# generate rc and trait for vGPU
else:
# get rc
gpu_dict["rc"] = constants.RESOURCES["VGPU"]
mdev_path = os.path.expandvars(
'/sys/bus/pci/devices/{}/mdev_supported_types'.
format(gpu_dict["devices"]))
'/sys/bus/pci/devices/{}/mdev_supported_types'.format(
gpu_dict["devices"]
)
)
valid_types = os.listdir(mdev_path)
if vgpu_type not in valid_types:
raise exception.InvalidVGPUType(name=vgpu_type)
gpu_dict["vGPU_type"] = vgpu_type
vGPU_path = os.path.expandvars(
'/sys/bus/pci/devices/{}/mdev_supported_types/{}/'
.format(gpu_dict["devices"], gpu_dict["vGPU_type"]))
'/sys/bus/pci/devices/{}/mdev_supported_types/{}/'.format(
gpu_dict["devices"], gpu_dict["vGPU_type"]
)
)
# transfer vgpu_type to vgpu_type_name.
# eg. transfer 'nvidia-223' to 'T4_1B'
with open(vGPU_path + 'name') as f:
name = f.read().strip()
vgpu_type_name = name.split(' ')[1].replace('-', '_')
traits = _get_traits(gpu_dict["vendor_id"],
gpu_dict["product_id"],
vgpu_type_name)
traits = _get_traits(
gpu_dict["vendor_id"],
gpu_dict["product_id"],
vgpu_type_name,
)
gpu_dict.update(traits)
gpu_list.append(_generate_driver_device(gpu_dict))
return gpu_list

View File

@@ -14,6 +14,7 @@
"""
Utils for GPU driver.
"""
from oslo_concurrency import processutils
from oslo_log import log as logging
@@ -26,11 +27,13 @@ import cyborg.privsep
LOG = logging.getLogger(__name__)
GPU_FLAGS = ["VGA compatible controller", "3D controller"]
GPU_INFO_PATTERN = re.compile(r"(?P<devices>[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:"
r"[0-9a-fA-F]{2}\.[0-9a-fA-F]) "
r"(?P<controller>.*) [\[].*]: (?P<model>.*) .*"
r"[\[](?P<vendor_id>[0-9a-fA-F]"
r"{4}):(?P<product_id>[0-9a-fA-F]{4})].*")
GPU_INFO_PATTERN = re.compile(
r"(?P<devices>[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:"
r"[0-9a-fA-F]{2}\.[0-9a-fA-F]) "
r"(?P<controller>.*) [\[].*]: (?P<model>.*) .*"
r"[\[](?P<vendor_id>[0-9a-fA-F]"
r"{4}):(?P<product_id>[0-9a-fA-F]{4})].*"
)
VENDOR_MAPS = {"10de": "nvidia", "102b": "matrox"}
PRODUCT_ID_MAPS = {"1eb8": "T4", "15f7": "P100_PCIE_12GB"}
@@ -56,8 +59,9 @@ def create_mdev_privileged(pci_addr, mdev_type, ah_uuid):
@cyborg.privsep.sys_admin_pctxt.entrypoint
def remove_mdev_privileged(physical_device, mdev_type, medv_uuid):
fpath = ('/sys/class/mdev_bus/{0}/mdev_supported_types/'
'{1}/devices/{2}/remove')
fpath = (
'/sys/class/mdev_bus/{0}/mdev_supported_types/{1}/devices/{2}/remove'
)
fpath = fpath.format(physical_device, mdev_type, medv_uuid)
with open(fpath, 'w') as f:
f.write("1")

View File

@@ -37,20 +37,22 @@ def _check_for_missing_params(info_dict, error_msg, param_prefix=''):
if missing_info:
exc_msg = _("%(error_msg)s. Missing are: %(missing_info)s")
raise exception.MissingParameterValue(
exc_msg % {'error_msg': error_msg, 'missing_info': missing_info})
exc_msg % {'error_msg': error_msg, 'missing_info': missing_info}
)
def _parse_driver_info(driver):
info = driver.driver_info
d_info = {k: info.get(k) for k in COMMON_PROPERTIES}
error_msg = _("Cannot validate Generic Driver. Some parameters were"
" missing in the configuration file.")
error_msg = _(
"Cannot validate Generic Driver. Some parameters were"
" missing in the configuration file."
)
_check_for_missing_params(d_info, error_msg)
return d_info
class GENERICDRIVER:
def get_properties(self):
"""Return the properties of the generic driver.
@@ -59,12 +61,10 @@ class GENERICDRIVER:
return COMMON_PROPERTIES
def attach(self, accelerator, instance):
def install(self, accelerator):
pass
def detach(self, accelerator, instance):
def uninstall(self, accelerator):
pass

View File

@@ -23,8 +23,8 @@ VENDOR_MAPS = {"0x8086": "intel"}
class NICDriver:
"""Base class for Nic drivers.
This is just a virtual NIC drivers interface.
Vendor should implement their specific drivers.
This is just a virtual NIC drivers interface.
Vendor should implement their specific drivers.
"""
@classmethod

View File

@@ -23,8 +23,9 @@ from cyborg.accelerator.drivers.nic.intel import sysinfo
class IntelNICDriver(NICDriver):
"""Class for Intel NIC drivers.
Vendor should implement their specific drivers in this class.
Vendor should implement their specific drivers in this class.
"""
VENDOR = "intel"
def __init__(self, *args, **kwargs):

View File

@@ -17,7 +17,6 @@
Cyborg Intel NIC driver implementation.
"""
import glob
import os
import socket
@@ -58,8 +57,9 @@ def _parse_config():
return pdm, fdm
def get_physical_network_and_traits(pci_info, physnet_device_mappings,
function_device_mappings, pf_nic=None):
def get_physical_network_and_traits(
pci_info, physnet_device_mappings, function_device_mappings, pf_nic=None
):
traits = []
physnet = None
func_name = None
@@ -99,20 +99,21 @@ def read_line(filename):
def find_nics_by_know_list():
return set(filter(
lambda p: (
read_line(os.path.join(p, "vendor")),
read_line(os.path.join(p, "device"))
) in KNOWN_NICS,
glob.glob(PCI_DEVICES_PATH_PATTERN)))
return set(
filter(
lambda p: (
read_line(os.path.join(p, "vendor")),
read_line(os.path.join(p, "device")),
)
in KNOWN_NICS,
glob.glob(PCI_DEVICES_PATH_PATTERN),
)
)
def pci_attributes(path):
with open(os.path.join(path, "uevent")) as f:
attributes = dict(map(
lambda p: p.strip().split("="),
f.readlines()
))
attributes = dict(map(lambda p: p.strip().split("="), f.readlines()))
with open(os.path.join(path, "vendor")) as f:
attributes["VENDOR"] = f.readline().strip()
@@ -123,8 +124,12 @@ def pci_attributes(path):
return attributes
def nic_gen(path, physnet_device_mappings=None, function_device_mappings=None,
pf_nic=None):
def nic_gen(
path,
physnet_device_mappings=None,
function_device_mappings=None,
pf_nic=None,
):
pci_info = pci_attributes(path)
nic = {
"name": "_".join((socket.gethostname(), pci_info["PCI_SLOT_NAME"])),
@@ -134,30 +139,32 @@ def nic_gen(path, physnet_device_mappings=None, function_device_mappings=None,
"product_id": pci_info["PRODUCT_ID"],
"rc": "CUSTOM_NIC",
"stub": False,
}
}
# TODO(Xinran): need check device id and call get_traits differently.
updates = get_physical_network_and_traits(pci_info,
physnet_device_mappings,
function_device_mappings,
pf_nic)
updates = get_physical_network_and_traits(
pci_info, physnet_device_mappings, function_device_mappings, pf_nic
)
nic.update(updates)
return nic
def all_pfs_with_vf():
return set(filter(
lambda p: glob.glob(os.path.join(p, VF)),
find_nics_by_know_list()))
return set(
filter(
lambda p: glob.glob(os.path.join(p, VF)), find_nics_by_know_list()
)
)
def all_vfs_in_pf(pf_path):
return map(
lambda p:
os.path.join(
lambda p: os.path.join(
os.path.dirname(os.path.dirname(p)),
os.path.basename(os.readlink(p))),
glob.glob(os.path.join(pf_path, VF)))
os.path.basename(os.readlink(p)),
),
glob.glob(os.path.join(pf_path, VF)),
)
def nic_tree():
@@ -169,8 +176,9 @@ def nic_tree():
if n in pfs_has_vf:
vfs = []
for vf in all_vfs_in_pf(n):
vf_nic = nic_gen(vf, physnet_device_mappings,
function_device_mappings, nic)
vf_nic = nic_gen(
vf, physnet_device_mappings, function_device_mappings, nic
)
vfs.append(vf_nic)
nic["vfs"] = vfs
nics.append(_generate_driver_device(nic))
@@ -183,8 +191,8 @@ def _generate_driver_device(nic):
driver_device_obj.stub = nic["stub"]
driver_device_obj.model = nic.get("model", "miss_model_info")
driver_device_obj.vendor_board_info = nic.get(
"vendor_board_info",
"miss_vb_info")
"vendor_board_info", "miss_vb_info"
)
std_board_info = {"product_id": nic.get("product_id")}
driver_device_obj.std_board_info = jsonutils.dumps(std_board_info)
driver_device_obj.type = nic["type"]
@@ -206,8 +214,7 @@ def _generate_dep_list(nic):
if "vfs" not in nic:
driver_dep = driver_deployable.DriverDeployable()
driver_dep.num_accelerators = 1
driver_dep.attach_handle_list = [
_generate_attach_handle(nic)]
driver_dep.attach_handle_list = [_generate_attach_handle(nic)]
driver_dep.name = nic["name"]
driver_dep.driver_name = DRIVER_NAME
driver_dep.attribute_list = _generate_attribute_list(nic)
@@ -217,8 +224,7 @@ def _generate_dep_list(nic):
for vf in nic["vfs"]:
driver_dep = driver_deployable.DriverDeployable()
driver_dep.num_accelerators = 1
driver_dep.attach_handle_list = [
_generate_attach_handle(vf)]
driver_dep.attach_handle_list = [_generate_attach_handle(vf)]
driver_dep.name = vf["name"]
driver_dep.driver_name = DRIVER_NAME
driver_dep.attribute_list = _generate_attribute_list(vf)
@@ -229,8 +235,9 @@ def _generate_dep_list(nic):
def _generate_attach_handle(nic):
driver_ah = driver_attach_handle.DriverAttachHandle()
driver_ah.attach_type = "PCI"
driver_ah.attach_info = utils.pci_str_to_json(nic["device"],
nic["physical_network"])
driver_ah.attach_info = utils.pci_str_to_json(
nic["device"], nic["physical_network"]
)
driver_ah.in_use = False
return driver_ah

View File

@@ -19,8 +19,7 @@ Cyborg Pci driver implementation.
class PciDriver:
"""Base class for Pci drivers.
"""
"""Base class for Pci drivers."""
def __init__(self, *args, **kwargs):
pass

View File

@@ -41,11 +41,14 @@ class PciAddressSpec(metaclass=abc.ABCMeta):
pass
def is_single_address(self):
return all([
all(c in string.hexdigits for c in self.domain),
all(c in string.hexdigits for c in self.bus),
all(c in string.hexdigits for c in self.slot),
all(c in string.hexdigits for c in self.func)])
return all(
[
all(c in string.hexdigits for c in self.domain),
all(c in string.hexdigits for c in self.bus),
all(c in string.hexdigits for c in self.slot),
all(c in string.hexdigits for c in self.func),
]
)
def _set_pci_dev_info(self, prop, maxval, hex_value):
a = getattr(self, prop)
@@ -55,13 +58,20 @@ class PciAddressSpec(metaclass=abc.ABCMeta):
v = int(a, 16)
except ValueError:
raise exception.PciConfigInvalidWhitelist(
reason=_("property %(property)s ('%(attr)s') does not parse "
"as a hex number.") % {'property': prop, 'attr': a})
reason=_(
"property %(property)s ('%(attr)s') does not parse "
"as a hex number."
)
% {'property': prop, 'attr': a}
)
if v > maxval:
raise exception.PciConfigInvalidWhitelist(
reason=_("property %(property)s (%(attr)s) is greater than "
"the maximum allowable value (%(max)X).") % {
'property': prop, 'attr': a, 'max': maxval})
reason=_(
"property %(property)s (%(attr)s) is greater than "
"the maximum allowable value (%(max)X)."
)
% {'property': prop, 'attr': a, 'max': maxval}
)
setattr(self, prop, hex_value % v)
@@ -81,7 +91,8 @@ class PhysicalPciAddress(PciAddressSpec):
self.func = pci_addr['function']
else:
self.domain, self.bus, self.slot, self.func = (
utils.get_pci_address_fields(pci_addr))
utils.get_pci_address_fields(pci_addr)
)
self._set_pci_dev_info('func', MAX_FUNC, '%1x')
self._set_pci_dev_info('domain', MAX_DOMAIN, '%04x')
self._set_pci_dev_info('bus', MAX_BUS, '%02x')
@@ -95,7 +106,7 @@ class PhysicalPciAddress(PciAddressSpec):
self.bus == phys_pci_addr.bus,
self.slot == phys_pci_addr.slot,
self.func == phys_pci_addr.func,
]
]
return all(conditions)
@@ -136,8 +147,8 @@ class PciAddressGlobSpec(PciAddressSpec):
self.domain in (ANY, phys_pci_addr.domain),
self.bus in (ANY, phys_pci_addr.bus),
self.slot in (ANY, phys_pci_addr.slot),
self.func in (ANY, phys_pci_addr.func)
]
self.func in (ANY, phys_pci_addr.func),
]
return all(conditions)
@@ -167,8 +178,8 @@ class PciAddressRegexSpec(PciAddressSpec):
bool(self.domain_regex.match(phys_pci_addr.domain)),
bool(self.bus_regex.match(phys_pci_addr.bus)),
bool(self.slot_regex.match(phys_pci_addr.slot)),
bool(self.func_regex.match(phys_pci_addr.func))
]
bool(self.func_regex.match(phys_pci_addr.func)),
]
return all(conditions)
@@ -197,12 +208,12 @@ class WhitelistPciAddress:
def _check_physical_function(self):
if self.pci_address_spec.is_single_address():
self.is_physical_function = (
utils.is_physical_function(
self.pci_address_spec.domain,
self.pci_address_spec.bus,
self.pci_address_spec.slot,
self.pci_address_spec.func))
self.is_physical_function = utils.is_physical_function(
self.pci_address_spec.domain,
self.pci_address_spec.bus,
self.pci_address_spec.slot,
self.pci_address_spec.func,
)
def _init_address_fields(self, pci_addr):
if not self.is_physical_function:
@@ -266,8 +277,7 @@ class PciDeviceSpec(PciAddressSpec):
def match(self, dev_dict):
if self.dev_name:
address_str, pf = utils.get_function_by_ifname(
self.dev_name)
address_str, pf = utils.get_function_by_ifname(self.dev_name)
if not address_str:
return False
# Note(moshele): In this case we always passing a string
@@ -275,17 +285,25 @@ class PciDeviceSpec(PciAddressSpec):
address_obj = WhitelistPciAddress(address_str, pf)
elif self.address:
address_obj = self.address
return all([
self.vendor_id in (ANY, dev_dict['vendor_id']),
self.product_id in (ANY, dev_dict['product_id']),
address_obj.match(dev_dict['address'],
dev_dict.get('parent_addr'))])
return all(
[
self.vendor_id in (ANY, dev_dict['vendor_id']),
self.product_id in (ANY, dev_dict['product_id']),
address_obj.match(
dev_dict['address'], dev_dict.get('parent_addr')
),
]
)
def match_pci_obj(self, pci_obj):
return self.match({'vendor_id': pci_obj.vendor_id,
'product_id': pci_obj.product_id,
'address': pci_obj.address,
'parent_addr': pci_obj.parent_addr})
return self.match(
{
'vendor_id': pci_obj.vendor_id,
'product_id': pci_obj.product_id,
'address': pci_obj.address,
'parent_addr': pci_obj.parent_addr,
}
)
def get_tags(self):
return self.tags

View File

@@ -23,7 +23,7 @@ from cyborg.accelerator.drivers.pci.pci import sysinfo
class PCIDriver(PciDriver):
"""Class for Pci drivers.
Vendor should implement their specific drivers in this class.
Vendor should implement their specific drivers in this class.
"""
def discover(self):

View File

@@ -16,6 +16,7 @@
"""
Cyborg PCI driver implementation.
"""
import re
from oslo_log import log as logging
@@ -73,7 +74,8 @@ def _generate_attribute_list(pci):
values = pci.get(k, [])
for val in values:
driver_attr = driver_attribute.DriverAttribute(
key="trait" + str(index), value=val)
key="trait" + str(index), value=val
)
index = index + 1
attr_list.append(driver_attr)
return attr_list
@@ -98,8 +100,7 @@ def _generate_dep_list(pci):
# NOTE(yumeng) Since Wallaby release, the deplpyable_name is named as
# <Compute_hostname>_<Device_address>
driver_dep.name = pci.get('hostname', '') + '_' + pci["devices"]
vendor_name = pci_utils.VENDOR_MAPS.get(
pci["vendor_id"], pci["vendor_id"])
vendor_name = pci_utils.VENDOR_MAPS.get(pci["vendor_id"], pci["vendor_id"])
driver_dep.driver_name = vendor_name.upper()
driver_dep.num_accelerators = 1
driver_dep.attach_handle_list = [_generate_attach_handle(pci)]
@@ -118,16 +119,18 @@ def _generate_driver_device(pci):
driver_device_obj = driver_device.DriverDevice()
driver_device_obj.vendor = pci['vendor_id']
driver_device_obj.model = pci['product_id']
std_board_info = {'product_id': pci.get('product_id'),
'controller': pci.get('controller'),
}
std_board_info = {
'product_id': pci.get('product_id'),
'controller': pci.get('controller'),
}
driver_device_obj.std_board_info = jsonutils.dumps(std_board_info)
driver_device_obj.type = constants.DEVICE_GPU
driver_device_obj.stub = pci.get('stub', False)
driver_device_obj.controlpath_id = _generate_controlpath_id(pci)
driver_device_obj.deployable_list, ais = _generate_dep_list(pci)
driver_device_obj.vendor_board_info = pci.get('vendor_board_info',
"miss_vb_info")
driver_device_obj.vendor_board_info = pci.get(
'vendor_board_info', "miss_vb_info"
)
return driver_device_obj
@@ -149,14 +152,13 @@ def _discover_pcis():
'vendor_id': pci_dict['vendor_id'],
'product_id': pci_dict['product_id'],
'address': pci_dict['devices'],
'parent_addr': None
'parent_addr': None,
}
if dev_filter.device_assignable(dev_info):
# get hostname for deployable_name usage
pci_dict['hostname'] = CONF.host
pci_dict["rc"] = constants.RESOURCES["PCI"]
traits = _get_traits(pci_dict["vendor_id"],
pci_dict["product_id"])
traits = _get_traits(pci_dict["vendor_id"], pci_dict["product_id"])
pci_dict.update(traits)
pci_list.append(_generate_driver_device(pci_dict))
LOG.info('pci_list: %s', pci_list)

View File

@@ -25,9 +25,9 @@ import cyborg.privsep
LOG = logging.getLogger(__name__)
PCI_VENDOR_PATTERN = "^(hex{4})$".replace("hex", r"[\da-fA-F]")
_PCI_ADDRESS_PATTERN = ("^(hex{4}):(hex{2}):(hex{2}).(oct{1})$".
replace("hex", r"[\da-fA-F]").
replace("oct", "[0-7]"))
_PCI_ADDRESS_PATTERN = "^(hex{4}):(hex{2}):(hex{2}).(oct{1})$".replace(
"hex", r"[\da-fA-F]"
).replace("oct", "[0-7]")
_PCI_ADDRESS_REGEX = re.compile(_PCI_ADDRESS_PATTERN)
_SRIOV_TOTALVFS = "sriov_totalvfs"
@@ -61,6 +61,7 @@ def pci_device_prop_match(pci_dev, specs):
"capabilities_network": ["rx", "tx", "tso", "gso"]}]
"""
def _matching_devices(spec):
for k, v in spec.items():
pci_dev_v = pci_dev.get(k)
@@ -127,8 +128,7 @@ def get_function_by_ifname(ifname):
# sriov_totalvfs contains the maximum possible VFs for this PF
with open(os.path.join(dev_path, _SRIOV_TOTALVFS)) as fd:
sriov_totalvfs = int(fd.read())
return (os.readlink(dev_path).strip("./"),
sriov_totalvfs > 0)
return (os.readlink(dev_path).strip("./"), sriov_totalvfs > 0)
except (OSError, ValueError):
return os.readlink(dev_path).strip("./"), False
return None, False
@@ -136,7 +136,11 @@ def get_function_by_ifname(ifname):
def is_physical_function(domain, bus, slot, function):
dev_path = "/sys/bus/pci/devices/%(d)s:%(b)s:%(s)s.%(f)s/" % {
"d": domain, "b": bus, "s": slot, "f": function}
"d": domain,
"b": bus,
"s": slot,
"f": function,
}
if os.path.isdir(dev_path):
try:
with open(dev_path + _SRIOV_TOTALVFS) as fd:
@@ -185,10 +189,12 @@ def get_mac_by_pci_address(pci_addr, pf_interface=False):
mac = next(f).strip()
return mac
except (OSError, StopIteration) as e:
LOG.warning("Could not find the expected sysfs file for "
"determining the MAC address of the PCI device "
"%(addr)s. May not be a NIC. Error: %(e)s",
{'addr': pci_addr, 'e': e})
LOG.warning(
"Could not find the expected sysfs file for "
"determining the MAC address of the PCI device "
"%(addr)s. May not be a NIC. Error: %(e)s",
{'addr': pci_addr, 'e': e},
)
raise exception.PciDeviceNotFoundById(id=pci_addr)
@@ -230,9 +236,13 @@ def get_net_name_by_vf_pci_address(vfaddress):
try:
mac = get_mac_by_pci_address(vfaddress).split(':')
ifname = get_ifname_by_pci_address(vfaddress)
return ("net_%(ifname)s_%(mac)s" %
{'ifname': ifname, 'mac': '_'.join(mac)})
return "net_%(ifname)s_%(mac)s" % {
'ifname': ifname,
'mac': '_'.join(mac),
}
except Exception:
LOG.warning("No net device was found for VF %(vfaddress)s",
{'vfaddress': vfaddress})
LOG.warning(
"No net device was found for VF %(vfaddress)s",
{'vfaddress': vfaddress},
)
return

View File

@@ -56,19 +56,21 @@ class Whitelist:
dev_spec = jsonutils.loads(jsonspec)
except ValueError:
raise exception.PciConfigInvalidWhitelist(
reason=_("Invalid entry: '%s'") % jsonspec)
reason=_("Invalid entry: '%s'") % jsonspec
)
if isinstance(dev_spec, dict):
dev_spec = [dev_spec]
elif not isinstance(dev_spec, list):
raise exception.PciConfigInvalidWhitelist(
reason=_("Invalid entry: '%s'; "
"Expecting list or dict") % jsonspec)
reason=_("Invalid entry: '%s'; Expecting list or dict")
% jsonspec
)
for ds in dev_spec:
if not isinstance(ds, dict):
raise exception.PciConfigInvalidWhitelist(
reason=_("Invalid entry: '%s'; "
"Expecting dict") % ds)
reason=_("Invalid entry: '%s'; Expecting dict") % ds
)
spec = devspec.PciDeviceSpec(ds)
specs.append(spec)

View File

@@ -23,8 +23,8 @@ VENDOR_MAPS = {"0x8086": "intel"}
class QATDriver:
"""Base class for QAT drivers.
This is just a virtual QAT drivers interface.
Vendor should implement their specific drivers.
This is just a virtual QAT drivers interface.
Vendor should implement their specific drivers.
"""
@classmethod

View File

@@ -23,8 +23,9 @@ from cyborg.accelerator.drivers.qat.intel import sysinfo
class IntelQATDriver(QATDriver):
"""Class for Intel QAT drivers.
Vendor should implement their specific drivers in this class.
Vendor should implement their specific drivers in this class.
"""
VENDOR = "intel"
def __init__(self, *args, **kwargs):

View File

@@ -17,7 +17,6 @@
Cyborg Intel QAT driver implementation.
"""
import glob
import os
import socket
@@ -41,17 +40,12 @@ INTEL_QAT_DEV_PREFIX = "intel-qat-dev"
RC_QAT = constants.RESOURCES["QAT"]
DRIVER_NAME = "intel"
RESOURCES = {
"qat": RC_QAT
}
RESOURCES = {"qat": RC_QAT}
def pci_attributes(path):
with open(os.path.join(path, "uevent")) as f:
attributes = dict(map(
lambda p: p.strip().split("="),
f.readlines()
))
attributes = dict(map(lambda p: p.strip().split("="), f.readlines()))
with open(os.path.join(path, "vendor")) as f:
attributes["VENDOR"] = f.readline().strip()
@@ -64,47 +58,49 @@ def pci_attributes(path):
def get_link_targets(links):
return map(
lambda p:
os.path.realpath(
os.path.join(os.path.dirname(p), os.readlink(p))),
links)
lambda p: os.path.realpath(
os.path.join(os.path.dirname(p), os.readlink(p))
),
links,
)
def all_qats():
return set(filter(
lambda p: (
pci_attributes(p)["VENDOR"],
pci_attributes(p)["PRODUCT_ID"]
) in KNOW_QATS,
glob.glob(os.path.join(PCI_DEVICES_PATH, "*"))))
return set(
filter(
lambda p: (
pci_attributes(p)["VENDOR"],
pci_attributes(p)["PRODUCT_ID"],
)
in KNOW_QATS,
glob.glob(os.path.join(PCI_DEVICES_PATH, "*")),
)
)
def all_pfs_with_vf():
return set(filter(
lambda p: glob.glob(os.path.join(p, VF)),
all_qats()))
return set(filter(lambda p: glob.glob(os.path.join(p, VF)), all_qats()))
def all_vfs_in_pf(pf_path):
return map(
lambda p:
os.path.join(
lambda p: os.path.join(
os.path.dirname(os.path.dirname(p)),
os.path.basename(os.readlink(p))),
glob.glob(os.path.join(pf_path, VF)))
os.path.basename(os.readlink(p)),
),
glob.glob(os.path.join(pf_path, VF)),
)
def find_pf_by_vf(vf_path):
return os.path.join(
os.path.dirname(vf_path),
os.path.basename(os.readlink(
os.path.join(vf_path, PF))))
os.path.basename(os.readlink(os.path.join(vf_path, PF))),
)
def all_vfs():
return map(
lambda p: all_vfs_in_pf(p), all_pfs_with_vf()
)
return map(lambda p: all_vfs_in_pf(p), all_pfs_with_vf())
def qat_gen(path):
@@ -142,8 +138,8 @@ def _generate_driver_device(qat):
driver_device_obj.stub = qat["stub"]
driver_device_obj.model = qat.get("model", "miss_model_info")
driver_device_obj.vendor_board_info = qat.get(
"vendor_board_info",
"miss_vb_info")
"vendor_board_info", "miss_vb_info"
)
std_board_info = {"product_id": qat.get("product_id")}
driver_device_obj.std_board_info = jsonutils.dumps(std_board_info)
driver_device_obj.type = qat["type"]
@@ -165,8 +161,7 @@ def _generate_dep_list(qat):
if "vfs" not in qat:
driver_dep = driver_deployable.DriverDeployable()
driver_dep.num_accelerators = 1
driver_dep.attach_handle_list = [
_generate_attach_handle(qat)]
driver_dep.attach_handle_list = [_generate_attach_handle(qat)]
driver_dep.name = qat["name"]
driver_dep.driver_name = DRIVER_NAME
driver_dep.attribute_list = _generate_attribute_list(qat)
@@ -176,8 +171,7 @@ def _generate_dep_list(qat):
for vf in qat["vfs"]:
driver_dep = driver_deployable.DriverDeployable()
driver_dep.num_accelerators = 1
driver_dep.attach_handle_list = [
_generate_attach_handle(vf)]
driver_dep.attach_handle_list = [_generate_attach_handle(vf)]
driver_dep.name = vf["name"]
driver_dep.driver_name = DRIVER_NAME
driver_dep.attribute_list = _generate_attribute_list(qat)

View File

@@ -52,7 +52,7 @@ class NVMFDRIVER(SPDKDRIVER):
accelerator_obj = {
'server': self.SERVER,
'bdevs': bdevs,
'subsystems': subsystems
'subsystems': subsystems,
}
return accelerator_obj
@@ -95,13 +95,9 @@ class NVMFDRIVER(SPDKDRIVER):
else:
raise exception.Invalid('Delete nvmf subsystem failed.')
def construct_subsystem(self,
nqn,
listen,
hosts,
serial_number,
namespaces
):
def construct_subsystem(
self, nqn, listen, hosts, serial_number, namespaces
):
"""Add a nvmf subsystem
:param nqn: Target nqn(ASCII).
@@ -117,14 +113,13 @@ class NVMFDRIVER(SPDKDRIVER):
:param namespaces: Whitespace-separated list of namespaces.
:raise exception: Invalid
"""
if ((namespaces != '' and listen != '') and
(hosts != '' and serial_number != '')) and nqn != '':
if (
(namespaces != '' and listen != '')
and (hosts != '' and serial_number != '')
) and nqn != '':
acc_client = NvmfTgt(self.py)
acc_client.construct_nvmf_subsystem(nqn,
listen,
hosts,
serial_number,
namespaces
)
acc_client.construct_nvmf_subsystem(
nqn, listen, hosts, serial_number, namespaces
)
else:
raise exception.Invalid('Construct nvmf subsystem failed.')

View File

@@ -16,15 +16,17 @@ Cyborg SPDK driver modules implementation.
"""
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
class SPDKDRIVER:
"""SPDKDRIVER
This is just a virtual SPDK drivers interface.
SPDK-based app server should implement their specific drivers.
This is just a virtual SPDK drivers interface.
SPDK-based app server should implement their specific drivers.
"""
@classmethod
def create(cls, server, *args, **kwargs):
for subclass in cls.__subclasses__():

View File

@@ -32,25 +32,29 @@ from cyborg.common.i18n import _
LOG = logging.getLogger(__name__)
accelerator_opts = [
cfg.StrOpt('spdk_conf_file',
default='/etc/cyborg/spdk.conf',
help=_('SPDK conf file to be used for the SPDK driver')),
cfg.StrOpt('accelerator_servers',
default=['vhost', 'nvmf', 'iscsi'],
help=_('A list of accelerator servers to enable by default')),
cfg.StrOpt('spdk_dir',
default='/home/wewe/spdk',
help=_('The SPDK directory is /home/{user_name}/spdk')),
cfg.StrOpt('device_type',
default='NVMe',
help=_('Backend device type is NVMe by default')),
cfg.BoolOpt('remoteable',
default=False,
help=_('Remoteable is false by default'))
cfg.StrOpt(
'spdk_conf_file',
default='/etc/cyborg/spdk.conf',
help=_('SPDK conf file to be used for the SPDK driver'),
),
cfg.StrOpt(
'accelerator_servers',
default=['vhost', 'nvmf', 'iscsi'],
help=_('A list of accelerator servers to enable by default'),
),
cfg.StrOpt(
'spdk_dir',
default='/home/wewe/spdk',
help=_('The SPDK directory is /home/{user_name}/spdk'),
),
cfg.StrOpt(
'device_type',
default='NVMe',
help=_('Backend device type is NVMe by default'),
),
cfg.BoolOpt(
'remoteable', default=False, help=_('Remoteable is false by default')
),
]
CONF = cfg.CONF
@@ -124,15 +128,9 @@ def construct_error_bdev(py, accelerator, basename):
acc_client.construct_error_bdev(basename)
def construct_nvme_bdev(py,
accelerator,
name,
trtype,
traddr,
adrfam,
trsvcid,
subnqn
):
def construct_nvme_bdev(
py, accelerator, name, trtype, traddr, adrfam, trsvcid, subnqn
):
"""Add a bdev with nvme backend
:param py: py_client.
@@ -148,22 +146,13 @@ def construct_nvme_bdev(py,
:return: name.
"""
acc_client = get_accelerator_client(py, accelerator)
acc_client.construct_nvme_bdev(name,
trtype,
traddr,
adrfam,
trsvcid,
subnqn
)
acc_client.construct_nvme_bdev(
name, trtype, traddr, adrfam, trsvcid, subnqn
)
return name
def construct_null_bdev(py,
accelerator,
name,
total_size,
block_size
):
def construct_null_bdev(py, accelerator, name, total_size, block_size):
"""Add a bdev with null backend
:param py: py_client.
@@ -189,7 +178,7 @@ def get_py_client(server):
py = PySPDK(server)
return py
else:
msg = (_("Could not find %s accelerator") % server)
msg = _("Could not find %s accelerator") % server
raise exception.InvalidAccelerator(msg)
@@ -204,7 +193,7 @@ def check_for_setup_error(py, server):
if py.is_alive():
return True
else:
msg = (_("%s accelerator is down") % server)
msg = _("%s accelerator is down") % server
raise exception.AcceleratorException(msg)
@@ -224,6 +213,5 @@ def get_accelerator_client(py, accelerator):
acc_client = NvmfTgt(py)
return acc_client
else:
exc_msg = (_("accelerator_client %(acc_client) is missing")
% acc_client)
exc_msg = _("accelerator_client %(acc_client) is missing") % acc_client
raise exception.InvalidAccelerator(exc_msg)

View File

@@ -18,19 +18,16 @@ LOG = logging.getLogger(__name__)
class NvmfTgt:
def __init__(self, py):
super().__init__()
self.py = py
def get_rpc_methods(self):
rpc_methods = self._get_json_objs(
'get_rpc_methods', '10.0.2.15')
rpc_methods = self._get_json_objs('get_rpc_methods', '10.0.2.15')
return rpc_methods
def get_bdevs(self):
block_devices = self._get_json_objs(
'get_bdevs', '10.0.2.15')
block_devices = self._get_json_objs('get_bdevs', '10.0.2.15')
return block_devices
def delete_bdev(self, name):
@@ -46,27 +43,20 @@ class NvmfTgt:
def construct_aio_bdev(self, filename, name, block_size):
sub_args = [filename, name, str(block_size)]
res = self.py.exec_rpc(
'construct_aio_bdev',
'10.0.2.15',
sub_args=sub_args)
'construct_aio_bdev', '10.0.2.15', sub_args=sub_args
)
LOG.info(res)
def construct_error_bdev(self, basename):
sub_args = [basename]
res = self.py.exec_rpc(
'construct_error_bdev',
'10.0.2.15',
sub_args=sub_args)
'construct_error_bdev', '10.0.2.15', sub_args=sub_args
)
LOG.info(res)
def construct_nvme_bdev(
self,
name,
trtype,
traddr,
adrfam=None,
trsvcid=None,
subnqn=None):
self, name, trtype, traddr, adrfam=None, trsvcid=None, subnqn=None
):
sub_args = ["-b", "-t", "-a"]
sub_args.insert(1, name)
sub_args.insert(2, trtype)
@@ -81,52 +71,42 @@ class NvmfTgt:
sub_args.append("-n")
sub_args.append(subnqn)
res = self.py.exec_rpc(
'construct_nvme_bdev',
'10.0.2.15',
sub_args=sub_args)
'construct_nvme_bdev', '10.0.2.15', sub_args=sub_args
)
return res
def construct_null_bdev(self, name, total_size, block_size):
sub_args = [name, str(total_size), str(block_size)]
res = self.py.exec_rpc(
'construct_null_bdev',
'10.0.2.15',
sub_args=sub_args)
'construct_null_bdev', '10.0.2.15', sub_args=sub_args
)
return res
def construct_malloc_bdev(self, total_size, block_size):
sub_args = [str(total_size), str(block_size)]
res = self.py.exec_rpc(
'construct_malloc_bdev',
'10.0.2.15',
sub_args=sub_args)
'construct_malloc_bdev', '10.0.2.15', sub_args=sub_args
)
LOG.info(res)
def delete_nvmf_subsystem(self, nqn):
sub_args = [nqn]
res = self.py.exec_rpc(
'delete_nvmf_subsystem',
'10.0.2.15',
sub_args=sub_args)
'delete_nvmf_subsystem', '10.0.2.15', sub_args=sub_args
)
LOG.info(res)
def construct_nvmf_subsystem(
self,
nqn,
listen,
hosts,
serial_number,
namespaces):
self, nqn, listen, hosts, serial_number, namespaces
):
sub_args = [nqn, listen, hosts, serial_number, namespaces]
res = self.py.exec_rpc(
'construct_nvmf_subsystem',
'10.0.2.15',
sub_args=sub_args)
'construct_nvmf_subsystem', '10.0.2.15', sub_args=sub_args
)
LOG.info(res)
def get_nvmf_subsystems(self):
subsystems = self._get_json_objs(
'get_nvmf_subsystems', '10.0.2.15')
subsystems = self._get_json_objs('get_nvmf_subsystems', '10.0.2.15')
return subsystems
def _get_json_objs(self, method, server_ip):

View File

@@ -24,7 +24,6 @@ LOG = logging.getLogger(__name__)
class PySPDK:
def __init__(self, pname):
super().__init__()
self.pid = None

View File

@@ -18,7 +18,6 @@ LOG = logging.getLogger(__name__)
class VhostTgt:
def __init__(self, py):
super().__init__()
self.py = py
@@ -28,8 +27,7 @@ class VhostTgt:
return rpc_methods
def get_scsi_devices(self):
scsi_devices = self._get_json_objs(
'get_scsi_devices', '127.0.0.1')
scsi_devices = self._get_json_objs('get_scsi_devices', '127.0.0.1')
return scsi_devices
def get_luns(self):
@@ -37,29 +35,25 @@ class VhostTgt:
return luns
def get_interfaces(self):
interfaces = self._get_json_objs(
'get_interfaces', '127.0.0.1')
interfaces = self._get_json_objs('get_interfaces', '127.0.0.1')
return interfaces
def add_ip_address(self, ifc_index, ip_addr):
sub_args = [ifc_index, ip_addr]
res = self.py.exec_rpc(
'add_ip_address',
'127.0.0.1',
sub_args=sub_args)
'add_ip_address', '127.0.0.1', sub_args=sub_args
)
return res
def delete_ip_address(self, ifc_index, ip_addr):
sub_args = [ifc_index, ip_addr]
res = self.py.exec_rpc(
'delete_ip_address',
'127.0.0.1',
sub_args=sub_args)
'delete_ip_address', '127.0.0.1', sub_args=sub_args
)
return res
def get_bdevs(self):
block_devices = self._get_json_objs(
'get_bdevs', '127.0.0.1')
block_devices = self._get_json_objs('get_bdevs', '127.0.0.1')
return block_devices
def delete_bdev(self, name):
@@ -75,27 +69,20 @@ class VhostTgt:
def construct_aio_bdev(self, filename, name, block_size):
sub_args = [filename, name, str(block_size)]
res = self.py.exec_rpc(
'construct_aio_bdev',
'127.0.0.1',
sub_args=sub_args)
'construct_aio_bdev', '127.0.0.1', sub_args=sub_args
)
LOG.info(res)
def construct_error_bdev(self, basename):
sub_args = [basename]
res = self.py.exec_rpc(
'construct_error_bdev',
'127.0.0.1',
sub_args=sub_args)
'construct_error_bdev', '127.0.0.1', sub_args=sub_args
)
LOG.info(res)
def construct_nvme_bdev(
self,
name,
trtype,
traddr,
adrfam=None,
trsvcid=None,
subnqn=None):
self, name, trtype, traddr, adrfam=None, trsvcid=None, subnqn=None
):
sub_args = ["-b", "-t", "-a"]
sub_args.insert(1, name)
sub_args.insert(2, trtype)
@@ -110,25 +97,22 @@ class VhostTgt:
sub_args.append("-n")
sub_args.append(subnqn)
res = self.py.exec_rpc(
'construct_nvme_bdev',
'127.0.0.1',
sub_args=sub_args)
'construct_nvme_bdev', '127.0.0.1', sub_args=sub_args
)
return res
def construct_null_bdev(self, name, total_size, block_size):
sub_args = [name, str(total_size), str(block_size)]
res = self.py.exec_rpc(
'construct_null_bdev',
'127.0.0.1',
sub_args=sub_args)
'construct_null_bdev', '127.0.0.1', sub_args=sub_args
)
return res
def construct_malloc_bdev(self, total_size, block_size):
sub_args = [str(total_size), str(block_size)]
res = self.py.exec_rpc(
'construct_malloc_bdev',
'10.0.2.15',
sub_args=sub_args)
'construct_malloc_bdev', '10.0.2.15', sub_args=sub_args
)
LOG.info(res)
def _get_json_objs(self, method, server_ip):

View File

@@ -28,7 +28,7 @@ LOG = logging.getLogger(__name__)
class VHOSTDRIVER(SPDKDRIVER):
"""VHOSTDRIVER class.
vhost server app should be able to implement this driver.
vhost server app should be able to implement this driver.
"""
SERVER = 'vhost'
@@ -57,7 +57,7 @@ class VHOSTDRIVER(SPDKDRIVER):
'bdevs': bdevs,
'scsi_devices': scsi_devices,
'luns': luns,
'interfaces': interfaces
'interfaces': interfaces,
}
return accelerator_obj

View File

@@ -16,6 +16,7 @@
"""
Cyborg Generic SSD driver implementation.
"""
from oslo_log import log as logging
from cyborg.accelerator.common import utils as pci_utils
@@ -28,8 +29,8 @@ VENDOR_MAPS = pci_utils.get_vendor_maps()
class SSDDriver:
"""Generic class for SSD drivers.
This is just a virtual SSD drivers interface.
Vendor should implement their specific drivers.
This is just a virtual SSD drivers interface.
Vendor should implement their specific drivers.
"""
@classmethod

View File

@@ -36,11 +36,13 @@ import cyborg.privsep
LOG = logging.getLogger(__name__)
SSD_FLAGS = ["Non-Volatile memory controller"]
SSD_INFO_PATTERN = re.compile(r"(?P<devices>[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:"
r"[0-9a-fA-F]{2}\.[0-9a-fA-F]) "
r"(?P<controller>.*) [\[].*]: (?P<model>.*) .*"
r"[\[](?P<vendor_id>[0-9a-fA-F]"
r"{4}):(?P<product_id>[0-9a-fA-F]{4})].*")
SSD_INFO_PATTERN = re.compile(
r"(?P<devices>[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:"
r"[0-9a-fA-F]{2}\.[0-9a-fA-F]) "
r"(?P<controller>.*) [\[].*]: (?P<model>.*) .*"
r"[\[](?P<vendor_id>[0-9a-fA-F]"
r"{4}):(?P<product_id>[0-9a-fA-F]{4})].*"
)
VENDOR_MAPS = utils.get_vendor_maps()
@@ -107,8 +109,10 @@ def _generate_driver_device(ssd):
driver_device_obj = driver_device.DriverDevice()
driver_device_obj.vendor = ssd["vendor_id"]
driver_device_obj.model = ssd.get('model', 'miss model info')
std_board_info = {'product_id': ssd.get('product_id'),
'controller': ssd.get('controller')}
std_board_info = {
'product_id': ssd.get('product_id'),
'controller': ssd.get('controller'),
}
vendor_board_info = {'vendor_info': ssd.get('vendor_info', 'ssd_vb_info')}
driver_device_obj.std_board_info = jsonutils.dumps(std_board_info)
driver_device_obj.vendor_board_info = jsonutils.dumps(vendor_board_info)
@@ -165,6 +169,7 @@ def _generate_attribute_list(ssd):
values = ssd.get(k, [])
for index, val in enumerate(values):
driver_attr = driver_attribute.DriverAttribute(
key="trait" + str(index), value=val)
key="trait" + str(index), value=val
)
attr_list.append(driver_attr)
return attr_list

View File

@@ -70,18 +70,22 @@ class AgentManager(periodic_task.PeriodicTasks):
for attempt in range(retries + 1):
try:
self.resource_provider_name = (
self._get_resource_provider_name())
self._get_resource_provider_name()
)
break
except exception.PlacementResourceProviderNotFound:
if attempt < retries:
wait = 2 ** attempt # 1, 2, 4, 8, ...
wait = 2**attempt # 1, 2, 4, 8, ...
LOG.warning(
"Resource provider not found in Placement, "
"retrying in %(wait)ds (attempt %(attempt)d/"
"%(total)d)",
{'wait': wait,
'attempt': attempt + 1,
'total': retries + 1})
{
'wait': wait,
'attempt': attempt + 1,
'total': retries + 1,
},
)
time.sleep(wait)
else:
raise
@@ -114,7 +118,8 @@ class AgentManager(periodic_task.PeriodicTasks):
LOG.warning(
"Resource provider not found with name '%(primary)s', "
"using fallback '%(fallback)s'",
{'primary': primary, 'fallback': candidate})
{'primary': primary, 'fallback': candidate},
)
LOG.info("Using resource provider name: %s", candidate)
return candidate
@@ -122,9 +127,12 @@ class AgentManager(periodic_task.PeriodicTasks):
"Could not find resource provider in Placement. Tried: %s. "
"Ensure nova-compute is running and has registered with "
"Placement, or set [agent] resource_provider_name to match "
"the compute node's hypervisor_hostname.", candidates)
"the compute node's hypervisor_hostname.",
candidates,
)
raise exception.PlacementResourceProviderNotFound(
resource_provider=primary)
resource_provider=primary
)
def _check_resource_provider_exists(self, hostname):
"""Check if a resource provider exists in Placement.
@@ -134,36 +142,38 @@ class AgentManager(periodic_task.PeriodicTasks):
"""
try:
resp = self.placement_client.get(
"/resource_providers?name="
+ urllib.parse.quote(hostname))
"/resource_providers?name=" + urllib.parse.quote(hostname)
)
providers = resp.json().get("resource_providers", [])
return len(providers) > 0
except (ValueError, AttributeError) as e:
LOG.warning(
"Failed to parse Placement response for "
"'%(name)s': %(err)s",
{'name': hostname, 'err': e})
"Failed to parse Placement response for '%(name)s': %(err)s",
{'name': hostname, 'err': e},
)
return False
except (ks_exc.ClientException,
exception.PlacementServerError) as e:
except (ks_exc.ClientException, exception.PlacementServerError) as e:
LOG.warning(
"Failed to check resource provider '%(name)s': %(err)s",
{'name': hostname, 'err': e})
{'name': hostname, 'err': e},
)
return False
def periodic_tasks(self, context, raise_on_error=False):
return self.run_periodic_tasks(context, raise_on_error=raise_on_error)
def fpga_program(self, context, controlpath_id,
bitstream_uuid, driver_name):
def fpga_program(
self, context, controlpath_id, bitstream_uuid, driver_name
):
bitstream_uuid = str(bitstream_uuid)
if not uuidutils.is_uuid_like(bitstream_uuid):
raise exception.InvalidUUID(uuid=bitstream_uuid)
download_path = tempfile.NamedTemporaryFile(suffix=".gbs",
prefix=bitstream_uuid)
self.image_api.download(context,
bitstream_uuid,
dest_path=download_path.name)
download_path = tempfile.NamedTemporaryFile(
suffix=".gbs", prefix=bitstream_uuid
)
self.image_api.download(
context, bitstream_uuid, dest_path=download_path.name
)
try:
driver = self.fpga_driver.create(driver_name)
ret = driver.program(controlpath_id, download_path.name)

View File

@@ -53,20 +53,22 @@ class ResourceTracker:
if not enabled_drivers:
enabled_drivers = CONF.agent.enabled_drivers
valid_drivers = ExtensionManager(
namespace='cyborg.accelerator.driver').names()
namespace='cyborg.accelerator.driver'
).names()
for d in enabled_drivers:
if d not in valid_drivers:
raise exception.InvalidDriver(name=d)
acc_driver = driver.DriverManager(
namespace='cyborg.accelerator.driver', name=d,
invoke_on_load=True).driver
namespace='cyborg.accelerator.driver',
name=d,
invoke_on_load=True,
).driver
acc_drivers.append(acc_driver)
self.acc_drivers = acc_drivers
@utils.synchronized(AGENT_RESOURCE_SEMAPHORE)
def update_usage(self, context):
"""Update the resource usage periodically.
"""
"""Update the resource usage periodically."""
acc_list = []
for acc_driver in self.acc_drivers:
acc_list.extend(acc_driver.discover())

View File

@@ -1,4 +1,3 @@
#
# 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
@@ -43,42 +42,64 @@ class AgentAPI:
def __init__(self, topic=None):
super().__init__()
self.topic = topic or constants.AGENT_TOPIC
target = messaging.Target(topic=self.topic,
version='1.0')
target = messaging.Target(topic=self.topic, version='1.0')
serializer = objects_base.CyborgObjectSerializer()
self.client = rpc.get_client(target,
version_cap=self.RPC_API_VERSION,
serializer=serializer)
self.client = rpc.get_client(
target, version_cap=self.RPC_API_VERSION, serializer=serializer
)
def fpga_program(self, context, hostname, controlpath_id,
bitstream_uuid, driver_name):
LOG.info('Agent fpga_program: hostname: (%s) ' +
'bitstream_id: (%s)', hostname, bitstream_uuid)
def fpga_program(
self, context, hostname, controlpath_id, bitstream_uuid, driver_name
):
LOG.info(
'Agent fpga_program: hostname: (%s) bitstream_id: (%s)',
hostname,
bitstream_uuid,
)
version = '1.0'
cctxt = self.client.prepare(server=hostname, version=version)
return cctxt.call(context, 'fpga_program',
controlpath_id=controlpath_id,
bitstream_uuid=bitstream_uuid,
driver_name=driver_name)
return cctxt.call(
context,
'fpga_program',
controlpath_id=controlpath_id,
bitstream_uuid=bitstream_uuid,
driver_name=driver_name,
)
def create_vgpu_mdev(self, context, hostname, pci_addr,
asked_type, ah_uuid):
LOG.debug('Agent create_vgpu_mdev: hostname: (%s) , pci_address: (%s)'
'gpu_id: (%s)', hostname, pci_addr, ah_uuid)
def create_vgpu_mdev(
self, context, hostname, pci_addr, asked_type, ah_uuid
):
LOG.debug(
'Agent create_vgpu_mdev: hostname: (%s) , pci_address: (%s)'
'gpu_id: (%s)',
hostname,
pci_addr,
ah_uuid,
)
version = '1.0'
cctxt = self.client.prepare(server=hostname, version=version)
return cctxt.call(context, 'create_vgpu_mdev',
pci_addr=pci_addr,
asked_type=asked_type,
ah_uuid=ah_uuid)
return cctxt.call(
context,
'create_vgpu_mdev',
pci_addr=pci_addr,
asked_type=asked_type,
ah_uuid=ah_uuid,
)
def remove_vgpu_mdev(self, context, hostname, pci_addr,
asked_type, ah_uuid):
LOG.debug('Agent remove_vgpu_mdev: hostname: (%s) '
'gpu_id: (%s)', hostname, ah_uuid)
def remove_vgpu_mdev(
self, context, hostname, pci_addr, asked_type, ah_uuid
):
LOG.debug(
'Agent remove_vgpu_mdev: hostname: (%s) gpu_id: (%s)',
hostname,
ah_uuid,
)
version = '1.0'
cctxt = self.client.prepare(server=hostname, version=version)
return cctxt.call(context, 'remove_vgpu_mdev',
pci_addr=pci_addr,
asked_type=asked_type,
ah_uuid=ah_uuid)
return cctxt.call(
context,
'remove_vgpu_mdev',
pci_addr=pci_addr,
asked_type=asked_type,
ah_uuid=ah_uuid,
)

View File

@@ -40,10 +40,12 @@ def setup_app(pecan_config=None, extra_hooks=None):
if not pecan_config:
pecan_config = get_pecan_config()
app_hooks = [hooks.ConfigHook(),
hooks.ConductorAPIHook(),
hooks.ContextHook(pecan_config.app.acl_public_routes),
hooks.PublicUrlHook()]
app_hooks = [
hooks.ConfigHook(),
hooks.ConductorAPIHook(),
hooks.ContextHook(pecan_config.app.acl_public_routes),
hooks.PublicUrlHook(),
]
if extra_hooks:
app_hooks.extend(extra_hooks)
@@ -53,7 +55,7 @@ def setup_app(pecan_config=None, extra_hooks=None):
force_canonical=getattr(pecan_config.app, 'force_canonical', True),
hooks=app_hooks,
wrap_app=middleware.ParsableErrorMiddleware,
**app_conf
**app_conf,
)
return app

View File

@@ -15,10 +15,7 @@
# Server Specific Configurations
# See https://pecan.readthedocs.org/en/latest/configuration.html#server-configuration # noqa
server = {
'port': '6666',
'host': '127.0.0.1'
}
server = {'port': '6666', 'host': '127.0.0.1'}
# Pecan Application Configurations
# See https://pecan.readthedocs.org/en/latest/configuration.html#application-configuration # noqa
@@ -27,14 +24,9 @@ app = {
'modules': ['cyborg.api'],
'static_root': '%(confdir)s/public',
'debug': False,
'acl_public_routes': [
'/',
'/v2'
]
'acl_public_routes': ['/', '/v2'],
}
# WSME Configurations
# See https://wsme.readthedocs.org/en/latest/integrate.html#configuration
wsme = {
'debug': False
}
wsme = {'debug': False}

View File

@@ -37,12 +37,14 @@ class APIBase(wtypes.Base):
def as_dict(self):
"""Render this object as a dict of its fields."""
return {k: getattr(self, k) for k in self.fields
if hasattr(self, k) and getattr(self, k) != wsme.Unset}
return {
k: getattr(self, k)
for k in self.fields
if hasattr(self, k) and getattr(self, k) != wsme.Unset
}
class CyborgController(rest.RestController):
def _handle_patch(self, method, remainder, request=None):
"""Routes ``PATCH`` _custom_actions."""
# route to a patch_all or get if no additional parts are available
@@ -91,7 +93,8 @@ class Version:
"""
(self.major, self.minor) = Version.parse_headers(
headers, default_version, latest_version)
headers, default_version, latest_version
)
def __repr__(self):
return '%s.%s' % (self.major, self.minor)
@@ -127,7 +130,8 @@ class Version:
if len(version) != 2:
raise exc.HTTPNotAcceptable(
"Invalid value for %s header" % Version.current_api_version)
"Invalid value for %s header" % Version.current_api_version
)
return version
def __gt__(self, other):

View File

@@ -24,11 +24,15 @@ def build_url(resource, resource_args, bookmark=False, base_url=None):
base_url = pecan.request.public_url
# TODO(Sundar) Return version etc. similar to other projects.
template = '%(url)s/accelerator/%(res)s' \
if bookmark else '%(url)s/accelerator/' + base.API_V2 + '/%(res)s'
template = (
'%(url)s/accelerator/%(res)s'
if bookmark
else '%(url)s/accelerator/' + base.API_V2 + '/%(res)s'
)
if resource_args:
template += ('%(args)s' if resource_args.startswith('?')
else '/%(args)s')
template += (
'%(args)s' if resource_args.startswith('?') else '/%(args)s'
)
return template % {'url': base_url, 'res': resource, 'args': resource_args}
@@ -45,10 +49,17 @@ class Link(base.APIBase):
"""Indicates the type of document/link."""
@staticmethod
def make_link(rel_name, url, resource, resource_args,
bookmark=False, type=wtypes.Unset):
href = build_url(resource, resource_args,
bookmark=bookmark, base_url=url)
def make_link(
rel_name,
url,
resource,
resource_args,
bookmark=False,
type=wtypes.Unset,
):
href = build_url(
resource, resource_args, bookmark=bookmark, base_url=url
)
return Link(href=href, rel=rel_name, type=type)
@staticmethod

View File

@@ -57,13 +57,17 @@ class Version(base.APIBase):
version.min_version = None
else:
v = importlib.import_module(
'cyborg.api.controllers.%s.versions' % id)
'cyborg.api.controllers.%s.versions' % id
)
version.max_version = v.max_version_string()
version.min_version = v.min_version_string()
version.id = id
version.status = status
version.links = [link.Link.make_link('self', pecan.request.host_url,
id, '', bookmark=True)]
version.links = [
link.Link.make_link(
'self', pecan.request.host_url, id, '', bookmark=True
)
]
return version
@@ -87,7 +91,8 @@ class Root(base.APIBase):
root.description = (
"Cyborg is the OpenStack project for lifecycle "
"management of hardware accelerators, such as GPUs,"
"FPGAs, AI chips, security accelerators, etc.")
"FPGAs, AI chips, security accelerators, etc."
)
root.versions = [Version.convert('v2')]
root.default_version = Version.convert('v2')
return root

View File

@@ -27,26 +27,35 @@ from cyborg.common.i18n import _
class FilterType(wtypes.UserType):
"""Query filter."""
name = 'filtertype'
basetype = wtypes.text
_supported_fields = wtypes.Enum(wtypes.text, 'parent_uuid', 'root_uuid',
'board', 'availability', 'interface_type',
'instance_uuid', 'limit', 'marker',
'sort_key', 'sort_dir', 'name')
_supported_fields = wtypes.Enum(
wtypes.text,
'parent_uuid',
'root_uuid',
'board',
'availability',
'interface_type',
'instance_uuid',
'limit',
'marker',
'sort_key',
'sort_dir',
'name',
)
field = wsme.wsattr(_supported_fields, mandatory=True)
value = wsme.wsattr(wtypes.text, mandatory=True)
def __repr__(self):
# for logging calls
return '<Query %s %s>' % (self.field,
self.value)
return '<Query %s %s>' % (self.field, self.value)
@classmethod
def sample(cls):
return cls(field='interface_type',
value='pci')
return cls(field='interface_type', value='pci')
def as_dict(self):
d = dict()
@@ -131,10 +140,12 @@ integer = wtypes.IntegerType()
class JsonPatchType(wtypes.Base):
"""A complex type that represents a single json-patch operation."""
path = wtypes.wsattr(wtypes.StringType(pattern=r'^(/[\w-]+)+$'),
mandatory=True)
op = wtypes.wsattr(wtypes.Enum(str, 'add', 'replace', 'remove'),
mandatory=True)
path = wtypes.wsattr(
wtypes.StringType(pattern=r'^(/[\w-]+)+$'), mandatory=True
)
op = wtypes.wsattr(
wtypes.Enum(str, 'add', 'replace', 'remove'), mandatory=True
)
value = wtypes.wsattr(jsontype, default=wtypes.Unset)
# The class of the objects being patched. Override this in subclasses.
@@ -170,8 +181,9 @@ class JsonPatchType(wtypes.Base):
if cls._non_removable_attrs is None:
cls._non_removable_attrs = cls._extra_non_removable_attrs.copy()
if cls._api_base:
fields = inspect.getmembers(cls._api_base,
lambda a: not inspect.isroutine(a))
fields = inspect.getmembers(
cls._api_base, lambda a: not inspect.isroutine(a)
)
for name, field in fields:
if getattr(field, 'mandatory', False):
cls._non_removable_attrs.add('/%s' % name)

View File

@@ -20,16 +20,20 @@ import wsme
from cyborg.common.i18n import _
JSONPATCH_EXCEPTIONS = (jsonpatch.JsonPatchException,
jsonpatch.JsonPointerException,
KeyError)
JSONPATCH_EXCEPTIONS = (
jsonpatch.JsonPatchException,
jsonpatch.JsonPointerException,
KeyError,
)
def apply_jsonpatch(doc, patch):
for p in patch:
if p['op'] == 'add' and p['path'].count('/') == 1:
if p['path'].lstrip('/') not in doc:
msg = _('Adding a new attribute (%s) to the root of '
' the resource is not allowed')
msg = _(
'Adding a new attribute (%s) to the root of '
' the resource is not allowed'
)
raise wsme.exc.ClientSideError(msg % p['path'])
return jsonpatch.apply_patch(doc, jsonpatch.JsonPatch(patch))

View File

@@ -36,13 +36,17 @@ from cyborg.api.controllers.v2 import versions
def min_version():
return base.Version(
{base.Version.current_api_version: versions.min_version_string()},
versions.min_version_string(), versions.max_version_string())
versions.min_version_string(),
versions.max_version_string(),
)
def max_version():
return base.Version(
{base.Version.current_api_version: versions.max_version_string()},
versions.min_version_string(), versions.max_version_string())
versions.min_version_string(),
versions.max_version_string(),
)
class V2(base.APIBase):
@@ -71,9 +75,8 @@ class V2(base.APIBase):
v2.min_version = str(min_version())
v2.status = 'CURRENT'
v2.links = [
link.Link.make_link('self', pecan.request.public_url,
'', ''),
]
link.Link.make_link('self', pecan.request.public_url, '', ''),
]
return v2
@@ -98,24 +101,35 @@ class Controller(rest.RestController):
raise exc.HTTPNotAcceptable(
"Mutually exclusive versions requested. Version %(ver)s "
"requested but not supported by this service. The supported "
"version range is: [%(min)s, %(max)s]." %
{'ver': version, 'min': versions.min_version_string(),
'max': versions.max_version_string()},
headers=headers)
"version range is: [%(min)s, %(max)s]."
% {
'ver': version,
'min': versions.min_version_string(),
'max': versions.max_version_string(),
},
headers=headers,
)
# ensure the minor version is within the supported range
if version < min_version() or version > max_version():
raise exc.HTTPNotAcceptable(
"Version %(ver)s was requested but the minor version is not "
"supported by this service. The supported version range is: "
"[%(min)s, %(max)s]." %
{'ver': version, 'min': versions.min_version_string(),
'max': versions.max_version_string()},
headers=headers)
"[%(min)s, %(max)s]."
% {
'ver': version,
'min': versions.min_version_string(),
'max': versions.max_version_string(),
},
headers=headers,
)
@pecan.expose()
def _route(self, args, request=None):
v = base.Version(pecan.request.headers, versions.min_version_string(),
versions.max_version_string())
v = base.Version(
pecan.request.headers,
versions.min_version_string(),
versions.max_version_string(),
)
# The Vary header is used as a hint to caching proxies and user agents
# that the response is also dependent on the OpenStack-API-Version and
@@ -124,9 +138,11 @@ class Controller(rest.RestController):
# Always set the min and max headers
pecan.response.headers[base.Version.min_api_version] = (
versions.min_version_string())
versions.min_version_string()
)
pecan.response.headers[base.Version.max_api_version] = (
versions.max_version_string())
versions.max_version_string()
)
# assert that requested version is supported
self._check_version(v, pecan.response.headers)

View File

@@ -41,6 +41,7 @@ class ARQ(base.APIBase):
This class enforces type checking and value constraints, and converts
between the internal object model and the API representation.
"""
uuid = types.uuid
"""The UUID of the ARQ"""
@@ -77,9 +78,13 @@ class ARQ(base.APIBase):
def convert_with_links(cls, obj_arq):
api_arq = cls(**obj_arq.as_dict())
api_arq.links = [
link.Link.make_link('self', pecan.request.public_url,
'accelerator_requests', api_arq.uuid)
]
link.Link.make_link(
'self',
pecan.request.public_url,
'accelerator_requests',
api_arq.uuid,
)
]
return api_arq
@@ -92,31 +97,33 @@ class ARQCollection(base.APIBase):
@classmethod
def convert_with_links(cls, obj_arqs):
collection = cls()
collection.arqs = [ARQ.convert_with_links(obj_arq)
for obj_arq in obj_arqs]
collection.arqs = [
ARQ.convert_with_links(obj_arq) for obj_arq in obj_arqs
]
return collection
class ARQsController(base.CyborgController):
"""REST controller for ARQs.
For the relationship between ARQs and device profiles, see
nova/nova/accelerator/cyborg.py.
For the relationship between ARQs and device profiles, see
nova/nova/accelerator/cyborg.py.
"""
@authorize_wsgi.authorize_wsgi("cyborg:arq", "create", False)
@expose.expose(ARQCollection, body=types.jsontype,
status_code=HTTPStatus.CREATED)
@expose.expose(
ARQCollection, body=types.jsontype, status_code=HTTPStatus.CREATED
)
def post(self, req):
"""Create one or more ARQs for a single device profile.
Request body:
{ 'device_profile_name': <string> }
Future:
{ 'device_profile_name': <string> # required
'device_profile_group_id': <integer>, # opt, default=0
'image_uuid': <glance-image-UUID>, #optional, for future
}
:param req: request body.
Request body:
{ 'device_profile_name': <string> }
Future:
{ 'device_profile_name': <string> # required
'device_profile_group_id': <integer>, # opt, default=0
'image_uuid': <glance-image-UUID>, #optional, for future
}
:param req: request body.
"""
LOG.info("[arq] post req = (%s)", req)
context = pecan.request.context
@@ -126,8 +133,8 @@ class ARQsController(base.CyborgController):
devprof = objects.DeviceProfile.get_by_name(context, dp_name)
except exception.ResourceNotFound:
raise exception.ResourceNotFound(
resource='Device Profile',
msg='with name=%s' % dp_name)
resource='Device Profile', msg='with name=%s' % dp_name
)
except Exception as e:
raise e
else:
@@ -145,8 +152,10 @@ class ARQsController(base.CyborgController):
accel_resources = [int(group.get("resources:FPGA"))] * 2
else:
accel_resources = [
int(val) for key, val in group.items()
if key.startswith('resources')]
int(val)
for key, val in group.items()
if key.startswith('resources')
]
# If/when we introduce non-accelerator resources, like
# device-local memory, the key search above needs to be
@@ -161,11 +170,13 @@ class ARQsController(base.CyborgController):
extarq_fields = {'arq': obj_arq}
obj_extarq = objects.ExtARQ(context, **extarq_fields)
new_extarq = pecan.request.conductor_api.arq_create(
context, obj_extarq, devprof.id)
context, obj_extarq, devprof.id
)
extarq_list.append(new_extarq)
ret = ARQCollection.convert_with_links(
[extarq.arq for extarq in extarq_list])
[extarq.arq for extarq in extarq_list]
)
LOG.info('[arqs] post returned: %s', ret)
return ret
@@ -182,8 +193,11 @@ class ARQsController(base.CyborgController):
def get_all(self, bind_state=None, instance=None):
"""Retrieve a list of arqs."""
# TODO(Sundar) Need to implement 'arq=uuid1,...' query parameter
LOG.info('[arqs] get_all. bind_state:(%s), instance:(%s)',
bind_state or '', instance or '')
LOG.info(
'[arqs] get_all. bind_state:(%s), instance:(%s)',
bind_state or '',
instance or '',
)
context = pecan.request.context
extarqs = objects.ExtARQ.list(context)
state_map = constants.ARQ_BIND_STATES_STATUS_MAP
@@ -193,32 +207,36 @@ class ARQsController(base.CyborgController):
# Apply instance filter before state filter.
if bind_state and bind_state != 'resolved':
raise exception.ARQBadState(
state=bind_state, uuid=None, expected=['resolved'])
state=bind_state, uuid=None, expected=['resolved']
)
if instance:
new_arqs = [arq for arq in arqs
if arq['instance_uuid'] == instance]
new_arqs = [
arq for arq in arqs if arq['instance_uuid'] == instance
]
arqs = new_arqs
if bind_state:
for arq in new_arqs:
if arq['state'] not in valid_bind_states:
# NOTE(Sundar) This should return HTTP code 423
# if any ARQ for this instance is not resolved.
LOG.warning('Some of ARQs for instance %s is not '
'resolved', instance)
LOG.warning(
'Some of ARQs for instance %s is not resolved',
instance,
)
return wsme.api.Response(
None,
status_code=HTTPStatus.LOCKED)
None, status_code=HTTPStatus.LOCKED
)
elif bind_state:
arqs = [arq for arq in arqs
if arq['state'] in valid_bind_states]
arqs = [arq for arq in arqs if arq['state'] in valid_bind_states]
ret = ARQCollection.convert_with_links(arqs)
LOG.info('[arqs:get_all] Returned: %s', ret)
return ret
@authorize_wsgi.authorize_wsgi("cyborg:arq", "delete", False)
@expose.expose(None, wtypes.text, types.uuid,
status_code=HTTPStatus.NO_CONTENT)
@expose.expose(
None, wtypes.text, types.uuid, status_code=HTTPStatus.NO_CONTENT
)
def delete(self, arqs=None, instance=None):
"""Delete one or more ARQS.
@@ -241,14 +259,16 @@ class ARQsController(base.CyborgController):
if (arqs and instance) or (not arqs and not instance):
raise exception.ObjectActionError(
action='delete',
reason='Provide either an ARQ uuid list or an instance UUID')
reason='Provide either an ARQ uuid list or an instance UUID',
)
elif arqs:
LOG.info("[arqs] delete. arqs=(%s)", arqs)
pecan.request.conductor_api.arq_delete_by_uuid(context, arqs)
else: # instance is not None
LOG.info("[arqs] delete. instance=(%s)", instance)
pecan.request.conductor_api.arq_delete_by_instance_uuid(
context, instance)
context, instance
)
def _validate_arq_patch(self, patch):
"""Validate a single patch for an ARQ.
@@ -258,31 +278,39 @@ class ARQsController(base.CyborgController):
value field of arq_uuid in patch() method below.
:returns: dict of valid fields
"""
valid_fields = {'hostname': None,
'device_rp_uuid': None,
'instance_uuid': None}
valid_fields = {
'hostname': None,
'device_rp_uuid': None,
'instance_uuid': None,
}
if utils.allow_project_id():
valid_fields['project_id'] = None
if ((not all(p['op'] == 'add' for p in patch)) and
(not all(p['op'] == 'remove' for p in patch))):
raise exception.PatchError(
reason='Every op must be add or remove')
if (not all(p['op'] == 'add' for p in patch)) and (
not all(p['op'] == 'remove' for p in patch)
):
raise exception.PatchError(reason='Every op must be add or remove')
for p in patch:
path = p['path'].lstrip('/')
if path == 'project_id' and not utils.allow_project_id():
raise exception.NotAcceptable(_(
"Request not acceptable. The minimal required API "
"version should be %(base)s.%(opr)s") %
{'base': versions.BASE_VERSION,
'opr': versions.MINOR_1_PROJECT_ID})
raise exception.NotAcceptable(
_(
"Request not acceptable. The minimal required API "
"version should be %(base)s.%(opr)s"
)
% {
'base': versions.BASE_VERSION,
'opr': versions.MINOR_1_PROJECT_ID,
}
)
if path not in valid_fields.keys():
reason = 'Invalid path in patch {}'.format(p['path'])
raise exception.PatchError(reason=reason)
if p['op'] == 'add':
valid_fields[path] = p['value']
not_found = [field for field, value in valid_fields.items()
if value is None]
not_found = [
field for field, value in valid_fields.items() if value is None
]
if patch[0]['op'] == 'add' and len(not_found) > 0:
msg = ','.join(not_found)
reason = _('Fields absent in patch {}').format(msg)
@@ -296,17 +324,20 @@ class ARQsController(base.CyborgController):
instance_uuid = patch_fields['instance_uuid']
extarqs = objects.ExtARQ.list(context)
extarqs_for_instance = [
extarq for extarq in extarqs
if extarq.arq['instance_uuid'] == instance_uuid]
extarq
for extarq in extarqs
if extarq.arq['instance_uuid'] == instance_uuid
]
if extarqs_for_instance: # duplicate binding request
msg = _('Instance {} already has accelerator requests. '
'Cannot bind additional ARQs.')
msg = _(
'Instance {} already has accelerator requests. '
'Cannot bind additional ARQs.'
)
reason = msg.format(instance_uuid)
raise exception.PatchError(reason=reason)
@authorize_wsgi.authorize_wsgi("cyborg:arq", "update", False)
@expose.expose(None, body=types.jsontype,
status_code=HTTPStatus.ACCEPTED)
@expose.expose(None, body=types.jsontype, status_code=HTTPStatus.ACCEPTED)
def patch(self, patch_list):
"""Bind/Unbind one or more ARQs.
@@ -345,4 +376,5 @@ class ARQsController(base.CyborgController):
self._check_if_already_bound(context, valid_fields)
pecan.request.conductor_api.arq_apply_patch(
context, patch_list, valid_fields)
context, patch_list, valid_fields
)

View File

@@ -26,6 +26,7 @@ from cyborg.api.controllers import types
from cyborg.api import expose
from cyborg.common import authorize_wsgi
from cyborg import objects
LOG = log.getLogger(__name__)
@@ -66,9 +67,13 @@ class Attribute(base.APIBase):
def convert_with_links(cls, obj_attribute):
api_attribute = cls(**obj_attribute.as_dict())
api_attribute.links = [
link.Link.make_link('self', pecan.request.public_url,
'attributes', api_attribute.uuid)
]
link.Link.make_link(
'self',
pecan.request.public_url,
'attributes',
api_attribute.uuid,
)
]
return api_attribute
def get_attribute(self, obj_attribute):
@@ -79,7 +84,7 @@ class Attribute(base.APIBase):
api_obj[field] = str(obj_attribute[field])
api_obj['links'] = [
link.Link.make_link_dict('attributes', api_obj['uuid'])
]
]
return api_obj
@@ -94,26 +99,30 @@ class AttributeCollection(Attribute):
collection = cls()
collection.attributes = [
Attribute.convert_with_links(obj_attribute)
for obj_attribute in obj_attributes]
for obj_attribute in obj_attributes
]
return collection
def get_attributes(self, obj_attributes):
api_obj_attributes = [
self.get_attribute(obj_attribute)
for obj_attribute in obj_attributes]
for obj_attribute in obj_attributes
]
return api_obj_attributes
class AttributesController(base.CyborgController,
AttributeCollection):
class AttributesController(base.CyborgController, AttributeCollection):
"""REST controller for Attributes."""
@authorize_wsgi.authorize_wsgi("cyborg:attribute", "get_all", False)
@expose.expose(AttributeCollection, wtypes.IntegerType(), wtypes.text)
def get_all(self, deployable_id=None, key=None):
"""Retrieve a list of attributes."""
LOG.info('[attributes] get_all by deployable_id:(%s) and key:(%s).',
deployable_id, key)
LOG.info(
'[attributes] get_all by deployable_id:(%s) and key:(%s).',
deployable_id,
key,
)
search_opts = {}
if deployable_id:
search_opts['deployable_id'] = deployable_id
@@ -137,8 +146,9 @@ class AttributesController(base.CyborgController,
return ret
@authorize_wsgi.authorize_wsgi("cyborg:attribute", "create", False)
@expose.expose(Attribute, body=types.jsontype,
status_code=HTTPStatus.CREATED)
@expose.expose(
Attribute, body=types.jsontype, status_code=HTTPStatus.CREATED
)
def post(self, req_attr):
"""Create one attribute.
:param req_attr: attribute value.
@@ -159,7 +169,7 @@ class AttributesController(base.CyborgController,
@expose.expose(None, wtypes.text, status_code=HTTPStatus.NO_CONTENT)
def delete(self, uuid):
"""Delete one attribute.
- UUID of a attribute.
- UUID of a attribute.
"""
LOG.info('[attributes] delete by uuid: %s.', uuid)
context = pecan.request.context

View File

@@ -82,12 +82,14 @@ class Deployable(base.APIBase):
url = pecan.request.public_url
api_dep.links = [
link.Link.make_link('self', url, 'deployables', api_dep.uuid),
link.Link.make_link('bookmark', url, 'deployables', api_dep.uuid,
bookmark=True)
]
link.Link.make_link(
'bookmark', url, 'deployables', api_dep.uuid, bookmark=True
),
]
query = {"deployable_id": obj_dep.id}
attr_get_list = objects.Attribute.get_by_filter(pecan.request.context,
query)
attr_get_list = objects.Attribute.get_by_filter(
pecan.request.context, query
)
attributes_list = []
for exist_attr in attr_get_list:
attributes_list.append({exist_attr.key: exist_attr.value})
@@ -104,12 +106,12 @@ class DeployableCollection(Deployable):
def convert_with_links(self, obj_deps):
collection = DeployableCollection()
collection.deployables = [
self.convert_with_link(obj_dep) for obj_dep in obj_deps]
self.convert_with_link(obj_dep) for obj_dep in obj_deps
]
return collection
class DeployablePatchType(types.JsonPatchType):
_api_base = Deployable
@staticmethod
@@ -118,8 +120,7 @@ class DeployablePatchType(types.JsonPatchType):
return defaults + ['/name', '/num_accelerators']
class DeployablesController(base.CyborgController,
DeployableCollection):
class DeployablesController(base.CyborgController, DeployableCollection):
"""REST controller for Deployables."""
_custom_actions = {'program': ['PATCH']}
@@ -148,15 +149,15 @@ class DeployablesController(base.CyborgController,
obj_dep = objects.Deployable.get(pecan.request.context, uuid)
obj_dev = objects.Device.get_by_device_id(
pecan.request.context,
obj_dep.device_id
pecan.request.context, obj_dep.device_id
)
hostname = obj_dev.hostname
driver_name = obj_dep.driver_name
cpid_list = obj_dep.get_cpid_list(pecan.request.context)
controlpath_id = cpid_list[0]
controlpath_id['cpid_info'] = jsonutils.loads(
cpid_list[0]['cpid_info'])
cpid_list[0]['cpid_info']
)
self.agent_rpcapi = AgentAPI()
ret = self.agent_rpcapi.fpga_program(
pecan.request.context,
@@ -164,7 +165,7 @@ class DeployablesController(base.CyborgController,
controlpath_id,
image_uuid,
driver_name,
)
)
if ret:
return self.convert_with_link(obj_dep)
else:

View File

@@ -51,6 +51,7 @@ from cyborg.common import constants
from cyborg.common import exception
from cyborg.common.i18n import _
from cyborg import objects
LOG = log.getLogger(__name__)
@@ -91,9 +92,13 @@ class DeviceProfile(base.APIBase):
def convert_with_links(cls, obj_devprof):
api_devprof = cls(**obj_devprof.as_dict())
api_devprof.links = [
link.Link.make_link('self', pecan.request.public_url,
'device_profiles', api_devprof.uuid)
]
link.Link.make_link(
'self',
pecan.request.public_url,
'device_profiles',
api_devprof.uuid,
)
]
return api_devprof
def get_device_profile(self, obj_devprof):
@@ -104,7 +109,7 @@ class DeviceProfile(base.APIBase):
api_obj[field] = str(obj_devprof[field])
api_obj['links'] = [
link.Link.make_link_dict('device_profiles', api_obj['uuid'])
]
]
return api_obj
@@ -119,23 +124,25 @@ class DeviceProfileCollection(DeviceProfile):
collection = cls()
collection.device_profiles = [
DeviceProfile.convert_with_links(obj_devprof)
for obj_devprof in obj_devprofs]
for obj_devprof in obj_devprofs
]
return collection
def get_device_profiles(self, obj_devprofs):
api_obj_devprofs = [
self.get_device_profile(obj_devprof)
for obj_devprof in obj_devprofs]
for obj_devprof in obj_devprofs
]
return api_obj_devprofs
class DeviceProfilesController(base.CyborgController,
DeviceProfileCollection):
class DeviceProfilesController(base.CyborgController, DeviceProfileCollection):
"""REST controller for Device Profiles."""
@authorize_wsgi.authorize_wsgi("cyborg:device_profile", "create", False)
@expose.expose(DeviceProfile, body=types.jsontype,
status_code=HTTPStatus.CREATED)
@expose.expose(
DeviceProfile, body=types.jsontype, status_code=HTTPStatus.CREATED
)
def post(self, req_devprof_list):
"""Create one or more device_profiles.
@@ -154,8 +161,8 @@ class DeviceProfilesController(base.CyborgController,
LOG.info("[device_profiles] POST request = (%s)", req_devprof_list)
if len(req_devprof_list) != 1:
raise exception.InvalidParameterValue(
err="Only one device profile allowed "
"per POST request for now.")
err="Only one device profile allowed per POST request for now."
)
req_devprof = req_devprof_list[0]
self._validate_post_request(req_devprof)
@@ -163,7 +170,8 @@ class DeviceProfilesController(base.CyborgController,
obj_devprof = objects.DeviceProfile(context, **req_devprof)
new_devprof = pecan.request.conductor_api.device_profile_create(
context, obj_devprof)
context, obj_devprof
)
return DeviceProfile.convert_with_links(new_devprof)
def _validate_post_request(self, req_devprof):
@@ -177,7 +185,8 @@ class DeviceProfilesController(base.CyborgController,
raise exception.DeviceProfileNameNeeded()
elif not re.match(NAME, name):
raise exception.InvalidParameterValue(
err="Device profile name must be of the form %s" % NAME)
err="Device profile name must be of the form %s" % NAME
)
groups = req_devprof.get("groups")
if not groups:
@@ -190,7 +199,8 @@ class DeviceProfilesController(base.CyborgController,
if not re.match(GROUP_KEYS, key):
raise exception.InvalidParameterValue(
err="Device profile group keys must be of"
" the form %s" % GROUP_KEYS)
" the form %s" % GROUP_KEYS
)
# check trait name and it's value
if key.startswith("trait:"):
inner_origin_trait = ":".join(key.split(":")[1:])
@@ -198,11 +208,13 @@ class DeviceProfilesController(base.CyborgController,
if not inner_trait.startswith('CUSTOM_'):
raise exception.InvalidParameterValue(
err="Unsupported trait name format %s, should "
"start with CUSTOM_" % inner_trait)
"start with CUSTOM_" % inner_trait
)
if value not in TRAIT_VALUES:
raise exception.InvalidParameterValue(
err="Unsupported trait value %s, the value must"
" be one among %s" % (value, TRAIT_VALUES))
" be one among %s" % (value, TRAIT_VALUES)
)
# strip " " and update old group key.
if inner_origin_trait != inner_trait:
del group[key]
@@ -212,15 +224,19 @@ class DeviceProfilesController(base.CyborgController,
if key.startswith("resources:"):
inner_origin_rc = ":".join(key.split(":")[1:])
inner_rc = inner_origin_rc.strip(" ")
if inner_rc not in constants.SUPPORT_RESOURCES and \
not inner_rc.startswith('CUSTOM_'):
if (
inner_rc not in constants.SUPPORT_RESOURCES
and not inner_rc.startswith('CUSTOM_')
):
raise exception.InvalidParameterValue(
err="Unsupported resource class %s" % inner_rc)
err="Unsupported resource class %s" % inner_rc
)
try:
int(value)
except ValueError:
raise exception.InvalidParameterValue(
err="Resources number %s is invalid" % value)
err="Resources number %s is invalid" % value
)
# strip " " and update old group key.
if inner_origin_rc != inner_rc:
del group[key]
@@ -233,12 +249,14 @@ class DeviceProfilesController(base.CyborgController,
context = pecan.request.context
obj_devprofs = objects.DeviceProfile.list(context)
if names:
new_obj_devprofs = [devprof for devprof in obj_devprofs
if devprof['name'] in names]
new_obj_devprofs = [
devprof for devprof in obj_devprofs if devprof['name'] in names
]
obj_devprofs = new_obj_devprofs
elif uuid is not None:
new_obj_devprofs = [devprof for devprof in obj_devprofs
if devprof['uuid'] == uuid]
new_obj_devprofs = [
devprof for devprof in obj_devprofs if devprof['uuid'] == uuid
]
obj_devprofs = new_obj_devprofs
return obj_devprofs
@@ -265,32 +283,39 @@ class DeviceProfilesController(base.CyborgController,
context = pecan.request.context
if uuidutils.is_uuid_like(dp_uuid_or_name):
LOG.info('[device_profiles] get_one. uuid=%s', dp_uuid_or_name)
obj_devprof = objects.DeviceProfile.get_by_uuid(context,
dp_uuid_or_name)
obj_devprof = objects.DeviceProfile.get_by_uuid(
context, dp_uuid_or_name
)
else:
if api.request.version.minor >= versions.MINOR_2_DP_BY_NAME:
LOG.info('[device_profiles] get_one. name=%s', dp_uuid_or_name)
obj_devprof = \
objects.DeviceProfile.get_by_name(context,
dp_uuid_or_name)
obj_devprof = objects.DeviceProfile.get_by_name(
context, dp_uuid_or_name
)
else:
raise exception.NotAcceptable(_(
"Request not acceptable. The minimal required API "
"version should be %(base)s.%(opr)s") %
{'base': versions.BASE_VERSION,
'opr': versions.MINOR_2_DP_BY_NAME})
raise exception.NotAcceptable(
_(
"Request not acceptable. The minimal required API "
"version should be %(base)s.%(opr)s"
)
% {
'base': versions.BASE_VERSION,
'opr': versions.MINOR_2_DP_BY_NAME,
}
)
if not obj_devprof:
LOG.warning("Device profile with %s not found!", dp_uuid_or_name)
raise exception.ResourceNotFound(
resource='Device profile',
msg='with %s' % dp_uuid_or_name)
resource='Device profile', msg='with %s' % dp_uuid_or_name
)
api_obj_devprof = self.get_device_profile(obj_devprof)
ret = {"device_profile": api_obj_devprof}
LOG.info('[device_profiles] get_one returned: %s', ret)
# TODO(Sundar) Replace this with convert_with_links()
return wsme.api.Response(ret, status_code=HTTPStatus.OK,
return_type=wsme.types.DictType)
return wsme.api.Response(
ret, status_code=HTTPStatus.OK, return_type=wsme.types.DictType
)
@authorize_wsgi.authorize_wsgi("cyborg:device_profile", "delete")
@expose.expose(None, wtypes.text, status_code=HTTPStatus.NO_CONTENT)
@@ -309,11 +334,13 @@ class DeviceProfilesController(base.CyborgController,
LOG.info('[device_profiles] delete uuid=%s', uuid)
obj_devprof = objects.DeviceProfile.get_by_uuid(context, uuid)
pecan.request.conductor_api.device_profile_delete(
context, obj_devprof)
context, obj_devprof
)
else:
names = value.split(",")
LOG.info('[device_profiles] delete names=(%s)', names)
for name in names:
obj_devprof = objects.DeviceProfile.get_by_name(context, name)
pecan.request.conductor_api.device_profile_delete(
context, obj_devprof)
context, obj_devprof
)

View File

@@ -41,6 +41,7 @@ class Device(base.APIBase):
This class enforces type checking and value constraints, and converts
between the internal object model and the API representation.
"""
uuid = types.uuid
"""The UUID of the device"""
@@ -82,9 +83,10 @@ class Device(base.APIBase):
if not api.request.version.minor >= versions.MINOR_3_DEVICE_STATUS:
delattr(api_device, 'status')
api_device.links = [
link.Link.make_link('self', pecan.request.public_url,
'devices', api_device.uuid)
]
link.Link.make_link(
'self', pecan.request.public_url, 'devices', api_device.uuid
)
]
return api_device
@@ -97,8 +99,9 @@ class DeviceCollection(base.APIBase):
@classmethod
def convert_with_links(cls, devices):
collection = cls()
collection.devices = [Device.convert_with_links(device)
for device in devices]
collection.devices = [
Device.convert_with_links(device) for device in devices
]
return collection
@@ -118,8 +121,13 @@ class DevicesController(base.CyborgController):
return Device.convert_with_links(device)
@authorize_wsgi.authorize_wsgi("cyborg:device", "get_all", False)
@expose.expose(DeviceCollection, wtypes.text, wtypes.text, wtypes.text,
wtypes.ArrayType(types.FilterType))
@expose.expose(
DeviceCollection,
wtypes.text,
wtypes.text,
wtypes.text,
wtypes.ArrayType(types.FilterType),
)
def get_all(self, type=None, vendor=None, hostname=None, filters=None):
"""Retrieve a list of devices.
:param type: type of a device.
@@ -144,8 +152,7 @@ class DevicesController(base.CyborgController):
return DeviceCollection.convert_with_links(obj_devices)
@authorize_wsgi.authorize_wsgi("cyborg:device", "disable")
@expose.expose(None, wtypes.text, types.uuid,
status_code=HTTPStatus.OK)
@expose.expose(None, wtypes.text, types.uuid, status_code=HTTPStatus.OK)
def disable(self, uuid):
context = pecan.request.context
device = objects.Device.get(context, uuid)
@@ -161,15 +168,17 @@ class DevicesController(base.CyborgController):
else:
raise exception.ResourceNotFound(
resource='Attribute',
msg='with deployable_id=%s,key=%s' % (deployable.id, 'rc'))
msg='with deployable_id=%s,key=%s' % (deployable.id, 'rc'),
)
client.update_rp_inventory_reserved(
deployable.rp_uuid, att_type,
deployable.rp_uuid,
att_type,
deployable.num_accelerators,
deployable.num_accelerators)
deployable.num_accelerators,
)
@authorize_wsgi.authorize_wsgi("cyborg:device", "enable")
@expose.expose(None, wtypes.text, types.uuid,
status_code=HTTPStatus.OK)
@expose.expose(None, wtypes.text, types.uuid, status_code=HTTPStatus.OK)
def enable(self, uuid):
context = pecan.request.context
device = objects.Device.get(context, uuid)
@@ -185,8 +194,8 @@ class DevicesController(base.CyborgController):
else:
raise exception.ResourceNotFound(
resource='Attribute',
msg='with deployable_id=%s,key=%s' % (deployable.id, 'rc'))
msg='with deployable_id=%s,key=%s' % (deployable.id, 'rc'),
)
client.update_rp_inventory_reserved(
deployable.rp_uuid, att_type,
deployable.num_accelerators,
0)
deployable.rp_uuid, att_type, deployable.num_accelerators, 0
)

View File

@@ -38,7 +38,8 @@ class PublicUrlHook(hooks.PecanHook):
def before(self, state):
state.request.public_url = (
cfg.CONF.api.public_endpoint or state.request.host_url)
cfg.CONF.api.public_endpoint or state.request.host_url
)
class ConductorAPIHook(hooks.PecanHook):
@@ -92,10 +93,11 @@ class ContextHook(hooks.PecanHook):
user_auth_plugin = req.environ.get('keystone.token_auth')
roles = req.headers.get('X-Roles', '').split(',')
is_admin = ('admin' in roles or 'administrator' in roles)
is_admin = 'admin' in roles or 'administrator' in roles
state.request.context = context.RequestContext.from_environ(
req.environ,
user_auth_plugin=user_auth_plugin,
is_admin=is_admin,
service_catalog=service_catalog)
service_catalog=service_catalog,
)

View File

@@ -20,5 +20,4 @@ from cyborg.api.middleware import parsable_error
ParsableErrorMiddleware = parsable_error.ParsableErrorMiddleware
AuthTokenMiddleware = auth_token.AuthTokenMiddleware
__all__ = ('ParsableErrorMiddleware',
'AuthTokenMiddleware')
__all__ = ('ParsableErrorMiddleware', 'AuthTokenMiddleware')

View File

@@ -40,8 +40,10 @@ class AuthTokenMiddleware(auth_token.AuthProtocol):
route_pattern_tpl = r'%s(\.json)?$'
try:
self.public_api_routes = [re.compile(route_pattern_tpl % route_tpl)
for route_tpl in public_api_routes]
self.public_api_routes = [
re.compile(route_pattern_tpl % route_tpl)
for route_tpl in public_api_routes
]
except re.error as e:
msg = _('Cannot compile public API routes: %s') % e
@@ -56,8 +58,11 @@ class AuthTokenMiddleware(auth_token.AuthProtocol):
# The information whether the API call is being performed against the
# public API is required for some other components. Saving it to the
# WSGI environment is reasonable thereby.
env['is_public_api'] = any(map(lambda pattern: re.match(pattern, path),
self.public_api_routes))
env['is_public_api'] = any(
map(
lambda pattern: re.match(pattern, path), self.public_api_routes
)
)
if env['is_public_api']:
return self.app(env, start_response)

View File

@@ -42,15 +42,18 @@ class ParsableErrorMiddleware:
except (ValueError, TypeError): # pragma: nocover
raise Exception(
'ParsableErrorMiddleware received an invalid '
'status %s' % status)
'status %s' % status
)
if (state['status_code'] // 100) not in (2, 3):
# Remove some headers so we can replace them later
# when we have the full error message and can
# compute the length.
headers = [
(h, v) for (h, v) in headers
if h not in ('Content-Length', 'Content-Type')]
(h, v)
for (h, v) in headers
if h not in ('Content-Length', 'Content-Type')
]
# Save the headers in case we need to modify them.
state['headers'] = headers

View File

@@ -31,9 +31,9 @@ def main():
cyborg_service.prepare_service(sys.argv)
priv_context.init(root_helper=shlex.split('sudo'))
mgr = cyborg_service.RPCService('cyborg.agent.manager',
'AgentManager',
constants.AGENT_TOPIC)
mgr = cyborg_service.RPCService(
'cyborg.agent.manager', 'AgentManager', constants.AGENT_TOPIC
)
launcher = service.launch(CONF, mgr, restart_method='mutate')
launcher.wait()

View File

@@ -31,9 +31,11 @@ def main():
# Parse config file and command line options, then start logging
cyborg_service.prepare_service(sys.argv)
mgr = cyborg_service.RPCService('cyborg.conductor.manager',
'ConductorManager',
constants.CONDUCTOR_TOPIC)
mgr = cyborg_service.RPCService(
'cyborg.conductor.manager',
'ConductorManager',
constants.CONDUCTOR_TOPIC,
)
launcher = service.launch(CONF, mgr, restart_method='mutate')
launcher.wait()

View File

@@ -28,7 +28,6 @@ from cyborg.db import migration
class DBCommand:
def upgrade(self):
migration.upgrade(CONF.command.revision)
@@ -50,16 +49,22 @@ def add_command_parsers(subparsers):
parser = subparsers.add_parser(
'upgrade',
help=_("Upgrade the database schema to the latest version. "
"Optionally, use --revision to specify an alembic revision "
"string to upgrade to."))
help=_(
"Upgrade the database schema to the latest version. "
"Optionally, use --revision to specify an alembic revision "
"string to upgrade to."
),
)
parser.set_defaults(func=command_object.upgrade)
parser.add_argument('--revision', nargs='?')
parser = subparsers.add_parser(
'revision',
help=_("Create a new alembic revision. "
"Use --message to set the message string."))
help=_(
"Create a new alembic revision. "
"Use --message to set the message string."
),
)
parser.set_defaults(func=command_object.revision)
parser.add_argument('-m', '--message')
parser.add_argument('--autogenerate', action='store_true')
@@ -69,21 +74,23 @@ def add_command_parsers(subparsers):
parser.add_argument('--revision', nargs='?')
parser = subparsers.add_parser(
'version',
help=_("Print the current version information and exit."))
'version', help=_("Print the current version information and exit.")
)
parser.set_defaults(func=command_object.version)
parser = subparsers.add_parser(
'create_schema',
help=_("Create the database schema."))
'create_schema', help=_("Create the database schema.")
)
parser.set_defaults(func=command_object.create_schema)
def main():
command_opt = cfg.SubCommandOpt('command',
title='Command',
help=_('Available commands'),
handler=add_command_parsers)
command_opt = cfg.SubCommandOpt(
'command',
title='Command',
help=_('Available commands'),
handler=add_command_parsers,
)
CONF.register_cli_opt(command_opt)

View File

@@ -25,21 +25,22 @@ CONF = cfg.CONF
class Checks(upgradecheck.UpgradeCommands):
"""Various upgrade checks should be added as separate methods in this class
and added to _upgrade_checks tuple.
"""
def _check_policy_json(self):
"Checks to see if policy file is JSON-formatted policy file."
msg = _("Your policy file is JSON-formatted which is "
"deprecated since Victoria release (Cyborg 5.0.0). "
"You need to switch to YAML-formatted file. You can use the "
"``oslopolicy-convert-json-to-yaml`` tool to convert existing "
"JSON-formatted files to YAML-formatted files in a "
"backwards-compatible manner: "
"https://docs.openstack.org/oslo.policy/"
"latest/cli/oslopolicy-convert-json-to-yaml.html.")
msg = _(
"Your policy file is JSON-formatted which is "
"deprecated since Victoria release (Cyborg 5.0.0). "
"You need to switch to YAML-formatted file. You can use the "
"``oslopolicy-convert-json-to-yaml`` tool to convert existing "
"JSON-formatted files to YAML-formatted files in a "
"backwards-compatible manner: "
"https://docs.openstack.org/oslo.policy/"
"latest/cli/oslopolicy-convert-json-to-yaml.html."
)
status = upgradecheck.Result(upgradecheck.Code.SUCCESS)
# NOTE(gmann): Check if policy file exist and is in
# JSON format by actually loading the file not just
@@ -64,7 +65,8 @@ class Checks(upgradecheck.UpgradeCommands):
def main():
return upgradecheck.main(
cfg.CONF, project='cyborg', upgrade_command=Checks())
cfg.CONF, project='cyborg', upgrade_command=Checks()
)
if __name__ == '__main__':

View File

@@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
"""Policy Authorize Engine For Cyborg."""
import functools
import sys
@@ -31,9 +32,13 @@ LOG = log.getLogger(__name__)
@lockutils.synchronized('policy_enforcer', 'cyborg-')
def init_enforcer(policy_file=None, rules=None,
default_rule=None, use_conf=True,
suppress_deprecation_warnings=False):
def init_enforcer(
policy_file=None,
rules=None,
default_rule=None,
use_conf=True,
suppress_deprecation_warnings=False,
):
"""Synchronously initializes the policy enforcer
:param policy_file: Custom policy file to use, if none is specified,
`CONF.oslo_policy.policy_file` will be used.
@@ -60,7 +65,8 @@ def init_enforcer(policy_file=None, rules=None,
policy_file=policy_file,
rules=rules,
default_rule=default_rule,
use_conf=use_conf)
use_conf=use_conf,
)
if suppress_deprecation_warnings:
_ENFORCER.suppress_deprecation_warnings = True
_ENFORCER.register_defaults(policies.list_policies())
@@ -87,8 +93,9 @@ def authorize(rule, target, creds, do_raise=False, *args, **kwargs):
"""
enforcer = get_enforcer()
try:
return enforcer.authorize(rule, target, creds, do_raise=do_raise,
*args, **kwargs)
return enforcer.authorize(
rule, target, creds, do_raise=do_raise, *args, **kwargs
)
except policy.PolicyNotAuthorized:
raise exception.HTTPForbidden(resource=rule)
@@ -112,6 +119,7 @@ def authorize_wsgi(api_name, act=None, need_target=True):
def post(self, values):
...
"""
def wrapper(fn):
action = '%s:%s' % (api_name, act or fn.__name__)
@@ -124,8 +132,7 @@ def authorize_wsgi(api_name, act=None, need_target=True):
orig_code = getattr(orig_exception, 'code', None)
pecan.response.status = orig_code or resp_status
data = wsme.api.format_exception(
exception_info,
pecan.conf.get('wsme', {}).get('debug', False)
exception_info, pecan.conf.get('wsme', {}).get('debug', False)
)
del exception_info
return data
@@ -145,10 +152,13 @@ def authorize_wsgi(api_name, act=None, need_target=True):
# just support object, other type will just keep target as
# empty, then follow authorize method will fail and throw
# an exception
if isinstance(resource,
object_base.VersionedObjectDictCompat):
target = {'project_id': resource.project_id,
'user_id': resource.user_id}
if isinstance(
resource, object_base.VersionedObjectDictCompat
):
target = {
'project_id': resource.project_id,
'user_id': resource.user_id,
}
except Exception:
return return_error(500)
elif need_target:
@@ -159,8 +169,10 @@ def authorize_wsgi(api_name, act=None, need_target=True):
else:
# for create method, before resource exsites, we can check the
# the credentials with itself.
target = {'project_id': context.project_id,
'user_id': context.user_id}
target = {
'project_id': context.project_id,
'user_id': context.user_id,
}
try:
authorize(action, target, credentials, do_raise=True)

View File

@@ -22,8 +22,10 @@ from cyborg import version
def parse_args(argv, default_config_files=None):
rpc.set_defaults(control_exchange='cyborg')
version_string = version.version_info.release_string()
cfg.CONF(argv[1:],
project='cyborg',
version=version_string,
default_config_files=default_config_files)
cfg.CONF(
argv[1:],
project='cyborg',
version=version_string,
default_config_files=default_config_files,
)
rpc.init(cfg.CONF)

View File

@@ -24,27 +24,42 @@ DEVICE_NIC = 'NIC'
DEVICE_SSD = 'SSD'
ARQ_STATES = (ARQ_INITIAL, ARQ_BIND_STARTED, ARQ_BOUND, ARQ_UNBOUND,
ARQ_BIND_FAILED, ARQ_UNBIND_FAILED, ARQ_DELETING) = (
'Initial', 'BindStarted', 'Bound', 'Unbound', 'BindFailed', 'UnbindFailed',
'Deleting')
ARQ_STATES = (
ARQ_INITIAL,
ARQ_BIND_STARTED,
ARQ_BOUND,
ARQ_UNBOUND,
ARQ_BIND_FAILED,
ARQ_UNBIND_FAILED,
ARQ_DELETING,
) = (
'Initial',
'BindStarted',
'Bound',
'Unbound',
'BindFailed',
'UnbindFailed',
'Deleting',
)
ARQ_BIND_STAGE = (ARQ_PRE_BIND, ARQ_FINISH_BIND,
ARQ_OUFOF_BIND_FLOW) = (
ARQ_BIND_STAGE = (ARQ_PRE_BIND, ARQ_FINISH_BIND, ARQ_OUFOF_BIND_FLOW) = (
[ARQ_INITIAL, ARQ_BIND_STARTED],
[ARQ_BOUND, ARQ_BIND_FAILED],
[ARQ_UNBOUND, ARQ_DELETING])
[ARQ_UNBOUND, ARQ_DELETING],
)
ARQ_BIND_STATUS = (ARQ_BIND_STATUS_FINISH, ARQ_BIND_STATUS_FAILED) = (
"completed", "failed")
"completed",
"failed",
)
ARQ_BIND_STATES_STATUS_MAP = {
ARQ_BOUND: ARQ_BIND_STATUS_FINISH,
ARQ_BIND_FAILED: ARQ_BIND_STATUS_FAILED,
ARQ_DELETING: ARQ_BIND_STATUS_FAILED
ARQ_DELETING: ARQ_BIND_STATUS_FAILED,
}
# TODO(Shaohe): maybe we can use oslo automaton lib
@@ -56,14 +71,25 @@ ARQ_STATES_TRANSFORM_MATRIX = {
ARQ_BOUND: [ARQ_BIND_STARTED],
ARQ_UNBOUND: [ARQ_INITIAL, ARQ_BIND_STARTED, ARQ_BOUND, ARQ_BIND_FAILED],
ARQ_BIND_FAILED: [ARQ_BIND_STARTED, ARQ_BOUND],
ARQ_DELETING: [ARQ_INITIAL, ARQ_BIND_STARTED, ARQ_BOUND,
ARQ_UNBOUND, ARQ_BIND_FAILED]
ARQ_DELETING: [
ARQ_INITIAL,
ARQ_BIND_STARTED,
ARQ_BOUND,
ARQ_UNBOUND,
ARQ_BIND_FAILED,
],
}
# Device type
DEVICE_TYPE = (DEVICE_GPU, DEVICE_FPGA, DEVICE_AICHIP, DEVICE_QAT, DEVICE_NIC,
DEVICE_SSD)
DEVICE_TYPE = (
DEVICE_GPU,
DEVICE_FPGA,
DEVICE_AICHIP,
DEVICE_QAT,
DEVICE_NIC,
DEVICE_SSD,
)
# Device type
@@ -73,11 +99,14 @@ DEVICE_STATUS = ("enabled", "maintaining")
# Attach handle type
# 'TEST_PCI': used by fake driver, ignored by Nova virt driver.
ATTACH_HANDLE_TYPES = (AH_TYPE_PCI, AH_TYPE_MDEV, AH_TYPE_TEST_PCI) = (
"PCI", "MDEV", "TEST_PCI")
"PCI",
"MDEV",
"TEST_PCI",
)
# Control Path ID type
CPID_TYPE = (CPID_TYPE_PCI) = ("PCI")
CPID_TYPE = CPID_TYPE_PCI = "PCI"
# Resource Class
@@ -92,26 +121,24 @@ RESOURCES = {
}
ACCEL_SPECS = (
ACCEL_BITSTREAM_ID,
ACCEL_FUNCTION_ID
) = (
ACCEL_SPECS = (ACCEL_BITSTREAM_ID, ACCEL_FUNCTION_ID) = (
"accel:bitstream_id",
"accel:function_id"
"accel:function_id",
)
SUPPORT_RESOURCES = (
FPGA, GPU, VGPU, PGPU, QAT, NIC, SSD) = (
"FPGA", "GPU", "VGPU", "PGPU", "CUSTOM_QAT", "CUSTOM_NIC", "CUSTOM_SSD"
SUPPORT_RESOURCES = (FPGA, GPU, VGPU, PGPU, QAT, NIC, SSD) = (
"FPGA",
"GPU",
"VGPU",
"PGPU",
"CUSTOM_QAT",
"CUSTOM_NIC",
"CUSTOM_SSD",
)
FPGA_TRAITS = (
FPGA_FUNCTION_ID,
) = (
"CUSTOM_FPGA_FUNCTION_ID",
)
FPGA_TRAITS = (FPGA_FUNCTION_ID,) = ("CUSTOM_FPGA_FUNCTION_ID",)
RESOURCES_PREFIX = "resources:"

View File

@@ -39,6 +39,7 @@ class CyborgException(Exception):
If you need to access the message from an exception you should use
str(exc).
"""
_msg_fmt = _("An unknown exception occurred.")
code = HTTPStatus.INTERNAL_SERVER_ERROR
headers = {}
@@ -61,8 +62,9 @@ class CyborgException(Exception):
# log the issue and the kwargs
LOG.exception('Exception in string format operation')
for name, value in kwargs.items():
LOG.error("%(name)s: %(value)s",
{"name": name, "value": value})
LOG.error(
"%(name)s: %(value)s", {"name": name, "value": value}
)
if CONF.fatal_exception_format_errors:
raise
@@ -84,8 +86,10 @@ class Forbidden(CyborgException):
class ARQBadState(CyborgException):
_msg_fmt = _('Bad state: %(state)s for ARQ: %(uuid)s. '
'Expected state(s): %(expected)s')
_msg_fmt = _(
'Bad state: %(state)s for ARQ: %(uuid)s. '
'Expected state(s): %(expected)s'
)
class AttachHandleAlreadyExists(CyborgException):
@@ -129,8 +133,7 @@ class ExtArqAlreadyExists(CyborgException):
class ExpectedOneObject(CyborgException):
_msg_fmt = _("Expected one object of type %(obj)s "
"but got %(count)s.")
_msg_fmt = _("Expected one object of type %(obj)s but got %(count)s.")
class InUse(CyborgException):
@@ -155,14 +158,17 @@ class InvalidJsonType(Invalid):
class InvalidAPIVersionString(Invalid):
_msg_fmt = _("API Version String %(version)s is of invalid format. Must "
"be of format MajorNum.MinorNum.")
_msg_fmt = _(
"API Version String %(version)s is of invalid format. Must "
"be of format MajorNum.MinorNum."
)
# TODO(All): Consider whether Placement/Image exceptions can be included here.
class InvalidAPIResponse(Invalid):
_msg_fmt = _('Bad API response from %(service)s for %(api)s API. '
'Details: %(msg)s')
_msg_fmt = _(
'Bad API response from %(service)s for %(api)s API. Details: %(msg)s'
)
# Cannot be templated as the error syntax varies.
@@ -203,8 +209,9 @@ class ServiceNotFound(NotFound):
class ConfGroupForServiceTypeNotFound(ServiceNotFound):
_msg_fmt = _("No conf group name could be found for service type "
"%(stype)s.")
_msg_fmt = _(
"No conf group name could be found for service type %(stype)s."
)
class InvalidDeployType(CyborgException):
@@ -233,18 +240,23 @@ class PlacementEndpointNotFound(NotFound):
class PlacementResourceProviderNotFound(NotFound):
_msg_fmt = _("Placement resource provider not found: "
"%(resource_provider)s.")
_msg_fmt = _(
"Placement resource provider not found: %(resource_provider)s."
)
class PlacementInventoryNotFound(NotFound):
_msg_fmt = _("Placement inventory not found for resource provider "
"%(resource_provider)s, resource class %(resource_class)s.")
_msg_fmt = _(
"Placement inventory not found for resource provider "
"%(resource_provider)s, resource class %(resource_class)s."
)
class PlacementInventoryUpdateConflict(Conflict):
_msg_fmt = _("Placement inventory update conflict for resource provider "
"%(resource_provider)s, resource class %(resource_class)s.")
_msg_fmt = _(
"Placement inventory update conflict for resource provider "
"%(resource_provider)s, resource class %(resource_class)s."
)
class ObjectActionError(CyborgException):
@@ -274,13 +286,15 @@ class ResourceProviderRetrievalFailed(CyborgException):
class ResourceProviderAggregateRetrievalFailed(CyborgException):
_msg_fmt = _("Failed to get aggregates for resource provider with UUID"
" %(uuid)s")
_msg_fmt = _(
"Failed to get aggregates for resource provider with UUID %(uuid)s"
)
class ResourceProviderTraitRetrievalFailed(CyborgException):
_msg_fmt = _("Failed to get traits for resource provider with UUID"
" %(uuid)s")
_msg_fmt = _(
"Failed to get traits for resource provider with UUID %(uuid)s"
)
class ResourceProviderCreationFailed(CyborgException):
@@ -292,13 +306,16 @@ class ResourceProviderDeletionFailed(CyborgException):
class ResourceProviderUpdateFailed(CyborgException):
_msg_fmt = _("Failed to update resource provider via URL %(url)s: "
"%(error)s")
_msg_fmt = _(
"Failed to update resource provider via URL %(url)s: %(error)s"
)
class ResourceProviderSyncFailed(CyborgException):
_msg_fmt = _("Failed to synchronize the placement service with resource "
"provider information supplied by the compute host.")
_msg_fmt = _(
"Failed to synchronize the placement service with resource "
"provider information supplied by the compute host."
)
class PlacementAPIConnectFailure(CyborgException):
@@ -313,16 +330,22 @@ class PlacementAPIConflict(CyborgException):
"""Any 409 error from placement APIs should use (a subclass of) this
exception.
"""
_msg_fmt = _("A conflict was encountered attempting to invoke the "
"placement API at URL %(url)s: %(error)s")
_msg_fmt = _(
"A conflict was encountered attempting to invoke the "
"placement API at URL %(url)s: %(error)s"
)
class ResourceProviderUpdateConflict(PlacementAPIConflict):
"""A 409 caused by generation mismatch from attempting to update an
existing provider record or its associated data (aggregates, traits, etc.).
"""
_msg_fmt = _("A conflict was encountered attempting to update resource "
"provider %(uuid)s (generation %(generation)d): %(error)s")
_msg_fmt = _(
"A conflict was encountered attempting to update resource "
"provider %(uuid)s (generation %(generation)d): %(error)s"
)
class TraitCreationFailed(CyborgException):
@@ -342,8 +365,10 @@ class InvalidResourceAmount(Invalid):
class InvalidInventory(Invalid):
_msg_fmt = _("Inventory for '%(resource_class)s' on "
"resource provider '%(resource_provider)s' invalid.")
_msg_fmt = _(
"Inventory for '%(resource_class)s' on "
"resource provider '%(resource_provider)s' invalid."
)
# An exception with this name is used on both sides of the placement/
@@ -351,8 +376,10 @@ class InvalidInventory(Invalid):
class InventoryInUse(InvalidInventory):
# NOTE(mriedem): This message cannot change without impacting the
# cyborg.services.client.report._RE_INV_IN_USE regex.
_msg_fmt = _("Inventory for '%(resource_classes)s' on "
"resource provider '%(resource_provider)s' in use.")
_msg_fmt = _(
"Inventory for '%(resource_classes)s' on "
"resource provider '%(resource_provider)s' in use."
)
class QuotaNotFound(NotFound):
@@ -372,8 +399,7 @@ class InvalidReservationExpiration(Invalid):
class GlanceConnectionFailed(CyborgException):
_msg_fmt = _("Connection to glance host %(server)s failed: "
"%(reason)s")
_msg_fmt = _("Connection to glance host %(server)s failed: %(reason)s")
class ImageUnacceptable(Invalid):
@@ -385,8 +411,9 @@ class ImageNotAuthorized(CyborgException):
class ImageBadRequest(Invalid):
_msg_fmt = _("Request of image %(image_id)s got BadRequest response: "
"%(response)s")
_msg_fmt = _(
"Request of image %(image_id)s got BadRequest response: %(response)s"
)
class InvalidDriver(Invalid):
@@ -406,8 +433,7 @@ class PciDeviceWrongAddressFormat(Invalid):
class InvalidType(Invalid):
_msg_fmt = _("Invalid type for %(obj)s: %(type)s."
"Expected: %(expected)s")
_msg_fmt = _("Invalid type for %(obj)s: %(type)s.Expected: %(expected)s")
class ResourceNotFound(NotFound):
@@ -432,5 +458,7 @@ class PciConfigInvalidWhitelist(Invalid):
class PciDeviceInvalidDeviceName(CyborgException):
_msg_fmt = _("Invalid PCI whitelist: The PCI whitelist can specify "
"devname or address, but not both.")
_msg_fmt = _(
"Invalid PCI whitelist: The PCI whitelist can specify "
"devname or address, but not both."
)

View File

@@ -26,11 +26,15 @@ class NovaAPI:
self.nova_client.default_microversion = '2.82'
def _get_acc_changed_events(self, instance_uuid, arq_bind_statuses):
return [{'name': 'accelerator-request-bound',
'server_uuid': instance_uuid,
'tag': arq_uuid,
'status': arq_bind_status,
} for (arq_uuid, arq_bind_status) in arq_bind_statuses]
return [
{
'name': 'accelerator-request-bound',
'server_uuid': instance_uuid,
'tag': arq_uuid,
'status': arq_bind_status,
}
for (arq_uuid, arq_bind_status) in arq_bind_statuses
]
def _send_events(self, events):
"""Send events to Nova external events API.
@@ -44,8 +48,10 @@ class NovaAPI:
# NOTE(Sundar): Response status should always be 200/207. See
# https://review.opendev.org/#/c/698037/
if response.status_code == 200:
LOG.info("Successfully sent events to Nova, events: %(events)s",
{"events": events})
LOG.info(
"Successfully sent events to Nova, events: %(events)s",
{"events": events},
)
elif response.status_code == 207:
# NOTE(Sundar): If Nova returns per-event code of 422, that
# is due to a race condition where Nova has not associated
@@ -55,30 +61,42 @@ class NovaAPI:
event_codes = {ev['code'] for ev in events}
if len(event_codes) == 1: # all events have same event code
if event_codes == {422}:
LOG.info('Ignoring Nova notification error that the '
'instance %s is not yet associated with a host.',
events[0]['server_uuid'])
LOG.info(
'Ignoring Nova notification error that the '
'instance %s is not yet associated with a host.',
events[0]['server_uuid'],
)
else:
msg = _('Unexpected event code %(code)s '
'for instance %(inst)s')
msg = msg % {'code': event_codes.pop(),
'inst': events[0]["server_uuid"]}
msg = _(
'Unexpected event code %(code)s for instance %(inst)s'
)
msg = msg % {
'code': event_codes.pop(),
'inst': events[0]["server_uuid"],
}
raise exception.InvalidAPIResponse(
service='Nova', api=url[1:], msg=msg)
service='Nova', api=url[1:], msg=msg
)
else:
msg = _('All event responses are expected to '
'have the same event code. Instance: %(inst)s')
msg = _(
'All event responses are expected to '
'have the same event code. Instance: %(inst)s'
)
msg = msg % {'inst': events[0]['server_uuid']}
raise exception.InvalidAPIResponse(
service='Nova', api=url[1:], msg=msg)
service='Nova', api=url[1:], msg=msg
)
else:
# Unexpected return code from Nova
msg = _('Failed to send events %(ev)s: HTTP %(code)s: %(txt)s')
msg = msg % {'ev': events,
'code': response.status_code,
'txt': response.text}
msg = msg % {
'ev': events,
'code': response.status_code,
'txt': response.text,
}
raise exception.InvalidAPIResponse(
service='Nova', api=url[1:], msg=msg)
service='Nova', api=url[1:], msg=msg
)
def notify_binding(self, instance_uuid, arq_bind_statuses):
"""Notify Nova that ARQ bindings are resolved for a given instance.

View File

@@ -34,20 +34,27 @@ class PlacementClient:
self._client = utils.get_sdk_adapter('placement')
def get(self, url, version=None, global_request_id=None):
res = self._client.get(url, microversion=version,
global_request_id=global_request_id)
res = self._client.get(
url, microversion=version, global_request_id=global_request_id
)
if res and res.status_code >= 500:
raise exception.PlacementServerError(
"Placement Server has some error at this time.")
"Placement Server has some error at this time."
)
LOG.debug('Successfully get resources from placement: %s', url)
return res
def post(self, url, data, version=None, global_request_id=None):
res = self._client.post(url, json=data, microversion=version,
global_request_id=global_request_id)
res = self._client.post(
url,
json=data,
microversion=version,
global_request_id=global_request_id,
)
if res.status_code >= 500:
raise exception.PlacementServerError(
"Placement Server has some error at this time.")
"Placement Server has some error at this time."
)
LOG.debug('Successfully create resources from placement: %s', url)
return res
@@ -55,31 +62,37 @@ class PlacementClient:
kwargs = {}
if data is not None:
kwargs['json'] = data
res = self._client.put(url, microversion=version,
global_request_id=global_request_id,
**kwargs)
res = self._client.put(
url,
microversion=version,
global_request_id=global_request_id,
**kwargs,
)
if res.status_code >= 500:
raise exception.PlacementServerError(
"Placement Server has some error at this time.")
"Placement Server has some error at this time."
)
LOG.debug('Successfully update resources from placement: %s', url)
return res
def delete(self, url, version=None, global_request_id=None):
res = self._client.delete(url, microversion=version,
global_request_id=global_request_id)
res = self._client.delete(
url, microversion=version, global_request_id=global_request_id
)
if res.status_code >= 500:
raise exception.PlacementServerError(
"Placement Server has some error at this time.")
"Placement Server has some error at this time."
)
LOG.debug('Successfully delete resources from placement: %s', url)
return res
def _get_rp_traits(self, rp_uuid):
resp = self.get(f"/resource_providers/{rp_uuid}/traits",
version='1.6')
resp = self.get(f"/resource_providers/{rp_uuid}/traits", version='1.6')
if resp.status_code != 200:
raise Exception(
f"Failed to get traits for rp {rp_uuid}:"
f" HTTP {resp.status_code}: {resp.text}")
f" HTTP {resp.status_code}: {resp.text}"
)
return resp.json()
def _ensure_traits(self, trait_names):
@@ -88,8 +101,9 @@ class PlacementClient:
for trait_name in trait_names:
trait = self.get(f"/traits/{trait_name}", version='1.6')
if trait:
LOG.info("Trait %(trait)s already existed",
{"trait": trait_name})
LOG.info(
"Trait %(trait)s already existed", {"trait": trait_name}
)
continue
resp = self.put(f"/traits/{trait_name}", None, version='1.6')
if resp.status_code == 201:
@@ -97,21 +111,25 @@ class PlacementClient:
else:
raise Exception(
f"Failed to create trait {trait_name}:"
f" HTTP {resp.status_code}: {resp.text}")
f" HTTP {resp.status_code}: {resp.text}"
)
def _put_rp_traits(self, rp_uuid, traits_json):
generation = self.get_resource_provider(
resource_provider_uuid=rp_uuid)['generation']
resource_provider_uuid=rp_uuid
)['generation']
payload = {
'resource_provider_generation': generation,
'traits': traits_json["traits"],
}
resp = self.put(
f"/resource_providers/{rp_uuid}/traits", payload, version='1.6')
f"/resource_providers/{rp_uuid}/traits", payload, version='1.6'
)
if resp.status_code != 200:
raise Exception(
f"Failed to set traits to {traits_json} for rp {rp_uuid}:"
f" HTTP {resp.status_code}: {resp.text}")
f" HTTP {resp.status_code}: {resp.text}"
)
def add_traits_to_rp(self, rp_uuid, trait_names):
self._ensure_traits(trait_names)
@@ -123,9 +141,8 @@ class PlacementClient:
def delete_trait_by_name(self, context, rp_uuid, trait_name):
traits_json = self._get_rp_traits(rp_uuid)
traits = [
trait for trait in traits_json['traits']
if trait != trait_name
]
trait for trait in traits_json['traits'] if trait != trait_name
]
traits_json['traits'] = traits
self._put_rp_traits(rp_uuid, traits_json)
self._delete_trait(context, trait_name)
@@ -133,9 +150,10 @@ class PlacementClient:
def delete_traits_with_prefixes(self, context, rp_uuid, trait_prefixes):
traits_json = self._get_rp_traits(rp_uuid)
traits = [
trait for trait in traits_json['traits']
if not any(trait.startswith(prefix)
for prefix in trait_prefixes)]
trait
for trait in traits_json['traits']
if not any(trait.startswith(prefix) for prefix in trait_prefixes)
]
delete_traits = set(traits_json['traits']) - set(traits)
traits_json['traits'] = traits
self._put_rp_traits(rp_uuid, traits_json)
@@ -147,21 +165,27 @@ class PlacementClient:
return response.headers.get(request_id.HTTP_RESP_HEADER_REQUEST_ID)
def update_inventory(
self, resource_provider_uuid, inventories,
resource_provider_generation=None, version=None):
self,
resource_provider_uuid,
inventories,
resource_provider_generation=None,
version=None,
):
if resource_provider_generation is None:
resource_provider_generation = self.get_resource_provider(
resource_provider_uuid=resource_provider_uuid)['generation']
resource_provider_uuid=resource_provider_uuid
)['generation']
url = f'/resource_providers/{resource_provider_uuid}/inventories'
body = {
'resource_provider_generation': resource_provider_generation,
'inventories': inventories
'inventories': inventories,
}
try:
return self.put(url, body, version=version).json()
except ks_exc.NotFound:
raise exception.PlacementResourceProviderNotFound(
resource_provider=resource_provider_uuid)
resource_provider=resource_provider_uuid
)
def get_resource_provider(self, resource_provider_uuid):
"""Get resource provider by UUID.
@@ -175,10 +199,12 @@ class PlacementClient:
return self.get(url).json()
except ks_exc.NotFound:
raise exception.PlacementResourceProviderNotFound(
resource_provider=resource_provider_uuid)
resource_provider=resource_provider_uuid
)
def _create_resource_provider(self, context, uuid, name,
parent_provider_uuid=None):
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
@@ -200,16 +226,21 @@ class PlacementClient:
# 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)
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.")
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,
@@ -218,21 +249,27 @@ class PlacementClient:
LOG.info(msg, args)
return resp.json()
def ensure_resource_provider(self, context, uuid, name=None,
parent_provider_uuid=None):
def ensure_resource_provider(
self, context, uuid, name=None, parent_provider_uuid=None
):
resp = self.get(f"/resource_providers/{uuid}", version='1.6')
if resp.status_code == 200:
LOG.info("Resource Provider %(uuid)s already exists",
{"uuid": uuid})
LOG.info(
"Resource Provider %(uuid)s already exists", {"uuid": uuid}
)
else:
LOG.info("Creating resource provider %(provider)s",
{"provider": name or uuid})
LOG.info(
"Creating resource provider %(provider)s",
{"provider": name or uuid},
)
try:
resp = self._create_resource_provider(context, uuid, name,
parent_provider_uuid)
resp = self._create_resource_provider(
context, uuid, name, parent_provider_uuid
)
except Exception:
raise exception.ResourceProviderCreationFailed(
name=name or uuid)
name=name or uuid
)
return uuid
def ensure_resource_classes(self, context, names):
@@ -245,12 +282,17 @@ class PlacementClient:
if name in orc.STANDARDS:
return
resp = self.put(
f"/resource_classes/{name}", None, version=version,
global_request_id=context.global_id)
f"/resource_classes/{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.")
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,
@@ -259,11 +301,15 @@ class PlacementClient:
LOG.error(msg, args)
raise exception.InvalidResourceClass(resource_class=name)
elif resp.status_code == 204:
LOG.info("Resource class %(rc_name)s already exists",
{"rc_name": name})
LOG.info(
"Resource class %(rc_name)s already exists",
{"rc_name": name},
)
elif resp.status_code == 201:
LOG.info("Successfully created resource class %(rc_name).", {
"rc_name", name})
LOG.info(
"Successfully created resource class %(rc_name).",
{"rc_name", name},
)
def get_providers_in_tree(self, context, uuid):
"""Queries the placement API for a list of the resource providers in
@@ -275,18 +321,22 @@ class PlacementClient:
empty if no provider exists with the specified UUID.
:raise: ResourceProviderRetrievalFailed on error.
"""
resp = self.get(f"/resource_providers?in_tree={uuid}",
version=NESTED_PROVIDER_API_VERSION,
global_request_id=context.global_id)
resp = self.get(
f"/resource_providers?in_tree={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.")
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,
@@ -297,22 +347,26 @@ class PlacementClient:
raise exception.ResourceProviderRetrievalFailed(uuid=uuid)
def delete_provider(self, rp_uuid, global_request_id=None):
resp = self.delete(f'/resource_providers/{rp_uuid}',
global_request_id=global_request_id)
resp = self.delete(
f'/resource_providers/{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.")
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
'err_text': resp.text,
}
LOG.error(msg, args)
# On conflict, the caller may wish to delete allocations and
@@ -325,11 +379,14 @@ class PlacementClient:
def delete_rc_by_name(self, context, name):
"""Delete resource class from placement by name."""
resp = self.delete(
f"/resource_classes/{name}", global_request_id=context.global_id)
f"/resource_classes/{name}", global_request_id=context.global_id
)
if not resp:
msg = ("Failed to delete resource class record with placement "
"API for resource class %(rc_name)s. Got "
"%(status_code)d: %(err_text)s.")
msg = (
"Failed to delete 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,
@@ -337,18 +394,25 @@ class PlacementClient:
}
LOG.error(msg, args)
elif resp.status_code == 204:
LOG.info("Successfully delete resource class %(rc_name).", {
"rc_name", name})
LOG.info(
"Successfully delete resource class %(rc_name).",
{"rc_name", name},
)
def _delete_trait(self, context, name):
"""Delete trait from placement by name."""
version = '1.6'
resp = self.delete(f"/traits/{name}", version=version,
global_request_id=context.global_id)
resp = self.delete(
f"/traits/{name}",
version=version,
global_request_id=context.global_id,
)
if not resp:
msg = ("Failed to delete trait record with placement "
"API for trait %(trait_name)s. Got "
"%(status_code)d: %(err_text)s.")
msg = (
"Failed to delete trait record with placement "
"API for trait %(trait_name)s. Got "
"%(status_code)d: %(err_text)s."
)
args = {
'trait_name': name,
'status_code': resp.status_code,
@@ -356,8 +420,10 @@ class PlacementClient:
}
LOG.error(msg, args)
elif resp.status_code == 204:
LOG.info("Successfully delete trait %(trait_name).", {
"trait_name", name})
LOG.info(
"Successfully delete trait %(trait_name).",
{"trait_name", name},
)
def update_rp_inventory_reserved(self, rp_uuid, resource, total, reserved):
update_inventory = {resource: {"total": total, "reserved": reserved}}

View File

@@ -15,8 +15,9 @@
"""legacy old_policies, the following old_policies will be removed once
new policies are implemented.
new policies are implemented.
"""
from oslo_policy import policy
# NOTE: to follow policy-in-code spec, we define defaults for
# the granular policies in code, rather than in policy.yaml.
@@ -24,73 +25,103 @@ from oslo_policy import policy
# depend on their existence throughout the code.
accelerator_request_policies = [
policy.RuleDefault('cyborg:arq:get_all',
'rule:default',
description='Retrieve accelerator request records.'),
policy.RuleDefault('cyborg:arq:get_one',
'rule:default',
description='Get an accelerator request record.'),
policy.RuleDefault('cyborg:arq:create',
'rule:allow',
description='Create accelerator request records.'),
policy.RuleDefault('cyborg:arq:delete',
'rule:default',
description='Delete accelerator request records.'),
policy.RuleDefault('cyborg:arq:update',
'rule:default',
description='Update accelerator request records.'),
policy.RuleDefault(
'cyborg:arq:get_all',
'rule:default',
description='Retrieve accelerator request records.',
),
policy.RuleDefault(
'cyborg:arq:get_one',
'rule:default',
description='Get an accelerator request record.',
),
policy.RuleDefault(
'cyborg:arq:create',
'rule:allow',
description='Create accelerator request records.',
),
policy.RuleDefault(
'cyborg:arq:delete',
'rule:default',
description='Delete accelerator request records.',
),
policy.RuleDefault(
'cyborg:arq:update',
'rule:default',
description='Update accelerator request records.',
),
]
device_policies = [
policy.RuleDefault('cyborg:device:get_one',
'rule:allow',
description='Show device detail'),
policy.RuleDefault('cyborg:device:get_all',
'rule:allow',
description='Retrieve all device records'),
policy.RuleDefault('cyborg:device:disable',
'rule:admin_api',
description='Disable a device'),
policy.RuleDefault('cyborg:device:enable',
'rule:admin_api',
description='Enable a device'),
policy.RuleDefault(
'cyborg:device:get_one', 'rule:allow', description='Show device detail'
),
policy.RuleDefault(
'cyborg:device:get_all',
'rule:allow',
description='Retrieve all device records',
),
policy.RuleDefault(
'cyborg:device:disable',
'rule:admin_api',
description='Disable a device',
),
policy.RuleDefault(
'cyborg:device:enable', 'rule:admin_api', description='Enable a device'
),
]
deployable_policies = [
policy.RuleDefault('cyborg:deployable:get_one',
'rule:allow',
description='Show deployable detail'),
policy.RuleDefault('cyborg:deployable:get_all',
'rule:allow',
description='Retrieve all deployable records'),
policy.RuleDefault('cyborg:deployable:program',
'rule:allow',
description='FPGA programming.'),
policy.RuleDefault(
'cyborg:deployable:get_one',
'rule:allow',
description='Show deployable detail',
),
policy.RuleDefault(
'cyborg:deployable:get_all',
'rule:allow',
description='Retrieve all deployable records',
),
policy.RuleDefault(
'cyborg:deployable:program',
'rule:allow',
description='FPGA programming.',
),
]
attribute_policies = [
policy.RuleDefault('cyborg:attribute:get_one',
'rule:allow',
description='Show attribute detail'),
policy.RuleDefault('cyborg:attribute:get_all',
'rule:allow',
description='Retrieve all attribute records'),
policy.RuleDefault('cyborg:attribute:create',
'rule:allow',
description='Create an attribute record'),
policy.RuleDefault('cyborg:attribute:delete',
'rule:allow',
description='Delete attribute records.'),
policy.RuleDefault(
'cyborg:attribute:get_one',
'rule:allow',
description='Show attribute detail',
),
policy.RuleDefault(
'cyborg:attribute:get_all',
'rule:allow',
description='Retrieve all attribute records',
),
policy.RuleDefault(
'cyborg:attribute:create',
'rule:allow',
description='Create an attribute record',
),
policy.RuleDefault(
'cyborg:attribute:delete',
'rule:allow',
description='Delete attribute records.',
),
]
fpga_policies = [
policy.RuleDefault('cyborg:fpga:get_one',
'rule:allow',
description='Show fpga detail'),
policy.RuleDefault('cyborg:fpga:get_all',
'rule:allow',
description='Retrieve all fpga records'),
policy.RuleDefault('cyborg:fpga:update',
'rule:allow',
description='Update fpga records'),
policy.RuleDefault(
'cyborg:fpga:get_one', 'rule:allow', description='Show fpga detail'
),
policy.RuleDefault(
'cyborg:fpga:get_all',
'rule:allow',
description='Retrieve all fpga records',
),
policy.RuleDefault(
'cyborg:fpga:update', 'rule:allow', description='Update fpga records'
),
]

View File

@@ -35,15 +35,14 @@ EXTRA_EXMODS = []
def init(conf):
global TRANSPORT, NOTIFICATION_TRANSPORT, NOTIFIER
exmods = get_allowed_exmods()
TRANSPORT = messaging.get_rpc_transport(conf,
allowed_remote_exmods=exmods)
TRANSPORT = messaging.get_rpc_transport(conf, allowed_remote_exmods=exmods)
NOTIFICATION_TRANSPORT = messaging.get_notification_transport(
conf,
allowed_remote_exmods=exmods)
conf, allowed_remote_exmods=exmods
)
serializer = RequestContextSerializer(messaging.JsonPayloadSerializer())
NOTIFIER = messaging.Notifier(NOTIFICATION_TRANSPORT,
serializer=serializer,
topics=['notifications'])
NOTIFIER = messaging.Notifier(
NOTIFICATION_TRANSPORT, serializer=serializer, topics=['notifications']
)
def cleanup():
@@ -97,8 +96,8 @@ def get_client(target, version_cap=None, serializer=None):
assert TRANSPORT is not None
serializer = RequestContextSerializer(serializer)
return messaging.get_rpc_client(
TRANSPORT, target, version_cap=version_cap,
serializer=serializer)
TRANSPORT, target, version_cap=version_cap, serializer=serializer
)
def get_server(target, endpoints, serializer=None):
@@ -106,8 +105,12 @@ def get_server(target, endpoints, serializer=None):
access_policy = dispatcher.DefaultRPCAccessPolicy
serializer = RequestContextSerializer(serializer)
return messaging.get_rpc_server(
TRANSPORT, target, endpoints,
serializer=serializer, access_policy=access_policy)
TRANSPORT,
target,
endpoints,
serializer=serializer,
access_policy=access_policy,
)
def get_notifier(service=None, host=None, publisher_id=None):

View File

@@ -58,24 +58,30 @@ class RPCService(service.Service):
self.tg.add_dynamic_timer_args(
self.manager.periodic_tasks,
kwargs={"context": admin_context},
periodic_interval_max=CONF.periodic_interval)
periodic_interval_max=CONF.periodic_interval,
)
LOG.info('Created RPC server for service %(service)s on host '
'%(host)s.',
{'service': self.topic, 'host': self.host})
LOG.info(
'Created RPC server for service %(service)s on host %(host)s.',
{'service': self.topic, 'host': self.host},
)
def stop(self, graceful=True):
try:
self.rpcserver.stop()
self.rpcserver.wait()
except Exception as e:
LOG.exception('Service error occurred when stopping the '
'RPC server. Error: %s', e)
LOG.exception(
'Service error occurred when stopping the '
'RPC server. Error: %s',
e,
)
super().stop(graceful=graceful)
LOG.info('Stopped RPC server for service %(service)s on host '
'%(host)s.',
{'service': self.topic, 'host': self.host})
LOG.info(
'Stopped RPC server for service %(service)s on host %(host)s.',
{'service': self.topic, 'host': self.host},
)
def prepare_service(argv=None):
@@ -105,17 +111,24 @@ class WSGIService(service.ServiceBase):
"""
self.name = name
self.app = app.load_app()
self.workers = (CONF.api.api_workers or
processutils.get_worker_count())
self.workers = CONF.api.api_workers or processutils.get_worker_count()
if self.workers and self.workers < 1:
raise exception.ConfigInvalid(
_("api_workers value of %d is invalid, "
"must be greater than 0.") % self.workers)
_(
"api_workers value of %d is invalid, "
"must be greater than 0."
)
% self.workers
)
self.server = wsgi.Server(CONF, self.name, self.app,
host=CONF.api.host_ip,
port=CONF.api.port,
use_ssl=use_ssl)
self.server = wsgi.Server(
CONF,
self.name,
self.app,
host=CONF.api.host_ip,
port=CONF.api.port,
use_ssl=use_ssl,
)
def start(self):
"""Start serving this service using loaded configuration.

View File

@@ -49,16 +49,24 @@ def safe_rstrip(value, chars=None):
"""
if not isinstance(value, str):
LOG.warning("Failed to remove trailing character. Returning "
"original object. Supplied object is not a string: "
"%s,", value)
LOG.warning(
"Failed to remove trailing character. Returning "
"original object. Supplied object is not a string: "
"%s,",
value,
)
return value
return value.rstrip(chars) or value
def get_ksa_adapter(service_type, ksa_auth=None, ksa_session=None,
min_version=None, max_version=None):
def get_ksa_adapter(
service_type,
ksa_auth=None,
ksa_session=None,
min_version=None,
max_version=None,
):
"""Construct a keystoneauth1 Adapter for a given service type.
We expect to find a conf group whose name corresponds to the service_type's
@@ -109,11 +117,17 @@ def get_ksa_adapter(service_type, ksa_auth=None, ksa_session=None,
if not ksa_session:
ksa_session = ks_loading.load_session_from_conf_options(
CONF, confgrp, auth=ksa_auth)
CONF, confgrp, auth=ksa_auth
)
return ks_loading.load_adapter_from_conf_options(
CONF, confgrp, session=ksa_session, auth=ksa_auth,
min_version=min_version, max_version=max_version)
CONF,
confgrp,
session=ksa_session,
auth=ksa_auth,
min_version=min_version,
max_version=max_version,
)
def _get_conf_group(service_type):
@@ -127,7 +141,8 @@ def _get_conf_group(service_type):
def _get_auth_and_session(confgrp):
ksa_auth = ks_loading.load_auth_from_conf_options(CONF, confgrp)
return ks_loading.load_session_from_conf_options(
CONF, confgrp, auth=ksa_auth)
CONF, confgrp, auth=ksa_auth
)
def get_sdk_adapter(service_type, check_service=False):
@@ -148,12 +163,16 @@ def get_sdk_adapter(service_type, check_service=False):
sess = _get_auth_and_session(confgrp)
try:
conn = connection.Connection(
session=sess, oslo_conf=CONF, service_types={service_type},
strict_proxies=check_service)
session=sess,
oslo_conf=CONF,
service_types={service_type},
strict_proxies=check_service,
)
except sdk_exc.ServiceDiscoveryException as e:
raise exception.ServiceUnavailable(
_("The %(service_type)s service is unavailable: %(error)s") %
{'service_type': service_type, 'error': str(e)})
_("The %(service_type)s service is unavailable: %(error)s")
% {'service_type': service_type, 'error': str(e)}
)
return getattr(conn, service_type)
@@ -195,7 +214,8 @@ def get_endpoint(ksa_adapter):
pass
raise ks_exc.EndpointNotFound(
"Could not find requested endpoint for any of the following "
"interfaces: %s" % interfaces)
"interfaces: %s" % interfaces
)
class _Singleton(type):
@@ -205,8 +225,9 @@ class _Singleton(type):
def __call__(cls, *args, **kwargs):
ins = cls._instances.get(cls)
if not ins or (hasattr(ins, "_reset")
and isinstance(ins, cls) and ins._reset()):
if not ins or (
hasattr(ins, "_reset") and isinstance(ins, cls) and ins._reset()
):
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
@@ -233,8 +254,13 @@ class ThreadWorks(Singleton):
def spawn(self, func, *args, **kwargs):
"""Put a job in thread pool."""
LOG.debug("Add an async jobs. func: %s is with parameters args: %s, "
"kwargs: %s", func, args, kwargs)
LOG.debug(
"Add an async jobs. func: %s is with parameters args: %s, "
"kwargs: %s",
func,
args,
kwargs,
)
future = self.executor.submit(func, *args, **kwargs)
return future
@@ -243,8 +269,13 @@ class ThreadWorks(Singleton):
executor = futures.ThreadPoolExecutor()
# TODO(Shaohe) every submit func should be wrapped with exception catch
job = executor.submit(func, *args, **kwargs)
LOG.debug("Spawn master job. func: %s is with parameters args: %s, "
"kwargs: %s", func, args, kwargs)
LOG.debug(
"Spawn master job. func: %s is with parameters args: %s, "
"kwargs: %s",
func,
args,
kwargs,
)
# NOTE(Shaohe) shutdown should be after job submit
executor.shutdown(wait=False)
# TODO(Shaohe) we need to consider resource collection such as the
@@ -302,17 +333,28 @@ class ThreadWorks(Singleton):
yield f.result(), f.exception(), f._state, None
else:
f = fs.pop()
yield (f.result(end_time - time.time()),
f.exception(), f._state, None)
yield (
f.result(end_time - time.time()),
f.exception(),
f._state,
None,
)
except Exception as e:
err = traceback.format_exc()
LOG.error("Error during check the worker status. Exception "
"info: %s", err)
LOG.error(
"Error during check the worker status. Exception info: %s",
err,
)
if f:
LOG.error("Error during check the worker status. "
"Exception info: %s, result: %s, state: %s. "
"Reason %s", f.exception(), f._result,
f._state, str(e))
LOG.error(
"Error during check the worker status. "
"Exception info: %s, result: %s, state: %s. "
"Reason %s",
f.exception(),
f._result,
f._state,
str(e),
)
yield f._result, f.exception(), f._state, err
finally:
# Do best to cancel remain jobs.
@@ -351,6 +393,7 @@ def format_tb(tb, limit=None):
def wrap_job_tb(msg="Reason: %s"):
"""Wrap a function with a is_job tag added, and catch Exception."""
def _wrap_job_tb(method):
@wraps(method)
def _impl(self, *args, **kwargs):
@@ -361,13 +404,16 @@ def wrap_job_tb(msg="Reason: %s"):
LOG.error(traceback.format_exc())
raise
return output
setattr(_impl, "is_job", True)
return _impl
return _wrap_job_tb
def factory_register(SuperClass, ClassName):
"""Register an concrete class to a factory Class."""
def decorator(Class):
# return Class
if not hasattr(SuperClass, "_factory"):
@@ -375,6 +421,7 @@ def factory_register(SuperClass, ClassName):
SuperClass._factory[ClassName] = Class
setattr(Class, "_factory_type", ClassName)
return Class
return decorator
@@ -387,16 +434,23 @@ class FactoryMixin:
f = getattr(cls, "_factory", {})
sclass = f.get(typ, None)
if sclass:
LOG.info("Find %s of concrete %s by %s.",
sclass.__name__, cls.__name__, typ)
LOG.info(
"Find %s of concrete %s by %s.",
sclass.__name__,
cls.__name__,
typ,
)
return sclass
for sclass in cls.__subclasses__():
if typ == getattr(cls, "_factory_type", None):
return sclass
else:
return cls
LOG.info("Use default %s, do not find concrete class"
"by %s.", cls.__name__, typ)
LOG.info(
"Use default %s, do not find concrete classby %s.",
cls.__name__,
typ,
)
def strtime(at):

View File

@@ -1,4 +1,3 @@
#
# 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

View File

@@ -117,24 +117,32 @@ class ConductorManager:
old_driver_device_list = DriverDevice.list(context, hostname)
# TODO(wangzhh): Remove invalid driver_devices without controlpath_id.
# Then diff two driver device list.
self.drv_device_make_diff(context, hostname,
old_driver_device_list, driver_device_list)
self.drv_device_make_diff(
context, hostname, old_driver_device_list, driver_device_list
)
def drv_device_make_diff(self, context, host, old_driver_device_list,
new_driver_device_list):
def drv_device_make_diff(
self, context, host, old_driver_device_list, new_driver_device_list
):
"""Compare new driver-side device object list with the old one in
one host.
"""
LOG.info("Start differing devices.")
# TODO(): The placement report will be implemented here.
# Use cpid.cpid_info to identify whether the device is the same.
stub_cpid_list = [driver_dev_obj.controlpath_id.cpid_info for
driver_dev_obj in new_driver_device_list
if driver_dev_obj.stub]
new_cpid_list = [driver_dev_obj.controlpath_id.cpid_info for
driver_dev_obj in new_driver_device_list]
old_cpid_list = [driver_dev_obj.controlpath_id.cpid_info for
driver_dev_obj in old_driver_device_list]
stub_cpid_list = [
driver_dev_obj.controlpath_id.cpid_info
for driver_dev_obj in new_driver_device_list
if driver_dev_obj.stub
]
new_cpid_list = [
driver_dev_obj.controlpath_id.cpid_info
for driver_dev_obj in new_driver_device_list
]
old_cpid_list = [
driver_dev_obj.controlpath_id.cpid_info
for driver_dev_obj in old_driver_device_list
]
same = set(new_cpid_list) & set(old_cpid_list) - set(stub_cpid_list)
added = set(new_cpid_list) - same - set(stub_cpid_list)
deleted = set(old_cpid_list) - same - set(stub_cpid_list)
@@ -152,10 +160,10 @@ class ConductorManager:
try:
new_driver_dev_obj.create(context, host)
except Exception as exc:
LOG.exception("Failed to add device %(device)s. "
"Reason: %(reason)s",
{'device': new_driver_dev_obj,
'reason': exc})
LOG.exception(
"Failed to add device %(device)s. Reason: %(reason)s",
{'device': new_driver_dev_obj, 'reason': exc},
)
new_driver_dev_obj.destroy(context, host)
# TODO(All): If report device data to Placement raise exception,
# we should revert driver device created in Cyborg and resources
@@ -164,14 +172,14 @@ class ConductorManager:
cleanup_inconsistency_resources = False
for driver_dep_obj in new_driver_dev_obj.deployable_list:
try:
self.get_placement_needed_info_and_report(context,
driver_dep_obj,
host_rp)
self.get_placement_needed_info_and_report(
context, driver_dep_obj, host_rp
)
except Exception as exc:
LOG.info("Failed to add device %(device)s. "
"Reason: %(reason)s",
{'device': new_driver_dev_obj,
'reason': exc})
LOG.info(
"Failed to add device %(device)s. Reason: %(reason)s",
{'device': new_driver_dev_obj, 'reason': exc},
)
cleanup_inconsistency_resources = True
break
if cleanup_inconsistency_resources:
@@ -190,36 +198,55 @@ class ConductorManager:
for dev_obj in device_obj_list:
# get cpid_obj, could be empty or only one value.
cpid_obj = ControlpathID.get_by_device_id_cpidinfo(
context, dev_obj.id, cpid_info)
context, dev_obj.id, cpid_info
)
# find the one cpid_obj with cpid_info
if cpid_obj is not None:
break
changed_key = ['std_board_info', 'vendor', 'vendor_board_info',
'model', 'type']
changed_key = [
'std_board_info',
'vendor',
'vendor_board_info',
'model',
'type',
]
for c_k in changed_key:
if getattr(new_driver_dev_obj, c_k) != getattr(
old_driver_dev_obj, c_k):
old_driver_dev_obj, c_k
):
setattr(dev_obj, c_k, getattr(new_driver_dev_obj, c_k))
dev_obj.save(context)
# diff the internal layer: driver_deployable
self.drv_deployable_make_diff(context, dev_obj.id, cpid_obj.id,
old_driver_dev_obj.deployable_list,
new_driver_dev_obj.deployable_list,
host_rp)
self.drv_deployable_make_diff(
context,
dev_obj.id,
cpid_obj.id,
old_driver_dev_obj.deployable_list,
new_driver_dev_obj.deployable_list,
host_rp,
)
def drv_deployable_make_diff(self, context, device_id, cpid_id,
old_driver_dep_list, new_driver_dep_list,
host_rp):
def drv_deployable_make_diff(
self,
context,
device_id,
cpid_id,
old_driver_dep_list,
new_driver_dep_list,
host_rp,
):
"""Compare new driver-side deployable object list with the old one in
one host.
"""
# use name to identify whether the deployable is the same.
LOG.info("Start differing deploybles.")
new_name_list = [driver_dep_obj.name for driver_dep_obj in
new_driver_dep_list]
old_name_list = [driver_dep_obj.name for driver_dep_obj in
old_driver_dep_list]
new_name_list = [
driver_dep_obj.name for driver_dep_obj in new_driver_dep_list
]
old_name_list = [
driver_dep_obj.name for driver_dep_obj in old_driver_dep_list
]
same = set(new_name_list) & set(old_name_list)
added = set(new_name_list) - same
deleted = set(old_name_list) - same
@@ -234,14 +261,15 @@ class ConductorManager:
new_driver_dep_obj = new_driver_dep_list[new_name_list.index(a)]
new_driver_dep_obj.create(context, device_id, cpid_id)
try:
self.get_placement_needed_info_and_report(context,
new_driver_dep_obj,
host_rp)
self.get_placement_needed_info_and_report(
context, new_driver_dep_obj, host_rp
)
except Exception as exc:
LOG.info("Failed to add deployable %(deployable)s. "
"Reason: %(reason)s",
{'deployable': new_driver_dep_obj,
'reason': exc})
LOG.info(
"Failed to add deployable %(deployable)s. "
"Reason: %(reason)s",
{'deployable': new_driver_dep_obj, 'reason': exc},
)
new_driver_dep_obj.destroy(context, device_id)
rp_uuid = self.get_rp_uuid_from_obj(new_driver_dep_obj)
# TODO(All): If report deployable data to Placement raise
@@ -263,38 +291,50 @@ class ConductorManager:
attrs = new_driver_dep_obj.attribute_list
resource_class = [i.value for i in attrs if i.key == 'rc'][0]
inv_data = _gen_resource_inventory(
resource_class, dep_obj.num_accelerators)
resource_class, dep_obj.num_accelerators
)
self.placement_client.update_inventory(rp_uuid, inv_data)
# diff the internal layer: driver_attribute_list
new_attribute_list = []
if hasattr(new_driver_dep_obj, 'attribute_list'):
new_attribute_list = new_driver_dep_obj.attribute_list
self.drv_attr_make_diff(context, dep_obj.id,
old_driver_dep_obj.attribute_list,
new_attribute_list)
self.drv_attr_make_diff(
context,
dep_obj.id,
old_driver_dep_obj.attribute_list,
new_attribute_list,
)
# diff the internal layer: driver_attach_hanle_list
self.drv_ah_make_diff(context, dep_obj.id, cpid_id,
old_driver_dep_obj.attach_handle_list,
new_driver_dep_obj.attach_handle_list)
self.drv_ah_make_diff(
context,
dep_obj.id,
cpid_id,
old_driver_dep_obj.attach_handle_list,
new_driver_dep_obj.attach_handle_list,
)
def drv_attr_make_diff(self, context, dep_id, old_driver_attr_list,
new_driver_attr_list):
def drv_attr_make_diff(
self, context, dep_id, old_driver_attr_list, new_driver_attr_list
):
"""Diff new driver-side Attribute Object lists with the old one."""
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_rp_uuid_from_obj(driver_dep)
new_key_list = [driver_attr_obj.key for driver_attr_obj in
new_driver_attr_list]
old_key_list = [driver_attr_obj.key for driver_attr_obj in
old_driver_attr_list]
new_key_list = [
driver_attr_obj.key for driver_attr_obj in new_driver_attr_list
]
old_key_list = [
driver_attr_obj.key for driver_attr_obj in old_driver_attr_list
]
same = set(new_key_list) & set(old_key_list)
# key is deleted.
deleted = set(old_key_list) - same
for d in deleted:
old_driver_attr_obj = old_driver_attr_list[old_key_list.index(d)]
self.placement_client.delete_trait_by_name(
context, rp_uuid, old_driver_attr_obj.value)
context, rp_uuid, old_driver_attr_obj.value
)
old_driver_attr_obj.delete_by_key(context, dep_id, d)
# key is added.
added = set(new_key_list) - same
@@ -302,7 +342,8 @@ class ConductorManager:
new_driver_attr_obj = new_driver_attr_list[new_key_list.index(a)]
new_driver_attr_obj.create(context, dep_id)
self.placement_client.add_traits_to_rp(
rp_uuid, [new_driver_attr_obj.value])
rp_uuid, [new_driver_attr_obj.value]
)
# key is same, diff the value.
for s in same:
# value is not same, update
@@ -315,28 +356,36 @@ class ConductorManager:
# Update traits here.
if new_driver_attr_obj.key.startswith("trait"):
self.placement_client.delete_trait_by_name(
context, rp_uuid, old_driver_attr_obj.value)
context, rp_uuid, old_driver_attr_obj.value
)
self.placement_client.add_traits_to_rp(
rp_uuid, [new_driver_attr_obj.value])
rp_uuid, [new_driver_attr_obj.value]
)
# Update resource classes here.
if new_driver_attr_obj.key.startswith("rc"):
self.placement_client.ensure_resource_classes(
context, [new_driver_attr_obj.value])
context, [new_driver_attr_obj.value]
)
inv_data = _gen_resource_inventory(
new_driver_attr_obj.value, dep_obj.num_accelerators)
new_driver_attr_obj.value, dep_obj.num_accelerators
)
self.placement_client.update_inventory(rp_uuid, inv_data)
self.placement_client.delete_rc_by_name(
context, old_driver_attr_obj.value)
context, old_driver_attr_obj.value
)
@classmethod
def drv_ah_make_diff(cls, context, dep_id, cpid_id, old_driver_ah_list,
new_driver_ah_list):
def drv_ah_make_diff(
cls, context, dep_id, cpid_id, old_driver_ah_list, new_driver_ah_list
):
"""Diff new driver-side AttachHandle Object lists with the old one."""
LOG.info("Start differing attach_handles.")
new_info_list = [driver_ah_obj.attach_info for driver_ah_obj in
new_driver_ah_list]
old_info_list = [driver_ah_obj.attach_info for driver_ah_obj in
old_driver_ah_list]
new_info_list = [
driver_ah_obj.attach_info for driver_ah_obj in new_driver_ah_list
]
old_info_list = [
driver_ah_obj.attach_info for driver_ah_obj in old_driver_ah_list
]
same = set(new_info_list) & set(old_info_list)
LOG.info('new info list %s', new_info_list)
LOG.info('old info list %s', old_info_list)
@@ -356,41 +405,45 @@ class ConductorManager:
new_driver_ah_obj = new_driver_ah_list[new_info_list.index(s)]
old_driver_ah_obj = old_driver_ah_list[old_info_list.index(s)]
changed_key = ['attach_type']
ah_obj = AttachHandle.get_ah_by_depid_attachinfo(context,
dep_id, s)
ah_obj = AttachHandle.get_ah_by_depid_attachinfo(
context, dep_id, s
)
for c_k in changed_key:
if getattr(new_driver_ah_obj, c_k) != getattr(
old_driver_ah_obj, c_k):
old_driver_ah_obj, c_k
):
setattr(ah_obj, c_k, getattr(new_driver_ah_obj, c_k))
ah_obj.save(context)
def _get_root_provider(self, context, hostname):
try:
provider = self.placement_client.get(
"resource_providers?name=" + hostname).json()
"resource_providers?name=" + hostname
).json()
pr_uuid = provider["resource_providers"][0]["uuid"]
return pr_uuid
except (IndexError, KeyError):
raise exception.PlacementResourceProviderNotFound(
resource_provider=hostname)
resource_provider=hostname
)
def _get_sub_provider(self, context, parent, name):
old_sub_pr_uuid = str(uuid.uuid3(uuid.NAMESPACE_DNS,
str(name)))
old_sub_pr_uuid = str(uuid.uuid3(uuid.NAMESPACE_DNS, str(name)))
new_sub_pr_uuid = self.placement_client.ensure_resource_provider(
context, old_sub_pr_uuid,
name=name, parent_provider_uuid=parent)
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):
def provider_report(
self, context, name, resource_class, traits, total, parent
):
self.placement_client.ensure_resource_classes(
context, [resource_class])
sub_pr_uuid = self._get_sub_provider(
context, parent, name)
context, [resource_class]
)
sub_pr_uuid = self._get_sub_provider(context, parent, name)
result = _gen_resource_inventory(resource_class, total)
self.placement_client.update_inventory(sub_pr_uuid, result)
# traits = ["CUSTOM_FPGA_INTEL", "CUSTOM_FPGA_INTEL_ARRIA10",
@@ -401,16 +454,17 @@ class ConductorManager:
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):
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 str(i.key).startswith("trait")]
traits = [i.value for i in attrs if str(i.key).startswith("trait")]
total = obj.num_accelerators
rp_uuid = self.provider_report(context, pr_name, resource_class,
traits, total, parent_uuid)
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)
@@ -419,13 +473,16 @@ class ConductorManager:
return str(uuid.uuid3(uuid.NAMESPACE_DNS, str(obj.name)))
def _delete_provider_and_sub_providers(self, context, rp_uuid):
rp_in_tree = self.placement_client.get_providers_in_tree(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("Successfully delete resource provider %(rp_uuid)s",
{"rp_uuid": rp["uuid"]})
LOG.info(
"Successfully delete resource provider %(rp_uuid)s",
{"rp_uuid": rp["uuid"]},
)
if rp["uuid"] == rp_uuid:
break

View File

@@ -40,20 +40,23 @@ class ConductorAPI:
def __init__(self, topic=None):
super().__init__()
self.topic = topic or constants.CONDUCTOR_TOPIC
target = messaging.Target(topic=self.topic,
version='1.0')
target = messaging.Target(topic=self.topic, version='1.0')
serializer = objects_base.CyborgObjectSerializer()
self.client = rpc.get_client(target,
version_cap=self.RPC_API_VERSION,
serializer=serializer)
self.client = rpc.get_client(
target, version_cap=self.RPC_API_VERSION, serializer=serializer
)
def report_data(self, context, hostname, driver_device_list):
"""Signal to conductor service to update the cyborg DB
:param context: request context.
"""
cctxt = self.client.prepare(topic=self.topic)
cctxt.call(context, 'report_data', hostname=hostname,
driver_device_list=driver_device_list)
cctxt.call(
context,
'report_data',
hostname=hostname,
driver_device_list=driver_device_list,
)
def device_profile_create(self, context, obj_devprof):
"""Signal to conductor service to create a device_profile.
@@ -63,8 +66,9 @@ class ConductorAPI:
:returns: created device_profile object.
"""
cctxt = self.client.prepare(topic=self.topic)
return cctxt.call(context, 'device_profile_create',
obj_devprof=obj_devprof)
return cctxt.call(
context, 'device_profile_create', obj_devprof=obj_devprof
)
def device_profile_delete(self, context, obj_devprof):
"""Signal to conductor service to delete a device_profile.
@@ -72,8 +76,7 @@ class ConductorAPI:
:param obj_devprof: a device_profile object to delete.
"""
cctxt = self.client.prepare(topic=self.topic)
cctxt.call(context, 'device_profile_delete',
obj_devprof=obj_devprof)
cctxt.call(context, 'device_profile_delete', obj_devprof=obj_devprof)
def arq_create(self, context, obj_extarq, devprof_id):
"""Signal to conductor service to create an accelerator requests.
@@ -85,8 +88,9 @@ class ConductorAPI:
:returns: saved accelerator_requests object.
"""
cctxt = self.client.prepare(topic=self.topic)
return cctxt.call(context, 'arq_create', obj_extarq=obj_extarq,
devprof_id=devprof_id)
return cctxt.call(
context, 'arq_create', obj_extarq=obj_extarq, devprof_id=devprof_id
)
def arq_delete_by_uuid(self, context, arqs):
"""Signal to conductor service to delete accelerator requests by
@@ -116,5 +120,9 @@ class ConductorAPI:
:param valid_fields: Dict of valid fields
"""
cctxt = self.client.prepare(topic=self.topic)
return cctxt.call(context, 'arq_apply_patch', patch_list=patch_list,
valid_fields=valid_fields)
return cctxt.call(
context,
'arq_apply_patch',
patch_list=patch_list,
valid_fields=valid_fields,
)

View File

@@ -20,36 +20,49 @@ from cyborg.common.i18n import _
opts = [
cfg.ListOpt('enabled_drivers',
default=['fake_driver'],
help=_('The accelerator drivers enabled on this agent. Such '
'as intel_fpga_driver, inspur_fpga_driver,'
'nvidia_gpu_driver, intel_qat_driver,'
'inspur_nvme_ssd_driver, xilinx_fpga_driver, etc.')),
cfg.IntOpt('resource_provider_startup_retries',
default=3,
min=0,
help=_('Number of times to retry looking up the resource '
'provider in Placement during agent startup. Uses '
'exponential backoff (1s, 2s, 4s, ...) between '
'attempts. Set to 0 to fail immediately without '
'retrying.')),
cfg.StrOpt('resource_provider_name',
default=socket.getfqdn(),
sample_default='compute.fully.qualified.name',
help=_('Name of the compute resource provider in Placement. '
'This should match the hypervisor_hostname used by Nova '
'for this compute host. Defaults to socket.getfqdn() '
'which typically matches libvirt behavior. If resource '
'provider lookup fails with this name, Cyborg will fall '
'back to using CONF.host.')),
cfg.ListOpt(
'enabled_drivers',
default=['fake_driver'],
help=_(
'The accelerator drivers enabled on this agent. Such '
'as intel_fpga_driver, inspur_fpga_driver,'
'nvidia_gpu_driver, intel_qat_driver,'
'inspur_nvme_ssd_driver, xilinx_fpga_driver, etc.'
),
),
cfg.IntOpt(
'resource_provider_startup_retries',
default=3,
min=0,
help=_(
'Number of times to retry looking up the resource '
'provider in Placement during agent startup. Uses '
'exponential backoff (1s, 2s, 4s, ...) between '
'attempts. Set to 0 to fail immediately without '
'retrying.'
),
),
cfg.StrOpt(
'resource_provider_name',