diff --git a/ironicclient/common/http.py b/ironicclient/common/http.py index 2c7138645..f2a1aefaa 100644 --- a/ironicclient/common/http.py +++ b/ironicclient/common/http.py @@ -37,7 +37,7 @@ from ironicclient import exc # http://specs.openstack.org/openstack/ironic-specs/specs/kilo/api-microversions.html # noqa # for full details. DEFAULT_VER = '1.9' -LAST_KNOWN_API_VERSION = 75 +LAST_KNOWN_API_VERSION = 76 LATEST_VERSION = '1.{}'.format(LAST_KNOWN_API_VERSION) LOG = logging.getLogger(__name__) diff --git a/ironicclient/osc/v1/baremetal_node.py b/ironicclient/osc/v1/baremetal_node.py index 45b3b30df..2229ac0bb 100755 --- a/ironicclient/osc/v1/baremetal_node.py +++ b/ironicclient/osc/v1/baremetal_node.py @@ -242,6 +242,38 @@ class BootdeviceShowBaremetalNode(command.ShowOne): return zip(*sorted(info.items())) +class BootmodeSetBaremetalNode(command.Command): + """Set boot mode for baremetal node""" + + log = logging.getLogger(__name__ + ".BootmodeSetBaremetalNode") + + def get_parser(self, prog_name): + parser = super(BootmodeSetBaremetalNode, self).get_parser(prog_name) + + parser.add_argument( + 'node', + metavar='', + help=_("Name or UUID of the node.") + ) + parser.add_argument( + 'boot_mode', + choices=['uefi', 'bios'], + metavar='', + help=_('The boot mode to set for node (uefi/bios)') + ) + + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + + baremetal_client = self.app.client_manager.baremetal + + baremetal_client.node.set_boot_mode( + parsed_args.node, + parsed_args.boot_mode) + + class CleanBaremetalNode(ProvisionStateWithWait): """Set provision state of baremetal node to 'clean'""" @@ -1086,6 +1118,50 @@ class RescueBaremetalNode(ProvisionStateWithWait): return parser +class SecurebootOnBaremetalNode(command.Command): + """Turn secure boot on""" + + log = logging.getLogger(__name__ + ".SecurebootOnBaremetalNode") + + def get_parser(self, prog_name): + parser = super(SecurebootOnBaremetalNode, self).get_parser(prog_name) + + parser.add_argument( + 'node', + metavar='', + help=_("Name or UUID of the node") + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + + baremetal_client = self.app.client_manager.baremetal + baremetal_client.node.set_secure_boot(parsed_args.node, 'on') + + +class SecurebootOffBaremetalNode(command.Command): + """Turn secure boot off""" + + log = logging.getLogger(__name__ + ".SecurebootOffBaremetalNode") + + def get_parser(self, prog_name): + parser = super(SecurebootOffBaremetalNode, self).get_parser(prog_name) + + parser.add_argument( + 'node', + metavar='', + help=_("Name or UUID of the node") + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + + baremetal_client = self.app.client_manager.baremetal + baremetal_client.node.set_secure_boot(parsed_args.node, 'off') + + class SetBaremetalNode(command.Command): """Set baremetal properties""" diff --git a/ironicclient/tests/unit/osc/v1/test_baremetal_node.py b/ironicclient/tests/unit/osc/v1/test_baremetal_node.py index 7b17e4772..1ec53be02 100644 --- a/ironicclient/tests/unit/osc/v1/test_baremetal_node.py +++ b/ironicclient/tests/unit/osc/v1/test_baremetal_node.py @@ -440,6 +440,69 @@ class TestConsoleShow(TestBaremetal): 'node_uuid') +class TestSecurebootOff(TestBaremetal): + def setUp(self): + super(TestSecurebootOff, self).setUp() + + # Get the command object to test + self.cmd = baremetal_node.SecurebootOffBaremetalNode(self.app, None) + + def test_secure_boot_off(self): + arglist = ['node_uuid'] + verifylist = [('node', 'node_uuid')] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.baremetal_mock.node.set_secure_boot.assert_called_once_with( + 'node_uuid', 'off') + + +class TestSecurebootOn(TestBaremetal): + def setUp(self): + super(TestSecurebootOn, self).setUp() + + # Get the command object to test + self.cmd = baremetal_node.SecurebootOnBaremetalNode(self.app, None) + + def test_console_enable(self): + arglist = ['node_uuid'] + verifylist = [('node', 'node_uuid')] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.baremetal_mock.node.set_secure_boot.assert_called_once_with( + 'node_uuid', 'on') + + +class TestBootmodeSet(TestBaremetal): + def setUp(self): + super(TestBootmodeSet, self).setUp() + + # Get the command object to test + self.cmd = baremetal_node.BootmodeSetBaremetalNode(self.app, None) + + def test_baremetal_boot_mode_bios(self): + arglist = ['node_uuid', + 'bios'] + verifylist = [ + ('node', 'node_uuid'), + ('boot_mode', 'bios'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.baremetal_mock.node.set_boot_mode.assert_called_once_with( + 'node_uuid', + 'bios' + ) + + class TestBaremetalCreate(TestBaremetal): def setUp(self): super(TestBaremetalCreate, self).setUp() diff --git a/ironicclient/tests/unit/v1/test_node.py b/ironicclient/tests/unit/v1/test_node.py index 648fce38b..85352e98d 100644 --- a/ironicclient/tests/unit/v1/test_node.py +++ b/ironicclient/tests/unit/v1/test_node.py @@ -412,6 +412,13 @@ fake_responses = { None, ), }, + '/v1/nodes/%s/states/boot_mode' % NODE1['uuid']: + { + 'PUT': ( + {}, + None, + ), + }, '/v1/nodes/%s/states/power' % NODE1['uuid']: { 'PUT': ( @@ -419,6 +426,13 @@ fake_responses = { POWER_STATE, ), }, + '/v1/nodes/%s/states/secure_boot' % NODE1['uuid']: + { + 'PUT': ( + {}, + None, + ), + }, '/v1/nodes/%s/validate' % NODE1['uuid']: { 'GET': ( @@ -1452,6 +1466,53 @@ class NodeManagerTest(testtools.TestCase): self.mgr.set_power_state, NODE1['uuid'], 'off', soft=False, timeout='a') + def test_node_set_boot_mode_bios(self): + target_state = 'bios' + self.mgr.set_boot_mode(NODE1['uuid'], target_state) + body = {'target': target_state} + expect = [ + ('PUT', '/v1/nodes/%s/states/boot_mode' % NODE1['uuid'], {}, body), + ] + self.assertEqual(expect, self.api.calls) + + def test_node_set_boot_mode_invalid(self): + self.assertRaises(ValueError, self.mgr.set_boot_mode, + NODE1['uuid'], 'ancient-bios') + + def test_node_set_secure_boot_bool(self): + secure_boot = self.mgr.set_secure_boot(NODE1['uuid'], True) + body = {'target': True} + expect = [ + ('PUT', '/v1/nodes/%s/states/secure_boot' % NODE1['uuid'], + {}, body), + ] + self.assertEqual(expect, self.api.calls) + self.assertIsNone(secure_boot) + + def test_node_set_secure_boot_on(self): + secure_boot = self.mgr.set_secure_boot(NODE1['uuid'], 'on') + body = {'target': True} + expect = [ + ('PUT', '/v1/nodes/%s/states/secure_boot' % NODE1['uuid'], + {}, body), + ] + self.assertEqual(expect, self.api.calls) + self.assertIsNone(secure_boot) + + def test_node_set_secure_boot_off(self): + secure_boot = self.mgr.set_secure_boot(NODE1['uuid'], 'off') + body = {'target': False} + expect = [ + ('PUT', '/v1/nodes/%s/states/secure_boot' % NODE1['uuid'], + {}, body), + ] + self.assertEqual(expect, self.api.calls) + self.assertIsNone(secure_boot) + + def test_node_set_secure_boot_bad(self): + self.assertRaises(exc.InvalidAttribute, self.mgr.set_secure_boot, + NODE1['uuid'], 'band') + def test_set_target_raid_config(self): self.mgr.set_target_raid_config( NODE1['uuid'], {'fake': 'config'}) diff --git a/ironicclient/v1/node.py b/ironicclient/v1/node.py index 88879f0f2..df008679b 100644 --- a/ironicclient/v1/node.py +++ b/ironicclient/v1/node.py @@ -590,6 +590,64 @@ class NodeManager(base.CreateManager): os_ironic_api_version=os_ironic_api_version, global_request_id=global_request_id) + def set_boot_mode(self, node_id, state, + os_ironic_api_version=None, global_request_id=None): + """Sets boot mode for a node. + + :param node_id: Node identifier + :param state: One of target boot modes, 'uefi' or 'bios' + :param os_ironic_api_version: String version (e.g. "1.76") to use for + the request. If not specified, the client's default is used. + :param global_request_id: String containing global request ID header + value (in form "req-") to use for the request. + + :raises: ValueError if boot mode is not one of 'uefi' / 'bios' + :returns: The status of the request + """ + if state not in ('uefi', 'bios'): + raise ValueError( + _("Valid boot modes are 'uefi' or 'bios'")) + + path = "%s/states/boot_mode" % node_id + target = state + body = {'target': target} + + return self.update(path, body, http_method='PUT', + os_ironic_api_version=os_ironic_api_version, + global_request_id=global_request_id) + + def set_secure_boot(self, node_id, state, + os_ironic_api_version=None, global_request_id=None): + """Set the secure boot state for the node. + + :param node_id: The UUID of the node. + :param state: the secure boot state; either a Boolean or a string + representation of a Boolean (eg, 'true', 'on', 'false', + 'off'). True to turn secure boot on; False + to turn secure boot off. + :param os_ironic_api_version: String version (e.g. "1.76") to use for + the request. If not specified, the client's default is used. + :param global_request_id: String containing global request ID header + value (in form "req-") to use for the request. + + :raises: InvalidAttribute if state is an invalid string (that doesn't + represent a Boolean). + """ + if isinstance(state, bool): + target = state + else: + try: + target = strutils.bool_from_string(state, strict=True) + except ValueError as e: + raise exc.InvalidAttribute(_("Argument 'state': %(err)s") % + {'err': e}) + path = "%s/states/secure_boot" % node_id + body = {'target': target} + + return self.update(path, body, http_method='PUT', + os_ironic_api_version=os_ironic_api_version, + global_request_id=global_request_id) + def set_target_raid_config( self, node_ident, target_raid_config, os_ironic_api_version=None, global_request_id=None): diff --git a/releasenotes/notes/add-node-boot-mode-set-9746b45aa3f80fe8.yaml b/releasenotes/notes/add-node-boot-mode-set-9746b45aa3f80fe8.yaml new file mode 100644 index 000000000..0abe738b5 --- /dev/null +++ b/releasenotes/notes/add-node-boot-mode-set-9746b45aa3f80fe8.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Adds support for changing node states ``boot_mode`` and ``secure_boot`` + in sync with functionality introduced in API 1.76. diff --git a/setup.cfg b/setup.cfg index b43014654..d8de9bbb8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -63,6 +63,7 @@ openstack.baremetal.v1 = baremetal_node_bios_setting_show = ironicclient.osc.v1.baremetal_node:BIOSSettingShowBaremetalNode baremetal_node_boot_device_set = ironicclient.osc.v1.baremetal_node:BootdeviceSetBaremetalNode baremetal_node_boot_device_show = ironicclient.osc.v1.baremetal_node:BootdeviceShowBaremetalNode + baremetal_node_boot_mode_set = ironicclient.osc.v1.baremetal_node:BootmodeSetBaremetalNode baremetal_node_clean = ironicclient.osc.v1.baremetal_node:CleanBaremetalNode baremetal_node_console_disable = ironicclient.osc.v1.baremetal_node:ConsoleDisableBaremetalNode baremetal_node_console_enable = ironicclient.osc.v1.baremetal_node:ConsoleEnableBaremetalNode @@ -84,6 +85,8 @@ openstack.baremetal.v1 = baremetal_node_rebuild = ironicclient.osc.v1.baremetal_node:RebuildBaremetalNode baremetal_node_remove_trait = ironicclient.osc.v1.baremetal_node:RemoveTraitBaremetalNode baremetal_node_rescue = ironicclient.osc.v1.baremetal_node:RescueBaremetalNode + baremetal_node_secure_boot_on = ironicclient.osc.v1.baremetal_node:SecurebootOnBaremetalNode + baremetal_node_secure_boot_off = ironicclient.osc.v1.baremetal_node:SecurebootOffBaremetalNode baremetal_node_set = ironicclient.osc.v1.baremetal_node:SetBaremetalNode baremetal_node_show = ironicclient.osc.v1.baremetal_node:ShowBaremetalNode baremetal_node_trait_list = ironicclient.osc.v1.baremetal_node:ListTraitsBaremetalNode