Merge "Revert "Fix vmedia network config drive handling""
This commit is contained in:
commit
beccfe8c92
@ -30,9 +30,8 @@ def run():
|
|||||||
"""Entrypoint for IronicPythonAgent."""
|
"""Entrypoint for IronicPythonAgent."""
|
||||||
# NOTE(dtantsur): this must happen very early of the files from
|
# NOTE(dtantsur): this must happen very early of the files from
|
||||||
# /etc/ironic-python-agent.d won't be loaded
|
# /etc/ironic-python-agent.d won't be loaded
|
||||||
vmedia_configuration = utils.copy_config_from_vmedia()
|
utils.copy_config_from_vmedia()
|
||||||
if vmedia_configuration:
|
|
||||||
utils.trigger_glean_network_refresh()
|
|
||||||
log.register_options(CONF)
|
log.register_options(CONF)
|
||||||
CONF(args=sys.argv[1:])
|
CONF(args=sys.argv[1:])
|
||||||
# Debug option comes from oslo.log, allow overriding it via kernel cmdline
|
# Debug option comes from oslo.log, allow overriding it via kernel cmdline
|
||||||
|
@ -883,7 +883,7 @@ class TestClockSyncUtils(ironic_agent_base.IronicAgentTest):
|
|||||||
@mock.patch.object(shutil, 'copy', autospec=True)
|
@mock.patch.object(shutil, 'copy', autospec=True)
|
||||||
@mock.patch.object(ironic_utils, 'mounted', autospec=True)
|
@mock.patch.object(ironic_utils, 'mounted', autospec=True)
|
||||||
@mock.patch.object(utils, 'execute', autospec=True)
|
@mock.patch.object(utils, 'execute', autospec=True)
|
||||||
class TestConfigFromVmedia(testtools.TestCase):
|
class TestCopyConfigFromVmedia(testtools.TestCase):
|
||||||
|
|
||||||
def test_vmedia_found_not_booted_from_vmedia(
|
def test_vmedia_found_not_booted_from_vmedia(
|
||||||
self, mock_execute, mock_mount, mock_copy,
|
self, mock_execute, mock_mount, mock_copy,
|
||||||
@ -909,107 +909,45 @@ class TestConfigFromVmedia(testtools.TestCase):
|
|||||||
mock_check_vmedia.assert_not_called()
|
mock_check_vmedia.assert_not_called()
|
||||||
self.assertFalse(mock_booted_from_vmedia.called)
|
self.assertFalse(mock_booted_from_vmedia.called)
|
||||||
|
|
||||||
@mock.patch.object(os.path, 'ismount', autospec=True)
|
|
||||||
def test_no_files(
|
def test_no_files(
|
||||||
self, mock_ismount, mock_execute, mock_mount, mock_copy,
|
self, mock_execute, mock_mount, mock_copy,
|
||||||
mock_find_device, mock_check_vmedia, mock_booted_from_vmedia):
|
mock_find_device, mock_check_vmedia, mock_booted_from_vmedia):
|
||||||
mock_ismount.return_value = False
|
|
||||||
mock_booted_from_vmedia.return_value = True
|
mock_booted_from_vmedia.return_value = True
|
||||||
temp_path = tempfile.mkdtemp()
|
temp_path = tempfile.mkdtemp()
|
||||||
self.addCleanup(lambda: shutil.rmtree(temp_path))
|
self.addCleanup(lambda: shutil.rmtree(temp_path))
|
||||||
|
|
||||||
mock_execute.side_effect = [('/mnt/config', ()),
|
mock_execute.side_effect = processutils.ProcessExecutionError
|
||||||
((), ())]
|
|
||||||
mock_find_device.return_value = '/dev/something'
|
mock_find_device.return_value = '/dev/something'
|
||||||
mock_mount.return_value.__enter__.return_value = temp_path
|
mock_mount.return_value.__enter__.return_value = temp_path
|
||||||
utils.copy_config_from_vmedia()
|
utils.copy_config_from_vmedia()
|
||||||
mock_mount.assert_called_once_with('/dev/something')
|
mock_mount.assert_called_once_with('/dev/something')
|
||||||
mock_execute.assert_has_calls([
|
mock_execute.assert_called_once_with('findmnt', '-n', '-oTARGET',
|
||||||
mock.call('findmnt', '-n', '-oTARGET', '/dev/something'),
|
'/dev/something')
|
||||||
mock.call('umount', '/mnt/config', run_as_root=True)])
|
|
||||||
self.assertEqual(2, mock_execute.call_count)
|
|
||||||
mock_copy.assert_not_called()
|
mock_copy.assert_not_called()
|
||||||
self.assertTrue(mock_booted_from_vmedia.called)
|
self.assertTrue(mock_booted_from_vmedia.called)
|
||||||
|
|
||||||
@mock.patch.object(os.path, 'ismount', autospec=True)
|
|
||||||
def test_mounted_no_files(
|
def test_mounted_no_files(
|
||||||
self, mock_ismount, mock_execute, mock_mount, mock_copy,
|
self, mock_execute, mock_mount, mock_copy,
|
||||||
mock_find_device, mock_check_vmedia, mock_booted_from_vmedia):
|
mock_find_device, mock_check_vmedia, mock_booted_from_vmedia):
|
||||||
|
|
||||||
mock_ismount.side_effect = [True, False]
|
|
||||||
mock_booted_from_vmedia.return_value = True
|
mock_booted_from_vmedia.return_value = True
|
||||||
mock_execute.side_effect = [('/some/path', ''),
|
mock_execute.return_value = '/some/path', ''
|
||||||
((), ()),
|
|
||||||
((), ())]
|
|
||||||
mock_find_device.side_effect = ('/dev/something', '')
|
|
||||||
mock_find_device.return_value = '/dev/something'
|
mock_find_device.return_value = '/dev/something'
|
||||||
utils.copy_config_from_vmedia()
|
utils.copy_config_from_vmedia()
|
||||||
mock_execute.assert_has_calls([
|
|
||||||
mock.call('findmnt', '-n', '-oTARGET', '/dev/something'),
|
|
||||||
mock.call('umount', '/some/path', run_as_root=True),
|
|
||||||
mock.call('umount', '/mnt/config', run_as_root=True)])
|
|
||||||
self.assertEqual(3, mock_execute.call_count)
|
|
||||||
mock_copy.assert_not_called()
|
|
||||||
mock_mount.assert_called_once_with('/dev/something')
|
|
||||||
self.assertTrue(mock_booted_from_vmedia.called)
|
|
||||||
|
|
||||||
@mock.patch.object(os.path, 'ismount', autospec=True)
|
|
||||||
@mock.patch.object(os, 'makedirs', autospec=True)
|
|
||||||
def test_copy(
|
|
||||||
self, mock_makedirs, mock_ismount, mock_execute, mock_mount,
|
|
||||||
mock_copy, mock_find_device, mock_check_vmedia,
|
|
||||||
mock_booted_from_vmedia):
|
|
||||||
|
|
||||||
mock_ismount.return_value = False
|
|
||||||
mock_booted_from_vmedia.return_value = True
|
|
||||||
mock_find_device.return_value = '/dev/something'
|
|
||||||
mock_execute.side_effect = processutils.ProcessExecutionError('meow')
|
|
||||||
path = tempfile.mkdtemp()
|
|
||||||
self.addCleanup(lambda: shutil.rmtree(path))
|
|
||||||
|
|
||||||
def _fake_mount(dev):
|
|
||||||
self.assertEqual('/dev/something', dev)
|
|
||||||
# NOTE(dtantsur): makedirs is mocked
|
|
||||||
os.mkdir(os.path.join(path, 'etc'))
|
|
||||||
os.mkdir(os.path.join(path, 'etc', 'ironic-python-agent'))
|
|
||||||
os.mkdir(os.path.join(path, 'etc', 'ironic-python-agent.d'))
|
|
||||||
with open(os.path.join(path, 'not copied'), 'wt') as fp:
|
|
||||||
fp.write('not copied')
|
|
||||||
with open(os.path.join(path, 'etc', 'ironic-python-agent',
|
|
||||||
'ironic.crt'), 'wt') as fp:
|
|
||||||
fp.write('I am a cert')
|
|
||||||
with open(os.path.join(path, 'etc', 'ironic-python-agent.d',
|
|
||||||
'ironic.conf'), 'wt') as fp:
|
|
||||||
fp.write('I am a config')
|
|
||||||
return mock.MagicMock(**{'__enter__.return_value': path})
|
|
||||||
|
|
||||||
mock_find_device.return_value = '/dev/something'
|
|
||||||
mock_mount.side_effect = _fake_mount
|
|
||||||
|
|
||||||
utils.copy_config_from_vmedia()
|
|
||||||
|
|
||||||
mock_makedirs.assert_has_calls([
|
|
||||||
mock.call('/etc/ironic-python-agent', exist_ok=True),
|
|
||||||
mock.call('/etc/ironic-python-agent.d', exist_ok=True),
|
|
||||||
], any_order=True)
|
|
||||||
mock_mount.assert_called_once_with('/dev/something')
|
|
||||||
mock_copy.assert_has_calls([
|
|
||||||
mock.call(mock.ANY, '/etc/ironic-python-agent/ironic.crt'),
|
|
||||||
mock.call(mock.ANY, '/etc/ironic-python-agent.d/ironic.conf'),
|
|
||||||
], any_order=True)
|
|
||||||
self.assertTrue(mock_booted_from_vmedia.called)
|
|
||||||
mock_execute.assert_called_once_with(
|
mock_execute.assert_called_once_with(
|
||||||
'findmnt', '-n', '-oTARGET', '/dev/something')
|
'findmnt', '-n', '-oTARGET', '/dev/something')
|
||||||
|
mock_copy.assert_not_called()
|
||||||
|
mock_mount.assert_not_called()
|
||||||
|
self.assertTrue(mock_booted_from_vmedia.called)
|
||||||
|
|
||||||
@mock.patch.object(os.path, 'ismount', autospec=True)
|
|
||||||
@mock.patch.object(os, 'makedirs', autospec=True)
|
@mock.patch.object(os, 'makedirs', autospec=True)
|
||||||
def test_copy_mounted(
|
def test_copy(
|
||||||
self, mock_makedirs, mock_ismount, mock_execute, mock_mount,
|
self, mock_makedirs, mock_execute, mock_mount, mock_copy,
|
||||||
mock_copy, mock_find_device, mock_check_vmedia,
|
mock_find_device, mock_check_vmedia, mock_booted_from_vmedia):
|
||||||
mock_booted_from_vmedia):
|
|
||||||
mock_ismount.side_effect = [True, True, False]
|
|
||||||
mock_booted_from_vmedia.return_value = True
|
mock_booted_from_vmedia.return_value = True
|
||||||
mock_find_device.side_effect = ('/dev/something', '')
|
mock_find_device.return_value = '/dev/something'
|
||||||
|
mock_execute.side_effect = processutils.ProcessExecutionError("")
|
||||||
path = tempfile.mkdtemp()
|
path = tempfile.mkdtemp()
|
||||||
self.addCleanup(lambda: shutil.rmtree(path))
|
self.addCleanup(lambda: shutil.rmtree(path))
|
||||||
|
|
||||||
@ -1031,10 +969,6 @@ class TestConfigFromVmedia(testtools.TestCase):
|
|||||||
|
|
||||||
mock_find_device.return_value = '/dev/something'
|
mock_find_device.return_value = '/dev/something'
|
||||||
mock_mount.side_effect = _fake_mount
|
mock_mount.side_effect = _fake_mount
|
||||||
mock_execute.side_effect = [(path, ''),
|
|
||||||
((), ()),
|
|
||||||
((), ()),
|
|
||||||
((), ())]
|
|
||||||
|
|
||||||
utils.copy_config_from_vmedia()
|
utils.copy_config_from_vmedia()
|
||||||
|
|
||||||
@ -1042,16 +976,52 @@ class TestConfigFromVmedia(testtools.TestCase):
|
|||||||
mock.call('/etc/ironic-python-agent', exist_ok=True),
|
mock.call('/etc/ironic-python-agent', exist_ok=True),
|
||||||
mock.call('/etc/ironic-python-agent.d', exist_ok=True),
|
mock.call('/etc/ironic-python-agent.d', exist_ok=True),
|
||||||
], any_order=True)
|
], any_order=True)
|
||||||
mock_execute.assert_has_calls([
|
mock_mount.assert_called_once_with('/dev/something')
|
||||||
mock.call('findmnt', '-n', '-oTARGET', '/dev/something'),
|
|
||||||
mock.call('umount', mock.ANY, run_as_root=True),
|
|
||||||
mock.call('umount', '/mnt/config', run_as_root=True),
|
|
||||||
mock.call('umount', '/mnt/config', run_as_root=True)])
|
|
||||||
mock_copy.assert_has_calls([
|
mock_copy.assert_has_calls([
|
||||||
mock.call(mock.ANY, '/etc/ironic-python-agent/ironic.crt'),
|
mock.call(mock.ANY, '/etc/ironic-python-agent/ironic.crt'),
|
||||||
mock.call(mock.ANY, '/etc/ironic-python-agent.d/ironic.conf'),
|
mock.call(mock.ANY, '/etc/ironic-python-agent.d/ironic.conf'),
|
||||||
], any_order=True)
|
], any_order=True)
|
||||||
mock_mount.assert_called_once_with('/dev/something')
|
self.assertTrue(mock_booted_from_vmedia.called)
|
||||||
|
|
||||||
|
@mock.patch.object(os, 'makedirs', autospec=True)
|
||||||
|
def test_copy_mounted(
|
||||||
|
self, mock_makedirs, mock_execute, mock_mount,
|
||||||
|
mock_copy, mock_find_device, mock_check_vmedia,
|
||||||
|
mock_booted_from_vmedia):
|
||||||
|
mock_booted_from_vmedia.return_value = True
|
||||||
|
mock_find_device.return_value = '/dev/something'
|
||||||
|
path = tempfile.mkdtemp()
|
||||||
|
self.addCleanup(lambda: shutil.rmtree(path))
|
||||||
|
|
||||||
|
# NOTE(dtantsur): makedirs is mocked
|
||||||
|
os.mkdir(os.path.join(path, 'etc'))
|
||||||
|
os.mkdir(os.path.join(path, 'etc', 'ironic-python-agent'))
|
||||||
|
os.mkdir(os.path.join(path, 'etc', 'ironic-python-agent.d'))
|
||||||
|
with open(os.path.join(path, 'not copied'), 'wt') as fp:
|
||||||
|
fp.write('not copied')
|
||||||
|
with open(os.path.join(path, 'etc', 'ironic-python-agent',
|
||||||
|
'ironic.crt'), 'wt') as fp:
|
||||||
|
fp.write('I am a cert')
|
||||||
|
with open(os.path.join(path, 'etc', 'ironic-python-agent.d',
|
||||||
|
'ironic.conf'), 'wt') as fp:
|
||||||
|
fp.write('I am a config')
|
||||||
|
|
||||||
|
mock_execute.return_value = path, ''
|
||||||
|
mock_find_device.return_value = '/dev/something'
|
||||||
|
|
||||||
|
utils.copy_config_from_vmedia()
|
||||||
|
|
||||||
|
mock_makedirs.assert_has_calls([
|
||||||
|
mock.call('/etc/ironic-python-agent', exist_ok=True),
|
||||||
|
mock.call('/etc/ironic-python-agent.d', exist_ok=True),
|
||||||
|
], any_order=True)
|
||||||
|
mock_execute.assert_called_once_with(
|
||||||
|
'findmnt', '-n', '-oTARGET', '/dev/something')
|
||||||
|
mock_copy.assert_has_calls([
|
||||||
|
mock.call(mock.ANY, '/etc/ironic-python-agent/ironic.crt'),
|
||||||
|
mock.call(mock.ANY, '/etc/ironic-python-agent.d/ironic.conf'),
|
||||||
|
], any_order=True)
|
||||||
|
mock_mount.assert_not_called()
|
||||||
self.assertTrue(mock_booted_from_vmedia.called)
|
self.assertTrue(mock_booted_from_vmedia.called)
|
||||||
|
|
||||||
|
|
||||||
@ -1152,73 +1122,6 @@ class TestCheckVirtualMedia(ironic_agent_base.IronicAgentTest):
|
|||||||
'/dev/sdh')
|
'/dev/sdh')
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(shutil, 'copy', autospec=True)
|
|
||||||
@mock.patch.object(os, 'makedirs', autospec=True)
|
|
||||||
@mock.patch.object(utils, '_determine_networking_path', autospec=True)
|
|
||||||
@mock.patch.object(utils, 'execute', autospec=True)
|
|
||||||
class TestNetworkConfigRefresh(ironic_agent_base.IronicAgentTest):
|
|
||||||
|
|
||||||
def test_tinycore(self, mock_exec, mock_path, mock_makedirs,
|
|
||||||
mock_copy):
|
|
||||||
mock_path.return_value = 'tinycore'
|
|
||||||
utils.trigger_glean_network_refresh()
|
|
||||||
self.assertEqual(1, mock_path.call_count)
|
|
||||||
mock_exec.assert_has_calls([
|
|
||||||
mock.call('glean', '--distro', 'tinycore', run_as_root=True),
|
|
||||||
mock.call('/bin/sh', '/opt/network.sh', run_as_root=True)])
|
|
||||||
mock_makedirs.assert_called_once_with('/mnt/config/openstack/latest/',
|
|
||||||
exist_ok=True)
|
|
||||||
|
|
||||||
def test_execution_error_is_handled(self, mock_exec, mock_path,
|
|
||||||
mock_makedirs, mock_copy):
|
|
||||||
mock_path.return_value = 'tinycore'
|
|
||||||
mock_exec.side_effect = OSError()
|
|
||||||
utils.trigger_glean_network_refresh()
|
|
||||||
self.assertEqual(1, mock_path.call_count)
|
|
||||||
mock_exec.assert_called_once_with(
|
|
||||||
'glean', '--distro', 'tinycore', run_as_root=True)
|
|
||||||
mock_makedirs.assert_called_once_with('/mnt/config/openstack/latest/',
|
|
||||||
exist_ok=True)
|
|
||||||
mock_copy.assert_called_once_with(
|
|
||||||
'/etc/ironic-python-agent.d/network_data.json',
|
|
||||||
'/mnt/config/openstack/latest/network_data.json')
|
|
||||||
|
|
||||||
def test_networkd(self, mock_exec, mock_path,
|
|
||||||
mock_makedirs, mock_copy):
|
|
||||||
mock_path.return_value = 'networkd'
|
|
||||||
utils.trigger_glean_network_refresh()
|
|
||||||
self.assertEqual(1, mock_path.call_count)
|
|
||||||
mock_exec.assert_has_calls([
|
|
||||||
mock.call('glean', '--distro', 'networkd', run_as_root=True),
|
|
||||||
mock.call('systemctl', 'restart', 'systemd-networkd.service',
|
|
||||||
run_as_root=True)])
|
|
||||||
mock_makedirs.assert_called_once_with('/mnt/config/openstack/latest/',
|
|
||||||
exist_ok=True)
|
|
||||||
mock_copy.assert_called_once_with(
|
|
||||||
'/etc/ironic-python-agent.d/network_data.json',
|
|
||||||
'/mnt/config/openstack/latest/network_data.json')
|
|
||||||
|
|
||||||
def test_networkmanager(self, mock_exec, mock_path,
|
|
||||||
mock_makedirs, mock_copy):
|
|
||||||
mock_path.return_value = 'networkmanager'
|
|
||||||
mock_exec.side_effect = [('', ''),
|
|
||||||
OSError(),
|
|
||||||
('', '')]
|
|
||||||
utils.trigger_glean_network_refresh()
|
|
||||||
self.assertEqual(1, mock_path.call_count)
|
|
||||||
mock_exec.assert_has_calls([
|
|
||||||
mock.call('glean', '--use-nm', run_as_root=True),
|
|
||||||
mock.call('systemctl', 'restart', 'NetworkManager',
|
|
||||||
run_as_root=True),
|
|
||||||
mock.call('systemctl', 'restart', 'NetworkManager.service',
|
|
||||||
run_as_root=True)])
|
|
||||||
mock_makedirs.assert_called_once_with('/mnt/config/openstack/latest/',
|
|
||||||
exist_ok=True)
|
|
||||||
mock_copy.assert_called_once_with(
|
|
||||||
'/etc/ironic-python-agent.d/network_data.json',
|
|
||||||
'/mnt/config/openstack/latest/network_data.json')
|
|
||||||
|
|
||||||
|
|
||||||
class TestCheckEarlyLogging(ironic_agent_base.IronicAgentTest):
|
class TestCheckEarlyLogging(ironic_agent_base.IronicAgentTest):
|
||||||
|
|
||||||
@mock.patch.object(utils, 'LOG', autospec=True)
|
@mock.patch.object(utils, 'LOG', autospec=True)
|
||||||
|
@ -21,7 +21,6 @@ import glob
|
|||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import platform
|
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
@ -34,7 +33,6 @@ from oslo_concurrency import processutils
|
|||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_utils import units
|
from oslo_utils import units
|
||||||
import psutil
|
|
||||||
import requests
|
import requests
|
||||||
import tenacity
|
import tenacity
|
||||||
|
|
||||||
@ -128,7 +126,6 @@ def _find_vmedia_device_by_labels(labels):
|
|||||||
for device in ironic_utils.parse_device_tags(lsblk_output):
|
for device in ironic_utils.parse_device_tags(lsblk_output):
|
||||||
for label in labels:
|
for label in labels:
|
||||||
if label.upper() == device['LABEL'].upper():
|
if label.upper() == device['LABEL'].upper():
|
||||||
_early_log('Found vmedia candidate %s', device['KNAME'])
|
|
||||||
candidates.append(device['KNAME'])
|
candidates.append(device['KNAME'])
|
||||||
|
|
||||||
for candidate in candidates:
|
for candidate in candidates:
|
||||||
@ -213,21 +210,13 @@ def _copy_config_from(path):
|
|||||||
msg = ("Unable to copy vmedia configuration %s to %s: %s"
|
msg = ("Unable to copy vmedia configuration %s to %s: %s"
|
||||||
% (src, dest, exc))
|
% (src, dest, exc))
|
||||||
raise errors.VirtualMediaBootError(msg)
|
raise errors.VirtualMediaBootError(msg)
|
||||||
network_data_path = os.path.join(
|
|
||||||
path, 'openstack/latest/network_data.json')
|
|
||||||
if os.path.isfile(network_data_path):
|
|
||||||
dest_path = '/etc/ironic-python-agent%s' % ext
|
|
||||||
_early_log('Copying network_data.json to %s', dest_path)
|
|
||||||
os.makedirs(dest_path, exist_ok=True)
|
|
||||||
shutil.copy(network_data_path, os.path.join(dest_path,
|
|
||||||
'network_data.json'))
|
|
||||||
|
|
||||||
|
|
||||||
def _find_mount_point(device):
|
def _find_mount_point(device):
|
||||||
try:
|
try:
|
||||||
path, _e = execute('findmnt', '-n', '-oTARGET', device)
|
path, _e = execute('findmnt', '-n', '-oTARGET', device)
|
||||||
except (processutils.ProcessExecutionError, OSError):
|
except processutils.ProcessExecutionError:
|
||||||
return None
|
return
|
||||||
else:
|
else:
|
||||||
return path.strip()
|
return path.strip()
|
||||||
|
|
||||||
@ -244,7 +233,6 @@ def _check_vmedia_device(vmedia_device_file):
|
|||||||
valid.
|
valid.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
_early_log('Checking device %s vmedia status.', vmedia_device_file)
|
|
||||||
output, _e = execute('lsblk', '-n', '-s', '-P', '-b',
|
output, _e = execute('lsblk', '-n', '-s', '-P', '-b',
|
||||||
'-oKNAME,TRAN,TYPE,SIZE',
|
'-oKNAME,TRAN,TYPE,SIZE',
|
||||||
vmedia_device_file)
|
vmedia_device_file)
|
||||||
@ -255,8 +243,6 @@ def _check_vmedia_device(vmedia_device_file):
|
|||||||
try:
|
try:
|
||||||
for device in ironic_utils.parse_device_tags(output):
|
for device in ironic_utils.parse_device_tags(output):
|
||||||
if device['TYPE'] == 'part':
|
if device['TYPE'] == 'part':
|
||||||
# This would exclude any existing configuration drive written
|
|
||||||
# by ironic previously.
|
|
||||||
_early_log('Excluding device %s from virtual media'
|
_early_log('Excluding device %s from virtual media'
|
||||||
'consideration as it is a partition.',
|
'consideration as it is a partition.',
|
||||||
device['KNAME'])
|
device['KNAME'])
|
||||||
@ -267,7 +253,7 @@ def _check_vmedia_device(vmedia_device_file):
|
|||||||
# registered for the scsi transport and thus type used.
|
# registered for the scsi transport and thus type used.
|
||||||
# This will most likely be a qemu driven testing VM,
|
# This will most likely be a qemu driven testing VM,
|
||||||
# or an older machine where SCSI transport is directly
|
# or an older machine where SCSI transport is directly
|
||||||
# used to convey in a virtual media device.
|
# used to convey in a virtual
|
||||||
return True
|
return True
|
||||||
if device['TYPE'] == 'disk' and device['TRAN'] == 'usb':
|
if device['TYPE'] == 'disk' and device['TRAN'] == 'usb':
|
||||||
# We know from experience on HPE machines, with ilo4/5, we see
|
# We know from experience on HPE machines, with ilo4/5, we see
|
||||||
@ -320,9 +306,6 @@ def copy_config_from_vmedia():
|
|||||||
"""Copies any configuration from a virtual media device.
|
"""Copies any configuration from a virtual media device.
|
||||||
|
|
||||||
Copies files under /etc/ironic-python-agent and /etc/ironic-python-agent.d.
|
Copies files under /etc/ironic-python-agent and /etc/ironic-python-agent.d.
|
||||||
|
|
||||||
:returns: True when we successfully copied content from the virtual media
|
|
||||||
device. Otherwise None.
|
|
||||||
"""
|
"""
|
||||||
vmedia_device_file = _find_vmedia_device_by_labels(
|
vmedia_device_file = _find_vmedia_device_by_labels(
|
||||||
['config-2', 'vmedia_boot_iso'])
|
['config-2', 'vmedia_boot_iso'])
|
||||||
@ -336,84 +319,10 @@ def copy_config_from_vmedia():
|
|||||||
# Determine the device
|
# Determine the device
|
||||||
mounted = _find_mount_point(vmedia_device_file)
|
mounted = _find_mount_point(vmedia_device_file)
|
||||||
if mounted:
|
if mounted:
|
||||||
# Unmount if already mounted by something like glean/cloud init.
|
_copy_config_from(mounted)
|
||||||
execute("umount", mounted, run_as_root=True)
|
|
||||||
with ironic_utils.mounted(vmedia_device_file) as vmedia_mount_point:
|
|
||||||
_copy_config_from(vmedia_mount_point)
|
|
||||||
|
|
||||||
# Finally, check mount and ensure unmounted before proceeding.
|
|
||||||
# NOTE(TheJulia): This is a reserved path by convention for configuration
|
|
||||||
# drive handling. Since you *can* stack multiple devices, we need to make
|
|
||||||
# sure no other tooling left anything in an unclean state.
|
|
||||||
while os.path.ismount("/mnt/config"):
|
|
||||||
try:
|
|
||||||
execute("umount", "/mnt/config", run_as_root=True)
|
|
||||||
_early_log("Sucessfully unmounted /mnt/config as an orphaned "
|
|
||||||
"source of configuration.")
|
|
||||||
except (OSError, processutils.ProcessExecutionError):
|
|
||||||
_early_log("We failed to umount /mnt/config. This may be fatal "
|
|
||||||
"if virtual media is in use for configuration.")
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def _determine_networking_path():
|
|
||||||
os_ver = platform.uname().version.lower()
|
|
||||||
if 'tinycore' in os_ver:
|
|
||||||
# Compact CI oriented IPA Image.
|
|
||||||
return 'tinycore'
|
|
||||||
if True in ['networkd' in x.name().lower()
|
|
||||||
for x in psutil.process_iter(['name'])]:
|
|
||||||
# Basically, this would be Ubuntu AFAIK.
|
|
||||||
return 'networkd'
|
|
||||||
else:
|
else:
|
||||||
# This is fairly safe to assume at this point.
|
with ironic_utils.mounted(vmedia_device_file) as vmedia_mount_point:
|
||||||
return 'networkmanager'
|
_copy_config_from(vmedia_mount_point)
|
||||||
|
|
||||||
|
|
||||||
def trigger_glean_network_refresh():
|
|
||||||
"""Trigger procesing and refresh of network configuration."""
|
|
||||||
|
|
||||||
# NOTE(TheJulia): Ironic explicitly doesn't support cloud-init
|
|
||||||
# as the consumer of this information in ramdisks. This stance
|
|
||||||
# may change in the future, but as of the beginning of the
|
|
||||||
# Caracal development cycle, cloud-init is uninstalled
|
|
||||||
# by ironic-python-agent-builder.
|
|
||||||
glean_read_path = '/mnt/config/openstack/latest/'
|
|
||||||
# NOTE(TheJulia) Exist_okay because we might end up re-triggering down
|
|
||||||
# this path if the agent restarts due to a transient failure.
|
|
||||||
os.makedirs(glean_read_path, exist_ok=True)
|
|
||||||
backup_copy = '/etc/ironic-python-agent.d/network_data.json'
|
|
||||||
network_data = os.path.join(glean_read_path, 'network_data.json')
|
|
||||||
shutil.copy(backup_copy, network_data)
|
|
||||||
|
|
||||||
_early_log('Working to apply network configuration refresh.')
|
|
||||||
network_type = _determine_networking_path()
|
|
||||||
# TODO(TheJulia): Check if python-glean is even in the $PATH.
|
|
||||||
# Two uniform actions, run glean, then restart all networking.
|
|
||||||
try:
|
|
||||||
if network_type == 'networkmanager':
|
|
||||||
# network manager present
|
|
||||||
execute('glean', '--use-nm', run_as_root=True)
|
|
||||||
try:
|
|
||||||
execute('systemctl', 'restart', 'NetworkManager',
|
|
||||||
run_as_root=True)
|
|
||||||
except Exception:
|
|
||||||
# Inconsistent naming across distributions.
|
|
||||||
execute('systemctl', 'restart', 'NetworkManager.service',
|
|
||||||
run_as_root=True)
|
|
||||||
if network_type == 'networkd':
|
|
||||||
execute('glean', '--distro', 'networkd', run_as_root=True)
|
|
||||||
execute('systemctl', 'restart', 'systemd-networkd.service',
|
|
||||||
run_as_root=True)
|
|
||||||
if network_type == 'tinycore':
|
|
||||||
execute('glean', '--distro', 'tinycore', run_as_root=True)
|
|
||||||
# While a shell script, glean doesn't set this file to
|
|
||||||
# be executable once it writes it.
|
|
||||||
execute('/bin/sh', '/opt/network.sh', run_as_root=True)
|
|
||||||
except Exception as e:
|
|
||||||
_early_log('Unable to execute configuration refresh for '
|
|
||||||
'configuration drive data. Error: %s' % e)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_cached_params():
|
def _get_cached_params():
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
---
|
|
||||||
fixes:
|
|
||||||
- |
|
|
||||||
Fixes an issue with the agent where the presence of a configuration drive
|
|
||||||
from a prior node deployment could prevent networking configuration from
|
|
||||||
being determined and loaded in DHCP-less use cases.
|
|
||||||
The agent now attempts to identify the appropriate configuration drive
|
|
||||||
networking data source utilizing the existing device identification
|
|
||||||
process, and then triggers glean configuration to be re-generated,
|
|
||||||
after which networking is restarted. For more information, please
|
|
||||||
view `bug 2032377 <https://bugs.launchpad.net/ironic/+bug/2032377>`_.
|
|
||||||
security:
|
|
||||||
- |
|
|
||||||
The ironic agent is now able to re-assert networking configuration
|
|
||||||
in DHCP-less modes should a prior node's ``config drive`` be
|
|
||||||
discovered as readable. Normally this issue is not a security
|
|
||||||
issue as long as ``ironic-python-agent-builder`` is utilized.
|
|
||||||
Ironic's agent automatically exlcudes possible configuration
|
|
||||||
drive copies from previously deployed baremetal nodes.
|
|
Loading…
Reference in New Issue
Block a user