From 84742be8c25c32a06e35bcaa584c4f4a44999276 Mon Sep 17 00:00:00 2001 From: Alfredo Moralejo Date: Tue, 5 Aug 2025 13:36:53 +0200 Subject: [PATCH] Add `status_message` column to Actions, Audits and ActionPlans tables This patch implements the changes in the database required for the skipped action blueprint. It just adds a new nullable column to the required tables and add tests for it. Note that I am also introducing a fix in a previous tables tests which will be affected by the changes in the objects. Implements: blueprint add-skip-actions Change-Id: I027bc3861b589bd281a7216583a8c5c351a53c57 Signed-off-by: Alfredo Moralejo --- ...d8f228_add_status_message_to_actionplan.py | 20 ++++++ watcher/db/sqlalchemy/models.py | 3 + watcher/tests/db/test_action.py | 8 +++ watcher/tests/db/test_action_plan.py | 9 +++ watcher/tests/db/test_audit.py | 8 +++ watcher/tests/db/test_migrations.py | 67 ++++++++++++++----- 6 files changed, 99 insertions(+), 16 deletions(-) create mode 100644 watcher/db/sqlalchemy/alembic/versions/7150a7d8f228_add_status_message_to_actionplan.py diff --git a/watcher/db/sqlalchemy/alembic/versions/7150a7d8f228_add_status_message_to_actionplan.py b/watcher/db/sqlalchemy/alembic/versions/7150a7d8f228_add_status_message_to_actionplan.py new file mode 100644 index 000000000..b33dbb3ae --- /dev/null +++ b/watcher/db/sqlalchemy/alembic/versions/7150a7d8f228_add_status_message_to_actionplan.py @@ -0,0 +1,20 @@ +"""Add status_message to Audits, ActionPlans and Actions +Revision ID: 7150a7d8f228 +Revises: 15f7375ca737 +Create Date: 2025-07-03 11:57:05.875500 +""" +# revision identifiers, used by Alembic. +revision = '7150a7d8f228' +down_revision = '15f7375ca737' +from alembic import op +import sqlalchemy as sa +def upgrade(): + op.add_column('action_plans', + sa.Column('status_message', sa.String(length=255), nullable=True) + ) + op.add_column('actions', + sa.Column('status_message', sa.String(length=255), nullable=True) + ) + op.add_column('audits', + sa.Column('status_message', sa.String(length=255), nullable=True) + ) diff --git a/watcher/db/sqlalchemy/models.py b/watcher/db/sqlalchemy/models.py index 35d6f8eee..c4488c2fa 100644 --- a/watcher/db/sqlalchemy/models.py +++ b/watcher/db/sqlalchemy/models.py @@ -177,6 +177,7 @@ class Audit(Base): start_time = Column(DateTime, nullable=True) end_time = Column(DateTime, nullable=True) force = Column(Boolean, nullable=False) + status_message = Column(String(255), nullable=True) goal = orm.relationship(Goal, foreign_keys=goal_id, lazy=None) strategy = orm.relationship(Strategy, foreign_keys=strategy_id, lazy=None) @@ -197,6 +198,7 @@ class ActionPlan(Base): state = Column(String(20), nullable=True) global_efficacy = Column(JSONEncodedList, nullable=True) hostname = Column(String(255), nullable=True) + status_message = Column(String(255), nullable=True) audit = orm.relationship(Audit, foreign_keys=audit_id, lazy=None) strategy = orm.relationship(Strategy, foreign_keys=strategy_id, lazy=None) @@ -219,6 +221,7 @@ class Action(Base): input_parameters = Column(JSONEncodedDict, nullable=True) state = Column(String(20), nullable=True) parents = Column(JSONEncodedList, nullable=True) + status_message = Column(String(255), nullable=True) action_plan = orm.relationship( ActionPlan, foreign_keys=action_plan_id, lazy=None) diff --git a/watcher/tests/db/test_action.py b/watcher/tests/db/test_action.py index 5e5f76fc6..cffc74910 100644 --- a/watcher/tests/db/test_action.py +++ b/watcher/tests/db/test_action.py @@ -401,3 +401,11 @@ class DbActionTestCase(base.DbTestCase): self.assertRaises(exception.ActionAlreadyExists, utils.create_test_action, id=2, uuid=uuid) + + def test_action_status_message(self): + action = utils.create_test_action() + self.assertIsNone(action.status_message) + self.dbapi.update_action(action['id'], + {'status_message': 'test'}) + action = self.dbapi.get_action_by_id(self.context, action['id']) + self.assertEqual(action.status_message, 'test') diff --git a/watcher/tests/db/test_action_plan.py b/watcher/tests/db/test_action_plan.py index cec8edd68..ff9d4ef77 100644 --- a/watcher/tests/db/test_action_plan.py +++ b/watcher/tests/db/test_action_plan.py @@ -389,3 +389,12 @@ class DbActionPlanTestCase(base.DbTestCase): self.assertRaises(exception.ActionPlanAlreadyExists, utils.create_test_action_plan, id=2, uuid=uuid) + + def test_action_plan_status_message(self): + action_plan = utils.create_test_action_plan() + self.assertIsNone(action_plan.status_message) + self.dbapi.update_action_plan(action_plan['id'], + {'status_message': 'test'}) + action_plan = self.dbapi.get_action_plan_by_id( + self.context, action_plan['id']) + self.assertEqual(action_plan.status_message, 'test') diff --git a/watcher/tests/db/test_audit.py b/watcher/tests/db/test_audit.py index 26c587255..bb10436f1 100644 --- a/watcher/tests/db/test_audit.py +++ b/watcher/tests/db/test_audit.py @@ -442,3 +442,11 @@ class DbAuditTestCase(base.DbTestCase): utils.create_test_audit, uuid=w_utils.generate_uuid(), name='my_audit') + + def test_audit_status_message(self): + audit = utils.create_test_audit() + self.assertIsNone(audit.status_message) + self.dbapi.update_audit(audit['id'], + {'status_message': 'test'}) + audit = self.dbapi.get_audit_by_id(self.context, audit['id']) + self.assertEqual(audit.status_message, 'test') diff --git a/watcher/tests/db/test_migrations.py b/watcher/tests/db/test_migrations.py index 3b45d18bf..1637e68d7 100644 --- a/watcher/tests/db/test_migrations.py +++ b/watcher/tests/db/test_migrations.py @@ -29,6 +29,7 @@ from alembic.script import ScriptDirectory from oslo_config import cfg from oslo_db.sqlalchemy import enginefacade from oslo_db.sqlalchemy import test_fixtures +from oslo_db.sqlalchemy import utils as oslodbutils from oslo_log import log as logging import sqlalchemy @@ -55,7 +56,7 @@ class MySQLDbMigrationsTestCase(test_fixtures.OpportunisticDBTestMixin, self.engine = enginefacade.writer.get_engine() self.dbapi = dbapi.get_instance() self.alembic_config = migration._alembic_config() - self.revisions_tested = set(["15f7375ca737"]) + self.revisions_tested = set(["15f7375ca737", "7150a7d8f228"]) def _apply_migration(self, connection, revision): if revision not in self.revisions_tested: @@ -113,6 +114,32 @@ class MySQLDbMigrationsTestCase(test_fixtures.OpportunisticDBTestMixin, class MySQLDbDataMigrationsTestCase(MySQLDbMigrationsTestCase): + def _transform_mutable_fields_to_text(self, obj_values): + transformed = {} + for key, value in obj_values.items(): + if type(value) in (dict, list): + transformed[key] = str(value) + else: + transformed[key] = value + return transformed + + def _create_manual_action_plan(self, connection, **kwargs): + ap_values = utils.get_test_action_plan(**kwargs) + ap_values = self._transform_mutable_fields_to_text(ap_values) + metadata = sqlalchemy.MetaData() + metadata.reflect(bind=self.engine) + ap_table = sqlalchemy.Table('action_plans', metadata) + with connection.begin(): + connection.execute(ap_table.insert(), ap_values) + + def _create_manual_audit(self, connection, **kwargs): + audit_values = utils.get_test_audit(**kwargs) + audit_values = self._transform_mutable_fields_to_text(audit_values) + metadata = sqlalchemy.MetaData() + metadata.reflect(bind=self.engine) + audit_table = sqlalchemy.Table('audits', metadata) + with connection.begin(): + connection.execute(audit_table.insert(), audit_values) def _create_manual_efficacy_indicator(self, connection, **kwargs): eff_ind_values = utils.get_test_efficacy_indicator(**kwargs) @@ -149,20 +176,22 @@ class MySQLDbDataMigrationsTestCase(MySQLDbMigrationsTestCase): name="STRATEGY_ID_1", display_name='My Strategy 1') self.audit_template = utils.create_test_audit_template( name="Audit Template", id=1, uuid=None) - self.audit = utils.create_test_audit( + self._create_manual_audit( + connection, audit_template_id=self.audit_template.id, id=1, uuid=None, name="AUDIT_1") - self.action_plan = utils.create_test_action_plan( - audit_id=self.audit.id, id=1, uuid=None) + self._create_manual_action_plan( + connection, + audit_id=1, id=1, uuid=None) self._create_manual_efficacy_indicator( connection, - action_plan_id=self.action_plan.id, id=1, uuid=None, + action_plan_id=1, id=1, uuid=None, name="efficacy_indicator1", description="Test Indicator 1", value=1.01234567912345678) self._create_manual_efficacy_indicator( connection, - action_plan_id=self.action_plan.id, id=2, uuid=None, + action_plan_id=1, id=2, uuid=None, name="efficacy_indicator2", description="Test Indicator 2", value=2.01234567912345678) @@ -171,7 +200,7 @@ class MySQLDbDataMigrationsTestCase(MySQLDbMigrationsTestCase): # check that creating a new efficacy_indicator after the migration # works utils.create_test_efficacy_indicator( - action_plan_id=self.action_plan.id, id=3, uuid=None, + action_plan_id=1, id=3, uuid=None, name="efficacy_indicator3", description="Test Indicator 3", value=0.01234567912345678) db_efficacy_indicator = self.dbapi.get_efficacy_indicator_by_id( @@ -183,15 +212,6 @@ class MySQLDbDataMigrationsTestCase(MySQLDbMigrationsTestCase): self.assertIsNone(self._read_efficacy_indicator(connection, 2)[0]) # check that the existing data is there after the migration - db_goal = self.dbapi.get_goal_by_id(self.context, 1) - self.assertEqual(db_goal.name, "GOAL_1") - db_strategy = self.dbapi.get_strategy_by_id(self.context, 1) - self.assertEqual(db_strategy.name, "STRATEGY_ID_1") - db_audit_template = self.dbapi.get_audit_template_by_id( - self.context, 1) - self.assertEqual(db_audit_template.name, "Audit Template") - db_audit = self.dbapi.get_audit_by_id(self.context, 1) - self.assertEqual(db_audit.name, "AUDIT_1") db_efficacy_indicator_1 = self.dbapi.get_efficacy_indicator_by_id( self.context, 1) self.assertAlmostEqual(db_efficacy_indicator_1.data, @@ -227,6 +247,21 @@ class MySQLDbDataMigrationsTestCase(MySQLDbMigrationsTestCase): self.assertAlmostEqual(eff_ind_2_data, 2.00, places=2) + def _check_7150a7d8f228(self, connection): + """Check new columen status_message have been created.""" + self.assertTrue( + oslodbutils.column_exists( + connection, "action_plans", "status_message") + ) + self.assertTrue( + oslodbutils.column_exists( + connection, "actions", "status_message") + ) + self.assertTrue( + oslodbutils.column_exists( + connection, "audits", "status_message") + ) + def test_migration_revisions(self): with self.engine.connect() as connection: self.alembic_config.attributes["connection"] = connection