Rename discoverd -> inspector

As agreed on the summit I'm renaming the python modules
and doing some adjustments:
* This is a breaking change, so version is bumped to 2.0.0
* Used this chance to split conf options over proper sections
* RELEASES.rst is gone; it's too hard to keep it up-to-date;
  anyway git does better job at doing history
* Dropped deprecated option ports_for_inactive_interfaces
* Dropped old /v1/discover endpoint and associated client call
* No longer set on_discovery and newly_discovered in Node.extra
  (deprecated since 1.0.0, superseded by the get status API)
* Default firewall chain name is "ironic-inspector" and
  is configurable

Notes:
* Some links will be updated after real move.
* Stable branches will probably use the old name.
* Some usage of discovery word is left in context of
  "discovered data"
* DIB element will probably be deprecated, so leaving it
  alone for now.
* Some usages of word "discovery" in the README will be updated
  later to make this patch a bit smaller
* Ramdisk code will be moved to IPA, so not touching it too much

Change-Id: I59f1f5bfb1248ab69973dab845aa028df493054e
This commit is contained in:
Dmitry Tantsur 2015-05-26 07:47:55 +02:00
parent b16c7b2223
commit d6404d2f99
50 changed files with 695 additions and 947 deletions

View File

@ -59,22 +59,22 @@ Github::
Run the service with::
.tox/py27/bin/ironic-discoverd --config-file example.conf
.tox/py27/bin/ironic-inspector --config-file example.conf
Of course you may have to modify ``example.conf`` to match your OpenStack
environment.
You can develop and test **ironic-discoverd** using
You can develop and test **ironic-inspector** using
`DevStack <http://docs.openstack.org/developer/devstack/>`_ plugin - see
https://etherpad.openstack.org/p/DiscoverdDevStack for the current status.
Writing a Plugin
~~~~~~~~~~~~~~~~
**ironic-discoverd** allows to hook your code into data processing chain after
**ironic-inspector** allows to hook your code into data processing chain after
introspection. Inherit ``ProcessingHook`` class defined in
`ironic_discoverd.plugins.base
<https://github.com/stackforge/ironic-discoverd/blob/master/ironic_discoverd/plugins/base.py>`_
`ironic_inspector.plugins.base
<https://github.com/stackforge/ironic-discoverd/blob/master/ironic_inspector/plugins/base.py>`_
module and overwrite any or both of the following methods:
``before_processing(node_info)``

View File

@ -1,7 +1,7 @@
HTTP API
--------
By default **ironic-discoverd** listens on ``0.0.0.0:5050``, port
By default **ironic-inspector** listens on ``0.0.0.0:5050``, port
can be changed in configuration. Protocol is JSON over HTTP.
The HTTP API consist of these endpoints:
@ -17,7 +17,7 @@ Requires X-Auth-Token header with Keystone token for authentication.
Optional parameters:
* ``new_ipmi_password`` if set, **ironic-discoverd** will try to set IPMI
* ``new_ipmi_password`` if set, **ironic-inspector** will try to set IPMI
password on the machine to this value. Power credentials validation will be
skipped and manual power on will be required. See `Setting IPMI
credentials`_ for details.
@ -28,7 +28,7 @@ Optional parameters:
Response:
* 202 - accepted discovery request
* 202 - accepted introspection request
* 400 - bad request
* 401, 403 - missing or invalid authentication
* 404 - node cannot be found
@ -36,7 +36,7 @@ Response:
Get Introspection Status
~~~~~~~~~~~~~~~~~~~~~~~~
``GET /v1/introspection/<UUID>`` get hardware discovery status.
``GET /v1/introspection/<UUID>`` get hardware introspection status.
Requires X-Auth-Token header with Keystone token for authentication.
@ -49,14 +49,14 @@ Response:
Response body: JSON dictionary with keys:
* ``finished`` (boolean) whether discovery is finished
* ``finished`` (boolean) whether introspection is finished
* ``error`` error string or ``null``
Ramdisk Callback
~~~~~~~~~~~~~~~~
``POST /v1/continue`` internal endpoint for the discovery ramdisk to post
back discovered data. Should not be used for anything other than implementing
``POST /v1/continue`` internal endpoint for the ramdisk to post back
discovered data. Should not be used for anything other than implementing
the ramdisk. Request body: JSON dictionary with at least these keys:
* ``cpus`` number of CPU

View File

@ -1,6 +1,6 @@
include example.conf
include LICENSE
include ironic-discoverd.8
include ironic-inspector.8
include requirements.txt
include test-requirements.txt
include plugin-requirements.txt

View File

@ -7,14 +7,14 @@ properties discovery is a process of getting hardware parameters required for
scheduling from a bare metal node, given it's power management credentials
(e.g. IPMI address, user name and password).
A special *discovery ramdisk* is required to collect the information on a
A special ramdisk is required to collect the information on a
node. The default one can be built using diskimage-builder_ and
`ironic-discoverd-ramdisk element`_ (see Configuration_ below).
Support for **ironic-discoverd** is present in `Tuskar UI`_ --
Support for **ironic-inspector** is present in `Tuskar UI`_ --
OpenStack Horizon plugin for TripleO_.
**ironic-discoverd** requires OpenStack Juno (2014.2) release or newer.
**ironic-inspector** requires OpenStack Kilo (2015.1) release or newer.
Please use launchpad_ to report bugs and ask questions. Use PyPI_ for
downloads and accessing the released version of this README. Refer to
@ -27,12 +27,15 @@ CONTRIBUTING.rst_ for instructions on how to contribute.
.. _PyPI: https://pypi.python.org/pypi/ironic-discoverd
.. _CONTRIBUTING.rst: https://github.com/stackforge/ironic-discoverd/blob/master/CONTRIBUTING.rst
.. note::
**ironic-inspector** was called *ironic-discoverd* before version 2.0.0.
Workflow
--------
Usual hardware introspection flow is as follows:
* Operator installs undercloud with **ironic-discoverd**
* Operator installs undercloud with **ironic-inspector**
(e.g. using instack-undercloud_).
* Operator enrolls nodes into Ironic either manually or by uploading CSV file
@ -43,19 +46,18 @@ Usual hardware introspection flow is as follows:
`Node States`_.
* Operator sends nodes on introspection either manually using
**ironic-discoverd** API (see Usage_) or again via `Tuskar UI`_.
**ironic-inspector** API (see Usage_) or again via `Tuskar UI`_.
* On receiving node UUID **ironic-discoverd**:
* On receiving node UUID **ironic-inspector**:
* validates node power credentials, current power and provisioning states,
* allows firewall access to PXE boot service for the nodes,
* issues reboot command for the nodes, so that they boot the
discovery ramdisk.
* issues reboot command for the nodes, so that they boot the ramdisk.
* The discovery ramdisk collects the required information and posts it back
to **ironic-discoverd**.
* The ramdisk collects the required information and posts it back to
**ironic-inspector**.
* On receiving data from the discovery ramdisk, **ironic-discoverd**:
* On receiving data from the ramdisk, **ironic-inspector**:
* validates received data,
* finds the node in Ironic database using it's BMC address (MAC address in
@ -63,13 +65,13 @@ Usual hardware introspection flow is as follows:
* fills missing node properties with received data and creates missing ports.
.. note::
**ironic-discoverd** is responsible to create Ironic ports for some or all
NIC's found on the node. **ironic-discoverd** is also capable of
**ironic-inspector** is responsible to create Ironic ports for some or all
NIC's found on the node. **ironic-inspector** is also capable of
deleting ports that should not be present. There are two important
configuration options that affect this behavior: ``add_ports`` and
``keep_ports`` (please refer to ``example.conf`` for detailed explanation).
Default values as of **ironic-discoverd** 1.1.0 are ``add_ports=pxe``,
Default values as of **ironic-inspector** 1.1.0 are ``add_ports=pxe``,
``keep_ports=all``, which means that only one port will be added, which is
associated with NIC the ramdisk PXE booted from. No ports will be deleted.
This setting ensures that deploying on introspected nodes will succeed
@ -96,32 +98,23 @@ package and should be done separately.
Installation
------------
**ironic-discoverd** is available as an RPM from Fedora 22 repositories or from
Juno (and later) `RDO <https://www.rdoproject.org/>`_ for Fedora 20, 21
and EPEL 7. It will be installed and preconfigured if you used
instack-undercloud_ to build your undercloud.
Otherwise after enabling required repositories install it using::
Install from PyPI_ (you may want to use virtualenv to isolate your
environment)::
yum install openstack-ironic-discoverd
pip install ironic-inspector
To install only Python packages (including the client), use::
yum install python-ironic-discoverd
Alternatively (e.g. if you need the latest version), you can install package
from PyPI_ (you may want to use virtualenv to isolate your environment)::
pip install ironic-discoverd
Finally, there is a `DevStack <http://docs.openstack.org/developer/devstack/>`_
plugin for **ironic-discoverd** - see
Also there is a `DevStack <http://docs.openstack.org/developer/devstack/>`_
plugin for **ironic-inspector** - see
https://etherpad.openstack.org/p/DiscoverdDevStack for the current status.
Finally, some distributions (e.g. Fedora) provide **ironic-inspector**
packaged, some of them - under its old name *ironic-discoverd*.
Configuration
~~~~~~~~~~~~~
Copy ``example.conf`` to some permanent place
(``/etc/ironic-discoverd/discoverd.conf`` is what is used in the RPM).
(e.g. ``/etc/ironic-inspector/inspector.conf``).
Fill in at least these configuration values:
* ``os_username``, ``os_password``, ``os_tenant_name`` - Keystone credentials
@ -130,7 +123,7 @@ Fill in at least these configuration values:
* ``os_auth_url``, ``identity_uri`` - Keystone endpoints for validating
authentication tokens and checking user roles;
* ``database`` - where you want **ironic-discoverd** SQLite database
* ``database`` - where you want **ironic-inspector** SQLite database
to be placed;
* ``dnsmasq_interface`` - interface on which ``dnsmasq`` (or another DHCP
@ -160,8 +153,8 @@ As for PXE boot environment, you'll need:
is always advised).
* You need PXE boot server (e.g. *dnsmasq*) running on **the same** machine as
**ironic-discoverd**. Don't do any firewall configuration:
**ironic-discoverd** will handle it for you. In **ironic-discoverd**
**ironic-inspector**. Don't do any firewall configuration:
**ironic-inspector** will handle it for you. In **ironic-inspector**
configuration file set ``dnsmasq_interface`` to the interface your
PXE boot server listens on. Here is an example *dnsmasq.conf*::
@ -191,15 +184,17 @@ As for PXE boot environment, you'll need:
instead of ``discoverd_callback_url``. Modify ``pxelinux.cfg/default``
accordingly if you have one of these.
Here is *discoverd.conf* you may end up with::
Here is *inspector.conf* you may end up with::
[discoverd]
[DEFAULT]
debug = false
[ironic]
identity_uri = http://127.0.0.1:35357
os_auth_url = http://127.0.0.1:5000/v2.0
os_username = admin
os_password = password
os_tenant_name = admin
[firewall]
dnsmasq_interface = br-ctlplane
.. note::
@ -211,40 +206,41 @@ Here is *discoverd.conf* you may end up with::
Running
~~~~~~~
If you installed **ironic-discoverd** from the RPM, you already have
If you installed **ironic-inspector** from the RPM, you might already have
a *systemd* unit, so you can::
systemctl enable openstack-ironic-discoverd
systemctl start openstack-ironic-discoverd
systemctl enable openstack-ironic-inspector
systemctl start openstack-ironic-inspector
Otherwise run as ``root``::
ironic-discoverd --config-file /etc/ironic-discoverd/discoverd.conf
ironic-inspector --config-file /etc/ironic-inspector/inspector.conf
.. note::
Running as ``root`` is not required if **ironic-discoverd** does not
Running as ``root`` is not required if **ironic-inspector** does not
manage the firewall (i.e. ``manage_firewall`` is set to ``false`` in the
configuration file).
A good starting point for writing your own *systemd* unit should be `one used
in Fedora <http://pkgs.fedoraproject.org/cgit/openstack-ironic-discoverd.git/plain/openstack-ironic-discoverd.service>`_.
in Fedora <http://pkgs.fedoraproject.org/cgit/openstack-ironic-discoverd.git/plain/openstack-ironic-discoverd.service>`_
(note usage of old name).
Usage
-----
**ironic-discoverd** has a simple client library for Python and a CLI tool
**ironic-inspector** has a simple client library for Python and a CLI tool
bundled with it.
Client library is in module ``ironic_discoverd.client``, every call
Client library is in module ``ironic_inspector.client``, every call
accepts additional optional arguments:
* ``base_url`` **ironic-discoverd** API endpoint, defaults to
* ``base_url`` **ironic-inspector** API endpoint, defaults to
``127.0.0.1:5050``,
* ``auth_token`` Keystone authentication token.
CLI tool is based on OpenStackClient_ with prefix
``openstack baremetal introspection``. Accepts optional argument
``--discoverd-url`` with the **ironic-discoverd** API endpoint.
``--inspector-url`` with the **ironic-inspector** API endpoint.
* **Start introspection on a node**:
@ -256,7 +252,7 @@ CLI tool is based on OpenStackClient_ with prefix
* ``uuid`` - Ironic node UUID;
* ``new_ipmi_username`` and ``new_ipmi_password`` - if these are set,
**ironic-discoverd** will switch to manual power on and assigning IPMI
**ironic-inspector** will switch to manual power on and assigning IPMI
credentials on introspection. See `Setting IPMI Credentials`_ for details.
* **Query introspection status**:
@ -279,7 +275,7 @@ Using from Ironic API
~~~~~~~~~~~~~~~~~~~~~
Ironic Kilo introduced support for hardware introspection under name of
"inspection". **ironic-discoverd** introspection is supported for some generic
"inspection". **ironic-inspector** introspection is supported for some generic
drivers, please refer to `Ironic inspection documentation`_ for details.
Node States
@ -312,17 +308,17 @@ Node States
Setting IPMI Credentials
~~~~~~~~~~~~~~~~~~~~~~~~
If you have physical access to your nodes, you can use **ironic-discoverd** to
If you have physical access to your nodes, you can use **ironic-inspector** to
set IPMI credentials for them without knowing the original ones. The workflow
is as follows:
* Ensure nodes will PXE boot on the right network by default.
* Set ``enable_setting_ipmi_credentials = true`` in the **ironic-discoverd**
* Set ``enable_setting_ipmi_credentials = true`` in the **ironic-inspector**
configuration file.
* Enroll nodes in Ironic with setting their ``ipmi_address`` only. This step
allows **ironic-discoverd** to distinguish nodes.
allows **ironic-inspector** to distinguish nodes.
* Set maintenance mode on nodes. That's an important step, otherwise Ironic
might interfere with introspection process.
@ -336,16 +332,16 @@ is as follows:
* Manually power on the nodes and wait.
* After introspection is finished (watch nodes power state or use
**ironic-discoverd** status API) you can turn maintenance mode off.
**ironic-inspector** status API) you can turn maintenance mode off.
Note that due to various limitations on password value in different BMC,
**ironic-discoverd** will only accept passwords with length between 1 and 20
**ironic-inspector** will only accept passwords with length between 1 and 20
consisting only of letters and numbers.
Plugins
~~~~~~~
**ironic-discoverd** heavily relies on plugins for data processing. Even the
**ironic-inspector** heavily relies on plugins for data processing. Even the
standard functionality is largely based on plugins. Set ``processing_hooks``
option in the configuration file to change the set of plugins to be run on
introspection data. Note that order does matter in this option.
@ -389,7 +385,7 @@ Errors when starting introspection
In Kilo release with *python-ironicclient* 0.5.0 or newer Ironic
defaults to reporting provision state ``AVAILABLE`` for newly enrolled
nodes. **ironic-discoverd** will refuse to conduct introspection in
nodes. **ironic-inspector** will refuse to conduct introspection in
this state, as such nodes are supposed to be used by Nova for scheduling.
See `Node States`_ for instructions on how to put nodes into
the correct state.
@ -403,7 +399,7 @@ There may be 3 reasons why introspection can time out after some time
#. Fatal failure in processing chain before node was found in the local cache.
See `Troubleshooting data processing`_ for the hints.
#. Failure to load discovery ramdisk on the target node. See `Troubleshooting
#. Failure to load the ramdisk on the target node. See `Troubleshooting
PXE boot`_ for the hints.
#. Failure during ramdisk run. See `Troubleshooting ramdisk run`_ for the
@ -411,17 +407,19 @@ There may be 3 reasons why introspection can time out after some time
Troubleshooting data processing
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In this case **ironic-discoverd** logs should give a good idea what went wrong.
In this case **ironic-inspector** logs should give a good idea what went wrong.
E.g. for RDO or Fedora the following command will output the full log::
sudo journalctl -u openstack-ironic-discoverd
sudo journalctl -u openstack-ironic-inspector
(use ``openstack-ironic-discoverd`` for version < 2.0.0).
.. note::
Service name and specific command might be different for other Linux
distributions.
distributions (and for old version of **ironic-inspector**).
If ``ramdisk_error`` plugin is enabled and ``ramdisk_logs_dir`` configuration
option is set, **ironic-discoverd** will store logs received from the ramdisk
option is set, **ironic-inspector** will store logs received from the ramdisk
to the ``ramdisk_logs_dir`` directory. This depends, however, on the ramdisk
implementation.
@ -436,7 +434,9 @@ on. You may need to restart introspection.
Another source of information is DHCP and TFTP server logs. Their location
depends on how the servers were installed and run. For RDO or Fedora use::
$ sudo journalctl -u openstack-ironic-discoverd-dnsmasq
$ sudo journalctl -u openstack-ironic-inspector-dnsmasq
(use ``openstack-ironic-discoverd-dnsmasq`` for version < 2.0.0).
The last resort is ``tcpdump`` utility. Use something like
::
@ -458,7 +458,7 @@ sure that:
propagating,
#. there is no additional firewall rules preventing access to port 67 on the
machine where *ironic-discoverd* and its DHCP server are installed.
machine where *ironic-inspector* and its DHCP server are installed.
If you see node receiving DHCP address and then failing to get kernel and/or
ramdisk or to boot them, make sure that:

View File

@ -1,211 +0,0 @@
Release Notes
-------------
1.2 Series
~~~~~~~~~~
See `1.2.0 release tracking page`_ for details.
**Upgrade Notes**
**Major Features**
**Other Changes**
**Known Issues**
.. _1.2.0 release tracking page: https://bugs.launchpad.net/ironic-discoverd/+milestone/1.2.0
1.1 Series
~~~~~~~~~~
See `1.1.0 release tracking page`_ for details.
**Upgrade Notes**
* This version no longer supports ancient ramdisks that sent ``macs`` instead
of ``interfaces``. It also raises exception if no valid interfaces were
found after processing.
* ``identity_uri`` parameter should be set to Keystone admin endpoint.
* ``overwrite_existing`` is now enabled by default.
* Running the service as
::
$ ironic-discoverd /path/to/config
is no longer supported, use
::
$ ironic-discoverd --config-file /path/to/config
**Major Features**
* Default to only creating a port for the NIC that the ramdisk was PXE booted
from, if such information is provided by ramdisk as ``boot_interface`` field.
Adjustable by ``add_ports`` option.
See `better-boot-interface-detection blueprint
<https://blueprints.launchpad.net/ironic-discoverd/+spec/better-boot-interface-detection>`_
for details.
* `Setting IPMI Credentials`_ feature is considered stable now and is exposed
in the client. It still needs to be enabled via configuration.
See `setup-ipmi-credentials-take2 blueprint
<https://blueprints.launchpad.net/ironic-discoverd/+spec/setup-ipmi-credentials-take2>`_
for what changed since 1.0.0 (tl;dr: everything).
* Proper CLI tool implemented as a plugin for OpenStackClient_.
Also client now properly sets error message from the server in its exception.
This might be a breaking change, if you relied on exception message
previously.
* The default value for ``overwrite_existing`` configuration option was
flipped, matching the default behavior for Ironic inspection.
* Switch to `oslo.config <http://docs.openstack.org/developer/oslo.config/>`_
for configuration management (many thanks to Yuiko Takada).
**Other Changes**
* New option ``add_ports`` allows precise control over which ports to add,
replacing deprecated ``ports_for_inactive_interfaces``.
* Experimental plugin ``edeploy`` to use with `eDeploy hardware detection and
classification utilities`_.
See `eDeploy blueprint`_ for details.
* Plugin ``root_device_hint`` for in-band root device discovery.
* Plugin ``ramdisk_error`` is now enabled by default.
* Serious authentication issues were fixed, ``keystonemiddleware`` is a new
requirement.
* Basic support for i18n via oslo.i18n.
**Known Issues**
.. _1.1.0 release tracking page: https://bugs.launchpad.net/ironic-discoverd/+milestone/1.1.0
.. _Setting IPMI Credentials: https://github.com/stackforge/ironic-discoverd#setting-ipmi-credentials
.. _OpenStackClient: http://docs.openstack.org/developer/python-openstackclient/
.. _eDeploy hardware detection and classification utilities: https://pypi.python.org/pypi/hardware
.. _eDeploy blueprint: https://blueprints.launchpad.net/ironic-discoverd/+spec/edeploy
1.0 Series
~~~~~~~~~~
1.0 is the first feature-complete release series. It's also the first series
to follow standard OpenStack processes from the beginning. All 0.2 series
users are advised to upgrade.
See `1.0.0 release tracking page`_ for details.
**1.0.1 release**
This maintenance fixed serious problem with authentication and unfortunately
brought new upgrade requirements:
* Dependency on *keystonemiddleware*;
* New configuration option ``identity_uri``, defaulting to localhost.
**Upgrade notes**
Action required:
* Fill in ``database`` option in the configuration file before upgrading.
* Stop relying on **ironic-discoverd** setting maintenance mode itself.
* Stop relying on ``discovery_timestamp`` node extra field.
Action recommended:
* Switch your init scripts to use ``ironic-discoverd --config-file <path>``
instead of just ``ironic-discoverd <path>``.
* Stop relying on ``on_discovery`` and ``newly_discovered`` being set in node
``extra`` field during and after introspection. Use new get status HTTP
endpoint and client API instead.
* Switch from ``discover`` to ``introspect`` HTTP endpoint and client API.
**Major features**
* Introspection now times out by default, set ``timeout`` option to alter.
* New API ``GET /v1/introspection/<uuid>`` and ``client.get_status`` for
getting discovery status.
See `get-status-api blueprint`_ for details.
* New API ``POST /v1/introspection/<uuid>`` and ``client.introspect``
is now used to initiate discovery, ``/v1/discover`` is deprecated.
See `v1 API reform blueprint`_ for details.
* ``/v1/continue`` is now sync:
* Errors are properly returned to the caller
* This call now returns value as a JSON dict (currently empty)
* Add support for plugins that hook into data processing pipeline. Refer to
Plugins_ for information on bundled plugins and to CONTRIBUTING.rst_ for
information on how to write your own.
See `plugin-architecture blueprint`_ for details.
* Support for OpenStack Kilo release and new Ironic state machine -
see `Kilo state machine blueprint`_.
As a side effect, no longer depend on maintenance mode for introspection.
Stop putting node in maintenance mode before introspection.
* Cache nodes under introspection in a local SQLite database.
``database`` configuration option sets where to place this database.
Improves performance by making less calls to Ironic API and makes possible
to get results of introspection.
**Other Changes**
* Firewall management can be disabled completely via ``manage_firewall``
option.
* Experimental support for updating IPMI credentials from within ramdisk.
Enable via configuration option ``enable_setting_ipmi_credentials``.
Beware that this feature lacks proper testing, is not supported
officially yet and is subject to changes without keeping backward
compatibility.
See `setup-ipmi-credentials blueprint`_ for details.
**Known Issues**
* `bug 1415040 <https://bugs.launchpad.net/ironic-discoverd/+bug/1415040>`_
it is required to set IP addresses instead of host names in
``ipmi_address``/``ilo_address``/``drac_host`` node ``driver_info`` field
for **ironic-discoverd** to work properly.
.. _1.0.0 release tracking page: https://bugs.launchpad.net/ironic-discoverd/+milestone/1.0.0
.. _setup-ipmi-credentials blueprint: https://blueprints.launchpad.net/ironic-discoverd/+spec/setup-ipmi-credentials
.. _Plugins: https://github.com/stackforge/ironic-discoverd#plugins
.. _CONTRIBUTING.rst: https://github.com/stackforge/ironic-discoverd/blob/master/CONTRIBUTING.rst
.. _plugin-architecture blueprint: https://blueprints.launchpad.net/ironic-discoverd/+spec/plugin-architecture
.. _get-status-api blueprint: https://blueprints.launchpad.net/ironic-discoverd/+spec/get-status-api
.. _Kilo state machine blueprint: https://blueprints.launchpad.net/ironic-discoverd/+spec/kilo-state-machine
.. _v1 API reform blueprint: https://blueprints.launchpad.net/ironic-discoverd/+spec/v1-api-reform
0.2 Series
~~~~~~~~~~
0.2 series is designed to work with OpenStack Juno release.
Not supported any more.
0.1 Series
~~~~~~~~~~
First stable release series. Not supported any more.

View File

@ -1,4 +0,0 @@
#!/usr/bin/env python
from ironic_discoverd_ramdisk import main
main.main()

View File

@ -0,0 +1,4 @@
#!/usr/bin/env python
from ironic_inspector_ramdisk import main
main.main()

View File

@ -1,52 +1,123 @@
[DEFAULT]
[discoverd]
#
# From ironic_discoverd
# From ironic_inspector
#
# Keystone authentication endpoint. (string value)
#os_auth_url = http://127.0.0.1:5000/v2.0
# IP to listen on. (string value)
# Deprecated group/name - [discoverd]/listen_address
#listen_address = 0.0.0.0
# User name for accessing Keystone and Ironic API. (string value)
#os_username =
# Port to listen on. (integer value)
# Deprecated group/name - [discoverd]/listen_port
#listen_port = 5050
# Password for accessing Keystone and Ironic API. (string value)
#os_password =
# Whether to authenticate with Keystone on public HTTP endpoints. Note
# that introspection ramdisk postback endpoint is never authenticated.
# (boolean value)
# Deprecated group/name - [discoverd]/authenticate
#authenticate = true
# Tenant name for accessing Keystone and Ironic API. (string value)
#os_tenant_name =
# SQLite3 database to store nodes under introspection, required. Do
# not use :memory: here, it won't work. (string value)
# Deprecated group/name - [discoverd]/database
#database =
# Keystone admin endpoint. (string value)
#identity_uri = http://127.0.0.1:35357
# Debug mode enabled/disabled. (boolean value)
# Deprecated group/name - [discoverd]/debug
#debug = false
# Number of attempts to do when trying to connect to Ironic on start
# up. (integer value)
#ironic_retry_attempts = 5
# Timeout after which introspection is considered failed, set to 0 to
# disable. (integer value)
# Deprecated group/name - [discoverd]/timeout
#timeout = 3600
# Amount of time between attempts to connect to Ironic on start up.
# (integer value)
#ironic_retry_period = 5
# For how much time (in seconds) to keep status information about
# nodes after introspection was finished for them. Default value is 1
# week. (integer value)
# Deprecated group/name - [discoverd]/node_status_keep_time
#node_status_keep_time = 604800
# Amount of time in seconds, after which repeat clean up of timed out
# nodes and old nodes status information. (integer value)
# Deprecated group/name - [discoverd]/clean_up_period
#clean_up_period = 60
[firewall]
#
# From ironic_inspector
#
# Whether to manage firewall rules for PXE port. (boolean value)
# Deprecated group/name - [discoverd]/manage_firewall
#manage_firewall = true
# Interface on which dnsmasq listens, the default is for VM's. (string
# value)
# Deprecated group/name - [discoverd]/dnsmasq_interface
#dnsmasq_interface = br-ctlplane
# Amount of time in seconds, after which repeat periodic update of
# firewall. (integer value)
# Deprecated group/name - [discoverd]/firewall_update_period
#firewall_update_period = 15
# iptables chain name to use. (string value)
#firewall_chain = ironic-inspector
[ironic]
#
# From ironic_inspector
#
# Keystone authentication endpoint. (string value)
# Deprecated group/name - [discoverd]/os_auth_url
#os_auth_url = http://127.0.0.1:5000/v2.0
# User name for accessing Keystone and Ironic API. (string value)
# Deprecated group/name - [discoverd]/os_username
#os_username =
# Password for accessing Keystone and Ironic API. (string value)
# Deprecated group/name - [discoverd]/os_password
#os_password =
# Tenant name for accessing Keystone and Ironic API. (string value)
# Deprecated group/name - [discoverd]/os_tenant_name
#os_tenant_name =
# Keystone admin endpoint. (string value)
# Deprecated group/name - [discoverd]/identity_uri
#identity_uri = http://127.0.0.1:35357
# Number of attempts to do when trying to connect to Ironic on start
# up. (integer value)
# Deprecated group/name - [discoverd]/ironic_retry_attempts
#ironic_retry_attempts = 5
# Amount of time between attempts to connect to Ironic on start up.
# (integer value)
# Deprecated group/name - [discoverd]/ironic_retry_period
#ironic_retry_period = 5
[processing]
#
# From ironic_inspector
#
# Which MAC addresses to add as ports during introspection. Possible
# values: all (all MAC addresses), active (MAC addresses of NIC with
# IP addresses), pxe (only MAC address of NIC node PXE booted from,
# falls back to "active" if PXE MAC is not supplied by the ramdisk).
# (string value)
# Allowed values: all, active, pxe
# Deprecated group/name - [discoverd]/add_ports
#add_ports = pxe
# Which ports (already present on a node) to keep after introspection.
@ -54,64 +125,36 @@
# which MACs were present in introspection data), added (keep only
# MACs that we added during introspection). (string value)
# Allowed values: all, present, added
# Deprecated group/name - [discoverd]/keep_ports
#keep_ports = all
# Timeout after which introspection is considered failed, set to 0 to
# disable. (integer value)
#timeout = 3600
# For how much time (in seconds) to keep status information about
# nodes after introspection was finished for them. Default value is 1
# week. (integer value)
#node_status_keep_time = 604800
# Amount of time in seconds, after which repeat clean up of timed out
# nodes and old nodes status information. (integer value)
#clean_up_period = 60
# Whether to overwrite existing values in node database. Disable this
# option to make introspection a non-destructive operation. (boolean
# value)
# Deprecated group/name - [discoverd]/overwrite_existing
#overwrite_existing = true
# Whether to enable setting IPMI credentials during introspection.
# This is an experimental and not well tested feature, use at your own
# risk. (boolean value)
# Deprecated group/name - [discoverd]/enable_setting_ipmi_credentials
#enable_setting_ipmi_credentials = false
# IP to listen on. (string value)
#listen_address = 0.0.0.0
# Port to listen on. (integer value)
#listen_port = 5050
# Whether to authenticate with Keystone on public HTTP endpoints. Note
# that introspection ramdisk postback endpoint is never authenticated.
# (boolean value)
#authenticate = true
# SQLite3 database to store nodes under introspection, required. Do
# not use :memory: here, it won't work. (string value)
#database =
# Comma-separated list of enabled hooks for processing pipeline. Hook
# 'scheduler' updates the node with the minimum properties required by
# the Nova scheduler. Hook 'validate_interfaces' ensures that valid
# NIC data was provided by the ramdisk.Do not exclude these two unless
# you really know what you're doing. (string value)
# Deprecated group/name - [discoverd]/processing_hooks
#processing_hooks = ramdisk_error,scheduler,validate_interfaces
# Debug mode enabled/disabled. (boolean value)
#debug = false
# If set, logs from ramdisk will be stored in this directory. (string
# value)
# Deprecated group/name - [discoverd]/ramdisk_logs_dir
#ramdisk_logs_dir = <None>
# Whether to store ramdisk logs even if it did not return an error
# message (dependent upon "ramdisk_logs_dir" option being set).
# (boolean value)
# Deprecated group/name - [discoverd]/always_store_ramdisk_logs
#always_store_ramdisk_logs = false
# DEPRECATED: use add_ports. (boolean value)
#ports_for_inactive_interfaces = false

View File

@ -28,20 +28,23 @@ import unittest
import mock
import requests
from ironic_discoverd import client
from ironic_discoverd import main
from ironic_discoverd.test import base
from ironic_discoverd import utils
from ironic_inspector import client
from ironic_inspector import main
from ironic_inspector.test import base
from ironic_inspector import utils
CONF = """
[discoverd]
[ironic]
os_auth_url = http://url
os_username = user
os_password = password
os_tenant_name = tenant
[firewall]
manage_firewall = False
[processing]
enable_setting_ipmi_credentials = True
[DEFAULT]
database = %(db_file)s
"""

View File

@ -1,15 +1,15 @@
.\" Manpage for ironic-discoverd.
.TH man 8 "08 Oct 2014" "1.0" "ironic-discoverd man page"
.\" Manpage for ironic-inspector.
.TH man 8 "08 Oct 2014" "1.0" "ironic-inspector man page"
.SH NAME
ironic-discoverd \- hardware discovery daemon for OpenStack Ironic.
ironic-inspector \- hardware introspection daemon for OpenStack Ironic.
.SH SYNOPSIS
ironic-discoverd CONFFILE
ironic-inspector CONFFILE
.SH DESCRIPTION
This command starts ironic-discoverd service, which starts and finishes
This command starts ironic-inspector service, which starts and finishes
hardware discovery and maintains firewall rules for nodes accessing PXE
boot service (usually dnsmasq).
.SH OPTIONS
The ironic-discoverd does not take any options. However, you should supply
The ironic-inspector does not take any options. However, you should supply
path to the configuration file.
.SH SEE ALSO
README page located at https://pypi.python.org/pypi/ironic-discoverd

View File

@ -11,5 +11,5 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version_info__ = (1, 2, 0)
__version_info__ = (2, 0, 0)
__version__ = '%d.%d.%d' % __version_info__

View File

@ -11,16 +11,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import argparse
import json
from oslo_utils import netutils
import requests
import six
from ironic_discoverd.common.i18n import _
from ironic_inspector.common.i18n import _
_DEFAULT_URL = 'http://' + netutils.get_my_ipv4() + ':5050/v1'
@ -38,7 +33,7 @@ def _prepare(base_url, auth_token):
class ClientError(requests.HTTPError):
"""Error returned from a server."""
def __init__(self, response):
# discoverd returns error message in body
# inspector returns error message in body
msg = response.content.decode(_ERROR_ENCODING)
super(ClientError, self).__init__(msg, response=response)
@ -54,10 +49,10 @@ def introspect(uuid, base_url=None, auth_token=None,
"""Start introspection for a node.
:param uuid: node uuid
:param base_url: *ironic-discoverd* URL in form: http://host:port[/ver],
:param base_url: *ironic-inspector* URL in form: http://host:port[/ver],
defaults to ``http://<current host>:5050/v1``.
:param auth_token: Keystone authentication token.
:param new_ipmi_password: if set, *ironic-discoverd* will update IPMI
:param new_ipmi_password: if set, *ironic-inspector* will update IPMI
password to this value.
:param new_ipmi_username: if new_ipmi_password is set, this values sets
new IPMI user name. Defaults to one in
@ -79,9 +74,9 @@ def introspect(uuid, base_url=None, auth_token=None,
def get_status(uuid, base_url=None, auth_token=None):
"""Get introspection status for a node.
New in ironic-discoverd version 1.0.0.
New in ironic-inspector version 1.0.0.
:param uuid: node uuid.
:param base_url: *ironic-discoverd* URL in form: http://host:port[/ver],
:param base_url: *ironic-inspector* URL in form: http://host:port[/ver],
defaults to ``http://<current host>:5050/v1``.
:param auth_token: Keystone authentication token.
:raises: *requests* library HTTP errors.
@ -94,44 +89,3 @@ def get_status(uuid, base_url=None, auth_token=None):
headers=headers)
ClientError.raise_if_needed(res)
return res.json()
def discover(uuids, base_url=None, auth_token=None):
"""Post node UUID's for discovery.
DEPRECATED. Use introspect instead.
"""
if not all(isinstance(s, six.string_types) for s in uuids):
raise TypeError(_("Expected list of strings for uuids argument, "
"got %s") % uuids)
base_url, headers = _prepare(base_url, auth_token)
headers['Content-Type'] = 'application/json'
res = requests.post(base_url + "/discover",
data=json.dumps(uuids), headers=headers)
ClientError.raise_if_needed(res)
if __name__ == '__main__': # pragma: no cover
parser = argparse.ArgumentParser(description='Discover nodes.')
parser.add_argument('cmd', metavar='cmd',
choices=['introspect', 'get_status'],
help='command: introspect or get_status.')
parser.add_argument('uuid', metavar='UUID', type=str,
help='node UUID.')
parser.add_argument('--base-url', dest='base_url', action='store',
default=_DEFAULT_URL,
help='base URL, default to localhost.')
parser.add_argument('--auth-token', dest='auth_token', action='store',
default='',
help='Keystone token.')
args = parser.parse_args()
func = globals()[args.cmd]
try:
res = func(uuid=args.uuid, base_url=args.base_url,
auth_token=args.auth_token)
except Exception as exc:
print('Error:', exc)
else:
if res:
print(json.dumps(res))

View File

@ -15,7 +15,7 @@
import oslo_i18n
_translators = oslo_i18n.TranslatorFactory(domain='ironic-discoverd')
_translators = oslo_i18n.TranslatorFactory(domain='ironic-inspector')
# The primary translation function using the well-known name "_"
_ = _translators.primary

View File

@ -17,42 +17,64 @@ from oslo_config import cfg
VALID_ADD_PORTS_VALUES = ('all', 'active', 'pxe')
VALID_KEEP_PORTS_VALUES = ('all', 'present', 'added')
SERVICE_OPTS = [
IRONIC_OPTS = [
cfg.StrOpt('os_auth_url',
default='http://127.0.0.1:5000/v2.0',
help='Keystone authentication endpoint.'),
help='Keystone authentication endpoint.',
deprecated_group='discoverd'),
cfg.StrOpt('os_username',
default='',
help='User name for accessing Keystone and Ironic API.'),
help='User name for accessing Keystone and Ironic API.',
deprecated_group='discoverd'),
cfg.StrOpt('os_password',
default='',
help='Password for accessing Keystone and Ironic API.',
secret=True),
secret=True,
deprecated_group='discoverd'),
cfg.StrOpt('os_tenant_name',
default='',
help='Tenant name for accessing Keystone and Ironic API.'),
help='Tenant name for accessing Keystone and Ironic API.',
deprecated_group='discoverd'),
cfg.StrOpt('identity_uri',
default='http://127.0.0.1:35357',
help='Keystone admin endpoint.'),
help='Keystone admin endpoint.',
deprecated_group='discoverd'),
cfg.IntOpt('ironic_retry_attempts',
default=5,
help='Number of attempts to do when trying to connect to '
'Ironic on start up.'),
'Ironic on start up.',
deprecated_group='discoverd'),
cfg.IntOpt('ironic_retry_period',
default=5,
help='Amount of time between attempts to connect to Ironic '
'on start up.'),
'on start up.',
deprecated_group='discoverd'),
]
FIREWALL_OPTS = [
cfg.BoolOpt('manage_firewall',
default=True,
help='Whether to manage firewall rules for PXE port.'),
help='Whether to manage firewall rules for PXE port.',
deprecated_group='discoverd'),
cfg.StrOpt('dnsmasq_interface',
default='br-ctlplane',
help='Interface on which dnsmasq listens, the default is for '
'VM\'s.'),
'VM\'s.',
deprecated_group='discoverd'),
cfg.IntOpt('firewall_update_period',
default=15,
help='Amount of time in seconds, after which repeat periodic '
'update of firewall.'),
'update of firewall.',
deprecated_group='discoverd'),
cfg.StrOpt('firewall_chain',
default='ironic-inspector',
help='iptables chain name to use.'),
]
PROCESSING_OPTS = [
cfg.StrOpt('add_ports',
default='pxe',
help='Which MAC addresses to add as ports during '
@ -61,7 +83,8 @@ SERVICE_OPTS = [
'addresses), pxe (only MAC address of NIC node PXE booted '
'from, falls back to "active" if PXE MAC is not supplied '
'by the ramdisk).',
choices=VALID_ADD_PORTS_VALUES),
choices=VALID_ADD_PORTS_VALUES,
deprecated_group='discoverd'),
cfg.StrOpt('keep_ports',
default='all',
help='Which ports (already present on a node) to keep after '
@ -69,45 +92,20 @@ SERVICE_OPTS = [
'all (do not delete anything), present (keep ports which MACs '
'were present in introspection data), added (keep only MACs '
'that we added during introspection).',
choices=VALID_KEEP_PORTS_VALUES),
cfg.IntOpt('timeout',
default=3600,
help='Timeout after which introspection is considered failed, '
'set to 0 to disable.'),
cfg.IntOpt('node_status_keep_time',
default=604800,
help='For how much time (in seconds) to keep status '
'information about nodes after introspection was '
'finished for them. Default value is 1 week.'),
cfg.IntOpt('clean_up_period',
default=60,
help='Amount of time in seconds, after which repeat clean up '
'of timed out nodes and old nodes status information.'),
choices=VALID_KEEP_PORTS_VALUES,
deprecated_group='discoverd'),
cfg.BoolOpt('overwrite_existing',
default=True,
help='Whether to overwrite existing values in node database. '
'Disable this option to make introspection a '
'non-destructive operation.'),
'non-destructive operation.',
deprecated_group='discoverd'),
cfg.BoolOpt('enable_setting_ipmi_credentials',
default=False,
help='Whether to enable setting IPMI credentials during '
'introspection. This is an experimental and not well '
'tested feature, use at your own risk.'),
cfg.StrOpt('listen_address',
default='0.0.0.0',
help='IP to listen on.'),
cfg.IntOpt('listen_port',
default=5050,
help='Port to listen on.'),
cfg.BoolOpt('authenticate',
default=True,
help='Whether to authenticate with Keystone on public HTTP '
'endpoints. Note that introspection ramdisk postback '
'endpoint is never authenticated.'),
cfg.StrOpt('database',
default='',
help='SQLite3 database to store nodes under introspection, '
'required. Do not use :memory: here, it won\'t work.'),
'tested feature, use at your own risk.',
deprecated_group='discoverd'),
cfg.StrOpt('processing_hooks',
default='ramdisk_error,scheduler,validate_interfaces',
help='Comma-separated list of enabled hooks for processing '
@ -116,27 +114,74 @@ SERVICE_OPTS = [
'Hook \'validate_interfaces\' ensures that valid NIC '
'data was provided by the ramdisk.'
'Do not exclude these two unless you really know what '
'you\'re doing.'),
cfg.BoolOpt('debug',
default=False,
help='Debug mode enabled/disabled.'),
'you\'re doing.',
deprecated_group='discoverd'),
cfg.StrOpt('ramdisk_logs_dir',
help='If set, logs from ramdisk will be stored in this '
'directory.'),
'directory.',
deprecated_group='discoverd'),
cfg.BoolOpt('always_store_ramdisk_logs',
default=False,
help='Whether to store ramdisk logs even if it did not return '
'an error message (dependent upon "ramdisk_logs_dir" option '
'being set).'),
cfg.BoolOpt('ports_for_inactive_interfaces',
default=False,
help='DEPRECATED: use add_ports.'),
'being set).',
deprecated_group='discoverd'),
]
cfg.CONF.register_opts(SERVICE_OPTS, group='discoverd')
SERVICE_OPTS = [
cfg.StrOpt('listen_address',
default='0.0.0.0',
help='IP to listen on.',
deprecated_group='discoverd'),
cfg.IntOpt('listen_port',
default=5050,
help='Port to listen on.',
deprecated_group='discoverd'),
cfg.BoolOpt('authenticate',
default=True,
help='Whether to authenticate with Keystone on public HTTP '
'endpoints. Note that introspection ramdisk postback '
'endpoint is never authenticated.',
deprecated_group='discoverd'),
cfg.StrOpt('database',
default='',
help='SQLite3 database to store nodes under introspection, '
'required. Do not use :memory: here, it won\'t work.',
deprecated_group='discoverd'),
cfg.BoolOpt('debug',
default=False,
help='Debug mode enabled/disabled.',
deprecated_group='discoverd'),
cfg.IntOpt('timeout',
default=3600,
help='Timeout after which introspection is considered failed, '
'set to 0 to disable.',
deprecated_group='discoverd'),
cfg.IntOpt('node_status_keep_time',
default=604800,
help='For how much time (in seconds) to keep status '
'information about nodes after introspection was '
'finished for them. Default value is 1 week.',
deprecated_group='discoverd'),
cfg.IntOpt('clean_up_period',
default=60,
help='Amount of time in seconds, after which repeat clean up '
'of timed out nodes and old nodes status information.',
deprecated_group='discoverd'),
]
cfg.CONF.register_opts(SERVICE_OPTS)
cfg.CONF.register_opts(FIREWALL_OPTS, group='firewall')
cfg.CONF.register_opts(PROCESSING_OPTS, group='processing')
cfg.CONF.register_opts(IRONIC_OPTS, group='ironic')
def list_opts():
return [
('discoverd', SERVICE_OPTS)
('', SERVICE_OPTS),
('firewall', FIREWALL_OPTS),
('ironic', IRONIC_OPTS),
('processing', PROCESSING_OPTS),
]

View File

@ -17,17 +17,17 @@ import subprocess
from eventlet import semaphore
from oslo_config import cfg
from ironic_discoverd.common.i18n import _LE
from ironic_discoverd import node_cache
from ironic_discoverd import utils
from ironic_inspector.common.i18n import _LE
from ironic_inspector import node_cache
from ironic_inspector import utils
LOG = logging.getLogger("ironic_discoverd.firewall")
NEW_CHAIN = 'discovery_temp'
CHAIN = 'discovery'
CONF = cfg.CONF
LOG = logging.getLogger("ironic_inspector.firewall")
NEW_CHAIN = None
CHAIN = None
INTERFACE = None
LOCK = semaphore.BoundedSemaphore()
CONF = cfg.CONF
def _iptables(*args, **kwargs):
@ -51,11 +51,14 @@ def init():
Must be called one on start-up.
"""
if not CONF.discoverd.manage_firewall:
if not CONF.firewall.manage_firewall:
return
global INTERFACE
INTERFACE = CONF.discoverd.dnsmasq_interface
global INTERFACE, CHAIN, NEW_CHAIN
INTERFACE = CONF.firewall.dnsmasq_interface
CHAIN = CONF.firewall.firewall_chain
NEW_CHAIN = CHAIN + '_temp'
_clean_up(CHAIN)
# Not really needed, but helps to validate that we have access to iptables
_iptables('-N', CHAIN)
@ -71,7 +74,7 @@ def _clean_up(chain):
def clean_up():
"""Clean up everything before exiting."""
if not CONF.discoverd.manage_firewall:
if not CONF.firewall.manage_firewall:
return
_clean_up(CHAIN)
@ -96,7 +99,7 @@ def update_filters(ironic=None):
:param ironic: Ironic client instance, optional.
"""
if not CONF.discoverd.manage_firewall:
if not CONF.firewall.manage_firewall:
return
assert INTERFACE is not None

View File

@ -20,21 +20,21 @@ import eventlet
from ironicclient import exceptions
from oslo_config import cfg
from ironic_discoverd.common.i18n import _, _LI, _LW
from ironic_discoverd import firewall
from ironic_discoverd import node_cache
from ironic_discoverd import utils
from ironic_inspector.common.i18n import _, _LI, _LW
from ironic_inspector import firewall
from ironic_inspector import node_cache
from ironic_inspector import utils
CONF = cfg.CONF
LOG = logging.getLogger("ironic_discoverd.introspect")
LOG = logging.getLogger("ironic_inspector.introspect")
PASSWORD_ACCEPTED_CHARS = set(string.ascii_letters + string.digits)
PASSWORD_MAX_LENGTH = 20 # IPMI v2.0
def _validate_ipmi_credentials(node, new_ipmi_credentials):
if not CONF.discoverd.enable_setting_ipmi_credentials:
if not CONF.processing.enable_setting_ipmi_credentials:
raise utils.Error(
_('IPMI credentials setup is disabled in configuration'))
@ -113,9 +113,6 @@ def introspect(uuid, new_ipmi_credentials=None):
def _background_introspect(ironic, cached_node):
patch = [{'op': 'add', 'path': '/extra/on_discovery', 'value': 'true'}]
utils.retry_on_conflict(ironic.node.update, cached_node.uuid, patch)
# TODO(dtantsur): pagination
macs = [p.address for p in ironic.node.list_ports(cached_node.uuid,
limit=0)]
@ -147,4 +144,4 @@ def _background_introspect(ironic, cached_node):
LOG.info(_LI('Introspection environment is ready for node %(node)s, '
'manual power on is required within %(timeout)d seconds') %
{'node': cached_node.uuid,
'timeout': CONF.discoverd.timeout})
'timeout': CONF.timeout})

View File

@ -23,21 +23,21 @@ import flask
from oslo_config import cfg
from oslo_utils import uuidutils
from ironic_discoverd.common.i18n import _, _LC, _LE, _LI, _LW
from ironic_inspector.common.i18n import _, _LC, _LE, _LI, _LW
# Import configuration options
from ironic_discoverd import conf # noqa
from ironic_discoverd import firewall
from ironic_discoverd import introspect
from ironic_discoverd import node_cache
from ironic_discoverd.plugins import base as plugins_base
from ironic_discoverd import process
from ironic_discoverd import utils
from ironic_inspector import conf # noqa
from ironic_inspector import firewall
from ironic_inspector import introspect
from ironic_inspector import node_cache
from ironic_inspector.plugins import base as plugins_base
from ironic_inspector import process
from ironic_inspector import utils
CONF = cfg.CONF
app = flask.Flask(__name__)
LOG = logging.getLogger('ironic_discoverd.main')
LOG = logging.getLogger('ironic_inspector.main')
def convert_exceptions(func):
@ -90,23 +90,6 @@ def api_introspection(uuid):
error=node_info.error or None)
@app.route('/v1/discover', methods=['POST'])
@convert_exceptions
def api_discover():
utils.check_auth(flask.request)
data = flask.request.get_json(force=True)
LOG.debug("/v1/discover got JSON %s", data)
for uuid in data:
if not uuidutils.is_uuid_like(uuid):
raise utils.Error(_('Invalid UUID value'), code=400)
for uuid in data:
introspect.introspect(uuid)
return "", 202
def periodic_update(period): # pragma: no cover
while True:
LOG.debug('Running periodic update of filters')
@ -136,9 +119,9 @@ def check_ironic_available():
2. Keystone has already started
3. Ironic has already started
"""
attempts = CONF.discoverd.ironic_retry_attempts
attempts = CONF.ironic.ironic_retry_attempts
assert attempts >= 0
retry_period = CONF.discoverd.ironic_retry_period
retry_period = CONF.ironic.ironic_retry_period
LOG.debug('Trying to connect to Ironic')
for i in range(attempts + 1): # one attempt always required
try:
@ -155,7 +138,7 @@ def check_ironic_available():
def init():
if CONF.discoverd.authenticate:
if CONF.authenticate:
utils.add_auth_middleware(app)
else:
LOG.warning(_LW('Starting unauthenticated, please check'
@ -173,21 +156,21 @@ def init():
LOG.info(_LI('Enabled processing hooks: %s'), hooks)
if CONF.discoverd.manage_firewall:
if CONF.firewall.manage_firewall:
firewall.init()
period = CONF.discoverd.firewall_update_period
period = CONF.firewall.firewall_update_period
eventlet.greenthread.spawn_n(periodic_update, period)
if CONF.discoverd.timeout > 0:
period = CONF.discoverd.clean_up_period
if CONF.timeout > 0:
period = CONF.clean_up_period
eventlet.greenthread.spawn_n(periodic_clean_up, period)
else:
LOG.warning(_LW('Timeout is disabled in configuration'))
def main(args=sys.argv[1:]): # pragma: no cover
CONF(args, project='ironic-discoverd')
debug = CONF.discoverd.debug
CONF(args, project='ironic-inspector')
debug = CONF.debug
logging.basicConfig(level=logging.DEBUG if debug else logging.INFO)
for third_party in ('urllib3.connectionpool',
@ -200,7 +183,7 @@ def main(args=sys.argv[1:]): # pragma: no cover
init()
try:
app.run(debug=debug,
host=CONF.discoverd.listen_address,
port=CONF.discoverd.listen_port)
host=CONF.listen_address,
port=CONF.listen_port)
finally:
firewall.clean_up()

View File

@ -23,13 +23,13 @@ import time
from oslo_config import cfg
from ironic_discoverd.common.i18n import _, _LC, _LE
from ironic_discoverd import utils
from ironic_inspector.common.i18n import _, _LC, _LE
from ironic_inspector import utils
CONF = cfg.CONF
LOG = logging.getLogger("ironic_discoverd.node_cache")
LOG = logging.getLogger("ironic_inspector.node_cache")
_DB_NAME = None
_SCHEMA = """
create table if not exists nodes
@ -135,9 +135,9 @@ def init():
"""Initialize the database."""
global _DB_NAME
_DB_NAME = CONF.discoverd.database.strip()
_DB_NAME = CONF.database.strip()
if not _DB_NAME:
LOG.critical(_LC('Configuration option discoverd.database'
LOG.critical(_LC('Configuration option inspector.database'
' should be set'))
sys.exit(1)
@ -269,13 +269,13 @@ def clean_up():
:return: list of timed out node UUID's
"""
status_keep_threshold = (time.time() -
CONF.discoverd.node_status_keep_time)
CONF.node_status_keep_time)
with _db() as db:
db.execute('delete from nodes where finished_at < ?',
(status_keep_threshold,))
timeout = CONF.discoverd.timeout
timeout = CONF.timeout
if timeout <= 0:
return []

View File

@ -45,7 +45,7 @@ class ProcessingHook(object): # pragma: no cover
:param node: Ironic node as returned by the Ironic client, should not
be modified directly by the hook.
:param ports: Ironic ports created by discoverd, also should not be
:param ports: Ironic ports created by inspector, also should not be
updated directly.
:param node_info: processed data from the ramdisk.
:returns: tuple (node patches, port patches) where
@ -74,9 +74,9 @@ def processing_hooks_manager(*args):
global _HOOKS_MGR
if _HOOKS_MGR is None:
names = [x.strip()
for x in CONF.discoverd.processing_hooks.split(',')
for x in CONF.processing.processing_hooks.split(',')
if x.strip()]
_HOOKS_MGR = named.NamedExtensionManager('ironic_discoverd.hooks',
_HOOKS_MGR = named.NamedExtensionManager('ironic_inspector.hooks',
names=names,
invoke_on_load=True,
invoke_args=args,

View File

@ -19,14 +19,14 @@ details on how to use it. Note that this plugin requires a special ramdisk.
import logging
from ironic_discoverd.common.i18n import _LW
from ironic_discoverd.plugins import base
from ironic_inspector.common.i18n import _LW
from ironic_inspector.plugins import base
LOG = logging.getLogger('ironic_discoverd.plugins.edeploy')
LOG = logging.getLogger('ironic_inspector.plugins.edeploy')
class eDeployHook(base.ProcessingHook):
"""Interact with eDeploy ramdisk for discovery data processing hooks."""
"""Processing hook for saving additional data from eDeploy ramdisk."""
def before_update(self, node, ports, node_info):
"""Store the hardware data from what has been discovered."""

View File

@ -15,10 +15,10 @@
import logging
from ironic_discoverd.plugins import base
from ironic_inspector.plugins import base
LOG = logging.getLogger('ironic_discoverd.plugins.example')
LOG = logging.getLogger('ironic_inspector.plugins.example')
class ExampleProcessingHook(base.ProcessingHook): # pragma: no cover

View File

@ -15,15 +15,15 @@
import logging
from ironic_discoverd.common.i18n import _LI, _LW
from ironic_discoverd.plugins import base
from ironic_inspector.common.i18n import _LI, _LW
from ironic_inspector.plugins import base
LOG = logging.getLogger('ironic_discoverd.plugins.root_device_hint')
LOG = logging.getLogger('ironic_inspector.plugins.root_device_hint')
class RootDeviceHintHook(base.ProcessingHook):
"""Interact with Instack ramdisk for discovery data processing hooks.
"""Processing hook for learning the root device after RAID creation.
The plugin can figure out the root device in 2 runs. First, it saves the
discovered block device serials in node.extra. The second run will check
@ -72,7 +72,7 @@ class RootDeviceHintHook(base.ProcessingHook):
], {}
else:
# No previously discovered devices - save the discoverd block
# No previously discovered devices - save the inspector block
# devices in node.extra
return [
{'op': 'add',

View File

@ -21,15 +21,15 @@ import sys
from oslo_config import cfg
from ironic_discoverd.common.i18n import _, _LC, _LI, _LW
from ironic_discoverd import conf
from ironic_discoverd.plugins import base
from ironic_discoverd import utils
from ironic_inspector.common.i18n import _, _LC, _LI, _LW
from ironic_inspector import conf
from ironic_inspector.plugins import base
from ironic_inspector import utils
CONF = cfg.CONF
LOG = logging.getLogger('ironic_discoverd.plugins.standard')
LOG = logging.getLogger('ironic_inspector.plugins.standard')
class SchedulerHook(base.ProcessingHook):
@ -51,7 +51,7 @@ class SchedulerHook(base.ProcessingHook):
def before_update(self, node, ports, node_info):
"""Update node with scheduler properties."""
overwrite = CONF.discoverd.overwrite_existing
overwrite = CONF.processing.overwrite_existing
patch = [{'op': 'add', 'path': '/properties/%s' % key,
'value': str(node_info[key])}
for key in self.KEYS
@ -63,28 +63,20 @@ class ValidateInterfacesHook(base.ProcessingHook):
"""Hook to validate network interfaces."""
def __init__(self):
if CONF.discoverd.add_ports not in conf.VALID_ADD_PORTS_VALUES:
LOG.critical(_LC('Accepted values for [discoverd]add_ports are '
if CONF.processing.add_ports not in conf.VALID_ADD_PORTS_VALUES:
LOG.critical(_LC('Accepted values for [processing]add_ports are '
'%(valid)s, got %(actual)s'),
{'valid': conf.VALID_ADD_PORTS_VALUES,
'actual': CONF.discoverd.add_ports})
'actual': CONF.processing.add_ports})
sys.exit(1)
if CONF.discoverd.keep_ports not in conf.VALID_KEEP_PORTS_VALUES:
LOG.critical(_LC('Accepted values for [discoverd]keep_ports are '
if CONF.processing.keep_ports not in conf.VALID_KEEP_PORTS_VALUES:
LOG.critical(_LC('Accepted values for [processing]keep_ports are '
'%(valid)s, got %(actual)s'),
{'valid': conf.VALID_KEEP_PORTS_VALUES,
'actual': CONF.discoverd.keep_ports})
'actual': CONF.processing.keep_ports})
sys.exit(1)
def _ports_to_add(self):
if CONF.discoverd.ports_for_inactive_interfaces:
LOG.warning(_LW('Using deprecated option '
'[discoverd]ports_for_inactive_interfaces'))
return 'all'
else:
return CONF.discoverd.add_ports
def before_processing(self, node_info):
"""Validate information about network interfaces."""
bmc_address = node_info.get('ipmi_address')
@ -96,10 +88,9 @@ class ValidateInterfacesHook(base.ProcessingHook):
if utils.is_valid_mac(iface.get('mac'))
}
ports_to_add = self._ports_to_add()
pxe_mac = node_info.get('boot_interface')
if ports_to_add == 'pxe' and pxe_mac:
if CONF.processing.add_ports == 'pxe' and pxe_mac:
LOG.info(_LI('PXE boot interface was %s'), pxe_mac)
if '-' in pxe_mac:
# pxelinux format: 01-aa-bb-cc-dd-ee-ff
@ -110,7 +101,7 @@ class ValidateInterfacesHook(base.ProcessingHook):
n: iface for n, iface in valid_interfaces.items()
if iface['mac'].lower() == pxe_mac
}
elif ports_to_add != 'all':
elif CONF.processing.add_ports != 'all':
valid_interfaces = {
n: iface for n, iface in valid_interfaces.items()
if iface.get('ip')
@ -139,10 +130,10 @@ class ValidateInterfacesHook(base.ProcessingHook):
def before_update(self, node, ports, node_info):
"""Drop ports that are not present in the data."""
if CONF.discoverd.keep_ports == 'present':
if CONF.processing.keep_ports == 'present':
expected_macs = {iface['mac']
for iface in node_info['all_interfaces'].values()}
elif CONF.discoverd.keep_ports == 'added':
elif CONF.processing.keep_ports == 'added':
expected_macs = set(node_info['macs'])
else:
return
@ -169,25 +160,25 @@ class RamdiskErrorHook(base.ProcessingHook):
error = node_info.get('error')
logs = node_info.get('logs')
if logs and (error or CONF.discoverd.always_store_ramdisk_logs):
if logs and (error or CONF.processing.always_store_ramdisk_logs):
self._store_logs(logs, node_info)
if error:
raise utils.Error(_('Ramdisk reported error: %s') % error)
def _store_logs(self, logs, node_info):
if not CONF.discoverd.ramdisk_logs_dir:
LOG.warn(_LW('Failed to store logs received from the discovery '
'ramdisk because ramdisk_logs_dir configuration '
'option is not set'))
if not CONF.processing.ramdisk_logs_dir:
LOG.warn(_LW('Failed to store logs received from the ramdisk '
'because ramdisk_logs_dir configuration option '
'is not set'))
return
if not os.path.exists(CONF.discoverd.ramdisk_logs_dir):
os.makedirs(CONF.discoverd.ramdisk_logs_dir)
if not os.path.exists(CONF.processing.ramdisk_logs_dir):
os.makedirs(CONF.processing.ramdisk_logs_dir)
time_fmt = datetime.datetime.utcnow().strftime(self.DATETIME_FORMAT)
bmc_address = node_info.get('ipmi_address', 'unknown')
file_name = 'bmc_%s_%s' % (bmc_address, time_fmt)
with open(os.path.join(CONF.discoverd.ramdisk_logs_dir, file_name),
with open(os.path.join(CONF.processing.ramdisk_logs_dir, file_name),
'wb') as fp:
fp.write(base64.b64decode(logs))

View File

@ -18,21 +18,21 @@ import logging
import eventlet
from ironicclient import exceptions
from ironic_discoverd.common.i18n import _, _LE, _LI, _LW
from ironic_discoverd import firewall
from ironic_discoverd import node_cache
from ironic_discoverd.plugins import base as plugins_base
from ironic_discoverd import utils
from ironic_inspector.common.i18n import _, _LE, _LI, _LW
from ironic_inspector import firewall
from ironic_inspector import node_cache
from ironic_inspector.plugins import base as plugins_base
from ironic_inspector import utils
LOG = logging.getLogger("ironic_discoverd.process")
LOG = logging.getLogger("ironic_inspector.process")
_CREDENTIALS_WAIT_RETRIES = 10
_CREDENTIALS_WAIT_PERIOD = 3
def process(node_info):
"""Process data from the discovery ramdisk.
"""Process data from the ramdisk.
This function heavily relies on the hooks to do the actual data processing.
"""
@ -212,10 +212,5 @@ def _finish(ironic, cached_node):
raise utils.Error(msg)
cached_node.finished()
patch = [{'op': 'add', 'path': '/extra/newly_discovered', 'value': 'true'},
{'op': 'remove', 'path': '/extra/on_discovery'}]
utils.retry_on_conflict(ironic.node.update, cached_node.uuid, patch)
LOG.info(_LI('Introspection finished successfully for node %s'),
cached_node.uuid)

View File

@ -11,7 +11,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""OpenStackClient plugin for ironic-discoverd."""
"""OpenStackClient plugin for ironic-inspector."""
from __future__ import print_function
@ -21,24 +21,24 @@ from cliff import command
from cliff import show
from openstackclient.common import utils
from ironic_discoverd import client
from ironic_inspector import client
LOG = logging.getLogger('ironic_discoverd.shell')
LOG = logging.getLogger('ironic_inspector.shell')
API_NAME = 'baremetal-introspection'
API_VERSION_OPTION = 'discoverd_api_version'
API_VERSION_OPTION = 'inspector_api_version'
DEFAULT_VERSION = '1'
API_VERSIONS = {
"1": "ironic_discoverd.shell",
"1": "ironic_inspector.shell",
}
def build_option_parser(parser):
parser.add_argument('--discoverd-api-version',
default=utils.env('DISCOVERD_VERSION',
parser.add_argument('--inspector-api-version',
default=utils.env('INSPECTOR_VERSION',
default=DEFAULT_VERSION),
help='discoverd API version, only 1 is supported now '
'(env: DISCOVERD_VERSION).')
help='inspector API version, only 1 is supported now '
'(env: INSPECTOR_VERSION).')
return parser
@ -50,17 +50,17 @@ class StartCommand(command.Command):
_add_common_arguments(parser)
parser.add_argument('--new-ipmi-username',
default=None,
help='if set, *ironic-discoverd* will update IPMI '
help='if set, *ironic-inspector* will update IPMI '
'user name to this value')
parser.add_argument('--new-ipmi-password',
default=None,
help='if set, *ironic-discoverd* will update IPMI '
help='if set, *ironic-inspector* will update IPMI '
'password to this value')
return parser
def take_action(self, parsed_args):
auth_token = self.app.client_manager.auth_ref.auth_token
client.introspect(parsed_args.uuid, base_url=parsed_args.discoverd_url,
client.introspect(parsed_args.uuid, base_url=parsed_args.inspector_url,
auth_token=auth_token,
new_ipmi_username=parsed_args.new_ipmi_username,
new_ipmi_password=parsed_args.new_ipmi_password)
@ -80,7 +80,7 @@ class StatusCommand(show.ShowOne):
def take_action(self, parsed_args):
auth_token = self.app.client_manager.auth_ref.auth_token
status = client.get_status(parsed_args.uuid,
base_url=parsed_args.discoverd_url,
base_url=parsed_args.inspector_url,
auth_token=auth_token)
return zip(*sorted(status.items()))
@ -90,7 +90,7 @@ def _add_common_arguments(parser):
parser.add_argument('uuid', help='baremetal node UUID')
# FIXME(dtantsur): this should be in build_option_parser, but then it won't
# be available in commands
parser.add_argument('--discoverd-url',
default=utils.env('DISCOVERD_URL', default=None),
help='discoverd URL, defaults to localhost '
'(env: DISCOVERD_URL).')
parser.add_argument('--inspector-url',
default=utils.env('INSPECTOR_URL', default=None),
help='inspector URL, defaults to localhost '
'(env: INSPECTOR_URL).')

View File

@ -17,11 +17,11 @@ import unittest
import mock
from oslo_config import cfg
from ironic_discoverd.common import i18n
from ironic_inspector.common import i18n
# Import configuration options
from ironic_discoverd import conf # noqa
from ironic_discoverd import node_cache
from ironic_discoverd.plugins import base as plugins_base
from ironic_inspector import conf # noqa
from ironic_inspector import node_cache
from ironic_inspector.plugins import base as plugins_base
CONF = cfg.CONF
@ -33,11 +33,12 @@ def init_test_conf():
# Unit tests
except Exception:
CONF.reset()
CONF.register_group(cfg.OptGroup('discoverd'))
if not CONF.discoverd.database:
for group in ('firewall', 'processing', 'ironic'):
CONF.register_group(cfg.OptGroup(group))
if not CONF.database:
# Might be set in functional tests
db_file = tempfile.NamedTemporaryFile()
CONF.set_override('database', db_file.name, 'discoverd')
CONF.set_override('database', db_file.name)
else:
db_file = None
node_cache._DB_NAME = None
@ -71,6 +72,6 @@ class NodeTest(BaseTest):
uuid=self.uuid,
power_state='power on',
provision_state='inspecting',
extra={'on_discovery': 'true'},
extra={},
instance_uuid=None,
maintenance=False)

View File

@ -17,7 +17,7 @@ import mock
from oslo_utils import netutils
from oslo_utils import uuidutils
from ironic_discoverd import client
from ironic_inspector import client
@mock.patch.object(client.requests, 'post', autospec=True,
@ -86,36 +86,6 @@ class TestIntrospect(unittest.TestCase):
client.introspect, self.uuid)
@mock.patch.object(client.requests, 'post', autospec=True,
**{'return_value.status_code': 200})
class TestDiscover(unittest.TestCase):
def setUp(self):
super(TestDiscover, self).setUp()
self.uuid = uuidutils.generate_uuid()
def test_old_discover(self, mock_post):
uuid2 = uuidutils.generate_uuid()
client.discover([self.uuid, uuid2], base_url="http://host:port",
auth_token="token")
mock_post.assert_called_once_with(
"http://host:port/v1/discover",
data='["%(uuid1)s", "%(uuid2)s"]' % {'uuid1': self.uuid,
'uuid2': uuid2},
headers={'Content-Type': 'application/json',
'X-Auth-Token': 'token'}
)
def test_invalid_input(self, _):
self.assertRaises(TypeError, client.discover, 42)
self.assertRaises(TypeError, client.discover, [42])
def test_failed(self, mock_post):
mock_post.return_value.status_code = 404
mock_post.return_value.content = b"boom"
self.assertRaisesRegexp(client.ClientError, "boom",
client.discover, [self.uuid])
@mock.patch.object(client.requests, 'get', autospec=True,
**{'return_value.status_code': 200})
class TestGetStatus(unittest.TestCase):

View File

@ -17,10 +17,10 @@ import mock
from oslo_config import cfg
from ironic_discoverd import firewall
from ironic_discoverd import node_cache
from ironic_discoverd.test import base as test_base
from ironic_discoverd import utils
from ironic_inspector import firewall
from ironic_inspector import node_cache
from ironic_inspector.test import base as test_base
from ironic_inspector import utils
CONF = cfg.CONF
@ -31,7 +31,7 @@ CONF = cfg.CONF
class TestFirewall(test_base.NodeTest):
def test_update_filters_without_manage_firewall(self, mock_get_client,
mock_iptables):
CONF.set_override('manage_firewall', False, 'discoverd')
CONF.set_override('manage_firewall', False, 'firewall')
firewall.update_filters()
self.assertEqual(0, mock_iptables.call_count)
@ -39,10 +39,10 @@ class TestFirewall(test_base.NodeTest):
firewall.init()
init_expected_args = [
('-D', 'INPUT', '-i', 'br-ctlplane', '-p', 'udp', '--dport', '67',
'-j', 'discovery'),
('-F', 'discovery'),
('-X', 'discovery'),
('-N', 'discovery')]
'-j', CONF.firewall.firewall_chain),
('-F', CONF.firewall.firewall_chain),
('-X', CONF.firewall.firewall_chain),
('-N', CONF.firewall.firewall_chain)]
call_args_list = mock_iptables.call_args_list
@ -66,23 +66,23 @@ class TestFirewall(test_base.NodeTest):
update_filters_expected_args = [
('-D', 'INPUT', '-i', 'br-ctlplane', '-p', 'udp', '--dport',
'67', '-j', 'discovery'),
('-F', 'discovery'),
('-X', 'discovery'),
('-N', 'discovery'),
'67', '-j', CONF.firewall.firewall_chain),
('-F', CONF.firewall.firewall_chain),
('-X', CONF.firewall.firewall_chain),
('-N', CONF.firewall.firewall_chain),
('-D', 'INPUT', '-i', 'br-ctlplane', '-p', 'udp', '--dport',
'67', '-j', 'discovery_temp'),
('-F', 'discovery_temp'),
('-X', 'discovery_temp'),
('-N', 'discovery_temp'),
('-A', 'discovery_temp', '-j', 'ACCEPT'),
'67', '-j', firewall.NEW_CHAIN),
('-F', firewall.NEW_CHAIN),
('-X', firewall.NEW_CHAIN),
('-N', firewall.NEW_CHAIN),
('-A', firewall.NEW_CHAIN, '-j', 'ACCEPT'),
('-I', 'INPUT', '-i', 'br-ctlplane', '-p', 'udp', '--dport',
'67', '-j', 'discovery_temp'),
'67', '-j', firewall.NEW_CHAIN),
('-D', 'INPUT', '-i', 'br-ctlplane', '-p', 'udp', '--dport',
'67', '-j', 'discovery'),
('-F', 'discovery'),
('-X', 'discovery'),
('-E', 'discovery_temp', 'discovery')
'67', '-j', CONF.firewall.firewall_chain),
('-F', CONF.firewall.firewall_chain),
('-X', CONF.firewall.firewall_chain),
('-E', firewall.NEW_CHAIN, CONF.firewall.firewall_chain)
]
firewall.update_filters()
@ -131,26 +131,26 @@ class TestFirewall(test_base.NodeTest):
update_filters_expected_args = [
('-D', 'INPUT', '-i', 'br-ctlplane', '-p', 'udp', '--dport',
'67', '-j', 'discovery'),
('-F', 'discovery'),
('-X', 'discovery'),
('-N', 'discovery'),
'67', '-j', CONF.firewall.firewall_chain),
('-F', CONF.firewall.firewall_chain),
('-X', CONF.firewall.firewall_chain),
('-N', CONF.firewall.firewall_chain),
('-D', 'INPUT', '-i', 'br-ctlplane', '-p', 'udp', '--dport',
'67', '-j', 'discovery_temp'),
('-F', 'discovery_temp'),
('-X', 'discovery_temp'),
('-N', 'discovery_temp'),
'67', '-j', firewall.NEW_CHAIN),
('-F', firewall.NEW_CHAIN),
('-X', firewall.NEW_CHAIN),
('-N', firewall.NEW_CHAIN),
# Blacklist
('-A', 'discovery_temp', '-m', 'mac', '--mac-source',
('-A', firewall.NEW_CHAIN, '-m', 'mac', '--mac-source',
inactive_mac[0], '-j', 'DROP'),
('-A', 'discovery_temp', '-j', 'ACCEPT'),
('-A', firewall.NEW_CHAIN, '-j', 'ACCEPT'),
('-I', 'INPUT', '-i', 'br-ctlplane', '-p', 'udp', '--dport',
'67', '-j', 'discovery_temp'),
'67', '-j', firewall.NEW_CHAIN),
('-D', 'INPUT', '-i', 'br-ctlplane', '-p', 'udp', '--dport',
'67', '-j', 'discovery'),
('-F', 'discovery'),
('-X', 'discovery'),
('-E', 'discovery_temp', 'discovery')
'67', '-j', CONF.firewall.firewall_chain),
('-F', CONF.firewall.firewall_chain),
('-X', CONF.firewall.firewall_chain),
('-E', firewall.NEW_CHAIN, CONF.firewall.firewall_chain)
]
firewall.update_filters(mock_get_client)

View File

@ -16,11 +16,11 @@ from ironicclient import exceptions
import mock
from oslo_config import cfg
from ironic_discoverd import firewall
from ironic_discoverd import introspect
from ironic_discoverd import node_cache
from ironic_discoverd.test import base as test_base
from ironic_discoverd import utils
from ironic_inspector import firewall
from ironic_inspector import introspect
from ironic_inspector import node_cache
from ironic_inspector.test import base as test_base
from ironic_inspector import utils
CONF = cfg.CONF
@ -35,11 +35,8 @@ class BaseTest(test_base.NodeTest):
maintenance=True,
# allowed with maintenance=True
power_state='power on',
provision_state='foobar',
extra={'on_discovery': True})
provision_state='foobar')
self.ports = [mock.Mock(address=m) for m in self.macs]
self.patch = [{'op': 'add', 'path': '/extra/on_discovery',
'value': 'true'}]
self.cached_node = mock.Mock(uuid=self.uuid, options={})
def _prepare(self, client_mock):
@ -67,7 +64,6 @@ class TestIntrospect(BaseTest):
cli.node.validate.assert_called_once_with(self.uuid)
cli.node.list_ports.assert_called_once_with(self.uuid, limit=0)
cli.node.update.assert_called_once_with(self.uuid, self.patch)
add_mock.assert_called_once_with(self.uuid,
bmc_address=self.bmc_address)
self.cached_node.add_attribute.assert_called_once_with('mac',
@ -96,9 +92,6 @@ class TestIntrospect(BaseTest):
cli = self._prepare(client_mock)
cli.node.validate.side_effect = [exceptions.Conflict,
mock.Mock(power={'result': True})]
cli.node.update.side_effect = [exceptions.Conflict,
exceptions.Conflict,
None]
cli.node.set_boot_device.side_effect = [exceptions.Conflict,
None]
cli.node.set_power_state.side_effect = [exceptions.Conflict,
@ -111,7 +104,6 @@ class TestIntrospect(BaseTest):
cli.node.validate.assert_called_with(self.uuid)
cli.node.list_ports.assert_called_once_with(self.uuid, limit=0)
cli.node.update.assert_called_with(self.uuid, self.patch)
add_mock.assert_called_once_with(self.uuid,
bmc_address=self.bmc_address)
filters_mock.assert_called_with(cli)
@ -131,7 +123,6 @@ class TestIntrospect(BaseTest):
cli.node.get.assert_called_once_with(self.uuid)
cli.node.update.assert_called_once_with(self.uuid, self.patch)
add_mock.assert_called_once_with(self.uuid,
bmc_address=self.bmc_address)
cli.node.set_boot_device.assert_called_once_with(self.uuid,
@ -151,14 +142,13 @@ class TestIntrospect(BaseTest):
cli.node.get.assert_called_once_with(self.uuid)
cli.node.update.assert_called_once_with(self.uuid, self.patch)
add_mock.assert_called_once_with(self.uuid,
bmc_address=self.bmc_address)
self.assertFalse(cli.node.set_boot_device.called)
add_mock.return_value.finished.assert_called_once_with(
error=mock.ANY)
def test_juno_compat(self, client_mock, add_mock, filters_mock):
def test_with_maintenance(self, client_mock, add_mock, filters_mock):
cli = client_mock.return_value
cli.node.get.return_value = self.node_compat
cli.node.validate.return_value = mock.Mock(power={'result': True})
@ -173,8 +163,6 @@ class TestIntrospect(BaseTest):
cli.node.list_ports.assert_called_once_with(self.node_compat.uuid,
limit=0)
cli.node.update.assert_called_once_with(self.node_compat.uuid,
self.patch)
add_mock.assert_called_once_with(self.node_compat.uuid,
bmc_address=None)
add_mock.return_value.add_attribute.assert_called_once_with('mac',
@ -195,7 +183,6 @@ class TestIntrospect(BaseTest):
cli.node.list_ports.assert_called_once_with(self.uuid, limit=0)
cli.node.update.assert_called_once_with(self.uuid, self.patch)
add_mock.assert_called_once_with(self.uuid,
bmc_address=self.bmc_address)
self.assertFalse(self.cached_node.add_attribute.called)
@ -221,7 +208,6 @@ class TestIntrospect(BaseTest):
self.assertEqual(0, cli.node.list_ports.call_count)
self.assertEqual(0, filters_mock.call_count)
self.assertEqual(0, cli.node.set_power_state.call_count)
self.assertEqual(0, cli.node.update.call_count)
self.assertFalse(add_mock.called)
def test_failed_to_validate_node(self, client_mock, add_mock,
@ -240,7 +226,6 @@ class TestIntrospect(BaseTest):
self.assertEqual(0, cli.node.list_ports.call_count)
self.assertEqual(0, filters_mock.call_count)
self.assertEqual(0, cli.node.set_power_state.call_count)
self.assertEqual(0, cli.node.update.call_count)
self.assertFalse(add_mock.called)
def test_wrong_provision_state(self, client_mock, add_mock, filters_mock):
@ -256,7 +241,6 @@ class TestIntrospect(BaseTest):
self.assertEqual(0, cli.node.list_ports.call_count)
self.assertEqual(0, filters_mock.call_count)
self.assertEqual(0, cli.node.set_power_state.call_count)
self.assertEqual(0, cli.node.update.call_count)
self.assertFalse(add_mock.called)
@ -268,7 +252,8 @@ class TestIntrospect(BaseTest):
class TestSetIpmiCredentials(BaseTest):
def setUp(self):
super(TestSetIpmiCredentials, self).setUp()
CONF.set_override('enable_setting_ipmi_credentials', True, 'discoverd')
CONF.set_override('enable_setting_ipmi_credentials', True,
'processing')
self.new_creds = ('user', 'password')
self.cached_node.options['new_ipmi_credentials'] = self.new_creds
self.node.maintenance = True
@ -279,7 +264,6 @@ class TestSetIpmiCredentials(BaseTest):
introspect.introspect(self.uuid, new_ipmi_credentials=self.new_creds)
cli.node.update.assert_called_once_with(self.uuid, self.patch)
add_mock.assert_called_once_with(self.uuid,
bmc_address=self.bmc_address)
filters_mock.assert_called_with(cli)
@ -291,7 +275,7 @@ class TestSetIpmiCredentials(BaseTest):
def test_disabled(self, client_mock, add_mock, filters_mock):
CONF.set_override('enable_setting_ipmi_credentials', False,
'discoverd')
'processing')
self._prepare(client_mock)
self.assertRaisesRegexp(utils.Error, 'disabled',
@ -312,7 +296,6 @@ class TestSetIpmiCredentials(BaseTest):
introspect.introspect(self.uuid,
new_ipmi_credentials=(None, self.new_creds[1]))
cli.node.update.assert_called_once_with(self.uuid, self.patch)
add_mock.assert_called_once_with(self.uuid,
bmc_address=self.bmc_address)
filters_mock.assert_called_with(cli)

View File

@ -18,15 +18,15 @@ import eventlet
import mock
from oslo_utils import uuidutils
from ironic_discoverd import firewall
from ironic_discoverd import introspect
from ironic_discoverd import main
from ironic_discoverd import node_cache
from ironic_discoverd.plugins import base as plugins_base
from ironic_discoverd.plugins import example as example_plugin
from ironic_discoverd import process
from ironic_discoverd.test import base as test_base
from ironic_discoverd import utils
from ironic_inspector import firewall
from ironic_inspector import introspect
from ironic_inspector import main
from ironic_inspector import node_cache
from ironic_inspector.plugins import base as plugins_base
from ironic_inspector.plugins import example as example_plugin
from ironic_inspector import process
from ironic_inspector.test import base as test_base
from ironic_inspector import utils
from oslo_config import cfg
CONF = cfg.CONF
@ -37,12 +37,12 @@ class TestApi(test_base.BaseTest):
super(TestApi, self).setUp()
main.app.config['TESTING'] = True
self.app = main.app.test_client()
CONF.set_override('authenticate', False, 'discoverd')
CONF.set_override('authenticate', False)
self.uuid = uuidutils.generate_uuid()
@mock.patch.object(introspect, 'introspect', autospec=True)
def test_introspect_no_authentication(self, introspect_mock):
CONF.set_override('authenticate', False, 'discoverd')
CONF.set_override('authenticate', False)
res = self.app.post('/v1/introspection/%s' % self.uuid)
self.assertEqual(202, res.status_code)
introspect_mock.assert_called_once_with(self.uuid,
@ -50,7 +50,7 @@ class TestApi(test_base.BaseTest):
@mock.patch.object(introspect, 'introspect', autospec=True)
def test_introspect_set_ipmi_credentials(self, introspect_mock):
CONF.set_override('authenticate', False, 'discoverd')
CONF.set_override('authenticate', False)
res = self.app.post('/v1/introspection/%s?new_ipmi_username=user&'
'new_ipmi_password=password' % self.uuid)
self.assertEqual(202, res.status_code)
@ -60,7 +60,7 @@ class TestApi(test_base.BaseTest):
@mock.patch.object(introspect, 'introspect', autospec=True)
def test_introspect_set_ipmi_credentials_no_user(self, introspect_mock):
CONF.set_override('authenticate', False, 'discoverd')
CONF.set_override('authenticate', False)
res = self.app.post('/v1/introspection/%s?'
'new_ipmi_password=password' % self.uuid)
self.assertEqual(202, res.status_code)
@ -82,7 +82,7 @@ class TestApi(test_base.BaseTest):
@mock.patch.object(introspect, 'introspect', autospec=True)
def test_introspect_failed_authentication(self, introspect_mock,
auth_mock):
CONF.set_override('authenticate', True, 'discoverd')
CONF.set_override('authenticate', True)
auth_mock.side_effect = utils.Error('Boom', code=403)
res = self.app.post('/v1/introspection/%s' % self.uuid,
headers={'X-Auth-Token': 'token'})
@ -95,22 +95,10 @@ class TestApi(test_base.BaseTest):
res = self.app.post('/v1/introspection/%s' % uuid_dummy)
self.assertEqual(400, res.status_code)
@mock.patch.object(introspect, 'introspect', autospec=True)
def test_discover(self, discover_mock):
res = self.app.post('/v1/discover', data='["%s"]' % self.uuid)
self.assertEqual(202, res.status_code)
discover_mock.assert_called_once_with(self.uuid)
@mock.patch.object(introspect, 'introspect', autospec=True)
def test_discover_invalid_uuid(self, discover_mock):
uuid_dummy = 'uuid1'
res = self.app.post('/v1/discover', data='["%s"]' % uuid_dummy)
self.assertEqual(400, res.status_code)
@mock.patch.object(process, 'process', autospec=True)
def test_continue(self, process_mock):
# should be ignored
CONF.set_override('authenticate', True, 'discoverd')
CONF.set_override('authenticate', True)
process_mock.return_value = [42]
res = self.app.post('/v1/continue', data='"JSON"')
self.assertEqual(200, res.status_code)
@ -161,10 +149,10 @@ class TestCheckIronicAvailable(test_base.BaseTest):
self.assertEqual(2, client_mock.call_count)
cli.driver.list.assert_called_once_with()
sleep_mock.assert_called_once_with(
CONF.discoverd.ironic_retry_period)
CONF.ironic.ironic_retry_period)
def test_failed(self, client_mock, sleep_mock):
attempts = CONF.discoverd.ironic_retry_attempts
attempts = CONF.ironic.ironic_retry_attempts
client_mock.side_effect = RuntimeError()
self.assertRaises(RuntimeError, main.check_ironic_available)
self.assertEqual(1 + attempts, client_mock.call_count)
@ -178,7 +166,7 @@ class TestPlugins(unittest.TestCase):
'before_update', autospec=True)
def test_hook(self, mock_post, mock_pre):
plugins_base._HOOKS_MGR = None
CONF.set_override('processing_hooks', 'example', 'discoverd')
CONF.set_override('processing_hooks', 'example', 'processing')
mgr = plugins_base.processing_hooks_manager()
mgr.map_method('before_processing', 'node_info')
mock_pre.assert_called_once_with(mock.ANY, 'node_info')
@ -199,15 +187,15 @@ class TestPlugins(unittest.TestCase):
class TestInit(test_base.BaseTest):
def test_ok(self, mock_node_cache, mock_get_client, mock_auth,
mock_firewall, mock_spawn_n):
CONF.set_override('authenticate', True, 'discoverd')
CONF.set_override('authenticate', True)
main.init()
mock_auth.assert_called_once_with(main.app)
mock_node_cache.assert_called_once_with()
mock_firewall.assert_called_once_with()
spawn_n_expected_args = [
(main.periodic_update, CONF.discoverd.firewall_update_period),
(main.periodic_clean_up, CONF.discoverd.clean_up_period)]
(main.periodic_update, CONF.firewall.firewall_update_period),
(main.periodic_clean_up, CONF.clean_up_period)]
spawn_n_call_args_list = mock_spawn_n.call_args_list
for (args, call) in zip(spawn_n_expected_args,
@ -216,18 +204,18 @@ class TestInit(test_base.BaseTest):
def test_init_without_authenticate(self, mock_node_cache, mock_get_client,
mock_auth, mock_firewall, mock_spawn_n):
CONF.set_override('authenticate', False, 'discoverd')
CONF.set_override('authenticate', False)
main.init()
self.assertFalse(mock_auth.called)
def test_init_without_manage_firewall(self, mock_node_cache,
mock_get_client, mock_auth,
mock_firewall, mock_spawn_n):
CONF.set_override('manage_firewall', False, 'discoverd')
CONF.set_override('manage_firewall', False, 'firewall')
main.init()
self.assertFalse(mock_firewall.called)
spawn_n_expected_args = [
(main.periodic_clean_up, CONF.discoverd.clean_up_period)]
(main.periodic_clean_up, CONF.clean_up_period)]
spawn_n_call_args_list = mock_spawn_n.call_args_list
for (args, call) in zip(spawn_n_expected_args,
spawn_n_call_args_list):
@ -235,10 +223,10 @@ class TestInit(test_base.BaseTest):
def test_init_with_timeout_0(self, mock_node_cache, mock_get_client,
mock_auth, mock_firewall, mock_spawn_n):
CONF.set_override('timeout', 0, 'discoverd')
CONF.set_override('timeout', 0)
main.init()
spawn_n_expected_args = [
(main.periodic_update, CONF.discoverd.firewall_update_period)]
(main.periodic_update, CONF.firewall.firewall_update_period)]
spawn_n_call_args_list = mock_spawn_n.call_args_list
for (args, call) in zip(spawn_n_expected_args,
@ -249,7 +237,7 @@ class TestInit(test_base.BaseTest):
def test_init_failed_processing_hook(self, mock_log, mock_node_cache,
mock_get_client, mock_auth,
mock_firewall, mock_spawn_n):
CONF.set_override('processing_hooks', 'foo!', 'discoverd')
CONF.set_override('processing_hooks', 'foo!', 'processing')
plugins_base._HOOKS_MGR = None
self.assertRaises(SystemExit, main.init)

View File

@ -19,9 +19,9 @@ import unittest
import mock
from oslo_config import cfg
from ironic_discoverd import node_cache
from ironic_discoverd.test import base as test_base
from ironic_discoverd import utils
from ironic_inspector import node_cache
from ironic_inspector.test import base as test_base
from ironic_inspector import utils
CONF = cfg.CONF
@ -158,7 +158,7 @@ class TestNodeCacheCleanUp(test_base.NodeTest):
'values(?, ?, ?)', (self.uuid, 'foo', 'bar'))
def test_no_timeout(self):
CONF.set_override('timeout', 0, 'discoverd')
CONF.set_override('timeout', 0)
self.assertFalse(node_cache.clean_up())
@ -192,7 +192,7 @@ class TestNodeCacheCleanUp(test_base.NodeTest):
'values(?, ?, ?)', (self.uuid + '1',
self.started_at,
self.started_at + 60))
CONF.set_override('timeout', 99, 'discoverd')
CONF.set_override('timeout', 99)
time_mock.return_value = self.started_at + 100
self.assertEqual([self.uuid], node_cache.clean_up())
@ -208,7 +208,7 @@ class TestNodeCacheCleanUp(test_base.NodeTest):
'select * from options').fetchall())
def test_old_status(self):
CONF.set_override('node_status_keep_time', 42, 'discoverd')
CONF.set_override('node_status_keep_time', 42)
with self.db:
self.db.execute('update nodes set finished_at=?',
(time.time() - 100,))
@ -276,7 +276,7 @@ class TestInit(unittest.TestCase):
def test_ok(self):
with tempfile.NamedTemporaryFile() as db_file:
CONF.set_override('database', db_file.name, 'discoverd')
CONF.set_override('database', db_file.name)
node_cache.init()
self.assertIsNotNone(node_cache._DB_NAME)
@ -285,12 +285,11 @@ class TestInit(unittest.TestCase):
def test_create_dir(self):
temp = tempfile.mkdtemp()
CONF.set_override('database', os.path.join(temp, 'dir', 'file'),
'discoverd')
CONF.set_override('database', os.path.join(temp, 'dir', 'file'))
node_cache.init()
def test_no_database(self):
CONF.set_override('database', '', 'discoverd')
CONF.set_override('database', '')
self.assertRaises(SystemExit, node_cache.init)

View File

@ -11,8 +11,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from ironic_discoverd.plugins import edeploy
from ironic_discoverd.test import base as test_base
from ironic_inspector.plugins import edeploy
from ironic_inspector.test import base as test_base
class TestEdeploy(test_base.NodeTest):

View File

@ -11,8 +11,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from ironic_discoverd.plugins import root_device_hint
from ironic_discoverd.test import base as test_base
from ironic_inspector.plugins import root_device_hint
from ironic_inspector.test import base as test_base
class TestRootDeviceHint(test_base.NodeTest):

View File

@ -18,10 +18,10 @@ import tempfile
from oslo_config import cfg
from ironic_discoverd.plugins import standard as std_plugins
from ironic_discoverd import process
from ironic_discoverd.test import base as test_base
from ironic_discoverd import utils
from ironic_inspector.plugins import standard as std_plugins
from ironic_inspector import process
from ironic_inspector.test import base as test_base
from ironic_inspector import utils
CONF = cfg.CONF
@ -38,7 +38,7 @@ class TestRamdiskError(test_base.BaseTest):
self.tempdir = tempfile.mkdtemp()
self.addCleanup(lambda: shutil.rmtree(self.tempdir))
CONF.set_override('ramdisk_logs_dir', self.tempdir, 'discoverd')
CONF.set_override('ramdisk_logs_dir', self.tempdir, 'processing')
def test_no_logs(self):
self.assertRaisesRegexp(utils.Error,
@ -48,7 +48,7 @@ class TestRamdiskError(test_base.BaseTest):
def test_logs_disabled(self):
self.data['logs'] = 'some log'
CONF.set_override('ramdisk_logs_dir', None, 'discoverd')
CONF.set_override('ramdisk_logs_dir', None, 'processing')
self.assertRaisesRegexp(utils.Error,
self.msg,
@ -94,7 +94,7 @@ class TestRamdiskError(test_base.BaseTest):
self.assertFalse(files)
def test_always_store_logs(self):
CONF.set_override('always_store_ramdisk_logs', True, 'discoverd')
CONF.set_override('always_store_ramdisk_logs', True, 'processing')
log = b'log contents'
del self.data['error']

View File

@ -19,14 +19,14 @@ from ironicclient import exceptions
import mock
from oslo_config import cfg
from ironic_discoverd import firewall
from ironic_discoverd import node_cache
from ironic_discoverd.plugins import base as plugins_base
from ironic_discoverd.plugins import example as example_plugin
from ironic_discoverd.plugins import standard as std_plugins
from ironic_discoverd import process
from ironic_discoverd.test import base as test_base
from ironic_discoverd import utils
from ironic_inspector import firewall
from ironic_inspector import node_cache
from ironic_inspector.plugins import base as plugins_base
from ironic_inspector.plugins import example as example_plugin
from ironic_inspector.plugins import standard as std_plugins
from ironic_inspector import process
from ironic_inspector.test import base as test_base
from ironic_inspector import utils
CONF = cfg.CONF
@ -140,7 +140,7 @@ class TestProcess(BaseTest):
@prepare_mocks
def test_add_ports_active(self, cli, pop_mock, process_mock):
CONF.set_override('add_ports', 'active', 'discoverd')
CONF.set_override('add_ports', 'active', 'processing')
res = process.process(self.data)
@ -160,7 +160,7 @@ class TestProcess(BaseTest):
@prepare_mocks
def test_add_ports_all(self, cli, pop_mock, process_mock):
CONF.set_override('add_ports', 'all', 'discoverd')
CONF.set_override('add_ports', 'all', 'processing')
res = process.process(self.data)
@ -195,23 +195,6 @@ class TestProcess(BaseTest):
del self.data['interfaces']
self.assertRaises(utils.Error, process.process, self.data)
@prepare_mocks
def test_ports_for_inactive(self, cli, pop_mock, process_mock):
CONF.set_override('ports_for_inactive_interfaces', True, 'discoverd')
del self.data['boot_interface']
process.process(self.data)
self.assertEqual(['em1', 'em2', 'em3'],
sorted(self.data['interfaces']))
self.assertEqual(self.all_macs, sorted(self.data['macs']))
pop_mock.assert_called_once_with(bmc_address=self.bmc_address,
mac=self.data['macs'])
cli.node.get.assert_called_once_with(self.uuid)
process_mock.assert_called_once_with(cli, cli.node.get.return_value,
self.data, pop_mock.return_value)
@prepare_mocks
def test_invalid_interfaces_all(self, cli, pop_mock, process_mock):
self.data['interfaces'] = {
@ -333,23 +316,19 @@ class TestProcessNode(BaseTest):
CONF.set_override('processing_hooks',
'ramdisk_error,scheduler,validate_interfaces,'
'example',
'discoverd')
'processing')
self.validate_attempts = 5
self.data['macs'] = self.macs # validate_interfaces hook
self.data['all_interfaces'] = self.data['interfaces']
self.ports = self.all_ports
self.cached_node = node_cache.NodeInfo(uuid=self.uuid,
started_at=self.started_at)
self.patch_before = [
self.patch_props = [
{'path': '/properties/cpus', 'value': '2', 'op': 'add'},
{'path': '/properties/cpu_arch', 'value': 'x86_64', 'op': 'add'},
{'path': '/properties/memory_mb', 'value': '1024', 'op': 'add'},
{'path': '/properties/local_gb', 'value': '20', 'op': 'add'}
] # scheduler hook
self.patch_after = [
{'op': 'add', 'path': '/extra/newly_discovered', 'value': 'true'},
{'op': 'remove', 'path': '/extra/on_discovery'},
]
self.new_creds = ('user', 'password')
self.patch_credentials = [
{'op': 'add', 'path': '/driver_info/ipmi_username',
@ -381,8 +360,8 @@ class TestProcessNode(BaseTest):
address=self.macs[0])
self.cli.port.create.assert_any_call(node_uuid=self.uuid,
address=self.macs[1])
self.cli.node.update.assert_any_call(self.uuid, self.patch_before)
self.cli.node.update.assert_any_call(self.uuid, self.patch_after)
self.cli.node.update.assert_called_once_with(self.uuid,
self.patch_props)
self.cli.node.set_power_state.assert_called_once_with(self.uuid, 'off')
self.assertFalse(self.cli.node.validate.called)
@ -394,7 +373,7 @@ class TestProcessNode(BaseTest):
finished_mock.assert_called_once_with(mock.ANY)
def test_overwrite_disabled(self, filters_mock, post_hook_mock):
CONF.set_override('overwrite_existing', False, 'discoverd')
CONF.set_override('overwrite_existing', False, 'processing')
patch = [
{'op': 'add', 'path': '/properties/cpus', 'value': '2'},
{'op': 'add', 'path': '/properties/memory_mb', 'value': '1024'},
@ -402,8 +381,7 @@ class TestProcessNode(BaseTest):
self.call()
self.cli.node.update.assert_any_call(self.uuid, patch)
self.cli.node.update.assert_any_call(self.uuid, self.patch_after)
self.cli.node.update.assert_called_once_with(self.uuid, patch)
def test_update_retry_on_conflict(self, filters_mock, post_hook_mock):
self.cli.node.update.side_effect = [exceptions.Conflict, self.node,
@ -415,9 +393,8 @@ class TestProcessNode(BaseTest):
address=self.macs[0])
self.cli.port.create.assert_any_call(node_uuid=self.uuid,
address=self.macs[1])
self.cli.node.update.assert_any_call(self.uuid, self.patch_before)
self.cli.node.update.assert_any_call(self.uuid, self.patch_after)
self.assertEqual(4, self.cli.node.update.call_count)
self.cli.node.update.assert_called_with(self.uuid, self.patch_props)
self.assertEqual(2, self.cli.node.update.call_count)
self.cli.node.set_power_state.assert_called_once_with(self.uuid, 'off')
def test_power_off_retry_on_conflict(self, filters_mock, post_hook_mock):
@ -429,8 +406,8 @@ class TestProcessNode(BaseTest):
address=self.macs[0])
self.cli.port.create.assert_any_call(node_uuid=self.uuid,
address=self.macs[1])
self.cli.node.update.assert_any_call(self.uuid, self.patch_before)
self.cli.node.update.assert_any_call(self.uuid, self.patch_after)
self.cli.node.update.assert_called_once_with(self.uuid,
self.patch_props)
self.cli.node.set_power_state.assert_called_with(self.uuid, 'off')
self.assertEqual(2, self.cli.node.set_power_state.call_count)
@ -443,8 +420,8 @@ class TestProcessNode(BaseTest):
address=self.macs[0])
self.cli.port.create.assert_any_call(node_uuid=self.uuid,
address=self.macs[1])
self.cli.node.update.assert_any_call(self.uuid, self.patch_before)
self.cli.node.update.assert_any_call(self.uuid, self.patch_after)
self.cli.node.update.assert_called_once_with(self.uuid,
self.patch_props)
post_hook_mock.assert_called_once_with(self.node, self.ports[1:],
self.data)
@ -457,9 +434,9 @@ class TestProcessNode(BaseTest):
self.call()
self.cli.node.update.assert_any_call(self.uuid,
self.patch_before + node_patches)
self.cli.node.update.assert_any_call(self.uuid, self.patch_after)
self.cli.node.update.assert_called_once_with(self.uuid,
self.patch_props
+ node_patches)
self.cli.port.update.assert_called_once_with(self.ports[1].uuid,
port_patch)
@ -499,7 +476,7 @@ class TestProcessNode(BaseTest):
self.assertRaisesRegexp(utils.Error, 'Failed to validate',
self.call)
self.cli.node.update.assert_any_call(self.uuid, self.patch_before)
self.cli.node.update.assert_any_call(self.uuid, self.patch_props)
self.cli.node.update.assert_any_call(self.uuid, self.patch_credentials)
self.assertEqual(2, self.cli.node.update.call_count)
self.assertEqual(process._CREDENTIALS_WAIT_RETRIES,
@ -520,7 +497,7 @@ class TestProcessNode(BaseTest):
self.cli.node.set_power_state.assert_called_once_with(self.uuid, 'off')
self.cli.node.update.assert_called_once_with(self.uuid,
self.patch_before)
self.patch_props)
finished_mock.assert_called_once_with(
mock.ANY,
error='Failed to power off node %s, check it\'s power management'
@ -529,7 +506,7 @@ class TestProcessNode(BaseTest):
@mock.patch.object(utils, 'get_client')
def test_keep_ports_present(self, client_mock, filters_mock,
post_hook_mock):
CONF.set_override('keep_ports', 'present', 'discoverd')
CONF.set_override('keep_ports', 'present', 'processing')
# 2 MACs valid, one invalid, one not present in data
all_macs = self.all_macs + ['01:09:02:08:03:07']
@ -548,7 +525,7 @@ class TestProcessNode(BaseTest):
@mock.patch.object(utils, 'get_client')
def test_keep_ports_added(self, client_mock, filters_mock, post_hook_mock):
CONF.set_override('keep_ports', 'added', 'discoverd')
CONF.set_override('keep_ports', 'added', 'processing')
# 2 MACs valid, one invalid, one not present in data
all_macs = self.all_macs + ['01:09:02:08:03:07']
@ -570,9 +547,9 @@ class TestProcessNode(BaseTest):
class TestValidateInterfacesHook(test_base.BaseTest):
def test_wrong_add_ports(self):
CONF.set_override('add_ports', 'foobar', 'discoverd')
CONF.set_override('add_ports', 'foobar', 'processing')
self.assertRaises(SystemExit, std_plugins.ValidateInterfacesHook)
def test_wrong_keep_ports(self):
CONF.set_override('keep_ports', 'foobar', 'discoverd')
CONF.set_override('keep_ports', 'foobar', 'processing')
self.assertRaises(SystemExit, std_plugins.ValidateInterfacesHook)

View File

@ -19,8 +19,8 @@ from keystonemiddleware import auth_token
import mock
from oslo_config import cfg
from ironic_discoverd.test import base
from ironic_discoverd import utils
from ironic_inspector.test import base
from ironic_inspector import utils
CONF = cfg.CONF
@ -28,13 +28,13 @@ CONF = cfg.CONF
class TestCheckAuth(base.BaseTest):
def setUp(self):
super(TestCheckAuth, self).setUp()
CONF.set_override('authenticate', True, 'discoverd')
CONF.set_override('authenticate', True)
@mock.patch.object(auth_token, 'AuthProtocol')
def test_middleware(self, mock_auth):
CONF.set_override('os_username', 'admin', 'discoverd')
CONF.set_override('os_tenant_name', 'admin', 'discoverd')
CONF.set_override('os_password', 'password', 'discoverd')
CONF.set_override('os_username', 'admin', 'ironic')
CONF.set_override('os_tenant_name', 'admin', 'ironic')
CONF.set_override('os_password', 'password', 'ironic')
app = mock.Mock(wsgi_app=mock.sentinel.app)
utils.add_auth_middleware(app)
@ -62,12 +62,12 @@ class TestCheckAuth(base.BaseTest):
self.assertRaises(utils.Error, utils.check_auth, request)
def test_disabled(self):
CONF.set_override('authenticate', False, 'discoverd')
CONF.set_override('authenticate', False)
request = mock.Mock(headers={'X-Identity-Status': 'Invalid'})
utils.check_auth(request)
@mock.patch('ironic_discoverd.node_cache.NodeInfo')
@mock.patch('ironic_inspector.node_cache.NodeInfo')
class TestGetIpmiAddress(base.BaseTest):
def test_ipv4_in_resolves(self, mock_node):
node = mock_node.return_value

View File

@ -22,7 +22,7 @@ from keystonemiddleware import auth_token
from oslo_config import cfg
import six
from ironic_discoverd.common.i18n import _, _LE, _LI, _LW
from ironic_inspector.common.i18n import _, _LE, _LI, _LW
CONF = cfg.CONF
@ -30,13 +30,13 @@ CONF = cfg.CONF
VALID_STATES = {'enroll', 'manageable', 'inspecting', 'inspectfail'}
LOG = logging.getLogger('ironic_discoverd.utils')
LOG = logging.getLogger('ironic_inspector.utils')
RETRY_COUNT = 12
RETRY_DELAY = 5
class Error(Exception):
"""Discoverd exception."""
"""Inspector exception."""
def __init__(self, msg, code=400):
super(Error, self).__init__(msg)
@ -46,10 +46,10 @@ class Error(Exception):
def get_client(): # pragma: no cover
"""Get Ironic client instance."""
args = dict({'os_password': CONF.discoverd.os_password,
'os_username': CONF.discoverd.os_username,
'os_auth_url': CONF.discoverd.os_auth_url,
'os_tenant_name': CONF.discoverd.os_tenant_name})
args = dict({'os_password': CONF.ironic.os_password,
'os_username': CONF.ironic.os_username,
'os_auth_url': CONF.ironic.os_auth_url,
'os_tenant_name': CONF.ironic.os_tenant_name})
return client.get_client(1, **args)
@ -58,12 +58,12 @@ def add_auth_middleware(app):
:param app: application.
"""
auth_conf = dict({'admin_password': CONF.discoverd.os_password,
'admin_user': CONF.discoverd.os_username,
'auth_uri': CONF.discoverd.os_auth_url,
'admin_tenant_name': CONF.discoverd.os_tenant_name})
auth_conf = dict({'admin_password': CONF.ironic.os_password,
'admin_user': CONF.ironic.os_username,
'auth_uri': CONF.ironic.os_auth_url,
'admin_tenant_name': CONF.ironic.os_tenant_name})
auth_conf['delay_auth_decision'] = True
auth_conf['identity_uri'] = CONF.discoverd.identity_uri
auth_conf['identity_uri'] = CONF.ironic.identity_uri
app.wsgi_app = auth_token.AuthProtocol(app.wsgi_app, auth_conf)
@ -73,7 +73,7 @@ def check_auth(request):
:param request: Flask request
:raises: utils.Error if access is denied
"""
if not CONF.discoverd.authenticate:
if not CONF.authenticate:
return
if request.headers.get('X-Identity-Status').lower() == 'invalid':
raise Error(_('Authentication required'), code=401)

View File

@ -23,7 +23,7 @@ import netifaces
import requests
LOG = logging.getLogger('ironic-discoverd-ramdisk')
LOG = logging.getLogger('ironic-inspector-ramdisk')
def try_call(*cmd, **kwargs):
@ -213,13 +213,13 @@ def discover_hardware(args, data, failures):
discover_block_devices(data)
def call_discoverd(args, data, failures):
def call_inspector(args, data, failures):
data['error'] = failures.get_error()
LOG.info('posting collected data to %s', args.callback_url)
resp = requests.post(args.callback_url, data=json.dumps(data))
if resp.status_code >= 400:
LOG.error('discoverd error %d: %s',
LOG.error('inspector error %d: %s',
resp.status_code,
resp.content.decode('utf-8'))
resp.raise_for_status()

View File

@ -17,16 +17,16 @@ import sys
import requests
from ironic_discoverd_ramdisk import discover
from ironic_inspector_ramdisk import discover
LOG = logging.getLogger('ironic-discoverd-ramdisk')
LOG = logging.getLogger('ironic-inspector-ramdisk')
def parse_args(args):
parser = argparse.ArgumentParser(description='Detect present hardware.')
parser.add_argument('-L', '--system-log-file', action='append',
help='System log file to be sent to discoverd, may be '
help='System log file to be sent to inspector, may be '
'specified multiple times')
parser.add_argument('-l', '--log-file', default='discovery-logs',
help='Path to log file, defaults to ./discovery-logs')
@ -37,9 +37,9 @@ def parse_args(args):
'python-hardware package')
parser.add_argument('--benchmark', action='store_true',
help='Enables benchmarking for hardware-detect')
# ironic-discoverd callback
# ironic-inspector callback
parser.add_argument('callback_url',
help='Full ironic-discoverd callback URL')
help='Full ironic-inspector callback URL')
return parser.parse_args(args)
@ -74,11 +74,11 @@ def main():
call_error = True
resp = {}
try:
resp = discover.call_discoverd(args, data, failures)
resp = discover.call_inspector(args, data, failures)
except requests.RequestException as exc:
LOG.error('%s when calling to discoverd', exc)
LOG.error('%s when calling to inspector', exc)
except Exception:
LOG.exception('failed to call discoverd')
LOG.exception('failed to call inspector')
else:
call_error = False

View File

@ -29,7 +29,7 @@ except ImportError:
import netifaces
import requests
from ironic_discoverd_ramdisk import discover
from ironic_inspector_ramdisk import discover
def get_fake_args():
@ -284,7 +284,7 @@ class TestCallDiscoverd(unittest.TestCase):
data = collections.OrderedDict(data=42)
mock_post.return_value.status_code = 200
discover.call_discoverd(FAKE_ARGS, data, failures)
discover.call_inspector(FAKE_ARGS, data, failures)
mock_post.assert_called_once_with('url',
data='{"data": 42, "error": null}')
@ -295,17 +295,17 @@ class TestCallDiscoverd(unittest.TestCase):
data = collections.OrderedDict(data=42)
mock_post.return_value.status_code = 200
discover.call_discoverd(FAKE_ARGS, data, failures)
discover.call_inspector(FAKE_ARGS, data, failures)
mock_post.assert_called_once_with('url',
data='{"data": 42, "error": "boom"}')
def test_discoverd_error(self, mock_post):
def test_inspector_error(self, mock_post):
failures = discover.AccumulatedFailure()
data = collections.OrderedDict(data=42)
mock_post.return_value.status_code = 400
discover.call_discoverd(FAKE_ARGS, data, failures)
discover.call_inspector(FAKE_ARGS, data, failures)
mock_post.assert_called_once_with('url',
data='{"data": 42, "error": null}')

View File

@ -16,9 +16,9 @@ import unittest
import mock
import requests
from ironic_discoverd_ramdisk import discover
from ironic_discoverd_ramdisk import main
from ironic_discoverd_ramdisk.test import test_discover
from ironic_inspector_ramdisk import discover
from ironic_inspector_ramdisk import main
from ironic_inspector_ramdisk.test import test_discover
FAKE_ARGS = test_discover.get_fake_args()
@ -41,7 +41,7 @@ class TestParseArgs(unittest.TestCase):
@mock.patch.object(main, 'parse_args', return_value=FAKE_ARGS,
autospec=True)
@mock.patch.object(discover, 'setup_ipmi_credentials', autospec=True)
@mock.patch.object(discover, 'call_discoverd', autospec=True,
@mock.patch.object(discover, 'call_inspector', autospec=True,
return_value={})
@mock.patch.object(discover, 'collect_logs', autospec=True)
@mock.patch.object(discover, 'discover_hardware', autospec=True)

View File

@ -1,15 +1,15 @@
# Translations template for ironic-discoverd.
# Translations template for ironic-inspector.
# Copyright (C) 2015 ORGANIZATION
# This file is distributed under the same license as the ironic-discoverd
# This file is distributed under the same license as the ironic-inspector
# project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2015.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: ironic-discoverd 1.1.0\n"
"Project-Id-Version: ironic-inspector 2.0.0\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2015-03-02 02:49+0000\n"
"POT-Creation-Date: 2015-05-28 15:25+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -18,67 +18,62 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 1.3\n"
#: ironic_discoverd/introspect.py:40
#: ironic_inspector/client.py:62 ironic_inspector/client.py:85
#, python-format
msgid "Expected string for uuid argument, got %r"
msgstr ""
#: ironic_inspector/client.py:64
msgid "Setting IPMI user name requires a new password"
msgstr ""
#: ironic_inspector/introspect.py:39
msgid "IPMI credentials setup is disabled in configuration"
msgstr ""
#: ironic_discoverd/introspect.py:44
#: ironic_inspector/introspect.py:43
msgid "Node should be in maintenance mode to set IPMI credentials on it"
msgstr ""
#: ironic_discoverd/introspect.py:51
#: ironic_inspector/introspect.py:50
#, python-format
msgid ""
"Setting IPMI credentials requested for node %s, but neither new user name"
" nor driver_info[ipmi_username] are provided"
msgstr ""
#: ironic_discoverd/introspect.py:58
#: ironic_inspector/introspect.py:57
#, python-format
msgid ""
"Forbidden characters encountered in new IPMI password for node %(node)s: "
"\"%(chars)s\"; use only letters and numbers"
msgstr ""
#: ironic_discoverd/introspect.py:63
#: ironic_inspector/introspect.py:62
#, python-format
msgid "IPMI password length should be > 0 and <= %d"
msgstr ""
#: ironic_discoverd/introspect.py:81
#: ironic_inspector/introspect.py:80
#, python-format
msgid "Cannot find node %s"
msgstr ""
#: ironic_discoverd/introspect.py:83
#: ironic_inspector/introspect.py:82
#, python-format
msgid "Cannot get node %(node)s: %(exc)s"
msgstr ""
#: ironic_discoverd/introspect.py:89
#, python-format
msgid ""
"Refusing to introspect node %(node)s with provision state \"%(state)s\" "
"and maintenance mode off"
msgstr ""
#: ironic_discoverd/introspect.py:96
#, python-format
msgid ""
"Refusing to introspect node %(node)s with power state \"%(state)s\" and "
"maintenance mode off"
msgstr ""
#: ironic_discoverd/introspect.py:109
#: ironic_inspector/introspect.py:93
#, python-format
msgid "Failed validation of power interface for node %(node)s, reason: %(reason)s"
msgstr ""
#: ironic_discoverd/introspect.py:124
#: ironic_inspector/introspect.py:108
msgid "Unexpected exception in background introspection thread"
msgstr ""
#: ironic_discoverd/introspect.py:158
#: ironic_inspector/introspect.py:139
#, python-format
msgid ""
"Failed to power on node %(node)s, check it's power management "
@ -86,95 +81,127 @@ msgid ""
"%(exc)s"
msgstr ""
#: ironic_discoverd/main.py:46
msgid "Authentication required"
#: ironic_inspector/main.py:70
msgid "Invalid UUID value"
msgstr ""
#: ironic_discoverd/main.py:51
msgid "Access denied"
msgstr ""
#: ironic_discoverd/node_cache.py:115
#: ironic_inspector/node_cache.py:118
#, python-format
msgid "Some or all of %(name)s's %(value)s are already on introspection"
msgstr ""
#: ironic_discoverd/node_cache.py:202
#: ironic_inspector/node_cache.py:209
#, python-format
msgid "Could not find node %s in cache"
msgstr ""
#: ironic_discoverd/node_cache.py:233
#: ironic_inspector/node_cache.py:240
#, python-format
msgid "Could not find a node for attributes %s"
msgstr ""
#: ironic_discoverd/node_cache.py:236
#: ironic_inspector/node_cache.py:243
#, python-format
msgid "Multiple matching nodes found for attributes %(attr)s: %(found)s"
msgstr ""
#: ironic_discoverd/node_cache.py:244
#: ironic_inspector/node_cache.py:251
#, python-format
msgid ""
"Could not find node %s in introspection cache, probably it's not on "
"introspection now"
msgstr ""
#: ironic_discoverd/node_cache.py:249
#: ironic_inspector/node_cache.py:256
#, python-format
msgid "Introspection for node %(node)s already finished on %(finish)s"
msgstr ""
#: ironic_discoverd/process.py:54
#: ironic_inspector/process.py:56
#, python-format
msgid "Unexpected exception during preprocessing in hook %s"
msgstr ""
#: ironic_inspector/process.py:65
#, python-format
msgid "Look up error: %s"
msgstr ""
#: ironic_inspector/process.py:71
#, python-format
msgid ""
"The following failures happened during running pre-processing hooks for "
"node %(uuid)s:\n"
"%(failures)s"
msgstr ""
#: ironic_inspector/process.py:76
msgid "Data pre-processing failed"
msgstr ""
#: ironic_inspector/process.py:79
#, python-format
msgid ""
"The following failures happened during running pre-processing hooks for "
"unknown node:\n"
"%(failures)s"
msgstr ""
#: ironic_inspector/process.py:89
#, python-format
msgid "Node UUID %s was found in cache, but is not found in Ironic"
msgstr ""
#: ironic_discoverd/process.py:65
#: ironic_inspector/process.py:100
msgid "Unexpected exception during processing"
msgstr ""
#: ironic_discoverd/process.py:155
#: ironic_inspector/process.py:196
#, python-format
msgid ""
"Failed to validate updated IPMI credentials for node %s, node might "
"require maintenance"
msgstr ""
#: ironic_discoverd/process.py:167
#: ironic_inspector/process.py:208
#, python-format
msgid ""
"Failed to power off node %(node)s, check it's power management "
"configuration: %(exc)s"
msgstr ""
#: ironic_discoverd/process.py:183
#: ironic_inspector/utils.py:79
msgid "Authentication required"
msgstr ""
#: ironic_inspector/utils.py:83
msgid "Access denied"
msgstr ""
#: ironic_inspector/utils.py:128
#, python-format
msgid "Timeout waiting for node %s to power off after introspection"
msgid ""
"Refusing to introspect node %(node)s with provision state \"%(state)s\" "
"and maintenance mode off"
msgstr ""
#: ironic_discoverd/plugins/edeploy.py:50
msgid "edeploy plugin: no \"data\" key in the received JSON"
msgstr ""
#: ironic_discoverd/plugins/standard.py:37
#: ironic_inspector/plugins/standard.py:45
#, python-format
msgid "The following required parameters are missing: %s"
msgstr ""
#: ironic_discoverd/plugins/standard.py:61
#: ironic_inspector/plugins/standard.py:84
msgid "No interfaces supplied by the ramdisk"
msgstr ""
#: ironic_discoverd/plugins/standard.py:91
#: ironic_inspector/plugins/standard.py:111
#, python-format
msgid ""
"No valid interfaces found for node with BMC %(ipmi_address)s, got "
"%(interfaces)s"
msgstr ""
#: ironic_discoverd/plugins/standard.py:119
#: ironic_inspector/plugins/standard.py:167
#, python-format
msgid "Ramdisk reported error: %s"
msgstr ""

View File

@ -1,13 +1,13 @@
[compile_catalog]
directory = locale
domain = ironic-discoverd
domain = ironic-inspector
[update_catalog]
domain = ironic-discoverd
domain = ironic-inspector
output_dir = locale
input_file = locale/ironic-discoverd.pot
input_file = locale/ironic-inspector.pot
[extract_messages]
keywords = _ gettext ngettext l_ lazy_gettext
mapping_file = babel.cfg
output_file = locale/ironic-discoverd.pot
output_file = locale/ironic-inspector.pot

View File

@ -13,44 +13,44 @@ except EnvironmentError:
install_requires = []
with open('ironic_discoverd/__init__.py', 'rb') as fp:
with open('ironic_inspector/__init__.py', 'rb') as fp:
exec(fp.read())
setup(
name = "ironic-discoverd",
name = "ironic-inspector",
version = __version__,
description = open('README.rst', 'r').readline().strip(),
author = "Dmitry Tantsur",
author_email = "dtantsur@redhat.com",
url = "https://pypi.python.org/pypi/ironic-discoverd",
packages = ['ironic_discoverd', 'ironic_discoverd.plugins',
'ironic_discoverd.test', 'ironic_discoverd.common',
'ironic_discoverd_ramdisk', 'ironic_discoverd_ramdisk.test'],
packages = ['ironic_inspector', 'ironic_inspector.plugins',
'ironic_inspector.test', 'ironic_inspector.common',
'ironic_inspector_ramdisk', 'ironic_inspector_ramdisk.test'],
install_requires = install_requires,
# because entry points don't work with multiple packages
scripts = ['bin/ironic-discoverd-ramdisk'],
scripts = ['bin/ironic-inspector-ramdisk'],
entry_points = {
'console_scripts': [
"ironic-discoverd = ironic_discoverd.main:main",
"ironic-inspector = ironic_inspector.main:main",
],
'ironic_discoverd.hooks': [
"scheduler = ironic_discoverd.plugins.standard:SchedulerHook",
"validate_interfaces = ironic_discoverd.plugins.standard:ValidateInterfacesHook",
"ramdisk_error = ironic_discoverd.plugins.standard:RamdiskErrorHook",
"example = ironic_discoverd.plugins.example:ExampleProcessingHook",
"edeploy = ironic_discoverd.plugins.edeploy:eDeployHook",
"root_device_hint = ironic_discoverd.plugins.root_device_hint:RootDeviceHintHook",
'ironic_inspector.hooks': [
"scheduler = ironic_inspector.plugins.standard:SchedulerHook",
"validate_interfaces = ironic_inspector.plugins.standard:ValidateInterfacesHook",
"ramdisk_error = ironic_inspector.plugins.standard:RamdiskErrorHook",
"example = ironic_inspector.plugins.example:ExampleProcessingHook",
"edeploy = ironic_inspector.plugins.edeploy:eDeployHook",
"root_device_hint = ironic_inspector.plugins.root_device_hint:RootDeviceHintHook",
],
'openstack.cli.extension': [
'baremetal-introspection = ironic_discoverd.shell',
'baremetal-introspection = ironic_inspector.shell',
],
'openstack.baremetal_introspection.v1': [
"baremetal_introspection_start = ironic_discoverd.shell:StartCommand",
"baremetal_introspection_status = ironic_discoverd.shell:StatusCommand",
"baremetal_introspection_start = ironic_inspector.shell:StartCommand",
"baremetal_introspection_status = ironic_inspector.shell:StatusCommand",
],
'oslo.config.opts': [
"ironic_discoverd = ironic_discoverd.conf:list_opts",
"ironic_inspector = ironic_inspector.conf:list_opts",
],
},
classifiers = [

12
tox.ini
View File

@ -8,8 +8,8 @@ deps =
-r{toxinidir}/test-requirements.txt
-r{toxinidir}/plugin-requirements.txt
commands =
coverage run --branch --include "ironic_discoverd*" -m unittest discover ironic_discoverd.test
coverage run --branch --include "ironic_discoverd_ramdisk*" -a -m unittest discover ironic_discoverd_ramdisk.test
coverage run --branch --include "ironic_inspector*" -m unittest discover ironic_inspector.test
coverage run --branch --include "ironic_inspector_ramdisk*" -a -m unittest discover ironic_inspector_ramdisk.test
coverage report -m --fail-under 90
setenv = PYTHONDONTWRITEBYTECODE=1
@ -20,14 +20,14 @@ deps =
-r{toxinidir}/test-requirements.txt
-r{toxinidir}/plugin-requirements.txt
commands =
flake8 ironic_discoverd ironic_discoverd_ramdisk
doc8 README.rst CONTRIBUTING.rst HTTP-API.rst RELEASES.rst
flake8 ironic_inspector ironic_inspector_ramdisk
doc8 README.rst CONTRIBUTING.rst HTTP-API.rst
[flake8]
max-complexity=15
[hacking]
import_exceptions = ironicclient.exceptions,ironic_discoverd.common.i18n
import_exceptions = ironicclient.exceptions,ironic_inspector.common.i18n
[testenv:func]
basepython = python2.7
@ -42,4 +42,4 @@ commands =
commands =
oslo-config-generator \
--output-file example.conf \
--namespace ironic_discoverd
--namespace ironic_inspector