Added strategy ID + Action Plan syncing

In this changeset, I implemented the logic which cancels
any audit or action plan whose goal has been re-synced
(upon restarting the Decision Engine).

Partially Implements: blueprint efficacy-indicator

Change-Id: I95d2739eb552d4a7a02c822b11844591008f648e
This commit is contained in:
Vincent Françoise
2016-06-03 12:25:18 +02:00
parent 64f45add5f
commit 6be758bc5a
21 changed files with 571 additions and 239 deletions

View File

@@ -73,6 +73,7 @@ from watcher.api.controllers.v1 import utils as api_utils
from watcher.applier import rpcapi from watcher.applier import rpcapi
from watcher.common import exception from watcher.common import exception
from watcher.common import policy from watcher.common import policy
from watcher.common import utils
from watcher import objects from watcher import objects
from watcher.objects import action_plan as ap_objects from watcher.objects import action_plan as ap_objects
@@ -117,6 +118,8 @@ class ActionPlan(base.APIBase):
""" """
_audit_uuid = None _audit_uuid = None
_strategy_uuid = None
_strategy_name = None
_first_action_uuid = None _first_action_uuid = None
_efficacy_indicators = None _efficacy_indicators = None
@@ -177,6 +180,43 @@ class ActionPlan(base.APIBase):
elif value and self._efficacy_indicators != value: elif value and self._efficacy_indicators != value:
self._efficacy_indicators = value self._efficacy_indicators = value
def _get_strategy(self, value):
if value == wtypes.Unset:
return None
strategy = None
try:
if utils.is_uuid_like(value) or utils.is_int_like(value):
strategy = objects.Strategy.get(
pecan.request.context, value)
else:
strategy = objects.Strategy.get_by_name(
pecan.request.context, value)
except exception.StrategyNotFound:
pass
if strategy:
self.strategy_id = strategy.id
return strategy
def _get_strategy_uuid(self):
return self._strategy_uuid
def _set_strategy_uuid(self, value):
if value and self._strategy_uuid != value:
self._strategy_uuid = None
strategy = self._get_strategy(value)
if strategy:
self._strategy_uuid = strategy.uuid
def _get_strategy_name(self):
return self._strategy_name
def _set_strategy_name(self, value):
if value and self._strategy_name != value:
self._strategy_name = None
strategy = self._get_strategy(value)
if strategy:
self._strategy_name = strategy.name
uuid = wtypes.wsattr(types.uuid, readonly=True) uuid = wtypes.wsattr(types.uuid, readonly=True)
"""Unique UUID for this action plan""" """Unique UUID for this action plan"""
@@ -189,6 +229,14 @@ class ActionPlan(base.APIBase):
mandatory=True) mandatory=True)
"""The UUID of the audit this port belongs to""" """The UUID of the audit this port belongs to"""
strategy_uuid = wsme.wsproperty(
wtypes.text, _get_strategy_uuid, _set_strategy_uuid, mandatory=False)
"""Strategy UUID the action plan refers to"""
strategy_name = wsme.wsproperty(
wtypes.text, _get_strategy_name, _set_strategy_name, mandatory=False)
"""The name of the strategy this action plan refers to"""
efficacy_indicators = wsme.wsproperty( efficacy_indicators = wsme.wsproperty(
types.jsontype, _get_efficacy_indicators, _set_efficacy_indicators, types.jsontype, _get_efficacy_indicators, _set_efficacy_indicators,
mandatory=True) mandatory=True)
@@ -219,6 +267,10 @@ class ActionPlan(base.APIBase):
self.fields.append('efficacy_indicators') self.fields.append('efficacy_indicators')
setattr(self, 'audit_uuid', kwargs.get('audit_id', wtypes.Unset)) setattr(self, 'audit_uuid', kwargs.get('audit_id', wtypes.Unset))
fields.append('strategy_uuid')
setattr(self, 'strategy_uuid', kwargs.get('strategy_id', wtypes.Unset))
fields.append('strategy_name')
setattr(self, 'strategy_name', kwargs.get('strategy_id', wtypes.Unset))
setattr(self, 'first_action_uuid', setattr(self, 'first_action_uuid',
kwargs.get('first_action_id', wtypes.Unset)) kwargs.get('first_action_id', wtypes.Unset))
@@ -227,7 +279,8 @@ class ActionPlan(base.APIBase):
if not expand: if not expand:
action_plan.unset_fields_except( action_plan.unset_fields_except(
['uuid', 'state', 'efficacy_indicators', 'global_efficacy', ['uuid', 'state', 'efficacy_indicators', 'global_efficacy',
'updated_at', 'audit_uuid', 'first_action_uuid']) 'updated_at', 'audit_uuid', 'strategy_uuid', 'strategy_name',
'first_action_uuid'])
action_plan.links = [ action_plan.links = [
link.Link.make_link( link.Link.make_link(
@@ -275,8 +328,8 @@ class ActionPlanCollection(collection.Collection):
@staticmethod @staticmethod
def convert_with_links(rpc_action_plans, limit, url=None, expand=False, def convert_with_links(rpc_action_plans, limit, url=None, expand=False,
**kwargs): **kwargs):
collection = ActionPlanCollection() ap_collection = ActionPlanCollection()
collection.action_plans = [ActionPlan.convert_with_links( ap_collection.action_plans = [ActionPlan.convert_with_links(
p, expand) for p in rpc_action_plans] p, expand) for p in rpc_action_plans]
if 'sort_key' in kwargs: if 'sort_key' in kwargs:
@@ -284,13 +337,13 @@ class ActionPlanCollection(collection.Collection):
if kwargs['sort_key'] == 'audit_uuid': if kwargs['sort_key'] == 'audit_uuid':
if 'sort_dir' in kwargs: if 'sort_dir' in kwargs:
reverse = True if kwargs['sort_dir'] == 'desc' else False reverse = True if kwargs['sort_dir'] == 'desc' else False
collection.action_plans = sorted( ap_collection.action_plans = sorted(
collection.action_plans, ap_collection.action_plans,
key=lambda action_plan: action_plan.audit_uuid, key=lambda action_plan: action_plan.audit_uuid,
reverse=reverse) reverse=reverse)
collection.next = collection.get_next(limit, url=url, **kwargs) ap_collection.next = ap_collection.get_next(limit, url=url, **kwargs)
return collection return ap_collection
@classmethod @classmethod
def sample(cls): def sample(cls):
@@ -301,6 +354,7 @@ class ActionPlanCollection(collection.Collection):
class ActionPlansController(rest.RestController): class ActionPlansController(rest.RestController):
"""REST controller for Actions.""" """REST controller for Actions."""
def __init__(self): def __init__(self):
super(ActionPlansController, self).__init__() super(ActionPlansController, self).__init__()
@@ -314,7 +368,8 @@ class ActionPlansController(rest.RestController):
def _get_action_plans_collection(self, marker, limit, def _get_action_plans_collection(self, marker, limit,
sort_key, sort_dir, expand=False, sort_key, sort_dir, expand=False,
resource_url=None, audit_uuid=None): resource_url=None, audit_uuid=None,
strategy=None):
limit = api_utils.validate_limit(limit) limit = api_utils.validate_limit(limit)
api_utils.validate_sort_dir(sort_dir) api_utils.validate_sort_dir(sort_dir)
@@ -328,6 +383,12 @@ class ActionPlansController(rest.RestController):
if audit_uuid: if audit_uuid:
filters['audit_uuid'] = audit_uuid filters['audit_uuid'] = audit_uuid
if strategy:
if utils.is_uuid_like(strategy):
filters['strategy_uuid'] = strategy
else:
filters['strategy_name'] = strategy
if sort_key == 'audit_uuid': if sort_key == 'audit_uuid':
sort_db_key = None sort_db_key = None
else: else:
@@ -347,9 +408,9 @@ class ActionPlansController(rest.RestController):
sort_dir=sort_dir) sort_dir=sort_dir)
@wsme_pecan.wsexpose(ActionPlanCollection, types.uuid, int, wtypes.text, @wsme_pecan.wsexpose(ActionPlanCollection, types.uuid, int, wtypes.text,
wtypes.text, types.uuid) wtypes.text, types.uuid, wtypes.text)
def get_all(self, marker=None, limit=None, def get_all(self, marker=None, limit=None,
sort_key='id', sort_dir='asc', audit_uuid=None): sort_key='id', sort_dir='asc', audit_uuid=None, strategy=None):
"""Retrieve a list of action plans. """Retrieve a list of action plans.
:param marker: pagination marker for large data sets. :param marker: pagination marker for large data sets.
@@ -358,18 +419,20 @@ class ActionPlansController(rest.RestController):
:param sort_dir: direction to sort. "asc" or "desc". Default: asc. :param sort_dir: direction to sort. "asc" or "desc". Default: asc.
:param audit_uuid: Optional UUID of an audit, to get only actions :param audit_uuid: Optional UUID of an audit, to get only actions
for that audit. for that audit.
:param strategy: strategy UUID or name to filter by
""" """
context = pecan.request.context context = pecan.request.context
policy.enforce(context, 'action_plan:get_all', policy.enforce(context, 'action_plan:get_all',
action='action_plan:get_all') action='action_plan:get_all')
return self._get_action_plans_collection( return self._get_action_plans_collection(
marker, limit, sort_key, sort_dir, audit_uuid=audit_uuid) marker, limit, sort_key, sort_dir,
audit_uuid=audit_uuid, strategy=strategy)
@wsme_pecan.wsexpose(ActionPlanCollection, types.uuid, int, wtypes.text, @wsme_pecan.wsexpose(ActionPlanCollection, types.uuid, int, wtypes.text,
wtypes.text, types.uuid) wtypes.text, types.uuid, wtypes.text)
def detail(self, marker=None, limit=None, def detail(self, marker=None, limit=None,
sort_key='id', sort_dir='asc', audit_uuid=None): sort_key='id', sort_dir='asc', audit_uuid=None, strategy=None):
"""Retrieve a list of action_plans with detail. """Retrieve a list of action_plans with detail.
:param marker: pagination marker for large data sets. :param marker: pagination marker for large data sets.
@@ -378,6 +441,7 @@ class ActionPlansController(rest.RestController):
:param sort_dir: direction to sort. "asc" or "desc". Default: asc. :param sort_dir: direction to sort. "asc" or "desc". Default: asc.
:param audit_uuid: Optional UUID of an audit, to get only actions :param audit_uuid: Optional UUID of an audit, to get only actions
for that audit. for that audit.
:param strategy: strategy UUID or name to filter by
""" """
context = pecan.request.context context = pecan.request.context
policy.enforce(context, 'action_plan:detail', policy.enforce(context, 'action_plan:detail',
@@ -391,9 +455,8 @@ class ActionPlansController(rest.RestController):
expand = True expand = True
resource_url = '/'.join(['action_plans', 'detail']) resource_url = '/'.join(['action_plans', 'detail'])
return self._get_action_plans_collection( return self._get_action_plans_collection(
marker, limit, marker, limit, sort_key, sort_dir, expand,
sort_key, sort_dir, expand, resource_url, audit_uuid=audit_uuid, strategy=strategy)
resource_url, audit_uuid=audit_uuid)
@wsme_pecan.wsexpose(ActionPlan, types.uuid) @wsme_pecan.wsexpose(ActionPlan, types.uuid)
def get_one(self, action_plan_uuid): def get_one(self, action_plan_uuid):
@@ -491,8 +554,8 @@ class ActionPlansController(rest.RestController):
if action_plan_to_update[field] != patch_val: if action_plan_to_update[field] != patch_val:
action_plan_to_update[field] = patch_val action_plan_to_update[field] = patch_val
if (field == 'state' if (field == 'state'and
and patch_val == objects.action_plan.State.PENDING): patch_val == objects.action_plan.State.PENDING):
launch_action_plan = True launch_action_plan = True
action_plan_to_update.save() action_plan_to_update.save()

View File

@@ -198,7 +198,7 @@ class Audit(base.APIBase):
else: else:
strategy = objects.Strategy.get_by_name( strategy = objects.Strategy.get_by_name(
pecan.request.context, value) pecan.request.context, value)
except exception.GoalNotFound: except exception.StrategyNotFound:
pass pass
if strategy: if strategy:
self.strategy_id = strategy.id self.strategy_id = strategy.id

View File

@@ -220,7 +220,8 @@ class PurgeCommand(object):
if audit not in orphans.audits] if audit not in orphans.audits]
orphans.action_plans = [ orphans.action_plans = [
ap for ap in action_plans ap for ap in action_plans
if ap.audit_id not in audit_ids] if ap.audit_id not in audit_ids or
ap.strategy_id not in strategy_ids]
# Objects with orphan parents are themselves orphans # Objects with orphan parents are themselves orphans
action_plan_ids = [ap.id for ap in action_plans action_plan_ids = [ap.id for ap in action_plans

View File

@@ -347,10 +347,15 @@ class Connection(api.BaseConnection):
if filters is None: if filters is None:
filters = {} filters = {}
plain_fields = ['uuid', 'state', 'audit_id'] plain_fields = ['uuid', 'state', 'audit_id', 'strategy_id']
join_fieldmap = { join_fieldmap = JoinMap(
'audit_uuid': ("uuid", models.Audit), audit_uuid=NaturalJoinFilter(
} join_fieldname="uuid", join_model=models.Audit),
strategy_uuid=NaturalJoinFilter(
join_fieldname="uuid", join_model=models.Strategy),
strategy_name=NaturalJoinFilter(
join_fieldname="name", join_model=models.Strategy),
)
return self._add_filters( return self._add_filters(
query=query, model=models.ActionPlan, filters=filters, query=query, model=models.ActionPlan, filters=filters,

View File

@@ -211,7 +211,8 @@ class ActionPlan(Base):
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
uuid = Column(String(36)) uuid = Column(String(36))
first_action_id = Column(Integer) first_action_id = Column(Integer)
audit_id = Column(Integer, ForeignKey('audits.id'), nullable=True) audit_id = Column(Integer, ForeignKey('audits.id'), nullable=False)
strategy_id = Column(Integer, ForeignKey('strategies.id'), nullable=False)
state = Column(String(20), nullable=True) state = Column(String(20), nullable=True)
global_efficacy = Column(JSONEncodedDict, nullable=True) global_efficacy = Column(JSONEncodedDict, nullable=True)

View File

@@ -74,8 +74,8 @@ class ContinuousAuditHandler(base.AuditHandler):
def do_execute(self, audit, request_context): def do_execute(self, audit, request_context):
# execute the strategy # execute the strategy
solution = self.strategy_context.execute_strategy(audit.uuid, solution = self.strategy_context.execute_strategy(
request_context) audit, request_context)
if audit.audit_type == audit_objects.AuditType.CONTINUOUS.value: if audit.audit_type == audit_objects.AuditType.CONTINUOUS.value:
a_plan_filters = {'audit_uuid': audit.uuid, a_plan_filters = {'audit_uuid': audit.uuid,

View File

@@ -20,7 +20,7 @@ from watcher.decision_engine.audit import base
class OneShotAuditHandler(base.AuditHandler): class OneShotAuditHandler(base.AuditHandler):
def do_execute(self, audit, request_context): def do_execute(self, audit, request_context):
# execute the strategy # execute the strategy
solution = self.strategy_context.execute_strategy(audit.uuid, solution = self.strategy_context.execute_strategy(
request_context) audit, request_context)
return solution return solution

View File

@@ -48,10 +48,11 @@ class DefaultPlanner(base.BasePlanner):
@classmethod @classmethod
def get_config_opts(cls): def get_config_opts(cls):
return [cfg.DictOpt( return [
'weights', cfg.DictOpt(
help="These weights are used to schedule the actions", 'weights',
default=cls.weights_dict), help="These weights are used to schedule the actions",
default=cls.weights_dict),
] ]
def create_action(self, def create_action(self,
@@ -113,9 +114,13 @@ class DefaultPlanner(base.BasePlanner):
return action_plan return action_plan
def _create_action_plan(self, context, audit_id, solution): def _create_action_plan(self, context, audit_id, solution):
strategy = objects.Strategy.get_by_name(
context, solution.strategy.name)
action_plan_dict = { action_plan_dict = {
'uuid': utils.generate_uuid(), 'uuid': utils.generate_uuid(),
'audit_id': audit_id, 'audit_id': audit_id,
'strategy_id': strategy.id,
'first_action_id': None, 'first_action_id': None,
'state': objects.action_plan.State.RECOMMENDED, 'state': objects.action_plan.State.RECOMMENDED,
'global_efficacy': solution.global_efficacy, 'global_efficacy': solution.global_efficacy,

View File

@@ -22,6 +22,16 @@ import six
@six.add_metaclass(abc.ABCMeta) @six.add_metaclass(abc.ABCMeta)
class BaseStrategyContext(object): class BaseStrategyContext(object):
@abc.abstractmethod @abc.abstractmethod
def execute_strategy(self, audit_uuid, request_context): def execute_strategy(self, audit, request_context):
"""Execute the strategy for the given an audit
:param audit: Audit object
:type audit: :py:class:`~.objects.audit.Audit` instance
:param request_context: Current request context
:type request_context: :py:class:`~.RequestContext` instance
:returns: The computed solution
:rtype: :py:class:`~.BaseSolution` instance
"""
raise NotImplementedError() raise NotImplementedError()

View File

@@ -30,9 +30,7 @@ class DefaultStrategyContext(base.BaseStrategyContext):
super(DefaultStrategyContext, self).__init__() super(DefaultStrategyContext, self).__init__()
LOG.debug("Initializing Strategy Context") LOG.debug("Initializing Strategy Context")
def execute_strategy(self, audit_uuid, request_context): def execute_strategy(self, audit, request_context):
audit = objects.Audit.get_by_uuid(request_context, audit_uuid)
osc = clients.OpenStackClients() osc = clients.OpenStackClients()
# todo(jed) retrieve in audit parameters (threshold,...) # todo(jed) retrieve in audit parameters (threshold,...)
# todo(jed) create ActionPlan # todo(jed) create ActionPlan

View File

@@ -22,6 +22,8 @@ from watcher._i18n import _LI, _LW
from watcher.common import context from watcher.common import context
from watcher.decision_engine.loading import default from watcher.decision_engine.loading import default
from watcher import objects from watcher import objects
from watcher.objects import action_plan as apobjects
from watcher.objects import audit as auditobjects
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@@ -54,6 +56,8 @@ class Syncer(object):
self.strategy_mapping = dict() self.strategy_mapping = dict()
self.stale_audit_templates_map = {} self.stale_audit_templates_map = {}
self.stale_audits_map = {}
self.stale_action_plans_map = {}
@property @property
def available_goals(self): def available_goals(self):
@@ -118,7 +122,7 @@ class Syncer(object):
self.strategy_mapping.update(self._sync_strategy(strategy_map)) self.strategy_mapping.update(self._sync_strategy(strategy_map))
self._sync_audit_templates() self._sync_objects()
def _sync_goal(self, goal_map): def _sync_goal(self, goal_map):
goal_name = goal_map.name goal_name = goal_map.name
@@ -177,25 +181,45 @@ class Syncer(object):
return strategy_mapping return strategy_mapping
def _sync_audit_templates(self): def _sync_objects(self):
# First we find audit templates that are stale because their associated # First we find audit templates, audits and action plans that are stale
# goal or strategy has been modified and we update them in-memory # because their associated goal or strategy has been modified and we
# update them in-memory
self._find_stale_audit_templates_due_to_goal() self._find_stale_audit_templates_due_to_goal()
self._find_stale_audit_templates_due_to_strategy() self._find_stale_audit_templates_due_to_strategy()
# Then we handle the case where an audit template became self._find_stale_audits_due_to_goal()
# stale because its related goal does not exist anymore. self._find_stale_audits_due_to_strategy()
self._find_stale_action_plans_due_to_strategy()
self._find_stale_action_plans_due_to_audit()
# Then we handle the case where an audit template, an audit or an
# action plan becomes stale because its related goal does not
# exist anymore.
self._soft_delete_removed_goals() self._soft_delete_removed_goals()
# Then we handle the case where an audit template became # Then we handle the case where an audit template, an audit or an
# stale because its related strategy does not exist anymore. # action plan becomes stale because its related strategy does not
# exist anymore.
self._soft_delete_removed_strategies() self._soft_delete_removed_strategies()
# Finally, we save into the DB the updated stale audit templates # Finally, we save into the DB the updated stale audit templates
# and soft delete stale audits and action plans
for stale_audit_template in self.stale_audit_templates_map.values(): for stale_audit_template in self.stale_audit_templates_map.values():
stale_audit_template.save() stale_audit_template.save()
LOG.info(_LI("Audit Template '%s' synced"), LOG.info(_LI("Audit Template '%s' synced"),
stale_audit_template.name) stale_audit_template.name)
for stale_audit in self.stale_audits_map.values():
stale_audit.save()
LOG.info(_LI("Stale audit '%s' synced and cancelled"),
stale_audit.uuid)
for stale_action_plan in self.stale_action_plans_map.values():
stale_action_plan.save()
LOG.info(_LI("Stale action plan '%s' synced and cancelled"),
stale_action_plan.uuid)
def _find_stale_audit_templates_due_to_goal(self): def _find_stale_audit_templates_due_to_goal(self):
for goal_id, synced_goal in self.goal_mapping.items(): for goal_id, synced_goal in self.goal_mapping.items():
filters = {"goal_id": goal_id} filters = {"goal_id": goal_id}
@@ -228,6 +252,72 @@ class Syncer(object):
self.stale_audit_templates_map[ self.stale_audit_templates_map[
audit_template.id].strategy_id = synced_strategy.id audit_template.id].strategy_id = synced_strategy.id
def _find_stale_audits_due_to_goal(self):
for goal_id, synced_goal in self.goal_mapping.items():
filters = {"goal_id": goal_id}
stale_audits = objects.Audit.list(
self.ctx, filters=filters)
# Update the goal ID for the stale audits (w/o saving)
for audit in stale_audits:
if audit.id not in self.stale_audits_map:
audit.goal_id = synced_goal.id
self.stale_audits_map[audit.id] = audit
else:
self.stale_audits_map[audit.id].goal_id = synced_goal.id
def _find_stale_audits_due_to_strategy(self):
for strategy_id, synced_strategy in self.strategy_mapping.items():
filters = {"strategy_id": strategy_id}
stale_audits = objects.Audit.list(self.ctx, filters=filters)
# Update strategy IDs for all stale audits (w/o saving)
for audit in stale_audits:
if audit.id not in self.stale_audits_map:
audit.strategy_id = synced_strategy.id
audit.state = auditobjects.State.CANCELLED
self.stale_audits_map[audit.id] = audit
else:
self.stale_audits_map[
audit.id].strategy_id = synced_strategy.id
self.stale_audits_map[
audit.id].state = auditobjects.State.CANCELLED
def _find_stale_action_plans_due_to_strategy(self):
for strategy_id, synced_strategy in self.strategy_mapping.items():
filters = {"strategy_id": strategy_id}
stale_action_plans = objects.ActionPlan.list(
self.ctx, filters=filters)
# Update strategy IDs for all stale action plans (w/o saving)
for action_plan in stale_action_plans:
if action_plan.id not in self.stale_action_plans_map:
action_plan.strategy_id = synced_strategy.id
action_plan.state = apobjects.State.CANCELLED
self.stale_action_plans_map[action_plan.id] = action_plan
else:
self.stale_action_plans_map[
action_plan.id].strategy_id = synced_strategy.id
self.stale_action_plans_map[
action_plan.id].state = apobjects.State.CANCELLED
def _find_stale_action_plans_due_to_audit(self):
for audit_id, synced_audit in self.stale_audits_map.items():
filters = {"audit_id": audit_id}
stale_action_plans = objects.ActionPlan.list(
self.ctx, filters=filters)
# Update audit IDs for all stale action plans (w/o saving)
for action_plan in stale_action_plans:
if action_plan.id not in self.stale_action_plans_map:
action_plan.audit_id = synced_audit.id
action_plan.state = apobjects.State.CANCELLED
self.stale_action_plans_map[action_plan.id] = action_plan
else:
self.stale_action_plans_map[
action_plan.id].audit_id = synced_audit.id
self.stale_action_plans_map[
action_plan.id].state = apobjects.State.CANCELLED
def _soft_delete_removed_goals(self): def _soft_delete_removed_goals(self):
removed_goals = [ removed_goals = [
g for g in self.available_goals g for g in self.available_goals
@@ -235,12 +325,24 @@ class Syncer(object):
for removed_goal in removed_goals: for removed_goal in removed_goals:
removed_goal.soft_delete() removed_goal.soft_delete()
filters = {"goal_id": removed_goal.id} filters = {"goal_id": removed_goal.id}
invalid_ats = objects.AuditTemplate.list(self.ctx, filters=filters) invalid_ats = objects.AuditTemplate.list(self.ctx, filters=filters)
for at in invalid_ats: for at in invalid_ats:
LOG.warning( LOG.warning(
_LW("Audit Template '%(audit_template)s' references a " _LW("Audit Template '%(audit_template)s' references a "
"goal that does not exist"), "goal that does not exist"), audit_template=at.uuid)
audit_template=at.uuid)
stale_audits = objects.Audit.list(self.ctx, filters=filters)
for audit in stale_audits:
LOG.warning(
_LW("Audit '%(audit)s' references a "
"goal that does not exist"), audit=audit.uuid)
if audit.id not in self.stale_audits_map:
audit.state = auditobjects.State.CANCELLED
self.stale_audits_map[audit.id] = audit
else:
self.stale_audits_map[
audit.id].state = auditobjects.State.CANCELLED
def _soft_delete_removed_strategies(self): def _soft_delete_removed_strategies(self):
removed_strategies = [ removed_strategies = [
@@ -265,6 +367,32 @@ class Syncer(object):
else: else:
self.stale_audit_templates_map[at.id].strategy_id = None self.stale_audit_templates_map[at.id].strategy_id = None
stale_audits = objects.Audit.list(self.ctx, filters=filters)
for audit in stale_audits:
LOG.warning(
_LW("Audit '%(audit)s' references a "
"strategy that does not exist"), audit=audit.uuid)
if audit.id not in self.stale_audits_map:
audit.state = auditobjects.State.CANCELLED
self.stale_audits_map[audit.id] = audit
else:
self.stale_audits_map[
audit.id].state = auditobjects.State.CANCELLED
stale_action_plans = objects.ActionPlan.list(
self.ctx, filters=filters)
for action_plan in stale_action_plans:
LOG.warning(
_LW("Action Plan '%(action_plan)s' references a "
"strategy that does not exist"),
action_plan=action_plan.uuid)
if action_plan.id not in self.stale_action_plans_map:
action_plan.state = apobjects.State.CANCELLED
self.stale_action_plans_map[action_plan.id] = action_plan
else:
self.stale_action_plans_map[
action_plan.id].state = apobjects.State.CANCELLED
def _discover(self): def _discover(self):
strategies_map = {} strategies_map = {}
goals_map = {} goals_map = {}

View File

@@ -97,6 +97,7 @@ class ActionPlan(base.WatcherObject):
'id': int, 'id': int,
'uuid': obj_utils.str_or_none, 'uuid': obj_utils.str_or_none,
'audit_id': obj_utils.int_or_none, 'audit_id': obj_utils.int_or_none,
'strategy_id': obj_utils.int_or_none,
'first_action_id': obj_utils.int_or_none, 'first_action_id': obj_utils.int_or_none,
'state': obj_utils.str_or_none, 'state': obj_utils.str_or_none,
'global_efficacy': obj_utils.dict_or_none, 'global_efficacy': obj_utils.dict_or_none,
@@ -253,7 +254,7 @@ class ActionPlan(base.WatcherObject):
self[field] = current[field] self[field] = current[field]
def soft_delete(self, context=None): def soft_delete(self, context=None):
"""soft Delete the Action plan from the DB. """Soft Delete the Action plan from the DB.
:param context: Security context. NOTE: This should only :param context: Security context. NOTE: This should only
be used internally by the indirection_api. be used internally by the indirection_api.

View File

@@ -13,39 +13,18 @@
import datetime import datetime
import itertools import itertools
import mock import mock
import pecan
from oslo_config import cfg from oslo_config import cfg
from oslo_serialization import jsonutils from oslo_serialization import jsonutils
from wsme import types as wtypes
from watcher.api.controllers.v1 import action_plan as api_action_plan
from watcher.applier import rpcapi as aapi from watcher.applier import rpcapi as aapi
from watcher.common import context
from watcher.common import utils from watcher.common import utils
from watcher.db import api as db_api from watcher.db import api as db_api
from watcher import objects from watcher import objects
from watcher.tests.api import base as api_base from watcher.tests.api import base as api_base
from watcher.tests.api import utils as api_utils
from watcher.tests import base
from watcher.tests.objects import utils as obj_utils from watcher.tests.objects import utils as obj_utils
class TestActionPlanObject(base.TestCase):
@mock.patch.object(objects.EfficacyIndicator,
'list', mock.Mock(return_value=[]))
@mock.patch.object(pecan, 'request')
def test_action_plan_init(self, m_request):
m_request.context = context.make_context()
act_plan_dict = api_utils.action_plan_post_data()
del act_plan_dict['state']
del act_plan_dict['audit_id']
del act_plan_dict['first_action_id']
act_plan = api_action_plan.ActionPlan(**act_plan_dict)
self.assertEqual(wtypes.Unset, act_plan.state)
class TestListActionPlan(api_base.FunctionalTest): class TestListActionPlan(api_base.FunctionalTest):
def test_empty(self): def test_empty(self):
@@ -53,20 +32,21 @@ class TestListActionPlan(api_base.FunctionalTest):
self.assertEqual([], response['action_plans']) self.assertEqual([], response['action_plans'])
def _assert_action_plans_fields(self, action_plan): def _assert_action_plans_fields(self, action_plan):
action_plan_fields = ['uuid', 'audit_uuid', 'state', 'global_efficacy', action_plan_fields = [
'efficacy_indicators'] 'uuid', 'audit_uuid', 'strategy_uuid', 'strategy_name',
'state', 'global_efficacy', 'efficacy_indicators']
for field in action_plan_fields: for field in action_plan_fields:
self.assertIn(field, action_plan) self.assertIn(field, action_plan)
def test_one(self): def test_one(self):
action_plan = obj_utils.create_action_plan_without_audit(self.context) action_plan = obj_utils.create_test_action_plan(self.context)
response = self.get_json('/action_plans') response = self.get_json('/action_plans')
self.assertEqual(action_plan.uuid, self.assertEqual(action_plan.uuid,
response['action_plans'][0]["uuid"]) response['action_plans'][0]["uuid"])
self._assert_action_plans_fields(response['action_plans'][0]) self._assert_action_plans_fields(response['action_plans'][0])
def test_one_soft_deleted(self): def test_one_soft_deleted(self):
action_plan = obj_utils.create_action_plan_without_audit(self.context) action_plan = obj_utils.create_test_action_plan(self.context)
action_plan.soft_delete() action_plan.soft_delete()
response = self.get_json('/action_plans', response = self.get_json('/action_plans',
headers={'X-Show-Deleted': 'True'}) headers={'X-Show-Deleted': 'True'})
@@ -100,7 +80,7 @@ class TestListActionPlan(api_base.FunctionalTest):
self._assert_action_plans_fields(response) self._assert_action_plans_fields(response)
def test_get_one_soft_deleted(self): def test_get_one_soft_deleted(self):
action_plan = obj_utils.create_action_plan_without_audit(self.context) action_plan = obj_utils.create_test_action_plan(self.context)
action_plan.soft_delete() action_plan.soft_delete()
response = self.get_json('/action_plans/%s' % action_plan['uuid'], response = self.get_json('/action_plans/%s' % action_plan['uuid'],
headers={'X-Show-Deleted': 'True'}) headers={'X-Show-Deleted': 'True'})
@@ -112,15 +92,14 @@ class TestListActionPlan(api_base.FunctionalTest):
self.assertEqual(404, response.status_int) self.assertEqual(404, response.status_int)
def test_detail(self): def test_detail(self):
action_plan = obj_utils.create_test_action_plan(self.context, action_plan = obj_utils.create_test_action_plan(self.context)
audit_id=None)
response = self.get_json('/action_plans/detail') response = self.get_json('/action_plans/detail')
self.assertEqual(action_plan.uuid, self.assertEqual(action_plan.uuid,
response['action_plans'][0]["uuid"]) response['action_plans'][0]["uuid"])
self._assert_action_plans_fields(response['action_plans'][0]) self._assert_action_plans_fields(response['action_plans'][0])
def test_detail_soft_deleted(self): def test_detail_soft_deleted(self):
action_plan = obj_utils.create_action_plan_without_audit(self.context) action_plan = obj_utils.create_test_action_plan(self.context)
action_plan.soft_delete() action_plan.soft_delete()
response = self.get_json('/action_plans/detail', response = self.get_json('/action_plans/detail',
headers={'X-Show-Deleted': 'True'}) headers={'X-Show-Deleted': 'True'})
@@ -141,7 +120,7 @@ class TestListActionPlan(api_base.FunctionalTest):
def test_many(self): def test_many(self):
action_plan_list = [] action_plan_list = []
for id_ in range(5): for id_ in range(5):
action_plan = obj_utils.create_action_plan_without_audit( action_plan = obj_utils.create_test_action_plan(
self.context, id=id_, uuid=utils.generate_uuid()) self.context, id=id_, uuid=utils.generate_uuid())
action_plan_list.append(action_plan.uuid) action_plan_list.append(action_plan.uuid)
response = self.get_json('/action_plans') response = self.get_json('/action_plans')
@@ -225,7 +204,7 @@ class TestListActionPlan(api_base.FunctionalTest):
def test_many_without_soft_deleted(self): def test_many_without_soft_deleted(self):
action_plan_list = [] action_plan_list = []
for id_ in [1, 2, 3]: for id_ in [1, 2, 3]:
action_plan = obj_utils.create_action_plan_without_audit( action_plan = obj_utils.create_test_action_plan(
self.context, id=id_, uuid=utils.generate_uuid()) self.context, id=id_, uuid=utils.generate_uuid())
action_plan_list.append(action_plan.uuid) action_plan_list.append(action_plan.uuid)
for id_ in [4, 5]: for id_ in [4, 5]:
@@ -240,11 +219,11 @@ class TestListActionPlan(api_base.FunctionalTest):
def test_many_with_soft_deleted(self): def test_many_with_soft_deleted(self):
action_plan_list = [] action_plan_list = []
for id_ in [1, 2, 3]: for id_ in [1, 2, 3]:
action_plan = obj_utils.create_action_plan_without_audit( action_plan = obj_utils.create_test_action_plan(
self.context, id=id_, uuid=utils.generate_uuid()) self.context, id=id_, uuid=utils.generate_uuid())
action_plan_list.append(action_plan.uuid) action_plan_list.append(action_plan.uuid)
for id_ in [4, 5]: for id_ in [4, 5]:
action_plan = obj_utils.create_action_plan_without_audit( action_plan = obj_utils.create_test_action_plan(
self.context, id=id_, uuid=utils.generate_uuid()) self.context, id=id_, uuid=utils.generate_uuid())
action_plan.soft_delete() action_plan.soft_delete()
action_plan_list.append(action_plan.uuid) action_plan_list.append(action_plan.uuid)
@@ -272,8 +251,7 @@ class TestListActionPlan(api_base.FunctionalTest):
def test_links(self): def test_links(self):
uuid = utils.generate_uuid() uuid = utils.generate_uuid()
obj_utils.create_action_plan_without_audit(self.context, obj_utils.create_test_action_plan(self.context, id=1, uuid=uuid)
id=1, uuid=uuid)
response = self.get_json('/action_plans/%s' % uuid) response = self.get_json('/action_plans/%s' % uuid)
self.assertIn('links', response.keys()) self.assertIn('links', response.keys())
self.assertEqual(2, len(response['links'])) self.assertEqual(2, len(response['links']))
@@ -284,7 +262,7 @@ class TestListActionPlan(api_base.FunctionalTest):
def test_collection_links(self): def test_collection_links(self):
for id_ in range(5): for id_ in range(5):
obj_utils.create_action_plan_without_audit( obj_utils.create_test_action_plan(
self.context, id=id_, uuid=utils.generate_uuid()) self.context, id=id_, uuid=utils.generate_uuid())
response = self.get_json('/action_plans/?limit=3') response = self.get_json('/action_plans/?limit=3')
self.assertEqual(3, len(response['action_plans'])) self.assertEqual(3, len(response['action_plans']))
@@ -296,9 +274,8 @@ class TestListActionPlan(api_base.FunctionalTest):
cfg.CONF.set_override('max_limit', 3, 'api', cfg.CONF.set_override('max_limit', 3, 'api',
enforce_type=True) enforce_type=True)
for id_ in range(5): for id_ in range(5):
obj_utils.create_action_plan_without_audit( obj_utils.create_test_action_plan(
self.context, id=id_, uuid=utils.generate_uuid(), self.context, id=id_, uuid=utils.generate_uuid())
audit_id=None)
response = self.get_json('/action_plans') response = self.get_json('/action_plans')
self.assertEqual(3, len(response['action_plans'])) self.assertEqual(3, len(response['action_plans']))
@@ -310,7 +287,7 @@ class TestDelete(api_base.FunctionalTest):
def setUp(self): def setUp(self):
super(TestDelete, self).setUp() super(TestDelete, self).setUp()
self.action_plan = obj_utils.create_action_plan_without_audit( self.action_plan = obj_utils.create_test_action_plan(
self.context) self.context)
p = mock.patch.object(db_api.BaseConnection, 'destroy_action_plan') p = mock.patch.object(db_api.BaseConnection, 'destroy_action_plan')
self.mock_action_plan_delete = p.start() self.mock_action_plan_delete = p.start()
@@ -366,7 +343,7 @@ class TestPatch(api_base.FunctionalTest):
def setUp(self): def setUp(self):
super(TestPatch, self).setUp() super(TestPatch, self).setUp()
self.action_plan = obj_utils.create_action_plan_without_audit( self.action_plan = obj_utils.create_test_action_plan(
self.context, state=objects.action_plan.State.RECOMMENDED) self.context, state=objects.action_plan.State.RECOMMENDED)
p = mock.patch.object(db_api.BaseConnection, 'update_action_plan') p = mock.patch.object(db_api.BaseConnection, 'update_action_plan')
self.mock_action_plan_update = p.start() self.mock_action_plan_update = p.start()
@@ -459,7 +436,7 @@ class TestPatch(api_base.FunctionalTest):
response = self.patch_json( response = self.patch_json(
'/action_plans/%s' % self.action_plan.uuid, '/action_plans/%s' % self.action_plan.uuid,
[{'path': '/state', 'value': new_state, [{'path': '/state', 'value': new_state,
'op': 'replace'}]) 'op': 'replace'}])
self.assertEqual('application/json', response.content_type) self.assertEqual('application/json', response.content_type)
self.assertEqual(200, response.status_code) self.assertEqual(200, response.status_code)
applier_mock.assert_called_once_with(mock.ANY, applier_mock.assert_called_once_with(mock.ANY,
@@ -509,7 +486,7 @@ class TestPatchStateTransitionDenied(api_base.FunctionalTest):
db_api.BaseConnection, 'update_action_plan', db_api.BaseConnection, 'update_action_plan',
mock.Mock(side_effect=lambda ap: ap.save() or ap)) mock.Mock(side_effect=lambda ap: ap.save() or ap))
def test_replace_state_pending_denied(self): def test_replace_state_pending_denied(self):
action_plan = obj_utils.create_action_plan_without_audit( action_plan = obj_utils.create_test_action_plan(
self.context, state=self.original_state) self.context, state=self.original_state)
initial_ap = self.get_json('/action_plans/%s' % action_plan.uuid) initial_ap = self.get_json('/action_plans/%s' % action_plan.uuid)
@@ -533,7 +510,7 @@ class TestPatchStateDeletedNotFound(api_base.FunctionalTest):
db_api.BaseConnection, 'update_action_plan', db_api.BaseConnection, 'update_action_plan',
mock.Mock(side_effect=lambda ap: ap.save() or ap)) mock.Mock(side_effect=lambda ap: ap.save() or ap))
def test_replace_state_pending_not_found(self): def test_replace_state_pending_not_found(self):
action_plan = obj_utils.create_action_plan_without_audit( action_plan = obj_utils.create_test_action_plan(
self.context, state=objects.action_plan.State.DELETED) self.context, state=objects.action_plan.State.DELETED)
response = self.get_json( response = self.get_json(
@@ -561,15 +538,14 @@ class TestPatchStateTransitionOk(api_base.FunctionalTest):
mock.Mock(side_effect=lambda ap: ap.save() or ap)) mock.Mock(side_effect=lambda ap: ap.save() or ap))
@mock.patch.object(aapi.ApplierAPI, 'launch_action_plan', mock.Mock()) @mock.patch.object(aapi.ApplierAPI, 'launch_action_plan', mock.Mock())
def test_replace_state_pending_ok(self): def test_replace_state_pending_ok(self):
action_plan = obj_utils.create_action_plan_without_audit( action_plan = obj_utils.create_test_action_plan(
self.context, state=self.original_state) self.context, state=self.original_state)
initial_ap = self.get_json('/action_plans/%s' % action_plan.uuid) initial_ap = self.get_json('/action_plans/%s' % action_plan.uuid)
response = self.patch_json( response = self.patch_json(
'/action_plans/%s' % action_plan.uuid, '/action_plans/%s' % action_plan.uuid,
[{'path': '/state', 'value': self.new_state, [{'path': '/state', 'value': self.new_state, 'op': 'replace'}])
'op': 'replace'}])
updated_ap = self.get_json('/action_plans/%s' % action_plan.uuid) updated_ap = self.get_json('/action_plans/%s' % action_plan.uuid)
self.assertNotEqual(self.new_state, initial_ap['state']) self.assertNotEqual(self.new_state, initial_ap['state'])

View File

@@ -155,14 +155,14 @@ class TestPurgeCommand(base.DbTestCase):
with freezegun.freeze_time(self.expired_date): with freezegun.freeze_time(self.expired_date):
self.action_plan1 = obj_utils.create_test_action_plan( self.action_plan1 = obj_utils.create_test_action_plan(
self.context, audit_id=self.audit1.id, self.context, id=self._generate_id(), uuid=None,
id=self._generate_id(), uuid=None) audit_id=self.audit1.id, strategy_id=self.strategy1.id)
self.action_plan2 = obj_utils.create_test_action_plan( self.action_plan2 = obj_utils.create_test_action_plan(
self.context, audit_id=self.audit2.id, self.context, id=self._generate_id(), uuid=None,
id=self._generate_id(), uuid=None) audit_id=self.audit2.id, strategy_id=self.strategy2.id)
self.action_plan3 = obj_utils.create_test_action_plan( self.action_plan3 = obj_utils.create_test_action_plan(
self.context, audit_id=self.audit3.id, self.context, id=self._generate_id(), uuid=None,
id=self._generate_id(), uuid=None) audit_id=self.audit3.id, strategy_id=self.strategy3.id)
self.action1 = obj_utils.create_test_action( self.action1 = obj_utils.create_test_action(
self.context, action_plan_id=self.action_plan1.id, self.context, action_plan_id=self.action_plan1.id,

View File

@@ -124,6 +124,7 @@ def get_test_action_plan(**kwargs):
'uuid': kwargs.get('uuid', '76be87bd-3422-43f9-93a0-e85a577e3061'), 'uuid': kwargs.get('uuid', '76be87bd-3422-43f9-93a0-e85a577e3061'),
'state': kwargs.get('state', 'ONGOING'), 'state': kwargs.get('state', 'ONGOING'),
'audit_id': kwargs.get('audit_id', 1), 'audit_id': kwargs.get('audit_id', 1),
'strategy_id': kwargs.get('strategy_id', 1),
'global_efficacy': kwargs.get('global_efficacy', {}), 'global_efficacy': kwargs.get('global_efficacy', {}),
'first_action_id': kwargs.get('first_action_id', 1), 'first_action_id': kwargs.get('first_action_id', 1),
'created_at': kwargs.get('created_at'), 'created_at': kwargs.get('created_at'),

View File

@@ -13,10 +13,11 @@
# implied. # implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import mock
import uuid import uuid
from apscheduler.schedulers import background from apscheduler.schedulers import background
import mock
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
@@ -30,13 +31,17 @@ from watcher.tests.objects import utils as obj_utils
class TestOneShotAuditHandler(base.DbTestCase): class TestOneShotAuditHandler(base.DbTestCase):
def setUp(self): def setUp(self):
super(TestOneShotAuditHandler, self).setUp() super(TestOneShotAuditHandler, self).setUp()
obj_utils.create_test_goal(self.context, id=1, name="dummy") obj_utils.create_test_goal(self.context, id=1, name="dummy")
self.strategy = obj_utils.create_test_strategy(
self.context, name='dummy')
audit_template = obj_utils.create_test_audit_template( audit_template = obj_utils.create_test_audit_template(
self.context) self.context, strategy_id=self.strategy.id)
self.audit = obj_utils.create_test_audit( self.audit = obj_utils.create_test_audit(
self.context, self.context,
strategy_id=self.strategy.id,
audit_template_id=audit_template.id) audit_template_id=audit_template.id)
@mock.patch.object(manager.CollectorManager, "get_cluster_model_collector") @mock.patch.object(manager.CollectorManager, "get_cluster_model_collector")
@@ -79,11 +84,12 @@ class TestContinuousAuditHandler(base.DbTestCase):
obj_utils.create_test_goal(self.context, id=1, name="DUMMY") obj_utils.create_test_goal(self.context, id=1, name="DUMMY")
audit_template = obj_utils.create_test_audit_template( audit_template = obj_utils.create_test_audit_template(
self.context) self.context)
self.audits = [obj_utils.create_test_audit( self.audits = [
self.context, obj_utils.create_test_audit(
uuid=uuid.uuid4(), self.context,
audit_template_id=audit_template.id, uuid=uuid.uuid4(),
audit_type=audit_objects.AuditType.CONTINUOUS.value) audit_template_id=audit_template.id,
audit_type=audit_objects.AuditType.CONTINUOUS.value)
for i in range(2)] for i in range(2)]
@mock.patch.object(background.BackgroundScheduler, 'add_job') @mock.patch.object(background.BackgroundScheduler, 'add_job')

View File

@@ -81,8 +81,3 @@ class FakeDummy1(FakeGoal):
class FakeDummy2(FakeGoal): class FakeDummy2(FakeGoal):
NAME = "dummy_2" NAME = "dummy_2"
DISPLAY_NAME = "Dummy 2" DISPLAY_NAME = "Dummy 2"
class FakeOtherDummy2(FakeGoal):
NAME = "dummy_2"
DISPLAY_NAME = "Other Dummy 2"

View File

@@ -59,11 +59,16 @@ class SolutionFakerSingleHyp(object):
class TestActionScheduling(base.DbTestCase): class TestActionScheduling(base.DbTestCase):
def setUp(self):
super(TestActionScheduling, self).setUp()
self.strategy = db_utils.create_test_strategy(name="dummy")
self.audit = db_utils.create_test_audit(
uuid=utils.generate_uuid(), strategy_id=self.strategy.id)
self.default_planner = pbase.DefaultPlanner(mock.Mock())
def test_schedule_actions(self): def test_schedule_actions(self):
default_planner = pbase.DefaultPlanner(mock.Mock())
audit = db_utils.create_test_audit(uuid=utils.generate_uuid())
solution = dsol.DefaultSolution( solution = dsol.DefaultSolution(
goal=mock.Mock(), strategy=mock.Mock()) goal=mock.Mock(), strategy=self.strategy)
parameters = { parameters = {
"source_node": "server1", "source_node": "server1",
@@ -74,11 +79,12 @@ class TestActionScheduling(base.DbTestCase):
input_parameters=parameters) input_parameters=parameters)
with mock.patch.object( with mock.patch.object(
pbase.DefaultPlanner, "create_action", pbase.DefaultPlanner, "create_action",
wraps=default_planner.create_action) as m_create_action: wraps=self.default_planner.create_action
default_planner.config.weights = {'migrate': 3} ) as m_create_action:
action_plan = default_planner.schedule(self.context, self.default_planner.config.weights = {'migrate': 3}
audit.id, solution) action_plan = self.default_planner.schedule(
self.context, self.audit.id, solution)
self.assertIsNotNone(action_plan.uuid) self.assertIsNotNone(action_plan.uuid)
self.assertEqual(1, m_create_action.call_count) self.assertEqual(1, m_create_action.call_count)
@@ -87,10 +93,8 @@ class TestActionScheduling(base.DbTestCase):
self.assertEqual("migrate", actions[0].action_type) self.assertEqual("migrate", actions[0].action_type)
def test_schedule_two_actions(self): def test_schedule_two_actions(self):
default_planner = pbase.DefaultPlanner(mock.Mock())
audit = db_utils.create_test_audit(uuid=utils.generate_uuid())
solution = dsol.DefaultSolution( solution = dsol.DefaultSolution(
goal=mock.Mock(), strategy=mock.Mock()) goal=mock.Mock(), strategy=self.strategy)
parameters = { parameters = {
"source_node": "server1", "source_node": "server1",
@@ -105,11 +109,12 @@ class TestActionScheduling(base.DbTestCase):
input_parameters={}) input_parameters={})
with mock.patch.object( with mock.patch.object(
pbase.DefaultPlanner, "create_action", pbase.DefaultPlanner, "create_action",
wraps=default_planner.create_action) as m_create_action: wraps=self.default_planner.create_action
default_planner.config.weights = {'migrate': 3, 'nop': 0} ) as m_create_action:
action_plan = default_planner.schedule(self.context, self.default_planner.config.weights = {'migrate': 3, 'nop': 0}
audit.id, solution) action_plan = self.default_planner.schedule(
self.context, self.audit.id, solution)
self.assertIsNotNone(action_plan.uuid) self.assertIsNotNone(action_plan.uuid)
self.assertEqual(2, m_create_action.call_count) self.assertEqual(2, m_create_action.call_count)
# check order # check order
@@ -119,10 +124,8 @@ class TestActionScheduling(base.DbTestCase):
self.assertEqual("migrate", actions[1].action_type) self.assertEqual("migrate", actions[1].action_type)
def test_schedule_actions_with_unknown_action(self): def test_schedule_actions_with_unknown_action(self):
default_planner = pbase.DefaultPlanner(mock.Mock())
audit = db_utils.create_test_audit(uuid=utils.generate_uuid())
solution = dsol.DefaultSolution( solution = dsol.DefaultSolution(
goal=mock.Mock(), strategy=mock.Mock()) goal=mock.Mock(), strategy=self.strategy)
parameters = { parameters = {
"src_uuid_node": "server1", "src_uuid_node": "server1",
@@ -137,11 +140,12 @@ class TestActionScheduling(base.DbTestCase):
input_parameters={}) input_parameters={})
with mock.patch.object( with mock.patch.object(
pbase.DefaultPlanner, "create_action", pbase.DefaultPlanner, "create_action",
wraps=default_planner.create_action) as m_create_action: wraps=self.default_planner.create_action
default_planner.config.weights = {'migrate': 0} ) as m_create_action:
self.assertRaises(KeyError, default_planner.schedule, self.default_planner.config.weights = {'migrate': 0}
self.context, audit.id, solution) self.assertRaises(KeyError, self.default_planner.schedule,
self.context, self.audit.id, solution)
self.assertEqual(2, m_create_action.call_count) self.assertEqual(2, m_create_action.call_count)
@@ -158,6 +162,7 @@ class TestDefaultPlanner(base.DbTestCase):
} }
obj_utils.create_test_audit_template(self.context) obj_utils.create_test_audit_template(self.context)
self.strategy = obj_utils.create_test_strategy(self.context)
p = mock.patch.object(db_api.BaseConnection, 'create_action_plan') p = mock.patch.object(db_api.BaseConnection, 'create_action_plan')
self.mock_create_action_plan = p.start() self.mock_create_action_plan = p.start()
@@ -179,14 +184,18 @@ class TestDefaultPlanner(base.DbTestCase):
action.create() action.create()
return action return action
def test_schedule_scheduled_empty(self): @mock.patch.object(objects.Strategy, 'get_by_name')
def test_schedule_scheduled_empty(self, m_get_by_name):
m_get_by_name.return_value = self.strategy
audit = db_utils.create_test_audit(uuid=utils.generate_uuid()) audit = db_utils.create_test_audit(uuid=utils.generate_uuid())
fake_solution = SolutionFakerSingleHyp.build() fake_solution = SolutionFakerSingleHyp.build()
action_plan = self.default_planner.schedule(self.context, action_plan = self.default_planner.schedule(self.context,
audit.id, fake_solution) audit.id, fake_solution)
self.assertIsNotNone(action_plan.uuid) self.assertIsNotNone(action_plan.uuid)
def test_scheduler_warning_empty_action_plan(self): @mock.patch.object(objects.Strategy, 'get_by_name')
def test_scheduler_warning_empty_action_plan(self, m_get_by_name):
m_get_by_name.return_value = self.strategy
audit = db_utils.create_test_audit(uuid=utils.generate_uuid()) audit = db_utils.create_test_audit(uuid=utils.generate_uuid())
fake_solution = SolutionFaker.build() fake_solution = SolutionFaker.build()
action_plan = self.default_planner.schedule(self.context, action_plan = self.default_planner.schedule(self.context,

View File

@@ -44,7 +44,7 @@ class TestStrategyContext(base.DbTestCase):
mock_call.return_value = strategies.DummyStrategy( mock_call.return_value = strategies.DummyStrategy(
config=mock.Mock()) config=mock.Mock())
solution = self.strategy_context.execute_strategy( solution = self.strategy_context.execute_strategy(
self.audit.uuid, self.context) self.audit, self.context)
self.assertIsInstance(solution, default.DefaultSolution) self.assertIsInstance(solution, default.DefaultSolution)
@mock.patch.object(manager.CollectorManager, "get_cluster_model_collector", @mock.patch.object(manager.CollectorManager, "get_cluster_model_collector",
@@ -65,8 +65,7 @@ class TestStrategyContext(base.DbTestCase):
uuid=utils.generate_uuid(), uuid=utils.generate_uuid(),
) )
solution = self.strategy_context.execute_strategy( solution = self.strategy_context.execute_strategy(audit, self.context)
audit.uuid, self.context)
self.assertEqual(len(solution.actions), 3) self.assertEqual(len(solution.actions), 3)
@@ -92,7 +91,6 @@ class TestStrategyContext(base.DbTestCase):
uuid=utils.generate_uuid(), uuid=utils.generate_uuid(),
) )
solution = self.strategy_context.execute_strategy( solution = self.strategy_context.execute_strategy(audit, self.context)
audit.uuid, self.context)
self.assertEqual(solution, expected_strategy) self.assertEqual(solution, expected_strategy)

View File

@@ -21,6 +21,7 @@ from watcher.common import utils
from watcher.decision_engine.loading import default from watcher.decision_engine.loading import default
from watcher.decision_engine import sync from watcher.decision_engine import sync
from watcher import objects from watcher import objects
from watcher.objects import action_plan as ap_objects
from watcher.tests.db import base from watcher.tests.db import base
from watcher.tests.decision_engine import fake_goals from watcher.tests.decision_engine import fake_goals
from watcher.tests.decision_engine import fake_strategies from watcher.tests.decision_engine import fake_strategies
@@ -73,6 +74,27 @@ class TestSyncer(base.DbTestCase):
self.addCleanup(p_goals_load.stop) self.addCleanup(p_goals_load.stop)
self.addCleanup(p_strategies.stop) self.addCleanup(p_strategies.stop)
@staticmethod
def _find_created_modified_unmodified_ids(befores, afters):
created = {
a_item.id: a_item for a_item in afters
if a_item.uuid not in (b_item.uuid for b_item in befores)
}
modified = {
a_item.id: a_item for a_item in afters
if a_item.as_dict() not in (
b_items.as_dict() for b_items in befores)
}
unmodified = {
a_item.id: a_item for a_item in afters
if a_item.as_dict() in (
b_items.as_dict() for b_items in befores)
}
return created, modified, unmodified
@mock.patch.object(objects.Strategy, "soft_delete") @mock.patch.object(objects.Strategy, "soft_delete")
@mock.patch.object(objects.Strategy, "save") @mock.patch.object(objects.Strategy, "save")
@mock.patch.object(objects.Strategy, "create") @mock.patch.object(objects.Strategy, "create")
@@ -257,15 +279,18 @@ class TestSyncer(base.DbTestCase):
strategy1 = objects.Strategy( strategy1 = objects.Strategy(
self.ctx, id=1, name="strategy_1", uuid=utils.generate_uuid(), self.ctx, id=1, name="strategy_1", uuid=utils.generate_uuid(),
display_name="Strategy 1", goal_id=goal1.id) display_name="Strategy 1", goal_id=goal1.id)
# Should stay unmodified after sync() # Should be modified after sync() because its related goal has been
# modified
strategy2 = objects.Strategy( strategy2 = objects.Strategy(
self.ctx, id=2, name="strategy_2", uuid=utils.generate_uuid(), self.ctx, id=2, name="strategy_2", uuid=utils.generate_uuid(),
display_name="Strategy 2", goal_id=goal2.id) display_name="Strategy 2", goal_id=goal2.id)
# Should be modified by the sync() # Should be modified after sync() because its strategy name has been
# modified
strategy3 = objects.Strategy( strategy3 = objects.Strategy(
self.ctx, id=3, name="strategy_3", uuid=utils.generate_uuid(), self.ctx, id=3, name="strategy_3", uuid=utils.generate_uuid(),
display_name="Original", goal_id=goal2.id) display_name="Original", goal_id=goal1.id)
# Should be modified by the sync() # Should be modified after sync() because both its related goal
# and its strategy name have been modified
strategy4 = objects.Strategy( strategy4 = objects.Strategy(
self.ctx, id=4, name="strategy_4", uuid=utils.generate_uuid(), self.ctx, id=4, name="strategy_4", uuid=utils.generate_uuid(),
display_name="Original", goal_id=goal2.id) display_name="Original", goal_id=goal2.id)
@@ -279,18 +304,18 @@ class TestSyncer(base.DbTestCase):
# Should stay unmodified after sync() # Should stay unmodified after sync()
audit_template1 = objects.AuditTemplate( audit_template1 = objects.AuditTemplate(
self.ctx, id=1, uuid=utils.generate_uuid(), self.ctx, id=1, name="Synced AT1", uuid=utils.generate_uuid(),
name="Synced AT1", goal_id=goal1.id, strategy_id=strategy1.id) goal_id=goal1.id, strategy_id=strategy1.id)
# Should be modified by the sync() because its associated goal # Should be modified by the sync() because its associated goal
# should be modified # has been modified (compared to the defined fake goals)
audit_template2 = objects.AuditTemplate( audit_template2 = objects.AuditTemplate(
self.ctx, id=2, name="Synced AT2", uuid=utils.generate_uuid(), self.ctx, id=2, name="Synced AT2", uuid=utils.generate_uuid(),
goal_id=goal2.id, strategy_id=strategy2.id) goal_id=goal2.id, strategy_id=strategy2.id)
# Should be modified by the sync() because its associated strategy # Should be modified by the sync() because its associated strategy
# should be modified # has been modified (compared to the defined fake strategies)
audit_template3 = objects.AuditTemplate( audit_template3 = objects.AuditTemplate(
self.ctx, id=3, name="Synced AT3", uuid=utils.generate_uuid(), self.ctx, id=3, name="Synced AT3", uuid=utils.generate_uuid(),
goal_id=goal2.id, strategy_id=strategy3.id) goal_id=goal1.id, strategy_id=strategy3.id)
# Modified because of both because its associated goal and associated # Modified because of both because its associated goal and associated
# strategy should be modified # strategy should be modified
audit_template4 = objects.AuditTemplate( audit_template4 = objects.AuditTemplate(
@@ -301,9 +326,70 @@ class TestSyncer(base.DbTestCase):
audit_template3.create() audit_template3.create()
audit_template4.create() audit_template4.create()
before_audit_templates = objects.AuditTemplate.list(self.ctx) # Should stay unmodified after sync()
audit1 = objects.Audit(
self.ctx, id=1, uuid=utils.generate_uuid(),
goal_id=goal1.id, strategy_id=strategy1.id)
# Should be modified by the sync() because its associated goal
# has been modified (compared to the defined fake goals)
audit2 = objects.Audit(
self.ctx, id=2, uuid=utils.generate_uuid(),
goal_id=goal2.id, strategy_id=strategy2.id)
# Should be modified by the sync() because its associated strategy
# has been modified (compared to the defined fake strategies)
audit3 = objects.Audit(
self.ctx, id=3, uuid=utils.generate_uuid(),
goal_id=goal1.id, strategy_id=strategy3.id)
# Modified because of both because its associated goal and associated
# strategy should be modified (compared to the defined fake
# goals/strategies)
audit4 = objects.Audit(
self.ctx, id=4, uuid=utils.generate_uuid(),
goal_id=goal2.id, strategy_id=strategy4.id)
audit1.create()
audit2.create()
audit3.create()
audit4.create()
# Should stay unmodified after sync()
action_plan1 = objects.ActionPlan(
self.ctx, id=1, uuid=utils.generate_uuid(),
audit_id=audit1.id, strategy_id=strategy1.id,
first_action_id=None, state='DOESNOTMATTER',
global_efficacy={})
# Stale after syncing because the goal of the audit has been modified
# (compared to the defined fake goals)
action_plan2 = objects.ActionPlan(
self.ctx, id=2, uuid=utils.generate_uuid(),
audit_id=audit2.id, strategy_id=strategy2.id,
first_action_id=None, state='DOESNOTMATTER',
global_efficacy={})
# Stale after syncing because the strategy has been modified
# (compared to the defined fake strategies)
action_plan3 = objects.ActionPlan(
self.ctx, id=3, uuid=utils.generate_uuid(),
audit_id=audit3.id, strategy_id=strategy3.id,
first_action_id=None, state='DOESNOTMATTER',
global_efficacy={})
# Stale after syncing because both the strategy and the related audit
# have been modified (compared to the defined fake goals/strategies)
action_plan4 = objects.ActionPlan(
self.ctx, id=4, uuid=utils.generate_uuid(),
audit_id=audit4.id, strategy_id=strategy4.id,
first_action_id=None, state='DOESNOTMATTER',
global_efficacy={})
action_plan1.create()
action_plan2.create()
action_plan3.create()
action_plan4.create()
before_goals = objects.Goal.list(self.ctx) before_goals = objects.Goal.list(self.ctx)
before_strategies = objects.Strategy.list(self.ctx) before_strategies = objects.Strategy.list(self.ctx)
before_audit_templates = objects.AuditTemplate.list(self.ctx)
before_audits = objects.Audit.list(self.ctx)
before_action_plans = objects.ActionPlan.list(self.ctx)
# ### Action under test ### # # ### Action under test ### #
@@ -314,30 +400,51 @@ class TestSyncer(base.DbTestCase):
# ### Assertions ### # # ### Assertions ### #
after_audit_templates = objects.AuditTemplate.list(self.ctx)
after_goals = objects.Goal.list(self.ctx) after_goals = objects.Goal.list(self.ctx)
after_strategies = objects.Strategy.list(self.ctx) after_strategies = objects.Strategy.list(self.ctx)
after_audit_templates = objects.AuditTemplate.list(self.ctx)
after_audits = objects.Audit.list(self.ctx)
after_action_plans = objects.ActionPlan.list(self.ctx)
self.assertEqual(2, len(before_goals)) self.assertEqual(2, len(before_goals))
self.assertEqual(4, len(before_strategies)) self.assertEqual(4, len(before_strategies))
self.assertEqual(4, len(before_audit_templates)) self.assertEqual(4, len(before_audit_templates))
self.assertEqual(4, len(before_audits))
self.assertEqual(4, len(before_action_plans))
self.assertEqual(2, len(after_goals)) self.assertEqual(2, len(after_goals))
self.assertEqual(4, len(after_strategies)) self.assertEqual(4, len(after_strategies))
self.assertEqual(4, len(after_audit_templates)) self.assertEqual(4, len(after_audit_templates))
self.assertEqual(4, len(after_audits))
self.assertEqual(4, len(after_action_plans))
self.assertEqual( self.assertEqual(
{"dummy_1", "dummy_2"}, {"dummy_1", "dummy_2"},
set([g.name for g in after_goals])) set([g.name for g in after_goals]))
self.assertEqual( self.assertEqual(
{"strategy_1", "strategy_2", "strategy_3", "strategy_4"}, {"strategy_1", "strategy_2", "strategy_3", "strategy_4"},
set([s.name for s in after_strategies])) set([s.name for s in after_strategies]))
created_goals = {
ag.name: ag for ag in after_goals created_goals, modified_goals, unmodified_goals = (
if ag.uuid not in [bg.uuid for bg in before_goals] self._find_created_modified_unmodified_ids(
} before_goals, after_goals))
created_strategies = {
a_s.name: a_s for a_s in after_strategies created_strategies, modified_strategies, unmodified_strategies = (
if a_s.uuid not in [b_s.uuid for b_s in before_strategies] self._find_created_modified_unmodified_ids(
} before_strategies, after_strategies))
(created_audit_templates, modified_audit_templates,
unmodified_audit_templates) = (
self._find_created_modified_unmodified_ids(
before_audit_templates, after_audit_templates))
created_audits, modified_audits, unmodified_audits = (
self._find_created_modified_unmodified_ids(
before_audits, after_audits))
(created_action_plans, modified_action_plans,
unmodified_action_plans) = (
self._find_created_modified_unmodified_ids(
before_action_plans, after_action_plans))
dummy_1_spec = [ dummy_1_spec = [
{'description': 'Dummy indicator', 'name': 'dummy', {'description': 'Dummy indicator', 'name': 'dummy',
@@ -351,40 +458,34 @@ class TestSyncer(base.DbTestCase):
self.assertEqual(1, len(created_goals)) self.assertEqual(1, len(created_goals))
self.assertEqual(3, len(created_strategies)) self.assertEqual(3, len(created_strategies))
self.assertEqual(0, len(created_audits))
modified_audit_templates = { self.assertEqual(0, len(created_action_plans))
a_at.id for a_at in after_audit_templates
if a_at.goal_id not in (
# initial goal IDs
b_at.goal_id for b_at in before_audit_templates) or
a_at.strategy_id not in (
# initial strategy IDs
b_at.strategy_id for b_at in before_audit_templates
if b_at.strategy_id is not None)
}
unmodified_audit_templates = {
a_at.id for a_at in after_audit_templates
if a_at.goal_id in (
# initial goal IDs
b_at.goal_id for b_at in before_audit_templates) and
a_at.strategy_id in (
# initial strategy IDs
b_at.strategy_id for b_at in before_audit_templates
if b_at.strategy_id is not None)
}
self.assertEqual(2, strategy2.goal_id) self.assertEqual(2, strategy2.goal_id)
self.assertIn(strategy2.name, created_strategies)
self.assertNotEqual(strategy2.id,
created_strategies[strategy2.name].id)
self.assertEqual(set([audit_template2.id, self.assertNotEqual(
audit_template3.id, set([strategy2.id, strategy3.id, strategy4.id]),
audit_template4.id]), set(modified_strategies))
modified_audit_templates) self.assertEqual(set([strategy1.id]), set(unmodified_strategies))
self.assertEqual(
set([audit_template2.id, audit_template3.id, audit_template4.id]),
set(modified_audit_templates))
self.assertEqual(set([audit_template1.id]), self.assertEqual(set([audit_template1.id]),
unmodified_audit_templates) set(unmodified_audit_templates))
self.assertEqual(
set([audit2.id, audit3.id, audit4.id]),
set(modified_audits))
self.assertEqual(set([audit1.id]), set(unmodified_audits))
self.assertEqual(
set([action_plan2.id, action_plan3.id, action_plan4.id]),
set(modified_action_plans))
self.assertTrue(
all(ap.state == ap_objects.State.CANCELLED
for ap in modified_action_plans.values()))
self.assertEqual(set([action_plan1.id]), set(unmodified_action_plans))
def test_end2end_sync_goals_with_removed_goal_and_strategy(self): def test_end2end_sync_goals_with_removed_goal_and_strategy(self):
# ### Setup ### # # ### Setup ### #
@@ -417,11 +518,13 @@ class TestSyncer(base.DbTestCase):
strategy1 = objects.Strategy( strategy1 = objects.Strategy(
self.ctx, id=1, name="strategy_1", uuid=utils.generate_uuid(), self.ctx, id=1, name="strategy_1", uuid=utils.generate_uuid(),
display_name="Strategy 1", goal_id=goal1.id) display_name="Strategy 1", goal_id=goal1.id)
# To be removed by the sync() # To be removed by the sync() because strategy entry point does not
# exist anymore
strategy2 = objects.Strategy( strategy2 = objects.Strategy(
self.ctx, id=2, name="strategy_2", uuid=utils.generate_uuid(), self.ctx, id=2, name="strategy_2", uuid=utils.generate_uuid(),
display_name="Strategy 2", goal_id=goal1.id) display_name="Strategy 2", goal_id=goal1.id)
# To be removed by the sync() # To be removed by the sync() because the goal has been soft deleted
# and because the strategy entry point does not exist anymore
strategy3 = objects.Strategy( strategy3 = objects.Strategy(
self.ctx, id=3, name="strategy_3", uuid=utils.generate_uuid(), self.ctx, id=3, name="strategy_3", uuid=utils.generate_uuid(),
display_name="Original", goal_id=goal2.id) display_name="Original", goal_id=goal2.id)
@@ -435,9 +538,9 @@ class TestSyncer(base.DbTestCase):
# The strategy of this audit template will be dereferenced # The strategy of this audit template will be dereferenced
# as it does not exist anymore # as it does not exist anymore
audit_template1 = objects.AuditTemplate( audit_template1 = objects.AuditTemplate(
self.ctx, id=1, uuid=utils.generate_uuid(), self.ctx, id=1, name="Synced AT1", uuid=utils.generate_uuid(),
name="Synced AT1", goal_id=goal1.id, strategy_id=strategy1.id) goal_id=goal1.id, strategy_id=strategy1.id)
# Stale even after syncing because the goal has been soft deleted # Stale after syncing because the goal has been soft deleted
audit_template2 = objects.AuditTemplate( audit_template2 = objects.AuditTemplate(
self.ctx, id=2, name="Synced AT2", uuid=utils.generate_uuid(), self.ctx, id=2, name="Synced AT2", uuid=utils.generate_uuid(),
goal_id=goal2.id, strategy_id=strategy2.id) goal_id=goal2.id, strategy_id=strategy2.id)
@@ -445,9 +548,39 @@ class TestSyncer(base.DbTestCase):
audit_template1.create() audit_template1.create()
audit_template2.create() audit_template2.create()
before_audit_templates = objects.AuditTemplate.list(self.ctx) # Should stay unmodified after sync()
audit1 = objects.Audit(
self.ctx, id=1, uuid=utils.generate_uuid(),
goal_id=goal1.id, strategy_id=strategy1.id)
# Stale after syncing because the goal has been soft deleted
audit2 = objects.Audit(
self.ctx, id=2, uuid=utils.generate_uuid(),
goal_id=goal2.id, strategy_id=strategy2.id)
audit1.create()
audit2.create()
# Stale after syncing because its related strategy has been be
# soft deleted
action_plan1 = objects.ActionPlan(
self.ctx, id=1, uuid=utils.generate_uuid(),
audit_id=audit1.id, strategy_id=strategy1.id,
first_action_id=None, state='DOESNOTMATTER',
global_efficacy={})
# Stale after syncing because its related goal has been soft deleted
action_plan2 = objects.ActionPlan(
self.ctx, id=2, uuid=utils.generate_uuid(),
audit_id=audit2.id, strategy_id=strategy2.id,
first_action_id=None, state='DOESNOTMATTER',
global_efficacy={})
action_plan1.create()
action_plan2.create()
before_goals = objects.Goal.list(self.ctx) before_goals = objects.Goal.list(self.ctx)
before_strategies = objects.Strategy.list(self.ctx) before_strategies = objects.Strategy.list(self.ctx)
before_audit_templates = objects.AuditTemplate.list(self.ctx)
before_audits = objects.Audit.list(self.ctx)
before_action_plans = objects.ActionPlan.list(self.ctx)
# ### Action under test ### # # ### Action under test ### #
@@ -458,54 +591,66 @@ class TestSyncer(base.DbTestCase):
# ### Assertions ### # # ### Assertions ### #
after_audit_templates = objects.AuditTemplate.list(self.ctx)
after_goals = objects.Goal.list(self.ctx) after_goals = objects.Goal.list(self.ctx)
after_strategies = objects.Strategy.list(self.ctx) after_strategies = objects.Strategy.list(self.ctx)
after_audit_templates = objects.AuditTemplate.list(self.ctx)
after_audits = objects.Audit.list(self.ctx)
after_action_plans = objects.ActionPlan.list(self.ctx)
self.assertEqual(2, len(before_goals)) self.assertEqual(2, len(before_goals))
self.assertEqual(3, len(before_strategies)) self.assertEqual(3, len(before_strategies))
self.assertEqual(2, len(before_audit_templates)) self.assertEqual(2, len(before_audit_templates))
self.assertEqual(2, len(before_audits))
self.assertEqual(2, len(before_action_plans))
self.assertEqual(1, len(after_goals)) self.assertEqual(1, len(after_goals))
self.assertEqual(1, len(after_strategies)) self.assertEqual(1, len(after_strategies))
self.assertEqual(2, len(after_audit_templates)) self.assertEqual(2, len(after_audit_templates))
self.assertEqual(2, len(after_audits))
self.assertEqual(2, len(after_action_plans))
self.assertEqual( self.assertEqual(
{"dummy_1"}, {"dummy_1"},
set([g.name for g in after_goals])) set([g.name for g in after_goals]))
self.assertEqual( self.assertEqual(
{"strategy_1"}, {"strategy_1"},
set([s.name for s in after_strategies])) set([s.name for s in after_strategies]))
created_goals = [ag for ag in after_goals
if ag.uuid not in [bg.uuid for bg in before_goals]] created_goals, modified_goals, unmodified_goals = (
created_strategies = [ self._find_created_modified_unmodified_ids(
a_s for a_s in after_strategies before_goals, after_goals))
if a_s.uuid not in [b_s.uuid for b_s in before_strategies]]
created_strategies, modified_strategies, unmodified_strategies = (
self._find_created_modified_unmodified_ids(
before_strategies, after_strategies))
(created_audit_templates, modified_audit_templates,
unmodified_audit_templates) = (
self._find_created_modified_unmodified_ids(
before_audit_templates, after_audit_templates))
created_audits, modified_audits, unmodified_audits = (
self._find_created_modified_unmodified_ids(
before_audits, after_audits))
(created_action_plans, modified_action_plans,
unmodified_action_plans) = (
self._find_created_modified_unmodified_ids(
before_action_plans, after_action_plans))
self.assertEqual(0, len(created_goals)) self.assertEqual(0, len(created_goals))
self.assertEqual(0, len(created_strategies)) self.assertEqual(0, len(created_strategies))
self.assertEqual(0, len(created_audits))
modified_audit_templates = { self.assertEqual(0, len(created_action_plans))
a_at.id for a_at in after_audit_templates
if a_at.goal_id not in (
# initial goal IDs
b_at.goal_id for b_at in before_audit_templates) or
a_at.strategy_id not in (
# initial strategy IDs
b_at.strategy_id for b_at in before_audit_templates
if b_at.strategy_id is not None)
}
unmodified_audit_templates = {
a_at.id for a_at in after_audit_templates
if a_at.goal_id in (
# initial goal IDs
b_at.goal_id for b_at in before_audit_templates) and
a_at.strategy_id in (
# initial strategy IDs
b_at.strategy_id for b_at in before_audit_templates
if b_at.strategy_id is not None)
}
self.assertEqual(set([audit_template2.id]), self.assertEqual(set([audit_template2.id]),
modified_audit_templates) set(modified_audit_templates))
self.assertEqual(set([audit_template1.id]), self.assertEqual(set([audit_template1.id]),
unmodified_audit_templates) set(unmodified_audit_templates))
self.assertEqual(set([audit2.id]), set(modified_audits))
self.assertEqual(set([audit1.id]), set(unmodified_audits))
self.assertEqual(set([action_plan2.id]), set(modified_action_plans))
self.assertTrue(
all(ap.state == ap_objects.State.CANCELLED
for ap in modified_action_plans.values()))
self.assertEqual(set([action_plan1.id]), set(unmodified_action_plans))

View File

@@ -100,16 +100,6 @@ def create_test_action_plan(context, **kw):
return action_plan return action_plan
def create_action_plan_without_audit(context, **kw):
"""Create and return a test action_plan object.
Create a action plan in the DB and return a ActionPlan object with
appropriate attributes.
"""
kw['audit_id'] = None
return create_test_action_plan(context, **kw)
def get_test_action(context, **kw): def get_test_action(context, **kw):
"""Return a Action object with appropriate attributes. """Return a Action object with appropriate attributes.