diff --git a/ironic/common/release_mappings.py b/ironic/common/release_mappings.py index 0cfe6a6b98..a3b467d129 100644 --- a/ironic/common/release_mappings.py +++ b/ironic/common/release_mappings.py @@ -781,7 +781,7 @@ RELEASE_MAPPING = { 'objects': { 'Allocation': ['1.1'], 'BIOSSetting': ['1.1'], - 'Node': ['1.40'], + 'Node': ['1.41'], 'NodeHistory': ['1.0'], 'NodeInventory': ['1.0'], 'Conductor': ['1.4'], diff --git a/ironic/db/sqlalchemy/alembic/versions/6e9cf6acce0b_node_disable_power_off.py b/ironic/db/sqlalchemy/alembic/versions/6e9cf6acce0b_node_disable_power_off.py new file mode 100644 index 0000000000..f39dabb083 --- /dev/null +++ b/ironic/db/sqlalchemy/alembic/versions/6e9cf6acce0b_node_disable_power_off.py @@ -0,0 +1,31 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""node disable_power_off + +Revision ID: 6e9cf6acce0b +Revises: 66bd9c5604d5 +Create Date: 2024-09-23 17:54:49.101988 + +""" + +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision = '6e9cf6acce0b' +down_revision = '66bd9c5604d5' + + +def upgrade(): + op.add_column('nodes', sa.Column('disable_power_off', sa.Boolean(), + nullable=True, server_default=sa.false())) diff --git a/ironic/db/sqlalchemy/models.py b/ironic/db/sqlalchemy/models.py index fbe5c1ff0a..800059afad 100644 --- a/ironic/db/sqlalchemy/models.py +++ b/ironic/db/sqlalchemy/models.py @@ -218,6 +218,8 @@ class NodeBase(Base): shard = Column(String(255), nullable=True) parent_node = Column(String(36), nullable=True) service_step = Column(db_types.JsonEncodedDict) + disable_power_off = Column(Boolean, nullable=True, default=False, + server_default=false()) class Node(NodeBase): diff --git a/ironic/objects/node.py b/ironic/objects/node.py index eb854d73de..3a26e97ab2 100644 --- a/ironic/objects/node.py +++ b/ironic/objects/node.py @@ -82,7 +82,8 @@ class Node(base.IronicObject, object_base.VersionedObjectDictCompat): # Version 1.38: Add parent_node field # Version 1.39: Add firmware_interface field # Version 1.40: Add service_step field - VERSION = '1.40' + # Version 1.41: Add disable_power_off field + VERSION = '1.41' dbapi = db_api.get_instance() @@ -182,6 +183,7 @@ class Node(base.IronicObject, object_base.VersionedObjectDictCompat): 'secure_boot': object_fields.BooleanField(nullable=True), 'shard': object_fields.StringField(nullable=True), 'parent_node': object_fields.StringField(nullable=True), + 'disable_power_off': objects.fields.BooleanField(nullable=True), } def as_dict(self, secure=False, mask_configdrive=True): @@ -695,18 +697,18 @@ class Node(base.IronicObject, object_base.VersionedObjectDictCompat): self._adjust_field_to_version(name, None, target_version, 1, minor, remove_unavailable_fields) - # NOTE(dtantsur): the default is False for protected - self._adjust_field_to_version('protected', False, target_version, - 1, 29, remove_unavailable_fields) + boolean_fields = [('protected', 29, False), + ('retired', 33, False), + ('disable_power_off', 41, False)] + + for name, minor, default in boolean_fields: + self._adjust_field_to_version(name, default, target_version, + 1, minor, remove_unavailable_fields) self._convert_deploy_step_field(target_version, remove_unavailable_fields) self._convert_conductor_group_field(target_version, remove_unavailable_fields) - - self._adjust_field_to_version('retired', False, target_version, - 1, 33, remove_unavailable_fields) - self._convert_network_data_field(target_version, remove_unavailable_fields) @@ -790,6 +792,7 @@ class NodePayload(notification.NotificationPayloadBase): 'created_at': ('node', 'created_at'), 'deploy_step': ('node', 'deploy_step'), 'description': ('node', 'description'), + 'disable_power_off': ('node', 'disable_power_off'), 'driver': ('node', 'driver'), 'extra': ('node', 'extra'), 'boot_mode': ('node', 'boot_mode'), @@ -849,7 +852,8 @@ class NodePayload(notification.NotificationPayloadBase): # Version 1.14: Add retired and retired_reason fields exposed via API. # Version 1.15: Add node lessee field. # Version 1.16: Add boot_mode and secure_boot fields. - VERSION = '1.16' + # Version 1.17: Add disable_power_off field. + VERSION = '1.17' fields = { 'clean_step': object_fields.FlexibleDictField(nullable=True), 'conductor_group': object_fields.StringField(nullable=True), @@ -857,6 +861,7 @@ class NodePayload(notification.NotificationPayloadBase): 'created_at': object_fields.DateTimeField(nullable=True), 'deploy_step': object_fields.FlexibleDictField(nullable=True), 'description': object_fields.StringField(nullable=True), + 'disable_power_off': objects.fields.BooleanField(nullable=True), 'driver': object_fields.StringField(nullable=True), 'extra': object_fields.FlexibleDictField(nullable=True), 'boot_mode': object_fields.StringField(nullable=True), @@ -941,7 +946,8 @@ class NodeSetPowerStatePayload(NodePayload): # Version 1.14: Parent NodePayload version 1.14 # Version 1.15: Parent NodePayload version 1.15 # Version 1.16: Parent NodePayload version 1.16 - VERSION = '1.16' + # Version 1.17: Parent NodePayload version 1.17 + VERSION = '1.17' fields = { # "to_power" indicates the future target_power_state of the node. A @@ -998,7 +1004,8 @@ class NodeCorrectedPowerStatePayload(NodePayload): # Version 1.14: Parent NodePayload version 1.14 # Version 1.15: Parent NodePayload version 1.15 # Version 1.16: Parent NodePayload version 1.16 - VERSION = '1.16' + # Version 1.17: Parent NodePayload version 1.17 + VERSION = '1.17' fields = { 'from_power': object_fields.StringField(nullable=True) @@ -1040,7 +1047,8 @@ class NodeSetProvisionStatePayload(NodePayload): # Version 1.15: Parent NodePayload version 1.15 # Version 1.16: add driver_internal_info # Version 1.17: Parent NodePayload version 1.16 - VERSION = '1.17' + # Version 1.18: Parent NodePayload version 1.17 + VERSION = '1.18' SCHEMA = dict(NodePayload.SCHEMA, **{'instance_info': ('node', 'instance_info'), @@ -1090,7 +1098,8 @@ class NodeCRUDPayload(NodePayload): # Version 1.12: Parent NodePayload version 1.14 # Version 1.13: Parent NodePayload version 1.15 # Version 1.14: Parent NodePayload version 1.16 - VERSION = '1.14' + # Version 1.15: Parent NodePayload version 1.17 + VERSION = '1.15' SCHEMA = dict(NodePayload.SCHEMA, **{'instance_info': ('node', 'instance_info'), diff --git a/ironic/tests/unit/db/utils.py b/ironic/tests/unit/db/utils.py index d3f13b7956..1cffbee8f1 100644 --- a/ironic/tests/unit/db/utils.py +++ b/ironic/tests/unit/db/utils.py @@ -235,6 +235,7 @@ def get_test_node(**kw): 'shard': kw.get('shard', None), 'parent_node': kw.get('parent_node', None), 'service_step': kw.get('service_step'), + 'disable_power_off': kw.get('disable_power_off', False), } for iface in drivers_base.ALL_INTERFACES: diff --git a/ironic/tests/unit/objects/test_objects.py b/ironic/tests/unit/objects/test_objects.py index 1e2efce85e..57169ca96c 100644 --- a/ironic/tests/unit/objects/test_objects.py +++ b/ironic/tests/unit/objects/test_objects.py @@ -676,7 +676,7 @@ class TestObject(_LocalTest, _TestObject): # version bump. It is an MD5 hash of the object fields and remotable methods. # The fingerprint values should only be changed if there is a version bump. expected_object_fingerprints = { - 'Node': '1.40-2182d4660bb5d5e4cc5670c37012ef71', + 'Node': '1.41-baff7b2b06243d97448b720030b2e612', 'MyObj': '1.5-9459d30d6954bffc7a9afd347a807ca6', 'Chassis': '1.3-d656e039fd8ae9f34efc232ab3980905', 'Port': '1.11-97bf15b61224f26c65e90f007d78bfd2', @@ -684,21 +684,21 @@ expected_object_fingerprints = { 'Conductor': '1.4-a9703208fdab5fab8f1cec420be1b4a7', 'EventType': '1.1-aa2ba1afd38553e3880c267404e8d370', 'NotificationPublisher': '1.0-51a09397d6c0687771fb5be9a999605d', - 'NodePayload': '1.16-9298b3aba63ab2b9c3359afd90fb9230', + 'NodePayload': '1.17-4022bb737b058d426a7ff878b1875e5c', 'NodeSetPowerStateNotification': '1.0-59acc533c11d306f149846f922739c15', - 'NodeSetPowerStatePayload': '1.16-d3695780185716e75683ebbba4f8a2e6', + 'NodeSetPowerStatePayload': '1.17-bde6f731995024e718f42021e50ba7b4', 'NodeCorrectedPowerStateNotification': '1.0-59acc533c11d306f149846f922739c15', - 'NodeCorrectedPowerStatePayload': '1.16-fdf636b04ba0827ee0c5ec20730b790d', + 'NodeCorrectedPowerStatePayload': '1.17-6e1766fc690e0403bc0a323c8f6f12b7', 'NodeSetProvisionStateNotification': '1.0-59acc533c11d306f149846f922739c15', - 'NodeSetProvisionStatePayload': '1.17-4efa07190b276f52fda09d846b4690a8', + 'NodeSetProvisionStatePayload': '1.18-ac98c88d6dc8c6c924a415868f0a36e7', 'VolumeConnector': '1.0-3e0252c0ab6e6b9d158d09238a577d97', 'VolumeTarget': '1.0-0b10d663d8dae675900b2c7548f76f5e', 'ChassisCRUDNotification': '1.0-59acc533c11d306f149846f922739c15', 'ChassisCRUDPayload': '1.0-dce63895d8186279a7dd577cffccb202', 'NodeCRUDNotification': '1.0-59acc533c11d306f149846f922739c15', - 'NodeCRUDPayload': '1.14-abe3a744767e5ada9f8370cf0caa1862', + 'NodeCRUDPayload': '1.15-9168946f843edd5859464aaa40ad70e0', 'PortCRUDNotification': '1.0-59acc533c11d306f149846f922739c15', 'PortCRUDPayload': '1.4-9411a1701077ae9dc0aea27d6bf586fc', 'NodeMaintenanceNotification': '1.0-59acc533c11d306f149846f922739c15',