diff --git a/releasenotes/notes/bug-root-disable-d675768d4e77b61f.yaml b/releasenotes/notes/bug-root-disable-d675768d4e77b61f.yaml new file mode 100644 index 0000000000..86070c5c75 --- /dev/null +++ b/releasenotes/notes/bug-root-disable-d675768d4e77b61f.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Fixes incorrect root status reporting. + `root show` could report `"is_root_enabled": True` after root was disabled; the status now reflects the actual instance state while preserving `root_enabled_history` logic. \ No newline at end of file diff --git a/trove/db/sqlalchemy/migrations/versions/f90016d7baf8_add_deleted_at_column_to_root_enabled_.py b/trove/db/sqlalchemy/migrations/versions/f90016d7baf8_add_deleted_at_column_to_root_enabled_.py new file mode 100644 index 0000000000..a0e71eda7a --- /dev/null +++ b/trove/db/sqlalchemy/migrations/versions/f90016d7baf8_add_deleted_at_column_to_root_enabled_.py @@ -0,0 +1,45 @@ +# 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 deleted_at column to root_enabled_history + +Revision ID: f90016d7baf8 +Revises: 7ee6154548a6 +Create Date: 2025-12-12 13:27:22.852134 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'f90016d7baf8' +down_revision: Union[str, None] = '7ee6154548a6' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.add_column( + 'root_enabled_history', + sa.Column( + 'deleted_at', + sa.DateTime(), + nullable=True + ) + ) + + +def downgrade(): + op.drop_column('root_enabled_history', 'deleted_at') diff --git a/trove/extensions/common/models.py b/trove/extensions/common/models.py index f619c89fe7..ef6074417d 100644 --- a/trove/extensions/common/models.py +++ b/trove/extensions/common/models.py @@ -66,6 +66,9 @@ class Root(object): # user hasn't been enabled. try: root_history = RootHistory.load(context, instance_id) + if root_history and root_history.deleted_at and \ + root_history.deleted_at > root_history.created: + return False except exception.NotFound: return False if not root_history: @@ -101,6 +104,10 @@ class Root(object): enabled_datastore=['mysql', 'mariadb', 'postgresql']) create_guest_client(context, instance_id).disable_root() + root_history = RootHistory.load(context, instance_id) + if root_history: + root_history.delete() + class ClusterRoot(Root): @@ -134,6 +141,10 @@ class RootHistory(object): {'name': self.__class__.__name__, 'dict': self.__dict__}) return get_db_api().save(self) + def delete(self): + self.deleted_at = timeutils.utcnow() + return get_db_api().save(self) + @classmethod def load(cls, context, instance_id): history = get_db_api().find_by(cls, id=instance_id) @@ -143,7 +154,9 @@ class RootHistory(object): def create(cls, context, instance_id): history = cls.load(context, instance_id) if history is not None: - return history + if not history.deleted_at or \ + history.deleted_at < history.created: + return history history = RootHistory(instance_id, context.user_id) return history.save() diff --git a/trove/tests/unittests/taskmanager/test_models.py b/trove/tests/unittests/taskmanager/test_models.py index 48f2fc932c..bb1fc3f57f 100644 --- a/trove/tests/unittests/taskmanager/test_models.py +++ b/trove/tests/unittests/taskmanager/test_models.py @@ -1235,6 +1235,14 @@ class RootReportTest(trove_testtools.TestCase): self.assertEqual(history.user, report.user) self.assertEqual(history.id, report.id) + def test_report_root_delete(self): + context = Mock() + context.user_id = utils.generate_uuid() + report = common_models.RootHistory.create( + context, utils.generate_uuid()) + report.delete() + self.assertIsNotNone(report.deleted_at) + class ClusterRootTest(trove_testtools.TestCase):