diff --git a/releasenotes/notes/ipmi-discovery-aaee9fb7082ffac4.yaml b/releasenotes/notes/ipmi-discovery-aaee9fb7082ffac4.yaml new file mode 100644 index 000000000..eeae265e4 --- /dev/null +++ b/releasenotes/notes/ipmi-discovery-aaee9fb7082ffac4.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add new command ``openstack overcloud node discover`` for nodes discovery + by probing a range of IP addresses for accessible BMCs. diff --git a/setup.cfg b/setup.cfg index fe250f907..2c557cede 100644 --- a/setup.cfg +++ b/setup.cfg @@ -76,6 +76,7 @@ openstack.tripleoclient.v1 = overcloud_node_import = tripleoclient.v1.overcloud_node:ImportNode overcloud_node_introspect = tripleoclient.v1.overcloud_node:IntrospectNode overcloud_node_provide = tripleoclient.v1.overcloud_node:ProvideNode + overcloud_node_discover = tripleoclient.v1.overcloud_node:DiscoverNode overcloud_parameters_set = tripleoclient.v1.overcloud_parameters:SetParameters overcloud_plan_create = tripleoclient.v1.overcloud_plan:CreatePlan overcloud_plan_delete = tripleoclient.v1.overcloud_plan:DeletePlan diff --git a/tripleoclient/tests/v1/overcloud_node/test_overcloud_node.py b/tripleoclient/tests/v1/overcloud_node/test_overcloud_node.py index 74bf1142b..a29436062 100644 --- a/tripleoclient/tests/v1/overcloud_node/test_overcloud_node.py +++ b/tripleoclient/tests/v1/overcloud_node/test_overcloud_node.py @@ -639,3 +639,100 @@ class TestConfigureNode(fakes.TestOvercloudNode): 'tripleo.baremetal.v1.configure', workflow_input=self.workflow_input ) + + +class TestDiscoverNode(fakes.TestOvercloudNode): + + def setUp(self): + super(TestDiscoverNode, self).setUp() + + self.workflow = self.app.client_manager.workflow_engine + client = self.app.client_manager.tripleoclient + self.websocket = client.messaging_websocket() + + self.cmd = overcloud_node.DiscoverNode(self.app, None) + + self.websocket.wait_for_messages.return_value = [{ + "status": "SUCCESS", + "message": "Success", + "registered_nodes": [{ + "uuid": "MOCK_NODE_UUID" + }] + }] + + def test_with_ip_range(self): + argslist = ['--range', '10.0.0.0/24', + '--credentials', 'admin:password'] + verifylist = [('ip_addresses', '10.0.0.0/24'), + ('credentials', ['admin:password'])] + + parsed_args = self.check_parser(self.cmd, argslist, verifylist) + self.cmd.take_action(parsed_args) + + self.workflow.executions.create.assert_called_once_with( + 'tripleo.baremetal.v1.discover_and_enroll_nodes', + workflow_input={'ip_addresses': '10.0.0.0/24', + 'credentials': [['admin', 'password']], + 'queue_name': mock.ANY, + 'kernel_name': 'bm-deploy-kernel', + 'ramdisk_name': 'bm-deploy-ramdisk', + 'instance_boot_option': 'local'} + ) + + def test_with_address_list(self): + argslist = ['--ip', '10.0.0.1', '--ip', '10.0.0.2', + '--credentials', 'admin:password'] + verifylist = [('ip_addresses', ['10.0.0.1', '10.0.0.2']), + ('credentials', ['admin:password'])] + + parsed_args = self.check_parser(self.cmd, argslist, verifylist) + self.cmd.take_action(parsed_args) + + self.workflow.executions.create.assert_called_once_with( + 'tripleo.baremetal.v1.discover_and_enroll_nodes', + workflow_input={'ip_addresses': ['10.0.0.1', '10.0.0.2'], + 'credentials': [['admin', 'password']], + 'queue_name': mock.ANY, + 'kernel_name': 'bm-deploy-kernel', + 'ramdisk_name': 'bm-deploy-ramdisk', + 'instance_boot_option': 'local'} + ) + + def test_with_all_options(self): + argslist = ['--range', '10.0.0.0/24', + '--credentials', 'admin:password', + '--credentials', 'admin2:password2', + '--port', '623', '--port', '6230', + '--introspect', '--provide', '--run-validations', + '--no-deploy-image', '--instance-boot-option', 'netboot'] + verifylist = [('ip_addresses', '10.0.0.0/24'), + ('credentials', ['admin:password', 'admin2:password2']), + ('port', [623, 6230]), + ('introspect', True), + ('run_validations', True), + ('provide', True), + ('no_deploy_image', True), + ('instance_boot_option', 'netboot')] + + parsed_args = self.check_parser(self.cmd, argslist, verifylist) + self.cmd.take_action(parsed_args) + + workflows_calls = [ + mock.call('tripleo.baremetal.v1.discover_and_enroll_nodes', + workflow_input={'ip_addresses': '10.0.0.0/24', + 'credentials': [['admin', 'password'], + ['admin2', 'password2']], + 'ports': [623, 6230], + 'queue_name': mock.ANY, + 'kernel_name': None, + 'ramdisk_name': None, + 'instance_boot_option': 'netboot'}), + mock.call('tripleo.baremetal.v1.introspect', + workflow_input={'node_uuids': ['MOCK_NODE_UUID'], + 'run_validations': True, + 'queue_name': mock.ANY}), + mock.call('tripleo.baremetal.v1.provide', + workflow_input={'node_uuids': ['MOCK_NODE_UUID'], + 'queue_name': mock.ANY}), + ] + self.workflow.executions.create.assert_has_calls(workflows_calls) diff --git a/tripleoclient/v1/overcloud_node.py b/tripleoclient/v1/overcloud_node.py index 6b4db7f54..e354f8ec9 100644 --- a/tripleoclient/v1/overcloud_node.py +++ b/tripleoclient/v1/overcloud_node.py @@ -316,3 +316,83 @@ class ConfigureNode(command.Command): overwrite_root_device_hints=( parsed_args.overwrite_root_device_hints) ) + + +class DiscoverNode(command.Command): + """Discover overcloud nodes by polling their BMCs.""" + + log = logging.getLogger(__name__ + ".DiscoverNode") + + def get_parser(self, prog_name): + parser = super(DiscoverNode, self).get_parser(prog_name) + ip_group = parser.add_mutually_exclusive_group(required=True) + ip_group.add_argument('--ip', action='append', + dest='ip_addresses', metavar='', + help=_('IP address(es) to probe')) + ip_group.add_argument('--range', dest='ip_addresses', + metavar='', help=_('IP range to probe')) + parser.add_argument('--credentials', metavar='', + action='append', required=True, + help=_('Key/value pairs of possible credentials')) + parser.add_argument('--port', action='append', metavar='', + type=int, help=_('BMC port(s) to probe')) + parser.add_argument('--introspect', action='store_true', + help=_('Introspect the imported nodes')) + parser.add_argument('--run-validations', action='store_true', + default=False, + help=_('Run the pre-deployment validations. These ' + 'external validations are from the TripleO ' + 'Validations project.')) + parser.add_argument('--provide', action='store_true', + help=_('Provide (make available) the nodes')) + parser.add_argument('--no-deploy-image', action='store_true', + help=_('Skip setting the deploy kernel and ' + 'ramdisk.')) + parser.add_argument('--instance-boot-option', + choices=['local', 'netboot'], default='local', + help=_('Whether to set instances for booting from ' + 'local hard drive (local) or network ' + '(netboot).')) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + + queue_name = str(uuid.uuid4()) + + if parsed_args.no_deploy_image: + deploy_kernel = None + deploy_ramdisk = None + else: + deploy_kernel = 'bm-deploy-kernel' + deploy_ramdisk = 'bm-deploy-ramdisk' + + credentials = [list(x.split(':', 1)) for x in parsed_args.credentials] + kwargs = {} + # Leave it up to the workflow to figure out the defaults + if parsed_args.port: + kwargs['ports'] = parsed_args.port + + nodes = baremetal.discover_and_enroll( + self.app.client_manager, + ip_addresses=parsed_args.ip_addresses, + credentials=credentials, + queue_name=queue_name, + kernel_name=deploy_kernel, + ramdisk_name=deploy_ramdisk, + instance_boot_option=parsed_args.instance_boot_option, + **kwargs + ) + + nodes_uuids = [node['uuid'] for node in nodes] + + if parsed_args.introspect: + baremetal.introspect(self.app.client_manager, + node_uuids=nodes_uuids, + run_validations=parsed_args.run_validations, + queue_name=queue_name) + + if parsed_args.provide: + baremetal.provide(self.app.client_manager, + node_uuids=nodes_uuids, + queue_name=queue_name) diff --git a/tripleoclient/workflows/baremetal.py b/tripleoclient/workflows/baremetal.py index 4dfe664b5..27095351e 100644 --- a/tripleoclient/workflows/baremetal.py +++ b/tripleoclient/workflows/baremetal.py @@ -296,3 +296,34 @@ def create_raid_configuration(clients, **workflow_input): else: raise RuntimeError( 'Failed to create RAID: {}'.format(payload['message'])) + + +def discover_and_enroll(clients, **workflow_input): + """Discover nodes. + + Run the tripleo.baremetal.v1.discover_and_enroll_nodes Mistral workflow. + """ + + workflow_client = clients.workflow_engine + tripleoclients = clients.tripleoclient + queue_name = workflow_input['queue_name'] + + with tripleoclients.messaging_websocket(queue_name) as ws: + execution = base.start_workflow( + workflow_client, + 'tripleo.baremetal.v1.discover_and_enroll_nodes', + workflow_input=workflow_input + ) + + for payload in base.wait_for_messages(workflow_client, ws, execution): + if payload.get('message'): + print(payload['message']) + + if payload['status'] == 'SUCCESS': + registered_nodes = payload['registered_nodes'] + for nd in registered_nodes: + print('Successfully registered node UUID %s' % nd['uuid']) + return registered_nodes + else: + raise exceptions.RegisterOrUpdateError( + 'Exception discovering nodes: {}'.format(payload['message']))