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:
parent
493d1791e0
commit
100d1edb39
@ -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():
|
||||
|
@ -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."""
|
||||
|
4
releasenotes/notes/heat_push_async-da3f31b63284a0ea.yaml
Normal file
4
releasenotes/notes/heat_push_async-da3f31b63284a0ea.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
---
|
||||
features:
|
||||
- io.murano.system.HeatStack.push can be called with
|
||||
async => true flag for asynchronous push
|
Loading…
Reference in New Issue
Block a user