From 016a168906c38c3f9e46a4b344b5c0f4f78ef781 Mon Sep 17 00:00:00 2001
From: Derek Higgins <derekh@redhat.com>
Date: Mon, 11 Nov 2024 16:55:10 +0000
Subject: [PATCH] Add support to set 'disable_power_off'

Add support to set 'disable_power_off' on node create and
set/unset on node property set.

Change-Id: Id70b4d2c9e3399e9944d5d3743b48b9889979fd4
---
 ironicclient/common/http.py                   |  2 +-
 ironicclient/osc/v1/baremetal_node.py         | 33 +++++++++++--
 .../tests/unit/osc/v1/test_baremetal_node.py  | 46 +++++++++++++++++++
 ironicclient/v1/node.py                       | 14 +++---
 ironicclient/v1/resource_fields.py            |  4 +-
 ...isable_power_off_api-1d9efcb96c5ba37f.yaml |  4 ++
 6 files changed, 89 insertions(+), 14 deletions(-)
 create mode 100644 releasenotes/notes/disable_power_off_api-1d9efcb96c5ba37f.yaml

diff --git a/ironicclient/common/http.py b/ironicclient/common/http.py
index cb7d90c5e..a71a22a6f 100644
--- a/ironicclient/common/http.py
+++ b/ironicclient/common/http.py
@@ -36,7 +36,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 = 92
+LAST_KNOWN_API_VERSION = 95
 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 94b582db6..b32d66436 100755
--- a/ironicclient/osc/v1/baremetal_node.py
+++ b/ironicclient/osc/v1/baremetal_node.py
@@ -597,17 +597,22 @@ class CreateBaremetalNode(command.ShowOne):
             help=_('Firmware interface used by the node\'s driver. This is '
                    'only applicable when the specified --driver is a '
                    'hardware type.'))
-
+        parser.add_argument(
+            '--disable-power-off',
+            action='store_true',
+            dest='disable_power_off',
+            default=None,
+            help=_('Explicitly disable power off actions on 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
 
-        field_list = ['automated_clean', 'chassis_uuid', 'driver',
-                      'driver_info', 'properties', 'extra', 'uuid', 'name',
-                      'conductor_group', 'owner', 'description', 'lessee',
-                      'shard', 'resource_class', 'parent_node',
+        field_list = ['automated_clean', 'chassis_uuid', 'disable_power_off',
+                      'driver', 'driver_info', 'properties', 'extra', 'uuid',
+                      'name', 'conductor_group', 'owner', 'description',
+                      'lessee', 'shard', 'resource_class', 'parent_node',
                       ] + ['%s_interface' % iface
                            for iface in SUPPORTED_INTERFACES]
         fields = dict((k, v) for (k, v) in vars(parsed_args).items()
@@ -1530,6 +1535,19 @@ class SetBaremetalNode(command.Command):
             metavar='<parent_node>',
             help=_('Set the parent node for the node'),
         )
+        power_off = parser.add_mutually_exclusive_group()
+        power_off.add_argument(
+            '--enable-power-off',
+            action='store_false',
+            dest='disable_power_off',
+            default=None,
+            help=_('Explicitly enable power off actions on nodes'))
+        power_off.add_argument(
+            '--disable-power-off',
+            action='store_true',
+            dest='disable_power_off',
+            default=None,
+            help=_('Explicitly disable power off actions on nodes'))
 
         return parser
 
@@ -1570,6 +1588,11 @@ class SetBaremetalNode(command.Command):
             properties.extend(utils.args_array_to_patch(
                 'add', ["automated_clean=%s" % parsed_args.automated_clean]))
 
+        if parsed_args.disable_power_off is not None:
+            properties.extend(utils.args_array_to_patch(
+                'add', ["disable_power_off=%s" % parsed_args.disable_power_off]
+            ))
+
         if parsed_args.reset_interfaces and not parsed_args.driver:
             raise exc.CommandError(
                 _("--reset-interfaces can only be specified with --driver"))
diff --git a/ironicclient/tests/unit/osc/v1/test_baremetal_node.py b/ironicclient/tests/unit/osc/v1/test_baremetal_node.py
index 11a9e8d05..319491023 100644
--- a/ironicclient/tests/unit/osc/v1/test_baremetal_node.py
+++ b/ironicclient/tests/unit/osc/v1/test_baremetal_node.py
@@ -989,6 +989,11 @@ class TestBaremetalCreate(TestBaremetal):
                                 [('parent_node', 'nodex')],
                                 {'parent_node': 'nodex'})
 
+    def test_baremetal_create_with_disable_power_off(self):
+        self.check_with_options(['--disable-power-off'],
+                                [('disable_power_off', True)],
+                                {'disable_power_off': True})
+
 
 class TestBaremetalDelete(TestBaremetal):
     def setUp(self):
@@ -1150,6 +1155,7 @@ class TestBaremetalList(TestBaremetal):
             'Deploy Interface',
             'Deploy Step',
             'Description',
+            'Disable Power Off',
             'Driver',
             'Driver Info',
             'Driver Internal Info',
@@ -3620,6 +3626,46 @@ class TestBaremetalSet(TestBaremetal):
             reset_interfaces=None,
         )
 
+    def test_baremetal_set_disable_power_off(self):
+        arglist = [
+            'node_uuid',
+            '--disable-power-off'
+        ]
+        verifylist = [
+            ('nodes', ['node_uuid']),
+            ('disable_power_off', True)
+        ]
+
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        self.cmd.take_action(parsed_args)
+
+        self.baremetal_mock.node.update.assert_called_once_with(
+            'node_uuid',
+            [{'path': '/disable_power_off', 'value': 'True', 'op': 'add'}],
+            reset_interfaces=None,
+        )
+
+    def test_baremetal_set_enable_power_off(self):
+        arglist = [
+            'node_uuid',
+            '--enable-power-off'
+        ]
+        verifylist = [
+            ('nodes', ['node_uuid']),
+            ('disable_power_off', False)
+        ]
+
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        self.cmd.take_action(parsed_args)
+
+        self.baremetal_mock.node.update.assert_called_once_with(
+            'node_uuid',
+            [{'path': '/disable_power_off', 'value': 'False', 'op': 'add'}],
+            reset_interfaces=None,
+        )
+
 
 class TestBaremetalShow(TestBaremetal):
     def setUp(self):
diff --git a/ironicclient/v1/node.py b/ironicclient/v1/node.py
index 5e2633b40..c36a27d64 100644
--- a/ironicclient/v1/node.py
+++ b/ironicclient/v1/node.py
@@ -48,13 +48,13 @@ class NodeManager(base.CreateManager):
                             'extra', 'uuid', 'properties', 'name',
                             'bios_interface', 'boot_interface',
                             'console_interface', 'deploy_interface',
-                            'inspect_interface', 'management_interface',
-                            'network_interface', 'power_interface',
-                            'raid_interface', 'rescue_interface',
-                            'storage_interface', 'vendor_interface',
-                            'firmware_interface', 'resource_class',
-                            'conductor_group', 'automated_clean',
-                            'network_data', 'parent_node',
+                            'disable_power_off', 'inspect_interface',
+                            'management_interface', 'network_interface',
+                            'power_interface', 'raid_interface',
+                            'rescue_interface', 'storage_interface',
+                            'vendor_interface', 'firmware_interface',
+                            'resource_class', 'conductor_group',
+                            'automated_clean', 'network_data', 'parent_node',
                             'owner', 'lessee', 'shard', 'description']
     _resource_name = 'nodes'
 
diff --git a/ironicclient/v1/resource_fields.py b/ironicclient/v1/resource_fields.py
index b45c9a11f..af73cf8b4 100644
--- a/ironicclient/v1/resource_fields.py
+++ b/ironicclient/v1/resource_fields.py
@@ -157,7 +157,8 @@ class Resource(object):
         'parent_node': 'Parent Node',
         'children': 'Child Nodes',
         'firmware_interface': 'Firmware Interface',
-        'public': 'Public'
+        'public': 'Public',
+        'disable_power_off': 'Disable Power Off'
     }
 
     def __init__(self, field_ids, sort_excluded=None, override_labels=None):
@@ -255,6 +256,7 @@ NODE_DETAILED_RESOURCE = Resource(
      'deploy_interface',
      'deploy_step',
      'description',
+     'disable_power_off',
      'driver',
      'driver_info',
      'driver_internal_info',
diff --git a/releasenotes/notes/disable_power_off_api-1d9efcb96c5ba37f.yaml b/releasenotes/notes/disable_power_off_api-1d9efcb96c5ba37f.yaml
new file mode 100644
index 000000000..68c6cfa63
--- /dev/null
+++ b/releasenotes/notes/disable_power_off_api-1d9efcb96c5ba37f.yaml
@@ -0,0 +1,4 @@
+---
+features:
+  - |
+    Support added to set "disable power off" on nodes.