Add auto_trigger support to watcher
This patch set adds support of auto-triggering of action plans. Change-Id: I36b7dff8eab5f6ebb18f6f4e752cf4b263456293 Partially-Implements: blueprint automatic-triggering-audit
This commit is contained in:
@@ -69,6 +69,8 @@ class AuditPostType(wtypes.Base):
|
|||||||
|
|
||||||
scope = wtypes.wsattr(types.jsontype, readonly=True)
|
scope = wtypes.wsattr(types.jsontype, readonly=True)
|
||||||
|
|
||||||
|
auto_trigger = wtypes.wsattr(bool, mandatory=False)
|
||||||
|
|
||||||
def as_audit(self, context):
|
def as_audit(self, context):
|
||||||
audit_type_values = [val.value for val in objects.audit.AuditType]
|
audit_type_values = [val.value for val in objects.audit.AuditType]
|
||||||
if self.audit_type not in audit_type_values:
|
if self.audit_type not in audit_type_values:
|
||||||
@@ -115,7 +117,8 @@ class AuditPostType(wtypes.Base):
|
|||||||
goal_id=self.goal,
|
goal_id=self.goal,
|
||||||
strategy_id=self.strategy,
|
strategy_id=self.strategy,
|
||||||
interval=self.interval,
|
interval=self.interval,
|
||||||
scope=self.scope,)
|
scope=self.scope,
|
||||||
|
auto_trigger=self.auto_trigger)
|
||||||
|
|
||||||
|
|
||||||
class AuditPatchType(types.JsonPatchType):
|
class AuditPatchType(types.JsonPatchType):
|
||||||
@@ -257,6 +260,9 @@ class Audit(base.APIBase):
|
|||||||
scope = wsme.wsattr(types.jsontype, mandatory=False)
|
scope = wsme.wsattr(types.jsontype, mandatory=False)
|
||||||
"""Audit Scope"""
|
"""Audit Scope"""
|
||||||
|
|
||||||
|
auto_trigger = wsme.wsattr(bool, mandatory=False, default=False)
|
||||||
|
"""Autoexecute action plan once audit is succeeded"""
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
self.fields = []
|
self.fields = []
|
||||||
fields = list(objects.Audit.fields)
|
fields = list(objects.Audit.fields)
|
||||||
@@ -313,7 +319,8 @@ class Audit(base.APIBase):
|
|||||||
deleted_at=None,
|
deleted_at=None,
|
||||||
updated_at=datetime.datetime.utcnow(),
|
updated_at=datetime.datetime.utcnow(),
|
||||||
interval=7200,
|
interval=7200,
|
||||||
scope=[])
|
scope=[],
|
||||||
|
auto_trigger=False)
|
||||||
|
|
||||||
sample.goal_id = '7ae81bb3-dec3-4289-8d6c-da80bd8001ae'
|
sample.goal_id = '7ae81bb3-dec3-4289-8d6c-da80bd8001ae'
|
||||||
sample.strategy_id = '7ae81bb3-dec3-4289-8d6c-da80bd8001ff'
|
sample.strategy_id = '7ae81bb3-dec3-4289-8d6c-da80bd8001ff'
|
||||||
|
|||||||
@@ -258,6 +258,12 @@ class ActionPlanReferenced(Invalid):
|
|||||||
"multiple actions")
|
"multiple actions")
|
||||||
|
|
||||||
|
|
||||||
|
class ActionPlanIsOngoing(Conflict):
|
||||||
|
msg_fmt = _("Action Plan %(action_plan)s is currently running. "
|
||||||
|
"New Action Plan %(new_action_plan)s will be set as "
|
||||||
|
"SUPERSEDED")
|
||||||
|
|
||||||
|
|
||||||
class ActionNotFound(ResourceNotFound):
|
class ActionNotFound(ResourceNotFound):
|
||||||
msg_fmt = _("Action %(action)s could not be found")
|
msg_fmt = _("Action %(action)s could not be found")
|
||||||
|
|
||||||
|
|||||||
@@ -663,6 +663,9 @@ class Connection(api.BaseConnection):
|
|||||||
if values.get('state') is None:
|
if values.get('state') is None:
|
||||||
values['state'] = objects.audit.State.PENDING
|
values['state'] = objects.audit.State.PENDING
|
||||||
|
|
||||||
|
if not values.get('auto_trigger'):
|
||||||
|
values['auto_trigger'] = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
audit = self._create(models.Audit, values)
|
audit = self._create(models.Audit, values)
|
||||||
except db_exc.DBDuplicateEntry:
|
except db_exc.DBDuplicateEntry:
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ SQLAlchemy models for watcher service
|
|||||||
from oslo_db.sqlalchemy import models
|
from oslo_db.sqlalchemy import models
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
import six.moves.urllib.parse as urlparse
|
import six.moves.urllib.parse as urlparse
|
||||||
|
from sqlalchemy import Boolean
|
||||||
from sqlalchemy import Column
|
from sqlalchemy import Column
|
||||||
from sqlalchemy import DateTime
|
from sqlalchemy import DateTime
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
@@ -176,6 +177,7 @@ class Audit(Base):
|
|||||||
goal_id = Column(Integer, ForeignKey('goals.id'), nullable=False)
|
goal_id = Column(Integer, ForeignKey('goals.id'), nullable=False)
|
||||||
strategy_id = Column(Integer, ForeignKey('strategies.id'), nullable=True)
|
strategy_id = Column(Integer, ForeignKey('strategies.id'), nullable=True)
|
||||||
scope = Column(JSONEncodedList, nullable=True)
|
scope = Column(JSONEncodedList, nullable=True)
|
||||||
|
auto_trigger = Column(Boolean, nullable=False)
|
||||||
|
|
||||||
goal = orm.relationship(Goal, foreign_keys=goal_id, lazy=None)
|
goal = orm.relationship(Goal, foreign_keys=goal_id, lazy=None)
|
||||||
strategy = orm.relationship(Strategy, foreign_keys=strategy_id, lazy=None)
|
strategy = orm.relationship(Strategy, foreign_keys=strategy_id, lazy=None)
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ import six
|
|||||||
|
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
|
from watcher.applier import rpcapi
|
||||||
|
from watcher.common import exception
|
||||||
from watcher.decision_engine.planner import manager as planner_manager
|
from watcher.decision_engine.planner import manager as planner_manager
|
||||||
from watcher.decision_engine.strategy.context import default as default_context
|
from watcher.decision_engine.strategy.context import default as default_context
|
||||||
from watcher import notifications
|
from watcher import notifications
|
||||||
@@ -79,11 +81,13 @@ class AuditHandler(BaseAuditHandler):
|
|||||||
request_context, audit,
|
request_context, audit,
|
||||||
action=fields.NotificationAction.PLANNER,
|
action=fields.NotificationAction.PLANNER,
|
||||||
phase=fields.NotificationPhase.START)
|
phase=fields.NotificationPhase.START)
|
||||||
self.planner.schedule(request_context, audit.id, solution)
|
action_plan = self.planner.schedule(request_context, audit.id,
|
||||||
|
solution)
|
||||||
notifications.audit.send_action_notification(
|
notifications.audit.send_action_notification(
|
||||||
request_context, audit,
|
request_context, audit,
|
||||||
action=fields.NotificationAction.PLANNER,
|
action=fields.NotificationAction.PLANNER,
|
||||||
phase=fields.NotificationPhase.END)
|
phase=fields.NotificationPhase.END)
|
||||||
|
return action_plan
|
||||||
except Exception:
|
except Exception:
|
||||||
notifications.audit.send_action_notification(
|
notifications.audit.send_action_notification(
|
||||||
request_context, audit,
|
request_context, audit,
|
||||||
@@ -104,15 +108,30 @@ class AuditHandler(BaseAuditHandler):
|
|||||||
self.update_audit_state(audit, objects.audit.State.ONGOING)
|
self.update_audit_state(audit, objects.audit.State.ONGOING)
|
||||||
|
|
||||||
def post_execute(self, audit, solution, request_context):
|
def post_execute(self, audit, solution, request_context):
|
||||||
self.do_schedule(request_context, audit, solution)
|
action_plan = self.do_schedule(request_context, audit, solution)
|
||||||
# change state of the audit to SUCCEEDED
|
a_plan_filters = {'state': objects.action_plan.State.ONGOING}
|
||||||
self.update_audit_state(audit, objects.audit.State.SUCCEEDED)
|
ongoing_action_plans = objects.ActionPlan.list(
|
||||||
|
request_context, filters=a_plan_filters)
|
||||||
|
if ongoing_action_plans:
|
||||||
|
action_plan.state = objects.action_plan.State.SUPERSEDED
|
||||||
|
action_plan.save()
|
||||||
|
raise exception.ActionPlanIsOngoing(
|
||||||
|
action_plan=ongoing_action_plans[0].uuid,
|
||||||
|
new_action_plan=action_plan.uuid)
|
||||||
|
elif audit.auto_trigger:
|
||||||
|
applier_client = rpcapi.ApplierAPI()
|
||||||
|
applier_client.launch_action_plan(request_context,
|
||||||
|
action_plan.uuid)
|
||||||
|
|
||||||
def execute(self, audit, request_context):
|
def execute(self, audit, request_context):
|
||||||
try:
|
try:
|
||||||
self.pre_execute(audit, request_context)
|
self.pre_execute(audit, request_context)
|
||||||
solution = self.do_execute(audit, request_context)
|
solution = self.do_execute(audit, request_context)
|
||||||
self.post_execute(audit, solution, request_context)
|
self.post_execute(audit, solution, request_context)
|
||||||
|
except exception.ActionPlanIsOngoing as e:
|
||||||
|
LOG.exception(e)
|
||||||
|
if audit.audit_type == objects.audit.AuditType.ONESHOT.value:
|
||||||
|
self.update_audit_state(audit, objects.audit.State.CANCELLED)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.exception(e)
|
LOG.exception(e)
|
||||||
self.update_audit_state(audit, objects.audit.State.FAILED)
|
self.update_audit_state(audit, objects.audit.State.FAILED)
|
||||||
|
|||||||
@@ -82,9 +82,6 @@ class ContinuousAuditHandler(base.AuditHandler):
|
|||||||
if not self._is_audit_inactive(audit):
|
if not self._is_audit_inactive(audit):
|
||||||
self.execute(audit, request_context)
|
self.execute(audit, request_context)
|
||||||
|
|
||||||
def post_execute(self, audit, solution, request_context):
|
|
||||||
self.do_schedule(request_context, audit, solution)
|
|
||||||
|
|
||||||
def launch_audits_periodically(self):
|
def launch_audits_periodically(self):
|
||||||
audit_context = context.RequestContext(is_admin=True)
|
audit_context = context.RequestContext(is_admin=True)
|
||||||
audit_filters = {
|
audit_filters = {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from watcher.decision_engine.audit import base
|
from watcher.decision_engine.audit import base
|
||||||
|
from watcher import objects
|
||||||
|
|
||||||
|
|
||||||
class OneShotAuditHandler(base.AuditHandler):
|
class OneShotAuditHandler(base.AuditHandler):
|
||||||
@@ -24,3 +25,9 @@ class OneShotAuditHandler(base.AuditHandler):
|
|||||||
audit, request_context)
|
audit, request_context)
|
||||||
|
|
||||||
return solution
|
return solution
|
||||||
|
|
||||||
|
def post_execute(self, audit, solution, request_context):
|
||||||
|
super(OneShotAuditHandler, self).post_execute(audit, solution,
|
||||||
|
request_context)
|
||||||
|
# change state of the audit to SUCCEEDED
|
||||||
|
self.update_audit_state(audit, objects.audit.State.SUCCEEDED)
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ class State(object):
|
|||||||
SUCCEEDED = 'SUCCEEDED'
|
SUCCEEDED = 'SUCCEEDED'
|
||||||
DELETED = 'DELETED'
|
DELETED = 'DELETED'
|
||||||
CANCELLED = 'CANCELLED'
|
CANCELLED = 'CANCELLED'
|
||||||
|
SUPERSEDED = 'SUPERSEDED'
|
||||||
|
|
||||||
|
|
||||||
@base.WatcherObjectRegistry.register
|
@base.WatcherObjectRegistry.register
|
||||||
|
|||||||
@@ -79,7 +79,8 @@ class Audit(base.WatcherPersistentObject, base.WatcherObject,
|
|||||||
|
|
||||||
# Version 1.0: Initial version
|
# Version 1.0: Initial version
|
||||||
# Version 1.1: Added 'goal' and 'strategy' object field
|
# Version 1.1: Added 'goal' and 'strategy' object field
|
||||||
VERSION = '1.1'
|
# Version 1.2 Added 'auto_trigger' boolean field
|
||||||
|
VERSION = '1.2'
|
||||||
|
|
||||||
dbapi = db_api.get_instance()
|
dbapi = db_api.get_instance()
|
||||||
|
|
||||||
@@ -93,6 +94,7 @@ class Audit(base.WatcherPersistentObject, base.WatcherObject,
|
|||||||
'scope': wfields.FlexibleListOfDictField(nullable=True),
|
'scope': wfields.FlexibleListOfDictField(nullable=True),
|
||||||
'goal_id': wfields.IntegerField(),
|
'goal_id': wfields.IntegerField(),
|
||||||
'strategy_id': wfields.IntegerField(nullable=True),
|
'strategy_id': wfields.IntegerField(nullable=True),
|
||||||
|
'auto_trigger': wfields.BooleanField(),
|
||||||
|
|
||||||
'goal': wfields.ObjectField('Goal', nullable=True),
|
'goal': wfields.ObjectField('Goal', nullable=True),
|
||||||
'strategy': wfields.ObjectField('Strategy', nullable=True),
|
'strategy': wfields.ObjectField('Strategy', nullable=True),
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ def get_test_audit(**kwargs):
|
|||||||
'goal_id': kwargs.get('goal_id', 1),
|
'goal_id': kwargs.get('goal_id', 1),
|
||||||
'strategy_id': kwargs.get('strategy_id', None),
|
'strategy_id': kwargs.get('strategy_id', None),
|
||||||
'scope': kwargs.get('scope', []),
|
'scope': kwargs.get('scope', []),
|
||||||
|
'auto_trigger': kwargs.get('auto_trigger', False)
|
||||||
}
|
}
|
||||||
# ObjectField doesn't allow None nor dict, so if we want to simulate a
|
# 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.
|
# non-eager object loading, the field should not be referenced at all.
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ from apscheduler.schedulers import background
|
|||||||
import mock
|
import mock
|
||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
|
|
||||||
|
from watcher.applier import rpcapi
|
||||||
|
from watcher.common import exception
|
||||||
from watcher.decision_engine.audit import continuous
|
from watcher.decision_engine.audit import continuous
|
||||||
from watcher.decision_engine.audit import oneshot
|
from watcher.decision_engine.audit import oneshot
|
||||||
from watcher.decision_engine.model.collector import manager
|
from watcher.decision_engine.model.collector import manager
|
||||||
@@ -150,6 +152,65 @@ class TestOneShotAuditHandler(base.DbTestCase):
|
|||||||
self.m_audit_notifications.send_action_notification.call_args_list)
|
self.m_audit_notifications.send_action_notification.call_args_list)
|
||||||
|
|
||||||
|
|
||||||
|
class TestAutoTriggerActionPlan(base.DbTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestAutoTriggerActionPlan, self).setUp()
|
||||||
|
self.goal = obj_utils.create_test_goal(
|
||||||
|
self.context, id=1, name=dummy_strategy.DummyStrategy.get_name())
|
||||||
|
self.strategy = obj_utils.create_test_strategy(
|
||||||
|
self.context, name=dummy_strategy.DummyStrategy.get_name(),
|
||||||
|
goal_id=self.goal.id)
|
||||||
|
audit_template = obj_utils.create_test_audit_template(
|
||||||
|
self.context)
|
||||||
|
self.audit = obj_utils.create_test_audit(
|
||||||
|
self.context,
|
||||||
|
id=0,
|
||||||
|
uuid=uuidutils.generate_uuid(),
|
||||||
|
audit_template_id=audit_template.id,
|
||||||
|
goal_id=self.goal.id,
|
||||||
|
audit_type=objects.audit.AuditType.CONTINUOUS.value,
|
||||||
|
goal=self.goal,
|
||||||
|
auto_trigger=True)
|
||||||
|
self.ongoing_action_plan = obj_utils.create_test_action_plan(
|
||||||
|
self.context,
|
||||||
|
uuid=uuidutils.generate_uuid(),
|
||||||
|
audit_id=self.audit.id)
|
||||||
|
self.recommended_action_plan = obj_utils.create_test_action_plan(
|
||||||
|
self.context,
|
||||||
|
uuid=uuidutils.generate_uuid(),
|
||||||
|
state=objects.action_plan.State.ONGOING,
|
||||||
|
audit_id=self.audit.id
|
||||||
|
)
|
||||||
|
|
||||||
|
@mock.patch.object(objects.action_plan.ActionPlan, 'list')
|
||||||
|
@mock.patch.object(objects.audit.Audit, 'get_by_id')
|
||||||
|
def test_trigger_action_plan_with_ongoing(self, mock_get_by_id, mock_list):
|
||||||
|
mock_get_by_id.return_value = self.audit
|
||||||
|
mock_list.return_value = [self.ongoing_action_plan]
|
||||||
|
auto_trigger_handler = oneshot.OneShotAuditHandler(mock.MagicMock())
|
||||||
|
with mock.patch.object(auto_trigger_handler, 'do_schedule'):
|
||||||
|
self.assertRaises(exception.ActionPlanIsOngoing,
|
||||||
|
auto_trigger_handler.post_execute,
|
||||||
|
self.audit, mock.MagicMock(), self.context)
|
||||||
|
|
||||||
|
@mock.patch.object(rpcapi.ApplierAPI, 'launch_action_plan')
|
||||||
|
@mock.patch.object(objects.action_plan.ActionPlan, 'list')
|
||||||
|
@mock.patch.object(objects.audit.Audit, 'get_by_id')
|
||||||
|
def test_trigger_action_plan_without_ongoing(self, mock_get_by_id,
|
||||||
|
mock_list, mock_applier):
|
||||||
|
mock_get_by_id.return_value = self.audit
|
||||||
|
mock_list.return_value = []
|
||||||
|
auto_trigger_handler = oneshot.OneShotAuditHandler(mock.MagicMock())
|
||||||
|
with mock.patch.object(auto_trigger_handler, 'do_schedule',
|
||||||
|
new_callable=mock.PropertyMock) as m_schedule:
|
||||||
|
m_schedule().uuid = self.recommended_action_plan.uuid
|
||||||
|
auto_trigger_handler.post_execute(self.audit, mock.MagicMock(),
|
||||||
|
self.context)
|
||||||
|
mock_applier.assert_called_once_with(self.context,
|
||||||
|
self.recommended_action_plan.uuid)
|
||||||
|
|
||||||
|
|
||||||
class TestContinuousAuditHandler(base.DbTestCase):
|
class TestContinuousAuditHandler(base.DbTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|||||||
@@ -330,21 +330,21 @@ class TestSyncer(base.DbTestCase):
|
|||||||
self.ctx, id=1, uuid=utils.generate_uuid(),
|
self.ctx, id=1, uuid=utils.generate_uuid(),
|
||||||
audit_type=objects.audit.AuditType.ONESHOT.value,
|
audit_type=objects.audit.AuditType.ONESHOT.value,
|
||||||
state=objects.audit.State.PENDING,
|
state=objects.audit.State.PENDING,
|
||||||
goal_id=goal1.id, strategy_id=strategy1.id)
|
goal_id=goal1.id, strategy_id=strategy1.id, auto_trigger=False)
|
||||||
# Should be modified by the sync() because its associated goal
|
# Should be modified by the sync() because its associated goal
|
||||||
# has been modified (compared to the defined fake goals)
|
# has been modified (compared to the defined fake goals)
|
||||||
audit2 = objects.Audit(
|
audit2 = objects.Audit(
|
||||||
self.ctx, id=2, uuid=utils.generate_uuid(),
|
self.ctx, id=2, uuid=utils.generate_uuid(),
|
||||||
audit_type=objects.audit.AuditType.ONESHOT.value,
|
audit_type=objects.audit.AuditType.ONESHOT.value,
|
||||||
state=objects.audit.State.PENDING,
|
state=objects.audit.State.PENDING,
|
||||||
goal_id=goal2.id, strategy_id=strategy2.id)
|
goal_id=goal2.id, strategy_id=strategy2.id, auto_trigger=False)
|
||||||
# Should be modified by the sync() because its associated strategy
|
# Should be modified by the sync() because its associated strategy
|
||||||
# has been modified (compared to the defined fake strategies)
|
# has been modified (compared to the defined fake strategies)
|
||||||
audit3 = objects.Audit(
|
audit3 = objects.Audit(
|
||||||
self.ctx, id=3, uuid=utils.generate_uuid(),
|
self.ctx, id=3, uuid=utils.generate_uuid(),
|
||||||
audit_type=objects.audit.AuditType.ONESHOT.value,
|
audit_type=objects.audit.AuditType.ONESHOT.value,
|
||||||
state=objects.audit.State.PENDING,
|
state=objects.audit.State.PENDING,
|
||||||
goal_id=goal1.id, strategy_id=strategy3.id)
|
goal_id=goal1.id, strategy_id=strategy3.id, auto_trigger=False)
|
||||||
# Modified because of both because its associated goal and associated
|
# Modified because of both because its associated goal and associated
|
||||||
# strategy should be modified (compared to the defined fake
|
# strategy should be modified (compared to the defined fake
|
||||||
# goals/strategies)
|
# goals/strategies)
|
||||||
@@ -352,7 +352,7 @@ class TestSyncer(base.DbTestCase):
|
|||||||
self.ctx, id=4, uuid=utils.generate_uuid(),
|
self.ctx, id=4, uuid=utils.generate_uuid(),
|
||||||
audit_type=objects.audit.AuditType.ONESHOT.value,
|
audit_type=objects.audit.AuditType.ONESHOT.value,
|
||||||
state=objects.audit.State.PENDING,
|
state=objects.audit.State.PENDING,
|
||||||
goal_id=goal2.id, strategy_id=strategy4.id)
|
goal_id=goal2.id, strategy_id=strategy4.id, auto_trigger=False)
|
||||||
|
|
||||||
audit1.create()
|
audit1.create()
|
||||||
audit2.create()
|
audit2.create()
|
||||||
@@ -560,13 +560,13 @@ class TestSyncer(base.DbTestCase):
|
|||||||
self.ctx, id=1, uuid=utils.generate_uuid(),
|
self.ctx, id=1, uuid=utils.generate_uuid(),
|
||||||
audit_type=objects.audit.AuditType.ONESHOT.value,
|
audit_type=objects.audit.AuditType.ONESHOT.value,
|
||||||
state=objects.audit.State.PENDING,
|
state=objects.audit.State.PENDING,
|
||||||
goal_id=goal1.id, strategy_id=strategy1.id)
|
goal_id=goal1.id, strategy_id=strategy1.id, auto_trigger=False)
|
||||||
# Stale after syncing because the goal has been soft deleted
|
# Stale after syncing because the goal has been soft deleted
|
||||||
audit2 = objects.Audit(
|
audit2 = objects.Audit(
|
||||||
self.ctx, id=2, uuid=utils.generate_uuid(),
|
self.ctx, id=2, uuid=utils.generate_uuid(),
|
||||||
audit_type=objects.audit.AuditType.ONESHOT.value,
|
audit_type=objects.audit.AuditType.ONESHOT.value,
|
||||||
state=objects.audit.State.PENDING,
|
state=objects.audit.State.PENDING,
|
||||||
goal_id=goal2.id, strategy_id=strategy2.id)
|
goal_id=goal2.id, strategy_id=strategy2.id, auto_trigger=False)
|
||||||
audit1.create()
|
audit1.create()
|
||||||
audit2.create()
|
audit2.create()
|
||||||
|
|
||||||
|
|||||||
@@ -412,7 +412,7 @@ expected_object_fingerprints = {
|
|||||||
'Goal': '1.0-93881622db05e7b67a65ca885b4a022e',
|
'Goal': '1.0-93881622db05e7b67a65ca885b4a022e',
|
||||||
'Strategy': '1.1-73f164491bdd4c034f48083a51bdeb7b',
|
'Strategy': '1.1-73f164491bdd4c034f48083a51bdeb7b',
|
||||||
'AuditTemplate': '1.1-b291973ffc5efa2c61b24fe34fdccc0b',
|
'AuditTemplate': '1.1-b291973ffc5efa2c61b24fe34fdccc0b',
|
||||||
'Audit': '1.1-dc246337c8d511646cb537144fcb0f3a',
|
'Audit': '1.2-910522db78b7b1cb59df614754656db4',
|
||||||
'ActionPlan': '1.2-42709eadf6b2bd228ea87817e8c3e31e',
|
'ActionPlan': '1.2-42709eadf6b2bd228ea87817e8c3e31e',
|
||||||
'Action': '1.1-52c77e4db4ce0aa9480c9760faec61a1',
|
'Action': '1.1-52c77e4db4ce0aa9480c9760faec61a1',
|
||||||
'EfficacyIndicator': '1.0-655b71234a82bc7478aff964639c4bb0',
|
'EfficacyIndicator': '1.0-655b71234a82bc7478aff964639c4bb0',
|
||||||
|
|||||||
Reference in New Issue
Block a user