Removed deadline, version, extra & host_aggregate

As we are about to version the Watcher objects, we need to make sure
that upcoming model/object modifications are additive in order to
avoid having to bump the major version of the API. Therefore,
this changeset removes 4 unused DB fields that were exposed in their
associated Watcher objects (i.e. AuditTemplate and Audit).

Change-Id: Ifb0783f21cd66db16b31e3c8e376fc9d6c07dea3
Partially-Implements: blueprint watcher-versioned-objects
This commit is contained in:
Vincent Françoise 2016-10-03 14:38:49 +02:00
parent 750e6bf213
commit ed95d621f4
21 changed files with 50 additions and 184 deletions

View File

@ -4,7 +4,7 @@ actor Administrator
== Create some Audit settings ==
Administrator -> Watcher : create new Audit Template (i.e. Audit settings : goal, scope, deadline,...)
Administrator -> Watcher : create new Audit Template (i.e. Audit settings : goal, scope, ...)
Watcher -> Watcher : save Audit Template in database
Administrator <-- Watcher : Audit Template UUID

View File

@ -41,9 +41,7 @@ table(audit_templates) {
uuid : String[36]
name : String[63], nullable
description : String[255], nullable
host_aggregate : Integer, nullable
extra : JSONEncodedDict
version : String[15], nullable
scope : JSONEncodedList
created_at : DateTime
updated_at : DateTime
@ -59,10 +57,9 @@ table(audits) {
uuid : String[36]
audit_type : String[20]
state : String[20], nullable
deadline : DateTime, nullable
interval : Integer, nullable
parameters : JSONEncodedDict, nullable
host_aggregate : Integer, nullable
scope : JSONEncodedList, nullable
created_at : DateTime
updated_at : DateTime

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 72 KiB

View File

@ -54,16 +54,12 @@ class AuditPostType(wtypes.Base):
audit_template_uuid = wtypes.wsattr(types.uuid, mandatory=False)
scope = wtypes.wsattr(types.jsontype, readonly=True)
goal = wtypes.wsattr(wtypes.text, mandatory=False)
strategy = wtypes.wsattr(wtypes.text, mandatory=False)
audit_type = wtypes.wsattr(wtypes.text, mandatory=True)
deadline = wtypes.wsattr(datetime.datetime, mandatory=False)
state = wsme.wsattr(wtypes.text, readonly=True,
default=objects.audit.State.PENDING)
@ -71,6 +67,8 @@ class AuditPostType(wtypes.Base):
default={})
interval = wsme.wsattr(int, mandatory=False)
scope = wtypes.wsattr(types.jsontype, readonly=True)
def as_audit(self, context):
audit_type_values = [val.value for val in objects.audit.AuditType]
if self.audit_type not in audit_type_values:
@ -113,7 +111,6 @@ class AuditPostType(wtypes.Base):
pass
return Audit(
audit_type=self.audit_type,
deadline=self.deadline,
parameters=self.parameters,
goal_id=self.goal,
strategy_id=self.strategy,
@ -229,9 +226,6 @@ class Audit(base.APIBase):
audit_type = wtypes.text
"""Type of this audit"""
deadline = datetime.datetime
"""deadline of the audit"""
state = wtypes.text
"""This audit state"""
@ -291,8 +285,8 @@ class Audit(base.APIBase):
@staticmethod
def _convert_with_links(audit, url, expand=True):
if not expand:
audit.unset_fields_except(['uuid', 'audit_type', 'deadline',
'state', 'goal_uuid', 'interval',
audit.unset_fields_except(['uuid', 'audit_type', 'state',
'goal_uuid', 'interval', 'scope',
'strategy_uuid', 'goal_name',
'strategy_name'])
@ -315,7 +309,6 @@ class Audit(base.APIBase):
sample = cls(uuid='27e3153e-d5bf-4b7e-b517-fb518e17f34c',
audit_type='ONESHOT',
state='PENDING',
deadline=None,
created_at=datetime.datetime.utcnow(),
deleted_at=None,
updated_at=datetime.datetime.utcnow(),
@ -324,6 +317,7 @@ class Audit(base.APIBase):
sample.goal_id = '7ae81bb3-dec3-4289-8d6c-da80bd8001ae'
sample.strategy_id = '7ae81bb3-dec3-4289-8d6c-da80bd8001ff'
return cls._convert_with_links(sample, 'http://localhost:9322', expand)
@ -423,16 +417,14 @@ class AuditsController(rest.RestController):
@wsme_pecan.wsexpose(AuditCollection, types.uuid, int, wtypes.text,
wtypes.text, wtypes.text, wtypes.text, int)
def get_all(self, marker=None, limit=None,
sort_key='id', sort_dir='asc', goal=None,
strategy=None):
def get_all(self, marker=None, limit=None, sort_key='id', sort_dir='asc',
goal=None, strategy=None):
"""Retrieve a list of audits.
:param marker: pagination marker for large data sets.
:param limit: maximum number of resources to return in a single result.
:param sort_key: column to sort results by. Default: id.
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
id.
:param goal: goal UUID or name to filter by
:param strategy: strategy UUID or name to filter by
"""

View File

@ -41,11 +41,6 @@ settings related to the level of automation for the
A flag will indicate whether the :ref:`Action Plan <action_plan_definition>`
will be launched automatically or will need a manual confirmation from the
:ref:`Administrator <administrator_definition>`.
Last but not least, an :ref:`Audit Template <audit_template_definition>` may
contain a list of extra parameters related to the
:ref:`Strategy <strategy_definition>` configuration. These parameters can be
provided as a list of key-value pairs.
"""
import datetime
@ -79,21 +74,12 @@ class AuditTemplatePostType(wtypes.Base):
description = wtypes.wsattr(wtypes.text, mandatory=False)
"""Short description of this audit template"""
deadline = wsme.wsattr(datetime.datetime, mandatory=False)
"""deadline of the audit template"""
extra = wtypes.wsattr({wtypes.text: types.jsontype}, mandatory=False)
"""The metadata of the audit template"""
goal = wtypes.wsattr(wtypes.text, mandatory=True)
"""Goal UUID or name of the audit template"""
strategy = wtypes.wsattr(wtypes.text, mandatory=False)
"""Strategy UUID or name of the audit template"""
version = wtypes.text
"""Internal version of the audit template"""
scope = wtypes.wsattr(types.jsontype, mandatory=False, default=[])
"""Audit Scope"""
@ -101,13 +87,10 @@ class AuditTemplatePostType(wtypes.Base):
return AuditTemplate(
name=self.name,
description=self.description,
deadline=self.deadline,
extra=self.extra,
goal_id=self.goal, # Dirty trick ...
goal=self.goal,
strategy_id=self.strategy, # Dirty trick ...
strategy_uuid=self.strategy,
version=self.version,
scope=self.scope,
)
@ -308,15 +291,9 @@ class AuditTemplate(base.APIBase):
name = wtypes.text
"""Name of this audit template"""
description = wtypes.text
description = wtypes.wsattr(wtypes.text, mandatory=False)
"""Short description of this audit template"""
deadline = datetime.datetime
"""deadline of the audit template"""
extra = {wtypes.text: types.jsontype}
"""The metadata of the audit template"""
goal_uuid = wsme.wsproperty(
wtypes.text, _get_goal_uuid, _set_goal_uuid, mandatory=True)
"""Goal UUID the audit template refers to"""
@ -333,9 +310,6 @@ class AuditTemplate(base.APIBase):
wtypes.text, _get_strategy_name, _set_strategy_name, mandatory=False)
"""The name of the strategy this audit template refers to"""
version = wtypes.text
"""Internal version of the audit template"""
audits = wsme.wsattr([link.Link], readonly=True)
"""Links to the collection of audits contained in this audit template"""
@ -378,7 +352,7 @@ class AuditTemplate(base.APIBase):
if not expand:
audit_template.unset_fields_except(
['uuid', 'name', 'goal_uuid', 'goal_name',
'strategy_uuid', 'strategy_name'])
'scope', 'strategy_uuid', 'strategy_name'])
# The numeric ID should not be exposed to
# the user, it's internal only.
@ -407,7 +381,6 @@ class AuditTemplate(base.APIBase):
description='Description of my audit template',
goal_uuid='83e44733-b640-40e2-8d8a-7dd3be7134e6',
strategy_uuid='367d826e-b6a4-4b70-bc44-c3f6fe1c9986',
extra={'automatic': True},
created_at=datetime.datetime.utcnow(),
deleted_at=None,
updated_at=datetime.datetime.utcnow(),

View File

@ -275,7 +275,6 @@ class BaseConnection(object):
'name': 'example',
'description': 'free text description'
'goal': 'DUMMY'
'extra': {'automatic': True}
}
:returns: An audit template.
:raises: :py:class:`~.AuditTemplateAlreadyExists`
@ -375,7 +374,6 @@ class BaseConnection(object):
{
'uuid': utils.generate_uuid(),
'type': 'ONESHOT',
'deadline': None
}
:returns: An audit.
:raises: :py:class:`~.AuditAlreadyExists`

View File

@ -165,8 +165,6 @@ class AuditTemplate(Base):
description = Column(String(255), nullable=True)
goal_id = Column(Integer, ForeignKey('goals.id'), nullable=False)
strategy_id = Column(Integer, ForeignKey('strategies.id'), nullable=True)
extra = Column(JSONEncodedDict)
version = Column(String(15), nullable=True)
scope = Column(JSONEncodedList)
goal = orm.relationship(Goal, foreign_keys=goal_id, lazy=None)
@ -185,7 +183,6 @@ class Audit(Base):
uuid = Column(String(36))
audit_type = Column(String(20))
state = Column(String(20), nullable=True)
deadline = Column(DateTime, nullable=True)
parameters = Column(JSONEncodedDict, nullable=True)
interval = Column(Integer, nullable=True)
goal_id = Column(Integer, ForeignKey('goals.id'), nullable=False)

View File

@ -83,7 +83,6 @@ class Audit(base.WatcherObject):
'uuid': obj_utils.str_or_none,
'audit_type': obj_utils.str_or_none,
'state': obj_utils.str_or_none,
'deadline': obj_utils.datetime_or_str_or_none,
'parameters': obj_utils.dict_or_none,
'interval': obj_utils.int_or_none,
'goal_id': obj_utils.int_or_none,

View File

@ -67,8 +67,6 @@ class AuditTemplate(base.WatcherObject):
'description': obj_utils.str_or_none,
'goal_id': obj_utils.int_or_none,
'strategy_id': obj_utils.int_or_none,
'extra': obj_utils.dict_or_none,
'version': obj_utils.str_or_none,
'scope': obj_utils.list_or_none,
}

View File

@ -87,7 +87,7 @@ class TestListAudit(api_base.FunctionalTest):
self.assertEqual([], response['audits'])
def _assert_audit_fields(self, audit):
audit_fields = ['audit_type', 'deadline', 'state', 'goal_uuid',
audit_fields = ['audit_type', 'scope', 'state', 'goal_uuid',
'strategy_uuid']
for field in audit_fields:
self.assertIn(field, audit)

View File

@ -1,52 +0,0 @@
# 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.
"""Tests for custom SQLAlchemy types via Magnum DB."""
from oslo_db import exception as db_exc
from watcher.common import utils as w_utils
from watcher.db import api as dbapi
import watcher.db.sqlalchemy.api as sa_api
from watcher.db.sqlalchemy import models
from watcher.tests.db import base
class SqlAlchemyCustomTypesTestCase(base.DbTestCase):
def setUp(self):
super(SqlAlchemyCustomTypesTestCase, self).setUp()
self.dbapi = dbapi.get_instance()
def test_JSONEncodedDict_default_value(self):
# Create audit_template w/o extra
audit_template1_id = w_utils.generate_uuid()
self.dbapi.create_audit_template({'uuid': audit_template1_id,
'goal_id': "DUMMY"})
audit_template1 = sa_api.model_query(models.AuditTemplate) \
.filter_by(uuid=audit_template1_id).one()
self.assertEqual({}, audit_template1.extra)
def test_JSONEncodedDict_extra_value(self):
# Create audit_template with extra
audit_template2_id = w_utils.generate_uuid()
self.dbapi.create_audit_template({'uuid': audit_template2_id,
'goal_id': "DUMMY",
'extra': {'bar': 'foo'}})
audit_template2 = sa_api.model_query(models.AuditTemplate) \
.filter_by(uuid=audit_template2_id).one()
self.assertEqual('foo', audit_template2.extra['bar'])
def test_JSONEncodedDict_type_check(self):
self.assertRaises(db_exc.DBError,
self.dbapi.create_audit_template,
{'extra': ['this is not a dict']})

View File

@ -278,7 +278,6 @@ class DbActionPlanTestCase(base.DbTestCase):
id=2,
audit_type='ONESHOT',
uuid=w_utils.generate_uuid(),
deadline=None,
state=ap_objects.State.ONGOING)
action_plan1 = self._create_test_action_plan(
id=1,

View File

@ -291,13 +291,11 @@ class DbAuditTestCase(base.DbTestCase):
id=1,
audit_type=objects.audit.AuditType.ONESHOT.value,
uuid=w_utils.generate_uuid(),
deadline=None,
state=objects.audit.State.ONGOING)
audit2 = self._create_test_audit(
id=2,
audit_type='CONTINUOUS',
uuid=w_utils.generate_uuid(),
deadline=None,
state=objects.audit.State.PENDING)
res = self.dbapi.get_audit_list(

View File

@ -273,15 +273,21 @@ class DbAuditTemplateTestCase(base.DbTestCase):
uuid=w_utils.generate_uuid(),
name='My Audit Template 1',
description='Description of my audit template 1',
goal='DUMMY',
extra={'automatic': True})
goal='DUMMY')
audit_template2 = self._create_test_audit_template(
id=2,
uuid=w_utils.generate_uuid(),
name='My Audit Template 2',
description='Description of my audit template 2',
goal='DUMMY',
extra={'automatic': True})
goal='DUMMY')
res = self.dbapi.get_audit_template_list(
self.context, filters={'name': 'My Audit Template 1'})
self.assertEqual([audit_template1['id']], [r.id for r in res])
res = self.dbapi.get_audit_template_list(
self.context, filters={'name': 'Does not exist'})
self.assertEqual([], [r.id for r in res])
res = self.dbapi.get_audit_template_list(
self.context,

View File

@ -28,12 +28,10 @@ def get_test_audit_template(**kwargs):
'strategy_id': kwargs.get('strategy_id', None),
'name': kwargs.get('name', 'My Audit Template'),
'description': kwargs.get('description', 'Desc. Of My Audit Template'),
'extra': kwargs.get('extra', {'automatic': False}),
'version': kwargs.get('version', 'v1'),
'scope': kwargs.get('scope', []),
'created_at': kwargs.get('created_at'),
'updated_at': kwargs.get('updated_at'),
'deleted_at': kwargs.get('deleted_at'),
'scope': kwargs.get('scope', []),
}
@ -59,7 +57,6 @@ def get_test_audit(**kwargs):
'uuid': kwargs.get('uuid', '10a47dd1-4874-4298-91cf-eff046dbdb8d'),
'audit_type': kwargs.get('audit_type', 'ONESHOT'),
'state': kwargs.get('state'),
'deadline': kwargs.get('deadline'),
'created_at': kwargs.get('created_at'),
'updated_at': kwargs.get('updated_at'),
'deleted_at': kwargs.get('deleted_at'),

View File

@ -62,7 +62,7 @@ class InfraOptimClientJSON(base.BaseInfraOptimClient):
:param description: The description of the audit template.
:param goal_uuid: The related Goal UUID associated.
:param strategy_uuid: The related Strategy UUID associated.
:param extra: Metadata associated to this audit template.
:param audit_scope: Scope the audit should apply to.
:return: A tuple with the server response and the created audit
template.
"""
@ -76,7 +76,7 @@ class InfraOptimClientJSON(base.BaseInfraOptimClient):
'description': parameters.get('description'),
'goal': parameters.get('goal'),
'strategy': parameters.get('strategy'),
'extra': parameters.get('extra', {}),
'scope': parameters.get('scope', []),
}
return self._create_request('audit_templates', audit_template)

View File

@ -115,21 +115,21 @@ class BaseInfraOptimTest(test.BaseTestCase):
@classmethod
def create_audit_template(cls, goal, name=None, description=None,
strategy=None, extra=None):
strategy=None, scope=None):
"""Wrapper utility for creating a test audit template
:param goal: Goal UUID or name related to the audit template.
:param name: The name of the audit template. Default: My Audit Template
:param description: The description of the audit template.
:param strategy: Strategy UUID or name related to the audit template.
:param extra: Metadata associated to this audit template.
:param scope: Scope that will be applied on all derived audits.
:return: A tuple with The HTTP response and its body
"""
description = description or data_utils.rand_name(
'test-audit_template')
resp, body = cls.client.create_audit_template(
name=name, description=description, goal=goal,
strategy=strategy, extra=extra)
name=name, description=description,
goal=goal, strategy=strategy, scope=scope)
cls.created_audit_templates.add(body['uuid'])
@ -153,19 +153,18 @@ class BaseInfraOptimTest(test.BaseTestCase):
@classmethod
def create_audit(cls, audit_template_uuid, audit_type='ONESHOT',
state=None, deadline=None, interval=None):
state=None, interval=None):
"""Wrapper utility for creating a test audit
:param audit_template_uuid: Audit Template UUID this audit will use
:param type: Audit type (either ONESHOT or CONTINUOUS)
:param state: Audit state (str)
:param deadline: Audit deadline (datetime)
:param interval: Audit interval in seconds (int)
:return: A tuple with The HTTP response and its body
"""
resp, body = cls.client.create_audit(
audit_template_uuid=audit_template_uuid, audit_type=audit_type,
state=state, deadline=deadline, interval=interval)
state=state, interval=interval)
cls.created_audits.add(body['uuid'])
cls.created_action_plans_audit_uuids.add(body['uuid'])

View File

@ -35,18 +35,14 @@ class TestCreateDeleteAuditTemplate(base.BaseInfraOptimTest):
params = {
'name': 'my at name %s' % uuid.uuid4(),
'description': 'my at description',
'goal': goal['uuid'],
'extra': {'str': 'value', 'int': 123, 'float': 0.123,
'bool': True, 'list': [1, 2, 3],
'dict': {'foo': 'bar'}}}
'goal': goal['uuid']}
expected_data = {
'name': params['name'],
'description': params['description'],
'goal_uuid': params['goal'],
'goal_name': goal_name,
'strategy_uuid': None,
'strategy_name': None,
'extra': params['extra']}
'strategy_name': None}
_, body = self.create_audit_template(**params)
self.assert_expected(expected_data, body)
@ -62,8 +58,7 @@ class TestCreateDeleteAuditTemplate(base.BaseInfraOptimTest):
params = {
'name': 'my at name %s' % uuid.uuid4(),
'description': 'my àt déscrïptïôn',
'goal': goal['uuid'],
'extra': {'foo': 'bar'}}
'goal': goal['uuid']}
expected_data = {
'name': params['name'],
@ -71,8 +66,7 @@ class TestCreateDeleteAuditTemplate(base.BaseInfraOptimTest):
'goal_uuid': params['goal'],
'goal_name': goal_name,
'strategy_uuid': None,
'strategy_name': None,
'extra': params['extra']}
'strategy_name': None}
_, body = self.create_audit_template(**params)
self.assert_expected(expected_data, body)
@ -166,14 +160,12 @@ class TestAuditTemplate(base.BaseInfraOptimTest):
params = {'name': 'my at name %s' % uuid.uuid4(),
'description': 'my at description',
'goal': self.goal['uuid'],
'extra': {'key1': 'value1', 'key2': 'value2'}}
'goal': self.goal['uuid']}
_, body = self.create_audit_template(**params)
new_name = 'my at new name %s' % uuid.uuid4()
new_description = 'my new at description'
new_extra = {'key1': 'new-value1', 'key2': 'new-value2'}
patch = [{'path': '/name',
'op': 'replace',
@ -186,13 +178,7 @@ class TestAuditTemplate(base.BaseInfraOptimTest):
'value': new_goal['uuid']},
{'path': '/strategy',
'op': 'replace',
'value': new_strategy['uuid']},
{'path': '/extra/key1',
'op': 'replace',
'value': new_extra['key1']},
{'path': '/extra/key2',
'op': 'replace',
'value': new_extra['key2']}]
'value': new_strategy['uuid']}]
self.client.update_audit_template(body['uuid'], patch)
@ -201,59 +187,40 @@ class TestAuditTemplate(base.BaseInfraOptimTest):
self.assertEqual(new_description, body['description'])
self.assertEqual(new_goal['uuid'], body['goal_uuid'])
self.assertEqual(new_strategy['uuid'], body['strategy_uuid'])
self.assertEqual(new_extra, body['extra'])
@test.attr(type='smoke')
def test_update_audit_template_remove(self):
extra = {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}
description = 'my at description'
name = 'my at name %s' % uuid.uuid4()
params = {'name': name,
'description': description,
'goal': self.goal['uuid'],
'extra': extra}
'goal': self.goal['uuid']}
_, audit_template = self.create_audit_template(**params)
# Removing one item from the collection
# Removing the description
self.client.update_audit_template(
audit_template['uuid'],
[{'path': '/extra/key2', 'op': 'remove'}])
[{'path': '/description', 'op': 'remove'}])
extra.pop('key2')
_, body = self.client.show_audit_template(audit_template['uuid'])
self.assertEqual(extra, body['extra'])
# Removing the collection
self.client.update_audit_template(
audit_template['uuid'],
[{'path': '/extra', 'op': 'remove'}])
_, body = self.client.show_audit_template(audit_template['uuid'])
self.assertEqual({}, body['extra'])
self.assertIsNone(body.get('description'))
# Assert nothing else was changed
self.assertEqual(name, body['name'])
self.assertEqual(description, body['description'])
self.assertIsNone(body['description'])
self.assertEqual(self.goal['uuid'], body['goal_uuid'])
@test.attr(type='smoke')
def test_update_audit_template_add(self):
params = {'name': 'my at name %s' % uuid.uuid4(),
'description': 'my at description',
'goal': self.goal['uuid']}
_, body = self.create_audit_template(**params)
extra = {'key1': 'value1', 'key2': 'value2'}
patch = [{'path': '/extra/key1',
'op': 'add',
'value': extra['key1']},
{'path': '/extra/key2',
'op': 'add',
'value': extra['key2']}]
patch = [{'path': '/description', 'op': 'add', 'value': 'description'}]
self.client.update_audit_template(body['uuid'], patch)
_, body = self.client.show_audit_template(body['uuid'])
self.assertEqual(extra, body['extra'])
self.assertEqual('description', body['description'])

View File

@ -74,21 +74,19 @@ class BaseInfraOptimScenarioTest(manager.ScenarioTest):
# ### AUDIT TEMPLATES ### #
def create_audit_template(self, goal, name=None, description=None,
strategy=None, extra=None):
strategy=None):
"""Wrapper utility for creating a test audit template
:param goal: Goal UUID or name related to the audit template.
:param name: The name of the audit template. Default: My Audit Template
:param description: The description of the audit template.
:param strategy: Strategy UUID or name related to the audit template.
:param extra: Metadata associated to this audit template.
:return: A tuple with The HTTP response and its body
"""
description = description or data_utils.rand_name(
'test-audit_template')
resp, body = self.client.create_audit_template(
name=name, description=description, goal=goal,
strategy=strategy, extra=extra)
name=name, description=description, goal=goal, strategy=strategy)
self.addCleanup(
self.delete_audit_template,
@ -109,7 +107,7 @@ class BaseInfraOptimScenarioTest(manager.ScenarioTest):
# ### AUDITS ### #
def create_audit(self, audit_template_uuid, audit_type='ONESHOT',
state=None, deadline=None):
state=None):
"""Wrapper utility for creating a test audit
:param audit_template_uuid: Audit Template UUID this audit will use
@ -118,7 +116,7 @@ class BaseInfraOptimScenarioTest(manager.ScenarioTest):
"""
resp, body = self.client.create_audit(
audit_template_uuid=audit_template_uuid, audit_type=audit_type,
state=state, deadline=deadline)
state=state)
self.addCleanup(self.delete_audit, audit_uuid=body["uuid"])
return resp, body