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:
@@ -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:
|
||||
|
||||
24
.zuul.yaml
24
.zuul.yaml
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -15,7 +15,6 @@ import abc
|
||||
|
||||
|
||||
class GenericDriver(metaclass=abc.ABCMeta):
|
||||
|
||||
@abc.abstractmethod
|
||||
def discover(self):
|
||||
"""Discover a specified accelerator.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.')
|
||||
|
||||
@@ -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__():
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -24,7 +24,6 @@ LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PySPDK:
|
||||
|
||||
def __init__(self, pname):
|
||||
super().__init__()
|
||||
self.pid = None
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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__':
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:"
|
||||
|
||||
@@ -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."
|
||||
)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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'
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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',
|
||||