Merge "Added strategy ID + Action Plan syncing"

This commit is contained in:
Jenkins 2016-08-25 21:08:08 +00:00 committed by Gerrit Code Review
commit ab10201c72
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.common import exception
from watcher.common import policy
from watcher.common import utils
from watcher import objects
from watcher.objects import action_plan as ap_objects
@ -117,6 +118,8 @@ class ActionPlan(base.APIBase):
"""
_audit_uuid = None
_strategy_uuid = None
_strategy_name = None
_first_action_uuid = None
_efficacy_indicators = None
@ -177,6 +180,43 @@ class ActionPlan(base.APIBase):
elif value and 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)
"""Unique UUID for this action plan"""
@ -189,6 +229,14 @@ class ActionPlan(base.APIBase):
mandatory=True)
"""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(
types.jsontype, _get_efficacy_indicators, _set_efficacy_indicators,
mandatory=True)
@ -219,6 +267,10 @@ class ActionPlan(base.APIBase):
self.fields.append('efficacy_indicators')
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',
kwargs.get('first_action_id', wtypes.Unset))
@ -227,7 +279,8 @@ class ActionPlan(base.APIBase):
if not expand:
action_plan.unset_fields_except(
['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 = [
link.Link.make_link(
@ -275,8 +328,8 @@ class ActionPlanCollection(collection.Collection):
@staticmethod
def convert_with_links(rpc_action_plans, limit, url=None, expand=False,
**kwargs):
collection = ActionPlanCollection()
collection.action_plans = [ActionPlan.convert_with_links(
ap_collection = ActionPlanCollection()
ap_collection.action_plans = [ActionPlan.convert_with_links(
p, expand) for p in rpc_action_plans]
if 'sort_key' in kwargs:
@ -284,13 +337,13 @@ class ActionPlanCollection(collection.Collection):
if kwargs['sort_key'] == 'audit_uuid':
if 'sort_dir' in kwargs:
reverse = True if kwargs['sort_dir'] == 'desc' else False
collection.action_plans = sorted(
collection.action_plans,
ap_collection.action_plans = sorted(
ap_collection.action_plans,
key=lambda action_plan: action_plan.audit_uuid,
reverse=reverse)
collection.next = collection.get_next(limit, url=url, **kwargs)
return collection
ap_collection.next = ap_collection.get_next(limit, url=url, **kwargs)
return ap_collection
@classmethod
def sample(cls):
@ -301,6 +354,7 @@ class ActionPlanCollection(collection.Collection):
class ActionPlansController(rest.RestController):
"""REST controller for Actions."""
def __init__(self):
super(ActionPlansController, self).__init__()
@ -314,7 +368,8 @@ class ActionPlansController(rest.RestController):
def _get_action_plans_collection(self, marker, limit,
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)
api_utils.validate_sort_dir(sort_dir)
@ -328,6 +383,12 @@ class ActionPlansController(rest.RestController):
if 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':
sort_db_key = None
else:
@ -347,9 +408,9 @@ class ActionPlansController(rest.RestController):
sort_dir=sort_dir)
@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,
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.
: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 audit_uuid: Optional UUID of an audit, to get only actions
for that audit.
:param strategy: strategy UUID or name to filter by
"""
context = pecan.request.context
policy.enforce(context, 'action_plan:get_all',
action='action_plan:get_all')
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,
wtypes.text, types.uuid)
wtypes.text, types.uuid, wtypes.text)
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.
: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 audit_uuid: Optional UUID of an audit, to get only actions
for that audit.
:param strategy: strategy UUID or name to filter by
"""
context = pecan.request.context
policy.enforce(context, 'action_plan:detail',
@ -391,9 +455,8 @@ class ActionPlansController(rest.RestController):
expand = True
resource_url = '/'.join(['action_plans', 'detail'])
return self._get_action_plans_collection(
marker, limit,
sort_key, sort_dir, expand,
resource_url, audit_uuid=audit_uuid)
marker, limit, sort_key, sort_dir, expand,
resource_url, audit_uuid=audit_uuid, strategy=strategy)
@wsme_pecan.wsexpose(ActionPlan, types.uuid)
def get_one(self, action_plan_uuid):
@ -491,8 +554,8 @@ class ActionPlansController(rest.RestController):
if action_plan_to_update[field] != patch_val:
action_plan_to_update[field] = patch_val
if (field == 'state'
and patch_val == objects.action_plan.State.PENDING):
if (field == 'state'and
patch_val == objects.action_plan.State.PENDING):
launch_action_plan = True
action_plan_to_update.save()

View File

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

View File

@ -220,7 +220,8 @@ class PurgeCommand(object):
if audit not in orphans.audits]
orphans.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
action_plan_ids = [ap.id for ap in action_plans

View File

@ -347,10 +347,15 @@ class Connection(api.BaseConnection):
if filters is None:
filters = {}
plain_fields = ['uuid', 'state', 'audit_id']
join_fieldmap = {
'audit_uuid': ("uuid", models.Audit),
}
plain_fields = ['uuid', 'state', 'audit_id', 'strategy_id']
join_fieldmap = JoinMap(
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(
query=query, model=models.ActionPlan, filters=filters,

View File

@ -211,7 +211,8 @@ class ActionPlan(Base):
id = Column(Integer, primary_key=True)
uuid = Column(String(36))
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)
global_efficacy = Column(JSONEncodedDict, nullable=True)

View File

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

View File

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

View File

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

View File

@ -22,6 +22,16 @@ import six
@six.add_metaclass(abc.ABCMeta)
class BaseStrategyContext(object):
@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()

View File

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

View File

@ -22,6 +22,8 @@ from watcher._i18n import _LI, _LW
from watcher.common import context
from watcher.decision_engine.loading import default
from watcher import objects
from watcher.objects import action_plan as apobjects
from watcher.objects import audit as auditobjects
LOG = log.getLogger(__name__)
@ -54,6 +56,8 @@ class Syncer(object):
self.strategy_mapping = dict()
self.stale_audit_templates_map = {}
self.stale_audits_map = {}
self.stale_action_plans_map = {}
@property
def available_goals(self):
@ -118,7 +122,7 @@ class Syncer(object):
self.strategy_mapping.update(self._sync_strategy(strategy_map))
self._sync_audit_templates()
self._sync_objects()
def _sync_goal(self, goal_map):
goal_name = goal_map.name
@ -177,25 +181,45 @@ class Syncer(object):
return strategy_mapping
def _sync_audit_templates(self):
# First we find audit templates that are stale because their associated
# goal or strategy has been modified and we update them in-memory
def _sync_objects(self):
# First we find audit templates, audits and action plans that are stale
# 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_strategy()
# Then we handle the case where an audit template became
# stale because its related goal does not exist anymore.
self._find_stale_audits_due_to_goal()
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()
# Then we handle the case where an audit template became
# stale because its related strategy does not exist anymore.
# Then we handle the case where an audit template, an audit or an
# action plan becomes stale because its related strategy does not
# exist anymore.
self._soft_delete_removed_strategies()
# 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():
stale_audit_template.save()
LOG.info(_LI("Audit Template '%s' synced"),
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):
for goal_id, synced_goal in self.goal_mapping.items():
filters = {"goal_id": goal_id}
@ -228,6 +252,72 @@ class Syncer(object):
self.stale_audit_templates_map[
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):
removed_goals = [
g for g in self.available_goals
@ -235,12 +325,24 @@ class Syncer(object):
for removed_goal in removed_goals:
removed_goal.soft_delete()
filters = {"goal_id": removed_goal.id}
invalid_ats = objects.AuditTemplate.list(self.ctx, filters=filters)
for at in invalid_ats:
LOG.warning(
_LW("Audit Template '%(audit_template)s' references a "
"goal that does not exist"),
audit_template=at.uuid)
"goal that does not exist"), 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):
removed_strategies = [
@ -265,6 +367,32 @@ class Syncer(object):
else:
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):
strategies_map = {}
goals_map = {}

View File

@ -97,6 +97,7 @@ class ActionPlan(base.WatcherObject):
'id': int,
'uuid': obj_utils.str_or_none,
'audit_id': obj_utils.int_or_none,
'strategy_id': obj_utils.int_or_none,
'first_action_id': obj_utils.int_or_none,
'state': obj_utils.str_or_none,
'global_efficacy': obj_utils.dict_or_none,
@ -253,7 +254,7 @@ class ActionPlan(base.WatcherObject):
self[field] = current[field]
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
be used internally by the indirection_api.

View File

@ -13,39 +13,18 @@
import datetime
import itertools
import mock
import pecan
from oslo_config import cfg
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.common import context
from watcher.common import utils
from watcher.db import api as db_api
from watcher import objects
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
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):
def test_empty(self):
@ -53,20 +32,21 @@ class TestListActionPlan(api_base.FunctionalTest):
self.assertEqual([], response['action_plans'])
def _assert_action_plans_fields(self, action_plan):
action_plan_fields = ['uuid', 'audit_uuid', 'state', 'global_efficacy',
'efficacy_indicators']
action_plan_fields = [
'uuid', 'audit_uuid', 'strategy_uuid', 'strategy_name',
'state', 'global_efficacy', 'efficacy_indicators']
for field in action_plan_fields:
self.assertIn(field, action_plan)
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')
self.assertEqual(action_plan.uuid,
response['action_plans'][0]["uuid"])
self._assert_action_plans_fields(response['action_plans'][0])
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()
response = self.get_json('/action_plans',
headers={'X-Show-Deleted': 'True'})
@ -100,7 +80,7 @@ class TestListActionPlan(api_base.FunctionalTest):
self._assert_action_plans_fields(response)
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()
response = self.get_json('/action_plans/%s' % action_plan['uuid'],
headers={'X-Show-Deleted': 'True'})
@ -112,15 +92,14 @@ class TestListActionPlan(api_base.FunctionalTest):
self.assertEqual(404, response.status_int)
def test_detail(self):
action_plan = obj_utils.create_test_action_plan(self.context,
audit_id=None)
action_plan = obj_utils.create_test_action_plan(self.context)
response = self.get_json('/action_plans/detail')
self.assertEqual(action_plan.uuid,
response['action_plans'][0]["uuid"])
self._assert_action_plans_fields(response['action_plans'][0])
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()
response = self.get_json('/action_plans/detail',
headers={'X-Show-Deleted': 'True'})
@ -141,7 +120,7 @@ class TestListActionPlan(api_base.FunctionalTest):
def test_many(self):
action_plan_list = []
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())
action_plan_list.append(action_plan.uuid)
response = self.get_json('/action_plans')
@ -225,7 +204,7 @@ class TestListActionPlan(api_base.FunctionalTest):
def test_many_without_soft_deleted(self):
action_plan_list = []
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())
action_plan_list.append(action_plan.uuid)
for id_ in [4, 5]:
@ -240,11 +219,11 @@ class TestListActionPlan(api_base.FunctionalTest):
def test_many_with_soft_deleted(self):
action_plan_list = []
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())
action_plan_list.append(action_plan.uuid)
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())
action_plan.soft_delete()
action_plan_list.append(action_plan.uuid)
@ -272,8 +251,7 @@ class TestListActionPlan(api_base.FunctionalTest):
def test_links(self):
uuid = utils.generate_uuid()
obj_utils.create_action_plan_without_audit(self.context,
id=1, uuid=uuid)
obj_utils.create_test_action_plan(self.context, id=1, uuid=uuid)
response = self.get_json('/action_plans/%s' % uuid)
self.assertIn('links', response.keys())
self.assertEqual(2, len(response['links']))
@ -284,7 +262,7 @@ class TestListActionPlan(api_base.FunctionalTest):
def test_collection_links(self):
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())
response = self.get_json('/action_plans/?limit=3')
self.assertEqual(3, len(response['action_plans']))
@ -296,9 +274,8 @@ class TestListActionPlan(api_base.FunctionalTest):
cfg.CONF.set_override('max_limit', 3, 'api',
enforce_type=True)
for id_ in range(5):
obj_utils.create_action_plan_without_audit(
self.context, id=id_, uuid=utils.generate_uuid(),
audit_id=None)
obj_utils.create_test_action_plan(
self.context, id=id_, uuid=utils.generate_uuid())
response = self.get_json('/action_plans')
self.assertEqual(3, len(response['action_plans']))
@ -310,7 +287,7 @@ class TestDelete(api_base.FunctionalTest):
def setUp(self):
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)
p = mock.patch.object(db_api.BaseConnection, 'destroy_action_plan')
self.mock_action_plan_delete = p.start()
@ -366,7 +343,7 @@ class TestPatch(api_base.FunctionalTest):
def setUp(self):
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)
p = mock.patch.object(db_api.BaseConnection, 'update_action_plan')
self.mock_action_plan_update = p.start()
@ -459,7 +436,7 @@ class TestPatch(api_base.FunctionalTest):
response = self.patch_json(
'/action_plans/%s' % self.action_plan.uuid,
[{'path': '/state', 'value': new_state,
'op': 'replace'}])
'op': 'replace'}])
self.assertEqual('application/json', response.content_type)
self.assertEqual(200, response.status_code)
applier_mock.assert_called_once_with(mock.ANY,
@ -509,7 +486,7 @@ class TestPatchStateTransitionDenied(api_base.FunctionalTest):
db_api.BaseConnection, 'update_action_plan',
mock.Mock(side_effect=lambda ap: ap.save() or ap))
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)
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',
mock.Mock(side_effect=lambda ap: ap.save() or ap))
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)
response = self.get_json(
@ -561,15 +538,14 @@ class TestPatchStateTransitionOk(api_base.FunctionalTest):
mock.Mock(side_effect=lambda ap: ap.save() or ap))
@mock.patch.object(aapi.ApplierAPI, 'launch_action_plan', mock.Mock())
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)
initial_ap = self.get_json('/action_plans/%s' % action_plan.uuid)
response = self.patch_json(
'/action_plans/%s' % action_plan.uuid,
[{'path': '/state', 'value': self.new_state,
'op': 'replace'}])
[{'path': '/state', 'value': self.new_state, 'op': 'replace'}])
updated_ap = self.get_json('/action_plans/%s' % action_plan.uuid)
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):
self.action_plan1 = obj_utils.create_test_action_plan(
self.context, audit_id=self.audit1.id,
id=self._generate_id(), uuid=None)
self.context, 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.context, audit_id=self.audit2.id,
id=self._generate_id(), uuid=None)
self.context, 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.context, audit_id=self.audit3.id,
id=self._generate_id(), uuid=None)
self.context, id=self._generate_id(), uuid=None,
audit_id=self.audit3.id, strategy_id=self.strategy3.id)
self.action1 = obj_utils.create_test_action(
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'),
'state': kwargs.get('state', 'ONGOING'),
'audit_id': kwargs.get('audit_id', 1),
'strategy_id': kwargs.get('strategy_id', 1),
'global_efficacy': kwargs.get('global_efficacy', {}),
'first_action_id': kwargs.get('first_action_id', 1),
'created_at': kwargs.get('created_at'),

View File

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

View File

@ -81,8 +81,3 @@ class FakeDummy1(FakeGoal):
class FakeDummy2(FakeGoal):
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):
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):
default_planner = pbase.DefaultPlanner(mock.Mock())
audit = db_utils.create_test_audit(uuid=utils.generate_uuid())
solution = dsol.DefaultSolution(
goal=mock.Mock(), strategy=mock.Mock())
goal=mock.Mock(), strategy=self.strategy)
parameters = {
"source_node": "server1",
@ -74,11 +79,12 @@ class TestActionScheduling(base.DbTestCase):
input_parameters=parameters)
with mock.patch.object(
pbase.DefaultPlanner, "create_action",
wraps=default_planner.create_action) as m_create_action:
default_planner.config.weights = {'migrate': 3}
action_plan = default_planner.schedule(self.context,
audit.id, solution)
pbase.DefaultPlanner, "create_action",
wraps=self.default_planner.create_action
) as m_create_action:
self.default_planner.config.weights = {'migrate': 3}
action_plan = self.default_planner.schedule(
self.context, self.audit.id, solution)
self.assertIsNotNone(action_plan.uuid)
self.assertEqual(1, m_create_action.call_count)
@ -87,10 +93,8 @@ class TestActionScheduling(base.DbTestCase):
self.assertEqual("migrate", actions[0].action_type)
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(
goal=mock.Mock(), strategy=mock.Mock())
goal=mock.Mock(), strategy=self.strategy)
parameters = {
"source_node": "server1",
@ -105,11 +109,12 @@ class TestActionScheduling(base.DbTestCase):
input_parameters={})
with mock.patch.object(
pbase.DefaultPlanner, "create_action",
wraps=default_planner.create_action) as m_create_action:
default_planner.config.weights = {'migrate': 3, 'nop': 0}
action_plan = default_planner.schedule(self.context,
audit.id, solution)
pbase.DefaultPlanner, "create_action",
wraps=self.default_planner.create_action
) as m_create_action:
self.default_planner.config.weights = {'migrate': 3, 'nop': 0}
action_plan = self.default_planner.schedule(
self.context, self.audit.id, solution)
self.assertIsNotNone(action_plan.uuid)
self.assertEqual(2, m_create_action.call_count)
# check order
@ -119,10 +124,8 @@ class TestActionScheduling(base.DbTestCase):
self.assertEqual("migrate", actions[1].action_type)
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(
goal=mock.Mock(), strategy=mock.Mock())
goal=mock.Mock(), strategy=self.strategy)
parameters = {
"src_uuid_node": "server1",
@ -137,11 +140,12 @@ class TestActionScheduling(base.DbTestCase):
input_parameters={})
with mock.patch.object(
pbase.DefaultPlanner, "create_action",
wraps=default_planner.create_action) as m_create_action:
default_planner.config.weights = {'migrate': 0}
self.assertRaises(KeyError, default_planner.schedule,
self.context, audit.id, solution)
pbase.DefaultPlanner, "create_action",
wraps=self.default_planner.create_action
) as m_create_action:
self.default_planner.config.weights = {'migrate': 0}
self.assertRaises(KeyError, self.default_planner.schedule,
self.context, self.audit.id, solution)
self.assertEqual(2, m_create_action.call_count)
@ -158,6 +162,7 @@ class TestDefaultPlanner(base.DbTestCase):
}
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')
self.mock_create_action_plan = p.start()
@ -179,14 +184,18 @@ class TestDefaultPlanner(base.DbTestCase):
action.create()
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())
fake_solution = SolutionFakerSingleHyp.build()
action_plan = self.default_planner.schedule(self.context,
audit.id, fake_solution)
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())
fake_solution = SolutionFaker.build()
action_plan = self.default_planner.schedule(self.context,

View File

@ -44,7 +44,7 @@ class TestStrategyContext(base.DbTestCase):
mock_call.return_value = strategies.DummyStrategy(
config=mock.Mock())
solution = self.strategy_context.execute_strategy(
self.audit.uuid, self.context)
self.audit, self.context)
self.assertIsInstance(solution, default.DefaultSolution)
@mock.patch.object(manager.CollectorManager, "get_cluster_model_collector",
@ -65,8 +65,7 @@ class TestStrategyContext(base.DbTestCase):
uuid=utils.generate_uuid(),
)
solution = self.strategy_context.execute_strategy(
audit.uuid, self.context)
solution = self.strategy_context.execute_strategy(audit, self.context)
self.assertEqual(len(solution.actions), 3)
@ -92,7 +91,6 @@ class TestStrategyContext(base.DbTestCase):
uuid=utils.generate_uuid(),
)
solution = self.strategy_context.execute_strategy(
audit.uuid, self.context)
solution = self.strategy_context.execute_strategy(audit, self.context)
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 import sync
from watcher import objects
from watcher.objects import action_plan as ap_objects
from watcher.tests.db import base
from watcher.tests.decision_engine import fake_goals
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_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, "save")
@mock.patch.object(objects.Strategy, "create")
@ -257,15 +279,18 @@ class TestSyncer(base.DbTestCase):
strategy1 = objects.Strategy(
self.ctx, id=1, name="strategy_1", uuid=utils.generate_uuid(),
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(
self.ctx, id=2, name="strategy_2", uuid=utils.generate_uuid(),
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(
self.ctx, id=3, name="strategy_3", uuid=utils.generate_uuid(),
display_name="Original", goal_id=goal2.id)
# Should be modified by the sync()
display_name="Original", goal_id=goal1.id)
# Should be modified after sync() because both its related goal
# and its strategy name have been modified
strategy4 = objects.Strategy(
self.ctx, id=4, name="strategy_4", uuid=utils.generate_uuid(),
display_name="Original", goal_id=goal2.id)
@ -279,18 +304,18 @@ class TestSyncer(base.DbTestCase):
# Should stay unmodified after sync()
audit_template1 = objects.AuditTemplate(
self.ctx, id=1, uuid=utils.generate_uuid(),
name="Synced AT1", goal_id=goal1.id, strategy_id=strategy1.id)
self.ctx, id=1, name="Synced AT1", uuid=utils.generate_uuid(),
goal_id=goal1.id, strategy_id=strategy1.id)
# 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(
self.ctx, id=2, name="Synced AT2", uuid=utils.generate_uuid(),
goal_id=goal2.id, strategy_id=strategy2.id)
# 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(
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
# strategy should be modified
audit_template4 = objects.AuditTemplate(
@ -301,9 +326,70 @@ class TestSyncer(base.DbTestCase):
audit_template3.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_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 ### #
@ -314,30 +400,51 @@ class TestSyncer(base.DbTestCase):
# ### Assertions ### #
after_audit_templates = objects.AuditTemplate.list(self.ctx)
after_goals = objects.Goal.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(4, len(before_strategies))
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(4, len(after_strategies))
self.assertEqual(4, len(after_audit_templates))
self.assertEqual(4, len(after_audits))
self.assertEqual(4, len(after_action_plans))
self.assertEqual(
{"dummy_1", "dummy_2"},
set([g.name for g in after_goals]))
self.assertEqual(
{"strategy_1", "strategy_2", "strategy_3", "strategy_4"},
set([s.name for s in after_strategies]))
created_goals = {
ag.name: ag for ag in after_goals
if ag.uuid not in [bg.uuid for bg in before_goals]
}
created_strategies = {
a_s.name: a_s for a_s in after_strategies
if a_s.uuid not in [b_s.uuid for b_s in before_strategies]
}
created_goals, modified_goals, unmodified_goals = (
self._find_created_modified_unmodified_ids(
before_goals, after_goals))
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))
dummy_1_spec = [
{'description': 'Dummy indicator', 'name': 'dummy',
@ -351,40 +458,34 @@ class TestSyncer(base.DbTestCase):
self.assertEqual(1, len(created_goals))
self.assertEqual(3, len(created_strategies))
modified_audit_templates = {
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(0, len(created_audits))
self.assertEqual(0, len(created_action_plans))
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,
audit_template3.id,
audit_template4.id]),
modified_audit_templates)
self.assertNotEqual(
set([strategy2.id, strategy3.id, strategy4.id]),
set(modified_strategies))
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]),
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):
# ### Setup ### #
@ -417,11 +518,13 @@ class TestSyncer(base.DbTestCase):
strategy1 = objects.Strategy(
self.ctx, id=1, name="strategy_1", uuid=utils.generate_uuid(),
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(
self.ctx, id=2, name="strategy_2", uuid=utils.generate_uuid(),
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(
self.ctx, id=3, name="strategy_3", uuid=utils.generate_uuid(),
display_name="Original", goal_id=goal2.id)
@ -435,9 +538,9 @@ class TestSyncer(base.DbTestCase):
# The strategy of this audit template will be dereferenced
# as it does not exist anymore
audit_template1 = objects.AuditTemplate(
self.ctx, id=1, uuid=utils.generate_uuid(),
name="Synced AT1", goal_id=goal1.id, strategy_id=strategy1.id)
# Stale even after syncing because the goal has been soft deleted
self.ctx, id=1, name="Synced AT1", uuid=utils.generate_uuid(),
goal_id=goal1.id, strategy_id=strategy1.id)
# Stale after syncing because the goal has been soft deleted
audit_template2 = objects.AuditTemplate(
self.ctx, id=2, name="Synced AT2", uuid=utils.generate_uuid(),
goal_id=goal2.id, strategy_id=strategy2.id)
@ -445,9 +548,39 @@ class TestSyncer(base.DbTestCase):
audit_template1.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_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 ### #
@ -458,54 +591,66 @@ class TestSyncer(base.DbTestCase):
# ### Assertions ### #
after_audit_templates = objects.AuditTemplate.list(self.ctx)
after_goals = objects.Goal.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(3, len(before_strategies))
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_strategies))
self.assertEqual(2, len(after_audit_templates))
self.assertEqual(2, len(after_audits))
self.assertEqual(2, len(after_action_plans))
self.assertEqual(
{"dummy_1"},
set([g.name for g in after_goals]))
self.assertEqual(
{"strategy_1"},
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_strategies = [
a_s for a_s in after_strategies
if a_s.uuid not in [b_s.uuid for b_s in before_strategies]]
created_goals, modified_goals, unmodified_goals = (
self._find_created_modified_unmodified_ids(
before_goals, after_goals))
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_strategies))
modified_audit_templates = {
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(0, len(created_audits))
self.assertEqual(0, len(created_action_plans))
self.assertEqual(set([audit_template2.id]),
modified_audit_templates)
set(modified_audit_templates))
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
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):
"""Return a Action object with appropriate attributes.