diff --git a/watcher/objects/action_plan.py b/watcher/objects/action_plan.py index 6d2e4c04e..32ab254ae 100644 --- a/watcher/objects/action_plan.py +++ b/watcher/objects/action_plan.py @@ -71,10 +71,9 @@ state may be one of the following: from watcher.common import exception from watcher.common import utils -from watcher.db import api as dbapi -from watcher.objects import action as action_objects +from watcher.db import api as db_api +from watcher import objects from watcher.objects import base -from watcher.objects import efficacy_indicator as indicator_objects from watcher.objects import fields as wfields @@ -91,10 +90,12 @@ class State(object): @base.WatcherObjectRegistry.register class ActionPlan(base.WatcherPersistentObject, base.WatcherObject, base.WatcherObjectDictCompat): - # Version 1.0: Initial version - VERSION = '1.0' - dbapi = dbapi.get_instance() + # Version 1.0: Initial version + # Version 1.1: Added 'audit' and 'strategy' object field + VERSION = '1.1' + + dbapi = db_api.get_instance() fields = { 'id': wfields.IntegerField(), @@ -104,50 +105,63 @@ class ActionPlan(base.WatcherPersistentObject, base.WatcherObject, 'first_action_id': wfields.IntegerField(nullable=True), 'state': wfields.StringField(nullable=True), 'global_efficacy': wfields.FlexibleDictField(nullable=True), + + 'audit': wfields.ObjectField('Audit', nullable=True), + 'strategy': wfields.ObjectField('Strategy', nullable=True), + } + + object_fields = { + 'audit': (objects.Audit, 'audit_id'), + 'strategy': (objects.Strategy, 'strategy_id'), } @base.remotable_classmethod - def get(cls, context, action_plan_id): + def get(cls, context, action_plan_id, eager=False): """Find a action_plan based on its id or uuid and return a Action object. :param action_plan_id: the id *or* uuid of a action_plan. + :param eager: Load object fields if True (Default: False) :returns: a :class:`Action` object. """ if utils.is_int_like(action_plan_id): - return cls.get_by_id(context, action_plan_id) + return cls.get_by_id(context, action_plan_id, eager=eager) elif utils.is_uuid_like(action_plan_id): - return cls.get_by_uuid(context, action_plan_id) + return cls.get_by_uuid(context, action_plan_id, eager=eager) else: raise exception.InvalidIdentity(identity=action_plan_id) @base.remotable_classmethod - def get_by_id(cls, context, action_plan_id): + def get_by_id(cls, context, action_plan_id, eager=False): """Find a action_plan based on its integer id and return a Action object. :param action_plan_id: the id of a action_plan. + :param eager: Load object fields if True (Default: False) :returns: a :class:`Action` object. """ db_action_plan = cls.dbapi.get_action_plan_by_id( - context, action_plan_id) - action_plan = ActionPlan._from_db_object( - cls(context), db_action_plan) + context, action_plan_id, eager=eager) + action_plan = cls._from_db_object( + cls(context), db_action_plan, eager=eager) return action_plan @base.remotable_classmethod - def get_by_uuid(cls, context, uuid): + def get_by_uuid(cls, context, uuid, eager=False): """Find a action_plan based on uuid and return a :class:`Action` object. :param uuid: the uuid of a action_plan. :param context: Security context + :param eager: Load object fields if True (Default: False) :returns: a :class:`Action` object. """ - db_action_plan = cls.dbapi.get_action_plan_by_uuid(context, uuid) - action_plan = ActionPlan._from_db_object(cls(context), db_action_plan) + db_action_plan = cls.dbapi.get_action_plan_by_uuid( + context, uuid, eager=eager) + action_plan = cls._from_db_object( + cls(context), db_action_plan, eager=eager) return action_plan @base.remotable_classmethod def list(cls, context, limit=None, marker=None, filters=None, - sort_key=None, sort_dir=None): + sort_key=None, sort_dir=None, eager=False): """Return a list of Action objects. :param context: Security context. @@ -156,30 +170,36 @@ class ActionPlan(base.WatcherPersistentObject, base.WatcherObject, :param filters: Filters to apply. Defaults to None. :param sort_key: column to sort results by. :param sort_dir: direction to sort. "asc" or "desc". + :param eager: Load object fields if True (Default: False) :returns: a list of :class:`ActionPlan` object. - """ db_action_plans = cls.dbapi.get_action_plan_list(context, limit=limit, marker=marker, filters=filters, sort_key=sort_key, - sort_dir=sort_dir) + sort_dir=sort_dir, + eager=eager) - return [cls._from_db_object(cls(context), obj) + return [cls._from_db_object(cls(context), obj, eager=eager) for obj in db_action_plans] @base.remotable def create(self): - """Create a Action record in the DB""" + """Create an :class:`ActionPlan` record in the DB. + + :returns: An :class:`ActionPlan` object. + """ values = self.obj_get_changes() db_action_plan = self.dbapi.create_action_plan(values) - self._from_db_object(self, db_action_plan) + # Note(v-francoise): Always load eagerly upon creation so we can send + # notifications containing information about the related relationships + self._from_db_object(self, db_action_plan, eager=True) @base.remotable def destroy(self): """Delete the action plan from the DB""" - related_efficacy_indicators = indicator_objects.EfficacyIndicator.list( + related_efficacy_indicators = objects.EfficacyIndicator.list( context=self._context, filters={"action_plan_uuid": self.uuid}) @@ -203,20 +223,21 @@ class ActionPlan(base.WatcherPersistentObject, base.WatcherObject, self.obj_reset_changes() @base.remotable - def refresh(self): + def refresh(self, eager=False): """Loads updates for this Action plan. Loads a action_plan with the same uuid from the database and checks for updated attributes. Updates are applied from the loaded action_plan column by column, if there are any updates. + :param eager: Load object fields if True (Default: False) """ - current = self.__class__.get_by_uuid(self._context, uuid=self.uuid) + current = self.get_by_uuid(self._context, uuid=self.uuid, eager=eager) self.obj_refresh(current) @base.remotable def soft_delete(self): """Soft Delete the Action plan from the DB""" - related_actions = action_objects.Action.list( + related_actions = objects.Action.list( context=self._context, filters={"action_plan_uuid": self.uuid}) @@ -224,7 +245,7 @@ class ActionPlan(base.WatcherPersistentObject, base.WatcherObject, for related_action in related_actions: related_action.soft_delete() - related_efficacy_indicators = indicator_objects.EfficacyIndicator.list( + related_efficacy_indicators = objects.EfficacyIndicator.list( context=self._context, filters={"action_plan_uuid": self.uuid}) diff --git a/watcher/tests/api/v1/test_actions.py b/watcher/tests/api/v1/test_actions.py index 909367f16..726a77d80 100644 --- a/watcher/tests/api/v1/test_actions.py +++ b/watcher/tests/api/v1/test_actions.py @@ -52,9 +52,10 @@ class TestListAction(api_base.FunctionalTest): def setUp(self): super(TestListAction, self).setUp() - obj_utils.create_test_goal(self.context) - obj_utils.create_test_strategy(self.context) - obj_utils.create_test_action_plan(self.context) + self.goal = obj_utils.create_test_goal(self.context) + self.strategy = obj_utils.create_test_strategy(self.context) + self.audit = obj_utils.create_test_audit(self.context) + self.action_plan = obj_utils.create_test_action_plan(self.context) def test_empty(self): response = self.get_json('/actions') @@ -155,12 +156,9 @@ class TestListAction(api_base.FunctionalTest): self.assertEqual(action_plan.uuid, action['action_plan_uuid']) def test_filter_by_audit_uuid(self): - audit = obj_utils.create_test_audit(self.context, - uuid=utils.generate_uuid()) action_plan_1 = obj_utils.create_test_action_plan( self.context, - uuid=utils.generate_uuid(), - audit_id=audit.id) + uuid=utils.generate_uuid()) action_list = [] for id_ in range(3): @@ -170,8 +168,8 @@ class TestListAction(api_base.FunctionalTest): uuid=utils.generate_uuid()) action_list.append(action.uuid) - audit2 = obj_utils.create_test_audit(self.context, - uuid=utils.generate_uuid()) + audit2 = obj_utils.create_test_audit( + self.context, id=2, uuid=utils.generate_uuid()) action_plan_2 = obj_utils.create_test_action_plan( self.context, uuid=utils.generate_uuid(), @@ -183,18 +181,15 @@ class TestListAction(api_base.FunctionalTest): action_plan_id=action_plan_2.id, uuid=utils.generate_uuid()) - response = self.get_json('/actions?audit_uuid=%s' % audit.uuid) + response = self.get_json('/actions?audit_uuid=%s' % self.audit.uuid) self.assertEqual(len(action_list), len(response['actions'])) for action in response['actions']: self.assertEqual(action_plan_1.uuid, action['action_plan_uuid']) def test_filter_by_action_plan_uuid(self): - audit = obj_utils.create_test_audit(self.context, - uuid=utils.generate_uuid()) action_plan_1 = obj_utils.create_test_action_plan( self.context, - uuid=utils.generate_uuid(), - audit_id=audit.id) + uuid=utils.generate_uuid()) action_list = [] for id_ in range(3): @@ -206,8 +201,7 @@ class TestListAction(api_base.FunctionalTest): action_plan_2 = obj_utils.create_test_action_plan( self.context, - uuid=utils.generate_uuid(), - audit_id=audit.id) + uuid=utils.generate_uuid()) for id_ in range(4, 5, 6): obj_utils.create_test_action( @@ -432,6 +426,9 @@ class TestPatch(api_base.FunctionalTest): def setUp(self): super(TestPatch, self).setUp() + obj_utils.create_test_goal(self.context) + obj_utils.create_test_strategy(self.context) + obj_utils.create_test_audit(self.context) obj_utils.create_test_action_plan(self.context) self.action = obj_utils.create_test_action(self.context, next=None) p = mock.patch.object(db_api.BaseConnection, 'update_action') @@ -465,7 +462,9 @@ class TestDelete(api_base.FunctionalTest): def setUp(self): super(TestDelete, self).setUp() - obj_utils.create_test_action_plan(self.context) + self.goal = obj_utils.create_test_goal(self.context) + self.strategy = obj_utils.create_test_strategy(self.context) + self.audit = obj_utils.create_test_audit(self.context) self.action = obj_utils.create_test_action(self.context, next=None) p = mock.patch.object(db_api.BaseConnection, 'update_action') self.mock_action_update = p.start() diff --git a/watcher/tests/api/v1/test_actions_plans.py b/watcher/tests/api/v1/test_actions_plans.py index 6410fb029..5c316c451 100644 --- a/watcher/tests/api/v1/test_actions_plans.py +++ b/watcher/tests/api/v1/test_actions_plans.py @@ -30,6 +30,7 @@ class TestListActionPlan(api_base.FunctionalTest): def setUp(self): super(TestListActionPlan, self).setUp() obj_utils.create_test_goal(self.context) + obj_utils.create_test_strategy(self.context) obj_utils.create_test_audit(self.context) def test_empty(self): @@ -292,6 +293,9 @@ class TestDelete(api_base.FunctionalTest): def setUp(self): super(TestDelete, self).setUp() + obj_utils.create_test_goal(self.context) + obj_utils.create_test_strategy(self.context) + obj_utils.create_test_audit(self.context) self.action_plan = obj_utils.create_test_action_plan( self.context) p = mock.patch.object(db_api.BaseConnection, 'destroy_action_plan') @@ -348,6 +352,9 @@ class TestPatch(api_base.FunctionalTest): def setUp(self): super(TestPatch, self).setUp() + obj_utils.create_test_goal(self.context) + obj_utils.create_test_strategy(self.context) + obj_utils.create_test_audit(self.context) self.action_plan = obj_utils.create_test_action_plan( self.context, state=objects.action_plan.State.RECOMMENDED) p = mock.patch.object(db_api.BaseConnection, 'update_action_plan') @@ -487,6 +494,12 @@ class TestPatchStateTransitionDenied(api_base.FunctionalTest): "new_state": new_state} not in ALLOWED_TRANSITIONS ] + def setUp(self): + super(TestPatchStateTransitionDenied, self).setUp() + obj_utils.create_test_goal(self.context) + obj_utils.create_test_strategy(self.context) + obj_utils.create_test_audit(self.context) + @mock.patch.object( db_api.BaseConnection, 'update_action_plan', mock.Mock(side_effect=lambda ap: ap.save() or ap)) @@ -520,6 +533,12 @@ class TestPatchStateTransitionOk(api_base.FunctionalTest): for transition in ALLOWED_TRANSITIONS ] + def setUp(self): + super(TestPatchStateTransitionOk, self).setUp() + obj_utils.create_test_goal(self.context) + obj_utils.create_test_strategy(self.context) + obj_utils.create_test_audit(self.context) + @mock.patch.object( db_api.BaseConnection, 'update_action_plan', mock.Mock(side_effect=lambda ap: ap.save() or ap)) @@ -543,6 +562,12 @@ class TestPatchStateTransitionOk(api_base.FunctionalTest): class TestActionPlanPolicyEnforcement(api_base.FunctionalTest): + def setUp(self): + super(TestActionPlanPolicyEnforcement, self).setUp() + obj_utils.create_test_goal(self.context) + obj_utils.create_test_strategy(self.context) + obj_utils.create_test_audit(self.context) + def _common_policy_check(self, rule, func, *arg, **kwarg): self.policy.set_rules({ "admin_api": "(role:admin or role:administrator)", diff --git a/watcher/tests/applier/action_plan/test_default_action_handler.py b/watcher/tests/applier/action_plan/test_default_action_handler.py index abe40407c..83668400c 100644 --- a/watcher/tests/applier/action_plan/test_default_action_handler.py +++ b/watcher/tests/applier/action_plan/test_default_action_handler.py @@ -28,8 +28,10 @@ from watcher.tests.objects import utils as obj_utils class TestDefaultActionPlanHandler(base.DbTestCase): def setUp(self): super(TestDefaultActionPlanHandler, self).setUp() - self.action_plan = obj_utils.create_test_action_plan( - self.context) + obj_utils.create_test_goal(self.context) + obj_utils.create_test_strategy(self.context) + obj_utils.create_test_audit(self.context) + self.action_plan = obj_utils.create_test_action_plan(self.context) def test_launch_action_plan(self): command = default.DefaultActionPlanHandler( diff --git a/watcher/tests/db/test_purge.py b/watcher/tests/db/test_purge.py index dcd4a4a48..486f71811 100644 --- a/watcher/tests/db/test_purge.py +++ b/watcher/tests/db/test_purge.py @@ -274,9 +274,9 @@ class TestPurgeCommand(base.DbTestCase): id=self._generate_id(), uuid=utils.generate_uuid()) action_plan4 = obj_utils.create_test_action_plan( - self.context, audit_id=audit4.id, - id=self._generate_id(), - uuid=utils.generate_uuid()) + self.context, + id=self._generate_id(), uuid=utils.generate_uuid(), + audit_id=audit4.id, strategy_id=self.strategy1.id) action4 = obj_utils.create_test_action( self.context, action_plan_id=action_plan4.id, id=self._generate_id(), @@ -292,9 +292,9 @@ class TestPurgeCommand(base.DbTestCase): id=self._generate_id(), uuid=utils.generate_uuid()) action_plan5 = obj_utils.create_test_action_plan( - self.context, audit_id=audit5.id, - id=self._generate_id(), - uuid=utils.generate_uuid()) + self.context, + id=self._generate_id(), uuid=utils.generate_uuid(), + audit_id=audit5.id, strategy_id=self.strategy1.id) action5 = obj_utils.create_test_action( self.context, action_plan_id=action_plan5.id, id=self._generate_id(), @@ -367,13 +367,13 @@ class TestPurgeCommand(base.DbTestCase): strategy_id=None, id=self._generate_id(), uuid=utils.generate_uuid()) audit4 = obj_utils.create_test_audit( - self.context, audit_template_id=audit_template4.id, - id=self._generate_id(), - uuid=utils.generate_uuid()) + self.context, + id=self._generate_id(), uuid=utils.generate_uuid(), + audit_template_id=audit_template4.id) action_plan4 = obj_utils.create_test_action_plan( - self.context, audit_id=audit4.id, - id=self._generate_id(), - uuid=utils.generate_uuid()) + self.context, + id=self._generate_id(), uuid=utils.generate_uuid(), + audit_id=audit4.id, strategy_id=self.strategy1.id) action4 = obj_utils.create_test_action( self.context, action_plan_id=action_plan4.id, id=self._generate_id(), @@ -389,9 +389,9 @@ class TestPurgeCommand(base.DbTestCase): id=self._generate_id(), uuid=utils.generate_uuid()) action_plan5 = obj_utils.create_test_action_plan( - self.context, audit_id=audit5.id, - id=self._generate_id(), - uuid=utils.generate_uuid()) + self.context, + id=self._generate_id(), uuid=utils.generate_uuid(), + audit_id=audit5.id, strategy_id=self.strategy1.id) action5 = obj_utils.create_test_action( self.context, action_plan_id=action_plan5.id, id=self._generate_id(), diff --git a/watcher/tests/db/utils.py b/watcher/tests/db/utils.py index 5d99a43ba..9a05a7a7b 100644 --- a/watcher/tests/db/utils.py +++ b/watcher/tests/db/utils.py @@ -137,7 +137,7 @@ def create_test_action(**kwargs): def get_test_action_plan(**kwargs): - return { + action_plan_data = { 'id': kwargs.get('id', 1), 'uuid': kwargs.get('uuid', '76be87bd-3422-43f9-93a0-e85a577e3061'), 'state': kwargs.get('state', objects.action_plan.State.ONGOING), @@ -150,6 +150,15 @@ def get_test_action_plan(**kwargs): 'deleted_at': kwargs.get('deleted_at'), } + # ObjectField doesn't allow None nor dict, so if we want to simulate a + # non-eager object loading, the field should not be referenced at all. + if kwargs.get('audit'): + action_plan_data['audit'] = kwargs.get('audit') + if kwargs.get('strategy'): + action_plan_data['strategy'] = kwargs.get('strategy') + + return action_plan_data + def create_test_action_plan(**kwargs): """Create test action plan entry in DB and return Action Plan DB object. diff --git a/watcher/tests/objects/test_action_plan.py b/watcher/tests/objects/test_action_plan.py index 20df2341e..f1916c733 100644 --- a/watcher/tests/objects/test_action_plan.py +++ b/watcher/tests/objects/test_action_plan.py @@ -14,158 +14,214 @@ # under the License. import mock + from watcher.common import exception +from watcher.db.sqlalchemy import api as db_api from watcher import objects -from watcher.objects import action_plan as apobjects from watcher.tests.db import base from watcher.tests.db import utils class TestActionPlanObject(base.DbTestCase): + audit_id = 2 + strategy_id = 2 + + scenarios = [ + ('non_eager', dict( + eager=False, + fake_action_plan=utils.get_test_action_plan( + audit_id=audit_id, + strategy_id=strategy_id))), + ('eager_with_non_eager_load', dict( + eager=True, + fake_action_plan=utils.get_test_action_plan( + audit_id=audit_id, + strategy_id=strategy_id))), + ('eager_with_eager_load', dict( + eager=True, + fake_action_plan=utils.get_test_action_plan( + strategy_id=strategy_id, + strategy=utils.get_test_strategy(id=strategy_id), + audit_id=audit_id, + audit=utils.get_test_audit(id=audit_id)))), + ] + def setUp(self): super(TestActionPlanObject, self).setUp() - self.fake_action_plan = utils.get_test_action_plan() + self.fake_audit = utils.create_test_audit(id=self.audit_id) + self.fake_strategy = utils.create_test_strategy( + id=self.strategy_id, name="DUMMY") - def test_get_by_id(self): + def eager_load_action_plan_assert(self, action_plan): + if self.eager: + self.assertIsNotNone(action_plan.audit) + fields_to_check = set( + super(objects.Audit, objects.Audit).fields + ).symmetric_difference(objects.Audit.fields) + db_data = { + k: v for k, v in self.fake_audit.as_dict().items() + if k in fields_to_check} + object_data = { + k: v for k, v in action_plan.audit.as_dict().items() + if k in fields_to_check} + self.assertEqual(db_data, object_data) + + @mock.patch.object(db_api.Connection, 'get_action_plan_by_id') + def test_get_by_id(self, mock_get_action_plan): + mock_get_action_plan.return_value = self.fake_action_plan action_plan_id = self.fake_action_plan['id'] - with mock.patch.object(self.dbapi, 'get_action_plan_by_id', - autospec=True) as mock_get_action_plan: - mock_get_action_plan.return_value = self.fake_action_plan - action_plan = objects.ActionPlan.get(self.context, action_plan_id) - mock_get_action_plan.assert_called_once_with( - self.context, action_plan_id) - self.assertEqual(self.context, action_plan._context) + action_plan = objects.ActionPlan.get( + self.context, action_plan_id, eager=self.eager) + mock_get_action_plan.assert_called_once_with( + self.context, action_plan_id, eager=self.eager) + self.assertEqual(self.context, action_plan._context) + self.eager_load_action_plan_assert(action_plan) - def test_get_by_uuid(self): + @mock.patch.object(db_api.Connection, 'get_action_plan_by_uuid') + def test_get_by_uuid(self, mock_get_action_plan): + mock_get_action_plan.return_value = self.fake_action_plan uuid = self.fake_action_plan['uuid'] - with mock.patch.object(self.dbapi, 'get_action_plan_by_uuid', - autospec=True) as mock_get_action_plan: - mock_get_action_plan.return_value = self.fake_action_plan - action_plan = objects.ActionPlan.get(self.context, uuid) - mock_get_action_plan.assert_called_once_with(self.context, uuid) - self.assertEqual(self.context, action_plan._context) + action_plan = objects.ActionPlan.get( + self.context, uuid, eager=self.eager) + mock_get_action_plan.assert_called_once_with( + self.context, uuid, eager=self.eager) + self.assertEqual(self.context, action_plan._context) + self.eager_load_action_plan_assert(action_plan) def test_get_bad_id_and_uuid(self): self.assertRaises(exception.InvalidIdentity, - objects.ActionPlan.get, self.context, 'not-a-uuid') + objects.ActionPlan.get, self.context, + 'not-a-uuid', eager=self.eager) - def test_list(self): - with mock.patch.object(self.dbapi, 'get_action_plan_list', - autospec=True) as mock_get_list: - mock_get_list.return_value = [self.fake_action_plan] - action_plans = objects.ActionPlan.list(self.context) - self.assertEqual(1, mock_get_list.call_count) - self.assertEqual(1, len(action_plans)) - self.assertIsInstance(action_plans[0], objects.ActionPlan) - self.assertEqual(self.context, action_plans[0]._context) + @mock.patch.object(db_api.Connection, 'get_action_plan_list') + def test_list(self, mock_get_list): + mock_get_list.return_value = [self.fake_action_plan] + action_plans = objects.ActionPlan.list(self.context, eager=self.eager) + self.assertEqual(1, mock_get_list.call_count) + self.assertEqual(1, len(action_plans)) + self.assertIsInstance(action_plans[0], objects.ActionPlan) + self.assertEqual(self.context, action_plans[0]._context) + for action_plan in action_plans: + self.eager_load_action_plan_assert(action_plan) - def test_create(self): - with mock.patch.object(self.dbapi, 'create_action_plan', - autospec=True) as mock_create_action_plan: - mock_create_action_plan.return_value = self.fake_action_plan - action_plan = objects.ActionPlan( - self.context, **self.fake_action_plan) - action_plan.create() - mock_create_action_plan.assert_called_once_with( - self.fake_action_plan) - self.assertEqual(self.context, action_plan._context) - - def test_destroy(self): - efficacy_indicator = utils.get_test_efficacy_indicator( - action_plan_id=self.fake_action_plan['id']) + @mock.patch.object(db_api.Connection, 'update_action_plan') + @mock.patch.object(db_api.Connection, 'get_action_plan_by_uuid') + def test_save(self, mock_get_action_plan, mock_update_action_plan): + mock_get_action_plan.return_value = self.fake_action_plan uuid = self.fake_action_plan['uuid'] + action_plan = objects.ActionPlan.get_by_uuid( + self.context, uuid, eager=self.eager) + action_plan.state = objects.action_plan.State.SUCCEEDED + action_plan.save() - with mock.patch.multiple( - self.dbapi, autospec=True, - get_action_plan_by_uuid=mock.DEFAULT, - destroy_action_plan=mock.DEFAULT, - get_efficacy_indicator_list=mock.DEFAULT, - destroy_efficacy_indicator=mock.DEFAULT, - ) as m_dict: - m_get_action_plan = m_dict['get_action_plan_by_uuid'] - m_destroy_action_plan = m_dict['destroy_action_plan'] - m_get_efficacy_indicator_list = ( - m_dict['get_efficacy_indicator_list']) - m_destroy_efficacy_indicator = m_dict['destroy_efficacy_indicator'] - m_get_action_plan.return_value = self.fake_action_plan - m_get_efficacy_indicator_list.return_value = [efficacy_indicator] - action_plan = objects.ActionPlan.get_by_uuid(self.context, uuid) - action_plan.destroy() - m_get_action_plan.assert_called_once_with(self.context, uuid) - m_get_efficacy_indicator_list.assert_called_once_with( - self.context, filters={"action_plan_uuid": uuid}, - limit=None, marker=None, sort_dir=None, sort_key=None) - m_destroy_action_plan.assert_called_once_with(uuid) - m_destroy_efficacy_indicator.assert_called_once_with( - efficacy_indicator['uuid']) - self.assertEqual(self.context, action_plan._context) + mock_get_action_plan.assert_called_once_with( + self.context, uuid, eager=self.eager) + mock_update_action_plan.assert_called_once_with( + uuid, {'state': objects.action_plan.State.SUCCEEDED}) + self.assertEqual(self.context, action_plan._context) + self.eager_load_action_plan_assert(action_plan) - def test_soft_delete(self): - efficacy_indicator = utils.get_test_efficacy_indicator( - action_plan_id=self.fake_action_plan['id']) - uuid = self.fake_action_plan['uuid'] - - with mock.patch.multiple( - self.dbapi, autospec=True, - get_action_plan_by_uuid=mock.DEFAULT, - soft_delete_action_plan=mock.DEFAULT, - update_action_plan=mock.DEFAULT, - get_efficacy_indicator_list=mock.DEFAULT, - soft_delete_efficacy_indicator=mock.DEFAULT, - ) as m_dict: - m_get_action_plan = m_dict['get_action_plan_by_uuid'] - m_soft_delete_action_plan = m_dict['soft_delete_action_plan'] - m_get_efficacy_indicator_list = ( - m_dict['get_efficacy_indicator_list']) - m_soft_delete_efficacy_indicator = ( - m_dict['soft_delete_efficacy_indicator']) - m_update_action_plan = m_dict['update_action_plan'] - m_get_action_plan.return_value = self.fake_action_plan - m_get_efficacy_indicator_list.return_value = [efficacy_indicator] - action_plan = objects.ActionPlan.get_by_uuid(self.context, uuid) - action_plan.soft_delete() - m_get_action_plan.assert_called_once_with(self.context, uuid) - m_get_efficacy_indicator_list.assert_called_once_with( - self.context, filters={"action_plan_uuid": uuid}, - limit=None, marker=None, sort_dir=None, sort_key=None) - m_soft_delete_action_plan.assert_called_once_with(uuid) - m_soft_delete_efficacy_indicator.assert_called_once_with( - efficacy_indicator['uuid']) - m_update_action_plan.assert_called_once_with( - uuid, {'state': apobjects.State.DELETED}) - self.assertEqual(self.context, action_plan._context) - - def test_save(self): - uuid = self.fake_action_plan['uuid'] - with mock.patch.object(self.dbapi, 'get_action_plan_by_uuid', - autospec=True) as mock_get_action_plan: - mock_get_action_plan.return_value = self.fake_action_plan - with mock.patch.object(self.dbapi, 'update_action_plan', - autospec=True) as mock_update_action_plan: - action_plan = objects.ActionPlan.get_by_uuid( - self.context, uuid) - action_plan.state = apobjects.State.SUCCEEDED - action_plan.save() - - mock_get_action_plan.assert_called_once_with( - self.context, uuid) - mock_update_action_plan.assert_called_once_with( - uuid, {'state': apobjects.State.SUCCEEDED}) - self.assertEqual(self.context, action_plan._context) - - def test_refresh(self): - uuid = self.fake_action_plan['uuid'] + @mock.patch.object(db_api.Connection, 'get_action_plan_by_uuid') + def test_refresh(self, mock_get_action_plan): returns = [dict(self.fake_action_plan, state="first state"), dict(self.fake_action_plan, state="second state")] - expected = [mock.call(self.context, uuid), - mock.call(self.context, uuid)] - with mock.patch.object(self.dbapi, 'get_action_plan_by_uuid', - side_effect=returns, - autospec=True) as mock_get_action_plan: - action_plan = objects.ActionPlan.get(self.context, uuid) - self.assertEqual("first state", action_plan.state) - action_plan.refresh() - self.assertEqual("second state", action_plan.state) - self.assertEqual(expected, mock_get_action_plan.call_args_list) - self.assertEqual(self.context, action_plan._context) + mock_get_action_plan.side_effect = returns + uuid = self.fake_action_plan['uuid'] + expected = [mock.call(self.context, uuid, eager=self.eager), + mock.call(self.context, uuid, eager=self.eager)] + action_plan = objects.ActionPlan.get( + self.context, uuid, eager=self.eager) + self.assertEqual("first state", action_plan.state) + action_plan.refresh(eager=self.eager) + self.assertEqual("second state", action_plan.state) + self.assertEqual(expected, mock_get_action_plan.call_args_list) + self.assertEqual(self.context, action_plan._context) + self.eager_load_action_plan_assert(action_plan) + + +class TestCreateDeleteActionPlanObject(base.DbTestCase): + + def setUp(self): + super(TestCreateDeleteActionPlanObject, self).setUp() + self.fake_strategy = utils.create_test_strategy(name="DUMMY") + self.fake_audit = utils.create_test_audit() + self.fake_action_plan = utils.get_test_action_plan() + + @mock.patch.object(db_api.Connection, 'create_action_plan') + def test_create(self, mock_create_action_plan): + mock_create_action_plan.return_value = self.fake_action_plan + action_plan = objects.ActionPlan( + self.context, **self.fake_action_plan) + action_plan.create() + mock_create_action_plan.assert_called_once_with( + self.fake_action_plan) + self.assertEqual(self.context, action_plan._context) + + @mock.patch.multiple( + db_api.Connection, + get_action_plan_by_uuid=mock.DEFAULT, + soft_delete_action_plan=mock.DEFAULT, + update_action_plan=mock.DEFAULT, + get_efficacy_indicator_list=mock.DEFAULT, + soft_delete_efficacy_indicator=mock.DEFAULT, + ) + def test_soft_delete(self, get_action_plan_by_uuid, + soft_delete_action_plan, update_action_plan, + get_efficacy_indicator_list, + soft_delete_efficacy_indicator): + efficacy_indicator = utils.get_test_efficacy_indicator( + action_plan_id=self.fake_action_plan['id']) + uuid = self.fake_action_plan['uuid'] + m_get_action_plan = get_action_plan_by_uuid + m_soft_delete_action_plan = soft_delete_action_plan + m_get_efficacy_indicator_list = get_efficacy_indicator_list + m_soft_delete_efficacy_indicator = soft_delete_efficacy_indicator + m_update_action_plan = update_action_plan + m_get_action_plan.return_value = self.fake_action_plan + m_get_efficacy_indicator_list.return_value = [efficacy_indicator] + action_plan = objects.ActionPlan.get_by_uuid(self.context, uuid) + action_plan.soft_delete() + + m_get_action_plan.assert_called_once_with( + self.context, uuid, eager=False) + m_get_efficacy_indicator_list.assert_called_once_with( + self.context, filters={"action_plan_uuid": uuid}, + limit=None, marker=None, sort_dir=None, sort_key=None) + m_soft_delete_action_plan.assert_called_once_with(uuid) + m_soft_delete_efficacy_indicator.assert_called_once_with( + efficacy_indicator['uuid']) + m_update_action_plan.assert_called_once_with( + uuid, {'state': objects.action_plan.State.DELETED}) + self.assertEqual(self.context, action_plan._context) + + @mock.patch.multiple( + db_api.Connection, + get_action_plan_by_uuid=mock.DEFAULT, + destroy_action_plan=mock.DEFAULT, + get_efficacy_indicator_list=mock.DEFAULT, + destroy_efficacy_indicator=mock.DEFAULT, + ) + def test_destroy(self, get_action_plan_by_uuid, destroy_action_plan, + get_efficacy_indicator_list, destroy_efficacy_indicator): + m_get_action_plan = get_action_plan_by_uuid + m_destroy_action_plan = destroy_action_plan + m_get_efficacy_indicator_list = get_efficacy_indicator_list + m_destroy_efficacy_indicator = destroy_efficacy_indicator + efficacy_indicator = utils.get_test_efficacy_indicator( + action_plan_id=self.fake_action_plan['id']) + uuid = self.fake_action_plan['uuid'] + m_get_action_plan.return_value = self.fake_action_plan + m_get_efficacy_indicator_list.return_value = [efficacy_indicator] + action_plan = objects.ActionPlan.get_by_uuid(self.context, uuid) + action_plan.destroy() + + m_get_action_plan.assert_called_once_with( + self.context, uuid, eager=False) + m_get_efficacy_indicator_list.assert_called_once_with( + self.context, filters={"action_plan_uuid": uuid}, + limit=None, marker=None, sort_dir=None, sort_key=None) + m_destroy_action_plan.assert_called_once_with(uuid) + m_destroy_efficacy_indicator.assert_called_once_with( + efficacy_indicator['uuid']) + self.assertEqual(self.context, action_plan._context) diff --git a/watcher/tests/objects/test_objects.py b/watcher/tests/objects/test_objects.py index 92b237a8f..9a27d302d 100644 --- a/watcher/tests/objects/test_objects.py +++ b/watcher/tests/objects/test_objects.py @@ -413,7 +413,7 @@ expected_object_fingerprints = { 'Strategy': '1.1-73f164491bdd4c034f48083a51bdeb7b', 'AuditTemplate': '1.1-b291973ffc5efa2c61b24fe34fdccc0b', 'Audit': '1.1-dc246337c8d511646cb537144fcb0f3a', - 'ActionPlan': '1.0-cc76fd7f0e8479aeff817dd266341de4', + 'ActionPlan': '1.1-299bd9c76f2402a0b2167f8e4d744a05', 'Action': '1.0-a78f69c0da98e13e601f9646f6b2f883', 'EfficacyIndicator': '1.0-655b71234a82bc7478aff964639c4bb0', 'ScoringEngine': '1.0-4abbe833544000728e17bd9e83f97576',