Add "logs" and "extra-hardware" inspection collectors

This is a port of downstream inspector ramdisk plugins we found helpful.
* logs - sends journald logs with inspection data.
* extra-hardware - uses hardware-detect utility to collect bigger
  hardware inventory and to run benchmarks.

Change-Id: If05402606c45185d618279eef46e68c51209f82b
This commit is contained in:
Dmitry Tantsur 2015-09-18 12:31:40 +02:00
parent 378197caee
commit 9d6b0864e3
4 changed files with 146 additions and 0 deletions

View File

@ -13,7 +13,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import base64
import io
import json
import logging
import tarfile
import netaddr
from oslo_concurrency import processutils
@ -251,3 +255,67 @@ def collect_default(data, failures):
# dropped after inspector is ready (probably in Mitaka cycle).
discover_network_properties(inventory, data, failures)
discover_scheduling_properties(inventory, data, root_disk)
def collect_logs(data, failures):
"""Collect journald logs from the ramdisk.
As inspection runs before any nodes details are known, it's handy to have
logs returned with data. This collector sends logs to inspector in format
expected by the 'ramdisk_error' plugin: base64 encoded tar.gz.
This collector should be installed last in the collector chain, otherwise
it won't collect enough logs.
This collector does not report failures.
:param data: mutable data that we'll send to inspector
:param failures: AccumulatedFailures object
"""
try:
out, _e = utils.execute('journalctl', '--full', '--no-pager', '-b',
'-n', '10000')
except (processutils.ProcessExecutionError, OSError):
LOG.warn('failed to get system journal')
return
journal = io.BytesIO(out.encode('utf-8'))
with io.BytesIO() as fp:
with tarfile.open(fileobj=fp, mode='w:gz') as tar:
tarinfo = tarfile.TarInfo('journal')
tarinfo.size = len(out)
tar.addfile(tarinfo, journal)
fp.seek(0)
data['logs'] = base64.b64encode(fp.getvalue())
def collect_extra_hardware(data, failures):
"""Collect detailed inventory using 'hardware-detect' utility.
Recognizes ipa-inspection-benchmarks with list of benchmarks (possible
values are cpu, disk, mem) to run. No benchmarks are run by default, as
they're pretty time-consuming.
Puts collected data as JSON under 'data' key.
Requires 'hardware' python package to be installed on the ramdisk in
addition to the packages in requirements.txt.
:param data: mutable data that we'll send to inspector
:param failures: AccumulatedFailures object
"""
benchmarks = utils.get_agent_params().get('ipa-inspection-benchmarks', [])
if benchmarks:
benchmarks = ['--benchmark'] + benchmarks.split(',')
try:
out, err = utils.execute('hardware-detect', *benchmarks)
except (processutils.ProcessExecutionError, OSError) as exc:
failures.add('failed to run hardware-detect utility: %s', exc)
return
try:
data['data'] = json.loads(out)
except ValueError as exc:
msg = 'JSON returned from hardware-detect cannot be decoded: %s'
failures.add(msg, exc)

View File

@ -13,8 +13,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import base64
import collections
import copy
import io
import tarfile
import unittest
import mock
@ -363,3 +366,74 @@ class TestCollectDefault(BaseDiscoverTest):
self.failures)
mock_discover_sched.assert_called_once_with(
self.inventory, self.data, root_disk=None)
@mock.patch.object(utils, 'execute', autospec=True)
class TestCollectLogs(unittest.TestCase):
def test(self, mock_execute):
contents = 'journal contents'
mock_execute.return_value = (contents, '')
data = {}
inspector.collect_logs(data, None)
res = io.BytesIO(base64.b64decode(data['logs']))
with tarfile.open(fileobj=res) as tar:
members = [(m.name, m.size) for m in tar]
self.assertEqual([('journal', len(contents))], members)
member = tar.extractfile('journal')
self.assertEqual(contents, member.read().decode('utf-8'))
def test_no_journal(self, mock_execute):
mock_execute.side_effect = OSError()
data = {}
inspector.collect_logs(data, None)
self.assertFalse(data)
@mock.patch.object(utils, 'execute', autospec=True)
class TestCollectExtraHardware(unittest.TestCase):
def setUp(self):
super(TestCollectExtraHardware, self).setUp()
self.data = {}
self.failures = utils.AccumulatedFailures()
def test_no_benchmarks(self, mock_execute):
mock_execute.return_value = ("[1, 2, 3]", "")
inspector.collect_extra_hardware(self.data, None)
self.assertEqual({'data': [1, 2, 3]}, self.data)
mock_execute.assert_called_once_with('hardware-detect')
@mock.patch.object(utils, 'get_agent_params', autospec=True)
def test_benchmarks(self, mock_params, mock_execute):
mock_params.return_value = {'ipa-inspection-benchmarks': 'cpu,mem'}
mock_execute.return_value = ("[1, 2, 3]", "")
inspector.collect_extra_hardware(self.data, None)
self.assertEqual({'data': [1, 2, 3]}, self.data)
mock_execute.assert_called_once_with('hardware-detect',
'--benchmark',
'cpu', 'mem')
def test_execute_failed(self, mock_execute):
mock_execute.side_effect = processutils.ProcessExecutionError()
inspector.collect_extra_hardware(self.data, self.failures)
self.assertNotIn('data', self.data)
self.assertTrue(self.failures)
mock_execute.assert_called_once_with('hardware-detect')
def test_parsing_failed(self, mock_execute):
mock_execute.return_value = ("foobar", "")
inspector.collect_extra_hardware(self.data, self.failures)
self.assertNotIn('data', self.data)
self.assertTrue(self.failures)
mock_execute.assert_called_once_with('hardware-detect')

2
plugin-requirements.txt Normal file
View File

@ -0,0 +1,2 @@
# Required for 'extra-hardware' inspection collector
hardware>=0.9

View File

@ -32,6 +32,8 @@ ironic_python_agent.hardware_managers =
ironic_python_agent.inspector.collectors =
default = ironic_python_agent.inspector:collect_default
logs = ironic_python_agent.inspector:collect_logs
extra-hardware = ironic_python_agent.inspector:collect_extra_hardware
[pbr]
autodoc_index_modules = True