From 1de00086f56c5756c01a8b400b978b03c174bde1 Mon Sep 17 00:00:00 2001 From: Alexander Chadin Date: Mon, 16 May 2016 20:16:07 +0300 Subject: [PATCH] Add continuously optimization This patch set adds implementation for CONTINUOUS type of audit. Change-Id: I5f4ec97b2082c8a6b3ccebe36b2a343fa4a67d19 Implements: blueprint continuously-optimization --- doc/source/architecture.rst | 7 + .../plantuml/watcher_db_schema_diagram.txt | 15 +- .../images/watcher_db_schema_diagram.png | Bin 50467 -> 65860 bytes requirements.txt | 1 + watcher/api/controllers/v1/audit.py | 37 ++++- watcher/common/exception.py | 8 + watcher/db/sqlalchemy/models.py | 1 + watcher/decision_engine/audit/base.py | 84 +++++++++++ watcher/decision_engine/audit/continuous.py | 126 ++++++++++++++++ watcher/decision_engine/audit/default.py | 88 ----------- watcher/decision_engine/audit/oneshot.py | 26 ++++ .../messaging/audit_endpoint.py | 12 +- watcher/objects/audit.py | 1 + watcher/tests/api/v1/test_audits.py | 60 ++++++++ watcher/tests/db/utils.py | 1 + .../audit/test_audit_handlers.py | 139 ++++++++++++++++++ .../audit/test_default_audit_handler.py | 69 --------- .../messaging/test_audit_endpoint.py | 28 ++-- 18 files changed, 516 insertions(+), 187 deletions(-) create mode 100644 watcher/decision_engine/audit/continuous.py delete mode 100644 watcher/decision_engine/audit/default.py create mode 100644 watcher/decision_engine/audit/oneshot.py create mode 100644 watcher/tests/decision_engine/audit/test_audit_handlers.py delete mode 100644 watcher/tests/decision_engine/audit/test_default_audit_handler.py diff --git a/doc/source/architecture.rst b/doc/source/architecture.rst index 26e7c7a86..cb13246de 100644 --- a/doc/source/architecture.rst +++ b/doc/source/architecture.rst @@ -261,6 +261,13 @@ previously created :ref:`Audit template `: .. image:: ./images/sequence_create_and_launch_audit.png :width: 100% +The :ref:`Administrator ` also can specify type of +Audit and interval (in case of CONTINUOUS type). There is two types of Audit: +ONESHOT and CONTINUOUS. Oneshot Audit is launched once and if it succeeded +executed new action plan list will be provided. Continuous Audit creates +action plans with specified interval (in seconds); if action plan +has been created, all previous action plans get CANCELLED state. + A message is sent on the :ref:`AMQP bus ` which triggers the Audit in the :ref:`Watcher Decision Engine `: diff --git a/doc/source/image_src/plantuml/watcher_db_schema_diagram.txt b/doc/source/image_src/plantuml/watcher_db_schema_diagram.txt index ab02d1c97..823e442e8 100644 --- a/doc/source/image_src/plantuml/watcher_db_schema_diagram.txt +++ b/doc/source/image_src/plantuml/watcher_db_schema_diagram.txt @@ -10,7 +10,7 @@ table(goal) { uuid : String[36] name : String[63] display_name : String[63] - + created_at : DateTime updated_at : DateTime deleted_at : DateTime @@ -24,7 +24,7 @@ table(strategy) { uuid : String[36] name : String[63] display_name : String[63] - + created_at : DateTime updated_at : DateTime deleted_at : DateTime @@ -42,7 +42,7 @@ table(audit_template) { host_aggregate : Integer, nullable extra : JSONEncodedDict version : String[15], nullable - + created_at : DateTime updated_at : DateTime deleted_at : DateTime @@ -54,10 +54,11 @@ table(audit) { primary_key(id: Integer) foreign_key("audit_template_id : Integer") uuid : String[36] - type : String[20] + audit_type : String[20] state : String[20], nullable deadline :DateTime, nullable - + interval : Integer, nullable + created_at : DateTime updated_at : DateTime deleted_at : DateTime @@ -72,7 +73,7 @@ table(action_plan) { first_action_id : Integer state : String[20], nullable global_efficacy : JSONEncodedDict, nullable - + created_at : DateTime updated_at : DateTime deleted_at : DateTime @@ -88,7 +89,7 @@ table(action) { input_parameters : JSONEncodedDict, nullable state : String[20], nullable next : String[36], nullable - + created_at : DateTime updated_at : DateTime deleted_at : DateTime diff --git a/doc/source/images/watcher_db_schema_diagram.png b/doc/source/images/watcher_db_schema_diagram.png index a43c78faaafa6814664559eae480635796e4f824..f85758dac62ec60442234a57bf2797ef77fd85f7 100644 GIT binary patch literal 65860 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 a_NfX(u(z+N91A3XKgFA>vIWvcKK}>dD<+o! diff --git a/requirements.txt b/requirements.txt index a71b964dc..74cb8d3d8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,7 @@ # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. +apscheduler # MIT License enum34;python_version=='2.7' or python_version=='2.6' or python_version=='3.3' # BSD jsonpatch>=1.1 # BSD keystoneauth1>=2.7.0 # Apache-2.0 diff --git a/watcher/api/controllers/v1/audit.py b/watcher/api/controllers/v1/audit.py index 02c16fac6..689d82626 100644 --- a/watcher/api/controllers/v1/audit.py +++ b/watcher/api/controllers/v1/audit.py @@ -63,25 +63,45 @@ class AuditPostType(wtypes.Base): parameters = wtypes.wsattr({wtypes.text: types.jsontype}, mandatory=False, default={}) + interval = wsme.wsattr(int, mandatory=False) def as_audit(self): audit_type_values = [val.value for val in objects.audit.AuditType] if self.audit_type not in audit_type_values: raise exception.AuditTypeNotFound(audit_type=self.audit_type) + if (self.audit_type == objects.audit.AuditType.ONESHOT.value and + self.interval != wtypes.Unset): + raise exception.AuditIntervalNotAllowed(audit_type=self.audit_type) + + if (self.audit_type == objects.audit.AuditType.CONTINUOUS.value and + self.interval == wtypes.Unset): + raise exception.AuditIntervalNotSpecified( + audit_type=self.audit_type) + return Audit( audit_template_id=self.audit_template_uuid, audit_type=self.audit_type, deadline=self.deadline, parameters=self.parameters, - ) + interval=self.interval) class AuditPatchType(types.JsonPatchType): @staticmethod def mandatory_attrs(): - return ['/audit_template_uuid'] + return ['/audit_template_uuid', '/type'] + + @staticmethod + def validate(patch): + serialized_patch = {'path': patch.path, 'op': patch.op} + if patch.path in AuditPatchType.mandatory_attrs(): + msg = _("%(field)s can't be updated.") + raise exception.PatchError( + patch=serialized_patch, + reason=msg % dict(field=patch.path)) + return types.JsonPatchType.validate(patch) class Audit(base.APIBase): @@ -160,6 +180,9 @@ class Audit(base.APIBase): links = wsme.wsattr([link.Link], readonly=True) """A list containing a self link and associated audit links""" + interval = wsme.wsattr(int, mandatory=False) + """Launch audit periodically (in seconds)""" + def __init__(self, **kwargs): self.fields = [] fields = list(objects.Audit.fields) @@ -187,7 +210,7 @@ class Audit(base.APIBase): if not expand: audit.unset_fields_except(['uuid', 'audit_type', 'deadline', 'state', 'audit_template_uuid', - 'audit_template_name']) + 'audit_template_name', 'interval']) # The numeric ID should not be exposed to # the user, it's internal only. @@ -215,7 +238,8 @@ class Audit(base.APIBase): deadline=None, created_at=datetime.datetime.utcnow(), deleted_at=None, - updated_at=datetime.datetime.utcnow()) + updated_at=datetime.datetime.utcnow(), + interval=7200) sample._audit_template_uuid = '7ae81bb3-dec3-4289-8d6c-da80bd8001ae' return cls._convert_with_links(sample, 'http://localhost:9322', expand) @@ -414,8 +438,9 @@ class AuditsController(rest.RestController): # trigger decision-engine to run the audit - dc_client = rpcapi.DecisionEngineAPI() - dc_client.trigger_audit(context, new_audit.uuid) + if new_audit.audit_type == objects.audit.AuditType.ONESHOT.value: + dc_client = rpcapi.DecisionEngineAPI() + dc_client.trigger_audit(context, new_audit.uuid) return Audit.convert_with_links(new_audit) diff --git a/watcher/common/exception.py b/watcher/common/exception.py index 95c1f0a0a..5e3e23cd3 100644 --- a/watcher/common/exception.py +++ b/watcher/common/exception.py @@ -221,6 +221,14 @@ class AuditAlreadyExists(Conflict): msg_fmt = _("An audit with UUID %(uuid)s already exists") +class AuditIntervalNotSpecified(Invalid): + msg_fmt = _("Interval of audit must be specified for %(audit_type)s.") + + +class AuditIntervalNotAllowed(Invalid): + msg_fmt = _("Interval of audit must not be set for %(audit_type)s.") + + class AuditReferenced(Invalid): msg_fmt = _("Audit %(audit)s is referenced by one or multiple action " "plans") diff --git a/watcher/db/sqlalchemy/models.py b/watcher/db/sqlalchemy/models.py index 9b265ced4..f6ca6f515 100644 --- a/watcher/db/sqlalchemy/models.py +++ b/watcher/db/sqlalchemy/models.py @@ -177,6 +177,7 @@ class Audit(Base): audit_template_id = Column(Integer, ForeignKey('audit_templates.id'), nullable=False) parameters = Column(JSONEncodedDict, nullable=True) + interval = Column(Integer, nullable=True) class Action(Base): diff --git a/watcher/decision_engine/audit/base.py b/watcher/decision_engine/audit/base.py index 743ee1ffd..e44d6fded 100644 --- a/watcher/decision_engine/audit/base.py +++ b/watcher/decision_engine/audit/base.py @@ -2,6 +2,7 @@ # Copyright (c) 2015 b<>com # # Authors: Jean-Emile DARTOIS +# Alexander Chadin # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -19,9 +20,92 @@ import abc import six +from oslo_log import log + +from watcher.common.messaging.events import event as watcher_event +from watcher.decision_engine.messaging import events as de_events +from watcher.decision_engine.planner import manager as planner_manager +from watcher.decision_engine.strategy.context import default as default_context +from watcher.objects import audit as audit_objects + +LOG = log.getLogger(__name__) + @six.add_metaclass(abc.ABCMeta) class BaseAuditHandler(object): @abc.abstractmethod def execute(self, audit_uuid, request_context): raise NotImplementedError() + + @abc.abstractmethod + def pre_execute(self, audit_uuid, request_context): + raise NotImplementedError() + + @abc.abstractmethod + def do_execute(self, audit, request_context): + raise NotImplementedError() + + @abc.abstractmethod + def post_execute(self, audit, solution, request_context): + raise NotImplementedError() + + +@six.add_metaclass(abc.ABCMeta) +class AuditHandler(BaseAuditHandler): + def __init__(self, messaging): + self._messaging = messaging + self._strategy_context = default_context.DefaultStrategyContext() + self._planner_manager = planner_manager.PlannerManager() + self._planner = None + + @property + def planner(self): + if self._planner is None: + self._planner = self._planner_manager.load() + return self._planner + + @property + def messaging(self): + return self._messaging + + @property + def strategy_context(self): + return self._strategy_context + + def notify(self, audit_uuid, event_type, status): + event = watcher_event.Event() + event.type = event_type + event.data = {} + payload = {'audit_uuid': audit_uuid, + 'audit_status': status} + self.messaging.status_topic_handler.publish_event( + event.type.name, payload) + + def update_audit_state(self, request_context, audit, state): + LOG.debug("Update audit state: %s", state) + audit.state = state + audit.save() + self.notify(audit.uuid, de_events.Events.TRIGGER_AUDIT, state) + + def pre_execute(self, audit, request_context): + LOG.debug("Trigger audit %s", audit.uuid) + # change state of the audit to ONGOING + self.update_audit_state(request_context, audit, + audit_objects.State.ONGOING) + + def post_execute(self, audit, solution, request_context): + self.planner.schedule(request_context, audit.id, solution) + + # change state of the audit to SUCCEEDED + self.update_audit_state(request_context, audit, + audit_objects.State.SUCCEEDED) + + def execute(self, audit, request_context): + try: + self.pre_execute(audit, request_context) + solution = self.do_execute(audit, request_context) + self.post_execute(audit, solution, request_context) + except Exception as e: + LOG.exception(e) + self.update_audit_state(request_context, audit, + audit_objects.State.FAILED) diff --git a/watcher/decision_engine/audit/continuous.py b/watcher/decision_engine/audit/continuous.py new file mode 100644 index 000000000..3aaf09e8a --- /dev/null +++ b/watcher/decision_engine/audit/continuous.py @@ -0,0 +1,126 @@ +# -*- encoding: utf-8 -*- +# Copyright (c) 2016 Servionica LTD +# +# Authors: Alexander Chadin +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import datetime + +from apscheduler.schedulers import background + +from oslo_config import cfg + +from watcher.common import context +from watcher.decision_engine.audit import base +from watcher.objects import action_plan as action_objects +from watcher.objects import audit as audit_objects + +CONF = cfg.CONF + +WATCHER_CONTINUOUS_OPTS = [ + cfg.IntOpt('continuous_audit_interval', + default=10, + help='Interval, in seconds, for checking new created' + 'continuous audit.') +] + +CONF.register_opts(WATCHER_CONTINUOUS_OPTS, 'watcher_decision_engine') + + +class ContinuousAuditHandler(base.AuditHandler): + def __init__(self, messaging): + super(ContinuousAuditHandler, self).__init__(messaging) + self._scheduler = None + self.jobs = [] + self._start() + self.context_show_deleted = context.RequestContext(is_admin=True, + show_deleted=True) + + @property + def scheduler(self): + if self._scheduler is None: + self._scheduler = background.BackgroundScheduler() + return self._scheduler + + def _is_audit_inactive(self, audit): + audit = audit_objects.Audit.get_by_uuid(self.context_show_deleted, + audit.uuid) + if audit.state in (audit_objects.State.CANCELLED, + audit_objects.State.DELETED, + audit_objects.State.FAILED): + # if audit isn't in active states, audit's job must be removed to + # prevent using of inactive audit in future. + job_to_delete = [job for job in self.jobs + if job.keys()[0] == audit.uuid][0] + self.jobs.remove(job_to_delete) + job_to_delete[audit.uuid].remove() + + return True + + return False + + def do_execute(self, audit, request_context): + # execute the strategy + solution = self.strategy_context.execute_strategy(audit.uuid, + request_context) + + if audit.audit_type == audit_objects.AuditType.CONTINUOUS.value: + a_plan_filters = {'audit_uuid': audit.uuid, + 'state': action_objects.State.RECOMMENDED} + action_plans = action_objects.ActionPlan.list( + request_context, + filters=a_plan_filters) + for plan in action_plans: + plan.state = action_objects.State.CANCELLED + plan.save() + return solution + + def execute_audit(self, audit, request_context): + if not self._is_audit_inactive(audit): + self.execute(audit, request_context) + + def post_execute(self, audit, solution, request_context): + self.planner.schedule(request_context, audit.id, solution) + + def launch_audits_periodically(self): + audit_context = context.RequestContext(is_admin=True) + audit_filters = { + 'audit_type': audit_objects.AuditType.CONTINUOUS.value, + 'state__in': (audit_objects.State.PENDING, + audit_objects.State.ONGOING, + audit_objects.State.SUCCEEDED) + } + audits = audit_objects.Audit.list(audit_context, + filters=audit_filters) + scheduler_job_args = [job.args for job in self.scheduler.get_jobs() + if job.name == 'execute_audit'] + for audit in audits: + if audit.uuid not in [arg[0].uuid for arg in scheduler_job_args]: + job = self.scheduler.add_job( + self.execute_audit, 'interval', + args=[audit, audit_context], + seconds=audit.interval, + name='execute_audit', + next_run_time=datetime.datetime.now()) + self.jobs.append({audit.uuid: job}) + + def _start(self): + self.scheduler.add_job( + self.launch_audits_periodically, + 'interval', + seconds=CONF.watcher_decision_engine.continuous_audit_interval, + next_run_time=datetime.datetime.now()) + self.scheduler.start() diff --git a/watcher/decision_engine/audit/default.py b/watcher/decision_engine/audit/default.py deleted file mode 100644 index 4ea64d179..000000000 --- a/watcher/decision_engine/audit/default.py +++ /dev/null @@ -1,88 +0,0 @@ -# -*- encoding: utf-8 -*- -# Copyright (c) 2015 b<>com -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from oslo_log import log - -from watcher.common.messaging.events import event as watcher_event -from watcher.decision_engine.audit import base -from watcher.decision_engine.messaging import events as de_events -from watcher.decision_engine.planner import manager as planner_manager -from watcher.decision_engine.strategy.context import default as default_context -from watcher.objects import audit as audit_objects - - -LOG = log.getLogger(__name__) - - -class DefaultAuditHandler(base.BaseAuditHandler): - def __init__(self, messaging): - super(DefaultAuditHandler, self).__init__() - self._messaging = messaging - self._strategy_context = default_context.DefaultStrategyContext() - self._planner_manager = planner_manager.PlannerManager() - self._planner = None - - @property - def planner(self): - if self._planner is None: - self._planner = self._planner_manager.load() - return self._planner - - @property - def messaging(self): - return self._messaging - - @property - def strategy_context(self): - return self._strategy_context - - def notify(self, audit_uuid, event_type, status): - event = watcher_event.Event() - event.type = event_type - event.data = {} - payload = {'audit_uuid': audit_uuid, - 'audit_status': status} - self.messaging.status_topic_handler.publish_event( - event.type.name, payload) - - def update_audit_state(self, request_context, audit_uuid, state): - LOG.debug("Update audit state: %s", state) - audit = audit_objects.Audit.get_by_uuid(request_context, audit_uuid) - audit.state = state - audit.save() - self.notify(audit_uuid, de_events.Events.TRIGGER_AUDIT, state) - return audit - - def execute(self, audit_uuid, request_context): - try: - LOG.debug("Trigger audit %s", audit_uuid) - # change state of the audit to ONGOING - audit = self.update_audit_state(request_context, audit_uuid, - audit_objects.State.ONGOING) - - # execute the strategy - solution = self.strategy_context.execute_strategy(audit_uuid, - request_context) - - self.planner.schedule(request_context, audit.id, solution) - - # change state of the audit to SUCCEEDED - self.update_audit_state(request_context, audit_uuid, - audit_objects.State.SUCCEEDED) - except Exception as e: - LOG.exception(e) - self.update_audit_state(request_context, audit_uuid, - audit_objects.State.FAILED) diff --git a/watcher/decision_engine/audit/oneshot.py b/watcher/decision_engine/audit/oneshot.py new file mode 100644 index 000000000..d7b68d926 --- /dev/null +++ b/watcher/decision_engine/audit/oneshot.py @@ -0,0 +1,26 @@ +# -*- encoding: utf-8 -*- +# Copyright (c) 2015 b<>com +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +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) + + return solution diff --git a/watcher/decision_engine/messaging/audit_endpoint.py b/watcher/decision_engine/messaging/audit_endpoint.py index ce606b4e2..a5c5f836b 100644 --- a/watcher/decision_engine/messaging/audit_endpoint.py +++ b/watcher/decision_engine/messaging/audit_endpoint.py @@ -21,7 +21,9 @@ from concurrent import futures from oslo_config import cfg from oslo_log import log -from watcher.decision_engine.audit import default +from watcher.decision_engine.audit import continuous as continuous_handler +from watcher.decision_engine.audit import oneshot as oneshot_handler +from watcher.objects import audit as audit_objects CONF = cfg.CONF LOG = log.getLogger(__name__) @@ -32,6 +34,10 @@ class AuditEndpoint(object): self._messaging = messaging self._executor = futures.ThreadPoolExecutor( max_workers=CONF.watcher_decision_engine.max_workers) + self._oneshot_handler = oneshot_handler.OneShotAuditHandler( + self.messaging) + self._continuous_handler = continuous_handler.ContinuousAuditHandler( + self.messaging) @property def executor(self): @@ -42,8 +48,8 @@ class AuditEndpoint(object): return self._messaging def do_trigger_audit(self, context, audit_uuid): - audit = default.DefaultAuditHandler(self.messaging) - audit.execute(audit_uuid, context) + audit = audit_objects.Audit.get_by_uuid(context, audit_uuid) + self._oneshot_handler.execute(audit, context) def trigger_audit(self, context, audit_uuid): LOG.debug("Trigger audit %s" % audit_uuid) diff --git a/watcher/objects/audit.py b/watcher/objects/audit.py index 422d93519..ff04570ed 100644 --- a/watcher/objects/audit.py +++ b/watcher/objects/audit.py @@ -86,6 +86,7 @@ class Audit(base.WatcherObject): 'deadline': obj_utils.datetime_or_str_or_none, 'audit_template_id': obj_utils.int_or_none, 'parameters': obj_utils.dict_or_none, + 'interval': obj_utils.int_or_none, } @staticmethod diff --git a/watcher/tests/api/v1/test_audits.py b/watcher/tests/api/v1/test_audits.py index e8f8b99fd..f1135281e 100644 --- a/watcher/tests/api/v1/test_audits.py +++ b/watcher/tests/api/v1/test_audits.py @@ -477,6 +477,7 @@ class TestPost(api_base.FunctionalTest): audit_dict = post_get_test_audit(state=objects.audit.State.PENDING) del audit_dict['uuid'] del audit_dict['state'] + del audit_dict['interval'] response = self.post_json('/audits', audit_dict) self.assertEqual('application/json', response.content_type) @@ -517,6 +518,7 @@ class TestPost(api_base.FunctionalTest): audit_dict = post_get_test_audit() del audit_dict['uuid'] del audit_dict['state'] + del audit_dict['interval'] # Make the audit template UUID some garbage value audit_dict['audit_template_uuid'] = ( '01234567-8910-1112-1314-151617181920') @@ -537,6 +539,7 @@ class TestPost(api_base.FunctionalTest): state = audit_dict['state'] del audit_dict['uuid'] del audit_dict['state'] + del audit_dict['interval'] with mock.patch.object(self.dbapi, 'create_audit', wraps=self.dbapi.create_audit) as cn_mock: response = self.post_json('/audits', audit_dict) @@ -552,6 +555,7 @@ class TestPost(api_base.FunctionalTest): audit_dict = post_get_test_audit() del audit_dict['uuid'] del audit_dict['state'] + del audit_dict['interval'] response = self.post_json('/audits', audit_dict) self.assertEqual('application/json', response.content_type) @@ -560,12 +564,66 @@ class TestPost(api_base.FunctionalTest): response.json['state']) self.assertTrue(utils.is_uuid_like(response.json['uuid'])) + @mock.patch.object(deapi.DecisionEngineAPI, 'trigger_audit') + def test_create_continuous_audit_with_period(self, mock_trigger_audit): + mock_trigger_audit.return_value = mock.ANY + + audit_dict = post_get_test_audit() + del audit_dict['uuid'] + del audit_dict['state'] + audit_dict['audit_type'] = objects.audit.AuditType.CONTINUOUS.value + audit_dict['interval'] = 1200 + + response = self.post_json('/audits', audit_dict) + self.assertEqual('application/json', response.content_type) + self.assertEqual(201, response.status_int) + self.assertEqual(objects.audit.State.PENDING, + response.json['state']) + self.assertEqual(audit_dict['interval'], response.json['interval']) + self.assertTrue(utils.is_uuid_like(response.json['uuid'])) + + @mock.patch.object(deapi.DecisionEngineAPI, 'trigger_audit') + def test_create_continuous_audit_without_period(self, mock_trigger_audit): + mock_trigger_audit.return_value = mock.ANY + + audit_dict = post_get_test_audit() + del audit_dict['uuid'] + del audit_dict['state'] + audit_dict['audit_type'] = objects.audit.AuditType.CONTINUOUS.value + del audit_dict['interval'] + + response = self.post_json('/audits', audit_dict, expect_errors=True) + self.assertEqual(400, response.status_int) + self.assertEqual('application/json', response.content_type) + expected_error_msg = ('Interval of audit must be specified ' + 'for CONTINUOUS.') + self.assertTrue(response.json['error_message']) + self.assertTrue(expected_error_msg in response.json['error_message']) + + @mock.patch.object(deapi.DecisionEngineAPI, 'trigger_audit') + def test_create_oneshot_audit_with_period(self, mock_trigger_audit): + mock_trigger_audit.return_value = mock.ANY + + audit_dict = post_get_test_audit() + del audit_dict['uuid'] + del audit_dict['state'] + audit_dict['audit_type'] = objects.audit.AuditType.ONESHOT.value + audit_dict['interval'] = 1200 + + response = self.post_json('/audits', audit_dict, expect_errors=True) + self.assertEqual(400, response.status_int) + self.assertEqual('application/json', response.content_type) + expected_error_msg = 'Interval of audit must not be set for ONESHOT.' + self.assertTrue(response.json['error_message']) + self.assertTrue(expected_error_msg in response.json['error_message']) + def test_create_audit_trigger_decision_engine(self): with mock.patch.object(deapi.DecisionEngineAPI, 'trigger_audit') as de_mock: audit_dict = post_get_test_audit(state=objects.audit.State.PENDING) del audit_dict['uuid'] del audit_dict['state'] + del audit_dict['interval'] response = self.post_json('/audits', audit_dict) de_mock.assert_called_once_with(mock.ANY, response.json['uuid']) @@ -586,6 +644,7 @@ class TestPost(api_base.FunctionalTest): audit_dict = post_get_test_audit(parameters={'name': 'Tom'}) del audit_dict['uuid'] del audit_dict['state'] + del audit_dict['interval'] response = self.post_json('/audits', audit_dict, expect_errors=True) self.assertEqual('application/json', response.content_type) @@ -605,6 +664,7 @@ class TestPost(api_base.FunctionalTest): parameters={'name': 'Tom'}) del audit_dict['uuid'] del audit_dict['state'] + del audit_dict['interval'] response = self.post_json('/audits', audit_dict, expect_errors=True) self.assertEqual('application/json', response.content_type) diff --git a/watcher/tests/db/utils.py b/watcher/tests/db/utils.py index 0d0c43857..881e4154a 100644 --- a/watcher/tests/db/utils.py +++ b/watcher/tests/db/utils.py @@ -62,6 +62,7 @@ def get_test_audit(**kwargs): 'updated_at': kwargs.get('updated_at'), 'deleted_at': kwargs.get('deleted_at'), 'parameters': kwargs.get('parameters', {}), + 'interval': kwargs.get('period', 3600), } diff --git a/watcher/tests/decision_engine/audit/test_audit_handlers.py b/watcher/tests/decision_engine/audit/test_audit_handlers.py new file mode 100644 index 000000000..590a98a01 --- /dev/null +++ b/watcher/tests/decision_engine/audit/test_audit_handlers.py @@ -0,0 +1,139 @@ +# -*- encoding: utf-8 -*- +# Copyright (c) 2015 b<>com +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import mock +import uuid + +from apscheduler.schedulers import background + +from watcher.decision_engine.audit import continuous +from watcher.decision_engine.audit import oneshot +from watcher.decision_engine.messaging import events +from watcher.metrics_engine.cluster_model_collector import manager +from watcher.objects import audit as audit_objects +from watcher.tests.db import base +from watcher.tests.decision_engine.strategy.strategies import \ + faker_cluster_state as faker +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") + audit_template = obj_utils.create_test_audit_template( + self.context) + self.audit = obj_utils.create_test_audit( + self.context, + audit_template_id=audit_template.id) + + @mock.patch.object(manager.CollectorManager, "get_cluster_model_collector") + def test_trigger_audit_without_errors(self, mock_collector): + mock_collector.return_value = faker.FakerModelCollector() + audit_handler = oneshot.OneShotAuditHandler(mock.MagicMock()) + audit_handler.execute(self.audit, self.context) + + @mock.patch.object(manager.CollectorManager, "get_cluster_model_collector") + def test_trigger_audit_state_succeeded(self, mock_collector): + mock_collector.return_value = faker.FakerModelCollector() + audit_handler = oneshot.OneShotAuditHandler(mock.MagicMock()) + audit_handler.execute(self.audit, self.context) + audit = audit_objects.Audit.get_by_uuid(self.context, self.audit.uuid) + self.assertEqual(audit_objects.State.SUCCEEDED, audit.state) + + @mock.patch.object(manager.CollectorManager, "get_cluster_model_collector") + def test_trigger_audit_send_notification(self, mock_collector): + messaging = mock.MagicMock() + mock_collector.return_value = faker.FakerModelCollector() + audit_handler = oneshot.OneShotAuditHandler(messaging) + audit_handler.execute(self.audit, self.context) + + call_on_going = mock.call(events.Events.TRIGGER_AUDIT.name, { + 'audit_status': audit_objects.State.ONGOING, + 'audit_uuid': self.audit.uuid}) + call_succeeded = mock.call(events.Events.TRIGGER_AUDIT.name, { + 'audit_status': audit_objects.State.SUCCEEDED, + 'audit_uuid': self.audit.uuid}) + + calls = [call_on_going, call_succeeded] + messaging.status_topic_handler.publish_event.assert_has_calls(calls) + self.assertEqual( + 2, messaging.status_topic_handler.publish_event.call_count) + + +class TestContinuousAuditHandler(base.DbTestCase): + def setUp(self): + super(TestContinuousAuditHandler, self).setUp() + 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) + for i in range(2)] + + @mock.patch.object(background.BackgroundScheduler, 'add_job') + @mock.patch.object(background.BackgroundScheduler, 'get_jobs') + @mock.patch.object(audit_objects.Audit, 'list') + def test_launch_audits_periodically(self, mock_list, + mock_jobs, mock_add_job): + audit_handler = continuous.ContinuousAuditHandler(mock.MagicMock()) + audits = [audit_objects.Audit.get_by_uuid(self.context, + self.audits[0].uuid)] + mock_list.return_value = audits + mock_jobs.return_value = mock.MagicMock() + audit_handler.launch_audits_periodically() + mock_add_job.assert_called() + + @mock.patch.object(background.BackgroundScheduler, 'add_job') + @mock.patch.object(background.BackgroundScheduler, 'get_jobs') + @mock.patch.object(audit_objects.Audit, 'list') + def test_launch_multiply_audits_periodically(self, mock_list, + mock_jobs, mock_add_job): + audit_handler = continuous.ContinuousAuditHandler(mock.MagicMock()) + audits = [audit_objects.Audit.get_by_uuid( + self.context, + audit.uuid) for audit in self.audits] + mock_list.return_value = audits + mock_jobs.return_value = mock.MagicMock() + calls = [mock.call(audit_handler.execute_audit, 'interval', + args=[mock.ANY, mock.ANY], + seconds=3600, + name='execute_audit', + next_run_time=mock.ANY) for audit in self.audits] + audit_handler.launch_audits_periodically() + mock_add_job.assert_has_calls(calls) + + @mock.patch.object(background.BackgroundScheduler, 'add_job') + @mock.patch.object(background.BackgroundScheduler, 'get_jobs') + @mock.patch.object(audit_objects.Audit, 'list') + def test_period_audit_not_called_when_deleted(self, mock_list, + mock_jobs, mock_add_job): + audit_handler = continuous.ContinuousAuditHandler(mock.MagicMock()) + audits = [audit_objects.Audit.get_by_uuid( + self.context, + audit.uuid) for audit in self.audits] + mock_list.return_value = audits + mock_jobs.return_value = mock.MagicMock() + audits[1].state = audit_objects.State.CANCELLED + calls = [mock.call(audit_handler.execute_audit, 'interval', + args=[mock.ANY, mock.ANY], + seconds=3600, + name='execute_audit', + next_run_time=mock.ANY)] + audit_handler.launch_audits_periodically() + mock_add_job.assert_has_calls(calls) diff --git a/watcher/tests/decision_engine/audit/test_default_audit_handler.py b/watcher/tests/decision_engine/audit/test_default_audit_handler.py deleted file mode 100644 index 005849822..000000000 --- a/watcher/tests/decision_engine/audit/test_default_audit_handler.py +++ /dev/null @@ -1,69 +0,0 @@ -# -*- encoding: utf-8 -*- -# Copyright (c) 2015 b<>com -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import mock - -from watcher.decision_engine.audit import default as default -from watcher.decision_engine.messaging import events -from watcher.metrics_engine.cluster_model_collector import manager -from watcher.objects import audit as audit_objects -from watcher.tests.db import base -from watcher.tests.decision_engine.strategy.strategies import \ - faker_cluster_state as faker -from watcher.tests.objects import utils as obj_utils - - -class TestDefaultAuditHandler(base.DbTestCase): - def setUp(self): - super(TestDefaultAuditHandler, self).setUp() - obj_utils.create_test_goal(self.context, id=1, name="dummy") - audit_template = obj_utils.create_test_audit_template( - self.context) - self.audit = obj_utils.create_test_audit( - self.context, - audit_template_id=audit_template.id) - - @mock.patch.object(manager.CollectorManager, "get_cluster_model_collector") - def test_trigger_audit_without_errors(self, mock_collector): - mock_collector.return_value = faker.FakerModelCollector() - audit_handler = default.DefaultAuditHandler(mock.MagicMock()) - audit_handler.execute(self.audit.uuid, self.context) - - @mock.patch.object(manager.CollectorManager, "get_cluster_model_collector") - def test_trigger_audit_state_succeeded(self, mock_collector): - mock_collector.return_value = faker.FakerModelCollector() - audit_handler = default.DefaultAuditHandler(mock.MagicMock()) - audit_handler.execute(self.audit.uuid, self.context) - audit = audit_objects.Audit.get_by_uuid(self.context, self.audit.uuid) - self.assertEqual(audit_objects.State.SUCCEEDED, audit.state) - - @mock.patch.object(manager.CollectorManager, "get_cluster_model_collector") - def test_trigger_audit_send_notification(self, mock_collector): - messaging = mock.MagicMock() - mock_collector.return_value = faker.FakerModelCollector() - audit_handler = default.DefaultAuditHandler(messaging) - audit_handler.execute(self.audit.uuid, self.context) - - call_on_going = mock.call(events.Events.TRIGGER_AUDIT.name, { - 'audit_status': audit_objects.State.ONGOING, - 'audit_uuid': self.audit.uuid}) - call_succeeded = mock.call(events.Events.TRIGGER_AUDIT.name, { - 'audit_status': audit_objects.State.SUCCEEDED, - 'audit_uuid': self.audit.uuid}) - - calls = [call_on_going, call_succeeded] - messaging.status_topic_handler.publish_event.assert_has_calls(calls) - self.assertEqual( - 2, messaging.status_topic_handler.publish_event.call_count) diff --git a/watcher/tests/decision_engine/messaging/test_audit_endpoint.py b/watcher/tests/decision_engine/messaging/test_audit_endpoint.py index 118c3a63c..6f5b1d6fc 100644 --- a/watcher/tests/decision_engine/messaging/test_audit_endpoint.py +++ b/watcher/tests/decision_engine/messaging/test_audit_endpoint.py @@ -16,8 +16,7 @@ import mock -from watcher.common import utils -from watcher.decision_engine.audit import default +from watcher.decision_engine.audit import oneshot as oneshot_handler from watcher.decision_engine.messaging import audit_endpoint from watcher.metrics_engine.cluster_model_collector import manager from watcher.tests.db import base @@ -38,28 +37,29 @@ class TestAuditEndpoint(base.DbTestCase): @mock.patch.object(manager.CollectorManager, "get_cluster_model_collector") def test_do_trigger_audit(self, mock_collector): mock_collector.return_value = faker_cluster_state.FakerModelCollector() - audit_uuid = utils.generate_uuid() - audit_handler = default.DefaultAuditHandler(mock.MagicMock()) + audit_handler = oneshot_handler.OneShotAuditHandler(mock.MagicMock()) endpoint = audit_endpoint.AuditEndpoint(audit_handler) - with mock.patch.object(default.DefaultAuditHandler, + with mock.patch.object(oneshot_handler.OneShotAuditHandler, 'execute') as mock_call: mock_call.return_value = 0 - endpoint.do_trigger_audit(audit_handler, audit_uuid) + endpoint.do_trigger_audit(self.context, self.audit.uuid) - mock_call.assert_called_once_with(audit_uuid, audit_handler) + self.assertEqual(mock_call.call_count, 1) @mock.patch.object(manager.CollectorManager, "get_cluster_model_collector") def test_trigger_audit(self, mock_collector): mock_collector.return_value = faker_cluster_state.FakerModelCollector() - audit_uuid = utils.generate_uuid() - audit_handler = default.DefaultAuditHandler(mock.MagicMock()) + + audit_handler = oneshot_handler.OneShotAuditHandler(mock.MagicMock()) endpoint = audit_endpoint.AuditEndpoint(audit_handler) - with mock.patch.object(default.DefaultAuditHandler, 'execute') \ - as mock_call: - mock_call.return_value = 0 - endpoint.trigger_audit(audit_handler, audit_uuid) + with mock.patch.object(endpoint.executor, 'submit') as mock_call: + mock_execute = mock.call(endpoint.do_trigger_audit, + self.context, + self.audit.uuid) + endpoint.trigger_audit(self.context, self.audit.uuid) - mock_call.assert_called_once_with(audit_uuid, audit_handler) + mock_call.assert_has_calls([mock_execute]) + self.assertEqual(mock_call.call_count, 1)