diff --git a/setup.cfg b/setup.cfg index b84737e8b..c3499de9a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -74,6 +74,7 @@ openstack.tripleoclient.v1 = overcloud_node_clean = tripleoclient.v1.overcloud_node:CleanNode overcloud_node_bios_configure = tripleoclient.v1.overcloud_bios:ConfigureBIOS overcloud_node_bios_reset = tripleoclient.v1.overcloud_bios:ResetBIOS + overcloud_node_provision = tripleoclient.v1.overcloud_node:ProvisionNode 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 7cea86f5d..f169bc2a3 100644 --- a/tripleoclient/tests/v1/overcloud_node/test_overcloud_node.py +++ b/tripleoclient/tests/v1/overcloud_node/test_overcloud_node.py @@ -21,6 +21,7 @@ import os import tempfile from osc_lib.tests import utils as test_utils +import yaml from tripleoclient import exceptions from tripleoclient.tests.v1.overcloud_node import fakes @@ -1010,3 +1011,55 @@ class TestDiscoverNode(fakes.TestOvercloudNode): ) ] self.workflow.executions.create.assert_has_calls(workflows_calls) + + +class TestProvisionNode(fakes.TestOvercloudNode): + + def setUp(self): + super(TestProvisionNode, self).setUp() + + self.workflow = self.app.client_manager.workflow_engine + execution = mock.Mock() + execution.id = "IDID" + self.workflow.executions.create.return_value = execution + client = self.app.client_manager.tripleoclient + self.websocket = client.messaging_websocket() + self.websocket.wait_for_messages.return_value = [{ + "status": "SUCCESS", + "message": "Success", + "environment": {"cat": "meow"}, + "execution": {"id": "IDID"} + }] + + self.cmd = overcloud_node.ProvisionNode(self.app, None) + + def test_ok(self): + with tempfile.NamedTemporaryFile() as inp: + with tempfile.NamedTemporaryFile() as outp: + with tempfile.NamedTemporaryFile() as keyf: + inp.write(b'- name: Compute\n- name: Controller\n') + inp.flush() + keyf.write(b'I am a key') + keyf.flush() + + argslist = ['--output', outp.name, + '--overcloud-ssh-key', keyf.name, + inp.name] + verifylist = [('input', inp.name), + ('output', outp.name), + ('overcloud_ssh_key', keyf.name)] + + parsed_args = self.check_parser(self.cmd, + argslist, verifylist) + self.cmd.take_action(parsed_args) + + data = yaml.safe_load(outp) + self.assertEqual({"cat": "meow"}, data) + + self.workflow.executions.create.assert_called_once_with( + 'tripleo.baremetal_deploy.v1.deploy_roles', + workflow_input={'roles': [{'name': 'Compute'}, + {'name': 'Controller'}], + 'ssh_keys': ['I am a key'], + 'ssh_user_name': 'heat-admin'} + ) diff --git a/tripleoclient/v1/overcloud_node.py b/tripleoclient/v1/overcloud_node.py index e2674d854..457661071 100644 --- a/tripleoclient/v1/overcloud_node.py +++ b/tripleoclient/v1/overcloud_node.py @@ -15,9 +15,11 @@ import argparse import logging +import os from osc_lib.i18n import _ from osc_lib import utils +import yaml from tripleoclient import command from tripleoclient import constants @@ -447,3 +449,55 @@ class DiscoverNode(command.Command): baremetal.provide(self.app.client_manager, node_uuids=nodes_uuids ) + + +class ProvisionNode(command.Command): + """Provision a new node using Ironic.""" + + log = logging.getLogger(__name__ + ".DiscoverNode") + + def get_parser(self, prog_name): + parser = super(ProvisionNode, self).get_parser(prog_name) + parser.add_argument('input', + metavar='', + help=_('Configuration file describing the ' + 'baremetal deployment')) + parser.add_argument('-o', '--output', + default='baremetal_environment.yaml', + help=_('The output environment file path')) + parser.add_argument('--stack', dest='stack', + help=_('Name or ID of heat stack ' + '(default=Env: OVERCLOUD_STACK_NAME)'), + default=utils.env('OVERCLOUD_STACK_NAME', + default='overcloud')) + + parser.add_argument('--overcloud-ssh-user', + default='heat-admin', + help=_('User for SSH access to newly deployed ' + 'nodes')) + parser.add_argument('--overcloud-ssh-key', + default=os.path.join( + os.path.expanduser('~'), '.ssh', 'id_rsa.pub'), + help=_('Public key path for SSH access to newly ' + 'deployed nodes')) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + + with open(parsed_args.input, 'r') as fp: + roles = yaml.safe_load(fp) + + with open(parsed_args.overcloud_ssh_key, 'rt') as fp: + ssh_key = fp.read() + + output = baremetal.deploy_roles( + self.app.client_manager, + roles=roles, ssh_keys=[ssh_key], + ssh_user_name=parsed_args.overcloud_ssh_user) + + with open(parsed_args.output, 'w') as fp: + yaml.safe_dump(output['environment'], fp) + + print('Nodes deployed successfully, add %s to your deployment' % + parsed_args.output) diff --git a/tripleoclient/workflows/baremetal.py b/tripleoclient/workflows/baremetal.py index 899d356b2..d6068c2c1 100644 --- a/tripleoclient/workflows/baremetal.py +++ b/tripleoclient/workflows/baremetal.py @@ -510,3 +510,30 @@ def reset_bios_configuration_on_manageable_nodes(clients, **workflow_input): else: raise RuntimeError( 'Failed to reset BIOS settings: {}'.format(payload['message'])) + + +def deploy_roles(clients, **workflow_input): + """Deploy provided roles using Ironic. + + Run the tripleo.baremetal_deploy.v1.deploy_roles Mistral workflow. + """ + + workflow_client = clients.workflow_engine + tripleoclients = clients.tripleoclient + + with tripleoclients.messaging_websocket() as ws: + execution = base.start_workflow( + workflow_client, + 'tripleo.baremetal_deploy.v1.deploy_roles', + 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': + raise exceptions.NodeConfigurationError( + 'Error deploying nodes: {}'.format(payload['message'])) + + return payload