Merge "Switch contributor documentation to hardware types"
This commit is contained in:
commit
5b75b6e4f2
@ -30,49 +30,10 @@ Drivers
|
||||
|
||||
The internal driver API provides a consistent interface between the
|
||||
Conductor service and the driver implementations. A driver is defined by
|
||||
a class inheriting from the `BaseDriver`_ class, defining certain interfaces;
|
||||
each interface is an instance of the relevant driver module.
|
||||
|
||||
For example, a fake driver class might look like this::
|
||||
|
||||
class FakePower(base.PowerInterface):
|
||||
def get_properties(self):
|
||||
return {}
|
||||
|
||||
def validate(self, task):
|
||||
pass
|
||||
|
||||
def get_power_state(self, task):
|
||||
return states.NOSTATE
|
||||
|
||||
def set_power_state(self, task, power_state):
|
||||
pass
|
||||
|
||||
def reboot(self, task):
|
||||
pass
|
||||
|
||||
class FakeDriver(base.BaseDriver):
|
||||
def __init__(self):
|
||||
self.power = FakePower()
|
||||
|
||||
|
||||
There are three categories of driver interfaces:
|
||||
|
||||
- `Core` interfaces provide the essential functionality for Ironic within
|
||||
OpenStack, and may be depended upon by other services. All drivers
|
||||
must implement these interfaces. The Core interfaces are `power` and `deploy`.
|
||||
- `Standard` interfaces provide functionality beyond the needs of OpenStack,
|
||||
but which have been standardized across all drivers and becomes part of
|
||||
Ironic's API. If a driver implements this interface, it must adhere to the
|
||||
standard. This is presented to encourage vendors to work together with the
|
||||
Ironic project and implement common features in a consistent way, thus
|
||||
reducing the burden on consumers of the API. The Standard interfaces are
|
||||
`management`, `console`, `boot`, `inspect`, and `raid`.
|
||||
- The `Vendor` interface allows an exemption to the API contract when a vendor
|
||||
wishes to expose unique functionality provided by their hardware and is
|
||||
unable to do so within the `Core` or `Standard` interfaces. In this case,
|
||||
Ironic will merely relay the message from the API service to the appropriate
|
||||
driver.
|
||||
a *hardware type* deriving from the AbstractHardwareType_ class, defining
|
||||
supported *hardware interfaces*. See :doc:`/install/enabling-drivers`
|
||||
for a more detailed explanation. See :doc:`drivers` for an explanation on how
|
||||
to write new hardware types and interfaces.
|
||||
|
||||
Driver-Specific Periodic Tasks
|
||||
------------------------------
|
||||
@ -113,7 +74,7 @@ driver actions such as take-over or clean-up.
|
||||
|
||||
|
||||
.. _API service: webapi.html
|
||||
.. _BaseDriver: api/ironic.drivers.base.html#ironic.drivers.base.BaseDriver
|
||||
.. _AbstractHardwareType: api/ironic.drivers.hardware_type.html#ironic.drivers.hardware_type.AbstractHardwareType
|
||||
.. _Conductor service: api/ironic.conductor.manager.html
|
||||
.. _DB API: api/ironic.db.api.html
|
||||
.. _diskimage-builder: https://docs.openstack.org/diskimage-builder/latest/
|
||||
|
@ -270,14 +270,25 @@ want to run a MySQL server on it all the time).
|
||||
|
||||
#. Create a configuration file within the ironic source directory::
|
||||
|
||||
# generate a sample config
|
||||
tox -egenconfig
|
||||
|
||||
# copy sample config and modify it as necessary
|
||||
cp etc/ironic/ironic.conf.sample etc/ironic/ironic.conf.local
|
||||
|
||||
# disable auth since we are not running keystone here
|
||||
sed -i "s/#auth_strategy = keystone/auth_strategy = noauth/" etc/ironic/ironic.conf.local
|
||||
|
||||
# Use the 'fake_ipmitool' test driver
|
||||
sed -i "s/#enabled_drivers = pxe_ipmitool/enabled_drivers = fake_ipmitool/" etc/ironic/ironic.conf.local
|
||||
# use the 'fake-hardware' test hardware type
|
||||
sed -i "s/#enabled_hardware_types = .*/enabled_hardware_types = fake-hardware/" etc/ironic/ironic.conf.local
|
||||
|
||||
# use the 'fake' deploy and boot interfaces
|
||||
sed -i "s/#enabled_deploy_interfaces = .*/enabled_deploy_interfaces = fake/" etc/ironic/ironic.conf.local
|
||||
sed -i "s/#enabled_boot_interfaces = .*/enabled_boot_interfaces = fake/" etc/ironic/ironic.conf.local
|
||||
|
||||
# enable both fake and ipmitool management and power interfaces
|
||||
sed -i "s/#enabled_management_interfaces = .*/enabled_management_interfaces = fake,ipmitool/" etc/ironic/ironic.conf.local
|
||||
sed -i "s/#enabled_power_interfaces = .*/enabled_power_interfaces = fake,ipmitool/" etc/ironic/ironic.conf.local
|
||||
|
||||
# set a fake host name [useful if you want to test multiple services on the same host]
|
||||
sed -i "s/#host = .*/host = test-host/" etc/ironic/ironic.conf.local
|
||||
@ -321,7 +332,14 @@ present in the python virtualenv, and observe both services' debug outputs in
|
||||
the other two windows. This is a good way to test new features or play with the
|
||||
functionality without necessarily starting DevStack.
|
||||
|
||||
To get started, list the available commands and resources::
|
||||
To get started, export the following variables to point the client at the
|
||||
local instance of ironic and disable the authentication::
|
||||
|
||||
export OS_AUTH_TYPE=token_endpoint
|
||||
export OS_TOKEN=fake
|
||||
export OS_ENDPOINT=http://127.0.0.1:6385
|
||||
|
||||
Then list the available commands and resources::
|
||||
|
||||
# get a list of available commands
|
||||
openstack help baremetal
|
||||
@ -339,10 +357,13 @@ Here is an example walkthrough of creating a node::
|
||||
IPMI_USER="admin" # replace with the BMC's user name
|
||||
IPMI_PASS="pass" # replace with the BMC's password
|
||||
|
||||
# enroll the node with the "fake" deploy driver and the "ipmitool" power driver
|
||||
# Note that driver info may be added at node creation time with "--driver-info"
|
||||
# enroll the node with the fake hardware type and IPMI-based power and
|
||||
# management interfaces. Note that driver info may be added at node
|
||||
# creation time with "--driver-info"
|
||||
NODE=$(openstack baremetal node create \
|
||||
--driver fake_ipmitool \
|
||||
--driver fake-hardware \
|
||||
--management-interface ipmitool \
|
||||
--power-interface ipmitool \
|
||||
--driver-info ipmi_address=$IPMI_ADDR \
|
||||
--driver-info ipmi_username=$IPMI_USER \
|
||||
-f value -c uuid)
|
||||
@ -429,10 +450,9 @@ Switch to the stack user and clone DevStack::
|
||||
git clone https://git.openstack.org/openstack-dev/devstack.git devstack
|
||||
|
||||
Create devstack/local.conf with minimal settings required to enable Ironic.
|
||||
You can use either of two drivers for deploy: agent\_\* or pxe\_\*, see
|
||||
:doc:`/admin/interfaces/deploy` for explanation. An example local.conf that
|
||||
enables both types of drivers and uses the ``agent_ipmitool`` driver
|
||||
by default::
|
||||
An example local.conf that enables both ``direct`` and ``iscsi``
|
||||
:doc:`deploy interfaces </admin/interfaces/deploy>` and uses the ``ipmi``
|
||||
hardware type by default::
|
||||
|
||||
cd devstack
|
||||
cat >local.conf <<END
|
||||
@ -452,7 +472,7 @@ by default::
|
||||
# Disable nova novnc service, ironic does not support it anyway.
|
||||
disable_service n-novnc
|
||||
|
||||
# Enable Swift for agent_* drivers
|
||||
# Enable Swift for the direct deploy interface.
|
||||
enable_service s-proxy
|
||||
enable_service s-object
|
||||
enable_service s-container
|
||||
@ -464,7 +484,7 @@ by default::
|
||||
# Disable Cinder
|
||||
disable_service cinder c-sch c-api c-vol
|
||||
|
||||
# Swift temp URL's are required for agent_* drivers.
|
||||
# Swift temp URL's are required for the direct deploy interface
|
||||
SWIFT_ENABLE_TEMPURLS=True
|
||||
|
||||
# Create 3 virtual machines to pose as Ironic's baremetal nodes.
|
||||
@ -472,12 +492,19 @@ by default::
|
||||
IRONIC_BAREMETAL_BASIC_OPS=True
|
||||
DEFAULT_INSTANCE_TYPE=baremetal
|
||||
|
||||
# Enable Ironic drivers.
|
||||
IRONIC_ENABLED_DRIVERS=fake,agent_ipmitool,pxe_ipmitool
|
||||
# Enable additional hardware types, if needed.
|
||||
#IRONIC_ENABLED_HARDWARE_TYPES=ipmi,fake-hardware
|
||||
# Don't forget that many hardware types require enabling of additional
|
||||
# interfaces, most often power and management:
|
||||
#IRONIC_ENABLED_MANAGEMENT_INTERFACES=ipmitool,fake
|
||||
#IRONIC_ENABLED_POWER_INTERFACES=ipmitool,fake
|
||||
# The 'ipmi' hardware type's default deploy interface is 'iscsi'.
|
||||
# This would change the default to 'direct':
|
||||
#IRONIC_DEFAULT_DEPLOY_INTERFACE=direct
|
||||
|
||||
# Change this to alter the default driver for nodes created by devstack.
|
||||
# This driver should be in the enabled list above.
|
||||
IRONIC_DEPLOY_DRIVER=agent_ipmitool
|
||||
IRONIC_DEPLOY_DRIVER=ipmi
|
||||
|
||||
# The parameters below represent the minimum possible values to create
|
||||
# functional nodes.
|
||||
@ -518,9 +545,10 @@ by default::
|
||||
enable_plugin ironic https://git.openstack.org/openstack/ironic
|
||||
|
||||
.. note::
|
||||
When a \*_ipmitool driver is set and IRONIC_IS_HARDWARE variable is false devstack
|
||||
will automatically set up `VirtualBMC <https://github.com/openstack/virtualbmc>`_
|
||||
to control the power state of the virtual baremetal nodes.
|
||||
When the ``ipmi`` hardware type is used and IRONIC_IS_HARDWARE variable is
|
||||
``false`` devstack will automatically set up `VirtualBMC
|
||||
<https://github.com/openstack/virtualbmc>`_ to control the power state of
|
||||
the virtual baremetal nodes.
|
||||
|
||||
.. note::
|
||||
When running QEMU as non-root user (e.g. ``qemu`` on Fedora or ``libvirt-qemu`` on Ubuntu),
|
||||
|
@ -6,23 +6,115 @@ Pluggable Drivers
|
||||
|
||||
Ironic supports a pluggable driver model. This allows contributors to easily
|
||||
add new drivers, and operators to use third-party drivers or write their own.
|
||||
A driver is built at runtime from a *hardware type* and *hardware interfaces*.
|
||||
See :doc:`/install/enabling-drivers` for a detailed explanation of these
|
||||
concepts.
|
||||
|
||||
Drivers are loaded by the ironic-conductor service during initialization, by
|
||||
enumerating the python entrypoint "ironic.drivers" and attempting to load
|
||||
all drivers specified in the "enabled_drivers" configuration option. A
|
||||
complete list of drivers available on the system may be found by
|
||||
Hardware types and interfaces are loaded by the ``ironic-conductor`` service
|
||||
during initialization from the setuptools entrypoints ``ironic.hardware.types``
|
||||
and ``ironic.hardware.interfaces.<INTERFACE>`` where ``<INTERFACE>`` is an
|
||||
interface type (for example, ``deploy``). Only hardware types listed in the
|
||||
configuration option ``enabled_hardware_types`` and interfaces listed in
|
||||
configuration options ``enabled_<INTERFACE>_interfaces`` are loaded.
|
||||
A complete list of hardware types available on the system may be found by
|
||||
enumerating this entrypoint by running the following python script::
|
||||
|
||||
#!/usr/bin/env python
|
||||
|
||||
import pkg_resources as pkg
|
||||
print [p.name for p in pkg.iter_entry_points("ironic.drivers") if not p.name.startswith("fake")]
|
||||
print [p.name for p in pkg.iter_entry_points("ironic.hardware.types") if not p.name.startswith("fake")]
|
||||
|
||||
A list of drivers enabled in a running Ironic service may be found by issuing
|
||||
the following command against that API end point::
|
||||
|
||||
openstack baremetal driver list
|
||||
|
||||
.. note::
|
||||
This listing also includes *classic drivers* which are deprecated and
|
||||
are not covered by this guide.
|
||||
|
||||
Writing a hardware type
|
||||
-----------------------
|
||||
|
||||
A hardware type is a Python class, inheriting
|
||||
:py:class:`ironic.drivers.hardware_type.AbstractHardwareType` and listed in
|
||||
the setuptools entry point ``ironic.hardware.types``. Most of the real world
|
||||
hardware types inherit :py:class:`ironic.drivers.generic.GenericHardware`
|
||||
instead. This helper class provides useful implementations for interfaces that
|
||||
are usually the same for all hardware types, such as ``deploy``.
|
||||
|
||||
The minimum required interfaces are:
|
||||
|
||||
* :doc:`boot </admin/interfaces/boot>` that specifies how to boot ramdisks and
|
||||
instances on the hardware. A generic ``pxe`` implementation is provided
|
||||
by the ``GenericHardware`` base class.
|
||||
|
||||
* :doc:`deploy </admin/interfaces/deploy>` that orchestrates the deployment.
|
||||
A few common implementations are provided by the ``GenericHardware`` base
|
||||
class.
|
||||
|
||||
.. note::
|
||||
Most of the hardware types should not override this interface.
|
||||
|
||||
* `power` implements power actions for the hardware. These common
|
||||
implementations may be used, if supported by the hardware:
|
||||
|
||||
* :py:class:`ironic.drivers.modules.ipmitool.IPMIPower`
|
||||
* :py:class:`ironic.drivers.modules.redfish.power.RedfishPower`
|
||||
|
||||
Otherwise, you need to write your own implementation by subclassing
|
||||
:py:class:`ironic.drivers.base.PowerInterface` and providing missing methods.
|
||||
|
||||
.. note::
|
||||
Power actions in Ironic are blocking - methods of a power interface should
|
||||
not return until the power action is finished or errors out.
|
||||
|
||||
* `management` implements additional out-of-band management actions, such as
|
||||
setting a boot device. A few common implementations exist and may be used,
|
||||
if supported by the hardware:
|
||||
|
||||
* :py:class:`ironic.drivers.modules.ipmitool.IPMIManagement`
|
||||
* :py:class:`ironic.drivers.modules.redfish.management.RedfishManagement`
|
||||
|
||||
Some hardware types, such as ``snmp`` do not support out-of-band management.
|
||||
They use the fake implementation in
|
||||
:py:class:`ironic.drivers.modules.fake.FakeManagement` instead.
|
||||
|
||||
Otherwise, you need to write your own implementation by subclassing
|
||||
:py:class:`ironic.drivers.base.ManagementInterface` and providing missing
|
||||
methods.
|
||||
|
||||
Combine the interfaces in a hardware type by populating the lists of
|
||||
supported interfaces. These lists are prioritized, with the most preferred
|
||||
implementation first. For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class MyHardware(generic.GenericHardware):
|
||||
|
||||
@property
|
||||
def supported_management_interfaces(self):
|
||||
"""List of supported management interfaces."""
|
||||
return [MyManagement, ipmitool.IPMIManagement]
|
||||
|
||||
@property
|
||||
def supported_power_interfaces(self):
|
||||
"""List of supported power interfaces."""
|
||||
return [MyPower, ipmitool.IPMIPower]
|
||||
|
||||
.. note::
|
||||
In this example, all interfaces, except for ``management`` and ``power``
|
||||
are taken from the ``GenericHardware`` base class.
|
||||
|
||||
Finally, give the new hardware type and new interfaces human-friendly names and
|
||||
create entry points for them in the ``setup.cfg`` file::
|
||||
|
||||
ironic.hardware.types =
|
||||
my-hardware = ironic.drivers.my_hardware:MyHardware
|
||||
ironic.hardware.interfaces.power =
|
||||
my-power = ironic.drivers.modules.my_hardware:MyPower
|
||||
ironic.hardware.interfaces.management =
|
||||
my-management = ironic.drivers.modules.my_hardware:MyManagement
|
||||
|
||||
Supported Drivers
|
||||
-----------------
|
||||
|
@ -61,7 +61,6 @@ the developer community about any implementation using this functionality.
|
||||
:maxdepth: 1
|
||||
|
||||
Driver Overview <drivers>
|
||||
Driver Base Class Definition <api/ironic.drivers.base>
|
||||
Writing "vendor_passthru" methods <vendor-passthru>
|
||||
Third party continuous integration testing <third-party-ci>
|
||||
|
||||
|
@ -43,7 +43,7 @@ description for DevStack is at :ref:`deploy_devstack`.
|
||||
enable_service q-meta
|
||||
enable_service neutron
|
||||
|
||||
# Enable Swift for agent_* drivers
|
||||
# Enable Swift for the direct deploy interface.
|
||||
enable_service s-proxy
|
||||
enable_service s-object
|
||||
enable_service s-container
|
||||
@ -55,7 +55,7 @@ description for DevStack is at :ref:`deploy_devstack`.
|
||||
# Disable Heat
|
||||
disable_service heat h-api h-api-cfn h-api-cw h-eng
|
||||
|
||||
# Swift temp URL's are required for agent_* drivers.
|
||||
# Swift temp URL's are required for the direct deploy interface.
|
||||
SWIFT_ENABLE_TEMPURLS=True
|
||||
|
||||
# Create 3 virtual machines to pose as Ironic's baremetal nodes.
|
||||
@ -63,12 +63,18 @@ description for DevStack is at :ref:`deploy_devstack`.
|
||||
IRONIC_BAREMETAL_BASIC_OPS=True
|
||||
DEFAULT_INSTANCE_TYPE=baremetal
|
||||
|
||||
# Enable Ironic drivers.
|
||||
IRONIC_ENABLED_DRIVERS=fake,agent_ipmitool,pxe_ipmitool
|
||||
# Enable additional hardware types, if needed.
|
||||
#IRONIC_ENABLED_HARDWARE_TYPES=ipmi,fake-hardware
|
||||
# Don't forget that many hardware types require enabling of additional
|
||||
# interfaces, most often power and management:
|
||||
#IRONIC_ENABLED_MANAGEMENT_INTERFACES=ipmitool,fake
|
||||
#IRONIC_ENABLED_POWER_INTERFACES=ipmitool,fake
|
||||
# The default deploy interface is 'iscsi', you can use 'direct' with
|
||||
#IRONIC_DEFAULT_DEPLOY_INTERFACE=direct
|
||||
|
||||
# Change this to alter the default driver for nodes created by devstack.
|
||||
# This driver should be in the enabled list above.
|
||||
IRONIC_DEPLOY_DRIVER=agent_ipmitool
|
||||
IRONIC_DEPLOY_DRIVER=ipmi
|
||||
|
||||
# The parameters below represent the minimum possible values to create
|
||||
# functional nodes.
|
||||
|
@ -68,7 +68,7 @@ configured in Neutron.
|
||||
# Disable nova novnc service, ironic does not support it anyway.
|
||||
disable_service n-novnc
|
||||
|
||||
# Enable Swift for agent_* drivers
|
||||
# Enable Swift for the direct deploy interface.
|
||||
enable_service s-proxy
|
||||
enable_service s-object
|
||||
enable_service s-container
|
||||
@ -83,19 +83,25 @@ configured in Neutron.
|
||||
# Disable Tempest
|
||||
disable_service tempest
|
||||
|
||||
# Swift temp URL's are required for agent_* drivers.
|
||||
# Swift temp URL's are required for the direct deploy interface.
|
||||
SWIFT_ENABLE_TEMPURLS=True
|
||||
|
||||
# Create 3 virtual machines to pose as Ironic's baremetal nodes.
|
||||
IRONIC_VM_COUNT=3
|
||||
IRONIC_BAREMETAL_BASIC_OPS=True
|
||||
|
||||
# Enable Ironic drivers.
|
||||
IRONIC_ENABLED_DRIVERS=fake,agent_ipmitool,pxe_ipmitool
|
||||
# Enable additional hardware types, if needed.
|
||||
#IRONIC_ENABLED_HARDWARE_TYPES=ipmi,fake-hardware
|
||||
# Don't forget that many hardware types require enabling of additional
|
||||
# interfaces, most often power and management:
|
||||
#IRONIC_ENABLED_MANAGEMENT_INTERFACES=ipmitool,fake
|
||||
#IRONIC_ENABLED_POWER_INTERFACES=ipmitool,fake
|
||||
# The default deploy interface is 'iscsi', you can use 'direct' with
|
||||
#IRONIC_DEFAULT_DEPLOY_INTERFACE=direct
|
||||
|
||||
# Change this to alter the default driver for nodes created by devstack.
|
||||
# This driver should be in the enabled list above.
|
||||
IRONIC_DEPLOY_DRIVER=agent_ipmitool
|
||||
IRONIC_DEPLOY_DRIVER=ipmi
|
||||
|
||||
# The parameters below represent the minimum possible values to create
|
||||
# functional nodes.
|
||||
|
@ -10,25 +10,30 @@ a driver.
|
||||
The first thing to note is that the Ironic API supports two vendor
|
||||
endpoints: A driver vendor passthru and a node vendor passthru.
|
||||
|
||||
* The driver vendor passthru allows drivers to expose a custom top-level
|
||||
* The ``VendorInterface`` allows hardware types to expose a custom top-level
|
||||
functionality which is not specific to a Node. For example, let's say
|
||||
the driver `pxe_ipmitool` exposed a method called `authentication_types`
|
||||
the driver `ipmi` exposed a method called `authentication_types`
|
||||
that would return what are the authentication types supported. It could
|
||||
be accessed via the Ironic API like:
|
||||
|
||||
::
|
||||
::
|
||||
|
||||
GET http://<address>:<port>/v1/drivers/pxe_ipmitool/vendor_passthru/authentication_types
|
||||
GET http://<address>:<port>/v1/drivers/ipmi/vendor_passthru/authentication_types
|
||||
|
||||
.. warning::
|
||||
The Bare Metal API currently only allows to use driver passthru for the
|
||||
default ``vendor`` interface implementation for a given hardware type.
|
||||
This limitation will be lifted in the future.
|
||||
|
||||
* The node vendor passthru allows drivers to expose custom functionality
|
||||
on per-node basis. For example the same driver `pxe_ipmitool` exposing a
|
||||
on per-node basis. For example the same driver `ipmi` exposing a
|
||||
method called `send_raw` that would send raw bytes to the BMC, the method
|
||||
also receives a parameter called `raw_bytes` which the value would be
|
||||
the bytes to be sent. It could be accessed via the Ironic API like:
|
||||
|
||||
::
|
||||
::
|
||||
|
||||
POST {'raw_bytes': '0x01 0x02'} http://<address>:<port>/v1/nodes/<node UUID>/vendor_passthru/send_raw
|
||||
POST {'raw_bytes': '0x01 0x02'} http://<address>:<port>/v1/nodes/<node UUID>/vendor_passthru/send_raw
|
||||
|
||||
|
||||
Writing Vendor Methods
|
||||
@ -106,11 +111,11 @@ Both decorators accept these parameters:
|
||||
if you want to use a different name this parameter is where this name
|
||||
can be set. For example:
|
||||
|
||||
.. code-block:: python
|
||||
.. code-block:: python
|
||||
|
||||
@passthru(['PUT'], method="alternative_name")
|
||||
def name(self, task, **kwargs):
|
||||
...
|
||||
@passthru(['PUT'], method="alternative_name")
|
||||
def name(self, task, **kwargs):
|
||||
...
|
||||
|
||||
* description: A string containing a nice description about what that
|
||||
method is supposed to do. Defaults to "" (empty string).
|
||||
@ -138,6 +143,24 @@ parameter:
|
||||
``ironic-conductor`` process. This can lead to starvation of the
|
||||
thread pool, resulting in a denial of service.
|
||||
|
||||
Give the new vendor interface implementation a human-friendly name and create
|
||||
an entry point for it in the ``setup.cfg``::
|
||||
|
||||
ironic.hardware.interfaces.vendor =
|
||||
example = ironic.drivers.modules.example:ExampleVendor
|
||||
|
||||
Finally, add it to the list of supported vendor interfaces for relevant
|
||||
hardware types, for example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class ExampleHardware(generic.GenericHardware):
|
||||
...
|
||||
|
||||
@property
|
||||
def supported_vendor_interfaces(self):
|
||||
return [example.ExampleVendor]
|
||||
|
||||
Backwards Compatibility
|
||||
=======================
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user