Async HeatStack::push

This patch adds new feature - async HeatStack::push
If HeatStack::push is called with flag async=True,
template will be pushed in separate greenthread with
1 sec delay. If push operation is not finished and next
push is called, thread will be killed and next push
will be scheduled.

Change-Id: I8aea5a88fdf964b8ed0436f7d692dac50caf854b
This commit is contained in:
Snihyr Kostyantyn 2016-09-27 14:48:56 +03:00
parent 493d1791e0
commit 100d1edb39
3 changed files with 107 additions and 46 deletions

View File

@ -52,6 +52,19 @@ class HeatStack(object):
self._last_stack_timestamps = (None, None)
self._tags = ''
self._region_name = region_name
self._push_thread = None
def _is_push_thread_alive(self):
return self._push_thread is not None and not self._push_thread.dead
def _kill_push_thread(self):
if self._is_push_thread_alive():
self._push_thread.kill()
def _wait_push_thread(self):
if not self._is_push_thread_alive():
return
self._push_thread.wait()
@staticmethod
def _create_client(session, region_name):
@ -142,14 +155,12 @@ class HeatStack(object):
def status_func(state_value):
status[0] = state_value
return True
self._wait_state(status_func)
return status[0]
def _wait_state(self, status_func, wait_progress=False):
tries = 4
delay = 1
while tries > 0:
while True:
try:
@ -197,9 +208,57 @@ class HeatStack(object):
return {}
def output(self):
self._wait_push_thread()
return self._wait_state(lambda status: True)
def push(self):
def _push(self, object_store=None):
template = copy.deepcopy(self._template)
LOG.debug('Pushing: {template}'.format(template=json.dumps(template)))
object_store = object_store or helpers.get_object_store()
while True:
try:
with helpers.with_object_store(object_store):
current_status = self._get_status()
resources = template.get('Resources') or template.get(
'resources')
if current_status == 'NOT_FOUND':
if resources is not None:
token_client = self._get_token_client()
token_client.stacks.create(
stack_name=self._name,
parameters=self._parameters,
template=template,
files=self._files,
environment=self._hot_environment,
disable_rollback=True,
tags=self._tags)
self._wait_state(
lambda status: status == 'CREATE_COMPLETE')
else:
if resources is not None:
self._client.stacks.update(
stack_id=self._name,
parameters=self._parameters,
files=self._files,
environment=self._hot_environment,
template=template,
disable_rollback=True,
tags=self._tags)
self._wait_state(
lambda status: status == 'UPDATE_COMPLETE',
True)
else:
self.delete()
except heat_exc.HTTPConflict as e:
LOG.warning(_LW('Conflicting operation: {msg}').format(msg=e))
eventlet.sleep(3)
else:
break
self._applied = self._template == template
def push(self, async=False):
if self._applied or self._template is None:
return
@ -210,51 +269,16 @@ class HeatStack(object):
if 'description' not in self._template and self._description:
self._template['description'] = self._description
template = copy.deepcopy(self._template)
LOG.debug('Pushing: {template}'.format(template=json.dumps(template)))
while True:
try:
current_status = self._get_status()
resources = template.get('Resources') or template.get(
'resources')
if current_status == 'NOT_FOUND':
if resources is not None:
token_client = self._get_token_client()
token_client.stacks.create(
stack_name=self._name,
parameters=self._parameters,
template=template,
files=self._files,
environment=self._hot_environment,
disable_rollback=True,
tags=self._tags)
self._wait_state(
lambda status: status == 'CREATE_COMPLETE')
else:
if resources is not None:
self._client.stacks.update(
stack_id=self._name,
parameters=self._parameters,
files=self._files,
environment=self._hot_environment,
template=template,
disable_rollback=True,
tags=self._tags)
self._wait_state(
lambda status: status == 'UPDATE_COMPLETE', True)
else:
self.delete()
except heat_exc.HTTPConflict as e:
LOG.warning(_LW('Conflicting operation: {msg}').format(msg=e))
eventlet.sleep(3)
else:
break
self._applied = self._template == template
self._kill_push_thread()
if async:
self._push_thread =\
eventlet.greenthread.spawn_after_local(
1, self._push, helpers.get_object_store())
else:
self._push()
def delete(self):
self._kill_push_thread()
while True:
try:
if not self.current():

View File

@ -168,6 +168,39 @@ class TestHeatStack(base.MuranoTestCase):
)
self.assertTrue(hs._applied)
@mock.patch(CLS_NAME + '._wait_state')
@mock.patch(CLS_NAME + '._get_status')
def test_heat_async_push(self, status_get, wait_st):
"""Assert that if heat_template_version is omitted, it's added."""
status_get.return_value = 'NOT_FOUND'
wait_st.return_value = {}
hs = heat_stack.HeatStack('test-stack', None)
hs._description = None
hs._template = {'resources': {'test': 1}}
hs._files = {"heatFile": "file"}
hs._hot_environment = 'environments'
hs._parameters = {}
hs._applied = False
hs.push(async=True)
expected_template = {
'heat_template_version': '2013-05-23',
'resources': {'test': 1}
}
self.heat_client_mock.stacks.create.assert_not_called()
hs.output()
self.heat_client_mock.stacks.create.assert_called_with(
stack_name='test-stack',
disable_rollback=True,
parameters={},
template=expected_template,
files={"heatFile": "file"},
environment='environments',
tags=self.mock_tag
)
self.assertTrue(hs._applied)
@mock.patch(CLS_NAME + '.current')
def test_update_wrong_template_version(self, current):
"""Template version other than expected should cause error."""

View File

@ -0,0 +1,4 @@
---
features:
- io.murano.system.HeatStack.push can be called with
async => true flag for asynchronous push