convergence scenario tests

Adding convergence prototype scenarios tests to run against actual
heat code base.
These tests are not modified and should test the sanity of covergence
implementation.

Change-Id: I69373a423da85f23597623457a7a7f1d5c2b0d2a
Implements: blueprint convergence-simulator-tests
Co-Authored-By: Anant Patil <anant.patil@hp.com>
This commit is contained in:
tyagi 2015-07-20 07:05:31 -07:00 committed by Anant Patil
parent 01c334f017
commit f38dc98ffd
34 changed files with 1494 additions and 1 deletions

View File

View File

@ -0,0 +1,103 @@
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from heat.db import api as db_api
from heat.engine import service
from heat.engine import stack
from heat.tests.convergence.framework import message_processor
from heat.tests.convergence.framework import message_queue
from heat.tests.convergence.framework import scenario_template
from heat.tests import utils
class Engine(message_processor.MessageProcessor):
'''
Wrapper to the engine service. Methods of this
class will be called from the scenario tests.
'''
queue = message_queue.MessageQueue('engine')
def __init__(self):
super(Engine, self).__init__('engine')
def scenario_template_to_hot(self, scenario_tmpl):
'''
Converts the scenario template into hot template.
'''
hot_tmpl = {"heat_template_version": "2013-05-23"}
resources = {}
for res_name, res_def in scenario_tmpl.resources.iteritems():
props = getattr(res_def, 'properties')
depends = getattr(res_def, 'depends_on')
res_defn = {"type": "OS::Heat::TestResource"}
if props:
props_def = {}
for prop_name, prop_value in props.items():
if type(prop_value) == scenario_template.GetRes:
prop_res = getattr(prop_value, "target_name")
prop_value = {'get_resource': prop_res}
elif type(prop_value) == scenario_template.GetAtt:
prop_res = getattr(prop_value, "target_name")
prop_attr = getattr(prop_value, "attr")
prop_value = {'get_attr': [prop_res, prop_attr]}
props_def[prop_name] = prop_value
res_defn["properties"] = props_def
if depends:
res_defn["depends_on"] = depends
resources[res_name] = res_defn
hot_tmpl['resources'] = resources
return hot_tmpl
@message_processor.asynchronous
def create_stack(self, stack_name, scenario_tmpl):
cnxt = utils.dummy_context()
srv = service.EngineService("host", "engine")
thread_group_mgr = service.ThreadGroupManager()
srv.thread_group_mgr = thread_group_mgr
hot_tmpl = self.scenario_template_to_hot(scenario_tmpl)
srv.create_stack(cnxt, stack_name, hot_tmpl,
params={}, files={}, args={})
@message_processor.asynchronous
def update_stack(self, stack_name, scenario_tmpl):
cnxt = utils.dummy_context()
db_stack = db_api.stack_get_by_name(cnxt, stack_name)
srv = service.EngineService("host", "engine")
thread_group_mgr = service.ThreadGroupManager()
srv.thread_group_mgr = thread_group_mgr
hot_tmpl = self.scenario_template_to_hot(scenario_tmpl)
stack_identity = {'stack_name': stack_name,
'stack_id': db_stack.id,
'tenant': db_stack.tenant,
'path': ''}
srv.update_stack(cnxt, stack_identity, hot_tmpl,
params={}, files={}, args={})
@message_processor.asynchronous
def delete_stack(self, stack_name):
cnxt = utils.dummy_context()
db_stack = db_api.stack_get_by_name(cnxt, stack_name)
stack_identity = {'stack_name': stack_name,
'stack_id': db_stack.id,
'tenant': db_stack.tenant,
'path': ''}
srv = service.EngineService("host", "engine")
srv.delete_stack(cnxt, stack_identity)
@message_processor.asynchronous
def rollback_stack(self, stack_name):
cntxt = utils.dummy_context()
db_stack = db_api.stack_get_by_name(cntxt, stack_name)
stk = stack.Stack.load(cntxt, stack=db_stack)
stk.rollback()

View File

@ -0,0 +1,22 @@
#
# 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.
class EventLoop(object):
def __init__(self, *processors):
self.processors = processors
def __call__(self):
while any([processor() for processor in self.processors]):
continue

View File

@ -0,0 +1,96 @@
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from heat.common import exception
from heat.common.i18n import _
from heat.engine import attributes
from heat.engine import properties
from heat.engine import resource
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
class TestResource(resource.Resource):
PROPERTIES = (
A, C, CA, rA, rB
) = (
'a', 'c', 'ca', '!a', '!b'
)
ATTRIBUTES = (
A, rA
) = (
'a', '!a'
)
properties_schema = {
A: properties.Schema(
properties.Schema.STRING,
_('Fake property a.'),
default='a',
update_allowed=True
),
C: properties.Schema(
properties.Schema.STRING,
_('Fake property c.'),
update_allowed=True,
default='c'
),
CA: properties.Schema(
properties.Schema.STRING,
_('Fake property ca.'),
update_allowed=True,
default='ca'
),
rA: properties.Schema(
properties.Schema.STRING,
_('Fake property !a.'),
update_allowed=True,
default='!a'
),
rB: properties.Schema(
properties.Schema.STRING,
_('Fake property !c.'),
update_allowed=True,
default='!b'
),
}
attributes_schema = {
A: attributes.Schema(
_('Fake attribute a.'),
cache_mode=attributes.Schema.CACHE_NONE
),
rA: attributes.Schema(
_('Fake attribute !a.'),
cache_mode=attributes.Schema.CACHE_NONE
),
}
def handle_create(self):
for prop in self.properties.props.keys():
self.data_set(prop, self.properties.get(prop), redact=False)
self.resource_id_set(self.physical_resource_name())
def handle_update(self, json_snippet=None, tmpl_diff=None, prop_diff=None):
for prop in prop_diff:
if '!' in prop:
raise exception.UpdateReplace(self.name)
self.data_set(prop, prop_diff.get(prop), redact=False)
def _resolve_attribute(self, name):
if name in self.attributes:
return self.data().get(name)

View File

@ -0,0 +1,109 @@
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import collections
import functools
import inspect
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
def asynchronous(function):
'''Decorator for MessageProcessor methods to make them asynchronous.
To use, simply call the method as usual. Instead of being executed
immediately, it will be placed on the queue for the MessageProcessor and
run on a future iteration of the event loop.
'''
arg_names = inspect.getargspec(function).args
MessageData = collections.namedtuple(function.__name__, arg_names[1:])
@functools.wraps(function)
def call_or_send(processor, *args, **kwargs):
if len(args) == 1 and not kwargs and isinstance(args[0], MessageData):
try:
return function(processor, **args[0]._asdict())
except Exception as exc:
LOG.exception('[%s] Exception in "%s": %s',
processor.name, function.__name__, exc)
raise
else:
data = inspect.getcallargs(function, processor, *args, **kwargs)
data.pop(arg_names[0]) # lose self
return processor.queue.send(function.__name__,
MessageData(**data))
call_or_send.MessageData = MessageData
return call_or_send
class MessageProcessor(object):
queue = None
def __init__(self, name):
self.name = name
def __call__(self):
message = self.queue.get()
if message is None:
LOG.debug('[%s] No messages' % self.name)
return False
try:
method = getattr(self, message.name)
except AttributeError:
LOG.error('[%s] Bad message name "%s"' % (self.name,
message.name))
raise
else:
LOG.info('[%s] %r' % (self.name, message.data))
method(message.data)
return True
@asynchronous
def noop(self, count=1):
'''
Insert <count> No-op operations in the message queue.
'''
assert isinstance(count, int)
if count > 1:
self.queue.send_priority('noop',
self.noop.MessageData(count - 1))
@asynchronous
def _execute(self, func):
'''
Insert a function call in the message queue.
The function takes no arguments, so use functools.partial to curry the
arguments before passing it here.
'''
func()
def call(self, func, *args, **kwargs):
'''
Insert a function call in the message queue.
'''
self._execute(functools.partial(func, *args, **kwargs))
def clear(self):
'''
Delete all the messages from the queue.
'''
self.queue.clear()
__all__ = ['MessageProcessor', 'asynchronous']

View File

@ -0,0 +1,39 @@
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import collections
Message = collections.namedtuple('Message', ['name', 'data'])
class MessageQueue(object):
def __init__(self, name):
self.name = name
self._queue = collections.deque()
def send(self, name, data=None):
self._queue.append(Message(name, data))
def send_priority(self, name, data=None):
self._queue.appendleft(Message(name, data))
def get(self):
try:
return self._queue.popleft()
except IndexError:
return None
def clear(self):
self._queue.clear()

View File

@ -0,0 +1,44 @@
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from heat.tests.convergence.framework import engine_wrapper
from heat.tests.convergence.framework import event_loop as event_loop_module
from heat.tests.convergence.framework import worker_wrapper
engine = None
worker = None
event_loop = None
class Processes(object):
def __init__(self):
global engine
global worker
global event_loop
engine = engine_wrapper.Engine()
worker = worker_wrapper.Worker()
event_loop = event_loop_module.EventLoop(engine, worker)
self.engine = engine
self.worker = worker
self.event_loop = event_loop
def clear(self):
self.engine.clear()
self.worker.clear()
Processes()

View File

@ -0,0 +1,51 @@
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from heat.common import exception
from heat.db import api as db_api
from heat.tests import utils
class RealityStore(object):
def __init__(self):
self.cntxt = utils.dummy_context()
def resources_by_logical_name(self, logical_name):
ret = []
resources = db_api.resource_get_all(self.cntxt)
for res in resources:
if (res.name == logical_name and res.action in ("CREATE", "UPDATE")
and res.status == "COMPLETE"):
ret.append(res)
return ret
def all_resources(self):
try:
resources = db_api.resource_get_all(self.cntxt)
except exception.NotFound:
return []
ret = []
for res in resources:
if res.action in ("CREATE", "UPDATE") and res.status == "COMPLETE":
ret.append(res)
return ret
def resource_properties(self, res, prop_name):
res_data = db_api.resource_data_get_by_key(self.cntxt,
res.id,
prop_name)
return res_data.value
reality = RealityStore()

View File

@ -0,0 +1,49 @@
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
def list_all():
scenario_dir = os.path.join(os.path.dirname(__file__), '../scenarios')
if not os.path.isdir(scenario_dir):
LOG.error('Scenario directory "%s" not found', scenario_dir)
return
for root, dirs, files in os.walk(scenario_dir):
for filename in files:
name, ext = os.path.splitext(filename)
if ext == '.py':
LOG.debug('Found scenario "%s"', name)
yield name, os.path.join(root, filename)
class Scenario(object):
def __init__(self, name, path):
self.name = name
with open(path) as f:
source = f.read()
self.code = compile(source, path, 'exec')
LOG.debug('Loaded scenario %s', self.name)
def __call__(self, _event_loop, **global_env):
LOG.info('*** Beginning scenario "%s"', self.name)
exec(self.code, global_env, {})
_event_loop()

View File

@ -0,0 +1,39 @@
#
# 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.
class GetRes(object):
def __init__(self, target_name):
self.target_name = target_name
class GetAtt(GetRes):
def __init__(self, target_name, attr):
super(GetAtt, self).__init__(target_name)
self.attr = attr
class RsrcDef(object):
def __init__(self, properties, depends_on):
self.properties = properties
self.depends_on = depends_on
class Template(object):
def __init__(self, resources={}, key=None):
self.key = key
self.resources = resources
def __repr__(self):
return 'Template(%r)' % self.resources

View File

@ -0,0 +1,70 @@
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import functools
from oslo_log import log as logging
from heat.tests.convergence.framework import reality
from heat.tests.convergence.framework import scenario_template
LOG = logging.getLogger(__name__)
def verify(test, reality, tmpl):
for name in tmpl.resources:
rsrc_count = len(reality.resources_by_logical_name(name))
test.assertEqual(1, rsrc_count,
'Found %d copies of resource "%s"' % (rsrc_count,
name))
all_rsrcs = reality.all_resources()
for name, defn in tmpl.resources.items():
phys_rsrc = reality.resources_by_logical_name(name)[0]
for prop_name, prop_def in defn.properties.items():
real_value = reality.resource_properties(phys_rsrc, prop_name)
if isinstance(prop_def, scenario_template.GetAtt):
targs = reality.resources_by_logical_name(prop_def.target_name)
att_value = targs[0].properties_data[prop_def.attr]
test.assertEqual(att_value, real_value)
elif isinstance(prop_def, scenario_template.GetRes):
targs = reality.resources_by_logical_name(prop_def.target_name)
test.assertEqual(targs[0].nova_instance, real_value)
else:
test.assertEqual(prop_def, real_value)
test.assertEqual(len(defn.properties), len(phys_rsrc.properties_data))
test.assertEqual(len(tmpl.resources), len(all_rsrcs))
def scenario_globals(procs, testcase):
return {
'test': testcase,
'reality': reality.reality,
'verify': functools.partial(verify,
testcase,
reality.reality),
'Template': scenario_template.Template,
'RsrcDef': scenario_template.RsrcDef,
'GetRes': scenario_template.GetRes,
'GetAtt': scenario_template.GetAtt,
'engine': procs.engine,
'worker': procs.worker,
}

View File

@ -0,0 +1,35 @@
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from heat.engine import worker
from heat.tests.convergence.framework import message_processor
from heat.tests.convergence.framework import message_queue
class Worker(message_processor.MessageProcessor):
queue = message_queue.MessageQueue('worker')
def __init__(self):
super(Worker, self).__init__('worker')
@message_processor.asynchronous
def check_resource(self, ctxt, resource_id,
current_traversal, data,
is_update, adopt_stack_data):
worker.WorkerService("fake_host", "fake_topic",
"fake_engine", "tgm").check_resource(
ctxt, resource_id,
current_traversal,
data, is_update,
adopt_stack_data)

View File

@ -0,0 +1,24 @@
#
# 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.
example_template = Template({
'A': RsrcDef({}, []),
'B': RsrcDef({}, []),
'C': RsrcDef({'a': '4alpha'}, ['A', 'B']),
'D': RsrcDef({'c': GetRes('C')}, []),
'E': RsrcDef({'ca': GetAtt('C', 'a')}, []),
})
engine.create_stack('foo', example_template)
engine.noop(5)
engine.call(verify, example_template)

View File

@ -0,0 +1,26 @@
#
# 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.
example_template = Template({
'A': RsrcDef({}, []),
'B': RsrcDef({}, []),
'C': RsrcDef({'a': '4alpha'}, ['A', 'B']),
'D': RsrcDef({'c': GetRes('C')}, []),
'E': RsrcDef({'ca': GetAtt('C', 'a')}, []),
})
engine.create_stack('foo', example_template)
engine.noop(3)
engine.rollback_stack('foo')
engine.noop(6)
engine.call(verify, Template())

View File

@ -0,0 +1,43 @@
#
# 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.
def check_resource_count(expected_count):
test.assertEqual(expected_count, len(reality.all_resources()))
example_template = Template({
'A': RsrcDef({}, []),
'B': RsrcDef({}, []),
'C': RsrcDef({'a': '4alpha'}, ['A', 'B']),
'D': RsrcDef({'c': GetRes('C')}, []),
'E': RsrcDef({'ca': GetAtt('C', 'a')}, []),
})
engine.create_stack('foo', example_template)
engine.noop(2)
example_template2 = Template({
'A': RsrcDef({}, []),
'B': RsrcDef({}, []),
'C': RsrcDef({'a': '4alpha'}, ['A', 'B']),
'D': RsrcDef({'c': GetRes('C')}, []),
'E': RsrcDef({'ca': GetAtt('C', 'a')}, []),
'F': RsrcDef({}, ['D', 'E']),
})
engine.update_stack('foo', example_template2)
engine.call(check_resource_count, 3)
engine.noop(11)
engine.call(verify, example_template2)
engine.delete_stack('foo')
engine.noop(6)
engine.call(verify, Template({}))

View File

@ -0,0 +1,27 @@
#
# 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.
example_template = Template({
'A': RsrcDef({}, []),
'B': RsrcDef({}, []),
'C': RsrcDef({'a': '4alpha'}, ['A', 'B']),
'D': RsrcDef({'c': GetRes('C')}, []),
'E': RsrcDef({'ca': GetAtt('C', 'a')}, []),
})
engine.create_stack('foo', example_template)
engine.noop(2)
engine.delete_stack('foo')
engine.noop(6)
engine.call(verify, Template({}))

View File

@ -0,0 +1,24 @@
#
# 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.
example_template = Template({
'A': RsrcDef({}, []),
'B': RsrcDef({}, []),
'C': RsrcDef({'a': '4alpha'}, ['A']),
'D': RsrcDef({'c': GetRes('C')}, []),
'E': RsrcDef({}, []),
})
engine.create_stack('foo', example_template)
engine.noop(5)
engine.call(verify, example_template)

View File

@ -0,0 +1,50 @@
#
# 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.
example_template = Template({
'A': RsrcDef({}, []),
'B': RsrcDef({}, []),
'C': RsrcDef({'a': '4alpha'}, ['A', 'B']),
'D': RsrcDef({'c': GetRes('C')}, []),
'E': RsrcDef({'ca': GetAtt('C', 'a')}, []),
})
engine.create_stack('foo', example_template)
engine.noop(5)
engine.call(verify, example_template)
example_template_shrunk = Template({
'A': RsrcDef({}, []),
'B': RsrcDef({}, []),
'C': RsrcDef({'a': '4alpha'}, ['A', 'B']),
'D': RsrcDef({'c': GetRes('C')}, []),
})
engine.update_stack('foo', example_template_shrunk)
engine.noop(10)
engine.call(verify, example_template_shrunk)
example_template_long = Template({
'A': RsrcDef({}, []),
'B': RsrcDef({}, []),
'C': RsrcDef({'a': '4alpha'}, ['A', 'B']),
'D': RsrcDef({'c': GetRes('C')}, []),
'E': RsrcDef({'ca': GetAtt('C', 'a')}, []),
'F': RsrcDef({}, ['D', 'E']),
})
engine.update_stack('foo', example_template_long)
engine.noop(12)
engine.call(verify, example_template_long)
engine.delete_stack('foo')
engine.noop(6)
engine.call(verify, Template({}))

View File

@ -0,0 +1,36 @@
#
# 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.
example_template = Template({
'A': RsrcDef({}, []),
'B': RsrcDef({}, []),
'C': RsrcDef({'a': '4alpha'}, ['A', 'B']),
'D': RsrcDef({'c': GetRes('C')}, []),
'E': RsrcDef({'ca': GetAtt('C', 'a')}, []),
})
engine.create_stack('foo', example_template)
engine.noop(5)
engine.call(verify, example_template)
example_template2 = Template({
'A': RsrcDef({}, []),
'B': RsrcDef({}, []),
'C': RsrcDef({'a': '4alpha'}, ['A', 'B']),
'D': RsrcDef({'c': GetRes('C')}, []),
'E': RsrcDef({'ca': GetAtt('C', 'a')}, []),
'F': RsrcDef({}, ['D', 'E']),
})
engine.update_stack('foo', example_template2)
engine.noop(11)
engine.call(verify, example_template2)

View File

@ -0,0 +1,39 @@
#
# 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.
def check_resource_count(expected_count):
test.assertEqual(expected_count, len(reality.all_resources()))
example_template = Template({
'A': RsrcDef({}, []),
'B': RsrcDef({}, []),
'C': RsrcDef({'a': '4alpha'}, ['A', 'B']),
'D': RsrcDef({'c': GetRes('C')}, []),
'E': RsrcDef({'ca': GetAtt('C', 'a')}, []),
})
engine.create_stack('foo', example_template)
engine.noop(2)
example_template2 = Template({
'A': RsrcDef({}, []),
'B': RsrcDef({}, []),
'C': RsrcDef({'a': '4alpha'}, ['A', 'B']),
'D': RsrcDef({'c': GetRes('C')}, []),
'E': RsrcDef({'ca': GetAtt('C', 'a')}, []),
'F': RsrcDef({}, ['D', 'E']),
})
engine.update_stack('foo', example_template2)
engine.call(check_resource_count, 3)
engine.noop(11)
engine.call(verify, example_template2)

View File

@ -0,0 +1,39 @@
#
# 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.
example_template = Template({
'A': RsrcDef({}, []),
'B': RsrcDef({}, []),
'C': RsrcDef({'a': '4alpha'}, ['A', 'B']),
'D': RsrcDef({'c': GetRes('C')}, []),
'E': RsrcDef({'ca': GetAtt('C', 'a')}, []),
})
engine.create_stack('foo', example_template)
engine.noop(5)
engine.call(verify, example_template)
example_template2 = Template({
'A': RsrcDef({}, []),
'B': RsrcDef({}, []),
'C': RsrcDef({'a': '4alpha'}, ['A', 'B']),
'D': RsrcDef({'c': GetRes('C')}, []),
'E': RsrcDef({'ca': GetAtt('C', 'a')}, []),
'F': RsrcDef({}, ['A']),
})
engine.update_stack('foo', example_template2)
engine.noop(4)
engine.rollback_stack('foo')
engine.noop(8)
engine.call(verify, example_template)

View File

@ -0,0 +1,39 @@
#
# 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.
example_template = Template({
'A': RsrcDef({}, []),
'B': RsrcDef({}, []),
'C': RsrcDef({'a': '4alpha'}, ['A', 'B']),
'D': RsrcDef({'c': GetRes('C')}, []),
'E': RsrcDef({'ca': GetAtt('C', 'a')}, []),
})
engine.create_stack('foo', example_template)
engine.noop(5)
engine.call(verify, example_template)
example_template2 = Template({
'A': RsrcDef({}, []),
'B': RsrcDef({}, []),
'C': RsrcDef({'a': '4alpha'}, ['A', 'B']),
'D': RsrcDef({'c': GetRes('C')}, []),
'E': RsrcDef({'ca': GetAtt('C', 'a')}, []),
'F': RsrcDef({}, ['D']),
})
engine.update_stack('foo', example_template2)
engine.noop(4)
engine.rollback_stack('foo')
engine.noop(8)
engine.call(verify, example_template)

View File

@ -0,0 +1,34 @@
#
# 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.
example_template = Template({
'A': RsrcDef({}, []),
'B': RsrcDef({}, []),
'C': RsrcDef({'a': '4alpha'}, ['A', 'B']),
'D': RsrcDef({'c': GetRes('C')}, []),
'E': RsrcDef({'ca': GetAtt('C', 'a')}, []),
})
engine.create_stack('foo', example_template)
engine.noop(5)
engine.call(verify, example_template)
example_template2 = Template({
'A': RsrcDef({}, []),
'B': RsrcDef({}, []),
'C': RsrcDef({'a': '4alpha'}, ['A', 'B']),
'D': RsrcDef({'c': GetRes('C')}, []),
})
engine.update_stack('foo', example_template2)
engine.noop(9)
engine.call(verify, example_template2)

View File

@ -0,0 +1,51 @@
#
# 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.
b_uuid = None
def store_b_uuid():
global b_uuid
b_uuid = next(iter(reality.resources_by_logical_name('B'))).uuid
def check_b_not_replaced():
test.assertEqual(b_uuid,
next(iter(reality.resources_by_logical_name('B'))).uuid)
test.assertIsNot(b_uuid, None)
example_template = Template({
'A': RsrcDef({}, []),
'B': RsrcDef({}, []),
'C': RsrcDef({'a': '4alpha'}, ['A', 'B']),
'D': RsrcDef({'c': GetRes('C')}, []),
'E': RsrcDef({'ca': GetAtt('C', 'a')}, []),
})
engine.create_stack('foo', example_template)
engine.noop(5)
engine.call(verify, example_template)
engine.call(store_b_uuid)
example_template2 = Template({
'A': RsrcDef({}, []),
'C': RsrcDef({'a': '4alpha'}, ['A']),
'D': RsrcDef({'c': GetRes('C')}, []),
'E': RsrcDef({'ca': GetAtt('C', 'a')}, []),
})
engine.update_stack('foo', example_template2)
engine.noop(2)
engine.rollback_stack('foo')
engine.noop(10)
engine.call(verify, example_template)
engine.call(check_b_not_replaced)

View File

@ -0,0 +1,65 @@
#
# 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.
c_uuid = None
def store_c_uuid():
global c_uuid
c_uuid = next(iter(reality.resources_by_logical_name('C'))).uuid
def check_c_replaced():
test.assertNotEqual(c_uuid,
next(iter(reality.resources_by_logical_name('C'))).uuid)
test.assertIsNot(c_uuid, None)
example_template = Template({
'A': RsrcDef({'a': 'initial'}, []),
'B': RsrcDef({}, []),
'C': RsrcDef({'!a': GetAtt('A', 'a')}, ['B']),
'D': RsrcDef({'c': GetRes('C')}, []),
'E': RsrcDef({'ca': GetAtt('C', '!a')}, []),
})
engine.create_stack('foo', example_template)
engine.noop(5)
engine.call(verify, example_template)
engine.call(store_c_uuid)
example_template_updated = Template({
'A': RsrcDef({'a': 'updated'}, []),
'B': RsrcDef({}, []),
'C': RsrcDef({'!a': GetAtt('A', 'a')}, ['B']),
'D': RsrcDef({'c': GetRes('C')}, []),
'E': RsrcDef({'ca': GetAtt('C', '!a')}, []),
})
engine.update_stack('foo', example_template_updated)
engine.noop(11)
engine.call(verify, example_template_updated)
example_template_long = Template({
'A': RsrcDef({'a': 'updated'}, []),
'B': RsrcDef({}, []),
'C': RsrcDef({'!a': GetAtt('A', 'a')}, ['B']),
'D': RsrcDef({'c': GetRes('C')}, []),
'E': RsrcDef({'ca': GetAtt('C', '!a')}, []),
'F': RsrcDef({}, ['D', 'E']),
})
engine.update_stack('foo', example_template_long)
engine.noop(12)
engine.call(verify, example_template_long)
engine.call(check_c_replaced)
engine.delete_stack('foo')
engine.noop(6)
engine.call(verify, Template({}))

View File

@ -0,0 +1,42 @@
#
# 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.
def check_resource_counts(count_map):
for name, count in count_map.items():
test.assertEqual(count,
len(list(reality.resources_by_logical_name(name))))
example_template = Template({
'A': RsrcDef({'!a': 'initial'}, []),
'B': RsrcDef({'!b': 'first'}, ['A']),
})
engine.create_stack('foo', example_template)
engine.noop(4)
engine.call(verify, example_template)
example_template_inverted = Template({
'A': RsrcDef({'!a': 'updated'}, ['B']),
'B': RsrcDef({'!b': 'second'}, []),
})
engine.update_stack('foo', example_template_inverted)
engine.noop(4)
engine.call(check_resource_counts, {'A': 2, 'B': 1})
engine.noop(2)
engine.call(verify, example_template_inverted)
engine.call(check_resource_counts, {'A': 1, 'B': 1})
engine.delete_stack('foo')
engine.noop(3)
engine.call(verify, Template({}))

View File

@ -0,0 +1,55 @@
#
# 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.
def check_c_count(expected_count):
test.assertEqual(expected_count,
len(reality.resources_by_logical_name('C')))
example_template = Template({
'A': RsrcDef({'a': 'initial'}, []),
'B': RsrcDef({}, []),
'C': RsrcDef({'!a': GetAtt('A', 'a')}, ['B']),
'D': RsrcDef({'c': GetRes('C')}, []),
'E': RsrcDef({'ca': GetAtt('C', '!a')}, []),
})
engine.create_stack('foo', example_template)
engine.noop(5)
engine.call(verify, example_template)
example_template_shrunk = Template({
'A': RsrcDef({'a': 'updated'}, []),
'B': RsrcDef({}, []),
'C': RsrcDef({'!a': GetAtt('A', 'a')}, ['B']),
'D': RsrcDef({'c': GetRes('C')}, []),
'E': RsrcDef({'ca': GetAtt('C', '!a')}, []),
})
engine.update_stack('foo', example_template_shrunk)
engine.noop(7)
example_template_long = Template({
'A': RsrcDef({'a': 'updated'}, []),
'B': RsrcDef({}, []),
'C': RsrcDef({'!a': GetAtt('A', 'a')}, ['B']),
'D': RsrcDef({'c': GetRes('C')}, []),
'E': RsrcDef({'ca': GetAtt('C', '!a')}, []),
'F': RsrcDef({}, ['D', 'E']),
})
engine.update_stack('foo', example_template_long)
engine.call(check_c_count, 2)
engine.noop(11)
engine.call(verify, example_template_long)
engine.delete_stack('foo')
engine.noop(12)
engine.call(verify, Template({}))

View File

@ -0,0 +1,43 @@
#
# 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.
def check_c_count(expected_count):
test.assertEqual(expected_count,
len(reality.resources_by_logical_name('C')))
example_template = Template({
'A': RsrcDef({'a': 'initial'}, []),
'B': RsrcDef({}, []),
'C': RsrcDef({'!a': GetAtt('A', 'a')}, ['B']),
'D': RsrcDef({'c': GetRes('C')}, []),
'E': RsrcDef({'ca': GetAtt('C', '!a')}, []),
})
engine.create_stack('foo', example_template)
engine.noop(5)
engine.call(verify, example_template)
example_template_shrunk = Template({
'A': RsrcDef({'a': 'updated'}, []),
'B': RsrcDef({}, []),
'C': RsrcDef({'!a': GetAtt('A', 'a')}, ['B']),
'D': RsrcDef({'c': GetRes('C')}, []),
'E': RsrcDef({'ca': GetAtt('C', '!a')}, []),
})
engine.update_stack('foo', example_template_shrunk)
engine.noop(7)
engine.delete_stack('foo')
engine.call(check_c_count, 2)
engine.noop(11)
engine.call(verify, Template({}))

View File

@ -0,0 +1,47 @@
#
# 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.
def check_c_count(expected_count):
test.assertEqual(expected_count,
len(reality.resources_by_logical_name('C')))
example_template = Template({
'A': RsrcDef({'a': 'initial'}, []),
'B': RsrcDef({}, []),
'C': RsrcDef({'!a': GetAtt('A', 'a')}, ['B']),
'D': RsrcDef({'c': GetRes('C')}, []),
'E': RsrcDef({'ca': GetAtt('C', '!a')}, []),
})
engine.create_stack('foo', example_template)
engine.noop(5)
engine.call(verify, example_template)
example_template2 = Template({
'A': RsrcDef({'a': 'updated'}, []),
'B': RsrcDef({}, []),
'C': RsrcDef({'!a': GetAtt('A', 'a')}, ['B']),
'D': RsrcDef({'c': GetRes('C')}, []),
'E': RsrcDef({'ca': GetAtt('C', '!a')}, []),
})
engine.update_stack('foo', example_template2)
engine.noop(4)
engine.rollback_stack('foo')
engine.call(check_c_count, 2)
engine.noop(11)
engine.call(verify, example_template)
engine.delete_stack('foo')
engine.noop(12)
engine.call(verify, Template({}))

View File

@ -0,0 +1,65 @@
#
# 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.
c_uuid = None
def store_c_uuid():
global c_uuid
c_uuid = next(iter(reality.resources_by_logical_name('C'))).uuid
def check_c_replaced():
test.assertNotEqual(c_uuid,
next(iter(reality.resources_by_logical_name('newC'))).uuid)
test.assertIsNot(c_uuid, None)
example_template = Template({
'A': RsrcDef({'a': 'initial'}, []),
'B': RsrcDef({}, []),
'C': RsrcDef({'!a': GetAtt('A', 'a')}, ['B']),
'D': RsrcDef({'c': GetRes('C')}, []),
'E': RsrcDef({'ca': GetAtt('C', '!a')}, []),
})
engine.create_stack('foo', example_template)
engine.noop(5)
engine.call(verify, example_template)
engine.call(store_c_uuid)
example_template_updated = Template({
'A': RsrcDef({'a': 'updated'}, []),
'B': RsrcDef({}, []),
'newC': RsrcDef({'!a': GetAtt('A', 'a')}, ['B']),
'D': RsrcDef({'c': GetRes('newC')}, []),
'E': RsrcDef({'ca': GetAtt('newC', '!a')}, []),
})
engine.update_stack('foo', example_template_updated)
engine.noop(11)
engine.call(verify, example_template_updated)
example_template_long = Template({
'A': RsrcDef({'a': 'updated'}, []),
'B': RsrcDef({}, []),
'newC': RsrcDef({'!a': GetAtt('A', 'a')}, ['B']),
'D': RsrcDef({'c': GetRes('newC')}, []),
'E': RsrcDef({'ca': GetAtt('newC', '!a')}, []),
'F': RsrcDef({}, ['D', 'E']),
})
engine.update_stack('foo', example_template_long)
engine.noop(12)
engine.call(verify, example_template_long)
engine.call(check_c_replaced)
engine.delete_stack('foo')
engine.noop(6)
engine.call(verify, Template({}))

View File

@ -0,0 +1,42 @@
#
# 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.
example_template = Template({
'A': RsrcDef({'a': 'initial'}, []),
'B': RsrcDef({}, []),
'C': RsrcDef({'!a': GetAtt('A', 'a')}, ['B']),
'D': RsrcDef({'c': GetRes('C')}, []),
'E': RsrcDef({'ca': GetAtt('C', '!a')}, []),
})
engine.create_stack('foo', example_template)
engine.noop(5)
engine.call(verify, example_template)
example_template_updated = Template({
'A': RsrcDef({'a': 'updated'}, []),
'B': RsrcDef({}, []),
'newC': RsrcDef({'!a': GetAtt('A', 'a')}, ['B']),
'D': RsrcDef({'c': GetRes('newC')}, []),
'E': RsrcDef({'ca': GetAtt('newC', '!a')}, []),
})
engine.update_stack('foo', example_template_updated)
engine.noop(3)
engine.rollback_stack('foo')
engine.noop(12)
engine.call(verify, example_template)
engine.delete_stack('foo')
engine.noop(6)
engine.call(verify, Template({}))

View File

@ -0,0 +1,45 @@
#!/usr/bin/env python
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from heat.engine import resource
from heat.tests import common
from heat.tests.convergence.framework import fake_resource
from heat.tests.convergence.framework import processes
from heat.tests.convergence.framework import scenario
from heat.tests.convergence.framework import testutils
from oslo_config import cfg
class ScenarioTest(common.HeatTestCase):
scenarios = [(name, {'name': name, 'path': path})
for name, path in scenario.list_all()]
def setUp(self):
super(ScenarioTest, self).setUp()
resource._register_class('OS::Heat::TestResource',
fake_resource.TestResource)
self.procs = processes.Processes()
po = self.patch("heat.rpc.worker_client.WorkerClient.check_resource")
po.side_effect = self.procs.worker.check_resource
cfg.CONF.set_default('convergence_engine', True)
def tearDown(self):
super(ScenarioTest, self).tearDown()
def test_scenario(self):
self.procs.clear()
runner = scenario.Scenario(self.name, self.path)
runner(self.procs.event_loop,
**testutils.scenario_globals(self.procs, self))

View File

@ -73,7 +73,7 @@ commands = bandit -c bandit.yaml -r heat -n5 -p heat_conservative
# H405 multi line docstring summary not separated with an empty line
ignore = H404,H405
show-source = true
exclude=.*,dist,*openstack/common*,*lib/python*,*egg,tools,build
exclude=.*,dist,*openstack/common*,*lib/python*,*egg,tools,build,*convergence/scenarios/*
max-complexity=20
[hacking]