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
This commit is contained in:
licanwei 2017-03-03 13:10:23 +08:00
parent 03a2c0142a
commit 38e4b48d70
6 changed files with 80 additions and 4 deletions

View File

@ -1,4 +1,4 @@
--- ---
features: features:
- Add superseded state for an action plan if the cluster data model has - Check the creation time of the action plan,
changed after it has been created. and set its state to SUPERSEDED if it has expired.

View File

@ -42,6 +42,15 @@ WATCHER_DECISION_ENGINE_OPTS = [
required=True, required=True,
help='The maximum number of threads that can be used to ' help='The maximum number of threads that can be used to '
'execute strategies'), '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 = [ WATCHER_CONTINUOUS_OPTS = [

View File

@ -19,12 +19,17 @@ import datetime
import eventlet import eventlet
from oslo_log import log from oslo_log import log
from watcher.common import context
from watcher.common import exception from watcher.common import exception
from watcher.common import scheduling from watcher.common import scheduling
from watcher.decision_engine.model.collector import manager from watcher.decision_engine.model.collector import manager
from watcher import objects
from watcher import conf
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
CONF = conf.CONF
class DecisionEngineSchedulingService(scheduling.BackgroundSchedulerService): class DecisionEngineSchedulingService(scheduling.BackgroundSchedulerService):
@ -73,9 +78,20 @@ class DecisionEngineSchedulingService(scheduling.BackgroundSchedulerService):
return _sync 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): def start(self):
"""Start service.""" """Start service."""
self.add_sync_jobs() self.add_sync_jobs()
self.add_checkstate_job()
super(DecisionEngineSchedulingService, self).start() super(DecisionEngineSchedulingService, self).start()
def stop(self): def stop(self):

View File

@ -71,15 +71,19 @@ state may be one of the following:
**RECOMMENDED** state and was superseded by the **RECOMMENDED** state and was superseded by the
:ref:`Administrator <administrator_definition>` :ref:`Administrator <administrator_definition>`
""" """
import datetime
from watcher.common import exception from watcher.common import exception
from watcher.common import utils from watcher.common import utils
from watcher import conf
from watcher.db import api as db_api from watcher.db import api as db_api
from watcher import notifications from watcher import notifications
from watcher import objects from watcher import objects
from watcher.objects import base from watcher.objects import base
from watcher.objects import fields as wfields from watcher.objects import fields as wfields
CONF = conf.CONF
class State(object): class State(object):
RECOMMENDED = 'RECOMMENDED' RECOMMENDED = 'RECOMMENDED'
@ -317,3 +321,18 @@ class ActionPlan(base.WatcherPersistentObject, base.WatcherObject,
notifications.action_plan.send_delete(self._context, self) notifications.action_plan.send_delete(self._context, self)
_notify() _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()

View File

@ -48,7 +48,7 @@ class TestDecisionEngineSchedulingService(base.TestCase):
m_start.assert_called_once_with(scheduler) m_start.assert_called_once_with(scheduler)
jobs = scheduler.get_jobs() jobs = scheduler.get_jobs()
self.assertEqual(1, len(jobs)) self.assertEqual(2, len(jobs))
job = jobs[0] job = jobs[0]
self.assertTrue(bool(fake_collector.cluster_data_model)) self.assertTrue(bool(fake_collector.cluster_data_model))
@ -77,7 +77,7 @@ class TestDecisionEngineSchedulingService(base.TestCase):
m_start.assert_called_once_with(scheduler) m_start.assert_called_once_with(scheduler)
jobs = scheduler.get_jobs() jobs = scheduler.get_jobs()
self.assertEqual(1, len(jobs)) self.assertEqual(2, len(jobs))
job = jobs[0] job = jobs[0]
job.func() job.func()

View File

@ -19,12 +19,16 @@ import iso8601
import mock import mock
from watcher.common import exception 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.db.sqlalchemy import api as db_api
from watcher import notifications from watcher import notifications
from watcher import objects from watcher import objects
from watcher.tests.db import base from watcher.tests.db import base
from watcher.tests.db import utils from watcher.tests.db import utils
CONF = conf.CONF
class TestActionPlanObject(base.DbTestCase): class TestActionPlanObject(base.DbTestCase):
@ -290,3 +294,31 @@ class TestCreateDeleteActionPlanObject(base.DbTestCase):
m_destroy_efficacy_indicator.assert_called_once_with( m_destroy_efficacy_indicator.assert_called_once_with(
efficacy_indicator['uuid']) efficacy_indicator['uuid'])
self.assertEqual(self.context, action_plan._context) 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)