Merge "Move baremetal configure commands from Ansible"

This commit is contained in:
Zuul
2022-02-03 09:07:36 +00:00
committed by Gerrit Code Review
5 changed files with 339 additions and 136 deletions

View File

@@ -25,6 +25,7 @@ import openstack
from osc_lib import exceptions as oscexc
from osc_lib.tests import utils as test_utils
from oslo_utils import units
import yaml
from tripleoclient import exceptions
@@ -697,6 +698,14 @@ class TestImportNodeMultiArch(fakes.TestOvercloudNode):
self._check_workflow_call(parsed_args, no_deploy_image=True)
@mock.patch.object(openstack.baremetal.v1._proxy, 'Proxy',
autospec=True, name='mock_bm')
@mock.patch('openstack.config', autospec=True,
name='mock_conf')
@mock.patch('openstack.connect', autospec=True,
name='mock_connect')
@mock.patch.object(openstack.connection,
'Connection', autospec=True)
class TestConfigureNode(fakes.TestOvercloudNode):
def setUp(self):
@@ -714,26 +723,62 @@ class TestConfigureNode(fakes.TestOvercloudNode):
'root_device_minimum_size': 4,
'overwrite_root_device_hints': False
}
# Mock disks
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},
]
def test_configure_all_manageable_nodes(self):
for i, disk in enumerate(self.disks):
disk['wwn'] = 'wwn%d' % i
disk['serial'] = 'serial%d' % i
self.fake_baremetal_node = fakes.make_fake_machine(
machine_name='node1',
machine_id='4e540e11-1366-4b57-85d5-319d168d98a1'
)
self.fake_baremetal_node2 = fakes.make_fake_machine(
machine_name='node2',
machine_id='9070e42d-1ad7-4bd0-b868-5418bc9c7176'
)
def test_configure_all_manageable_nodes(self, mock_conn,
mock_connect, mock_conf,
mock_bm):
mock_conn.return_value = mock_bm
mock_bm.baremetal = mock_bm
mock_bm.baremetal.nodes.side_effect = [
iter([self.fake_baremetal_node]),
iter([self.fake_baremetal_node])]
parsed_args = self.check_parser(self.cmd,
['--all-manageable'],
[('all_manageable', True)])
self.cmd.take_action(parsed_args)
def test_configure_specified_nodes(self):
def test_configure_specified_nodes(self, mock_conn,
mock_connect, mock_conf,
mock_bm):
mock_conn.return_value = mock_bm
mock_bm.baremetal = mock_bm
argslist = ['node_uuid1', 'node_uuid2']
verifylist = [('node_uuids', ['node_uuid1', 'node_uuid2'])]
parsed_args = self.check_parser(self.cmd, argslist, verifylist)
self.cmd.take_action(parsed_args)
def test_configure_no_node_or_flag_specified(self):
def test_configure_no_node_or_flag_specified(self, mock_conn,
mock_connect, mock_conf,
mock_bm):
self.assertRaises(test_utils.ParserException,
self.check_parser,
self.cmd, [], [])
def test_configure_uuids_and_all_both_specified(self):
def test_configure_uuids_and_all_both_specified(self, mock_conn,
mock_connect, mock_conf,
mock_bm):
argslist = ['node_id1', 'node_id2', '--all-manageable']
verifylist = [('node_uuids', ['node_id1', 'node_id2']),
('all_manageable', True)]
@@ -741,7 +786,23 @@ class TestConfigureNode(fakes.TestOvercloudNode):
self.check_parser,
self.cmd, argslist, verifylist)
def test_configure_kernel_and_ram(self):
def test_configure_kernel_and_ram(self, mock_conn,
mock_connect, mock_conf,
mock_bm):
mock_conn.return_value = mock_bm
mock_bm.baremetal = mock_bm
mock_bm.baremetal_introspection = mock_bm
introspector_client = mock_bm.baremetal_introspection
introspector_client.get_introspection_data = mock_bm
introspector_client.get_introspection_data.return_value = {
'inventory': {'disks': self.disks}
}
mock_bm.baremetal.nodes.side_effect = [
iter([self.fake_baremetal_node]),
iter([self.fake_baremetal_node])]
argslist = ['--all-manageable', '--deploy-ramdisk', 'test_ramdisk',
'--deploy-kernel', 'test_kernel']
verifylist = [('deploy_kernel', 'test_kernel'),
@@ -750,14 +811,35 @@ class TestConfigureNode(fakes.TestOvercloudNode):
parsed_args = self.check_parser(self.cmd, argslist, verifylist)
self.cmd.take_action(parsed_args)
def test_configure_instance_boot_option(self):
def test_configure_instance_boot_option(self, mock_conn,
mock_connect, mock_conf,
mock_bm):
mock_conn.return_value = mock_bm
mock_bm.baremetal = mock_bm
mock_bm.baremetal.nodes.side_effect = [
iter([self.fake_baremetal_node]),
iter([self.fake_baremetal_node])]
argslist = ['--all-manageable', '--instance-boot-option', 'netboot']
verifylist = [('instance_boot_option', 'netboot')]
parsed_args = self.check_parser(self.cmd, argslist, verifylist)
self.cmd.take_action(parsed_args)
def test_configure_root_device(self):
def test_configure_root_device(self, mock_conn,
mock_connect, mock_conf,
mock_bm):
mock_conn.return_value = mock_bm
mock_bm.baremetal = mock_bm
mock_bm.baremetal_introspection = mock_bm
introspector_client = mock_bm.baremetal_introspection
introspector_client.get_introspection_data = mock_bm
introspector_client.get_introspection_data.return_value = {
'inventory': {'disks': self.disks}
}
mock_bm.baremetal.nodes.side_effect = [
iter([self.fake_baremetal_node]),
iter([self.fake_baremetal_node])]
argslist = ['--all-manageable',
'--root-device', 'smallest',
'--root-device-minimum-size', '2',
@@ -772,7 +854,23 @@ class TestConfigureNode(fakes.TestOvercloudNode):
@mock.patch('tripleoclient.workflows.baremetal.'
'_apply_root_device_strategy')
def test_configure_specified_node_with_all_arguments(
self, mock_root_device):
self, mock_root_device, mock_conn,
mock_connect, mock_conf,
mock_bm):
mock_conn.return_value = mock_bm
mock_bm.baremetal = mock_bm
mock_bm.baremetal_introspection = mock_bm
introspector_client = mock_bm.baremetal_introspection
introspector_client.get_introspection_data = mock_bm
introspector_client.get_introspection_data.return_value = {
'inventory': {'disks': self.disks}
}
mock_bm.baremetal.nodes.side_effect = [
iter([self.fake_baremetal_node]),
iter([self.fake_baremetal_node])]
argslist = ['node_id',
'--deploy-kernel', 'test_kernel',
'--deploy-ramdisk', 'test_ramdisk',

View File

@@ -108,12 +108,6 @@ class TestBaremetalWorkflows(fakes.FakePlaybookExecution):
node_timeout=1200, max_retries=1, retry_timeout=120,
)
def test_configure_success(self):
baremetal.configure(self.app.client_manager, node_uuids=[])
def test_configure_manageable_nodes_success(self):
baremetal.configure_manageable_nodes(self.app.client_manager)
def test_run_instance_boot_option(self):
result = baremetal._configure_boot(
self.app.client_manager,

View File

@@ -333,36 +333,31 @@ class ConfigureNode(command.Command):
action='store_true',
help=_('Whether to overwrite existing root device '
'hints when --root-device is used.'))
parser.add_argument("--verbosity",
type=int,
default=1,
help=_("Print debug output during execution"))
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)" % parsed_args)
conf = tb.TripleoConfigure(
kernel_name=parsed_args.deploy_kernel,
ramdisk_name=parsed_args.deploy_ramdisk,
instance_boot_option=parsed_args.instance_boot_option,
boot_mode=parsed_args.boot_mode,
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)
)
if parsed_args.node_uuids:
baremetal.configure(
self.app.client_manager,
node_uuids=parsed_args.node_uuids,
kernel_name=parsed_args.deploy_kernel,
ramdisk_name=parsed_args.deploy_ramdisk,
instance_boot_option=parsed_args.instance_boot_option,
boot_mode=parsed_args.boot_mode,
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)
)
conf.configure(
node_uuids=parsed_args.node_uuids)
else:
baremetal.configure_manageable_nodes(
self.app.client_manager,
kernel_name=parsed_args.deploy_kernel,
ramdisk_name=parsed_args.deploy_ramdisk,
instance_boot_option=parsed_args.instance_boot_option,
boot_mode=parsed_args.boot_mode,
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)
)
conf.configure_manageable_nodes()
class DiscoverNode(command.Command):

View File

@@ -351,105 +351,6 @@ def _apply_root_device_strategy(clients, node_uuid, strategy,
{'node': node.uuid, 'dev': root_device, 'local_gb': new_size})
def configure(clients, node_uuids, kernel_name=None,
ramdisk_name=None, instance_boot_option=None,
boot_mode=None, root_device=None, root_device_minimum_size=4,
overwrite_root_device_hints=False):
"""Configure Node boot options.
:param node_uuids: List of instance UUID(s).
:type node_uuids: List
:param kernel_name: Kernel to use
:type kernel_name: String
:param ramdisk_name: RAMDISK to use
:type ramdisk_name: String
:param instance_boot_option: Boot options to use
:type instance_boot_option: String
:param boot_mode: Boot mode to use
:type instance_boot_option: String
:param root_device: Path (name) of the root device.
:type root_device: String
:param root_device_minimum_size: Size of the given root device.
:type root_device_minimum_size: Integer
:param overwrite_root_device_hints: Whether to overwrite existing root
device hints when `root_device` is
used.
:type overwrite_root_device_hints: Boolean
"""
for node_uuid in node_uuids:
_configure_boot(clients, node_uuid, kernel_name,
ramdisk_name, instance_boot_option, boot_mode)
if root_device:
_apply_root_device_strategy(
clients, node_uuid,
strategy=root_device,
minimum_size=root_device_minimum_size,
overwrite=overwrite_root_device_hints)
print('Successfully configured the nodes.')
def configure_manageable_nodes(clients, kernel_name='bm-deploy-kernel',
ramdisk_name='bm-deploy-ramdisk',
instance_boot_option=None, boot_mode=None,
root_device=None, root_device_minimum_size=4,
overwrite_root_device_hints=False):
"""Configure all manageable Nodes.
kernel_name=parsed_args.deploy_kernel,
ramdisk_name=parsed_args.deploy_ramdisk,
instance_boot_option=parsed_args.instance_boot_option,
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)
:param kernel_name: Kernel to use
:type kernel_name: String
:param ramdisk_name: RAMDISK to use
:type ramdisk_name: String
:param instance_boot_option: Boot options to use
:type instance_boot_option: String
:param boot_mode: Boot mode to use
:type instance_boot_option: String
:param root_device: Path (name) of the root device.
:type root_device: String
:param root_device_minimum_size: Size of the given root device.
:type root_device_minimum_size: Integer
:param overwrite_root_device_hints: Whether to overwrite existing root
device hints when `root_device` is
used.
:type overwrite_root_device_hints: Boolean
"""
configure(
clients=clients,
node_uuids=[
i.uuid for i in clients.baremetal.node.list()
if i.provision_state == "manageable" and not i.maintenance
],
kernel_name=kernel_name,
ramdisk_name=ramdisk_name,
instance_boot_option=instance_boot_option,
boot_mode=boot_mode,
root_device=root_device,
root_device_minimum_size=root_device_minimum_size,
overwrite_root_device_hints=overwrite_root_device_hints
)
def create_raid_configuration(clients, node_uuids, configuration,
verbosity=0):
"""Create RAID configuration on nodes.

View File

@@ -19,7 +19,9 @@ from concurrent import futures
from openstack import connect as sdkclient
from openstack import exceptions
from openstack.utils import iterate_timeout
from oslo_utils import units
from tripleoclient import exceptions as ooo_exceptions
from tripleo_common.utils import nodes as node_utils
class TripleoBaremetal(object):
@@ -309,3 +311,216 @@ class TripleoClean(TripleoBaremetal):
self.log.info(msg)
except exceptions.OpenStackCloudException as err:
self.log.error(str(err))
class TripleoConfigure(TripleoBaremetal):
"""TripleoConfigure handles properties for the ironic nodes.
We use this class to set the properties for each node such as the
kernel, ramdisk, boot device, root_device.
:param kernel_name: The name of the kernel image we will deploy
:type kernel_name: String
:param ramdisk_name: The name of the ramdisk image we will deploy
:type ramdisk_name: String
:param instance_boot: Should the node boot from local disks or something
else
:type instance_boot: String
:param boot_mode: Is this node using BIOS or UEFI
:type boot_mode: String
:param: root_device: What is the root device for this node. eg /dev/sda
:type root_device: String
:param root_device_minimum_size: What is the smallest disk we should
consider acceptable for deployment
:type root_device: Integer
:param overwrite_root_device_hints: Should we overwrite existing root
device hints when root_device is used.
:type overwrite_root_device_hints: Boolean
"""
log = logging.getLogger(__name__)
def __init__(self, kernel_name: str = None, ramdisk_name: str = None,
instance_boot_option: str = None, boot_mode: str = None,
root_device: str = None, verbosity: int = 0,
root_device_minimum_size: int = 4,
overwrite_root_device_hints: bool = False):
super().__init__(verbosity=verbosity)
self.kernel_name = kernel_name
self.ramdisk_name = ramdisk_name
self.instance_boot_option = instance_boot_option
self.boot_mode = boot_mode
self.root_device = root_device
self.root_device_minimum_size = root_device_minimum_size
self.overwrite_root_device_hints = overwrite_root_device_hints
def _apply_root_device_strategy(self, node_uuid: List,
strategy: str, minimum_size: int = 4,
overwrite: bool = False):
clients = self.conn
node = clients.baremetal.find_node(node_uuid)
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 '
'{} and overwriting is not requested,'
' skipping'.format(node.id))
self.log.warning('You may unset them by running $ ironic '
'node-update {} remove '
'properties/root_device'.format(node.id))
return
inspector_client = self.conn.baremetal_introspection
baremetal_client = self.conn.baremetal
try:
data = inspector_client.get_introspection_data(node.id)
except Exception:
raise exceptions.RootDeviceDetectionError(
f'No introspection data found for node {node.id}, '
'root device cannot be detected')
try:
disks = data['inventory']['disks']
except KeyError:
raise exceptions.RootDeviceDetectionError(
f'Malformed introspection data for node {node.id}: '
'disks list is missing')
minimum_size *= units.Gi
disks = [d for d in disks if d.get('size', 0) >= minimum_size]
if not disks:
raise exceptions.RootDeviceDetectionError(
f'No suitable disks found for node {node.id}')
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(
f'Cannot find a disk with any of names {strategy} '
f'for node {node.id}')
hint = None
for hint_name in ('wwn_with_extension', '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(
f"Neither WWN nor serial number are known for device "
f"{root_device['name']} "
f"on node {node.id}; root device hints cannot be used")
# 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
baremetal_client.update_node(
node.id,
[{'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 %s, new device '
'is %s, new local_gb is %s',
node.id, root_device, new_size
)
def _configure_boot(self, node_uuid: List,
kernel_name: str = None,
ramdisk_name: str = None,
instance_boot_option: str = None,
boot_mode: str = None):
baremetal_client = self.conn.baremetal
image_ids = {'kernel': kernel_name, 'ramdisk': ramdisk_name}
node = baremetal_client.find_node(node_uuid)
capabilities = node.properties.get('capabilities', {})
capabilities = node_utils.capabilities_to_dict(capabilities)
if instance_boot_option is not None:
capabilities['boot_option'] = instance_boot_option
if boot_mode is not None:
capabilities['boot_mode'] = boot_mode
capabilities = node_utils.dict_to_capabilities(capabilities)
baremetal_client.update_node(node.id, [
{
'op': 'add',
'path': '/properties/capabilities',
'value': capabilities,
},
{
'op': 'add',
'path': '/driver_info/deploy_ramdisk',
'value': image_ids['ramdisk'],
},
{
'op': 'add',
'path': '/driver_info/deploy_kernel',
'value': image_ids['kernel'],
},
{
'op': 'add',
'path': '/driver_info/rescue_ramdisk',
'value': image_ids['ramdisk'],
},
{
'op': 'add',
'path': '/driver_info/rescue_kernel',
'value': image_ids['kernel'],
},
])
def configure(self, node_uuids: List):
"""Configure Node boot options.
:param node_uuids: List of instance UUID(s).
:type node_uuids: List
"""
for node_uuid in node_uuids:
self._configure_boot(node_uuid, self.kernel_name,
self.ramdisk_name, self.instance_boot_option,
self.boot_mode)
if self.root_device:
self._apply_root_device_strategy(
node_uuid,
strategy=self.root_device,
minimum_size=self.root_device_minimum_size,
overwrite=self.overwrite_root_device_hints)
self.log.info('Successfully configured the nodes.')
def configure_manageable_nodes(self):
self.configure(node_uuids=self.all_manageable_nodes())