diff --git a/ironicclient/osc/v1/baremetal_node.py b/ironicclient/osc/v1/baremetal_node.py index a57ed40d9..2bd68ade9 100644 --- a/ironicclient/osc/v1/baremetal_node.py +++ b/ironicclient/osc/v1/baremetal_node.py @@ -574,6 +574,14 @@ class SetBaremetalNode(command.Command): metavar='', help='Set the resource class for the node', ) + parser.add_argument( + '--target-raid-config', + metavar='', + help='Set the target RAID configuration (JSON) for the node. This ' + 'can be one of: 1. a file containing JSON data of the RAID ' + 'configuration; 2. "-" to read the contents from standard ' + 'input; or 3. a valid JSON string.', + ) parser.add_argument( "--property", metavar="", @@ -610,6 +618,17 @@ class SetBaremetalNode(command.Command): baremetal_client = self.app.client_manager.baremetal + # NOTE(rloo): Do this before updating the rest. Otherwise, it won't + # work if parsed_args.node is the name and the name is + # also being modified. + if parsed_args.target_raid_config: + raid_config = parsed_args.target_raid_config + if raid_config == '-': + raid_config = utils.get_from_stdin('target_raid_config') + raid_config = utils.handle_json_or_file_arg(raid_config) + baremetal_client.node.set_target_raid_config(parsed_args.node, + raid_config) + properties = [] if parsed_args.instance_uuid: instance_uuid = ["instance_uuid=%s" % parsed_args.instance_uuid] @@ -646,7 +665,8 @@ class SetBaremetalNode(command.Command): properties.extend(utils.args_array_to_patch( 'add', ['instance_info/' + x for x in parsed_args.instance_info])) - baremetal_client.node.update(parsed_args.node, properties) + if properties: + baremetal_client.node.update(parsed_args.node, properties) class SetBaremetal(SetBaremetalNode): @@ -756,6 +776,11 @@ class UnsetBaremetalNode(command.Command): action='store_true', help="Unset the resource class of the node", ) + parser.add_argument( + "--target-raid-config", + action='store_true', + help="Unset the target RAID configuration of the node", + ) parser.add_argument( '--property', metavar='', @@ -792,6 +817,12 @@ class UnsetBaremetalNode(command.Command): baremetal_client = self.app.client_manager.baremetal + # NOTE(rloo): Do this before removing the rest. Otherwise, it won't + # work if parsed_args.node is the name and the name is + # also being removed. + if parsed_args.target_raid_config: + baremetal_client.node.set_target_raid_config(parsed_args.node, {}) + properties = [] if parsed_args.instance_uuid: properties.extend(utils.args_array_to_patch('remove', @@ -817,8 +848,8 @@ class UnsetBaremetalNode(command.Command): properties.extend(utils.args_array_to_patch('remove', ['instance_info/' + x for x in parsed_args.instance_info])) - - baremetal_client.node.update(parsed_args.node, properties) + if properties: + baremetal_client.node.update(parsed_args.node, properties) class UnsetBaremetal(UnsetBaremetalNode): diff --git a/ironicclient/tests/unit/osc/v1/test_baremetal_node.py b/ironicclient/tests/unit/osc/v1/test_baremetal_node.py index d9032ea2d..6ea2830d4 100644 --- a/ironicclient/tests/unit/osc/v1/test_baremetal_node.py +++ b/ironicclient/tests/unit/osc/v1/test_baremetal_node.py @@ -19,6 +19,7 @@ import mock from osc_lib.tests import utils as oscutils +from ironicclient.common import utils as commonutils from ironicclient import exc from ironicclient.osc.v1 import baremetal_node from ironicclient.tests.unit.osc.v1 import fakes as baremetal_fakes @@ -956,6 +957,99 @@ class TestBaremetalSet(TestBaremetal): [{'path': '/instance_info/foo', 'value': 'bar', 'op': 'add'}] ) + @mock.patch.object(commonutils, 'get_from_stdin', autospec=True) + @mock.patch.object(commonutils, 'handle_json_or_file_arg', autospec=True) + def test_baremetal_set_target_raid_config(self, mock_handle, mock_stdin): + target_raid_config_string = '{"raid": "config"}' + expected_target_raid_config = {'raid': 'config'} + mock_handle.return_value = expected_target_raid_config.copy() + + arglist = ['node_uuid', + '--target-raid-config', target_raid_config_string] + verifylist = [('node', 'node_uuid'), + ('target_raid_config', target_raid_config_string)] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.assertFalse(mock_stdin.called) + mock_handle.assert_called_once_with(target_raid_config_string) + self.baremetal_mock.node.set_target_raid_config.\ + assert_called_once_with('node_uuid', expected_target_raid_config) + self.assertFalse(self.baremetal_mock.node.update.called) + + @mock.patch.object(commonutils, 'get_from_stdin', autospec=True) + @mock.patch.object(commonutils, 'handle_json_or_file_arg', autospec=True) + def test_baremetal_set_target_raid_config_and_name( + self, mock_handle, mock_stdin): + target_raid_config_string = '{"raid": "config"}' + expected_target_raid_config = {'raid': 'config'} + mock_handle.return_value = expected_target_raid_config.copy() + + arglist = ['node_uuid', + '--name', 'xxxxx', + '--target-raid-config', target_raid_config_string] + verifylist = [('node', 'node_uuid'), + ('name', 'xxxxx'), + ('target_raid_config', target_raid_config_string)] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.assertFalse(mock_stdin.called) + mock_handle.assert_called_once_with(target_raid_config_string) + self.baremetal_mock.node.set_target_raid_config.\ + assert_called_once_with('node_uuid', expected_target_raid_config) + self.baremetal_mock.node.update.assert_called_once_with( + 'node_uuid', + [{'path': '/name', 'value': 'xxxxx', 'op': 'add'}]) + + @mock.patch.object(commonutils, 'get_from_stdin', autospec=True) + @mock.patch.object(commonutils, 'handle_json_or_file_arg', autospec=True) + def test_baremetal_set_target_raid_config_stdin(self, mock_handle, + mock_stdin): + target_value = '-' + target_raid_config_string = '{"raid": "config"}' + expected_target_raid_config = {'raid': 'config'} + mock_stdin.return_value = target_raid_config_string + mock_handle.return_value = expected_target_raid_config.copy() + + arglist = ['node_uuid', + '--target-raid-config', target_value] + verifylist = [('node', 'node_uuid'), + ('target_raid_config', target_value)] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + mock_stdin.assert_called_once_with('target_raid_config') + mock_handle.assert_called_once_with(target_raid_config_string) + self.baremetal_mock.node.set_target_raid_config.\ + assert_called_once_with('node_uuid', expected_target_raid_config) + self.assertFalse(self.baremetal_mock.node.update.called) + + @mock.patch.object(commonutils, 'get_from_stdin', autospec=True) + @mock.patch.object(commonutils, 'handle_json_or_file_arg', autospec=True) + def test_baremetal_set_target_raid_config_stdin_exception( + self, mock_handle, mock_stdin): + target_value = '-' + mock_stdin.side_effect = exc.InvalidAttribute('bad') + + arglist = ['node_uuid', + '--target-raid-config', target_value] + verifylist = [('node', 'node_uuid'), + ('target_raid_config', target_value)] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exc.InvalidAttribute, + self.cmd.take_action, parsed_args) + + mock_stdin.assert_called_once_with('target_raid_config') + self.assertFalse(mock_handle.called) + self.assertFalse( + self.baremetal_mock.node.set_target_raid_config.called) + self.assertFalse(self.baremetal_mock.node.update.called) + class TestBaremetalShow(TestBaremetal): def setUp(self): @@ -1271,3 +1365,44 @@ class TestBaremetalUnset(TestBaremetal): 'node_uuid', [{'path': '/instance_info/foo', 'op': 'remove'}] ) + + def test_baremetal_unset_target_raid_config(self): + arglist = [ + 'node_uuid', + '--target-raid-config', + ] + verifylist = [ + ('node', 'node_uuid'), + ('target_raid_config', True) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.assertFalse(self.baremetal_mock.node.update.called) + self.baremetal_mock.node.set_target_raid_config.\ + assert_called_once_with('node_uuid', {}) + + def test_baremetal_unset_target_raid_config_and_name(self): + arglist = [ + 'node_uuid', + '--name', + '--target-raid-config', + ] + verifylist = [ + ('node', 'node_uuid'), + ('name', True), + ('target_raid_config', True) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.baremetal_mock.node.set_target_raid_config.\ + assert_called_once_with('node_uuid', {}) + self.baremetal_mock.node.update.assert_called_once_with( + 'node_uuid', + [{'path': '/name', 'op': 'remove'}] + ) diff --git a/releasenotes/notes/osc-plugin-node-set-target-raid-config-5d538d6253902ecb.yaml b/releasenotes/notes/osc-plugin-node-set-target-raid-config-5d538d6253902ecb.yaml new file mode 100644 index 000000000..75c279161 --- /dev/null +++ b/releasenotes/notes/osc-plugin-node-set-target-raid-config-5d538d6253902ecb.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Allows setting and unsetting (clearing) the node's target + RAID configuration via the OpenStackClient plugin commands + 'openstack baremetal node set --target-raid-config' and + 'openstack baremetal node unset --target-raid-config' + respectively.