From 38e4b48d70f33987296e5ba6bbf47d91065e71e8 Mon Sep 17 00:00:00 2001
From: licanwei
Date: Fri, 3 Mar 2017 13:10:23 +0800
Subject: [PATCH] stale the action plan
Check the creation time of the actionplan,
and set the state to SUPERSEDED if it has expired.
Change-Id: I900e8dc5011dec4cffd58913b9c5083a6131d70d
Implements: blueprint stale-action-plan
---
.../stale-action-plan-b6a6b08df873c128.yaml | 4 +--
watcher/conf/decision_engine.py | 9 ++++++
watcher/decision_engine/scheduling.py | 16 ++++++++++
watcher/objects/action_plan.py | 19 +++++++++++
.../tests/decision_engine/test_scheduling.py | 4 +--
watcher/tests/objects/test_action_plan.py | 32 +++++++++++++++++++
6 files changed, 80 insertions(+), 4 deletions(-)
diff --git a/releasenotes/notes/stale-action-plan-b6a6b08df873c128.yaml b/releasenotes/notes/stale-action-plan-b6a6b08df873c128.yaml
index 1ddf8cdc9..9ef9a4c77 100644
--- a/releasenotes/notes/stale-action-plan-b6a6b08df873c128.yaml
+++ b/releasenotes/notes/stale-action-plan-b6a6b08df873c128.yaml
@@ -1,4 +1,4 @@
---
features:
- - Add superseded state for an action plan if the cluster data model has
- changed after it has been created.
+ - Check the creation time of the action plan,
+ and set its state to SUPERSEDED if it has expired.
diff --git a/watcher/conf/decision_engine.py b/watcher/conf/decision_engine.py
index 4ff90da22..162dc29d4 100644
--- a/watcher/conf/decision_engine.py
+++ b/watcher/conf/decision_engine.py
@@ -42,6 +42,15 @@ WATCHER_DECISION_ENGINE_OPTS = [
required=True,
help='The maximum number of threads that can be used to '
'execute strategies'),
+ cfg.IntOpt('action_plan_expiry',
+ default=24,
+ help='An expiry timespan(hours). Watcher invalidates any '
+ 'action plan for which its creation time '
+ '-whose number of hours has been offset by this value-'
+ ' is older that the current time.'),
+ cfg.IntOpt('check_periodic_interval',
+ default=30*60,
+ help='Interval (in seconds) for checking action plan expiry.')
]
WATCHER_CONTINUOUS_OPTS = [
diff --git a/watcher/decision_engine/scheduling.py b/watcher/decision_engine/scheduling.py
index d5fa1f965..4ef0481cd 100644
--- a/watcher/decision_engine/scheduling.py
+++ b/watcher/decision_engine/scheduling.py
@@ -19,12 +19,17 @@ import datetime
import eventlet
from oslo_log import log
+from watcher.common import context
from watcher.common import exception
from watcher.common import scheduling
from watcher.decision_engine.model.collector import manager
+from watcher import objects
+
+from watcher import conf
LOG = log.getLogger(__name__)
+CONF = conf.CONF
class DecisionEngineSchedulingService(scheduling.BackgroundSchedulerService):
@@ -73,9 +78,20 @@ class DecisionEngineSchedulingService(scheduling.BackgroundSchedulerService):
return _sync
+ def add_checkstate_job(self):
+ # 30 minutes interval
+ interval = CONF.watcher_decision_engine.check_periodic_interval
+ ap_manager = objects.action_plan.StateManager()
+ if CONF.watcher_decision_engine.action_plan_expiry != 0:
+ self.add_job(ap_manager.check_expired, 'interval',
+ args=[context.make_context()],
+ seconds=interval,
+ next_run_time=datetime.datetime.now())
+
def start(self):
"""Start service."""
self.add_sync_jobs()
+ self.add_checkstate_job()
super(DecisionEngineSchedulingService, self).start()
def stop(self):
diff --git a/watcher/objects/action_plan.py b/watcher/objects/action_plan.py
index b08ec3319..dc171f263 100644
--- a/watcher/objects/action_plan.py
+++ b/watcher/objects/action_plan.py
@@ -71,15 +71,19 @@ state may be one of the following:
**RECOMMENDED** state and was superseded by the
:ref:`Administrator `
"""
+import datetime
from watcher.common import exception
from watcher.common import utils
+from watcher import conf
from watcher.db import api as db_api
from watcher import notifications
from watcher import objects
from watcher.objects import base
from watcher.objects import fields as wfields
+CONF = conf.CONF
+
class State(object):
RECOMMENDED = 'RECOMMENDED'
@@ -317,3 +321,18 @@ class ActionPlan(base.WatcherPersistentObject, base.WatcherObject,
notifications.action_plan.send_delete(self._context, self)
_notify()
+
+
+class StateManager(object):
+ def check_expired(self, context):
+ action_plan_expiry = (
+ CONF.watcher_decision_engine.action_plan_expiry)
+ date_created = datetime.datetime.utcnow() - datetime.timedelta(
+ hours=action_plan_expiry)
+ filters = {'state__eq': State.RECOMMENDED,
+ 'created_at__lt': date_created}
+ action_plans = objects.ActionPlan.list(
+ context, filters=filters, eager=True)
+ for action_plan in action_plans:
+ action_plan.state = State.SUPERSEDED
+ action_plan.save()
diff --git a/watcher/tests/decision_engine/test_scheduling.py b/watcher/tests/decision_engine/test_scheduling.py
index f414ad300..d4a057cd9 100644
--- a/watcher/tests/decision_engine/test_scheduling.py
+++ b/watcher/tests/decision_engine/test_scheduling.py
@@ -48,7 +48,7 @@ class TestDecisionEngineSchedulingService(base.TestCase):
m_start.assert_called_once_with(scheduler)
jobs = scheduler.get_jobs()
- self.assertEqual(1, len(jobs))
+ self.assertEqual(2, len(jobs))
job = jobs[0]
self.assertTrue(bool(fake_collector.cluster_data_model))
@@ -77,7 +77,7 @@ class TestDecisionEngineSchedulingService(base.TestCase):
m_start.assert_called_once_with(scheduler)
jobs = scheduler.get_jobs()
- self.assertEqual(1, len(jobs))
+ self.assertEqual(2, len(jobs))
job = jobs[0]
job.func()
diff --git a/watcher/tests/objects/test_action_plan.py b/watcher/tests/objects/test_action_plan.py
index 7c8ee0ecc..a06c94813 100644
--- a/watcher/tests/objects/test_action_plan.py
+++ b/watcher/tests/objects/test_action_plan.py
@@ -19,12 +19,16 @@ import iso8601
import mock
from watcher.common import exception
+from watcher.common import utils as common_utils
+from watcher import conf
from watcher.db.sqlalchemy import api as db_api
from watcher import notifications
from watcher import objects
from watcher.tests.db import base
from watcher.tests.db import utils
+CONF = conf.CONF
+
class TestActionPlanObject(base.DbTestCase):
@@ -290,3 +294,31 @@ class TestCreateDeleteActionPlanObject(base.DbTestCase):
m_destroy_efficacy_indicator.assert_called_once_with(
efficacy_indicator['uuid'])
self.assertEqual(self.context, action_plan._context)
+
+
+@mock.patch.object(notifications.action_plan, 'send_update', mock.Mock())
+class TestStateManager(base.DbTestCase):
+
+ def setUp(self):
+ super(TestStateManager, self).setUp()
+ self.state_manager = objects.action_plan.StateManager()
+
+ def test_check_expired(self):
+ CONF.set_default('action_plan_expiry', 0,
+ group='watcher_decision_engine')
+ strategy_1 = utils.create_test_strategy(
+ uuid=common_utils.generate_uuid())
+ audit_1 = utils.create_test_audit(
+ uuid=common_utils.generate_uuid())
+ action_plan_1 = utils.create_test_action_plan(
+ state=objects.action_plan.State.RECOMMENDED,
+ uuid=common_utils.generate_uuid(),
+ audit_id=audit_1.id,
+ strategy_id=strategy_1.id)
+
+ self.state_manager.check_expired(self.context)
+
+ action_plan = objects.action_plan.ActionPlan.get_by_uuid(
+ self.context, action_plan_1.uuid)
+ self.assertEqual(objects.action_plan.State.SUPERSEDED,
+ action_plan.state)