Merge "Attempt to read the partition table after writing an image"
This commit is contained in:
commit
c5f31db691
@ -17,6 +17,7 @@ import os
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from ironic_lib import disk_utils
|
from ironic_lib import disk_utils
|
||||||
|
from ironic_lib import exception
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
@ -359,6 +360,40 @@ def _validate_image_info(ext, image_info=None, **kwargs):
|
|||||||
'Image \'os_hash_value\' must be a non-empty string.')
|
'Image \'os_hash_value\' must be a non-empty string.')
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_partitioning(device):
|
||||||
|
"""Validate the final partition table.
|
||||||
|
|
||||||
|
Check if after writing the image to disk we have a valid partition
|
||||||
|
table by trying to read it. This will fail if the disk is junk.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Ensure we re-read the partition table before we try to list
|
||||||
|
# partitions
|
||||||
|
utils.execute('partprobe', device, run_as_root=True,
|
||||||
|
attempts=CONF.disk_utils.partprobe_attempts)
|
||||||
|
except (processutils.UnknownArgumentError,
|
||||||
|
processutils.ProcessExecutionError, OSError) as e:
|
||||||
|
LOG.warning("Unable to probe for partitions on device %(device)s "
|
||||||
|
"after writing the image, the partitioning table may "
|
||||||
|
"be broken. Error: %(error)s",
|
||||||
|
{'device': device, 'error': e})
|
||||||
|
|
||||||
|
try:
|
||||||
|
nparts = len(disk_utils.list_partitions(device))
|
||||||
|
except (processutils.UnknownArgumentError,
|
||||||
|
processutils.ProcessExecutionError, OSError) as e:
|
||||||
|
msg = ("Unable to find a valid partition table on the disk after "
|
||||||
|
"writing the image. Error {}".format(e))
|
||||||
|
raise exception.InstanceDeployFailure(msg)
|
||||||
|
|
||||||
|
# Check if there is at least one partition in the partition table after
|
||||||
|
# deploy
|
||||||
|
if not nparts:
|
||||||
|
msg = ("No partitions found on the device {} after writing "
|
||||||
|
"the image.".format(device))
|
||||||
|
raise exception.InstanceDeployFailure(msg)
|
||||||
|
|
||||||
|
|
||||||
class StandbyExtension(base.BaseAgentExtension):
|
class StandbyExtension(base.BaseAgentExtension):
|
||||||
"""Extension which adds stand-by related functionality to agent."""
|
"""Extension which adds stand-by related functionality to agent."""
|
||||||
def __init__(self, agent=None):
|
def __init__(self, agent=None):
|
||||||
@ -495,6 +530,8 @@ class StandbyExtension(base.BaseAgentExtension):
|
|||||||
else:
|
else:
|
||||||
self._cache_and_write_image(image_info, device)
|
self._cache_and_write_image(image_info, device)
|
||||||
|
|
||||||
|
_validate_partitioning(device)
|
||||||
|
|
||||||
# the configdrive creation is taken care by ironic-lib's
|
# the configdrive creation is taken care by ironic-lib's
|
||||||
# work_on_disk().
|
# work_on_disk().
|
||||||
if image_info.get('image_type') != 'partition':
|
if image_info.get('image_type') != 'partition':
|
||||||
|
@ -603,6 +603,10 @@ class TestStandbyExtension(base.IronicAgentTest):
|
|||||||
|
|
||||||
@mock.patch('ironic_lib.disk_utils.get_disk_identifier',
|
@mock.patch('ironic_lib.disk_utils.get_disk_identifier',
|
||||||
lambda dev: 'ROOT')
|
lambda dev: 'ROOT')
|
||||||
|
@mock.patch('ironic_python_agent.utils.execute',
|
||||||
|
autospec=True)
|
||||||
|
@mock.patch('ironic_lib.disk_utils.list_partitions',
|
||||||
|
autospec=True)
|
||||||
@mock.patch('ironic_lib.disk_utils.create_config_drive_partition',
|
@mock.patch('ironic_lib.disk_utils.create_config_drive_partition',
|
||||||
autospec=True)
|
autospec=True)
|
||||||
@mock.patch('ironic_python_agent.hardware.dispatch_to_managers',
|
@mock.patch('ironic_python_agent.hardware.dispatch_to_managers',
|
||||||
@ -615,12 +619,15 @@ class TestStandbyExtension(base.IronicAgentTest):
|
|||||||
download_mock,
|
download_mock,
|
||||||
write_mock,
|
write_mock,
|
||||||
dispatch_mock,
|
dispatch_mock,
|
||||||
configdrive_copy_mock):
|
configdrive_copy_mock,
|
||||||
|
list_part_mock,
|
||||||
|
execute_mock):
|
||||||
image_info = _build_fake_image_info()
|
image_info = _build_fake_image_info()
|
||||||
download_mock.return_value = None
|
download_mock.return_value = None
|
||||||
write_mock.return_value = None
|
write_mock.return_value = None
|
||||||
dispatch_mock.return_value = 'manager'
|
dispatch_mock.return_value = 'manager'
|
||||||
configdrive_copy_mock.return_value = None
|
configdrive_copy_mock.return_value = None
|
||||||
|
list_part_mock.return_value = [mock.MagicMock()]
|
||||||
|
|
||||||
async_result = self.agent_extension.prepare_image(
|
async_result = self.agent_extension.prepare_image(
|
||||||
image_info=image_info,
|
image_info=image_info,
|
||||||
@ -640,7 +647,14 @@ class TestStandbyExtension(base.IronicAgentTest):
|
|||||||
cmd_result = ('prepare_image: image ({}) written to device {} '
|
cmd_result = ('prepare_image: image ({}) written to device {} '
|
||||||
'root_uuid=ROOT').format(image_info['id'], 'manager')
|
'root_uuid=ROOT').format(image_info['id'], 'manager')
|
||||||
self.assertEqual(cmd_result, async_result.command_result['result'])
|
self.assertEqual(cmd_result, async_result.command_result['result'])
|
||||||
|
list_part_mock.assert_called_with('manager')
|
||||||
|
execute_mock.assert_called_with('partprobe', 'manager',
|
||||||
|
run_as_root=True,
|
||||||
|
attempts=mock.ANY)
|
||||||
|
|
||||||
|
@mock.patch('ironic_python_agent.utils.execute', autospec=True)
|
||||||
|
@mock.patch('ironic_lib.disk_utils.list_partitions',
|
||||||
|
autospec=True)
|
||||||
@mock.patch('ironic_lib.disk_utils.create_config_drive_partition',
|
@mock.patch('ironic_lib.disk_utils.create_config_drive_partition',
|
||||||
autospec=True)
|
autospec=True)
|
||||||
@mock.patch('ironic_python_agent.hardware.dispatch_to_managers',
|
@mock.patch('ironic_python_agent.hardware.dispatch_to_managers',
|
||||||
@ -653,12 +667,15 @@ class TestStandbyExtension(base.IronicAgentTest):
|
|||||||
download_mock,
|
download_mock,
|
||||||
write_mock,
|
write_mock,
|
||||||
dispatch_mock,
|
dispatch_mock,
|
||||||
configdrive_copy_mock):
|
configdrive_copy_mock,
|
||||||
|
list_part_mock,
|
||||||
|
execute_mock):
|
||||||
image_info = _build_fake_partition_image_info()
|
image_info = _build_fake_partition_image_info()
|
||||||
download_mock.return_value = None
|
download_mock.return_value = None
|
||||||
write_mock.return_value = {'root uuid': 'root_uuid'}
|
write_mock.return_value = {'root uuid': 'root_uuid'}
|
||||||
dispatch_mock.return_value = 'manager'
|
dispatch_mock.return_value = 'manager'
|
||||||
configdrive_copy_mock.return_value = None
|
configdrive_copy_mock.return_value = None
|
||||||
|
list_part_mock.return_value = [mock.MagicMock()]
|
||||||
|
|
||||||
async_result = self.agent_extension.prepare_image(
|
async_result = self.agent_extension.prepare_image(
|
||||||
image_info=image_info,
|
image_info=image_info,
|
||||||
@ -698,11 +715,18 @@ class TestStandbyExtension(base.IronicAgentTest):
|
|||||||
'root_uuid={}').format(
|
'root_uuid={}').format(
|
||||||
image_info['id'], 'manager', 'root_uuid')
|
image_info['id'], 'manager', 'root_uuid')
|
||||||
self.assertEqual(cmd_result, async_result.command_result['result'])
|
self.assertEqual(cmd_result, async_result.command_result['result'])
|
||||||
|
list_part_mock.assert_called_with('manager')
|
||||||
|
execute_mock.assert_called_with('partprobe', 'manager',
|
||||||
|
run_as_root=True,
|
||||||
|
attempts=mock.ANY)
|
||||||
|
|
||||||
@mock.patch('ironic_lib.disk_utils.get_disk_identifier',
|
@mock.patch('ironic_lib.disk_utils.get_disk_identifier',
|
||||||
lambda dev: 'ROOT')
|
lambda dev: 'ROOT')
|
||||||
|
@mock.patch('ironic_python_agent.utils.execute', autospec=True)
|
||||||
@mock.patch('ironic_lib.disk_utils.create_config_drive_partition',
|
@mock.patch('ironic_lib.disk_utils.create_config_drive_partition',
|
||||||
autospec=True)
|
autospec=True)
|
||||||
|
@mock.patch('ironic_lib.disk_utils.list_partitions',
|
||||||
|
autospec=True)
|
||||||
@mock.patch('ironic_python_agent.hardware.dispatch_to_managers',
|
@mock.patch('ironic_python_agent.hardware.dispatch_to_managers',
|
||||||
autospec=True)
|
autospec=True)
|
||||||
@mock.patch('ironic_python_agent.extensions.standby._write_image',
|
@mock.patch('ironic_python_agent.extensions.standby._write_image',
|
||||||
@ -713,12 +737,15 @@ class TestStandbyExtension(base.IronicAgentTest):
|
|||||||
download_mock,
|
download_mock,
|
||||||
write_mock,
|
write_mock,
|
||||||
dispatch_mock,
|
dispatch_mock,
|
||||||
configdrive_copy_mock):
|
list_part_mock,
|
||||||
|
configdrive_copy_mock,
|
||||||
|
execute_mock):
|
||||||
image_info = _build_fake_image_info()
|
image_info = _build_fake_image_info()
|
||||||
download_mock.return_value = None
|
download_mock.return_value = None
|
||||||
write_mock.return_value = None
|
write_mock.return_value = None
|
||||||
dispatch_mock.return_value = 'manager'
|
dispatch_mock.return_value = 'manager'
|
||||||
configdrive_copy_mock.return_value = None
|
configdrive_copy_mock.return_value = None
|
||||||
|
list_part_mock.return_value = [mock.MagicMock()]
|
||||||
|
|
||||||
async_result = self.agent_extension.prepare_image(
|
async_result = self.agent_extension.prepare_image(
|
||||||
image_info=image_info,
|
image_info=image_info,
|
||||||
@ -740,6 +767,55 @@ class TestStandbyExtension(base.IronicAgentTest):
|
|||||||
@mock.patch('ironic_lib.disk_utils.get_disk_identifier',
|
@mock.patch('ironic_lib.disk_utils.get_disk_identifier',
|
||||||
lambda dev: 'ROOT')
|
lambda dev: 'ROOT')
|
||||||
@mock.patch('ironic_lib.disk_utils.work_on_disk', autospec=True)
|
@mock.patch('ironic_lib.disk_utils.work_on_disk', autospec=True)
|
||||||
|
@mock.patch('ironic_lib.disk_utils.create_config_drive_partition',
|
||||||
|
autospec=True)
|
||||||
|
@mock.patch('ironic_lib.disk_utils.list_partitions',
|
||||||
|
autospec=True)
|
||||||
|
@mock.patch('ironic_python_agent.hardware.dispatch_to_managers',
|
||||||
|
autospec=True)
|
||||||
|
@mock.patch('ironic_python_agent.extensions.standby._write_image',
|
||||||
|
autospec=True)
|
||||||
|
@mock.patch('ironic_python_agent.extensions.standby._download_image',
|
||||||
|
autospec=True)
|
||||||
|
def test_prepare_image_bad_partition(self,
|
||||||
|
download_mock,
|
||||||
|
write_mock,
|
||||||
|
dispatch_mock,
|
||||||
|
list_part_mock,
|
||||||
|
configdrive_copy_mock,
|
||||||
|
work_on_disk_mock):
|
||||||
|
list_part_mock.side_effect = processutils.ProcessExecutionError
|
||||||
|
image_info = _build_fake_image_info()
|
||||||
|
download_mock.return_value = None
|
||||||
|
write_mock.return_value = None
|
||||||
|
dispatch_mock.return_value = 'manager'
|
||||||
|
configdrive_copy_mock.return_value = None
|
||||||
|
work_on_disk_mock.return_value = {
|
||||||
|
'root uuid': 'a318821b-2a60-40e5-a011-7ac07fce342b',
|
||||||
|
'partitions': {
|
||||||
|
'root': '/dev/foo-part1',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async_result = self.agent_extension.prepare_image(
|
||||||
|
image_info=image_info,
|
||||||
|
configdrive=None
|
||||||
|
)
|
||||||
|
async_result.join()
|
||||||
|
|
||||||
|
download_mock.assert_called_once_with(image_info)
|
||||||
|
write_mock.assert_called_once_with(image_info, 'manager')
|
||||||
|
dispatch_mock.assert_called_once_with('get_os_install_device')
|
||||||
|
|
||||||
|
self.assertFalse(configdrive_copy_mock.called)
|
||||||
|
self.assertEqual('FAILED', async_result.command_status)
|
||||||
|
|
||||||
|
@mock.patch('ironic_python_agent.utils.execute', mock.Mock())
|
||||||
|
@mock.patch('ironic_lib.disk_utils.list_partitions',
|
||||||
|
lambda _dev: [mock.Mock()])
|
||||||
|
@mock.patch('ironic_lib.disk_utils.get_disk_identifier',
|
||||||
|
lambda dev: 'ROOT')
|
||||||
|
@mock.patch('ironic_lib.disk_utils.work_on_disk', autospec=True)
|
||||||
@mock.patch('ironic_lib.disk_utils.create_config_drive_partition',
|
@mock.patch('ironic_lib.disk_utils.create_config_drive_partition',
|
||||||
autospec=True)
|
autospec=True)
|
||||||
@mock.patch('ironic_python_agent.hardware.dispatch_to_managers',
|
@mock.patch('ironic_python_agent.hardware.dispatch_to_managers',
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
fixes:
|
||||||
|
- |
|
||||||
|
Fixes a bug with bad whole disk images and config drives, where we would
|
||||||
|
attempt to write a config drive partition to the disk without a valid
|
||||||
|
partition table.
|
Loading…
Reference in New Issue
Block a user