diff --git a/tripleoclient/tests/v1/baremetal/fakes.py b/tripleoclient/tests/v1/baremetal/fakes.py index f59eb5760..2c184fd34 100644 --- a/tripleoclient/tests/v1/baremetal/fakes.py +++ b/tripleoclient/tests/v1/baremetal/fakes.py @@ -88,3 +88,5 @@ class TestBaremetal(utils.TestCommand): self.app.client_manager.baremetal = mock.Mock() self.app.client_manager.image = mock.Mock() self.app.client_manager.baremetal_introspection = FakeInspectorClient() + self.app.client_manager._region_name = "Arcadia" + self.app.client_manager.session = mock.Mock() diff --git a/tripleoclient/tests/v1/baremetal/test_baremetal.py b/tripleoclient/tests/v1/baremetal/test_baremetal.py index 124bd97f4..30bf01ac4 100644 --- a/tripleoclient/tests/v1/baremetal/test_baremetal.py +++ b/tripleoclient/tests/v1/baremetal/test_baremetal.py @@ -326,6 +326,22 @@ pxe_ssh,192.168.122.2,stack,"KEY2",00:0b:d0:69:7e:58""") self.instack_json.close() self.yaml_file.close() self.instack_yaml.close() + self.baremetal = self.app.client_manager.baremetal + self.baremetal.http_client.os_ironic_api_version = '1.11' + self.baremetal.node = fakes.FakeBaremetalNodeClient( + states={"ABCDEFGH": "enroll", "IJKLMNOP": "enroll"}, + transitions={ + ("ABCDEFGH", "manage"): "manageable", + ("IJKLMNOP", "manage"): "manageable", + ("ABCDEFGH", "provide"): "available", + ("IJKLMNOP", "provide"): "available", + + } + ) + self.mock_initial_nodes = { + mock.Mock(uuid="ABCDEFGH", provision_state="enroll"), + mock.Mock(uuid="IJKLMNOP", provision_state="enroll") + } def tearDown(self): @@ -347,13 +363,85 @@ pxe_ssh,192.168.122.2,stack,"KEY2",00:0b:d0:69:7e:58""") ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + mock_register_nodes.return_value = self.mock_initial_nodes self.cmd.take_action(parsed_args) mock_register_nodes.assert_called_with( 'http://localhost', self.nodes_list, - client=self.app.client_manager.baremetal, + client=self.baremetal, keystone_client=None) + 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, mock_register_nodes): + + arglist = [ + self.json_file.name, + '--json', + '-s', 'http://localhost', + '--initial-state', 'enroll' + ] + + verifylist = [ + ('csv', False), + ('json', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + mock_register_nodes.return_value = self.mock_initial_nodes + + self.cmd.take_action(parsed_args) + + mock_register_nodes.assert_called_with( + 'http://localhost', self.nodes_list, + client=self.baremetal, + keystone_client=None) + 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, mock_register_nodes): + arglist = [self.json_file.name, '--json', '-s', 'http://localhost'] + + verifylist = [ + ('csv', False), + ('json', True), + ] + self.baremetal.http_client.os_ironic_api_version = '1.6' + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + mock_register_nodes.return_value = self.mock_initial_nodes + + self.cmd.take_action(parsed_args) + + mock_register_nodes.assert_called_with( + 'http://localhost', self.nodes_list, + client=self.baremetal, + keystone_client=None) + self.assertEqual(sorted([ + ('ABCDEFGH', 'manage'), ('IJKLMNOP', 'manage'), + ('ABCDEFGH', 'provide'), ('IJKLMNOP', 'provide') + ]), sorted(self.baremetal.node.updates)) + + def test_enroll_requires_api_1_11(self): + arglist = [ + self.json_file.name, + '--json', + '-s', 'http://localhost', + '--initial-state', 'enroll' + ] + + verifylist = [ + ('csv', False), + ('json', True), + ] + self.baremetal.http_client.os_ironic_api_version = '1.6' + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaisesRegexp(exceptions.InvalidConfiguration, + 'OS_BAREMETAL_API_VERSION', + self.cmd.take_action, parsed_args) @mock.patch('tripleo_common.utils.nodes.register_all_nodes', autospec=True) def test_json_import_detect_suffix(self, mock_register_nodes): @@ -371,7 +459,7 @@ pxe_ssh,192.168.122.2,stack,"KEY2",00:0b:d0:69:7e:58""") mock_register_nodes.assert_called_with( 'http://localhost', self.nodes_list, - client=self.app.client_manager.baremetal, + client=self.baremetal, keystone_client=None) @mock.patch('tripleo_common.utils.nodes.register_all_nodes', autospec=True) @@ -385,13 +473,18 @@ pxe_ssh,192.168.122.2,stack,"KEY2",00:0b:d0:69:7e:58""") ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + mock_register_nodes.return_value = self.mock_initial_nodes self.cmd.take_action(parsed_args) mock_register_nodes.assert_called_with( 'http://localhost', self.nodes_list, - client=self.app.client_manager.baremetal, + client=self.baremetal, keystone_client=None) + 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, mock_register_nodes): @@ -404,12 +497,13 @@ pxe_ssh,192.168.122.2,stack,"KEY2",00:0b:d0:69:7e:58""") ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + mock_register_nodes.return_value = self.mock_initial_nodes self.cmd.take_action(parsed_args) mock_register_nodes.assert_called_with( 'http://localhost', self.nodes_list, - client=self.app.client_manager.baremetal, + client=self.baremetal, keystone_client=None) @mock.patch('tripleo_common.utils.nodes.register_all_nodes', autospec=True) @@ -428,7 +522,7 @@ pxe_ssh,192.168.122.2,stack,"KEY2",00:0b:d0:69:7e:58""") mock_register_nodes.assert_called_with( 'http://localhost', self.nodes_list, - client=self.app.client_manager.baremetal, + client=self.baremetal, keystone_client=None) @mock.patch('tripleo_common.utils.nodes.register_all_nodes', autospec=True) @@ -447,7 +541,7 @@ pxe_ssh,192.168.122.2,stack,"KEY2",00:0b:d0:69:7e:58""") mock_register_nodes.assert_called_with( 'http://localhost', self.nodes_list, - client=self.app.client_manager.baremetal, + client=self.baremetal, keystone_client=None) def test_invalid_import_filetype(self): @@ -476,13 +570,18 @@ pxe_ssh,192.168.122.2,stack,"KEY2",00:0b:d0:69:7e:58""") ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + mock_register_nodes.return_value = self.mock_initial_nodes self.cmd.take_action(parsed_args) mock_register_nodes.assert_called_with( 'http://localhost', self.nodes_list, - client=self.app.client_manager.baremetal, + client=self.baremetal, keystone_client=None) + self.assertEqual(sorted([ + ('ABCDEFGH', 'manage'), ('IJKLMNOP', 'manage'), + ('ABCDEFGH', 'provide'), ('IJKLMNOP', 'provide') + ]), sorted(self.baremetal.node.updates)) @mock.patch('time.sleep', lambda sec: None) diff --git a/tripleoclient/utils.py b/tripleoclient/utils.py index 61d74de06..4361448e8 100644 --- a/tripleoclient/utils.py +++ b/tripleoclient/utils.py @@ -475,7 +475,7 @@ def set_nodes_state(baremetal_client, nodes, transition, target_state, continue log.debug( - "Setting provision state from {0} to '{1} for Node {2}" + "Setting provision state from '{0}' to '{1}' for Node {2}" .format(node.provision_state, transition, node.uuid)) baremetal_client.node.set_provision_state(node.uuid, transition) diff --git a/tripleoclient/v1/baremetal.py b/tripleoclient/v1/baremetal.py index 17b5ce1ec..0df58d82b 100644 --- a/tripleoclient/v1/baremetal.py +++ b/tripleoclient/v1/baremetal.py @@ -159,6 +159,13 @@ class ImportBaremetal(command.Command): '--csv', dest='csv', action='store_true', help=_('Deprecated, now detected via file extension.')) parser.add_argument('file_in', type=argparse.FileType('r')) + parser.add_argument( + '--initial-state', + choices=['enroll', 'available'], + default='available', + help='Provision state for newly-enrolled nodes.' + ) + return parser def take_action(self, parsed_args): @@ -179,12 +186,32 @@ class ImportBaremetal(command.Command): if 'nodes' in nodes_config: nodes_config = nodes_config['nodes'] - nodes.register_all_nodes( + client = self.app.client_manager.baremetal + if parsed_args.initial_state == "enroll": + api_version = client.http_client.os_ironic_api_version + if [int(part) for part in api_version.split('.')] < [1, 11]: + raise exceptions.InvalidConfiguration( + _("OS_BAREMETAL_API_VERSION must be >=1.11 for use of " + "'enroll' provision state; currently %s") % api_version) + new_nodes = nodes.register_all_nodes( parsed_args.service_host, nodes_config, - client=self.app.client_manager.baremetal, + client=client, keystone_client=self.app.client_manager.identity) + if parsed_args.initial_state == "available": + manageable_node_uuids = list(utils.set_nodes_state( + client, new_nodes, "manage", "manageable", + 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): """Start bulk introspection on all baremetal nodes"""