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 jsonschema!=2.5.0,<3.0.0,>=2.0.0 # MIT
keystonemiddleware>=4.12.0 # Apache-2.0 keystonemiddleware>=4.12.0 # Apache-2.0
lxml!=3.7.0,>=2.3 # BSD lxml!=3.7.0,>=2.3 # BSD
croniter>=0.3.4 # MIT License
oslo.concurrency>=3.8.0 # Apache-2.0 oslo.concurrency>=3.8.0 # Apache-2.0
oslo.cache>=1.5.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 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, parameters = wtypes.wsattr({wtypes.text: types.jsontype}, mandatory=False,
default={}) default={})
interval = wsme.wsattr(int, mandatory=False) interval = wsme.wsattr(types.interval_or_cron, mandatory=False)
scope = wtypes.wsattr(types.jsontype, readonly=True) scope = wtypes.wsattr(types.jsontype, readonly=True)
@ -261,7 +261,7 @@ class Audit(base.APIBase):
links = wsme.wsattr([link.Link], readonly=True) links = wsme.wsattr([link.Link], readonly=True)
"""A list containing a self link and associated audit links""" """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)""" """Launch audit periodically (in seconds)"""
scope = wsme.wsattr(types.jsontype, mandatory=False) scope = wsme.wsattr(types.jsontype, mandatory=False)
@ -270,6 +270,9 @@ class Audit(base.APIBase):
auto_trigger = wsme.wsattr(bool, mandatory=False, default=False) auto_trigger = wsme.wsattr(bool, mandatory=False, default=False)
"""Autoexecute action plan once audit is succeeded""" """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): def __init__(self, **kwargs):
self.fields = [] self.fields = []
fields = list(objects.Audit.fields) fields = list(objects.Audit.fields)
@ -301,7 +304,8 @@ class Audit(base.APIBase):
audit.unset_fields_except(['uuid', 'audit_type', 'state', audit.unset_fields_except(['uuid', 'audit_type', 'state',
'goal_uuid', 'interval', 'scope', 'goal_uuid', 'interval', 'scope',
'strategy_uuid', 'goal_name', 'strategy_uuid', 'goal_name',
'strategy_name', 'auto_trigger']) 'strategy_name', 'auto_trigger',
'next_run_time'])
audit.links = [link.Link.make_link('self', url, audit.links = [link.Link.make_link('self', url,
'audits', audit.uuid), 'audits', audit.uuid),
@ -325,9 +329,10 @@ class Audit(base.APIBase):
created_at=datetime.datetime.utcnow(), created_at=datetime.datetime.utcnow(),
deleted_at=None, deleted_at=None,
updated_at=datetime.datetime.utcnow(), updated_at=datetime.datetime.utcnow(),
interval=7200, interval='7200',
scope=[], scope=[],
auto_trigger=False) auto_trigger=False,
next_run_time=datetime.datetime.utcnow())
sample.goal_id = '7ae81bb3-dec3-4289-8d6c-da80bd8001ae' sample.goal_id = '7ae81bb3-dec3-4289-8d6c-da80bd8001ae'
sample.strategy_id = '7ae81bb3-dec3-4289-8d6c-da80bd8001ff' sample.strategy_id = '7ae81bb3-dec3-4289-8d6c-da80bd8001ff'

View File

@ -43,6 +43,28 @@ class UuidOrNameType(wtypes.UserType):
return UuidOrNameType.validate(value) 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): class NameType(wtypes.UserType):
"""A simple logical name type.""" """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") 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): class GoalNotFound(ResourceNotFound):
msg_fmt = _("Goal %(goal)s could not be found") msg_fmt = _("Goal %(goal)s could not be found")
@ -418,6 +422,10 @@ class WildcardCharacterIsUsed(WatcherException):
"wildcard character.") "wildcard character.")
class CronFormatIsInvalid(WatcherException):
msg_fmt = _("Provided cron is invalid: %(message)s")
# Model # Model
class ComputeResourceNotFound(WatcherException): class ComputeResourceNotFound(WatcherException):

View File

@ -16,8 +16,11 @@
"""Utilities and helper functions.""" """Utilities and helper functions."""
import datetime
import re import re
from croniter import croniter
from jsonschema import validators from jsonschema import validators
from oslo_log import log as logging from oslo_log import log as logging
from oslo_utils import strutils from oslo_utils import strutils
@ -63,6 +66,15 @@ is_int_like = strutils.is_int_like
strtime = timeutils.strtime 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): def safe_rstrip(value, chars=None):
"""Removes trailing characters from a string if that does not make it empty """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)) audit_type = Column(String(20))
state = Column(String(20), nullable=True) state = Column(String(20), nullable=True)
parameters = Column(JSONEncodedDict, 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) goal_id = Column(Integer, ForeignKey('goals.id'), nullable=False)
strategy_id = Column(Integer, ForeignKey('strategies.id'), nullable=True) strategy_id = Column(Integer, ForeignKey('strategies.id'), nullable=True)
scope = Column(JSONEncodedList, nullable=True) scope = Column(JSONEncodedList, nullable=True)
auto_trigger = Column(Boolean, nullable=False) auto_trigger = Column(Boolean, nullable=False)
next_run_time = Column(DateTime, nullable=True)
goal = orm.relationship(Goal, foreign_keys=goal_id, lazy=None) goal = orm.relationship(Goal, foreign_keys=goal_id, lazy=None)
strategy = orm.relationship(Strategy, foreign_keys=strategy_id, lazy=None) strategy = orm.relationship(Strategy, foreign_keys=strategy_id, lazy=None)

View File

@ -19,11 +19,14 @@
import datetime import datetime
from dateutil import tz
from apscheduler.jobstores import memory from apscheduler.jobstores import memory
from croniter import croniter
from watcher.common import context from watcher.common import context
from watcher.common import scheduling from watcher.common import scheduling
from watcher.common import utils
from watcher import conf from watcher import conf
from watcher.db.sqlalchemy import api as sq_api from watcher.db.sqlalchemy import api as sq_api
from watcher.db.sqlalchemy import job_store from watcher.db.sqlalchemy import job_store
@ -81,11 +84,38 @@ class ContinuousAuditHandler(base.AuditHandler):
plan.save() plan.save()
return solution 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 @classmethod
def execute_audit(cls, audit, request_context): def execute_audit(cls, audit, request_context):
self = cls() self = cls()
if not self._is_audit_inactive(audit): 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): def launch_audits_periodically(self):
audit_context = context.RequestContext(is_admin=True) audit_context = context.RequestContext(is_admin=True)
@ -101,13 +131,34 @@ class ContinuousAuditHandler(base.AuditHandler):
job.args for job in self.scheduler.get_jobs() job.args for job in self.scheduler.get_jobs()
if job.name == 'execute_audit'] if job.name == 'execute_audit']
for audit in audits: 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]: if audit.uuid not in [arg[0].uuid for arg in scheduler_job_args]:
self.scheduler.add_job( # if interval is provided with seconds
self.execute_audit, 'interval', if utils.is_int_like(audit.interval):
args=[audit, audit_context], # if audit has already been provided and we need
seconds=audit.interval, # to restore it after shutdown
name='execute_audit', if audit.next_run_time is not None:
next_run_time=datetime.datetime.now()) 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): def start(self):
self.scheduler.add_job( self.scheduler.add_job(

View File

@ -39,6 +39,8 @@ class TerseAuditPayload(notificationbase.NotificationPayloadBase):
'parameters': ('audit', 'parameters'), 'parameters': ('audit', 'parameters'),
'interval': ('audit', 'interval'), 'interval': ('audit', 'interval'),
'scope': ('audit', 'scope'), 'scope': ('audit', 'scope'),
'auto_trigger': ('audit', 'auto_trigger'),
'next_run_time': ('audit', 'next_run_time'),
'created_at': ('audit', 'created_at'), 'created_at': ('audit', 'created_at'),
'updated_at': ('audit', 'updated_at'), 'updated_at': ('audit', 'updated_at'),
@ -46,17 +48,22 @@ class TerseAuditPayload(notificationbase.NotificationPayloadBase):
} }
# Version 1.0: Initial version # 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 = { fields = {
'uuid': wfields.UUIDField(), 'uuid': wfields.UUIDField(),
'audit_type': wfields.StringField(), 'audit_type': wfields.StringField(),
'state': wfields.StringField(), 'state': wfields.StringField(),
'parameters': wfields.FlexibleDictField(nullable=True), 'parameters': wfields.FlexibleDictField(nullable=True),
'interval': wfields.IntegerField(nullable=True), 'interval': wfields.StringField(nullable=True),
'scope': wfields.FlexibleListOfDictField(nullable=True), 'scope': wfields.FlexibleListOfDictField(nullable=True),
'goal_uuid': wfields.UUIDField(), 'goal_uuid': wfields.UUIDField(),
'strategy_uuid': wfields.UUIDField(nullable=True), 'strategy_uuid': wfields.UUIDField(nullable=True),
'auto_trigger': wfields.BooleanField(),
'next_run_time': wfields.DateTimeField(nullable=True),
'created_at': wfields.DateTimeField(nullable=True), 'created_at': wfields.DateTimeField(nullable=True),
'updated_at': wfields.DateTimeField(nullable=True), 'updated_at': wfields.DateTimeField(nullable=True),
@ -79,6 +86,8 @@ class AuditPayload(TerseAuditPayload):
'parameters': ('audit', 'parameters'), 'parameters': ('audit', 'parameters'),
'interval': ('audit', 'interval'), 'interval': ('audit', 'interval'),
'scope': ('audit', 'scope'), 'scope': ('audit', 'scope'),
'auto_trigger': ('audit', 'auto_trigger'),
'next_run_time': ('audit', 'next_run_time'),
'created_at': ('audit', 'created_at'), 'created_at': ('audit', 'created_at'),
'updated_at': ('audit', 'updated_at'), 'updated_at': ('audit', 'updated_at'),
@ -86,7 +95,9 @@ class AuditPayload(TerseAuditPayload):
} }
# Version 1.0: Initial version # Version 1.0: Initial version
VERSION = '1.0' # Version 1.1: Added 'auto_trigger' field,
# Added 'next_run_time' field
VERSION = '1.1'
fields = { fields = {
'goal': wfields.ObjectField('GoalPayload'), 'goal': wfields.ObjectField('GoalPayload'),
@ -119,7 +130,9 @@ class AuditStateUpdatePayload(notificationbase.NotificationPayloadBase):
@base.WatcherObjectRegistry.register_notification @base.WatcherObjectRegistry.register_notification
class AuditCreatePayload(AuditPayload): class AuditCreatePayload(AuditPayload):
# Version 1.0: Initial version # Version 1.0: Initial version
VERSION = '1.0' # Version 1.1: Added 'auto_trigger' field,
# Added 'next_run_time' field
VERSION = '1.1'
fields = {} fields = {}
def __init__(self, audit, goal, strategy): def __init__(self, audit, goal, strategy):
@ -133,7 +146,9 @@ class AuditCreatePayload(AuditPayload):
@base.WatcherObjectRegistry.register_notification @base.WatcherObjectRegistry.register_notification
class AuditUpdatePayload(AuditPayload): class AuditUpdatePayload(AuditPayload):
# Version 1.0: Initial version # Version 1.0: Initial version
VERSION = '1.0' # Version 1.1: Added 'auto_trigger' field,
# Added 'next_run_time' field
VERSION = '1.1'
fields = { fields = {
'state_update': wfields.ObjectField('AuditStateUpdatePayload'), 'state_update': wfields.ObjectField('AuditStateUpdatePayload'),
} }
@ -150,7 +165,9 @@ class AuditUpdatePayload(AuditPayload):
@base.WatcherObjectRegistry.register_notification @base.WatcherObjectRegistry.register_notification
class AuditActionPayload(AuditPayload): class AuditActionPayload(AuditPayload):
# Version 1.0: Initial version # Version 1.0: Initial version
VERSION = '1.0' # Version 1.1: Added 'auto_trigger' field,
# Added 'next_run_time' field
VERSION = '1.1'
fields = { fields = {
'fault': wfields.ObjectField('ExceptionPayload', nullable=True), 'fault': wfields.ObjectField('ExceptionPayload', nullable=True),
} }
@ -167,7 +184,9 @@ class AuditActionPayload(AuditPayload):
@base.WatcherObjectRegistry.register_notification @base.WatcherObjectRegistry.register_notification
class AuditDeletePayload(AuditPayload): class AuditDeletePayload(AuditPayload):
# Version 1.0: Initial version # Version 1.0: Initial version
VERSION = '1.0' # Version 1.1: Added 'auto_trigger' field,
# Added 'next_run_time' field
VERSION = '1.1'
fields = {} fields = {}
def __init__(self, audit, goal, strategy): 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.0: Initial version
# Version 1.1: Added 'goal' and 'strategy' object field # Version 1.1: Added 'goal' and 'strategy' object field
# Version 1.2 Added 'auto_trigger' boolean field # Version 1.2: Added 'auto_trigger' boolean field
VERSION = '1.2' # 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() dbapi = db_api.get_instance()
@ -94,11 +96,13 @@ class Audit(base.WatcherPersistentObject, base.WatcherObject,
'audit_type': wfields.StringField(), 'audit_type': wfields.StringField(),
'state': wfields.StringField(), 'state': wfields.StringField(),
'parameters': wfields.FlexibleDictField(nullable=True), 'parameters': wfields.FlexibleDictField(nullable=True),
'interval': wfields.IntegerField(nullable=True), 'interval': wfields.StringField(nullable=True),
'scope': wfields.FlexibleListOfDictField(nullable=True), 'scope': wfields.FlexibleListOfDictField(nullable=True),
'goal_id': wfields.IntegerField(), 'goal_id': wfields.IntegerField(),
'strategy_id': wfields.IntegerField(nullable=True), 'strategy_id': wfields.IntegerField(nullable=True),
'auto_trigger': wfields.BooleanField(), 'auto_trigger': wfields.BooleanField(),
'next_run_time': wfields.DateTimeField(nullable=True,
tzinfo_aware=False),
'goal': wfields.ObjectField('Goal', nullable=True), 'goal': wfields.ObjectField('Goal', nullable=True),
'strategy': wfields.ObjectField('Strategy', 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['state']
del audit_dict['interval'] del audit_dict['interval']
del audit_dict['scope'] del audit_dict['scope']
del audit_dict['next_run_time']
response = self.post_json('/audits', audit_dict) response = self.post_json('/audits', audit_dict)
self.assertEqual('application/json', response.content_type) self.assertEqual('application/json', response.content_type)
@ -523,6 +524,7 @@ class TestPost(api_base.FunctionalTest):
del audit_dict['state'] del audit_dict['state']
del audit_dict['interval'] del audit_dict['interval']
del audit_dict['scope'] del audit_dict['scope']
del audit_dict['next_run_time']
# Make the audit template UUID some garbage value # Make the audit template UUID some garbage value
audit_dict['audit_template_uuid'] = ( audit_dict['audit_template_uuid'] = (
'01234567-8910-1112-1314-151617181920') '01234567-8910-1112-1314-151617181920')
@ -545,6 +547,7 @@ class TestPost(api_base.FunctionalTest):
del audit_dict['state'] del audit_dict['state']
del audit_dict['interval'] del audit_dict['interval']
del audit_dict['scope'] del audit_dict['scope']
del audit_dict['next_run_time']
with mock.patch.object(self.dbapi, 'create_audit', with mock.patch.object(self.dbapi, 'create_audit',
wraps=self.dbapi.create_audit) as cn_mock: wraps=self.dbapi.create_audit) as cn_mock:
response = self.post_json('/audits', audit_dict) response = self.post_json('/audits', audit_dict)
@ -562,6 +565,7 @@ class TestPost(api_base.FunctionalTest):
del audit_dict['state'] del audit_dict['state']
del audit_dict['interval'] del audit_dict['interval']
del audit_dict['scope'] del audit_dict['scope']
del audit_dict['next_run_time']
response = self.post_json('/audits', audit_dict) response = self.post_json('/audits', audit_dict)
self.assertEqual('application/json', response.content_type) 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'])) self.assertTrue(utils.is_uuid_like(response.json['uuid']))
@mock.patch.object(deapi.DecisionEngineAPI, 'trigger_audit') @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 mock_trigger_audit.return_value = mock.ANY
audit_dict = post_get_test_audit() audit_dict = post_get_test_audit()
del audit_dict['uuid'] del audit_dict['uuid']
del audit_dict['state'] del audit_dict['state']
del audit_dict['scope'] del audit_dict['scope']
del audit_dict['next_run_time']
audit_dict['audit_type'] = objects.audit.AuditType.CONTINUOUS.value audit_dict['audit_type'] = objects.audit.AuditType.CONTINUOUS.value
audit_dict['interval'] = 1200 audit_dict['interval'] = '1200'
response = self.post_json('/audits', audit_dict) response = self.post_json('/audits', audit_dict)
self.assertEqual('application/json', response.content_type) 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.assertEqual(audit_dict['interval'], response.json['interval'])
self.assertTrue(utils.is_uuid_like(response.json['uuid'])) 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') @mock.patch.object(deapi.DecisionEngineAPI, 'trigger_audit')
def test_create_continuous_audit_without_period(self, mock_trigger_audit): def test_create_continuous_audit_without_period(self, mock_trigger_audit):
mock_trigger_audit.return_value = mock.ANY 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 audit_dict['audit_type'] = objects.audit.AuditType.CONTINUOUS.value
del audit_dict['interval'] del audit_dict['interval']
del audit_dict['scope'] del audit_dict['scope']
del audit_dict['next_run_time']
response = self.post_json('/audits', audit_dict, expect_errors=True) response = self.post_json('/audits', audit_dict, expect_errors=True)
self.assertEqual(400, response.status_int) self.assertEqual(400, response.status_int)
@ -616,8 +664,8 @@ class TestPost(api_base.FunctionalTest):
del audit_dict['uuid'] del audit_dict['uuid']
del audit_dict['state'] del audit_dict['state']
audit_dict['audit_type'] = objects.audit.AuditType.ONESHOT.value audit_dict['audit_type'] = objects.audit.AuditType.ONESHOT.value
audit_dict['interval'] = 1200
del audit_dict['scope'] del audit_dict['scope']
del audit_dict['next_run_time']
response = self.post_json('/audits', audit_dict, expect_errors=True) response = self.post_json('/audits', audit_dict, expect_errors=True)
self.assertEqual(400, response.status_int) self.assertEqual(400, response.status_int)
@ -634,6 +682,7 @@ class TestPost(api_base.FunctionalTest):
del audit_dict['state'] del audit_dict['state']
del audit_dict['interval'] del audit_dict['interval']
del audit_dict['scope'] del audit_dict['scope']
del audit_dict['next_run_time']
response = self.post_json('/audits', audit_dict) response = self.post_json('/audits', audit_dict)
de_mock.assert_called_once_with(mock.ANY, response.json['uuid']) 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['state']
del audit_dict['interval'] del audit_dict['interval']
del audit_dict['scope'] del audit_dict['scope']
del audit_dict['next_run_time']
response = self.post_json('/audits', audit_dict, expect_errors=True) response = self.post_json('/audits', audit_dict, expect_errors=True)
self.assertEqual('application/json', response.content_type) self.assertEqual('application/json', response.content_type)
@ -678,6 +728,7 @@ class TestPost(api_base.FunctionalTest):
del audit_dict['state'] del audit_dict['state']
del audit_dict['interval'] del audit_dict['interval']
del audit_dict['scope'] del audit_dict['scope']
del audit_dict['next_run_time']
response = self.post_json('/audits', audit_dict, expect_errors=True) response = self.post_json('/audits', audit_dict, expect_errors=True)
self.assertEqual('application/json', response.content_type) self.assertEqual('application/json', response.content_type)
@ -700,7 +751,7 @@ class TestPost(api_base.FunctionalTest):
audit_dict['audit_template_uuid'] = audit_template['uuid'] audit_dict['audit_template_uuid'] = audit_template['uuid']
del_keys = ['uuid', 'goal_id', 'strategy_id', 'state', 'interval', del_keys = ['uuid', 'goal_id', 'strategy_id', 'state', 'interval',
'scope'] 'scope', 'next_run_time']
for k in del_keys: for k in del_keys:
del audit_dict[k] del audit_dict[k]
@ -839,6 +890,7 @@ class TestAuditPolicyEnforcement(api_base.FunctionalTest):
del audit_dict['uuid'] del audit_dict['uuid']
del audit_dict['state'] del audit_dict['state']
del audit_dict['scope'] del audit_dict['scope']
del audit_dict['next_run_time']
self._common_policy_check( self._common_policy_check(
"audit:create", self.post_json, '/audits', audit_dict, "audit:create", self.post_json, '/audits', audit_dict,
expect_errors=True) expect_errors=True)

View File

@ -89,11 +89,12 @@ def get_test_audit(**kwargs):
'updated_at': kwargs.get('updated_at'), 'updated_at': kwargs.get('updated_at'),
'deleted_at': kwargs.get('deleted_at'), 'deleted_at': kwargs.get('deleted_at'),
'parameters': kwargs.get('parameters', {}), 'parameters': kwargs.get('parameters', {}),
'interval': kwargs.get('interval', 3600), 'interval': kwargs.get('interval', '3600'),
'goal_id': kwargs.get('goal_id', 1), 'goal_id': kwargs.get('goal_id', 1),
'strategy_id': kwargs.get('strategy_id', None), 'strategy_id': kwargs.get('strategy_id', None),
'scope': kwargs.get('scope', []), '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 # 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. # 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 # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import datetime
import mock import mock
from oslo_utils import uuidutils from oslo_utils import uuidutils
from apscheduler import job from apscheduler import job
from watcher.applier import rpcapi from watcher.applier import rpcapi
from watcher.common import exception
from watcher.common import scheduling from watcher.common import scheduling
from watcher.db.sqlalchemy import api as sq_api from watcher.db.sqlalchemy import api as sq_api
from watcher.decision_engine.audit import continuous 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, 'add_job')
@mock.patch.object(scheduling.BackgroundSchedulerService, 'get_jobs') @mock.patch.object(scheduling.BackgroundSchedulerService, 'get_jobs')
@mock.patch.object(objects.audit.Audit, 'list') @mock.patch.object(objects.audit.Audit, 'list')
def test_launch_audits_periodically(self, mock_list, mock_jobs, def test_launch_audits_periodically_with_interval(
m_add_job, m_engine, m_service): self, mock_list, mock_jobs, m_add_job, m_engine, m_service):
audit_handler = continuous.ContinuousAuditHandler() audit_handler = continuous.ContinuousAuditHandler()
mock_list.return_value = self.audits 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() mock_jobs.return_value = mock.MagicMock()
m_engine.return_value = mock.MagicMock() m_engine.return_value = mock.MagicMock()
m_add_job.return_value = audit_handler.execute_audit( m_add_job.return_value = mock.MagicMock()
self.audits[0], self.context)
audit_handler.launch_audits_periodically() audit_handler.launch_audits_periodically()
m_service.assert_called() m_service.assert_called()
m_engine.assert_called() m_engine.assert_called()
m_add_job.assert_called() m_add_job.assert_called()
mock_jobs.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(objects.service.Service, 'list')
@mock.patch.object(sq_api, 'get_engine') @mock.patch.object(sq_api, 'get_engine')
@ -273,7 +321,7 @@ class TestContinuousAuditHandler(base.DbTestCase):
args=[mock.ANY, mock.ANY], args=[mock.ANY, mock.ANY],
seconds=3600, seconds=3600,
name='execute_audit', 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() audit_handler.launch_audits_periodically()
m_add_job.assert_has_calls(calls) m_add_job.assert_has_calls(calls)

View File

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

View File

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

View File

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

View File

@ -412,7 +412,7 @@ expected_object_fingerprints = {
'Goal': '1.0-93881622db05e7b67a65ca885b4a022e', 'Goal': '1.0-93881622db05e7b67a65ca885b4a022e',
'Strategy': '1.1-73f164491bdd4c034f48083a51bdeb7b', 'Strategy': '1.1-73f164491bdd4c034f48083a51bdeb7b',
'AuditTemplate': '1.1-b291973ffc5efa2c61b24fe34fdccc0b', 'AuditTemplate': '1.1-b291973ffc5efa2c61b24fe34fdccc0b',
'Audit': '1.2-910522db78b7b1cb59df614754656db4', 'Audit': '1.3-f47ffb1ee79d8248eb991674bda565ce',
'ActionPlan': '2.0-394f1abbf5d73d7b6675a118fe1a0284', 'ActionPlan': '2.0-394f1abbf5d73d7b6675a118fe1a0284',
'Action': '2.0-1dd4959a7e7ac30c62ef170fe08dd935', 'Action': '2.0-1dd4959a7e7ac30c62ef170fe08dd935',
'EfficacyIndicator': '1.0-655b71234a82bc7478aff964639c4bb0', '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_template_uuid: Audit Template UUID this audit will use
:param audit_type: Audit type (either ONESHOT or CONTINUOUS) :param audit_type: Audit type (either ONESHOT or CONTINUOUS)
:param state: Audit state (str) :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 :return: A tuple with The HTTP response and its body
""" """
resp, body = cls.client.create_audit( resp, body = cls.client.create_audit(

View File

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