Move baremetal provide commands from Ansible

Currently, we leverage Ansible to handle the
baremetal provide process. As part of our efforts
to support work on Tripleo.Next, these Ansible
workflows will need to be migrated to tripleoclient.

This change is consolodating Python methods from
tripleo-ansible into tripleoclient.

Change-Id: I1ff12506d4f6a7d5868aac7128b9f9dc3b8893ff
This commit is contained in:
Brendan Shephard 2022-01-06 01:49:35 +00:00
parent 2ac3953410
commit 3ac32b592c
11 changed files with 603 additions and 140 deletions

View File

@ -143,3 +143,10 @@ class HeatPodMessageQueueException(Base):
class InvalidPlaybook(Base): class InvalidPlaybook(Base):
"""Invalid playbook path specified""" """Invalid playbook path specified"""
class NoNodeFound(Base):
"""No nodes matching specifications found"""
def __init__(self):
message = "No nodes matching specifications could be found. "
super(NoNodeFound, self).__init__(message)

View File

@ -422,3 +422,24 @@ class FakeFlavor(object):
'capabilities:boot_option': 'local', 'capabilities:boot_option': 'local',
'capabilities:profile': self.profile 'capabilities:profile': self.profile
} }
class FakeMachine:
def __init__(self, id, name=None, driver=None, driver_info=None,
chassis_uuid=None, instance_info=None, instance_uuid=None,
properties=None, reservation=None, last_error=None,
provision_state='available', is_maintenance=False,
power_state='power off'):
self.id = id
self.name = name
self.driver = driver
self.driver_info = driver_info
self.chassis_uuid = chassis_uuid
self.instance_info = instance_info
self.instance_uuid = instance_uuid
self.properties = properties
self.reservation = reservation
self.last_error = last_error
self.provision_state = provision_state
self.is_maintenance = is_maintenance
self.power_state = power_state

View File

@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
# #
import uuid
from unittest import mock from unittest import mock
@ -35,3 +36,12 @@ class TestOvercloudNode(fakes.FakePlaybookExecution):
) )
self.mock_playbook.start() self.mock_playbook.start()
self.addCleanup(self.mock_playbook.stop) self.addCleanup(self.mock_playbook.stop)
def make_fake_machine(machine_name, provision_state='manageable',
is_maintenance=False, machine_id=None):
if not machine_id:
machine_id = uuid.uuid4().hex
return(fakes.FakeMachine(id=machine_id, name=machine_name,
provision_state=provision_state,
is_maintenance=is_maintenance))

View File

@ -21,6 +21,8 @@ import os
import tempfile import tempfile
from unittest import mock from unittest import mock
import openstack
from osc_lib import exceptions as oscexc from osc_lib import exceptions as oscexc
from osc_lib.tests import utils as test_utils from osc_lib.tests import utils as test_utils
import yaml import yaml
@ -375,40 +377,96 @@ class TestDeleteNode(fakes.TestDeleteNode):
mock_warning.assert_called_once_with(expected_message) mock_warning.assert_called_once_with(expected_message)
@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 TestProvideNode(fakes.TestOvercloudNode): class TestProvideNode(fakes.TestOvercloudNode):
def setUp(self): def setUp(self):
super(TestProvideNode, self).setUp() super(TestProvideNode, self).setUp()
# Get the command object to test # Get the command object to test
self.cmd = overcloud_node.ProvideNode(self.app, None) self.cmd = overcloud_node.ProvideNode(self.app, None)
def test_provide_all_manageable_nodes(self): iterate_timeout = mock.MagicMock()
iterate_timeout.start()
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_provide_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_node2])
]
mock_bm.baremetal.get_node.side_effect = [
self.fake_baremetal_node,
self.fake_baremetal_node2]
parsed_args = self.check_parser(self.cmd, parsed_args = self.check_parser(self.cmd,
['--all-manageable'], ['--all-manageable'],
[('all_manageable', True)]) [('all_manageable', True)])
self.cmd.take_action(parsed_args) self.cmd.take_action(parsed_args)
def test_provide_one_node(self): def test_provide_one_node(self, mock_conn,
mock_connect, mock_conf,
mock_bm):
node_id = 'node_uuid1' node_id = 'node_uuid1'
mock_conn.return_value = mock_bm
mock_bm.baremetal = mock_bm
mock_bm.baremetal.get_node.side_effect = [
self.fake_baremetal_node]
parsed_args = self.check_parser(self.cmd, parsed_args = self.check_parser(self.cmd,
[node_id], [node_id],
[('node_uuids', [node_id])]) [('node_uuids', [node_id])])
self.cmd.take_action(parsed_args) self.cmd.take_action(parsed_args)
def test_provide_multiple_nodes(self): def test_provide_multiple_nodes(self, mock_conn,
mock_connect, mock_conf,
mock_bm):
node_id1 = 'node_uuid1' node_id1 = 'node_uuid1'
node_id2 = 'node_uuid2' node_id2 = 'node_uuid2'
argslist = [node_id1, node_id2] argslist = [node_id1, node_id2]
verifylist = [('node_uuids', [node_id1, node_id2])] verifylist = [('node_uuids', [node_id1, node_id2])]
mock_conn.return_value = mock_bm
mock_bm.baremetal = mock_bm
mock_bm.baremetal.get_node.side_effect = [
self.fake_baremetal_node,
self.fake_baremetal_node2
]
parsed_args = self.check_parser(self.cmd, argslist, verifylist) parsed_args = self.check_parser(self.cmd, argslist, verifylist)
self.cmd.take_action(parsed_args) self.cmd.take_action(parsed_args)
@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 TestCleanNode(fakes.TestOvercloudNode): class TestCleanNode(fakes.TestOvercloudNode):
def setUp(self): def setUp(self):
@ -417,41 +475,102 @@ class TestCleanNode(fakes.TestOvercloudNode):
# Get the command object to test # Get the command object to test
self.cmd = overcloud_node.CleanNode(self.app, None) self.cmd = overcloud_node.CleanNode(self.app, None)
def _check_clean_all_manageable(self, parsed_args, provide=False): 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 _check_clean_all_manageable(self, parsed_args, mock_conn,
mock_connect, mock_conf,
mock_bm,
provide=False):
mock_bm.baremetal.nodes.side_effect = [
iter([self.fake_baremetal_node]),
iter([self.fake_baremetal_node])
]
mock_bm.baremetal.get_node.side_effect = [
self.fake_baremetal_node,
self.fake_baremetal_node]
self.cmd.take_action(parsed_args) self.cmd.take_action(parsed_args)
def _check_clean_nodes(self, parsed_args, nodes, provide=False): def _check_clean_nodes(self, parsed_args, nodes, mock_conn,
mock_connect, mock_conf,
mock_bm, provide=False):
self.cmd.take_action(parsed_args) self.cmd.take_action(parsed_args)
def test_clean_all_manageable_nodes_without_provide(self): def test_clean_all_manageable_nodes_without_provide(self, mock_conn,
mock_connect,
mock_conf,
mock_bm):
mock_conn.return_value = mock_bm
mock_bm.baremetal = mock_bm
mock_bm.baremetal.nodes.return_value = iter([
self.fake_baremetal_node
])
parsed_args = self.check_parser(self.cmd, parsed_args = self.check_parser(self.cmd,
['--all-manageable'], ['--all-manageable'],
[('all_manageable', True)]) [('all_manageable', True)])
self._check_clean_all_manageable(parsed_args, provide=False) self._check_clean_all_manageable(parsed_args, mock_conn,
mock_connect, mock_conf,
mock_bm, provide=False)
def test_clean_all_manageable_nodes_with_provide(self): def test_clean_all_manageable_nodes_with_provide(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])]
mock_bm.baremetal.get_node.side_effect = [
self.fake_baremetal_node,
self.fake_baremetal_node]
parsed_args = self.check_parser(self.cmd, parsed_args = self.check_parser(self.cmd,
['--all-manageable', '--provide'], ['--all-manageable', '--provide'],
[('all_manageable', True), [('all_manageable', True),
('provide', True)]) ('provide', True)])
self._check_clean_all_manageable(parsed_args, provide=True) self._check_clean_all_manageable(parsed_args, mock_conn,
mock_connect, mock_conf,
mock_bm, provide=False)
def test_clean_nodes_without_provide(self): def test_clean_nodes_without_provide(self, mock_conn,
mock_connect, mock_conf,
mock_bm):
mock_conn.return_value = mock_bm
mock_bm.baremetal = mock_bm
nodes = ['node_uuid1', 'node_uuid2'] nodes = ['node_uuid1', 'node_uuid2']
parsed_args = self.check_parser(self.cmd, parsed_args = self.check_parser(self.cmd,
nodes, nodes,
[('node_uuids', nodes)]) [('node_uuids', nodes)])
self._check_clean_nodes(parsed_args, nodes, provide=False) self._check_clean_nodes(parsed_args, nodes, mock_conn,
mock_connect, mock_conf,
mock_bm, provide=False)
def test_clean_nodes_with_provide(self, mock_conn,
mock_connect, mock_conf,
mock_bm):
mock_conn.return_value = mock_bm
mock_bm.baremetal = mock_bm
def test_clean_nodes_with_provide(self):
nodes = ['node_uuid1', 'node_uuid2'] nodes = ['node_uuid1', 'node_uuid2']
argslist = nodes + ['--provide'] argslist = nodes + ['--provide']
mock_bm.baremetal.get_node.side_effect = [
self.fake_baremetal_node,
self.fake_baremetal_node2
]
parsed_args = self.check_parser(self.cmd, parsed_args = self.check_parser(self.cmd,
argslist, argslist,
[('node_uuids', nodes), [('node_uuids', nodes),
('provide', True)]) ('provide', True)])
self._check_clean_nodes(parsed_args, nodes, provide=True) self._check_clean_nodes(parsed_args, nodes, mock_conn,
mock_connect, mock_conf,
mock_bm, provide=False)
class TestImportNodeMultiArch(fakes.TestOvercloudNode): class TestImportNodeMultiArch(fakes.TestOvercloudNode):
@ -673,6 +792,17 @@ class TestConfigureNode(fakes.TestOvercloudNode):
self.cmd.take_action(parsed_args) self.cmd.take_action(parsed_args)
@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)
@mock.patch('tripleo_common.utils.nodes._populate_node_mapping',
name='mock_nodemap')
@mock.patch('tripleo_common.utils.nodes.register_all_nodes',
name='mock_tcnode')
@mock.patch('oslo_concurrency.processutils.execute',
name="mock_subproc")
class TestDiscoverNode(fakes.TestOvercloudNode): class TestDiscoverNode(fakes.TestOvercloudNode):
def setUp(self): def setUp(self):
@ -688,8 +818,19 @@ class TestDiscoverNode(fakes.TestOvercloudNode):
self.addCleanup(self.gcn.stop) self.addCleanup(self.gcn.stop)
self.http_boot = '/var/lib/ironic/httpboot' self.http_boot = '/var/lib/ironic/httpboot'
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_with_ip_range(self): def test_with_ip_range(self, mock_subproc, mock_tcnode,
mock_nodemap, mock_conn,
mock_connect, mock_conf,
mock_bm):
argslist = ['--range', '10.0.0.0/24', argslist = ['--range', '10.0.0.0/24',
'--credentials', 'admin:password'] '--credentials', 'admin:password']
verifylist = [('ip_addresses', '10.0.0.0/24'), verifylist = [('ip_addresses', '10.0.0.0/24'),
@ -698,7 +839,10 @@ class TestDiscoverNode(fakes.TestOvercloudNode):
parsed_args = self.check_parser(self.cmd, argslist, verifylist) parsed_args = self.check_parser(self.cmd, argslist, verifylist)
self.cmd.take_action(parsed_args) self.cmd.take_action(parsed_args)
def test_with_address_list(self): def test_with_address_list(self, mock_subproc, mock_tcnode,
mock_nodemap, mock_conn,
mock_connect, mock_conf,
mock_bm):
argslist = ['--ip', '10.0.0.1', '--ip', '10.0.0.2', argslist = ['--ip', '10.0.0.1', '--ip', '10.0.0.2',
'--credentials', 'admin:password'] '--credentials', 'admin:password']
verifylist = [('ip_addresses', ['10.0.0.1', '10.0.0.2']), verifylist = [('ip_addresses', ['10.0.0.1', '10.0.0.2']),
@ -707,7 +851,18 @@ class TestDiscoverNode(fakes.TestOvercloudNode):
parsed_args = self.check_parser(self.cmd, argslist, verifylist) parsed_args = self.check_parser(self.cmd, argslist, verifylist)
self.cmd.take_action(parsed_args) self.cmd.take_action(parsed_args)
def test_with_all_options(self): def test_with_all_options(self, mock_subproc, mock_tcnode,
mock_nodemap, mock_conn,
mock_connect, mock_conf,
mock_bm):
mock_conn.return_value = mock_bm
mock_bm.baremetal = mock_bm
mock_bm.baremetal.get_node.side_effect = [
self.fake_baremetal_node,
self.fake_baremetal_node2,
self.fake_baremetal_node,
self.fake_baremetal_node2
]
argslist = ['--range', '10.0.0.0/24', argslist = ['--range', '10.0.0.0/24',
'--credentials', 'admin:password', '--credentials', 'admin:password',
'--credentials', 'admin2:password2', '--credentials', 'admin2:password2',

View File

@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
# #
import uuid
from tripleoclient.tests import fakes from tripleoclient.tests import fakes
@ -26,3 +27,12 @@ class TestOvercloudNode(fakes.FakePlaybookExecution):
def setUp(self): def setUp(self):
super(TestOvercloudNode, self).setUp() super(TestOvercloudNode, self).setUp()
def make_fake_machine(machine_name, provision_state,
is_maintenance, machine_id=None):
if not machine_id:
machine_id = uuid.uuid4().hex
return(fakes.FakeMachine(id=machine_id, name=machine_name,
provision_state=provision_state,
is_maintenance=is_maintenance))

View File

@ -20,13 +20,22 @@ import os
import tempfile import tempfile
from unittest import mock from unittest import mock
import openstack
from osc_lib.tests import utils as test_utils from osc_lib.tests import utils as test_utils
from tripleoclient import constants from tripleoclient import constants
from tripleoclient.tests.v2.overcloud_node import fakes from tripleoclient.tests.v2.overcloud_node import fakes
from tripleoclient.v2 import overcloud_node from tripleoclient.v2 import overcloud_node
from tripleoclient.workflows import tripleo_baremetal as tb
@mock.patch('tripleoclient.utils.run_ansible_playbook',
autospec=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 TestImportNode(fakes.TestOvercloudNode): class TestImportNode(fakes.TestOvercloudNode):
def setUp(self): def setUp(self):
@ -49,6 +58,18 @@ class TestImportNode(fakes.TestOvercloudNode):
] ]
}] }]
self.fake_baremetal_node = fakes.make_fake_machine(
machine_name='node1',
machine_id='4e540e11-1366-4b57-85d5-319d168d98a1',
provision_state='manageable',
is_maintenance=False
)
self.fake_baremetal_node2 = fakes.make_fake_machine(
machine_name='node2',
machine_id='9070e42d-1ad7-4bd0-b868-5418bc9c7176',
provision_state='manageable',
is_maintenance=False
)
self.json_file = tempfile.NamedTemporaryFile( self.json_file = tempfile.NamedTemporaryFile(
mode='w', delete=False, suffix='.json') mode='w', delete=False, suffix='.json')
json.dump(self.nodes_list, self.json_file) json.dump(self.nodes_list, self.json_file)
@ -72,18 +93,24 @@ class TestImportNode(fakes.TestOvercloudNode):
for i in ('agent.kernel', for i in ('agent.kernel',
'agent.ramdisk')])) 'agent.ramdisk')]))
@mock.patch('tripleoclient.utils.run_ansible_playbook', def test_import_only(self,
autospec=True) mock_conn,
def test_import_only(self, mock_playbook): mock_connect,
mock_conf,
mock_bm,
mock_playbook):
parsed_args = self.check_parser(self.cmd, parsed_args = self.check_parser(self.cmd,
[self.json_file.name], [self.json_file.name],
[('introspect', False), [('introspect', False),
('provide', False)]) ('provide', False)])
self.cmd.take_action(parsed_args) self.cmd.take_action(parsed_args)
@mock.patch('tripleoclient.utils.run_ansible_playbook', def test_import_and_introspect(self,
autospec=True) mock_conn,
def test_import_and_introspect(self, mock_playbook): mock_connect,
mock_conf,
mock_bm,
mock_playbook):
parsed_args = self.check_parser(self.cmd, parsed_args = self.check_parser(self.cmd,
[self.json_file.name, [self.json_file.name,
'--introspect'], '--introspect'],
@ -103,25 +130,45 @@ class TestImportNode(fakes.TestOvercloudNode):
} }
) )
@mock.patch('tripleoclient.utils.run_ansible_playbook', def test_import_and_provide(self,
autospec=True) mock_conn,
def test_import_and_provide(self, mock_playbook): mock_connect,
mock_conf,
mock_bm,
mock_playbook):
parsed_args = self.check_parser(self.cmd, parsed_args = self.check_parser(self.cmd,
[self.json_file.name, [self.json_file.name,
'--provide'], '--provide'],
[('introspect', False), [('introspect', False),
('provide', True)]) ('provide', True)])
mock_conn.return_value = mock_bm
mock_bm.baremetal = mock_bm
mock_bm.baremetal_introspection = mock_bm
mock_bm.baremetal.get_node.side_effect = [
self.fake_baremetal_node,
self.fake_baremetal_node2]
self.cmd.take_action(parsed_args) self.cmd.take_action(parsed_args)
@mock.patch('tripleoclient.utils.run_ansible_playbook', def test_import_and_introspect_and_provide(self,
autospec=True) mock_conn,
def test_import_and_introspect_and_provide(self, mock_playbook): mock_connect,
mock_conf,
mock_bm,
mock_playbook):
parsed_args = self.check_parser(self.cmd, parsed_args = self.check_parser(self.cmd,
[self.json_file.name, [self.json_file.name,
'--introspect', '--introspect',
'--provide'], '--provide'],
[('introspect', True), [('introspect', True),
('provide', True)]) ('provide', True)])
mock_conn.return_value = mock_bm
mock_bm.baremetal = mock_bm
mock_bm.baremetal_introspection = mock_bm
mock_bm.baremetal.get_node.side_effect = [
self.fake_baremetal_node,
self.fake_baremetal_node2]
self.cmd.take_action(parsed_args) self.cmd.take_action(parsed_args)
mock_playbook.assert_called_with( mock_playbook.assert_called_with(
workdir=mock.ANY, workdir=mock.ANY,
@ -130,22 +177,30 @@ class TestImportNode(fakes.TestOvercloudNode):
playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS, playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS,
verbosity=mock.ANY, verbosity=mock.ANY,
extra_vars={ extra_vars={
'node_uuids': ['MOCK_NODE_UUID'] 'node_uuids': ['MOCK_NODE_UUID'],
'run_validations': False,
'concurrency': 20
} }
) )
@mock.patch('tripleoclient.utils.run_ansible_playbook', def test_import_with_netboot(self,
autospec=True) mock_conn,
def test_import_with_netboot(self, mock_playbook): mock_connect,
mock_conf,
mock_bm,
mock_playbook):
parsed_args = self.check_parser(self.cmd, parsed_args = self.check_parser(self.cmd,
[self.json_file.name, [self.json_file.name,
'--no-deploy-image'], '--no-deploy-image'],
[('no_deploy_image', True)]) [('no_deploy_image', True)])
self.cmd.take_action(parsed_args) self.cmd.take_action(parsed_args)
@mock.patch('tripleoclient.utils.run_ansible_playbook', def test_import_with_no_deployed_image(self,
autospec=True) mock_conn,
def test_import_with_no_deployed_image(self, mock_playbook): mock_connect,
mock_conf,
mock_bm,
mock_playbook):
parsed_args = self.check_parser(self.cmd, parsed_args = self.check_parser(self.cmd,
[self.json_file.name, [self.json_file.name,
'--instance-boot-option', '--instance-boot-option',
@ -154,16 +209,37 @@ class TestImportNode(fakes.TestOvercloudNode):
self.cmd.take_action(parsed_args) self.cmd.take_action(parsed_args)
@mock.patch('tripleoclient.utils.run_ansible_playbook',
autospec=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 TestIntrospectNode(fakes.TestOvercloudNode): class TestIntrospectNode(fakes.TestOvercloudNode):
def setUp(self): def setUp(self):
super(TestIntrospectNode, self).setUp() super(TestIntrospectNode, self).setUp()
# Get the command object to test # Get the command object to test
self.cmd = overcloud_node.IntrospectNode(self.app, None) self.cmd = overcloud_node.IntrospectNode(self.app, None)
self.fake_baremetal_node = fakes.make_fake_machine(
machine_name='node1',
machine_id='4e540e11-1366-4b57-85d5-319d168d98a1',
provision_state='manageable',
is_maintenance=False
)
self.fake_baremetal_node2 = fakes.make_fake_machine(
machine_name='node2',
machine_id='9070e42d-1ad7-4bd0-b868-5418bc9c7176',
provision_state='manageable',
is_maintenance=False
)
@mock.patch('tripleoclient.utils.run_ansible_playbook',
autospec=True)
def test_introspect_all_manageable_nodes_without_provide(self, def test_introspect_all_manageable_nodes_without_provide(self,
mock_conn,
mock_connect,
mock_conf,
mock_bm,
mock_playbook): mock_playbook):
parsed_args = self.check_parser(self.cmd, parsed_args = self.check_parser(self.cmd,
['--all-manageable'], ['--all-manageable'],
@ -185,29 +261,54 @@ class TestIntrospectNode(fakes.TestOvercloudNode):
} }
) )
@mock.patch('tripleoclient.utils.run_ansible_playbook',
autospec=True)
def test_introspect_all_manageable_nodes_with_provide(self, def test_introspect_all_manageable_nodes_with_provide(self,
mock_conn,
mock_connect,
mock_conf,
mock_bm,
mock_playbook): mock_playbook):
parsed_args = self.check_parser(self.cmd, parsed_args = self.check_parser(self.cmd,
['--all-manageable', '--provide'], ['--all-manageable', '--provide'],
[('all_manageable', True), [('all_manageable', True),
('provide', True)]) ('provide', True)])
tb.TripleoProvide.provide = mock.MagicMock()
mock_conn.return_value = mock_bm
mock_bm.baremetal = mock_bm
mock_bm.baremetal.nodes.side_effect = [
iter([self.fake_baremetal_node,
self.fake_baremetal_node2]),
iter([self.fake_baremetal_node,
self.fake_baremetal_node2])
]
expected_nodes = ['4e540e11-1366-4b57-85d5-319d168d98a1',
'9070e42d-1ad7-4bd0-b868-5418bc9c7176']
self.cmd.take_action(parsed_args) self.cmd.take_action(parsed_args)
mock_playbook.assert_called_with( mock_playbook.assert_called_with(
workdir=mock.ANY, workdir=mock.ANY,
playbook='cli-overcloud-node-provide.yaml', playbook='cli-baremetal-introspect.yaml',
inventory=mock.ANY, inventory=mock.ANY,
playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS, playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS,
verbosity=mock.ANY, verbosity=mock.ANY,
extra_vars={ extra_vars={
'node_uuids': [] 'node_uuids': [],
'run_validations': False,
'concurrency': 20,
'node_timeout': 1200,
'max_retries': 1,
'retry_timeout': 120,
} }
) )
@mock.patch('tripleoclient.utils.run_ansible_playbook', tb.TripleoProvide.provide.assert_called_with(
autospec=True) expected_nodes)
def test_introspect_nodes_without_provide(self, mock_playbook):
def test_introspect_nodes_without_provide(self,
mock_conn,
mock_connect,
mock_conf,
mock_bm,
mock_playbook):
nodes = ['node_uuid1', 'node_uuid2'] nodes = ['node_uuid1', 'node_uuid2']
parsed_args = self.check_parser(self.cmd, parsed_args = self.check_parser(self.cmd,
nodes, nodes,
@ -229,37 +330,48 @@ class TestIntrospectNode(fakes.TestOvercloudNode):
} }
) )
@mock.patch('tripleoclient.utils.run_ansible_playbook', def test_introspect_nodes_with_provide(self,
autospec=True) mock_conn,
def test_introspect_nodes_with_provide(self, mock_playbook): mock_connect,
nodes = ['node_uuid1', 'node_uuid2'] mock_conf,
mock_bm,
mock_playbook):
nodes = ['node1', 'node2']
argslist = nodes + ['--provide'] argslist = nodes + ['--provide']
parsed_args = self.check_parser(self.cmd, parsed_args = self.check_parser(self.cmd,
argslist, argslist,
[('node_uuids', nodes), [('node_uuids', nodes),
('provide', True)]) ('provide', True)])
tb.TripleoProvide.provide = mock.MagicMock()
mock_conn.return_value = mock_bm
mock_bm.baremetal = mock_bm
mock_bm.baremetal_introspection = mock_bm
mock_bm.baremetal.get_node.side_effect = [
self.fake_baremetal_node,
self.fake_baremetal_node2]
self.cmd.take_action(parsed_args) self.cmd.take_action(parsed_args)
mock_playbook.assert_called_with(
workdir=mock.ANY, tb.TripleoProvide.provide.assert_called_with(
playbook='cli-overcloud-node-provide.yaml', nodes=nodes
inventory=mock.ANY,
playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS,
verbosity=mock.ANY,
extra_vars={
'node_uuids': nodes
}
) )
@mock.patch('tripleoclient.utils.run_ansible_playbook', def test_introspect_no_node_or_flag_specified(self,
autospec=True) mock_conn,
def test_introspect_no_node_or_flag_specified(self, mock_playbook): mock_connect,
mock_conf,
mock_bm,
mock_playbook):
self.assertRaises(test_utils.ParserException, self.assertRaises(test_utils.ParserException,
self.check_parser, self.check_parser,
self.cmd, [], []) self.cmd, [], [])
@mock.patch('tripleoclient.utils.run_ansible_playbook', def test_introspect_uuids_and_all_both_specified(self,
autospec=True) mock_conn,
def test_introspect_uuids_and_all_both_specified(self, mock_playbook): mock_connect,
mock_conf,
mock_bm,
mock_playbook):
argslist = ['node_id1', 'node_id2', '--all-manageable'] argslist = ['node_id1', 'node_id2', '--all-manageable']
verifylist = [('node_uuids', ['node_id1', 'node_id2']), verifylist = [('node_uuids', ['node_id1', 'node_id2']),
('all_manageable', True)] ('all_manageable', True)]
@ -267,7 +379,12 @@ class TestIntrospectNode(fakes.TestOvercloudNode):
self.check_parser, self.check_parser,
self.cmd, argslist, verifylist) self.cmd, argslist, verifylist)
def _check_introspect_all_manageable(self, parsed_args, provide=False): def _check_introspect_all_manageable(self, parsed_args,
mock_conn,
mock_connect,
mock_conf,
mock_bm,
mock_playbook, provide=False):
self.cmd.take_action(parsed_args) self.cmd.take_action(parsed_args)
call_list = [mock.call( call_list = [mock.call(
@ -285,7 +402,12 @@ class TestIntrospectNode(fakes.TestOvercloudNode):
self.assertEqual(self.workflow.executions.create.call_count, self.assertEqual(self.workflow.executions.create.call_count,
2 if provide else 1) 2 if provide else 1)
def _check_introspect_nodes(self, parsed_args, nodes, provide=False): def _check_introspect_nodes(self, parsed_args, nodes,
mock_conn,
mock_connect,
mock_conf,
mock_bm,
mock_playbook, provide=False):
self.cmd.take_action(parsed_args) self.cmd.take_action(parsed_args)
call_list = [mock.call( call_list = [mock.call(

View File

@ -96,9 +96,6 @@ class TestBaremetalWorkflows(fakes.FakePlaybookExecution):
instance_boot_option='local' instance_boot_option='local'
), [mock.ANY]) ), [mock.ANY])
def test_provide_success(self):
baremetal.provide(node_uuids=[])
def test_introspect_success(self): def test_introspect_success(self):
baremetal.introspect(self.app.client_manager, node_uuids=[], baremetal.introspect(self.app.client_manager, node_uuids=[],
run_validations=True, concurrency=20, run_validations=True, concurrency=20,
@ -111,11 +108,6 @@ class TestBaremetalWorkflows(fakes.FakePlaybookExecution):
node_timeout=1200, max_retries=1, retry_timeout=120, node_timeout=1200, max_retries=1, retry_timeout=120,
) )
def test_provide_manageable_nodes_success(self):
baremetal.provide_manageable_nodes(
self.app.client_manager
)
def test_configure_success(self): def test_configure_success(self):
baremetal.configure(self.app.client_manager, node_uuids=[]) baremetal.configure(self.app.client_manager, node_uuids=[])

View File

@ -34,6 +34,7 @@ from tripleoclient import command
from tripleoclient import constants from tripleoclient import constants
from tripleoclient import utils as oooutils from tripleoclient import utils as oooutils
from tripleoclient.workflows import baremetal from tripleoclient.workflows import baremetal
from tripleoclient.workflows import tripleo_baremetal as tb
class DeleteNode(command.Command): class DeleteNode(command.Command):
@ -214,19 +215,22 @@ class ProvideNode(command.Command):
action='store_true', action='store_true',
help=_("Provide all nodes currently in 'manageable'" help=_("Provide all nodes currently in 'manageable'"
" state")) " state"))
group.add_argument("--verbosity",
type=int,
default=1,
help=_("Print debug output during execution"))
return parser return parser
def take_action(self, parsed_args): def take_action(self, parsed_args):
self.log.debug("take_action(%s)" % parsed_args) self.log.debug("take_action(%s)" % parsed_args)
provide = tb.TripleoProvide(verbosity=parsed_args.verbosity)
if parsed_args.node_uuids: if parsed_args.node_uuids:
baremetal.provide(node_uuids=parsed_args.node_uuids, provide.provide(nodes=parsed_args.node_uuids)
verbosity=oooutils.playbook_verbosity(self))
else: else:
baremetal.provide_manageable_nodes( provide.provide_manageable_nodes()
self.app.client_manager,
verbosity=oooutils.playbook_verbosity(self))
class CleanNode(command.Command): class CleanNode(command.Command):
@ -247,6 +251,10 @@ class CleanNode(command.Command):
action='store_true', action='store_true',
help=_("Clean all nodes currently in 'manageable'" help=_("Clean all nodes currently in 'manageable'"
" state")) " state"))
group.add_argument("--verbosity",
type=int,
default=1,
help=_("Print debug output during execution"))
parser.add_argument('--provide', parser.add_argument('--provide',
action='store_true', action='store_true',
help=_('Provide (make available) the nodes once ' help=_('Provide (make available) the nodes once '
@ -270,11 +278,11 @@ class CleanNode(command.Command):
) )
if parsed_args.provide: if parsed_args.provide:
provide = tb.TripleoProvide(verbosity=parsed_args.verbosity)
if nodes: if nodes:
baremetal.provide(node_uuids=nodes, provide.provide(nodes=nodes)
verbosity=oooutils.playbook_verbosity(self))
else: else:
baremetal.provide_manageable_nodes(self.app.client_manager) provide.provide_manageable_nodes()
class ConfigureNode(command.Command): class ConfigureNode(command.Command):
@ -410,6 +418,10 @@ class DiscoverNode(command.Command):
default=120, default=120,
help=_('Maximum timeout between introspection' help=_('Maximum timeout between introspection'
'retries')) 'retries'))
parser.add_argument("--verbosity",
type=int,
default=1,
help=_("Print debug output during execution"))
return parser return parser
# FIXME(tonyb): This is not multi-arch safe :( # FIXME(tonyb): This is not multi-arch safe :(
@ -457,9 +469,8 @@ class DiscoverNode(command.Command):
) )
if parsed_args.provide: if parsed_args.provide:
baremetal.provide( provide = tb.TripleoProvide(verbosity=parsed_args.verbosity)
node_uuids=nodes_uuids, provide.provide(nodes=nodes_uuids)
verbosity=oooutils.playbook_verbosity(self))
class ExtractProvisionedNode(command.Command): class ExtractProvisionedNode(command.Command):

View File

@ -38,6 +38,7 @@ 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 ProvideNode # noqa from tripleoclient.v1.overcloud_node import ProvideNode # noqa
from tripleoclient.workflows import tripleo_baremetal as tb
class ImportNode(command.Command): class ImportNode(command.Command):
@ -87,6 +88,9 @@ class ImportNode(command.Command):
default=20, default=20,
help=_('Maximum number of nodes to introspect at ' help=_('Maximum number of nodes to introspect at '
'once.')) 'once.'))
parser.add_argument('--verbosity', type=int,
default=1,
help=_('Print debug logs during execution'))
parser.add_argument('env_file', type=argparse.FileType('r')) parser.add_argument('env_file', type=argparse.FileType('r'))
return parser return parser
@ -132,10 +136,8 @@ class ImportNode(command.Command):
) )
if parsed_args.provide: if parsed_args.provide:
baremetal.provide( provide = tb.TripleoProvide(verbosity=parsed_args.verbosity)
verbosity=oooutils.playbook_verbosity(self=self), provide.provide(nodes=nodes_uuids)
node_uuids=nodes_uuids
)
class IntrospectNode(command.Command): class IntrospectNode(command.Command):
@ -179,6 +181,9 @@ class IntrospectNode(command.Command):
default=120, default=120,
help=_('Maximum timeout between introspection' help=_('Maximum timeout between introspection'
'retries')) 'retries'))
parser.add_argument('--verbosity', type=int,
default=1,
help=_('Print debug logs during execution'))
return parser return parser
def take_action(self, parsed_args): def take_action(self, parsed_args):
@ -209,16 +214,13 @@ class IntrospectNode(command.Command):
# NOTE(cloudnull): This is using the old provide function, in a future # NOTE(cloudnull): This is using the old provide function, in a future
# release this may be ported to a standalone playbook # release this may be ported to a standalone playbook
if parsed_args.provide: if parsed_args.provide:
provide = tb.TripleoProvide(verbosity=parsed_args.verbosity)
if parsed_args.node_uuids: if parsed_args.node_uuids:
baremetal.provide( provide.provide(
node_uuids=parsed_args.node_uuids, nodes=parsed_args.node_uuids,
verbosity=oooutils.playbook_verbosity(self=self)
) )
else: else:
baremetal.provide_manageable_nodes( provide.provide_manageable_nodes()
clients=self.app.client_manager,
verbosity=oooutils.playbook_verbosity(self=self)
)
class ProvisionNode(command.Command): class ProvisionNode(command.Command):

View File

@ -105,50 +105,6 @@ def register_or_update(clients, nodes_json, kernel_name=None,
return registered_nodes return registered_nodes
def provide(node_uuids, verbosity=0):
"""Provide Baremetal Nodes
:param node_uuids: List of instance UUID(s).
:type node_uuids: List
:param verbosity: Verbosity level
:type verbosity: Integer
"""
with utils.TempDirs() as tmp:
utils.run_ansible_playbook(
playbook='cli-overcloud-node-provide.yaml',
inventory='localhost,',
workdir=tmp,
playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS,
verbosity=verbosity,
extra_vars={
'node_uuids': node_uuids
}
)
print('Successfully provided nodes: {}'.format(node_uuids))
def provide_manageable_nodes(clients, verbosity=0):
"""Provide all manageable Nodes
:param clients: Application client object.
:type clients: Object
:param verbosity: Verbosity level
:type verbosity: Integer
"""
provide(
node_uuids=[
i.uuid for i in clients.baremetal.node.list()
if i.provision_state == "manageable" and not i.maintenance
],
verbosity=verbosity
)
def introspect(clients, node_uuids, run_validations, concurrency, def introspect(clients, node_uuids, run_validations, concurrency,
node_timeout, max_retries, retry_timeout, verbosity=0): node_timeout, max_retries, retry_timeout, verbosity=0):
"""Introspect Baremetal Nodes """Introspect Baremetal Nodes

View File

@ -0,0 +1,177 @@
# -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
from openstack import connect as sdkclient
from openstack import exceptions
from openstack.utils import iterate_timeout
from tripleoclient import exceptions as ooo_exceptions
class TripleoBaremetal(object):
"""Base class for TripleO Baremetal operations.
The TripleoBase class provides access to commonly used elements
required to interact with and perform baremetal operations for TripleO.
:param timeout: How long to wait until we consider this job to have
timed out
:type timeout: integer
:param verbosity: How verbose should we be. Currently, this just sets
DEBUG for any non-zero value provided.
:type verbosity: integer
"""
def __init__(self, timeout: int = 1200, verbosity: int = 1):
self.conn = sdkclient(
cloud='undercloud'
)
self.timeout = timeout
self.log = logging.getLogger(__name__)
if verbosity > 0:
self.log.setLevel(logging.DEBUG)
def all_manageable_nodes(self):
"""This method returns a list of manageable nodes from Ironic
We take no arguments and instead create a list of nodes that
are in the manageable state and NOT in maintenenace. We return the
subsequent list.
Raises:
NoNodeFound: If no nodes match the above description, we will raise
an exception.
Returns:
nodes: The List of manageable nodes that are not currently in
maintenance.
"""
nodes = [n.id for n in self.conn.baremetal.nodes(
provision_state='manageable', is_maintenance=False)]
if not nodes:
raise ooo_exceptions.NoNodeFound
return nodes
class TripleoProvide(TripleoBaremetal):
"""TripleoProvide handles state transition of baremetal nodes.
The TripleoProvide class handles the transition of nodes between the
manageable and available states.
:param wait_for_bridge_mapping: Bool to determine whether or not we are
waiting for the bridge mapping to be
active in ironic-neutron-agent
:type wait_for_bridge_mapping: bool
"""
def __init__(self, wait_for_bridge_mappings: bool = False,
verbosity: int = 1):
super().__init__(verbosity)
self.wait_for_bridge_mappings = wait_for_bridge_mappings
def _wait_for_unlocked(self, node: str, timeout: int):
timeout_msg = f'Timeout waiting for node {node} to be unlocked'
for count in iterate_timeout(timeout, timeout_msg):
node_info = self.conn.baremetal.get_node(
node,
fields=['reservation']
)
if node_info.reservation is None:
return
def _wait_for_bridge_mapping(self, node: str):
client = self.conn.network
timeout_msg = (f'Timeout waiting for node {node} to have '
'bridge_mappings set in the ironic-neutron-agent '
'entry')
# default agent polling period is 30s, so wait 60s
timeout = 60
for count in iterate_timeout(timeout, timeout_msg):
agents = list(
client.agents(host=node, binary='ironic-neutron-agent'))
if agents:
if agents[0].configuration.get('bridge_mappings'):
return
def provide(self, nodes: str):
"""Transition nodes to the Available state.
provide handles the state transition from the nodes current state
to the available state
:param nodes: The node UUID or name that we will be working on
:type nodes: String
"""
client = self.conn.baremetal
node_timeout = self.timeout
nodes_wait = nodes[:]
for node in nodes:
self.log.info('Providing node: {}'.format(node))
self._wait_for_unlocked(node, node_timeout)
if self.wait_for_bridge_mappings:
self._wait_for_bridge_mapping(node)
try:
client.set_node_provision_state(
node,
"provide",
wait=False)
except Exception as e:
nodes_wait.remove(node)
self.log.error(
"Can not start providing for node {}: {}".format(
nodes, e))
return
try:
self.log.info(
"Waiting for available state: {}".format(nodes_wait))
client.wait_for_nodes_provision_state(
nodes=nodes_wait,
expected_state='available',
timeout=self.timeout,
fail=False
)
except exceptions.ResourceFailure as e:
self.log.error("Failed providing nodes due to failure: {}".format(
e))
return
except exceptions.ResourceTimeout as e:
self.log.error("Failed providing nodes due to timeout: {}".format(
e))
def provide_manageable_nodes(self):
self.provide(self.all_manageable_nodes())