summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDmitry Tantsur <dtantsur@protonmail.com>2022-06-17 09:48:19 +0200
committerDmitry Tantsur <dtantsur@protonmail.com>2022-06-17 16:19:58 +0200
commita98675890f3d404189e5a967d8f215628beb9ac7 (patch)
tree908e038a063e19ac444f2e6feea2811c9e806aba
parent9dca97736f8811aa9dd851eee758f208a57e1822 (diff)
downloadironic-python-agent-a98675890f3d404189e5a967d8f215628beb9ac7.tar.gz
Collect udev properties in the ramdisk logs
Change-Id: Ifcf3dfff00b604dec1e2f430369ab8053f50f137
-rw-r--r--ironic_python_agent/tests/unit/test_utils.py45
-rw-r--r--ironic_python_agent/utils.py43
-rw-r--r--releasenotes/notes/collect-udev-f6ada5163cf4a26c.yaml5
3 files changed, 89 insertions, 4 deletions
diff --git a/ironic_python_agent/tests/unit/test_utils.py b/ironic_python_agent/tests/unit/test_utils.py
index c99f7dca..8adb78cc 100644
--- a/ironic_python_agent/tests/unit/test_utils.py
+++ b/ironic_python_agent/tests/unit/test_utils.py
@@ -16,6 +16,7 @@
import errno
import glob
import io
+import json
import os
import shutil
import subprocess
@@ -410,12 +411,14 @@ class TestUtils(ironic_agent_base.IronicAgentTest):
mock_call.side_effect = os_error
self.assertFalse(utils.is_journalctl_present())
+ @mock.patch.object(utils, '_collect_udev', autospec=True)
@mock.patch.object(utils, 'gzip_and_b64encode', autospec=True)
@mock.patch.object(utils, 'is_journalctl_present', autospec=True)
@mock.patch.object(utils, 'get_command_output', autospec=True)
@mock.patch.object(utils, 'get_journalctl_output', autospec=True)
def test_collect_system_logs_journald(
- self, mock_logs, mock_outputs, mock_journalctl, mock_gzip_b64):
+ self, mock_logs, mock_outputs, mock_journalctl, mock_gzip_b64,
+ mock_udev):
mock_journalctl.return_value = True
ret = 'Patrick Star'
mock_gzip_b64.return_value = ret
@@ -435,13 +438,16 @@ class TestUtils(ironic_agent_base.IronicAgentTest):
'mount': mock.ANY, 'parted': mock.ANY,
'multipath': mock.ANY},
file_list=[])
+ mock_udev.assert_called_once_with(mock.ANY)
+ @mock.patch.object(utils, '_collect_udev', autospec=True)
@mock.patch.object(utils, 'gzip_and_b64encode', autospec=True)
@mock.patch.object(utils, 'is_journalctl_present', autospec=True)
@mock.patch.object(utils, 'get_command_output', autospec=True)
@mock.patch.object(utils, 'get_journalctl_output', autospec=True)
def test_collect_system_logs_journald_with_logfile(
- self, mock_logs, mock_outputs, mock_journalctl, mock_gzip_b64):
+ self, mock_logs, mock_outputs, mock_journalctl, mock_gzip_b64,
+ mock_udev):
tmp = tempfile.NamedTemporaryFile()
self.addCleanup(lambda: tmp.close())
@@ -465,12 +471,15 @@ class TestUtils(ironic_agent_base.IronicAgentTest):
'mount': mock.ANY, 'parted': mock.ANY,
'multipath': mock.ANY},
file_list=[tmp.name])
+ mock_udev.assert_called_once_with(mock.ANY)
+ @mock.patch.object(utils, '_collect_udev', autospec=True)
@mock.patch.object(utils, 'gzip_and_b64encode', autospec=True)
@mock.patch.object(utils, 'is_journalctl_present', autospec=True)
@mock.patch.object(utils, 'get_command_output', autospec=True)
def test_collect_system_logs_non_journald(
- self, mock_outputs, mock_journalctl, mock_gzip_b64):
+ self, mock_outputs, mock_journalctl, mock_gzip_b64,
+ mock_udev):
mock_journalctl.return_value = False
ret = 'SpongeBob SquarePants'
mock_gzip_b64.return_value = ret
@@ -490,12 +499,15 @@ class TestUtils(ironic_agent_base.IronicAgentTest):
'mount': mock.ANY, 'parted': mock.ANY,
'multipath': mock.ANY},
file_list=['/var/log'])
+ mock_udev.assert_called_once_with(mock.ANY)
+ @mock.patch.object(utils, '_collect_udev', autospec=True)
@mock.patch.object(utils, 'gzip_and_b64encode', autospec=True)
@mock.patch.object(utils, 'is_journalctl_present', autospec=True)
@mock.patch.object(utils, 'get_command_output', autospec=True)
def test_collect_system_logs_non_journald_with_logfile(
- self, mock_outputs, mock_journalctl, mock_gzip_b64):
+ self, mock_outputs, mock_journalctl, mock_gzip_b64,
+ mock_udev):
tmp = tempfile.NamedTemporaryFile()
self.addCleanup(lambda: tmp.close())
@@ -519,6 +531,31 @@ class TestUtils(ironic_agent_base.IronicAgentTest):
'mount': mock.ANY, 'parted': mock.ANY,
'multipath': mock.ANY},
file_list=['/var/log', tmp.name])
+ mock_udev.assert_called_once_with(mock.ANY)
+
+ @mock.patch('pyudev.Context', lambda: mock.sentinel.context)
+ @mock.patch('pyudev.Devices.from_device_file', autospec=True)
+ @mock.patch.object(ironic_utils, 'execute', autospec=True)
+ def test_collect_udev(self, mock_execute, mock_from_dev):
+ mock_execute.return_value = """
+ fake0
+ fake1
+ fake42
+ """, ""
+ mock_from_dev.side_effect = [
+ mock.Mock(properties={'ID_UUID': '0'}),
+ RuntimeError('nope'),
+ {'ID_UUID': '42'}
+ ]
+
+ result = {}
+ utils._collect_udev(result)
+ self.assertEqual({'udev/fake0', 'udev/fake42'}, set(result))
+ for i in ('0', '42'):
+ buf = result[f'udev/fake{i}']
+ # Avoiding getvalue on purpose - checking that the IO is not closed
+ val = json.loads(buf.read().decode('utf-8'))
+ self.assertEqual({'ID_UUID': i}, val)
def test_get_ssl_client_options(self):
# defaults
diff --git a/ironic_python_agent/utils.py b/ironic_python_agent/utils.py
index 66f6819f..bcf9a0a4 100644
--- a/ironic_python_agent/utils.py
+++ b/ironic_python_agent/utils.py
@@ -18,6 +18,7 @@ import copy
import errno
import glob
import io
+import json
import os
import re
import shutil
@@ -33,6 +34,7 @@ from oslo_log import log as logging
from oslo_serialization import base64
from oslo_serialization import jsonutils
from oslo_utils import units
+import pyudev
import requests
import tenacity
@@ -530,6 +532,42 @@ def gzip_and_b64encode(io_dict=None, file_list=None):
return base64.encode_as_text(fp.getvalue())
+def _collect_udev(io_dict):
+ """Collect device properties from udev."""
+ try:
+ out, _e = ironic_utils.execute('lsblk', '-no', 'KNAME')
+ except processutils.ProcessExecutionError as exc:
+ LOG.warning('Could not list block devices: %s', exc)
+ return
+
+ context = pyudev.Context()
+
+ for kname in out.splitlines():
+ kname = kname.strip()
+ if not kname:
+ continue
+
+ name = os.path.join('/dev', kname)
+
+ try:
+ udev = pyudev.Devices.from_device_file(context, name)
+ except Exception as e:
+ LOG.warning("Device %(dev)s is inaccessible, skipping... "
+ "Error: %(error)s", {'dev': name, 'error': e})
+ continue
+
+ try:
+ props = dict(udev.properties)
+ except AttributeError: # pyudev < 0.20
+ props = dict(udev)
+
+ fp = io.TextIOWrapper(io.BytesIO(), encoding='utf-8')
+ json.dump(props, fp)
+ buf = fp.detach()
+ buf.seek(0)
+ io_dict[f'udev/{kname}'] = buf
+
+
def collect_system_logs(journald_max_lines=None):
"""Collect system logs.
@@ -568,6 +606,11 @@ def collect_system_logs(journald_max_lines=None):
for name, cmd in COLLECT_LOGS_COMMANDS.items():
try_get_command_output(io_dict, name, cmd)
+ try:
+ _collect_udev(io_dict)
+ except Exception:
+ LOG.exception('Unexpected error when collecting udev properties')
+
return gzip_and_b64encode(io_dict=io_dict, file_list=file_list)
diff --git a/releasenotes/notes/collect-udev-f6ada5163cf4a26c.yaml b/releasenotes/notes/collect-udev-f6ada5163cf4a26c.yaml
new file mode 100644
index 00000000..24437c3b
--- /dev/null
+++ b/releasenotes/notes/collect-udev-f6ada5163cf4a26c.yaml
@@ -0,0 +1,5 @@
+---
+other:
+ - |
+ Block devices properties reported by udev are now collected with the
+ ramdisk logs.