Merge "Wipe any metadata from a nodes disk"

This commit is contained in:
Jenkins 2014-06-12 21:10:15 +00:00 committed by Gerrit Code Review
commit fb7817a574
4 changed files with 160 additions and 19 deletions

View File

@ -6,6 +6,7 @@
iscsiadm: CommandFilter, iscsiadm, root
dd: CommandFilter, dd, root
blkid: CommandFilter, blkid, root
blockdev: CommandFilter, blockdev, root
# ironic/common/utils.py
mkswap: CommandFilter, mkswap, root

View File

@ -200,8 +200,65 @@ def get_image_mb(image_path):
return image_mb
def get_dev_block_size(dev):
"""Get the device size in 512 byte sectors."""
block_sz, cmderr = utils.execute('blockdev', '--getsz', dev,
run_as_root=True, check_exit_code=[0])
return int(block_sz)
def destroy_disk_metadata(dev, node_uuid):
"""Destroy metadata structures on node's disk.
Ensure that node's disk appears to be blank without zeroing the entire
drive. To do this we will zero:
- the first 18KiB to clear MBR / GPT data
- the last 18KiB to clear GPT and other metadata like: LVM, veritas,
MDADM, DMRAID, ...
"""
# NOTE(NobodyCam): This is needed to work around bug:
# https://bugs.launchpad.net/ironic/+bug/1317647
try:
utils.execute('dd', 'if=/dev/zero', 'of=%s' % dev,
'bs=512', 'count=36', run_as_root=True,
check_exit_code=[0])
except processutils.ProcessExecutionError as err:
with excutils.save_and_reraise_exception():
LOG.error(_("Failed to erase beginning of disk for node "
"%(node)s. Command: %(command)s. Error: %(error)s."),
{'node': node_uuid,
'command': err.cmd,
'error': err.stderr})
# now wipe the end of the disk.
# get end of disk seek value
try:
block_sz = get_dev_block_size(dev)
except processutils.ProcessExecutionError as err:
with excutils.save_and_reraise_exception():
LOG.error(_("Failed to get disk block count for node %(node)s. "
"Command: %(command)s. Error: %(error)s."),
{'node': node_uuid,
'command': err.cmd,
'error': err.stderr})
else:
seek_value = block_sz - 36
try:
utils.execute('dd', 'if=/dev/zero', 'of=%s' % dev,
'bs=512', 'count=36', 'seek=%d' % seek_value,
run_as_root=True, check_exit_code=[0])
except processutils.ProcessExecutionError as err:
with excutils.save_and_reraise_exception():
LOG.error(_("Failed to erase the end of the disk on node "
"%(node)s. Command: %(command)s. "
"Error: %(error)s."),
{'node': node_uuid,
'command': err.cmd,
'error': err.stderr})
def work_on_disk(dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format,
image_path, preserve_ephemeral=False):
image_path, node_uuid, preserve_ephemeral=False):
"""Create partitions and copy an image to the root partition.
:param dev: Path for the device to work on.
@ -212,6 +269,7 @@ def work_on_disk(dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format,
:param ephemeral_format: The type of file system to format the ephemeral
partition.
:param image_path: Path for the instance's disk image.
:param node_uuid: node's uuid. Used for logging.
:param preserve_ephemeral: If True, no filesystem is written to the
ephemeral block device, preserving whatever content it had (if the
partition table has not changed).
@ -224,6 +282,9 @@ def work_on_disk(dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format,
# the only way for preserve_ephemeral to be set to true is if we are
# rebuilding an instance with --preserve_ephemeral.
commit = not preserve_ephemeral
# now if we are committing the changes to disk clean first.
if commit:
destroy_disk_metadata(dev, node_uuid)
part_dict = make_partitions(dev, root_mb, swap_mb, ephemeral_mb,
commit=commit)
@ -258,7 +319,7 @@ def work_on_disk(dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format,
def deploy(address, port, iqn, lun, image_path, pxe_config_path,
root_mb, swap_mb, ephemeral_mb, ephemeral_format,
root_mb, swap_mb, ephemeral_mb, ephemeral_format, node_uuid,
preserve_ephemeral=False):
"""All-in-one function to deploy a node.
@ -274,6 +335,7 @@ def deploy(address, port, iqn, lun, image_path, pxe_config_path,
no ephemeral partition will be created.
:param ephemeral_format: The type of file system to format the ephemeral
partition.
:param node_uuid: node's uuid. Used for logging.
:param preserve_ephemeral: If True, no filesystem is written to the
ephemeral block device, preserving whatever content it had (if the
partition table has not changed).
@ -287,7 +349,7 @@ def deploy(address, port, iqn, lun, image_path, pxe_config_path,
login_iscsi(address, port, iqn)
try:
root_uuid = work_on_disk(dev, root_mb, swap_mb, ephemeral_mb,
ephemeral_format, image_path,
ephemeral_format, image_path, node_uuid,
preserve_ephemeral)
except processutils.ProcessExecutionError as err:
with excutils.save_and_reraise_exception():

View File

@ -677,6 +677,7 @@ class VendorPassthru(base.VendorInterface):
'swap_mb': int(d_info['swap_mb']),
'ephemeral_mb': 1024 * int(d_info['ephemeral_gb']),
'preserve_ephemeral': d_info['preserve_ephemeral'],
'node_uuid': node.uuid,
}
missing = [key for key in params.keys() if params[key] is None]

View File

@ -25,9 +25,9 @@ from ironic.common import disk_partitioner
from ironic.common import exception
from ironic.common import utils as common_utils
from ironic.drivers.modules import deploy_utils as utils
from ironic.openstack.common import processutils
from ironic.tests import base as tests_base
_PXECONF_DEPLOY = """
default deploy
@ -87,6 +87,7 @@ class PhysicalWorkTestCase(tests_base.TestCase):
swap_mb = 64
ephemeral_mb = 0
ephemeral_format = None
node_uuid = "12345678-1234-1234-1234-1234567890abcxyz"
dev = '/dev/fake'
swap_part = '/dev/fake-part1'
@ -96,7 +97,7 @@ class PhysicalWorkTestCase(tests_base.TestCase):
name_list = ['get_dev', 'get_image_mb', 'discovery', 'login_iscsi',
'logout_iscsi', 'delete_iscsi', 'make_partitions',
'is_block_device', 'dd', 'mkswap', 'block_uuid',
'switch_pxe_config', 'notify']
'switch_pxe_config', 'notify', 'destroy_disk_metadata']
parent_mock = self._mock_calls(name_list)
parent_mock.get_dev.return_value = dev
parent_mock.get_image_mb.return_value = 1
@ -109,6 +110,7 @@ class PhysicalWorkTestCase(tests_base.TestCase):
mock.call.discovery(address, port),
mock.call.login_iscsi(address, port, iqn),
mock.call.is_block_device(dev),
mock.call.destroy_disk_metadata(dev, node_uuid),
mock.call.make_partitions(dev, root_mb, swap_mb,
ephemeral_mb,
commit=True),
@ -124,7 +126,8 @@ class PhysicalWorkTestCase(tests_base.TestCase):
mock.call.notify(address, 10000)]
utils.deploy(address, port, iqn, lun, image_path, pxe_config_path,
root_mb, swap_mb, ephemeral_mb, ephemeral_format)
root_mb, swap_mb, ephemeral_mb, ephemeral_format,
node_uuid)
self.assertEqual(calls_expected, parent_mock.mock_calls)
@ -140,6 +143,7 @@ class PhysicalWorkTestCase(tests_base.TestCase):
swap_mb = 0
ephemeral_mb = 0
ephemeral_format = None
node_uuid = "12345678-1234-1234-1234-1234567890abcxyz"
dev = '/dev/fake'
root_part = '/dev/fake-part1'
@ -148,7 +152,7 @@ class PhysicalWorkTestCase(tests_base.TestCase):
name_list = ['get_dev', 'get_image_mb', 'discovery', 'login_iscsi',
'logout_iscsi', 'delete_iscsi', 'make_partitions',
'is_block_device', 'dd', 'block_uuid',
'switch_pxe_config', 'notify']
'switch_pxe_config', 'notify', 'destroy_disk_metadata']
parent_mock = self._mock_calls(name_list)
parent_mock.get_dev.return_value = dev
parent_mock.get_image_mb.return_value = 1
@ -160,6 +164,7 @@ class PhysicalWorkTestCase(tests_base.TestCase):
mock.call.discovery(address, port),
mock.call.login_iscsi(address, port, iqn),
mock.call.is_block_device(dev),
mock.call.destroy_disk_metadata(dev, node_uuid),
mock.call.make_partitions(dev, root_mb, swap_mb,
ephemeral_mb,
commit=True),
@ -173,7 +178,8 @@ class PhysicalWorkTestCase(tests_base.TestCase):
mock.call.notify(address, 10000)]
utils.deploy(address, port, iqn, lun, image_path, pxe_config_path,
root_mb, swap_mb, ephemeral_mb, ephemeral_format)
root_mb, swap_mb, ephemeral_mb, ephemeral_format,
node_uuid)
self.assertEqual(calls_expected, parent_mock.mock_calls)
@ -189,6 +195,7 @@ class PhysicalWorkTestCase(tests_base.TestCase):
swap_mb = 64
ephemeral_mb = 256
ephemeral_format = 'exttest'
node_uuid = "12345678-1234-1234-1234-1234567890abcxyz"
dev = '/dev/fake'
ephemeral_part = '/dev/fake-part1'
@ -199,7 +206,8 @@ class PhysicalWorkTestCase(tests_base.TestCase):
name_list = ['get_dev', 'get_image_mb', 'discovery', 'login_iscsi',
'logout_iscsi', 'delete_iscsi', 'make_partitions',
'is_block_device', 'dd', 'mkswap', 'block_uuid',
'switch_pxe_config', 'notify', 'mkfs_ephemeral']
'switch_pxe_config', 'notify', 'mkfs_ephemeral',
'destroy_disk_metadata']
parent_mock = self._mock_calls(name_list)
parent_mock.get_dev.return_value = dev
parent_mock.get_image_mb.return_value = 1
@ -213,6 +221,7 @@ class PhysicalWorkTestCase(tests_base.TestCase):
mock.call.discovery(address, port),
mock.call.login_iscsi(address, port, iqn),
mock.call.is_block_device(dev),
mock.call.destroy_disk_metadata(dev, node_uuid),
mock.call.make_partitions(dev, root_mb, swap_mb,
ephemeral_mb,
commit=True),
@ -231,7 +240,8 @@ class PhysicalWorkTestCase(tests_base.TestCase):
mock.call.notify(address, 10000)]
utils.deploy(address, port, iqn, lun, image_path, pxe_config_path,
root_mb, swap_mb, ephemeral_mb, ephemeral_format)
root_mb, swap_mb, ephemeral_mb, ephemeral_format,
node_uuid)
self.assertEqual(calls_expected, parent_mock.mock_calls)
@ -247,6 +257,7 @@ class PhysicalWorkTestCase(tests_base.TestCase):
swap_mb = 64
ephemeral_mb = 256
ephemeral_format = 'exttest'
node_uuid = "12345678-1234-1234-1234-1234567890abcxyz"
dev = '/dev/fake'
ephemeral_part = '/dev/fake-part1'
@ -254,13 +265,11 @@ class PhysicalWorkTestCase(tests_base.TestCase):
root_part = '/dev/fake-part3'
root_uuid = '12345678-1234-1234-12345678-12345678abcdef'
mock_mkfs_eph = mock.patch.object(utils, 'mkfs_ephemeral').start()
self.addCleanup(mock_mkfs_eph.stop)
name_list = ['get_dev', 'get_image_mb', 'discovery', 'login_iscsi',
'logout_iscsi', 'delete_iscsi', 'make_partitions',
'is_block_device', 'dd', 'mkswap', 'block_uuid',
'switch_pxe_config', 'notify']
'switch_pxe_config', 'notify', 'mkfs_ephemeral',
'get_dev_block_size']
parent_mock = self._mock_calls(name_list)
parent_mock.get_dev.return_value = dev
parent_mock.get_image_mb.return_value = 1
@ -292,10 +301,10 @@ class PhysicalWorkTestCase(tests_base.TestCase):
utils.deploy(address, port, iqn, lun, image_path, pxe_config_path,
root_mb, swap_mb, ephemeral_mb, ephemeral_format,
preserve_ephemeral=True)
node_uuid, preserve_ephemeral=True)
self.assertEqual(calls_expected, parent_mock.mock_calls)
# mkfs_ephemeral should not be called
self.assertFalse(mock_mkfs_eph.called)
self.assertFalse(parent_mock.mkfs_ephemeral.called)
self.assertFalse(parent_mock.get_dev_block_size.called)
def test_always_logout_and_delete_iscsi(self):
"""Check if logout_iscsi() and delete_iscsi() are called.
@ -314,6 +323,7 @@ class PhysicalWorkTestCase(tests_base.TestCase):
swap_mb = 64
ephemeral_mb = 256
ephemeral_format = 'exttest'
node_uuid = "12345678-1234-1234-1234-1234567890abcxyz"
dev = '/dev/fake'
@ -341,14 +351,14 @@ class PhysicalWorkTestCase(tests_base.TestCase):
mock.call.work_on_disk(dev, root_mb, swap_mb,
ephemeral_mb,
ephemeral_format, image_path,
False),
node_uuid, False),
mock.call.logout_iscsi(address, port, iqn),
mock.call.delete_iscsi(address, port, iqn)]
self.assertRaises(TestException, utils.deploy,
address, port, iqn, lun, image_path,
pxe_config_path, root_mb, swap_mb, ephemeral_mb,
ephemeral_format)
ephemeral_format, node_uuid)
self.assertEqual(calls_expected, parent_mock.mock_calls)
@ -411,6 +421,9 @@ class WorkOnDiskTestCase(tests_base.TestCase):
self.mock_mp = mock.patch.object(utils, 'make_partitions').start()
self.addCleanup(self.mock_ibd.stop)
self.addCleanup(self.mock_mp.stop)
self.mock_remlbl = mock.patch.object(utils,
'destroy_disk_metadata').start()
self.addCleanup(self.mock_remlbl.stop)
self.mock_mp.return_value = {'swap': self.swap_part,
'root': self.root_part}
@ -508,3 +521,67 @@ class MakePartitionsTestCase(tests_base.TestCase):
self.ephemeral_mb)
mock_exc.assert_called_once_with(*cmd, run_as_root=True,
check_exit_code=[0])
@mock.patch.object(utils, 'get_dev_block_size')
@mock.patch.object(common_utils, 'execute')
class DestroyMetaDataTestCase(tests_base.TestCase):
def setUp(self):
super(DestroyMetaDataTestCase, self).setUp()
self.dev = 'fake-dev'
self.node_uuid = "12345678-1234-1234-1234-1234567890abcxyz"
def test_destroy_disk_metadata(self, mock_exec, mock_gz):
mock_gz.return_value = 64
expected_calls = [mock.call('dd', 'if=/dev/zero', 'of=fake-dev',
'bs=512', 'count=36', run_as_root=True,
check_exit_code=[0]),
mock.call('dd', 'if=/dev/zero', 'of=fake-dev',
'bs=512', 'count=36', 'seek=28',
run_as_root=True,
check_exit_code=[0])]
utils.destroy_disk_metadata(self.dev, self.node_uuid)
mock_exec.assert_has_calls(expected_calls)
self.assertTrue(mock_gz.called)
def test_destroy_disk_metadata_get_dev_size_fail(self, mock_exec, mock_gz):
mock_gz.side_effect = processutils.ProcessExecutionError
expected_call = [mock.call('dd', 'if=/dev/zero', 'of=fake-dev',
'bs=512', 'count=36', run_as_root=True,
check_exit_code=[0])]
self.assertRaises(processutils.ProcessExecutionError,
utils.destroy_disk_metadata,
self.dev,
self.node_uuid)
mock_exec.assert_has_calls(expected_call)
def test_destroy_disk_metadata_dd_fail(self, mock_exec, mock_gz):
mock_exec.side_effect = processutils.ProcessExecutionError
expected_call = [mock.call('dd', 'if=/dev/zero', 'of=fake-dev',
'bs=512', 'count=36', run_as_root=True,
check_exit_code=[0])]
self.assertRaises(processutils.ProcessExecutionError,
utils.destroy_disk_metadata,
self.dev,
self.node_uuid)
mock_exec.assert_has_calls(expected_call)
self.assertFalse(mock_gz.called)
@mock.patch.object(common_utils, 'execute')
class GetDeviceBlockSizeTestCase(tests_base.TestCase):
def setUp(self):
super(GetDeviceBlockSizeTestCase, self).setUp()
self.dev = 'fake-dev'
self.node_uuid = "12345678-1234-1234-1234-1234567890abcxyz"
def test_get_dev_block_size(self, mock_exec):
mock_exec.return_value = ("64", "")
expected_call = [mock.call('blockdev', '--getsz', self.dev,
run_as_root=True, check_exit_code=[0])]
utils.get_dev_block_size(self.dev)
mock_exec.assert_has_calls(expected_call)