diff --git a/ironic/api/controllers/v1/node.py b/ironic/api/controllers/v1/node.py index c12410b0eb..e43fd5baf4 100644 --- a/ironic/api/controllers/v1/node.py +++ b/ironic/api/controllers/v1/node.py @@ -49,7 +49,7 @@ class NodePatchType(types.JsonPatchType): return defaults + ['/console_enabled', '/last_error', '/power_state', '/provision_state', '/reservation', '/target_power_state', '/target_provision_state', - '/provision_updated_at'] + '/provision_updated_at', '/maintenance_reason'] @staticmethod def mandatory_attrs(): @@ -396,6 +396,9 @@ class Node(base.APIBase): maintenance = types.boolean "Indicates whether the node is in maintenance mode." + maintenance_reason = wsme.wsattr(wtypes.text, readonly=True) + "Indicates reason for putting a node in maintenance mode." + target_provision_state = wsme.wsattr(wtypes.text, readonly=True) "The user modified desired provision state of the node." @@ -500,7 +503,8 @@ class Node(base.APIBase): properties={'memory_mb': '1024', 'local_gb': '10', 'cpus': '1'}, updated_at=time, created_at=time, provision_updated_at=time, instance_info={}, - maintenance=False, console_enabled=False) + maintenance=False, maintenance_reason=None, + console_enabled=False) # NOTE(matty_dubs): The chassis_uuid getter() is based on the # _chassis_uuid variable: sample._chassis_uuid = 'edcad704-b2da-41d5-96d9-afd580ecfa12' diff --git a/ironic/db/sqlalchemy/alembic/versions/242cc6a923b3_add_node_maintenance_reason.py b/ironic/db/sqlalchemy/alembic/versions/242cc6a923b3_add_node_maintenance_reason.py new file mode 100644 index 0000000000..57d1987852 --- /dev/null +++ b/ironic/db/sqlalchemy/alembic/versions/242cc6a923b3_add_node_maintenance_reason.py @@ -0,0 +1,36 @@ +# 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. + +"""Add Node.maintenance_reason + +Revision ID: 242cc6a923b3 +Revises: 487deb87cc9d +Create Date: 2014-10-15 23:00:43.164061 + +""" + +# revision identifiers, used by Alembic. +revision = '242cc6a923b3' +down_revision = '487deb87cc9d' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.add_column('nodes', sa.Column('maintenance_reason', + sa.Text(), + nullable=True)) + + +def downgrade(): + op.drop_column('nodes', 'maintenance_reason') diff --git a/ironic/db/sqlalchemy/models.py b/ironic/db/sqlalchemy/models.py index 691214a84a..5f4579d10d 100644 --- a/ironic/db/sqlalchemy/models.py +++ b/ironic/db/sqlalchemy/models.py @@ -180,6 +180,7 @@ class Node(Base): nullable=True) maintenance = Column(Boolean, default=False) + maintenance_reason = Column(Text, nullable=True) console_enabled = Column(Boolean, default=False) extra = Column(JSONEncodedDict) diff --git a/ironic/objects/node.py b/ironic/objects/node.py index abe6b47bc2..0038877975 100644 --- a/ironic/objects/node.py +++ b/ironic/objects/node.py @@ -30,7 +30,8 @@ class Node(base.IronicObject): # Version 1.5: Add list() # Version 1.6: Add reserve() and release() # Version 1.7: Add conductor_affinity - VERSION = '1.7' + # Version 1.8: Add maintenance_reason + VERSION = '1.8' dbapi = db_api.get_instance() @@ -65,6 +66,7 @@ class Node(base.IronicObject): 'target_provision_state': obj_utils.str_or_none, 'maintenance': bool, + 'maintenance_reason': obj_utils.str_or_none, 'console_enabled': bool, # Any error from the most recent (last) asynchronous transaction diff --git a/ironic/tests/api/v1/test_nodes.py b/ironic/tests/api/v1/test_nodes.py index b766f96d16..b45934ab33 100644 --- a/ironic/tests/api/v1/test_nodes.py +++ b/ironic/tests/api/v1/test_nodes.py @@ -97,6 +97,7 @@ class TestListNodes(base.FunctionalTest): self.assertNotIn('target_power_state', data['nodes'][0]) self.assertNotIn('target_provision_state', data['nodes'][0]) self.assertNotIn('provision_updated_at', data['nodes'][0]) + self.assertNotIn('maintenance_reason', data['nodes'][0]) # never expose the chassis_id self.assertNotIn('chassis_id', data['nodes'][0]) @@ -110,6 +111,7 @@ class TestListNodes(base.FunctionalTest): self.assertIn('properties', data) self.assertIn('chassis_uuid', data) self.assertIn('reservation', data) + self.assertIn('maintenance_reason', data) # never expose the chassis_id self.assertNotIn('chassis_id', data) diff --git a/ironic/tests/db/sqlalchemy/test_migrations.py b/ironic/tests/db/sqlalchemy/test_migrations.py index a24cc23505..edb6cb8add 100644 --- a/ironic/tests/db/sqlalchemy/test_migrations.py +++ b/ironic/tests/db/sqlalchemy/test_migrations.py @@ -321,6 +321,13 @@ class MigrationCheckersMixin(object): (sqlalchemy.exc.IntegrityError, db_exc.DBDuplicateEntry), nodes.insert().execute, data) + def _check_242cc6a923b3(self, engine, data): + nodes = db_utils.get_table(engine, 'nodes') + col_names = [column.name for column in nodes.c] + self.assertIn('maintenance_reason', col_names) + self.assertIsInstance(nodes.c.maintenance_reason.type, + sqlalchemy.types.String) + def test_upgrade_and_version(self): with patch_with_engine(self.engine): self.migration_api.upgrade('head') diff --git a/ironic/tests/db/utils.py b/ironic/tests/db/utils.py index edbf1d4d9b..f15835bbbf 100644 --- a/ironic/tests/db/utils.py +++ b/ironic/tests/db/utils.py @@ -169,6 +169,7 @@ def get_test_node(**kw): 'properties': kw.get('properties', properties), 'reservation': kw.get('reservation'), 'maintenance': kw.get('maintenance', False), + 'maintenance_reason': kw.get('maintenance_reason'), 'console_enabled': kw.get('console_enabled', False), 'extra': kw.get('extra', {}), 'updated_at': kw.get('created_at'),