Merge "Start moving the import CLI command to the Ansible implementation"
This commit is contained in:
commit
ed75570496
@ -70,7 +70,7 @@ openstack.tripleoclient.v2 =
|
|||||||
overcloud_image_upload = tripleoclient.v1.overcloud_image:UploadOvercloudImage
|
overcloud_image_upload = tripleoclient.v1.overcloud_image:UploadOvercloudImage
|
||||||
overcloud_node_configure = tripleoclient.v1.overcloud_node:ConfigureNode
|
overcloud_node_configure = tripleoclient.v1.overcloud_node:ConfigureNode
|
||||||
overcloud_node_delete = tripleoclient.v1.overcloud_node:DeleteNode
|
overcloud_node_delete = tripleoclient.v1.overcloud_node:DeleteNode
|
||||||
overcloud_node_import = tripleoclient.v1.overcloud_node:ImportNode
|
overcloud_node_import = tripleoclient.v2.overcloud_node:ImportNode
|
||||||
overcloud_node_introspect = tripleoclient.v2.overcloud_node:IntrospectNode
|
overcloud_node_introspect = tripleoclient.v2.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_node_discover = tripleoclient.v1.overcloud_node:DiscoverNode
|
||||||
|
@ -13,7 +13,12 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
import collections
|
||||||
|
import fixtures
|
||||||
|
import json
|
||||||
import mock
|
import mock
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
|
||||||
from osc_lib.tests import utils as test_utils
|
from osc_lib.tests import utils as test_utils
|
||||||
|
|
||||||
@ -22,6 +27,151 @@ from tripleoclient.tests.v2.overcloud_node import fakes
|
|||||||
from tripleoclient.v2 import overcloud_node
|
from tripleoclient.v2 import overcloud_node
|
||||||
|
|
||||||
|
|
||||||
|
class TestImportNode(fakes.TestOvercloudNode):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestImportNode, self).setUp()
|
||||||
|
|
||||||
|
self.nodes_list = [{
|
||||||
|
"pm_user": "stack",
|
||||||
|
"pm_addr": "192.168.122.1",
|
||||||
|
"pm_password": "KEY1",
|
||||||
|
"pm_type": "pxe_ssh",
|
||||||
|
"mac": [
|
||||||
|
"00:0b:d0:69:7e:59"
|
||||||
|
],
|
||||||
|
}, {
|
||||||
|
"pm_user": "stack",
|
||||||
|
"pm_addr": "192.168.122.2",
|
||||||
|
"pm_password": "KEY2",
|
||||||
|
"pm_type": "pxe_ssh",
|
||||||
|
"mac": [
|
||||||
|
"00:0b:d0:69:7e:58"
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
|
||||||
|
# NOTE(cloudnull): Workflow and client calls are still mocked because
|
||||||
|
# mistal is still presnet here.
|
||||||
|
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",
|
||||||
|
"registered_nodes": [{
|
||||||
|
"uuid": "MOCK_NODE_UUID"
|
||||||
|
}],
|
||||||
|
"execution_id": execution.id
|
||||||
|
}]
|
||||||
|
|
||||||
|
self.json_file = tempfile.NamedTemporaryFile(
|
||||||
|
mode='w', delete=False, suffix='.json')
|
||||||
|
json.dump(self.nodes_list, self.json_file)
|
||||||
|
self.json_file.close()
|
||||||
|
self.addCleanup(os.unlink, self.json_file.name)
|
||||||
|
|
||||||
|
# Get the command object to test
|
||||||
|
self.cmd = overcloud_node.ImportNode(self.app, None)
|
||||||
|
|
||||||
|
image = collections.namedtuple('image', ['id', 'name'])
|
||||||
|
self.app.client_manager.image = mock.Mock()
|
||||||
|
self.app.client_manager.image.images.list.return_value = [
|
||||||
|
image(id=3, name='overcloud-full'),
|
||||||
|
]
|
||||||
|
|
||||||
|
self.http_boot = '/var/lib/ironic/httpboot'
|
||||||
|
|
||||||
|
self.useFixture(fixtures.MockPatch(
|
||||||
|
'os.path.exists', autospec=True,
|
||||||
|
side_effect=lambda path: path in [os.path.join(self.http_boot, i)
|
||||||
|
for i in ('agent.kernel',
|
||||||
|
'agent.ramdisk')]))
|
||||||
|
|
||||||
|
@mock.patch('tripleoclient.utils.run_ansible_playbook',
|
||||||
|
autospec=True)
|
||||||
|
def test_import_only(self, mock_playbook):
|
||||||
|
parsed_args = self.check_parser(self.cmd,
|
||||||
|
[self.json_file.name],
|
||||||
|
[('introspect', False),
|
||||||
|
('provide', False)])
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
@mock.patch('tripleoclient.utils.run_ansible_playbook',
|
||||||
|
autospec=True)
|
||||||
|
def test_import_and_introspect(self, mock_playbook):
|
||||||
|
parsed_args = self.check_parser(self.cmd,
|
||||||
|
[self.json_file.name,
|
||||||
|
'--introspect'],
|
||||||
|
[('introspect', True),
|
||||||
|
('provide', False)])
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
mock_playbook.assert_called_once_with(
|
||||||
|
workdir=mock.ANY,
|
||||||
|
playbook=mock.ANY,
|
||||||
|
inventory=mock.ANY,
|
||||||
|
playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS,
|
||||||
|
extra_vars={
|
||||||
|
'node_uuids': ['MOCK_NODE_UUID'],
|
||||||
|
'run_validations': False,
|
||||||
|
'concurrency': 20
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@mock.patch('tripleoclient.utils.run_ansible_playbook',
|
||||||
|
autospec=True)
|
||||||
|
def test_import_and_provide(self, mock_playbook):
|
||||||
|
parsed_args = self.check_parser(self.cmd,
|
||||||
|
[self.json_file.name,
|
||||||
|
'--provide'],
|
||||||
|
[('introspect', False),
|
||||||
|
('provide', True)])
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
@mock.patch('tripleoclient.utils.run_ansible_playbook',
|
||||||
|
autospec=True)
|
||||||
|
def test_import_and_introspect_and_provide(self, mock_playbook):
|
||||||
|
parsed_args = self.check_parser(self.cmd,
|
||||||
|
[self.json_file.name,
|
||||||
|
'--introspect',
|
||||||
|
'--provide'],
|
||||||
|
[('introspect', True),
|
||||||
|
('provide', True)])
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
mock_playbook.assert_called_once_with(
|
||||||
|
workdir=mock.ANY,
|
||||||
|
playbook=mock.ANY,
|
||||||
|
inventory=mock.ANY,
|
||||||
|
playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS,
|
||||||
|
extra_vars={
|
||||||
|
'node_uuids': ['MOCK_NODE_UUID'],
|
||||||
|
'run_validations': False,
|
||||||
|
'concurrency': 20
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@mock.patch('tripleoclient.utils.run_ansible_playbook',
|
||||||
|
autospec=True)
|
||||||
|
def test_import_with_netboot(self, mock_playbook):
|
||||||
|
parsed_args = self.check_parser(self.cmd,
|
||||||
|
[self.json_file.name,
|
||||||
|
'--no-deploy-image'],
|
||||||
|
[('no_deploy_image', True)])
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
@mock.patch('tripleoclient.utils.run_ansible_playbook',
|
||||||
|
autospec=True)
|
||||||
|
def test_import_with_no_deployed_image(self, mock_playbook):
|
||||||
|
parsed_args = self.check_parser(self.cmd,
|
||||||
|
[self.json_file.name,
|
||||||
|
'--instance-boot-option',
|
||||||
|
'netboot'],
|
||||||
|
[('instance_boot_option', 'netboot')])
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
|
||||||
class TestIntrospectNode(fakes.TestOvercloudNode):
|
class TestIntrospectNode(fakes.TestOvercloudNode):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -13,7 +13,9 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
from osc_lib.i18n import _
|
from osc_lib.i18n import _
|
||||||
|
|
||||||
@ -28,12 +30,103 @@ from tripleoclient.v1.overcloud_node import CleanNode # noqa
|
|||||||
from tripleoclient.v1.overcloud_node import ConfigureNode # noqa
|
from tripleoclient.v1.overcloud_node import ConfigureNode # noqa
|
||||||
from tripleoclient.v1.overcloud_node import DeleteNode # noqa
|
from tripleoclient.v1.overcloud_node import DeleteNode # noqa
|
||||||
from tripleoclient.v1.overcloud_node import DiscoverNode # noqa
|
from tripleoclient.v1.overcloud_node import DiscoverNode # noqa
|
||||||
from tripleoclient.v1.overcloud_node import ImportNode # noqa
|
|
||||||
from tripleoclient.v1.overcloud_node import ProvideNode # noqa
|
from tripleoclient.v1.overcloud_node import ProvideNode # noqa
|
||||||
from tripleoclient.v1.overcloud_node import ProvisionNode # noqa
|
from tripleoclient.v1.overcloud_node import ProvisionNode # noqa
|
||||||
from tripleoclient.v1.overcloud_node import UnprovisionNode # noqa
|
from tripleoclient.v1.overcloud_node import UnprovisionNode # noqa
|
||||||
|
|
||||||
|
|
||||||
|
class ImportNode(command.Command):
|
||||||
|
"""Import baremetal nodes from a JSON, YAML or CSV file.
|
||||||
|
|
||||||
|
The node status will be set to 'manageable' by default.
|
||||||
|
"""
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__ + ".ImportNode")
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(ImportNode, self).get_parser(prog_name)
|
||||||
|
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('--validate-only', action='store_true',
|
||||||
|
default=False,
|
||||||
|
help=_('Validate the env_file and then exit '
|
||||||
|
'without actually importing the nodes.'))
|
||||||
|
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=None,
|
||||||
|
help=_('Whether to set instances for booting from'
|
||||||
|
' local hard drive (local) or network '
|
||||||
|
' (netboot).'))
|
||||||
|
parser.add_argument("--http-boot",
|
||||||
|
default=os.environ.get(
|
||||||
|
'HTTP_BOOT',
|
||||||
|
constants.IRONIC_HTTP_BOOT_BIND_MOUNT),
|
||||||
|
help=_("Root directory for the "
|
||||||
|
" ironic-python-agent image"))
|
||||||
|
parser.add_argument('--concurrency', type=int,
|
||||||
|
default=20,
|
||||||
|
help=_('Maximum number of nodes to introspect at '
|
||||||
|
'once.'))
|
||||||
|
parser.add_argument('env_file', type=argparse.FileType('r'))
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
self.log.debug("take_action(%s)" % parsed_args)
|
||||||
|
|
||||||
|
nodes_config = oooutils.parse_env_file(parsed_args.env_file)
|
||||||
|
parsed_args.env_file.close()
|
||||||
|
|
||||||
|
if parsed_args.validate_only:
|
||||||
|
return baremetal.validate_nodes(self.app.client_manager,
|
||||||
|
nodes_json=nodes_config)
|
||||||
|
|
||||||
|
# Look for *specific* deploy images and update the node data if
|
||||||
|
# one is found.
|
||||||
|
if not parsed_args.no_deploy_image:
|
||||||
|
oooutils.update_nodes_deploy_data(nodes_config,
|
||||||
|
http_boot=parsed_args.http_boot)
|
||||||
|
nodes = baremetal.register_or_update(
|
||||||
|
self.app.client_manager,
|
||||||
|
nodes_json=nodes_config,
|
||||||
|
instance_boot_option=parsed_args.instance_boot_option
|
||||||
|
)
|
||||||
|
|
||||||
|
nodes_uuids = [node['uuid'] for node in nodes]
|
||||||
|
|
||||||
|
if parsed_args.introspect:
|
||||||
|
extra_vars = {
|
||||||
|
"node_uuids": nodes_uuids,
|
||||||
|
"run_validations": parsed_args.run_validations,
|
||||||
|
"concurrency": parsed_args.concurrency,
|
||||||
|
}
|
||||||
|
|
||||||
|
with oooutils.TempDirs() as tmp:
|
||||||
|
oooutils.run_ansible_playbook(
|
||||||
|
playbook='cli-baremetal-introspect.yaml',
|
||||||
|
inventory='localhost,',
|
||||||
|
workdir=tmp,
|
||||||
|
playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS,
|
||||||
|
extra_vars=extra_vars
|
||||||
|
)
|
||||||
|
|
||||||
|
if parsed_args.provide:
|
||||||
|
baremetal.provide(
|
||||||
|
self.app.client_manager,
|
||||||
|
node_uuids=nodes_uuids
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class IntrospectNode(command.Command):
|
class IntrospectNode(command.Command):
|
||||||
"""Introspect specified nodes or all nodes in 'manageable' state."""
|
"""Introspect specified nodes or all nodes in 'manageable' state."""
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user