Merge "New cron type for audit interval"

This commit is contained in:
Jenkins 2017-07-19 02:46:25 +00:00 committed by Gerrit Code Review
commit 2266e2baa3
20 changed files with 326 additions and 54 deletions

View File

@ -9,6 +9,7 @@ keystoneauth1>=2.21.0 # Apache-2.0
jsonschema!=2.5.0,<3.0.0,>=2.0.0 # MIT
keystonemiddleware>=4.12.0 # Apache-2.0
lxml!=3.7.0,>=2.3 # BSD
croniter>=0.3.4 # MIT License
oslo.concurrency>=3.8.0 # Apache-2.0
oslo.cache>=1.5.0 # Apache-2.0
oslo.config!=4.3.0,!=4.4.0,>=4.0.0 # Apache-2.0

View File

@ -65,7 +65,7 @@ class AuditPostType(wtypes.Base):
parameters = wtypes.wsattr({wtypes.text: types.jsontype}, mandatory=False,
default={})
interval = wsme.wsattr(int, mandatory=False)
interval = wsme.wsattr(types.interval_or_cron, mandatory=False)
scope = wtypes.wsattr(types.jsontype, readonly=True)
@ -261,7 +261,7 @@ 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)
interval = wsme.wsattr(wtypes.text, mandatory=False)
"""Launch audit periodically (in seconds)"""
scope = wsme.wsattr(types.jsontype, mandatory=False)
@ -270,6 +270,9 @@ class Audit(base.APIBase):
auto_trigger = wsme.wsattr(bool, mandatory=False, default=False)
"""Autoexecute action plan once audit is succeeded"""
next_run_time = wsme.wsattr(datetime.datetime, mandatory=False)
"""The next time audit launch"""
def __init__(self, **kwargs):
self.fields = []
fields = list(objects.Audit.fields)
@ -301,7 +304,8 @@ class Audit(base.APIBase):
audit.unset_fields_except(['uuid', 'audit_type', 'state',
'goal_uuid', 'interval', 'scope',
'strategy_uuid', 'goal_name',
'strategy_name', 'auto_trigger'])
'strategy_name', 'auto_trigger',
'next_run_time'])
audit.links = [link.Link.make_link('self', url,
'audits', audit.uuid),
@ -325,9 +329,10 @@ class Audit(base.APIBase):
created_at=datetime.datetime.utcnow(),
deleted_at=None,
updated_at=datetime.datetime.utcnow(),
interval=7200,
interval='7200',
scope=[],
auto_trigger=False)
auto_trigger=False,
next_run_time=datetime.datetime.utcnow())
sample.goal_id = '7ae81bb3-dec3-4289-8d6c-da80bd8001ae'
sample.strategy_id = '7ae81bb3-dec3-4289-8d6c-da80bd8001ff'

View File

@ -43,6 +43,28 @@ class UuidOrNameType(wtypes.UserType):
return UuidOrNameType.validate(value)
class IntervalOrCron(wtypes.UserType):
"""A simple int value or cron syntax type"""
basetype = wtypes.text
name = 'interval_or_cron'
@staticmethod
def validate(value):
if not (utils.is_int_like(value) or utils.is_cron_like(value)):
raise exception.InvalidIntervalOrCron(name=value)
return value
@staticmethod
def frombasetype(value):
if value is None:
return None
return IntervalOrCron.validate(value)
interval_or_cron = IntervalOrCron()
class NameType(wtypes.UserType):
"""A simple logical name type."""

View File

@ -202,6 +202,10 @@ class InvalidUuidOrName(Invalid):
msg_fmt = _("Expected a logical name or uuid but received %(name)s")
class InvalidIntervalOrCron(Invalid):
msg_fmt = _("Expected an interval or cron syntax but received %(name)s")
class GoalNotFound(ResourceNotFound):
msg_fmt = _("Goal %(goal)s could not be found")
@ -418,6 +422,10 @@ class WildcardCharacterIsUsed(WatcherException):
"wildcard character.")
class CronFormatIsInvalid(WatcherException):
msg_fmt = _("Provided cron is invalid: %(message)s")
# Model
class ComputeResourceNotFound(WatcherException):

View File

@ -16,8 +16,11 @@
"""Utilities and helper functions."""
import datetime
import re
from croniter import croniter
from jsonschema import validators
from oslo_log import log as logging
from oslo_utils import strutils
@ -63,6 +66,15 @@ is_int_like = strutils.is_int_like
strtime = timeutils.strtime
def is_cron_like(value):
"""Return True is submitted value is like cron syntax"""
try:
croniter(value, datetime.datetime.now())
except Exception as e:
raise exception.CronFormatIsInvalid(message=str(e))
return True
def safe_rstrip(value, chars=None):
"""Removes trailing characters from a string if that does not make it empty

View File

@ -0,0 +1,26 @@
"""Add cron support for audit table
Revision ID: d098df6021e2
Revises: 0f6042416884
Create Date: 2017-06-08 16:21:35.746752
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'd098df6021e2'
down_revision = '0f6042416884'
def upgrade():
op.alter_column('audits', 'interval', existing_type=sa.String(36),
nullable=True)
op.add_column('audits',
sa.Column('next_run_time', sa.DateTime(), nullable=True))
def downgrade():
op.alter_column('audits', 'interval', existing_type=sa.Integer(),
nullable=True)
op.drop_column('audits', 'next_run_time')

View File

@ -173,11 +173,12 @@ class Audit(Base):
audit_type = Column(String(20))
state = Column(String(20), nullable=True)
parameters = Column(JSONEncodedDict, nullable=True)
interval = Column(Integer, nullable=True)
interval = Column(String(36), nullable=True)
goal_id = Column(Integer, ForeignKey('goals.id'), nullable=False)
strategy_id = Column(Integer, ForeignKey('strategies.id'), nullable=True)
scope = Column(JSONEncodedList, nullable=True)
auto_trigger = Column(Boolean, nullable=False)
next_run_time = Column(DateTime, nullable=True)
goal = orm.relationship(Goal, foreign_keys=goal_id, lazy=None)
strategy = orm.relationship(Strategy, foreign_keys=strategy_id, lazy=None)

View File

@ -19,11 +19,14 @@
import datetime
from dateutil import tz
from apscheduler.jobstores import memory
from croniter import croniter
from watcher.common import context
from watcher.common import scheduling
from watcher.common import utils
from watcher import conf
from watcher.db.sqlalchemy import api as sq_api
from watcher.db.sqlalchemy import job_store
@ -81,11 +84,38 @@ class ContinuousAuditHandler(base.AuditHandler):
plan.save()
return solution
def _next_cron_time(self, audit):
if utils.is_cron_like(audit.interval):
return croniter(audit.interval, datetime.datetime.utcnow()
).get_next(datetime.datetime)
@classmethod
def execute_audit(cls, audit, request_context):
self = cls()
if not self._is_audit_inactive(audit):
self.execute(audit, request_context)
try:
self.execute(audit, request_context)
except Exception:
raise
finally:
if utils.is_int_like(audit.interval):
audit.next_run_time = (
datetime.datetime.utcnow() +
datetime.timedelta(seconds=int(audit.interval)))
else:
audit.next_run_time = self._next_cron_time(audit)
audit.save()
def _add_job(self, trigger, audit, audit_context, **trigger_args):
time_var = 'next_run_time' if trigger_args.get(
'next_run_time') else 'run_date'
# We should convert UTC time to local time without tzinfo
trigger_args[time_var] = trigger_args[time_var].replace(
tzinfo=tz.tzutc()).astimezone(tz.tzlocal()).replace(tzinfo=None)
self.scheduler.add_job(self.execute_audit, trigger,
args=[audit, audit_context],
name='execute_audit',
**trigger_args)
def launch_audits_periodically(self):
audit_context = context.RequestContext(is_admin=True)
@ -101,13 +131,34 @@ class ContinuousAuditHandler(base.AuditHandler):
job.args for job in self.scheduler.get_jobs()
if job.name == 'execute_audit']
for audit in audits:
# if audit is not presented in scheduled audits yet.
if audit.uuid not in [arg[0].uuid for arg in scheduler_job_args]:
self.scheduler.add_job(
self.execute_audit, 'interval',
args=[audit, audit_context],
seconds=audit.interval,
name='execute_audit',
next_run_time=datetime.datetime.now())
# if interval is provided with seconds
if utils.is_int_like(audit.interval):
# if audit has already been provided and we need
# to restore it after shutdown
if audit.next_run_time is not None:
old_run_time = audit.next_run_time
current = datetime.datetime.utcnow()
if old_run_time < current:
delta = datetime.timedelta(
seconds=(int(audit.interval) - (
current - old_run_time).seconds %
int(audit.interval)))
audit.next_run_time = current + delta
next_run_time = audit.next_run_time
# if audit is new one
else:
next_run_time = datetime.datetime.utcnow()
self._add_job('interval', audit, audit_context,
seconds=int(audit.interval),
next_run_time=next_run_time)
else:
audit.next_run_time = self._next_cron_time(audit)
self._add_job('date', audit, audit_context,
run_date=audit.next_run_time)
audit.save()
def start(self):
self.scheduler.add_job(

View File

@ -39,6 +39,8 @@ class TerseAuditPayload(notificationbase.NotificationPayloadBase):
'parameters': ('audit', 'parameters'),
'interval': ('audit', 'interval'),
'scope': ('audit', 'scope'),
'auto_trigger': ('audit', 'auto_trigger'),
'next_run_time': ('audit', 'next_run_time'),
'created_at': ('audit', 'created_at'),
'updated_at': ('audit', 'updated_at'),
@ -46,17 +48,22 @@ class TerseAuditPayload(notificationbase.NotificationPayloadBase):
}
# Version 1.0: Initial version
VERSION = '1.0'
# Version 1.1: Added 'auto_trigger' boolean field,
# Added 'next_run_time' DateTime field,
# 'interval' type has been changed from Integer to String
VERSION = '1.1'
fields = {
'uuid': wfields.UUIDField(),
'audit_type': wfields.StringField(),
'state': wfields.StringField(),
'parameters': wfields.FlexibleDictField(nullable=True),
'interval': wfields.IntegerField(nullable=True),
'interval': wfields.StringField(nullable=True),
'scope': wfields.FlexibleListOfDictField(nullable=True),
'goal_uuid': wfields.UUIDField(),
'strategy_uuid': wfields.UUIDField(nullable=True),
'auto_trigger': wfields.BooleanField(),
'next_run_time': wfields.DateTimeField(nullable=True),
'created_at': wfields.DateTimeField(nullable=True),
'updated_at': wfields.DateTimeField(nullable=True),
@ -79,6 +86,8 @@ class AuditPayload(TerseAuditPayload):
'parameters': ('audit', 'parameters'),
'interval': ('audit', 'interval'),
'scope': ('audit', 'scope'),
'auto_trigger': ('audit', 'auto_trigger'),
'next_run_time': ('audit', 'next_run_time'),
'created_at': ('audit', 'created_at'),
'updated_at': ('audit', 'updated_at'),
@ -86,7 +95,9 @@ class AuditPayload(TerseAuditPayload):
}
# Version 1.0: Initial version
VERSION = '1.0'
# Version 1.1: Added 'auto_trigger' field,
# Added 'next_run_time' field
VERSION = '1.1'
fields = {
'goal': wfields.ObjectField('GoalPayload'),
@ -119,7 +130,9 @@ class AuditStateUpdatePayload(notificationbase.NotificationPayloadBase):
@base.WatcherObjectRegistry.register_notification
class AuditCreatePayload(AuditPayload):
# Version 1.0: Initial version
VERSION = '1.0'
# Version 1.1: Added 'auto_trigger' field,
# Added 'next_run_time' field
VERSION = '1.1'
fields = {}
def __init__(self, audit, goal, strategy):
@ -133,7 +146,9 @@ class AuditCreatePayload(AuditPayload):
@base.WatcherObjectRegistry.register_notification
class AuditUpdatePayload(AuditPayload):
# Version 1.0: Initial version
VERSION = '1.0'
# Version 1.1: Added 'auto_trigger' field,
# Added 'next_run_time' field
VERSION = '1.1'
fields = {
'state_update': wfields.ObjectField('AuditStateUpdatePayload'),
}
@ -150,7 +165,9 @@ class AuditUpdatePayload(AuditPayload):
@base.WatcherObjectRegistry.register_notification
class AuditActionPayload(AuditPayload):
# Version 1.0: Initial version
VERSION = '1.0'
# Version 1.1: Added 'auto_trigger' field,
# Added 'next_run_time' field
VERSION = '1.1'
fields = {
'fault': wfields.ObjectField('ExceptionPayload', nullable=True),
}
@ -167,7 +184,9 @@ class AuditActionPayload(AuditPayload):
@base.WatcherObjectRegistry.register_notification
class AuditDeletePayload(AuditPayload):
# Version 1.0: Initial version
VERSION = '1.0'
# Version 1.1: Added 'auto_trigger' field,
# Added 'next_run_time' field
VERSION = '1.1'
fields = {}
def __init__(self, audit, goal, strategy):

View File

@ -83,8 +83,10 @@ class Audit(base.WatcherPersistentObject, base.WatcherObject,
# Version 1.0: Initial version
# Version 1.1: Added 'goal' and 'strategy' object field
# Version 1.2 Added 'auto_trigger' boolean field
VERSION = '1.2'
# Version 1.2: Added 'auto_trigger' boolean field
# Version 1.3: Added 'next_run_time' DateTime field,
# 'interval' type has been changed from Integer to String
VERSION = '1.3'
dbapi = db_api.get_instance()
@ -94,11 +96,13 @@ class Audit(base.WatcherPersistentObject, base.WatcherObject,
'audit_type': wfields.StringField(),
'state': wfields.StringField(),
'parameters': wfields.FlexibleDictField(nullable=True),
'interval': wfields.IntegerField(nullable=True),
'interval': wfields.StringField(nullable=True),
'scope': wfields.FlexibleListOfDictField(nullable=True),
'goal_id': wfields.IntegerField(),
'strategy_id': wfields.IntegerField(nullable=True),
'auto_trigger': wfields.BooleanField(),
'next_run_time': wfields.DateTimeField(nullable=True,
tzinfo_aware=False),
'goal': wfields.ObjectField('Goal', nullable=True),
'strategy': wfields.ObjectField('Strategy', nullable=True),

View File

@ -481,6 +481,7 @@ class TestPost(api_base.FunctionalTest):
del audit_dict['state']
del audit_dict['interval']
del audit_dict['scope']
del audit_dict['next_run_time']
response = self.post_json('/audits', audit_dict)
self.assertEqual('application/json', response.content_type)
@ -523,6 +524,7 @@ class TestPost(api_base.FunctionalTest):
del audit_dict['state']
del audit_dict['interval']
del audit_dict['scope']
del audit_dict['next_run_time']
# Make the audit template UUID some garbage value
audit_dict['audit_template_uuid'] = (
'01234567-8910-1112-1314-151617181920')
@ -545,6 +547,7 @@ class TestPost(api_base.FunctionalTest):
del audit_dict['state']
del audit_dict['interval']
del audit_dict['scope']
del audit_dict['next_run_time']
with mock.patch.object(self.dbapi, 'create_audit',
wraps=self.dbapi.create_audit) as cn_mock:
response = self.post_json('/audits', audit_dict)
@ -562,6 +565,7 @@ class TestPost(api_base.FunctionalTest):
del audit_dict['state']
del audit_dict['interval']
del audit_dict['scope']
del audit_dict['next_run_time']
response = self.post_json('/audits', audit_dict)
self.assertEqual('application/json', response.content_type)
@ -571,15 +575,16 @@ class TestPost(api_base.FunctionalTest):
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):
def test_create_continuous_audit_with_interval(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']
del audit_dict['scope']
del audit_dict['next_run_time']
audit_dict['audit_type'] = objects.audit.AuditType.CONTINUOUS.value
audit_dict['interval'] = 1200
audit_dict['interval'] = '1200'
response = self.post_json('/audits', audit_dict)
self.assertEqual('application/json', response.content_type)
@ -589,6 +594,48 @@ class TestPost(api_base.FunctionalTest):
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_with_cron_interval(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']
del audit_dict['scope']
del audit_dict['next_run_time']
audit_dict['audit_type'] = objects.audit.AuditType.CONTINUOUS.value
audit_dict['interval'] = '* * * * *'
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_with_wrong_interval(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']
del audit_dict['scope']
del audit_dict['next_run_time']
audit_dict['audit_type'] = objects.audit.AuditType.CONTINUOUS.value
audit_dict['interval'] = 'zxc'
response = self.post_json('/audits', audit_dict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(500, response.status_int)
expected_error_msg = ('Exactly 5 or 6 columns has to be '
'specified for iteratorexpression.')
self.assertTrue(response.json['error_message'])
self.assertIn(expected_error_msg, response.json['error_message'])
@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
@ -599,6 +646,7 @@ class TestPost(api_base.FunctionalTest):
audit_dict['audit_type'] = objects.audit.AuditType.CONTINUOUS.value
del audit_dict['interval']
del audit_dict['scope']
del audit_dict['next_run_time']
response = self.post_json('/audits', audit_dict, expect_errors=True)
self.assertEqual(400, response.status_int)
@ -616,8 +664,8 @@ class TestPost(api_base.FunctionalTest):
del audit_dict['uuid']
del audit_dict['state']
audit_dict['audit_type'] = objects.audit.AuditType.ONESHOT.value
audit_dict['interval'] = 1200
del audit_dict['scope']
del audit_dict['next_run_time']
response = self.post_json('/audits', audit_dict, expect_errors=True)
self.assertEqual(400, response.status_int)
@ -634,6 +682,7 @@ class TestPost(api_base.FunctionalTest):
del audit_dict['state']
del audit_dict['interval']
del audit_dict['scope']
del audit_dict['next_run_time']
response = self.post_json('/audits', audit_dict)
de_mock.assert_called_once_with(mock.ANY, response.json['uuid'])
@ -657,6 +706,7 @@ class TestPost(api_base.FunctionalTest):
del audit_dict['state']
del audit_dict['interval']
del audit_dict['scope']
del audit_dict['next_run_time']
response = self.post_json('/audits', audit_dict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
@ -678,6 +728,7 @@ class TestPost(api_base.FunctionalTest):
del audit_dict['state']
del audit_dict['interval']
del audit_dict['scope']
del audit_dict['next_run_time']
response = self.post_json('/audits', audit_dict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
@ -700,7 +751,7 @@ class TestPost(api_base.FunctionalTest):
audit_dict['audit_template_uuid'] = audit_template['uuid']
del_keys = ['uuid', 'goal_id', 'strategy_id', 'state', 'interval',
'scope']
'scope', 'next_run_time']
for k in del_keys:
del audit_dict[k]
@ -839,6 +890,7 @@ class TestAuditPolicyEnforcement(api_base.FunctionalTest):
del audit_dict['uuid']
del audit_dict['state']
del audit_dict['scope']
del audit_dict['next_run_time']
self._common_policy_check(
"audit:create", self.post_json, '/audits', audit_dict,
expect_errors=True)

View File

@ -89,11 +89,12 @@ def get_test_audit(**kwargs):
'updated_at': kwargs.get('updated_at'),
'deleted_at': kwargs.get('deleted_at'),
'parameters': kwargs.get('parameters', {}),
'interval': kwargs.get('interval', 3600),
'interval': kwargs.get('interval', '3600'),
'goal_id': kwargs.get('goal_id', 1),
'strategy_id': kwargs.get('strategy_id', None),
'scope': kwargs.get('scope', []),
'auto_trigger': kwargs.get('auto_trigger', False)
'auto_trigger': kwargs.get('auto_trigger', False),
'next_run_time': kwargs.get('next_run_time')
}
# ObjectField doesn't allow None nor dict, so if we want to simulate a
# non-eager object loading, the field should not be referenced at all.

View File

@ -14,12 +14,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import datetime
import mock
from oslo_utils import uuidutils
from apscheduler import job
from watcher.applier import rpcapi
from watcher.common import exception
from watcher.common import scheduling
from watcher.db.sqlalchemy import api as sq_api
from watcher.decision_engine.audit import continuous
@ -241,20 +244,65 @@ class TestContinuousAuditHandler(base.DbTestCase):
@mock.patch.object(scheduling.BackgroundSchedulerService, 'add_job')
@mock.patch.object(scheduling.BackgroundSchedulerService, 'get_jobs')
@mock.patch.object(objects.audit.Audit, 'list')
def test_launch_audits_periodically(self, mock_list, mock_jobs,
m_add_job, m_engine, m_service):
def test_launch_audits_periodically_with_interval(
self, mock_list, mock_jobs, m_add_job, m_engine, m_service):
audit_handler = continuous.ContinuousAuditHandler()
mock_list.return_value = self.audits
self.audits[0].next_run_time = (datetime.datetime.now() -
datetime.timedelta(seconds=1800))
mock_jobs.return_value = mock.MagicMock()
m_engine.return_value = mock.MagicMock()
m_add_job.return_value = audit_handler.execute_audit(
self.audits[0], self.context)
m_add_job.return_value = mock.MagicMock()
audit_handler.launch_audits_periodically()
m_service.assert_called()
m_engine.assert_called()
m_add_job.assert_called()
mock_jobs.assert_called()
self.assertIsNotNone(self.audits[0].next_run_time)
self.assertIsNone(self.audits[1].next_run_time)
@mock.patch.object(objects.service.Service, 'list')
@mock.patch.object(sq_api, 'get_engine')
@mock.patch.object(scheduling.BackgroundSchedulerService, 'add_job')
@mock.patch.object(scheduling.BackgroundSchedulerService, 'get_jobs')
@mock.patch.object(objects.audit.Audit, 'list')
def test_launch_audits_periodically_with_cron(
self, mock_list, mock_jobs, m_add_job, m_engine, m_service):
audit_handler = continuous.ContinuousAuditHandler()
mock_list.return_value = self.audits
self.audits[0].interval = "*/5 * * * *"
mock_jobs.return_value = mock.MagicMock()
m_engine.return_value = mock.MagicMock()
m_add_job.return_value = mock.MagicMock()
audit_handler.launch_audits_periodically()
m_service.assert_called()
m_engine.assert_called()
m_add_job.assert_called()
mock_jobs.assert_called()
self.assertIsNotNone(self.audits[0].next_run_time)
self.assertIsNone(self.audits[1].next_run_time)
@mock.patch.object(continuous.ContinuousAuditHandler, '_next_cron_time')
@mock.patch.object(objects.service.Service, 'list')
@mock.patch.object(sq_api, 'get_engine')
@mock.patch.object(scheduling.BackgroundSchedulerService, 'add_job')
@mock.patch.object(scheduling.BackgroundSchedulerService, 'get_jobs')
@mock.patch.object(objects.audit.Audit, 'list')
def test_launch_audits_periodically_with_invalid_cron(
self, mock_list, mock_jobs, m_add_job, m_engine, m_service,
mock_cron):
audit_handler = continuous.ContinuousAuditHandler()
mock_list.return_value = self.audits
self.audits[0].interval = "*/5* * * *"
mock_cron.side_effect = exception.CronFormatIsInvalid
mock_jobs.return_value = mock.MagicMock()
m_engine.return_value = mock.MagicMock()
m_add_job.return_value = mock.MagicMock()
self.assertRaises(exception.CronFormatIsInvalid,
audit_handler.launch_audits_periodically)
@mock.patch.object(objects.service.Service, 'list')
@mock.patch.object(sq_api, 'get_engine')
@ -273,7 +321,7 @@ class TestContinuousAuditHandler(base.DbTestCase):
args=[mock.ANY, mock.ANY],
seconds=3600,
name='execute_audit',
next_run_time=mock.ANY) for audit in self.audits]
next_run_time=mock.ANY) for _ in self.audits]
audit_handler.launch_audits_periodically()
m_add_job.assert_has_calls(calls)

View File

@ -94,6 +94,8 @@ class TestActionPlanNotification(base.DbTestCase):
"audit": {
"watcher_object.data": {
"interval": None,
"next_run_time": None,
"auto_trigger": False,
"parameters": {},
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
"strategy_uuid": None,
@ -108,7 +110,7 @@ class TestActionPlanNotification(base.DbTestCase):
},
"watcher_object.name": "TerseAuditPayload",
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0"
"watcher_object.version": "1.1"
},
"deleted_at": None,
"state": "ONGOING",
@ -168,6 +170,8 @@ class TestActionPlanNotification(base.DbTestCase):
"audit": {
"watcher_object.data": {
"interval": None,
"next_run_time": None,
"auto_trigger": False,
"parameters": {},
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
"strategy_uuid": None,
@ -182,7 +186,7 @@ class TestActionPlanNotification(base.DbTestCase):
},
"watcher_object.name": "TerseAuditPayload",
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0"
"watcher_object.version": "1.1"
},
"deleted_at": None,
"state": "PENDING",
@ -234,6 +238,8 @@ class TestActionPlanNotification(base.DbTestCase):
"audit": {
"watcher_object.data": {
"interval": None,
"next_run_time": None,
"auto_trigger": False,
"parameters": {},
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
"strategy_uuid": None,
@ -248,7 +254,7 @@ class TestActionPlanNotification(base.DbTestCase):
},
"watcher_object.name": "TerseAuditPayload",
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0"
"watcher_object.version": "1.1"
},
"deleted_at": None,
"state": "DELETED",
@ -287,9 +293,11 @@ class TestActionPlanNotification(base.DbTestCase):
"audit": {
"watcher_object.namespace": "watcher",
"watcher_object.name": "TerseAuditPayload",
"watcher_object.version": "1.0",
"watcher_object.version": "1.1",
"watcher_object.data": {
"interval": None,
"next_run_time": None,
"auto_trigger": False,
"parameters": {},
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
"strategy_uuid": None,
@ -373,6 +381,8 @@ class TestActionPlanNotification(base.DbTestCase):
"audit": {
"watcher_object.data": {
"interval": None,
"next_run_time": None,
"auto_trigger": False,
"parameters": {},
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
"strategy_uuid": None,
@ -387,7 +397,7 @@ class TestActionPlanNotification(base.DbTestCase):
},
"watcher_object.name": "TerseAuditPayload",
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0"
"watcher_object.version": "1.1"
},
"global_efficacy": {},
"state": "ONGOING",

View File

@ -69,9 +69,11 @@ class TestAuditNotification(base.DbTestCase):
self.assertDictEqual(
{
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0",
"watcher_object.version": "1.1",
"watcher_object.data": {
"interval": None,
"next_run_time": None,
"auto_trigger": False,
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
"strategy": {
"watcher_object.namespace": "watcher",
@ -141,9 +143,11 @@ class TestAuditNotification(base.DbTestCase):
self.assertDictEqual(
{
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0",
"watcher_object.version": "1.1",
"watcher_object.data": {
"interval": None,
"next_run_time": None,
"auto_trigger": False,
"parameters": {},
"uuid": "10a47dd1-4874-4298-91cf-eff046dbdb8d",
"goal_uuid": "f7ad87ae-4298-91cf-93a0-f35a852e3652",
@ -200,9 +204,11 @@ class TestAuditNotification(base.DbTestCase):
self.assertDictEqual(
{
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0",
"watcher_object.version": "1.1",
"watcher_object.data": {
"interval": None,
"next_run_time": None,
"auto_trigger": False,
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
"strategy": {
"watcher_object.namespace": "watcher",
@ -263,9 +269,11 @@ class TestAuditNotification(base.DbTestCase):
self.assertDictEqual(
{
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0",
"watcher_object.version": "1.1",
"watcher_object.data": {
"interval": None,
"next_run_time": None,
"auto_trigger": False,
"strategy_uuid": "cb3d0b58-4415-4d90-b75b-1e96878730e3",
"strategy": {
"watcher_object.namespace": "watcher",
@ -350,6 +358,8 @@ class TestAuditNotification(base.DbTestCase):
"watcher_object.version": "1.0"
},
"interval": None,
"next_run_time": None,
"auto_trigger": False,
"parameters": {},
"scope": [],
"state": "ONGOING",
@ -374,7 +384,7 @@ class TestAuditNotification(base.DbTestCase):
},
"watcher_object.name": "AuditActionPayload",
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0"
"watcher_object.version": "1.1"
}
},
notification
@ -434,6 +444,8 @@ class TestAuditNotification(base.DbTestCase):
"watcher_object.version": "1.0"
},
"interval": None,
"next_run_time": None,
"auto_trigger": False,
"parameters": {},
"scope": [],
"state": "ONGOING",
@ -458,7 +470,7 @@ class TestAuditNotification(base.DbTestCase):
},
"watcher_object.name": "AuditActionPayload",
"watcher_object.namespace": "watcher",
"watcher_object.version": "1.0"
"watcher_object.version": "1.1"
}
},
notification

View File

@ -254,17 +254,17 @@ expected_notification_fingerprints = {
'ExceptionNotification': '1.0-9b69de0724fda8310d05e18418178866',
'ExceptionPayload': '1.0-4516ae282a55fe2fd5c754967ee6248b',
'NotificationPublisher': '1.0-bbbc1402fb0e443a3eb227cc52b61545',
'TerseAuditPayload': '1.0-aaf31166b8698f08d12cae98c380b8e0',
'AuditPayload': '1.0-30c85c834648c8ca11f54fc5e084d86b',
'TerseAuditPayload': '1.1-19b0e9224c0953366418a30ed785f267',
'AuditPayload': '1.1-4c59e0cc5d30c42d3b842ce0332709d5',
'AuditStateUpdatePayload': '1.0-1a1b606bf14a2c468800c2b010801ce5',
'AuditUpdateNotification': '1.0-9b69de0724fda8310d05e18418178866',
'AuditUpdatePayload': '1.0-d3aace28d9eb978c1ecf833e108f61f7',
'AuditUpdatePayload': '1.1-9b1f725e736051b976571701e5cc1e55',
'AuditCreateNotification': '1.0-9b69de0724fda8310d05e18418178866',
'AuditCreatePayload': '1.0-30c85c834648c8ca11f54fc5e084d86b',
'AuditCreatePayload': '1.1-4c59e0cc5d30c42d3b842ce0332709d5',
'AuditDeleteNotification': '1.0-9b69de0724fda8310d05e18418178866',
'AuditDeletePayload': '1.0-30c85c834648c8ca11f54fc5e084d86b',
'AuditDeletePayload': '1.1-4c59e0cc5d30c42d3b842ce0332709d5',
'AuditActionNotification': '1.0-9b69de0724fda8310d05e18418178866',
'AuditActionPayload': '1.0-09f5d005f94ba9e5f6b9200170332c52',
'AuditActionPayload': '1.1-5a43e7321495c19f98ef5663efa0a821',
'GoalPayload': '1.0-fa1fecb8b01dd047eef808ded4d50d1a',
'StrategyPayload': '1.0-94f01c137b083ac236ae82573c1fcfc1',
'ActionPlanActionPayload': '1.0-d9f134708e06cf2ff2d3b8d522ac2aa8',

View File

@ -412,7 +412,7 @@ expected_object_fingerprints = {
'Goal': '1.0-93881622db05e7b67a65ca885b4a022e',
'Strategy': '1.1-73f164491bdd4c034f48083a51bdeb7b',
'AuditTemplate': '1.1-b291973ffc5efa2c61b24fe34fdccc0b',
'Audit': '1.2-910522db78b7b1cb59df614754656db4',
'Audit': '1.3-f47ffb1ee79d8248eb991674bda565ce',
'ActionPlan': '2.0-394f1abbf5d73d7b6675a118fe1a0284',
'Action': '2.0-1dd4959a7e7ac30c62ef170fe08dd935',
'EfficacyIndicator': '1.0-655b71234a82bc7478aff964639c4bb0',

View File

@ -171,7 +171,7 @@ class BaseInfraOptimTest(test.BaseTestCase):
:param audit_template_uuid: Audit Template UUID this audit will use
:param audit_type: Audit type (either ONESHOT or CONTINUOUS)
:param state: Audit state (str)
:param interval: Audit interval in seconds (int)
:param interval: Audit interval in seconds or cron syntax (str)
:return: A tuple with The HTTP response and its body
"""
resp, body = cls.client.create_audit(

View File

@ -63,7 +63,7 @@ class TestCreateUpdateDeleteAudit(base.BaseInfraOptimTest):
audit_params = dict(
audit_template_uuid=audit_template['uuid'],
audit_type='CONTINUOUS',
interval=7200,
interval='7200',
)
_, body = self.create_audit(**audit_params)