manual introspection trigger command

Change-Id: I64e66682c1e54f6edc260a22f46f5f6df8e85af1
Story: 2005896
Task: 33756
This commit is contained in:
Julia Kreger 2019-06-18 18:26:13 -07:00
parent 94048fe97e
commit 696606f682
9 changed files with 180 additions and 6 deletions

View File

@ -60,6 +60,33 @@ full endpoint of Ironic Inspector, for example::
Make sure your DHCP environment is set to boot IPA by default.
For the cases where the infrastructure operator and cloud user are the same,
an additional tool exists that can be installed alongside the agent inside
a running instance. This is the ``ironic-collect-introspection-data``
command which allows for a node in ``ACTIVE`` state to publish updated
introspection data to ironic-inspector. This ability requires ironic-inspector
to be configured with ``[processing]permit_active_introspection`` set to
``True``. For example::
ironic-collect-introspection-data --inspection_callback_url http://IP:5050/v1/continue
Alternatively, this command may also be used with multicast DNS
functionality to identify the `Ironic Inspector`_ service endpoint.
For example::
ironic-collect-introspection-data --inspection_callback_url mdns
An additional daemon mode may be useful for some operators who wish to receive
regular updates, in the form of the ``[DEFAULT]introspection_daemon`` boolean
configuration option.
For example::
ironic-collect-introspection-data --inspection_callback_url mdns --introspection_daemon
The above command will attempt to connect to introspection and will then enter
a loop to publish every 300 seconds. This can be tuned with the
``[DEFAULT]introspection_daemon_post_interval`` configuration option.
.. _Ironic Inspector: https://docs.openstack.org/ironic-inspector/
Hardware Inventory

View File

@ -388,8 +388,13 @@ class IronicPythonAgent(base.ExecuteCommandMixin):
# lookup will fail due to unknown MAC.
uuid = None
if cfg.CONF.inspection_callback_url:
uuid = inspector.inspect()
try:
# Attempt inspection. This may fail, and previously
# an error would be logged.
uuid = inspector.inspect()
except errors.InspectionError as e:
LOG.error('Failed to perform inspection: %(err)s',
{'error': e})
if self.api_url:
self._wait_for_interface()
content = self.api_client.lookup_node(

View File

@ -0,0 +1,30 @@
# Copyright 2013 Rackspace, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
from oslo_config import cfg
from oslo_log import log
from ironic_python_agent import inspect as inspection
CONF = cfg.CONF
def run():
"""Entrypoint for IronicPythonAgent."""
log.register_options(CONF)
CONF(args=sys.argv[1:])
log.setup(CONF, 'ironic-python-agent')
inspection.IronicInspection().run()

View File

@ -217,6 +217,18 @@ cli_opts = [
'Must be provided together with "certfile" option. '
'Default is to not present any client certificates to '
'the server.'),
cfg.BoolOpt('introspection_daemon',
default=False,
help='When the ``ironic-collect-introspection-data`` '
'command is executed, continue running as '
'a background process and continue to post data '
'to the bare metal inspection service.'),
cfg.IntOpt('introspection_daemon_post_interval',
default=300,
help='The interval in seconds by which to transmit data to '
'the bare metal introspection service when the '
'``ironic-collect-introspection-data`` program is '
'executing in daemon mode.'),
]
CONF.register_cli_opts(cli_opts)

View File

@ -0,0 +1,86 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import select
import threading
from ironic_lib import exception
from oslo_config import cfg
from oslo_log import log
from ironic_python_agent import errors
from ironic_python_agent import inspector
LOG = log.getLogger(__name__)
class IronicInspection(threading.Thread):
"""Class for manual inspection functionality."""
def __init__(self):
super(IronicInspection, self).__init__()
if bool(cfg.CONF.keyfile) != bool(cfg.CONF.certfile):
LOG.warning("Only one of 'keyfile' and 'certfile' options is "
"defined in config file. Its value will be ignored.")
def _run(self):
try:
daemon_mode = cfg.CONF.introspection_daemon
post_interval = cfg.CONF.introspection_daemon_post_interval
inspector.inspect()
if not daemon_mode:
# No reason to continue unless we're in daemon mode.
return
self.reader, self.writer = os.pipe()
p = select.poll()
p.register(self.reader)
try:
while daemon_mode:
LOG.info('Sleeping until next check-in.')
# TODO(TheJulia): It would likely be good to introduce
# some jitter into this at some point...
if p.poll(post_interval * 1000):
if os.read(self.reader, 1).decode() == 'a':
break
try:
inspector.inspect()
except errors.InspectionError as e:
# Failures happen, no reason to exit as
# the failure could be intermittent.
LOG.warning('Error reporting introspection '
'data: %(err)s',
{'err': e})
except exception.ServiceLookupFailure as e:
# Likely a mDNS lookup failure. We should
# keep retrying.
LOG.error('Error looking up introspection '
'endpoint: %(err)s',
{'err': e})
finally:
os.close(self.reader)
os.close(self.writer)
self.reader = None
self.writer = None
except errors.InspectionError as e:
msg = "Inspection failed: %s" % e
raise errors.InspectionError(msg)
def run(self):
"""Run Inspection."""
if not cfg.CONF.inspection_callback_url:
cfg.CONF.set_override('inspection_callback_url', 'mdns')
self._run()

View File

@ -104,8 +104,8 @@ def inspect():
failures.raise_if_needed()
if resp is None:
LOG.info('stopping inspection, as inspector returned an error')
return
raise errors.InspectionError('stopping inspection, as inspector '
'returned an error')
LOG.info('inspection finished successfully')
return resp.get('uuid')

View File

@ -137,11 +137,11 @@ class TestInspect(base.IronicAgentTest):
mock_call.return_value = None
mock_ext_mgr.return_value = [self.mock_ext]
result = inspector.inspect()
self.assertRaises(errors.InspectionError,
inspector.inspect)
self.mock_collect.assert_called_with_failure()
mock_call.assert_called_with_failure()
self.assertIsNone(result)
@mock.patch.object(requests, 'post', autospec=True)

View File

@ -0,0 +1,13 @@
---
features:
- |
Adds a new CLI command ``ironic-collect-introspection-data`` to enable
manually publishing into the ``baremetal-introspection`` service.
Executing this command on a system unknown to the Bare Metal service
will likely result in the machine becoming registered to Ironic, and
as such this command should be used with caution.
If the capability to update introspection data for running machines
has been enabled in the Bare Metal introspection service, then an
operator may use this command in the ``active`` or ``rescue`` states
to update introspection data.

View File

@ -23,6 +23,7 @@ oslo.config.opts =
console_scripts =
ironic-python-agent = ironic_python_agent.cmd.agent:run
ironic-collect-introspection-data = ironic_python_agent.cmd.inspect:run
ironic_python_agent.extensions =
standby = ironic_python_agent.extensions.standby:StandbyExtension