Add 'overcloud node discover' command

This is the frontend for discover_and_enroll_nodes workflow.

Change-Id: I822ec89add3742020262c091e79ff94f92ef92e7
Depends-On: I158f0b8f5251d9d94e7e57b3fe24362316d26599
Implements: blueprint node-discovery-by-range
This commit is contained in:
Dmitry Tantsur 2017-06-07 15:13:09 +02:00
parent 933b4fa422
commit c86212cb76
5 changed files with 214 additions and 0 deletions

View File

@ -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.

View File

@ -76,6 +76,7 @@ openstack.tripleoclient.v1 =
overcloud_node_import = tripleoclient.v1.overcloud_node:ImportNode overcloud_node_import = tripleoclient.v1.overcloud_node:ImportNode
overcloud_node_introspect = tripleoclient.v1.overcloud_node:IntrospectNode overcloud_node_introspect = tripleoclient.v1.overcloud_node:IntrospectNode
overcloud_node_provide = tripleoclient.v1.overcloud_node:ProvideNode 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_parameters_set = tripleoclient.v1.overcloud_parameters:SetParameters
overcloud_plan_create = tripleoclient.v1.overcloud_plan:CreatePlan overcloud_plan_create = tripleoclient.v1.overcloud_plan:CreatePlan
overcloud_plan_delete = tripleoclient.v1.overcloud_plan:DeletePlan overcloud_plan_delete = tripleoclient.v1.overcloud_plan:DeletePlan

View File

@ -639,3 +639,100 @@ class TestConfigureNode(fakes.TestOvercloudNode):
'tripleo.baremetal.v1.configure', 'tripleo.baremetal.v1.configure',
workflow_input=self.workflow_input 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)

View File

@ -316,3 +316,83 @@ class ConfigureNode(command.Command):
overwrite_root_device_hints=( overwrite_root_device_hints=(
parsed_args.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='<ips>',
help=_('IP address(es) to probe'))
ip_group.add_argument('--range', dest='ip_addresses',
metavar='<range>', help=_('IP range to probe'))
parser.add_argument('--credentials', metavar='<key:value>',
action='append', required=True,
help=_('Key/value pairs of possible credentials'))
parser.add_argument('--port', action='append', metavar='<ports>',
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)

View File

@ -296,3 +296,34 @@ def create_raid_configuration(clients, **workflow_input):
else: else:
raise RuntimeError( raise RuntimeError(
'Failed to create RAID: {}'.format(payload['message'])) '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']))