Use Mistral for baremetal registration
Updates the baremetal registration workflows to use Mistral instead of python. Co-Authored-By: Dougal Matthews <dougal@redhat.com> Co-Authored-By: Ryan Brady <rbrady@redhat.com> Change-Id: Ide8b7753829170f503ef962b4ad4fde388cbb0ba Depends-On: Ifc6bdd273a8e129ea7c4269d00add64e72cd371b Depends-On: I910f50a377bcbc2c23b527953e9df7eee9c938a4
This commit is contained in:
parent
13c2afda86
commit
9244349742
@ -10,6 +10,7 @@ passlib>=1.6 # BSD
|
|||||||
python-ironic-inspector-client>=1.5.0 # Apache-2.0
|
python-ironic-inspector-client>=1.5.0 # Apache-2.0
|
||||||
python-heatclient>=1.1.0 # Apache-2.0
|
python-heatclient>=1.1.0 # Apache-2.0
|
||||||
python-ironicclient>=1.1.0 # Apache-2.0
|
python-ironicclient>=1.1.0 # Apache-2.0
|
||||||
|
python-mistralclient>=2.0.0 # Apache-2.0
|
||||||
python-openstackclient>=2.1.0 # Apache-2.0
|
python-openstackclient>=2.1.0 # Apache-2.0
|
||||||
six>=1.9.0 # MIT
|
six>=1.9.0 # MIT
|
||||||
os-cloud-config # Apache-2.0
|
os-cloud-config # Apache-2.0
|
||||||
|
@ -31,6 +31,11 @@ class UnknownService(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowServiceError(Exception):
|
||||||
|
"""The service type is unknown"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class NotFound(Exception):
|
class NotFound(Exception):
|
||||||
"""Resource not found"""
|
"""Resource not found"""
|
||||||
pass
|
pass
|
||||||
@ -54,6 +59,14 @@ class IntrospectionError(RuntimeError):
|
|||||||
"""Introspection failed"""
|
"""Introspection failed"""
|
||||||
|
|
||||||
|
|
||||||
|
class RegisterOrUpdateError(WorkflowServiceError):
|
||||||
|
"""Introspection failed"""
|
||||||
|
|
||||||
|
|
||||||
|
class NodeProvideError(WorkflowServiceError):
|
||||||
|
"""Node Provide failed."""
|
||||||
|
|
||||||
|
|
||||||
class StateTransitionFailed(Exception):
|
class StateTransitionFailed(Exception):
|
||||||
"""Ironic node state transition failed"""
|
"""Ironic node state transition failed"""
|
||||||
|
|
||||||
|
@ -34,6 +34,8 @@ class FakeApp(object):
|
|||||||
class FakeClientManager(object):
|
class FakeClientManager(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.identity = None
|
self.identity = None
|
||||||
|
self.workflow_engine = None
|
||||||
|
self.tripleoclient = None
|
||||||
self.auth_ref = None
|
self.auth_ref = None
|
||||||
self.tripleoclient = FakeClientWrapper()
|
self.tripleoclient = FakeClientWrapper()
|
||||||
|
|
||||||
|
@ -13,8 +13,9 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
import ironic_inspector_client
|
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
|
import ironic_inspector_client
|
||||||
from openstackclient.tests import utils
|
from openstackclient.tests import utils
|
||||||
|
|
||||||
|
|
||||||
@ -90,6 +91,19 @@ class FakeInspectorClient(object):
|
|||||||
return {uuid: self.states[uuid] for uuid in uuids}
|
return {uuid: self.states[uuid] for uuid in uuids}
|
||||||
|
|
||||||
|
|
||||||
|
class ClientWrapper(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._instance = None
|
||||||
|
self._mock_websocket = mock.Mock()
|
||||||
|
self._mock_websocket.__enter__ = mock.Mock(
|
||||||
|
return_value=self._mock_websocket)
|
||||||
|
self._mock_websocket.__exit__ = mock.Mock()
|
||||||
|
|
||||||
|
def messaging_websocket(self, queue_name='tripleo'):
|
||||||
|
return self._mock_websocket
|
||||||
|
|
||||||
|
|
||||||
class TestBaremetal(utils.TestCommand):
|
class TestBaremetal(utils.TestCommand):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@ -101,3 +115,10 @@ class TestBaremetal(utils.TestCommand):
|
|||||||
self.app.client_manager.baremetal_introspection = FakeInspectorClient()
|
self.app.client_manager.baremetal_introspection = FakeInspectorClient()
|
||||||
self.app.client_manager._region_name = "Arcadia"
|
self.app.client_manager._region_name = "Arcadia"
|
||||||
self.app.client_manager.session = mock.Mock()
|
self.app.client_manager.session = mock.Mock()
|
||||||
|
self.app.client_manager.workflow_engine = mock.Mock()
|
||||||
|
self.app.client_manager.tripleoclient = ClientWrapper()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
super(TestBaremetal, self).tearDown()
|
||||||
|
|
||||||
|
mock.patch.stopall()
|
||||||
|
@ -339,10 +339,24 @@ pxe_ssh,192.168.122.2,stack,"KEY2",00:0b:d0:69:7e:58""")
|
|||||||
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.mock_initial_nodes = {
|
self.mock_websocket_success = [{
|
||||||
mock.Mock(uuid="ABCDEFGH", provision_state="enroll"),
|
"status": "SUCCESS",
|
||||||
mock.Mock(uuid="IJKLMNOP", provision_state="enroll")
|
"registered_nodes": [{
|
||||||
}
|
"uuid": "MOCK_NODE_UUID"
|
||||||
|
}],
|
||||||
|
}, {
|
||||||
|
"status": "SUCCESS"
|
||||||
|
}]
|
||||||
|
|
||||||
|
self.workflow = self.app.client_manager.workflow_engine
|
||||||
|
tripleoclient = self.app.client_manager.tripleoclient
|
||||||
|
websocket = tripleoclient.messaging_websocket()
|
||||||
|
websocket.wait_for_message.side_effect = self.mock_websocket_success
|
||||||
|
self.websocket = websocket
|
||||||
|
|
||||||
|
uuid4_patcher = mock.patch('uuid.uuid4', return_value="UUID4")
|
||||||
|
self.mock_uuid4 = uuid4_patcher.start()
|
||||||
|
self.addCleanup(self.mock_uuid4.stop)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
|
|
||||||
@ -353,25 +367,38 @@ pxe_ssh,192.168.122.2,stack,"KEY2",00:0b:d0:69:7e:58""")
|
|||||||
os.unlink(self.yaml_file.name)
|
os.unlink(self.yaml_file.name)
|
||||||
os.unlink(self.instack_yaml.name)
|
os.unlink(self.instack_yaml.name)
|
||||||
|
|
||||||
def _check_register_call(self, mock_register_nodes, local=True, **kwargs):
|
def _check_workflow_call(self, local=True, provide=True,
|
||||||
|
kernel_name='bm-deploy-kernel',
|
||||||
|
ramdisk_name='bm-deploy-ramdisk'):
|
||||||
nodes_list = copy.deepcopy(self.nodes_list)
|
nodes_list = copy.deepcopy(self.nodes_list)
|
||||||
for node in nodes_list:
|
for node in nodes_list:
|
||||||
if local:
|
if local:
|
||||||
node['capabilities'] = 'boot_option:local'
|
node['capabilities'] = 'boot_option:local'
|
||||||
else:
|
else:
|
||||||
node['capabilities'] = 'boot_option:netboot'
|
node['capabilities'] = 'boot_option:netboot'
|
||||||
kwargs.setdefault('kernel_name', 'bm-deploy-kernel')
|
|
||||||
kwargs.setdefault('ramdisk_name', 'bm-deploy-ramdisk')
|
|
||||||
|
|
||||||
mock_register_nodes.assert_called_with(
|
call_list = [mock.call(
|
||||||
'http://localhost', nodes_list,
|
'tripleo.baremetal.v1.register_or_update', workflow_input={
|
||||||
client=self.app.client_manager.baremetal,
|
'kernel_name': kernel_name,
|
||||||
keystone_client=None,
|
'nodes_json': nodes_list,
|
||||||
glance_client=self.app.client_manager.image,
|
'queue_name': 'UUID4',
|
||||||
**kwargs)
|
'ramdisk_name': ramdisk_name}
|
||||||
|
)]
|
||||||
|
|
||||||
@mock.patch('tripleo_common.utils.nodes.register_all_nodes', autospec=True)
|
if provide:
|
||||||
def test_json_import(self, mock_register_nodes):
|
call_list.append(mock.call(
|
||||||
|
'tripleo.baremetal.v1.provide', workflow_input={
|
||||||
|
'node_uuids': ['MOCK_NODE_UUID', ],
|
||||||
|
'queue_name': 'UUID4'
|
||||||
|
}
|
||||||
|
))
|
||||||
|
|
||||||
|
self.workflow.executions.create.assert_has_calls(call_list)
|
||||||
|
|
||||||
|
self.assertEqual(self.workflow.executions.create.call_count,
|
||||||
|
2 if provide else 1)
|
||||||
|
|
||||||
|
def test_json_import(self):
|
||||||
|
|
||||||
arglist = [self.json_file.name, '--json', '-s', 'http://localhost']
|
arglist = [self.json_file.name, '--json', '-s', 'http://localhost']
|
||||||
|
|
||||||
@ -381,18 +408,12 @@ pxe_ssh,192.168.122.2,stack,"KEY2",00:0b:d0:69:7e:58""")
|
|||||||
]
|
]
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
mock_register_nodes.return_value = self.mock_initial_nodes
|
|
||||||
|
|
||||||
self.cmd.take_action(parsed_args)
|
self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
self._check_register_call(mock_register_nodes)
|
self._check_workflow_call()
|
||||||
self.assertEqual(sorted([
|
|
||||||
('ABCDEFGH', 'manage'), ('IJKLMNOP', 'manage'),
|
|
||||||
('ABCDEFGH', 'provide'), ('IJKLMNOP', 'provide')
|
|
||||||
]), sorted(self.baremetal.node.updates))
|
|
||||||
|
|
||||||
@mock.patch('tripleo_common.utils.nodes.register_all_nodes', autospec=True)
|
def test_json_import_initial_state_enroll(self):
|
||||||
def test_json_import_initial_state_enroll(self, mock_register_nodes):
|
|
||||||
|
|
||||||
arglist = [
|
arglist = [
|
||||||
self.json_file.name,
|
self.json_file.name,
|
||||||
@ -407,14 +428,12 @@ pxe_ssh,192.168.122.2,stack,"KEY2",00:0b:d0:69:7e:58""")
|
|||||||
]
|
]
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
mock_register_nodes.return_value = self.mock_initial_nodes
|
|
||||||
|
|
||||||
self.cmd.take_action(parsed_args)
|
self.cmd.take_action(parsed_args)
|
||||||
self._check_register_call(mock_register_nodes)
|
self._check_workflow_call(provide=False)
|
||||||
self.assertEqual([], self.baremetal.node.updates)
|
self.assertEqual([], self.baremetal.node.updates)
|
||||||
|
|
||||||
@mock.patch('tripleo_common.utils.nodes.register_all_nodes', autospec=True)
|
def test_available_does_not_require_api_1_11(self):
|
||||||
def test_available_does_not_require_api_1_11(self, mock_register_nodes):
|
|
||||||
arglist = [self.json_file.name, '--json', '-s', 'http://localhost']
|
arglist = [self.json_file.name, '--json', '-s', 'http://localhost']
|
||||||
|
|
||||||
verifylist = [
|
verifylist = [
|
||||||
@ -423,15 +442,9 @@ pxe_ssh,192.168.122.2,stack,"KEY2",00:0b:d0:69:7e:58""")
|
|||||||
]
|
]
|
||||||
self.baremetal.http_client.os_ironic_api_version = '1.6'
|
self.baremetal.http_client.os_ironic_api_version = '1.6'
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
mock_register_nodes.return_value = self.mock_initial_nodes
|
|
||||||
|
|
||||||
self.cmd.take_action(parsed_args)
|
self.cmd.take_action(parsed_args)
|
||||||
self._check_register_call(mock_register_nodes)
|
self._check_workflow_call()
|
||||||
|
|
||||||
self.assertEqual(sorted([
|
|
||||||
('ABCDEFGH', 'manage'), ('IJKLMNOP', 'manage'),
|
|
||||||
('ABCDEFGH', 'provide'), ('IJKLMNOP', 'provide')
|
|
||||||
]), sorted(self.baremetal.node.updates))
|
|
||||||
|
|
||||||
def test_enroll_requires_api_1_11(self):
|
def test_enroll_requires_api_1_11(self):
|
||||||
arglist = [
|
arglist = [
|
||||||
@ -450,9 +463,9 @@ pxe_ssh,192.168.122.2,stack,"KEY2",00:0b:d0:69:7e:58""")
|
|||||||
self.assertRaisesRegexp(exceptions.InvalidConfiguration,
|
self.assertRaisesRegexp(exceptions.InvalidConfiguration,
|
||||||
'OS_BAREMETAL_API_VERSION',
|
'OS_BAREMETAL_API_VERSION',
|
||||||
self.cmd.take_action, parsed_args)
|
self.cmd.take_action, parsed_args)
|
||||||
|
self.workflow.executions.create.assert_not_called()
|
||||||
|
|
||||||
@mock.patch('tripleo_common.utils.nodes.register_all_nodes', autospec=True)
|
def test_json_import_detect_suffix(self):
|
||||||
def test_json_import_detect_suffix(self, mock_register_nodes):
|
|
||||||
|
|
||||||
arglist = [self.json_file.name, '-s', 'http://localhost']
|
arglist = [self.json_file.name, '-s', 'http://localhost']
|
||||||
|
|
||||||
@ -465,10 +478,9 @@ pxe_ssh,192.168.122.2,stack,"KEY2",00:0b:d0:69:7e:58""")
|
|||||||
|
|
||||||
self.cmd.take_action(parsed_args)
|
self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
self._check_register_call(mock_register_nodes)
|
self._check_workflow_call()
|
||||||
|
|
||||||
@mock.patch('tripleo_common.utils.nodes.register_all_nodes', autospec=True)
|
def test_instack_json_import(self):
|
||||||
def test_instack_json_import(self, mock_register_nodes):
|
|
||||||
|
|
||||||
arglist = [self.instack_json.name, '--json', '-s', 'http://localhost']
|
arglist = [self.instack_json.name, '--json', '-s', 'http://localhost']
|
||||||
|
|
||||||
@ -478,18 +490,12 @@ pxe_ssh,192.168.122.2,stack,"KEY2",00:0b:d0:69:7e:58""")
|
|||||||
]
|
]
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
mock_register_nodes.return_value = self.mock_initial_nodes
|
|
||||||
|
|
||||||
self.cmd.take_action(parsed_args)
|
self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
self._check_register_call(mock_register_nodes)
|
self._check_workflow_call()
|
||||||
self.assertEqual(sorted([
|
|
||||||
('ABCDEFGH', 'manage'), ('IJKLMNOP', 'manage'),
|
|
||||||
('ABCDEFGH', 'provide'), ('IJKLMNOP', 'provide')
|
|
||||||
]), sorted(self.baremetal.node.updates))
|
|
||||||
|
|
||||||
@mock.patch('tripleo_common.utils.nodes.register_all_nodes', autospec=True)
|
def test_csv_import(self):
|
||||||
def test_csv_import(self, mock_register_nodes):
|
|
||||||
|
|
||||||
arglist = [self.csv_file.name, '--csv', '-s', 'http://localhost']
|
arglist = [self.csv_file.name, '--csv', '-s', 'http://localhost']
|
||||||
|
|
||||||
@ -499,14 +505,12 @@ pxe_ssh,192.168.122.2,stack,"KEY2",00:0b:d0:69:7e:58""")
|
|||||||
]
|
]
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
mock_register_nodes.return_value = self.mock_initial_nodes
|
|
||||||
|
|
||||||
self.cmd.take_action(parsed_args)
|
self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
self._check_register_call(mock_register_nodes)
|
self._check_workflow_call()
|
||||||
|
|
||||||
@mock.patch('tripleo_common.utils.nodes.register_all_nodes', autospec=True)
|
def test_csv_import_detect_suffix(self):
|
||||||
def test_csv_import_detect_suffix(self, mock_register_nodes):
|
|
||||||
|
|
||||||
arglist = [self.csv_file.name, '-s', 'http://localhost']
|
arglist = [self.csv_file.name, '-s', 'http://localhost']
|
||||||
|
|
||||||
@ -519,10 +523,9 @@ pxe_ssh,192.168.122.2,stack,"KEY2",00:0b:d0:69:7e:58""")
|
|||||||
|
|
||||||
self.cmd.take_action(parsed_args)
|
self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
self._check_register_call(mock_register_nodes)
|
self._check_workflow_call()
|
||||||
|
|
||||||
@mock.patch('tripleo_common.utils.nodes.register_all_nodes', autospec=True)
|
def test_yaml_import(self):
|
||||||
def test_yaml_import(self, mock_register_nodes):
|
|
||||||
|
|
||||||
arglist = [self.yaml_file.name, '-s', 'http://localhost']
|
arglist = [self.yaml_file.name, '-s', 'http://localhost']
|
||||||
|
|
||||||
@ -535,7 +538,7 @@ pxe_ssh,192.168.122.2,stack,"KEY2",00:0b:d0:69:7e:58""")
|
|||||||
|
|
||||||
self.cmd.take_action(parsed_args)
|
self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
self._check_register_call(mock_register_nodes)
|
self._check_workflow_call()
|
||||||
|
|
||||||
def test_invalid_import_filetype(self):
|
def test_invalid_import_filetype(self):
|
||||||
|
|
||||||
@ -552,8 +555,7 @@ pxe_ssh,192.168.122.2,stack,"KEY2",00:0b:d0:69:7e:58""")
|
|||||||
'Invalid file extension',
|
'Invalid file extension',
|
||||||
self.cmd.take_action, parsed_args)
|
self.cmd.take_action, parsed_args)
|
||||||
|
|
||||||
@mock.patch('tripleo_common.utils.nodes.register_all_nodes', autospec=True)
|
def test_instack_yaml_import(self):
|
||||||
def test_instack_yaml_import(self, mock_register_nodes):
|
|
||||||
|
|
||||||
arglist = [self.instack_yaml.name, '-s', 'http://localhost']
|
arglist = [self.instack_yaml.name, '-s', 'http://localhost']
|
||||||
|
|
||||||
@ -563,18 +565,12 @@ pxe_ssh,192.168.122.2,stack,"KEY2",00:0b:d0:69:7e:58""")
|
|||||||
]
|
]
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
mock_register_nodes.return_value = self.mock_initial_nodes
|
|
||||||
|
|
||||||
self.cmd.take_action(parsed_args)
|
self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
self._check_register_call(mock_register_nodes)
|
self._check_workflow_call()
|
||||||
self.assertEqual(sorted([
|
|
||||||
('ABCDEFGH', 'manage'), ('IJKLMNOP', 'manage'),
|
|
||||||
('ABCDEFGH', 'provide'), ('IJKLMNOP', 'provide')
|
|
||||||
]), sorted(self.baremetal.node.updates))
|
|
||||||
|
|
||||||
@mock.patch('tripleo_common.utils.nodes.register_all_nodes', autospec=True)
|
def test_netboot(self):
|
||||||
def test_netboot(self, mock_register_nodes):
|
|
||||||
|
|
||||||
arglist = [self.json_file.name, '-s', 'http://localhost',
|
arglist = [self.json_file.name, '-s', 'http://localhost',
|
||||||
'--instance-boot-option', 'netboot']
|
'--instance-boot-option', 'netboot']
|
||||||
@ -587,10 +583,9 @@ pxe_ssh,192.168.122.2,stack,"KEY2",00:0b:d0:69:7e:58""")
|
|||||||
|
|
||||||
self.cmd.take_action(parsed_args)
|
self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
self._check_register_call(mock_register_nodes, local=False)
|
self._check_workflow_call(local=False)
|
||||||
|
|
||||||
@mock.patch('tripleo_common.utils.nodes.register_all_nodes', autospec=True)
|
def test_custom_image(self):
|
||||||
def test_custom_image(self, mock_register_nodes):
|
|
||||||
|
|
||||||
arglist = [self.json_file.name, '-s', 'http://localhost',
|
arglist = [self.json_file.name, '-s', 'http://localhost',
|
||||||
'--deploy-kernel', 'k', '--deploy-ramdisk', 'r']
|
'--deploy-kernel', 'k', '--deploy-ramdisk', 'r']
|
||||||
@ -604,11 +599,9 @@ pxe_ssh,192.168.122.2,stack,"KEY2",00:0b:d0:69:7e:58""")
|
|||||||
|
|
||||||
self.cmd.take_action(parsed_args)
|
self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
self._check_register_call(mock_register_nodes, kernel_name='k',
|
self._check_workflow_call(kernel_name='k', ramdisk_name='r')
|
||||||
ramdisk_name='r')
|
|
||||||
|
|
||||||
@mock.patch('tripleo_common.utils.nodes.register_all_nodes', autospec=True)
|
def test_no_image(self):
|
||||||
def test_no_image(self, mock_register_nodes):
|
|
||||||
|
|
||||||
arglist = [self.json_file.name, '-s', 'http://localhost',
|
arglist = [self.json_file.name, '-s', 'http://localhost',
|
||||||
'--no-deploy-image']
|
'--no-deploy-image']
|
||||||
@ -621,8 +614,7 @@ pxe_ssh,192.168.122.2,stack,"KEY2",00:0b:d0:69:7e:58""")
|
|||||||
|
|
||||||
self.cmd.take_action(parsed_args)
|
self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
self._check_register_call(mock_register_nodes, kernel_name=None,
|
self._check_workflow_call(kernel_name=None, ramdisk_name=None)
|
||||||
ramdisk_name=None)
|
|
||||||
|
|
||||||
|
|
||||||
class TestStartBaremetalIntrospectionBulk(fakes.TestBaremetal):
|
class TestStartBaremetalIntrospectionBulk(fakes.TestBaremetal):
|
||||||
|
0
tripleoclient/tests/workflows/__init__.py
Normal file
0
tripleoclient/tests/workflows/__init__.py
Normal file
121
tripleoclient/tests/workflows/test_baremetal.py
Normal file
121
tripleoclient/tests/workflows/test_baremetal.py
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
# -*- 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 mock
|
||||||
|
|
||||||
|
from openstackclient.tests import utils
|
||||||
|
|
||||||
|
from tripleoclient import exceptions
|
||||||
|
from tripleoclient.workflows import baremetal
|
||||||
|
|
||||||
|
|
||||||
|
class TestBaremetalWorkflows(utils.TestCommand):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestBaremetalWorkflows, self).setUp()
|
||||||
|
|
||||||
|
self.app.client_manager.workflow_engine = self.workflow = mock.Mock()
|
||||||
|
self.tripleoclient = mock.Mock()
|
||||||
|
self.websocket = mock.Mock()
|
||||||
|
self.websocket.__enter__ = lambda s: self.websocket
|
||||||
|
self.websocket.__exit__ = lambda s, *exc: None
|
||||||
|
self.tripleoclient.messaging_websocket.return_value = self.websocket
|
||||||
|
self.app.client_manager.tripleoclient = self.tripleoclient
|
||||||
|
|
||||||
|
def test_register_or_update_success(self):
|
||||||
|
|
||||||
|
self.websocket.wait_for_message.return_value = {
|
||||||
|
"status": "SUCCESS",
|
||||||
|
"registered_nodes": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assertEqual(baremetal.register_or_update(
|
||||||
|
self.app.client_manager,
|
||||||
|
nodes_json=[],
|
||||||
|
queue_name="QUEUE_NAME",
|
||||||
|
kernel_name="kernel",
|
||||||
|
ramdisk_name="ramdisk"
|
||||||
|
), [])
|
||||||
|
|
||||||
|
self.workflow.executions.create.assert_called_once_with(
|
||||||
|
'tripleo.baremetal.v1.register_or_update',
|
||||||
|
workflow_input={
|
||||||
|
'kernel_name': 'kernel',
|
||||||
|
'queue_name': 'QUEUE_NAME',
|
||||||
|
'nodes_json': [],
|
||||||
|
'ramdisk_name': 'ramdisk'
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_register_or_update_error(self):
|
||||||
|
|
||||||
|
self.websocket.wait_for_message.return_value = {
|
||||||
|
"status": "FAIL",
|
||||||
|
"message": "FAILED",
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
exceptions.RegisterOrUpdateError,
|
||||||
|
baremetal.register_or_update,
|
||||||
|
self.app.client_manager,
|
||||||
|
nodes_json=[],
|
||||||
|
queue_name="QUEUE_NAME",
|
||||||
|
kernel_name="kernel",
|
||||||
|
ramdisk_name="ramdisk"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.workflow.executions.create.assert_called_once_with(
|
||||||
|
'tripleo.baremetal.v1.register_or_update',
|
||||||
|
workflow_input={
|
||||||
|
'kernel_name': 'kernel',
|
||||||
|
'queue_name': 'QUEUE_NAME',
|
||||||
|
'nodes_json': [],
|
||||||
|
'ramdisk_name': 'ramdisk'
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_provide_success(self):
|
||||||
|
|
||||||
|
self.websocket.wait_for_message.return_value = {
|
||||||
|
"status": "SUCCESS",
|
||||||
|
}
|
||||||
|
|
||||||
|
baremetal.provide(self.app.client_manager, node_uuids=[],
|
||||||
|
queue_name="QUEUE_NAME")
|
||||||
|
|
||||||
|
self.workflow.executions.create.assert_called_once_with(
|
||||||
|
'tripleo.baremetal.v1.provide',
|
||||||
|
workflow_input={
|
||||||
|
'node_uuids': [],
|
||||||
|
'queue_name': "QUEUE_NAME"
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_provide_error(self):
|
||||||
|
|
||||||
|
self.websocket.wait_for_message.return_value = {
|
||||||
|
"status": "FAIL",
|
||||||
|
"message": "Failed"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
exceptions.NodeProvideError,
|
||||||
|
baremetal.provide,
|
||||||
|
self.app.client_manager,
|
||||||
|
node_uuids=[],
|
||||||
|
queue_name="QUEUE_NAME")
|
||||||
|
|
||||||
|
self.workflow.executions.create.assert_called_once_with(
|
||||||
|
'tripleo.baremetal.v1.provide',
|
||||||
|
workflow_input={
|
||||||
|
'node_uuids': [],
|
||||||
|
'queue_name': "QUEUE_NAME"
|
||||||
|
})
|
@ -20,6 +20,7 @@ import csv
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
|
import uuid
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from cliff import command
|
from cliff import command
|
||||||
@ -28,10 +29,10 @@ import ironic_inspector_client
|
|||||||
from openstackclient.common import utils as osc_utils
|
from openstackclient.common import utils as osc_utils
|
||||||
from openstackclient.i18n import _
|
from openstackclient.i18n import _
|
||||||
from oslo_utils import units
|
from oslo_utils import units
|
||||||
from tripleo_common.utils import nodes
|
|
||||||
|
|
||||||
from tripleoclient import exceptions
|
from tripleoclient import exceptions
|
||||||
from tripleoclient import utils
|
from tripleoclient import utils
|
||||||
|
from tripleoclient.workflows import baremetal
|
||||||
|
|
||||||
|
|
||||||
def _csv_to_nodes_dict(nodes_csv):
|
def _csv_to_nodes_dict(nodes_csv):
|
||||||
@ -152,8 +153,7 @@ class ImportBaremetal(command.Command):
|
|||||||
def get_parser(self, prog_name):
|
def get_parser(self, prog_name):
|
||||||
parser = super(ImportBaremetal, self).get_parser(prog_name)
|
parser = super(ImportBaremetal, self).get_parser(prog_name)
|
||||||
parser.add_argument('-s', '--service-host', dest='service_host',
|
parser.add_argument('-s', '--service-host', dest='service_host',
|
||||||
help=_('Nova compute service host to register '
|
help=_('Deprecated, this argument has no impact.'))
|
||||||
'nodes with'))
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--json', dest='json', action='store_true',
|
'--json', dest='json', action='store_true',
|
||||||
help=_('Deprecated, now detected via file extension.'))
|
help=_('Deprecated, now detected via file extension.'))
|
||||||
@ -210,34 +210,34 @@ class ImportBaremetal(command.Command):
|
|||||||
_("OS_BAREMETAL_API_VERSION must be >=1.11 for use of "
|
_("OS_BAREMETAL_API_VERSION must be >=1.11 for use of "
|
||||||
"'enroll' provision state; currently %s") % api_version)
|
"'enroll' provision state; currently %s") % api_version)
|
||||||
|
|
||||||
|
# NOTE (dprince) move this to tripleo-common?
|
||||||
for node in nodes_config:
|
for node in nodes_config:
|
||||||
caps = utils.capabilities_to_dict(node.get('capabilities', {}))
|
caps = utils.capabilities_to_dict(node.get('capabilities', {}))
|
||||||
caps.setdefault('boot_option', parsed_args.instance_boot_option)
|
caps.setdefault('boot_option', parsed_args.instance_boot_option)
|
||||||
node['capabilities'] = utils.dict_to_capabilities(caps)
|
node['capabilities'] = utils.dict_to_capabilities(caps)
|
||||||
|
|
||||||
new_nodes = nodes.register_all_nodes(
|
queue_name = str(uuid.uuid4())
|
||||||
parsed_args.service_host,
|
|
||||||
nodes_config,
|
if parsed_args.no_deploy_image:
|
||||||
client=client,
|
deploy_kernel = None
|
||||||
keystone_client=self.app.client_manager.identity,
|
deploy_ramdisk = None
|
||||||
glance_client=self.app.client_manager.image,
|
else:
|
||||||
kernel_name=(parsed_args.deploy_kernel if not
|
deploy_kernel = parsed_args.deploy_kernel
|
||||||
parsed_args.no_deploy_image else None),
|
deploy_ramdisk = parsed_args.deploy_ramdisk
|
||||||
ramdisk_name=(parsed_args.deploy_ramdisk if not
|
|
||||||
parsed_args.no_deploy_image else None))
|
nodes = baremetal.register_or_update(
|
||||||
|
self.app.client_manager,
|
||||||
|
nodes_json=nodes_config,
|
||||||
|
queue_name=queue_name,
|
||||||
|
kernel_name=deploy_kernel,
|
||||||
|
ramdisk_name=deploy_ramdisk
|
||||||
|
)
|
||||||
|
|
||||||
|
node_uuids = [node['uuid'] for node in nodes]
|
||||||
|
|
||||||
if parsed_args.initial_state == "available":
|
if parsed_args.initial_state == "available":
|
||||||
manageable_node_uuids = list(utils.set_nodes_state(
|
baremetal.provide(self.app.client_manager, node_uuids=node_uuids,
|
||||||
client, new_nodes, "manage", "manageable",
|
queue_name=queue_name)
|
||||||
skipped_states={'manageable', 'available'}
|
|
||||||
))
|
|
||||||
manageable_nodes = [
|
|
||||||
n for n in new_nodes if n.uuid in manageable_node_uuids
|
|
||||||
]
|
|
||||||
list(utils.set_nodes_state(
|
|
||||||
client, manageable_nodes, "provide", "available",
|
|
||||||
skipped_states={'available'}
|
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
class StartBaremetalIntrospectionBulk(command.Command):
|
class StartBaremetalIntrospectionBulk(command.Command):
|
||||||
@ -257,9 +257,9 @@ class StartBaremetalIntrospectionBulk(command.Command):
|
|||||||
self.log.debug("Moving available/enroll nodes to manageable state.")
|
self.log.debug("Moving available/enroll nodes to manageable state.")
|
||||||
available_nodes = utils.nodes_in_states(client,
|
available_nodes = utils.nodes_in_states(client,
|
||||||
("available", "enroll"))
|
("available", "enroll"))
|
||||||
for uuid in utils.set_nodes_state(client, available_nodes, 'manage',
|
for uu in utils.set_nodes_state(client, available_nodes, 'manage',
|
||||||
'manageable'):
|
'manageable'):
|
||||||
self.log.debug("Node {0} has been set to manageable.".format(uuid))
|
self.log.debug("Node {0} has been set to manageable.".format(uu))
|
||||||
|
|
||||||
manageable_nodes = utils.nodes_in_states(client, ("manageable",))
|
manageable_nodes = utils.nodes_in_states(client, ("manageable",))
|
||||||
for node in manageable_nodes:
|
for node in manageable_nodes:
|
||||||
@ -272,25 +272,25 @@ class StartBaremetalIntrospectionBulk(command.Command):
|
|||||||
errors = []
|
errors = []
|
||||||
successful_node_uuids = set()
|
successful_node_uuids = set()
|
||||||
results = inspector_client.wait_for_finish(node_uuids)
|
results = inspector_client.wait_for_finish(node_uuids)
|
||||||
for uuid, status in results.items():
|
for uu, status in results.items():
|
||||||
if status['error'] is None:
|
if status['error'] is None:
|
||||||
print("Introspection for UUID {0} finished successfully."
|
print("Introspection for UUID {0} finished successfully."
|
||||||
.format(uuid))
|
.format(uu))
|
||||||
successful_node_uuids.add(uuid)
|
successful_node_uuids.add(uu)
|
||||||
else:
|
else:
|
||||||
print("Introspection for UUID {0} finished with error: {1}"
|
print("Introspection for UUID {0} finished with error: {1}"
|
||||||
.format(uuid, status['error']))
|
.format(uu, status['error']))
|
||||||
errors.append("%s: %s" % (uuid, status['error']))
|
errors.append("%s: %s" % (uu, status['error']))
|
||||||
|
|
||||||
print("Setting manageable nodes to available...")
|
print("Setting manageable nodes to available...")
|
||||||
|
|
||||||
self.log.debug("Moving manageable nodes to available state.")
|
self.log.debug("Moving manageable nodes to available state.")
|
||||||
successful_nodes = [n for n in manageable_nodes
|
successful_nodes = [n for n in manageable_nodes
|
||||||
if n.uuid in successful_node_uuids]
|
if n.uuid in successful_node_uuids]
|
||||||
for uuid in utils.set_nodes_state(
|
for uu in utils.set_nodes_state(
|
||||||
client, successful_nodes, 'provide',
|
client, successful_nodes, 'provide',
|
||||||
'available', skipped_states=("available", "active")):
|
'available', skipped_states=("available", "active")):
|
||||||
print("Node {0} has been set to available.".format(uuid))
|
print("Node {0} has been set to available.".format(uu))
|
||||||
|
|
||||||
if errors:
|
if errors:
|
||||||
raise exceptions.IntrospectionError(
|
raise exceptions.IntrospectionError(
|
||||||
|
0
tripleoclient/workflows/__init__.py
Normal file
0
tripleoclient/workflows/__init__.py
Normal file
70
tripleoclient/workflows/baremetal.py
Normal file
70
tripleoclient/workflows/baremetal.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
# -*- 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.
|
||||||
|
|
||||||
|
from tripleoclient import exceptions
|
||||||
|
|
||||||
|
|
||||||
|
def register_or_update(clients, **workflow_input):
|
||||||
|
"""Node Registration or Update
|
||||||
|
|
||||||
|
Run the tripleo.baremetal.v1.register_or_update Mistral workflow.
|
||||||
|
"""
|
||||||
|
|
||||||
|
workflow_client = clients.workflow_engine
|
||||||
|
tripleoclients = clients.tripleoclient
|
||||||
|
queue_name = workflow_input['queue_name']
|
||||||
|
|
||||||
|
execution = workflow_client.executions.create(
|
||||||
|
'tripleo.baremetal.v1.register_or_update',
|
||||||
|
workflow_input=workflow_input
|
||||||
|
)
|
||||||
|
|
||||||
|
with tripleoclients.messaging_websocket(queue_name) as ws:
|
||||||
|
payload = ws.wait_for_message(execution.id)
|
||||||
|
|
||||||
|
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 registering nodes: {}'.format(payload['message']))
|
||||||
|
|
||||||
|
|
||||||
|
def provide(clients, **workflow_input):
|
||||||
|
"""Provide Baremetal Nodes
|
||||||
|
|
||||||
|
Run the tripleo.baremetal.v1.provide Mistral workflow.
|
||||||
|
"""
|
||||||
|
|
||||||
|
workflow_client = clients.workflow_engine
|
||||||
|
tripleoclients = clients.tripleoclient
|
||||||
|
queue_name = workflow_input['queue_name']
|
||||||
|
|
||||||
|
execution = workflow_client.executions.create(
|
||||||
|
'tripleo.baremetal.v1.provide',
|
||||||
|
workflow_input={'node_uuids': workflow_input['node_uuids'],
|
||||||
|
'queue_name': queue_name}
|
||||||
|
)
|
||||||
|
|
||||||
|
with tripleoclients.messaging_websocket(queue_name) as ws:
|
||||||
|
payload = ws.wait_for_message(execution.id)
|
||||||
|
|
||||||
|
if payload['status'] == 'SUCCESS':
|
||||||
|
print('Successfully set all nodes to available.')
|
||||||
|
else:
|
||||||
|
raise exceptions.NodeProvideError(
|
||||||
|
'Failed to set nodes to available state: {}'.format(
|
||||||
|
payload['message']))
|
Loading…
Reference in New Issue
Block a user