Use Mistral for baremetal boot configuration

This updates the 'baremetal configure boot' command to work with the
new Mistral workflow. The Root Device tests have migrated to
tripleo-common where the main code now lives as a Mistral action (see
dependent patch).

Change-Id: I3acbfd7900177fc2159448b839b71599e8e51d5f
Depends-On: I5ba0a3710012c44822dd3b8e69662bbef04d3787
Related-Bug: #1595205
This commit is contained in:
Julie Pichon 2016-08-19 15:54:29 +00:00
parent d0b6de2b4a
commit 6900d694e3
2 changed files with 104 additions and 551 deletions

View File

@ -12,13 +12,12 @@
# License for the specific language governing permissions and limitations
# under the License.
#
import copy
import json
import os
import tempfile
import mock
from oslo_utils import units
import yaml
from tripleoclient import exceptions
@ -892,17 +891,42 @@ class TestConfigureBaremetalBoot(fakes.TestBaremetal):
# Get the command object to test
self.cmd = baremetal.ConfigureBaremetalBoot(self.app, None)
@mock.patch('openstackclient.common.utils.find_resource', autospec=True)
def test_configure_boot(self, find_resource_mock):
# Mistral-related mocks
self.workflow = self.app.client_manager.workflow_engine
client = self.app.client_manager.tripleoclient
self.websocket = client.messaging_websocket()
self.websocket.wait_for_message.return_value = {
"status": "SUCCESS",
"message": ""
}
find_resource_mock.return_value = mock.Mock(id="IDIDID")
bm_client = self.app.client_manager.baremetal
bm_client.node.list.return_value = [
uuid4_patcher = mock.patch('uuid.uuid4', return_value="UUID4")
self.mock_uuid4 = uuid4_patcher.start()
self.addCleanup(self.mock_uuid4.stop)
self.workflow_input = {'queue_name': 'UUID4',
'node_uuids': ['ABCDEFGH'],
'kernel_name': 'bm-deploy-kernel',
'ramdisk_name': 'bm-deploy-ramdisk',
'root_device': None,
'root_device_minimum_size': 4,
'overwrite_root_device_hints': False}
# Ironic mocks
self.bm_client = self.app.client_manager.baremetal
self.bm_client.node.list.return_value = [
mock.Mock(uuid="ABCDEFGH"),
]
self.node = mock.Mock(uuid="ABCDEFGH", properties={})
self.bm_client.node.get.return_value = self.node
def test_configure_boot(self):
self.bm_client.node.list.return_value = [
mock.Mock(uuid="ABCDEFGH"),
mock.Mock(uuid="IJKLMNOP"),
]
bm_client.node.get.side_effect = [
self.bm_client.node.get.side_effect = [
mock.Mock(uuid="ABCDEFGH", properties={}),
mock.Mock(uuid="IJKLMNOP", properties={}),
]
@ -910,47 +934,23 @@ class TestConfigureBaremetalBoot(fakes.TestBaremetal):
parsed_args = self.check_parser(self.cmd, [], [])
self.cmd.take_action(parsed_args)
self.assertEqual(find_resource_mock.call_count, 2)
self.assertEqual(find_resource_mock.mock_calls, [
mock.call(mock.ANY, 'bm-deploy-kernel'),
mock.call(mock.ANY, 'bm-deploy-ramdisk')
])
call_list = [mock.call('tripleo.baremetal.v1.configure',
workflow_input=self.workflow_input)]
self.assertEqual(bm_client.node.update.call_count, 2)
self.assertEqual(bm_client.node.update.mock_calls, [
mock.call('ABCDEFGH', [{
'op': 'add', 'value': 'boot_option:local',
'path': '/properties/capabilities'
}, {
'op': 'add', 'value': 'IDIDID',
'path': '/driver_info/deploy_ramdisk'
}, {
'op': 'add', 'value': 'IDIDID',
'path': '/driver_info/deploy_kernel'
}]),
mock.call('IJKLMNOP', [{
'op': 'add', 'value': 'boot_option:local',
'path': '/properties/capabilities'
}, {
'op': 'add', 'value': 'IDIDID',
'path': '/driver_info/deploy_ramdisk'
}, {
'op': 'add', 'value': 'IDIDID',
'path': '/driver_info/deploy_kernel'
}])
])
workflow_input = copy.copy(self.workflow_input)
workflow_input['node_uuids'] = ["IJKLMNOP"]
call_list.append(mock.call('tripleo.baremetal.v1.configure',
workflow_input=workflow_input))
@mock.patch('openstackclient.common.utils.find_resource', autospec=True)
def test_configure_boot_with_suffix(self, find_resource_mock):
self.workflow.executions.create.assert_has_calls(call_list)
find_resource_mock.return_value = mock.Mock(id="IDIDID")
bm_client = self.app.client_manager.baremetal
bm_client.node.list.return_value = [
def test_configure_boot_with_suffix(self):
self.bm_client.node.list.return_value = [
mock.Mock(uuid="ABCDEFGH"),
mock.Mock(uuid="IJKLMNOP"),
]
bm_client.node.get.side_effect = [
self.bm_client.node.get.side_effect = [
mock.Mock(uuid="ABCDEFGH", properties={}),
mock.Mock(uuid="IJKLMNOP", properties={}),
]
@ -963,390 +963,86 @@ class TestConfigureBaremetalBoot(fakes.TestBaremetal):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
self.assertEqual(find_resource_mock.call_count, 2)
self.assertEqual(find_resource_mock.mock_calls, [
mock.call(mock.ANY, 'bm-deploy-kernel_20150101T100620'),
mock.call(mock.ANY, 'bm-deploy-ramdisk_20150101T100620')
])
self.workflow_input['kernel_name'] = 'bm-deploy-kernel_20150101T100620'
self.workflow_input['ramdisk_name'] = (
'bm-deploy-ramdisk_20150101T100620')
self.assertEqual(bm_client.node.update.call_count, 2)
self.assertEqual(bm_client.node.update.mock_calls, [
mock.call('ABCDEFGH', [{
'op': 'add', 'value': 'boot_option:local',
'path': '/properties/capabilities'
}, {
'op': 'add', 'value': 'IDIDID',
'path': '/driver_info/deploy_ramdisk'
}, {
'op': 'add', 'value': 'IDIDID',
'path': '/driver_info/deploy_kernel'
}]),
mock.call('IJKLMNOP', [{
'op': 'add', 'value': 'boot_option:local',
'path': '/properties/capabilities'
}, {
'op': 'add', 'value': 'IDIDID',
'path': '/driver_info/deploy_ramdisk'
}, {
'op': 'add', 'value': 'IDIDID',
'path': '/driver_info/deploy_kernel'
}])
])
call_list = [mock.call('tripleo.baremetal.v1.configure',
workflow_input=self.workflow_input)]
workflow_input = copy.copy(self.workflow_input)
workflow_input['node_uuids'] = ["IJKLMNOP"]
call_list.append(mock.call('tripleo.baremetal.v1.configure',
workflow_input=workflow_input))
self.workflow.executions.create.assert_has_calls(call_list)
@mock.patch('openstackclient.common.utils.find_resource', autospec=True)
@mock.patch.object(baremetal.ConfigureBaremetalBoot, 'sleep_time',
new_callable=mock.PropertyMock,
return_value=0)
def test_configure_boot_in_transition(self, _, find_resource_mock):
find_resource_mock.return_value = mock.Mock(id="IDIDID")
def test_configure_boot_in_transition(self, _):
self.bm_client.node.list.return_value = [mock.Mock(uuid="ABCDEFGH",
power_state=None)]
self.bm_client.node.get.side_effect = [
mock.Mock(uuid="ABCDEFGH", power_state=None, properties={}),
mock.Mock(uuid="ABCDEFGH", power_state=None, properties={}),
mock.Mock(uuid="ABCDEFGH", power_state='available', properties={}),
mock.Mock(uuid="ABCDEFGH", power_state='available', properties={}),
]
bm_client = self.app.client_manager.baremetal
bm_client.node.list.return_value = [mock.Mock(uuid="ABCDEFGH",
power_state=None),
]
bm_client.node.get.side_effect = [mock.Mock(uuid="ABCDEFGH",
power_state=None,
properties={}),
mock.Mock(uuid="ABCDEFGH",
power_state=None,
properties={}),
mock.Mock(uuid="ABCDEFGH",
power_state='available',
properties={}),
mock.Mock(uuid="ABCDEFGH",
power_state='available',
properties={}),
]
parsed_args = self.check_parser(self.cmd, [], [])
self.cmd.take_action(parsed_args)
self.assertEqual(1, bm_client.node.list.call_count)
self.assertEqual(3, bm_client.node.get.call_count)
self.assertEqual(1, bm_client.node.update.call_count)
self.assertEqual(1, self.bm_client.node.list.call_count)
self.assertEqual(3, self.bm_client.node.get.call_count)
self.assertEqual(1, self.workflow.executions.create.call_count)
@mock.patch('openstackclient.common.utils.find_resource', autospec=True)
@mock.patch.object(baremetal.ConfigureBaremetalBoot, 'sleep_time',
new_callable=mock.PropertyMock,
return_value=0)
def test_configure_boot_timeout(self, _, find_resource_mock):
find_resource_mock.return_value = mock.Mock(id="IDIDID")
bm_client = self.app.client_manager.baremetal
bm_client.node.list.return_value = [mock.Mock(uuid="ABCDEFGH",
power_state=None),
]
bm_client.node.get.return_value = mock.Mock(uuid="ABCDEFGH",
power_state=None)
def test_configure_boot_timeout(self, _):
self.bm_client.node.list.return_value = [mock.Mock(uuid="ABCDEFGH",
power_state=None)]
self.bm_client.node.get.return_value = mock.Mock(uuid="ABCDEFGH",
power_state=None)
parsed_args = self.check_parser(self.cmd, [], [])
self.assertRaises(exceptions.Timeout,
self.cmd.take_action,
parsed_args)
@mock.patch('openstackclient.common.utils.find_resource', autospec=True)
def test_configure_boot_skip_maintenance(self, find_resource_mock):
find_resource_mock.return_value = mock.Mock(id="IDIDID")
bm_client = self.app.client_manager.baremetal
bm_client.node.list.return_value = [
def test_configure_boot_skip_maintenance(self):
self.bm_client.node.list.return_value = [
mock.Mock(uuid="ABCDEFGH", maintenance=False),
]
bm_client.node.get.return_value = mock.Mock(uuid="ABCDEFGH",
maintenance=False,
properties={})
self.bm_client.node.get.return_value = mock.Mock(uuid="ABCDEFGH",
maintenance=False,
properties={})
parsed_args = self.check_parser(self.cmd, [], [])
self.cmd.take_action(parsed_args)
self.assertEqual(bm_client.node.list.mock_calls, [mock.call(
self.assertEqual(self.bm_client.node.list.mock_calls, [mock.call(
maintenance=False)])
@mock.patch('openstackclient.common.utils.find_resource', autospec=True)
def test_configure_boot_existing_properties(self, find_resource_mock):
find_resource_mock.return_value = mock.Mock(id="IDIDID")
bm_client = self.app.client_manager.baremetal
bm_client.node.list.return_value = [
mock.Mock(uuid="ABCDEFGH"),
mock.Mock(uuid="IJKLMNOP"),
mock.Mock(uuid="QRSTUVWX"),
mock.Mock(uuid="YZABCDEF"),
]
bm_client.node.get.side_effect = [
mock.Mock(uuid="ABCDEFGH", properties={
'capabilities': 'existing:cap'
}),
mock.Mock(uuid="IJKLMNOP", properties={
'capabilities': 'boot_option:local'
}),
mock.Mock(uuid="QRSTUVWX", properties={
'capabilities': 'boot_option:remote'
}),
mock.Mock(uuid="YZABCDEF", properties={}),
]
parsed_args = self.check_parser(self.cmd, [], [])
self.cmd.take_action(parsed_args)
self.assertEqual(find_resource_mock.call_count, 2)
self.assertEqual(bm_client.node.update.call_count, 4)
self.assertEqual(bm_client.node.update.mock_calls, [
mock.call('ABCDEFGH', [{
'op': 'add', 'value': 'boot_option:local,existing:cap',
'path': '/properties/capabilities'
}, {
'op': 'add', 'value': 'IDIDID',
'path': '/driver_info/deploy_ramdisk'
}, {
'op': 'add', 'value': 'IDIDID',
'path': '/driver_info/deploy_kernel'
}]),
mock.call('IJKLMNOP', [{
'op': 'add', 'value': 'boot_option:local',
'path': '/properties/capabilities'
}, {
'op': 'add', 'value': 'IDIDID',
'path': '/driver_info/deploy_ramdisk'
}, {
'op': 'add', 'value': 'IDIDID',
'path': '/driver_info/deploy_kernel'
}]),
mock.call('QRSTUVWX', [{
'op': 'add', 'value': 'boot_option:remote',
'path': '/properties/capabilities'
}, {
'op': 'add', 'value': 'IDIDID',
'path': '/driver_info/deploy_ramdisk'
}, {
'op': 'add', 'value': 'IDIDID',
'path': '/driver_info/deploy_kernel'
}]),
mock.call('YZABCDEF', [{
'op': 'add', 'value': 'boot_option:local',
'path': '/properties/capabilities'
}, {
'op': 'add', 'value': 'IDIDID',
'path': '/driver_info/deploy_ramdisk'
}, {
'op': 'add', 'value': 'IDIDID',
'path': '/driver_info/deploy_kernel'
}]),
])
@mock.patch('openstackclient.common.utils.find_resource', autospec=True)
class TestConfigureBaremetalBootRootDeviceDetection(fakes.TestBaremetal):
def setUp(self):
super(TestConfigureBaremetalBootRootDeviceDetection, self).setUp()
# Get the command object to test
self.cmd = baremetal.ConfigureBaremetalBoot(self.app, None)
self.disks = [
{'name': '/dev/sda', 'size': 11 * units.Gi},
{'name': '/dev/sdb', 'size': 2 * units.Gi},
{'name': '/dev/sdc', 'size': 5 * units.Gi},
{'name': '/dev/sdd', 'size': 21 * units.Gi},
{'name': '/dev/sde', 'size': 13 * units.Gi},
]
for i, disk in enumerate(self.disks):
disk['wwn'] = 'wwn%d' % i
disk['serial'] = 'serial%d' % i
self.inspector_client = self.app.client_manager.baremetal_introspection
self.inspector_client.data['ABCDEFGH'] = {
'inventory': {'disks': self.disks}
}
self.bm_client = self.app.client_manager.baremetal
self.bm_client.node.list.return_value = [
mock.Mock(uuid="ABCDEFGH"),
]
self.node = mock.Mock(uuid="ABCDEFGH", properties={})
self.bm_client.node.get.return_value = self.node
def test_smallest(self, find_resource_mock):
find_resource_mock.return_value = mock.Mock(id="IDIDID")
arglist = ['--root-device', 'smallest']
verifylist = [('root_device', 'smallest')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
self.assertEqual(self.bm_client.node.update.call_count, 2)
root_device_args = self.bm_client.node.update.call_args_list[1]
expected_patch = [{'op': 'add', 'path': '/properties/root_device',
'value': {'wwn': 'wwn2'}},
{'op': 'add', 'path': '/properties/local_gb',
'value': 4}]
self.assertEqual(mock.call('ABCDEFGH', expected_patch),
root_device_args)
def test_largest(self, find_resource_mock):
find_resource_mock.return_value = mock.Mock(id="IDIDID")
arglist = ['--root-device', 'largest']
verifylist = [('root_device', 'largest')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
self.assertEqual(self.bm_client.node.update.call_count, 2)
root_device_args = self.bm_client.node.update.call_args_list[1]
expected_patch = [{'op': 'add', 'path': '/properties/root_device',
'value': {'wwn': 'wwn3'}},
{'op': 'add', 'path': '/properties/local_gb',
'value': 20}]
self.assertEqual(mock.call('ABCDEFGH', expected_patch),
root_device_args)
def test_no_overwrite(self, find_resource_mock):
find_resource_mock.return_value = mock.Mock(id="IDIDID")
self.node.properties['root_device'] = {'foo': 'bar'}
arglist = ['--root-device', 'smallest']
verifylist = [('root_device', 'smallest')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
self.assertEqual(self.bm_client.node.update.call_count, 1)
def test_with_overwrite(self, find_resource_mock):
find_resource_mock.return_value = mock.Mock(id="IDIDID")
self.node.properties['root_device'] = {'foo': 'bar'}
arglist = ['--root-device', 'smallest',
'--overwrite-root-device-hints']
def test_root_device_options(self):
argslist = ['--root-device', 'smallest',
'--root-device-minimum-size', '2',
'--overwrite-root-device-hints']
verifylist = [('root_device', 'smallest'),
('root_device_minimum_size', 2),
('overwrite_root_device_hints', True)]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
parsed_args = self.check_parser(self.cmd, argslist, verifylist)
self.cmd.take_action(parsed_args)
self.assertEqual(self.bm_client.node.update.call_count, 2)
root_device_args = self.bm_client.node.update.call_args_list[1]
expected_patch = [{'op': 'add', 'path': '/properties/root_device',
'value': {'wwn': 'wwn2'}},
{'op': 'add', 'path': '/properties/local_gb',
'value': 4}]
self.assertEqual(mock.call('ABCDEFGH', expected_patch),
root_device_args)
def test_minimum_size(self, find_resource_mock):
find_resource_mock.return_value = mock.Mock(id="IDIDID")
arglist = ['--root-device', 'smallest',
'--root-device-minimum-size', '10']
verifylist = [('root_device', 'smallest')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
self.assertEqual(self.bm_client.node.update.call_count, 2)
root_device_args = self.bm_client.node.update.call_args_list[1]
expected_patch = [{'op': 'add', 'path': '/properties/root_device',
'value': {'wwn': 'wwn0'}},
{'op': 'add', 'path': '/properties/local_gb',
'value': 10}]
self.assertEqual(mock.call('ABCDEFGH', expected_patch),
root_device_args)
def test_bad_inventory(self, find_resource_mock):
find_resource_mock.return_value = mock.Mock(id="IDIDID")
del self.inspector_client.data['ABCDEFGH']['inventory']
arglist = ['--root-device', 'smallest']
verifylist = [('root_device', 'smallest')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertRaisesRegexp(exceptions.RootDeviceDetectionError,
"Malformed introspection data",
self.cmd.take_action, parsed_args)
self.assertEqual(self.bm_client.node.update.call_count, 1)
def test_no_disks(self, find_resource_mock):
find_resource_mock.return_value = mock.Mock(id="IDIDID")
self.inspector_client.data['ABCDEFGH']['inventory']['disks'] = [
{'name': '/dev/sda', 'size': 1 * units.Gi}
]
arglist = ['--root-device', 'smallest']
verifylist = [('root_device', 'smallest')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertRaisesRegexp(exceptions.RootDeviceDetectionError,
"No suitable disks",
self.cmd.take_action, parsed_args)
self.assertEqual(self.bm_client.node.update.call_count, 1)
def test_no_data(self, find_resource_mock):
find_resource_mock.return_value = mock.Mock(id="IDIDID")
del self.inspector_client.data['ABCDEFGH']
arglist = ['--root-device', 'smallest']
verifylist = [('root_device', 'smallest')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertRaisesRegexp(exceptions.RootDeviceDetectionError,
"No introspection data",
self.cmd.take_action, parsed_args)
self.assertEqual(self.bm_client.node.update.call_count, 1)
def test_no_wwn_and_serial(self, find_resource_mock):
find_resource_mock.return_value = mock.Mock(id="IDIDID")
self.inspector_client.data['ABCDEFGH']['inventory']['disks'] = [
{'name': '/dev/sda', 'size': 10 * units.Gi}
]
arglist = ['--root-device', 'smallest']
verifylist = [('root_device', 'smallest')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertRaisesRegexp(exceptions.RootDeviceDetectionError,
"Neither WWN nor serial number are known",
self.cmd.take_action, parsed_args)
self.assertEqual(self.bm_client.node.update.call_count, 1)
def test_device_list(self, find_resource_mock):
find_resource_mock.return_value = mock.Mock(id="IDIDID")
arglist = ['--root-device', 'hda,sda,sdb,sdc']
verifylist = [('root_device', 'hda,sda,sdb,sdc')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
self.assertEqual(self.bm_client.node.update.call_count, 2)
root_device_args = self.bm_client.node.update.call_args_list[1]
expected_patch = [{'op': 'add', 'path': '/properties/root_device',
'value': {'wwn': 'wwn0'}},
{'op': 'add', 'path': '/properties/local_gb',
'value': 10}]
self.assertEqual(mock.call('ABCDEFGH', expected_patch),
root_device_args)
def test_device_list_not_found(self, find_resource_mock):
find_resource_mock.return_value = mock.Mock(id="IDIDID")
arglist = ['--root-device', 'hda']
verifylist = [('root_device', 'hda')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertRaisesRegexp(exceptions.RootDeviceDetectionError,
"Cannot find a disk",
self.cmd.take_action, parsed_args)
self.assertEqual(self.bm_client.node.update.call_count, 1)
self.workflow_input['root_device'] = 'smallest'
self.workflow_input['root_device_minimum_size'] = 2
self.workflow_input['overwrite_root_device_hints'] = True
self.workflow.executions.create.assert_called_once_with(
'tripleo.baremetal.v1.configure',
workflow_input=self.workflow_input
)
class TestShowNodeCapabilities(fakes.TestBaremetal):

View File

@ -23,10 +23,7 @@ import uuid
from cliff import command
from cliff import lister
import ironic_inspector_client
from openstackclient.common import utils as osc_utils
from openstackclient.i18n import _
from oslo_utils import units
from tripleoclient import exceptions
from tripleoclient import utils
@ -391,31 +388,10 @@ class ConfigureBaremetalBoot(command.Command):
def take_action(self, parsed_args):
self.log.debug("take_action(%s)" % parsed_args)
queue_name = str(uuid.uuid4())
bm_client = self.app.client_manager.baremetal
image_client = self.app.client_manager.image
try:
kernel_id = osc_utils.find_resource(
image_client.images, parsed_args.deploy_kernel).id
except AttributeError:
self.log.error("Please make sure that an image named \"%s\" exists"
" in Glance and is the only image with this name."
% parsed_args.deploy_ramdisk)
return
try:
ramdisk_id = osc_utils.find_resource(
image_client.images, parsed_args.deploy_ramdisk).id
except AttributeError:
self.log.error("Please make sure that an image named \"%s\" exists"
" in Glance and is the only image with this name."
% parsed_args.deploy_ramdisk)
return
self.log.debug("Using kernel ID: {0} and ramdisk ID: {1}".format(
kernel_id, ramdisk_id))
for node in bm_client.node.list(maintenance=False):
# NOTE(bnemec): Ironic won't let us update the node while the
# power_state is transitioning.
@ -438,136 +414,17 @@ class ConfigureBaremetalBoot(command.Command):
node.uuid)
raise exceptions.Timeout(msg)
# Get the full node info
capabilities = node_detail.properties.get('capabilities', None)
# Only update capabilities to add boot_option if it doesn't exist.
if capabilities:
if 'boot_option' not in capabilities:
capabilities = "boot_option:local,%s" % capabilities
else:
capabilities = "boot_option:local"
self.log.debug("Configuring boot for Node {0}".format(
node.uuid))
bm_client.node.update(node.uuid, [
{
'op': 'add',
'path': '/properties/capabilities',
'value': capabilities,
},
{
'op': 'add',
'path': '/driver_info/deploy_ramdisk',
'value': ramdisk_id,
},
{
'op': 'add',
'path': '/driver_info/deploy_kernel',
'value': kernel_id,
},
])
self._apply_root_device_strategy(
node_detail, parsed_args.root_device,
parsed_args.root_device_minimum_size,
parsed_args.overwrite_root_device_hints)
def _apply_root_device_strategy(self, node, strategy, minimum_size,
overwrite=False):
if not strategy:
return
if node.properties.get('root_device') and not overwrite:
# This is a correct situation, we still want to allow people to
# fine-tune the root device setting for a subset of nodes.
# However, issue a warning, so that they know which nodes were not
# updated during this run.
self.log.warning('Root device hints are already set for node %s '
'and overwriting is not requested, skipping',
node.uuid)
self.log.warning('You may unset them by running $ ironic '
'node-update %s remove properties/root_device',
node.uuid)
return
inspector_client = self.app.client_manager.baremetal_introspection
try:
data = inspector_client.get_data(node.uuid)
except ironic_inspector_client.ClientError:
raise exceptions.RootDeviceDetectionError(
'No introspection data found for node %s, '
'root device cannot be detected' % node.uuid)
except AttributeError:
raise RuntimeError('Ironic inspector client version 1.2.0 or '
'newer is required for detecting root device')
try:
disks = data['inventory']['disks']
except KeyError:
raise exceptions.RootDeviceDetectionError(
'Malformed introspection data for node %s: '
'disks list is missing' % node.uuid)
minimum_size *= units.Gi
disks = [d for d in disks if d.get('size', 0) >= minimum_size]
if not disks:
raise exceptions.RootDeviceDetectionError(
'No suitable disks found for node %s' % node.uuid)
if strategy == 'smallest':
disks.sort(key=lambda d: d['size'])
root_device = disks[0]
elif strategy == 'largest':
disks.sort(key=lambda d: d['size'], reverse=True)
root_device = disks[0]
else:
disk_names = [x.strip() for x in strategy.split(',')]
disks = {d['name']: d for d in disks}
for candidate in disk_names:
try:
root_device = disks['/dev/%s' % candidate]
except KeyError:
continue
else:
break
else:
raise exceptions.RootDeviceDetectionError(
'Cannot find a disk with any of names %(strategy)s '
'for node %(node)s' %
{'strategy': strategy, 'node': node.uuid})
hint = None
for hint_name in ('wwn', 'serial'):
if root_device.get(hint_name):
hint = {hint_name: root_device[hint_name]}
break
if hint is None:
# I don't think it might actually happen, but just in case
raise exceptions.RootDeviceDetectionError(
'Neither WWN nor serial number are known for device %(dev)s '
'on node %(node)s; root device hints cannot be used' %
{'dev': root_device['name'], 'node': node.uuid})
# During the introspection process we got local_gb assigned according
# to the default strategy. Now we need to update it.
new_size = root_device['size'] / units.Gi
# This -1 is what we always do to account for partitioning
new_size -= 1
bm_client = self.app.client_manager.baremetal
bm_client.node.update(
node.uuid,
[{'op': 'add', 'path': '/properties/root_device', 'value': hint},
{'op': 'add', 'path': '/properties/local_gb', 'value': new_size}])
self.log.info('Updated root device for node %(node)s, new device '
'is %(dev)s, new local_gb is %(local_gb)d',
{'node': node.uuid, 'dev': root_device,
'local_gb': new_size})
baremetal.configure(
self.app.client_manager,
node_uuids=[node.uuid],
queue_name=queue_name,
kernel_name=parsed_args.deploy_kernel,
ramdisk_name=parsed_args.deploy_ramdisk,
root_device=parsed_args.root_device,
root_device_minimum_size=parsed_args.root_device_minimum_size,
overwrite_root_device_hints=(
parsed_args.overwrite_root_device_hints)
)
class ShowNodeCapabilities(lister.Lister):