Collect udev properties in the ramdisk logs

Change-Id: Ifcf3dfff00b604dec1e2f430369ab8053f50f137
This commit is contained in:
Dmitry Tantsur 2022-06-17 09:48:19 +02:00
parent 9dca97736f
commit a98675890f
3 changed files with 89 additions and 4 deletions

View File

@ -16,6 +16,7 @@
import errno import errno
import glob import glob
import io import io
import json
import os import os
import shutil import shutil
import subprocess import subprocess
@ -410,12 +411,14 @@ class TestUtils(ironic_agent_base.IronicAgentTest):
mock_call.side_effect = os_error mock_call.side_effect = os_error
self.assertFalse(utils.is_journalctl_present()) 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, 'gzip_and_b64encode', autospec=True)
@mock.patch.object(utils, 'is_journalctl_present', 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_command_output', autospec=True)
@mock.patch.object(utils, 'get_journalctl_output', autospec=True) @mock.patch.object(utils, 'get_journalctl_output', autospec=True)
def test_collect_system_logs_journald( 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 mock_journalctl.return_value = True
ret = 'Patrick Star' ret = 'Patrick Star'
mock_gzip_b64.return_value = ret mock_gzip_b64.return_value = ret
@ -435,13 +438,16 @@ class TestUtils(ironic_agent_base.IronicAgentTest):
'mount': mock.ANY, 'parted': mock.ANY, 'mount': mock.ANY, 'parted': mock.ANY,
'multipath': mock.ANY}, 'multipath': mock.ANY},
file_list=[]) 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, 'gzip_and_b64encode', autospec=True)
@mock.patch.object(utils, 'is_journalctl_present', 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_command_output', autospec=True)
@mock.patch.object(utils, 'get_journalctl_output', autospec=True) @mock.patch.object(utils, 'get_journalctl_output', autospec=True)
def test_collect_system_logs_journald_with_logfile( 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() tmp = tempfile.NamedTemporaryFile()
self.addCleanup(lambda: tmp.close()) self.addCleanup(lambda: tmp.close())
@ -465,12 +471,15 @@ class TestUtils(ironic_agent_base.IronicAgentTest):
'mount': mock.ANY, 'parted': mock.ANY, 'mount': mock.ANY, 'parted': mock.ANY,
'multipath': mock.ANY}, 'multipath': mock.ANY},
file_list=[tmp.name]) 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, 'gzip_and_b64encode', autospec=True)
@mock.patch.object(utils, 'is_journalctl_present', 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_command_output', autospec=True)
def test_collect_system_logs_non_journald( 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 mock_journalctl.return_value = False
ret = 'SpongeBob SquarePants' ret = 'SpongeBob SquarePants'
mock_gzip_b64.return_value = ret mock_gzip_b64.return_value = ret
@ -490,12 +499,15 @@ class TestUtils(ironic_agent_base.IronicAgentTest):
'mount': mock.ANY, 'parted': mock.ANY, 'mount': mock.ANY, 'parted': mock.ANY,
'multipath': mock.ANY}, 'multipath': mock.ANY},
file_list=['/var/log']) 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, 'gzip_and_b64encode', autospec=True)
@mock.patch.object(utils, 'is_journalctl_present', 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_command_output', autospec=True)
def test_collect_system_logs_non_journald_with_logfile( 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() tmp = tempfile.NamedTemporaryFile()
self.addCleanup(lambda: tmp.close()) self.addCleanup(lambda: tmp.close())
@ -519,6 +531,31 @@ class TestUtils(ironic_agent_base.IronicAgentTest):
'mount': mock.ANY, 'parted': mock.ANY, 'mount': mock.ANY, 'parted': mock.ANY,
'multipath': mock.ANY}, 'multipath': mock.ANY},
file_list=['/var/log', tmp.name]) 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): def test_get_ssl_client_options(self):
# defaults # defaults

View File

@ -18,6 +18,7 @@ import copy
import errno import errno
import glob import glob
import io import io
import json
import os import os
import re import re
import shutil import shutil
@ -33,6 +34,7 @@ from oslo_log import log as logging
from oslo_serialization import base64 from oslo_serialization import base64
from oslo_serialization import jsonutils from oslo_serialization import jsonutils
from oslo_utils import units from oslo_utils import units
import pyudev
import requests import requests
import tenacity import tenacity
@ -530,6 +532,42 @@ def gzip_and_b64encode(io_dict=None, file_list=None):
return base64.encode_as_text(fp.getvalue()) 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): def collect_system_logs(journald_max_lines=None):
"""Collect system logs. """Collect system logs.
@ -568,6 +606,11 @@ def collect_system_logs(journald_max_lines=None):
for name, cmd in COLLECT_LOGS_COMMANDS.items(): for name, cmd in COLLECT_LOGS_COMMANDS.items():
try_get_command_output(io_dict, name, cmd) 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) return gzip_and_b64encode(io_dict=io_dict, file_list=file_list)

View File

@ -0,0 +1,5 @@
---
other:
- |
Block devices properties reported by udev are now collected with the
ramdisk logs.