From aa514daead373d9b447953173c997d339dbdf64e Mon Sep 17 00:00:00 2001 From: Dmitry Tantsur Date: Wed, 30 Mar 2016 14:36:42 +0200 Subject: [PATCH] Allow import command to set deploy image and local boot In our default flow we already have all the images uploaded by the time we import the Ironic nodes. So we can make the "configure boot" command optional by setting images and the local boot capability right during the import. This change also introduces 2 more flags to opt-out from these changes: --no-deploy-image will skip setting the deploy image --instance-boot-option allows setting the boot_option capability explicitly (it still requires changing flavors to match) It's still possible to override the capabilities in the instackenv.json. Change-Id: I00bbdff724ecfd6b840a74767617126734d7a32c --- .../tests/v1/baremetal/test_baremetal.py | 124 ++++++++++++------ tripleoclient/v1/baremetal.py | 25 +++- 2 files changed, 106 insertions(+), 43 deletions(-) diff --git a/tripleoclient/tests/v1/baremetal/test_baremetal.py b/tripleoclient/tests/v1/baremetal/test_baremetal.py index 845c353e7..c4b16bd97 100644 --- a/tripleoclient/tests/v1/baremetal/test_baremetal.py +++ b/tripleoclient/tests/v1/baremetal/test_baremetal.py @@ -13,15 +13,15 @@ # under the License. # +import copy +import json +import os import tempfile -import json -import mock -import os -import yaml - import fixtures +import mock from oslo_utils import units +import yaml from tripleoclient import exceptions from tripleoclient.tests.v1.baremetal import fakes @@ -353,6 +353,23 @@ pxe_ssh,192.168.122.2,stack,"KEY2",00:0b:d0:69:7e:58""") os.unlink(self.yaml_file.name) os.unlink(self.instack_yaml.name) + def _check_register_call(self, mock_register_nodes, local=True, **kwargs): + nodes_list = copy.deepcopy(self.nodes_list) + for node in nodes_list: + if local: + node['capabilities'] = 'boot_option:local' + else: + 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( + 'http://localhost', nodes_list, + client=self.app.client_manager.baremetal, + keystone_client=None, + glance_client=self.app.client_manager.image, + **kwargs) + @mock.patch('tripleo_common.utils.nodes.register_all_nodes', autospec=True) def test_json_import(self, mock_register_nodes): @@ -368,10 +385,7 @@ pxe_ssh,192.168.122.2,stack,"KEY2",00:0b:d0:69:7e:58""") self.cmd.take_action(parsed_args) - mock_register_nodes.assert_called_with( - 'http://localhost', self.nodes_list, - client=self.baremetal, - keystone_client=None) + self._check_register_call(mock_register_nodes) self.assertEqual(sorted([ ('ABCDEFGH', 'manage'), ('IJKLMNOP', 'manage'), ('ABCDEFGH', 'provide'), ('IJKLMNOP', 'provide') @@ -396,11 +410,7 @@ pxe_ssh,192.168.122.2,stack,"KEY2",00:0b:d0:69:7e:58""") 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._check_register_call(mock_register_nodes) self.assertEqual([], self.baremetal.node.updates) @mock.patch('tripleo_common.utils.nodes.register_all_nodes', autospec=True) @@ -416,11 +426,8 @@ pxe_ssh,192.168.122.2,stack,"KEY2",00:0b:d0:69:7e:58""") mock_register_nodes.return_value = self.mock_initial_nodes self.cmd.take_action(parsed_args) + self._check_register_call(mock_register_nodes) - 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') @@ -458,10 +465,7 @@ pxe_ssh,192.168.122.2,stack,"KEY2",00:0b:d0:69:7e:58""") self.cmd.take_action(parsed_args) - mock_register_nodes.assert_called_with( - 'http://localhost', self.nodes_list, - client=self.baremetal, - keystone_client=None) + self._check_register_call(mock_register_nodes) @mock.patch('tripleo_common.utils.nodes.register_all_nodes', autospec=True) def test_instack_json_import(self, mock_register_nodes): @@ -478,10 +482,7 @@ pxe_ssh,192.168.122.2,stack,"KEY2",00:0b:d0:69:7e:58""") self.cmd.take_action(parsed_args) - mock_register_nodes.assert_called_with( - 'http://localhost', self.nodes_list, - client=self.baremetal, - keystone_client=None) + self._check_register_call(mock_register_nodes) self.assertEqual(sorted([ ('ABCDEFGH', 'manage'), ('IJKLMNOP', 'manage'), ('ABCDEFGH', 'provide'), ('IJKLMNOP', 'provide') @@ -502,10 +503,7 @@ pxe_ssh,192.168.122.2,stack,"KEY2",00:0b:d0:69:7e:58""") self.cmd.take_action(parsed_args) - mock_register_nodes.assert_called_with( - 'http://localhost', self.nodes_list, - client=self.baremetal, - keystone_client=None) + self._check_register_call(mock_register_nodes) @mock.patch('tripleo_common.utils.nodes.register_all_nodes', autospec=True) def test_csv_import_detect_suffix(self, mock_register_nodes): @@ -521,10 +519,7 @@ pxe_ssh,192.168.122.2,stack,"KEY2",00:0b:d0:69:7e:58""") self.cmd.take_action(parsed_args) - mock_register_nodes.assert_called_with( - 'http://localhost', self.nodes_list, - client=self.baremetal, - keystone_client=None) + self._check_register_call(mock_register_nodes) @mock.patch('tripleo_common.utils.nodes.register_all_nodes', autospec=True) def test_yaml_import(self, mock_register_nodes): @@ -540,10 +535,7 @@ pxe_ssh,192.168.122.2,stack,"KEY2",00:0b:d0:69:7e:58""") self.cmd.take_action(parsed_args) - mock_register_nodes.assert_called_with( - 'http://localhost', self.nodes_list, - client=self.baremetal, - keystone_client=None) + self._check_register_call(mock_register_nodes) def test_invalid_import_filetype(self): @@ -575,15 +567,63 @@ pxe_ssh,192.168.122.2,stack,"KEY2",00:0b:d0:69:7e:58""") self.cmd.take_action(parsed_args) - mock_register_nodes.assert_called_with( - 'http://localhost', self.nodes_list, - client=self.baremetal, - keystone_client=None) + self._check_register_call(mock_register_nodes) 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, mock_register_nodes): + + arglist = [self.json_file.name, '-s', 'http://localhost', + '--instance-boot-option', 'netboot'] + + verifylist = [ + ('instance_boot_option', 'netboot') + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self._check_register_call(mock_register_nodes, local=False) + + @mock.patch('tripleo_common.utils.nodes.register_all_nodes', autospec=True) + def test_custom_image(self, mock_register_nodes): + + arglist = [self.json_file.name, '-s', 'http://localhost', + '--deploy-kernel', 'k', '--deploy-ramdisk', 'r'] + + verifylist = [ + ('deploy_kernel', 'k'), + ('deploy_ramdisk', 'r') + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self._check_register_call(mock_register_nodes, kernel_name='k', + ramdisk_name='r') + + @mock.patch('tripleo_common.utils.nodes.register_all_nodes', autospec=True) + def test_no_image(self, mock_register_nodes): + + arglist = [self.json_file.name, '-s', 'http://localhost', + '--no-deploy-image'] + + verifylist = [ + ('no_deploy_image', True) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self._check_register_call(mock_register_nodes, kernel_name=None, + ramdisk_name=None) + class TestStartBaremetalIntrospectionBulk(fakes.TestBaremetal): diff --git a/tripleoclient/v1/baremetal.py b/tripleoclient/v1/baremetal.py index 0dcd842ca..6c2243117 100644 --- a/tripleoclient/v1/baremetal.py +++ b/tripleoclient/v1/baremetal.py @@ -160,6 +160,18 @@ class ImportBaremetal(command.Command): parser.add_argument( '--csv', dest='csv', action='store_true', help=_('Deprecated, now detected via file extension.')) + parser.add_argument('--deploy-kernel', + default='bm-deploy-kernel', + help='Image with deploy kernel.') + parser.add_argument('--deploy-ramdisk', + default='bm-deploy-ramdisk', + help='Image with deploy ramdisk.') + 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='local', + help='Whether to set instances for booting from ' + 'local hard drive (local) or network (netboot).') parser.add_argument('file_in', type=argparse.FileType('r')) parser.add_argument( '--initial-state', @@ -195,11 +207,22 @@ class ImportBaremetal(command.Command): raise exceptions.InvalidConfiguration( _("OS_BAREMETAL_API_VERSION must be >=1.11 for use of " "'enroll' provision state; currently %s") % api_version) + + for node in nodes_config: + caps = utils.capabilities_to_dict(node.get('capabilities', {})) + caps.setdefault('boot_option', parsed_args.instance_boot_option) + node['capabilities'] = utils.dict_to_capabilities(caps) + new_nodes = nodes.register_all_nodes( parsed_args.service_host, nodes_config, client=client, - keystone_client=self.app.client_manager.identity) + keystone_client=self.app.client_manager.identity, + glance_client=self.app.client_manager.image, + kernel_name=(parsed_args.deploy_kernel if not + parsed_args.no_deploy_image else None), + ramdisk_name=(parsed_args.deploy_ramdisk if not + parsed_args.no_deploy_image else None)) if parsed_args.initial_state == "available": manageable_node_uuids = list(utils.set_nodes_state(