Use poll_for_events for "openstack stack delete"
Change-Id: Ie918e095e7d67c94991f1a7e4b0ede9127134936 Blueprint: heat-support-python-openstackclient
This commit is contained in:
@@ -17,7 +17,6 @@ import base64
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import textwrap
|
import textwrap
|
||||||
import time
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
@@ -116,30 +115,6 @@ def event_log_formatter(events):
|
|||||||
return "\n".join(event_log)
|
return "\n".join(event_log)
|
||||||
|
|
||||||
|
|
||||||
def wait_for_delete(status_f,
|
|
||||||
res_id,
|
|
||||||
status_field='status',
|
|
||||||
sleep_time=5,
|
|
||||||
timeout=300):
|
|
||||||
"""Wait for resource deletion."""
|
|
||||||
|
|
||||||
total_time = 0
|
|
||||||
while total_time < timeout:
|
|
||||||
try:
|
|
||||||
res = status_f(res_id)
|
|
||||||
except exc.HTTPNotFound:
|
|
||||||
return True
|
|
||||||
|
|
||||||
status = res.get(status_field, '').lower()
|
|
||||||
if 'failed' in status:
|
|
||||||
return False
|
|
||||||
|
|
||||||
time.sleep(sleep_time)
|
|
||||||
total_time += sleep_time
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def print_update_list(lst, fields, formatters=None):
|
def print_update_list(lst, fields, formatters=None):
|
||||||
"""Print the stack-update --dry-run output as a table.
|
"""Print the stack-update --dry-run output as a table.
|
||||||
|
|
||||||
|
@@ -631,25 +631,39 @@ class DeleteStack(command.Command):
|
|||||||
failure_count = 0
|
failure_count = 0
|
||||||
stacks_waiting = []
|
stacks_waiting = []
|
||||||
for sid in parsed_args.stack:
|
for sid in parsed_args.stack:
|
||||||
|
marker = None
|
||||||
|
if parsed_args.wait:
|
||||||
|
try:
|
||||||
|
# find the last event to use as the marker
|
||||||
|
events = event_utils.get_events(heat_client,
|
||||||
|
stack_id=sid,
|
||||||
|
event_args={
|
||||||
|
'sort_dir': 'desc',
|
||||||
|
'limit': 1})
|
||||||
|
if events:
|
||||||
|
marker = events[0].id
|
||||||
|
except heat_exc.CommandError as ex:
|
||||||
|
failure_count += 1
|
||||||
|
print(ex)
|
||||||
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
heat_client.stacks.delete(sid)
|
heat_client.stacks.delete(sid)
|
||||||
stacks_waiting.append(sid)
|
stacks_waiting.append((sid, marker))
|
||||||
except heat_exc.HTTPNotFound:
|
except heat_exc.HTTPNotFound:
|
||||||
failure_count += 1
|
failure_count += 1
|
||||||
print(_('Stack not found: %s') % sid)
|
print(_('Stack not found: %s') % sid)
|
||||||
|
|
||||||
if parsed_args.wait:
|
if parsed_args.wait:
|
||||||
for sid in stacks_waiting:
|
for sid, marker in stacks_waiting:
|
||||||
def status_f(id):
|
try:
|
||||||
return heat_client.stacks.get(id).to_dict()
|
stack_status, msg = event_utils.poll_for_events(
|
||||||
|
heat_client, sid, action='DELETE', marker=marker)
|
||||||
# TODO(jonesbr): switch to use openstack client wait_for_delete
|
except heat_exc.CommandError:
|
||||||
# when version 2.1.0 is adopted.
|
continue
|
||||||
if not heat_utils.wait_for_delete(status_f,
|
if stack_status == 'DELETE_FAILED':
|
||||||
sid,
|
|
||||||
status_field='stack_status'):
|
|
||||||
failure_count += 1
|
failure_count += 1
|
||||||
print(_('Stack failed to delete: %s') % sid)
|
print(msg)
|
||||||
|
|
||||||
if failure_count:
|
if failure_count:
|
||||||
msg = (_('Unable to delete %(count)d of the %(total)d stacks.') %
|
msg = (_('Unable to delete %(count)d of the %(total)d stacks.') %
|
||||||
|
@@ -581,38 +581,35 @@ class TestStackDelete(TestStack):
|
|||||||
self.stack_client.delete.assert_any_call('stack2')
|
self.stack_client.delete.assert_any_call('stack2')
|
||||||
self.assertEqual('Unable to delete 1 of the 2 stacks.', str(error))
|
self.assertEqual('Unable to delete 1 of the 2 stacks.', str(error))
|
||||||
|
|
||||||
def test_stack_delete_wait(self):
|
@mock.patch('heatclient.common.event_utils.poll_for_events',
|
||||||
|
return_value=('DELETE_COMPLETE',
|
||||||
|
'Stack my_stack DELETE_COMPLETE'))
|
||||||
|
@mock.patch('heatclient.common.event_utils.get_events', return_value=[])
|
||||||
|
def test_stack_delete_wait(self, mock_get_event, mock_poll, ):
|
||||||
arglist = ['stack1', 'stack2', 'stack3', '--wait']
|
arglist = ['stack1', 'stack2', 'stack3', '--wait']
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, [])
|
parsed_args = self.check_parser(self.cmd, arglist, [])
|
||||||
|
|
||||||
self.cmd.take_action(parsed_args)
|
self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
self.stack_client.delete.assert_any_call('stack1')
|
self.stack_client.delete.assert_any_call('stack1')
|
||||||
self.stack_client.get.assert_any_call('stack1')
|
|
||||||
self.stack_client.delete.assert_any_call('stack2')
|
self.stack_client.delete.assert_any_call('stack2')
|
||||||
self.stack_client.get.assert_any_call('stack2')
|
|
||||||
self.stack_client.delete.assert_any_call('stack3')
|
self.stack_client.delete.assert_any_call('stack3')
|
||||||
self.stack_client.get.assert_any_call('stack3')
|
|
||||||
|
|
||||||
def test_stack_delete_wait_one_pass_one_fail(self):
|
@mock.patch('heatclient.common.event_utils.poll_for_events')
|
||||||
|
@mock.patch('heatclient.common.event_utils.get_events', return_value=[])
|
||||||
|
def test_stack_delete_wait_fail(self, mock_get_event, mock_poll):
|
||||||
|
mock_poll.side_effect = [['DELETE_COMPLETE',
|
||||||
|
'Stack my_stack DELETE_COMPLETE'],
|
||||||
|
['DELETE_FAILED',
|
||||||
|
'Stack my_stack DELETE_FAILED'],
|
||||||
|
['DELETE_COMPLETE',
|
||||||
|
'Stack my_stack DELETE_COMPLETE']]
|
||||||
arglist = ['stack1', 'stack2', 'stack3', '--wait']
|
arglist = ['stack1', 'stack2', 'stack3', '--wait']
|
||||||
self.stack_client.get.side_effect = [
|
|
||||||
stacks.Stack(None, {'stack_status': 'DELETE_FAILED'}),
|
|
||||||
heat_exc.HTTPNotFound,
|
|
||||||
stacks.Stack(None, {'stack_status': 'DELETE_FAILED'}),
|
|
||||||
]
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, [])
|
parsed_args = self.check_parser(self.cmd, arglist, [])
|
||||||
|
|
||||||
error = self.assertRaises(exc.CommandError,
|
error = self.assertRaises(exc.CommandError,
|
||||||
self.cmd.take_action, parsed_args)
|
self.cmd.take_action, parsed_args)
|
||||||
|
|
||||||
self.stack_client.delete.assert_any_call('stack1')
|
self.stack_client.delete.assert_any_call('stack1')
|
||||||
self.stack_client.get.assert_any_call('stack1')
|
|
||||||
self.stack_client.delete.assert_any_call('stack2')
|
self.stack_client.delete.assert_any_call('stack2')
|
||||||
self.stack_client.get.assert_any_call('stack2')
|
|
||||||
self.stack_client.delete.assert_any_call('stack3')
|
self.stack_client.delete.assert_any_call('stack3')
|
||||||
self.stack_client.get.assert_any_call('stack3')
|
self.assertEqual('Unable to delete 1 of the 3 stacks.', str(error))
|
||||||
self.assertEqual('Unable to delete 2 of the 3 stacks.', str(error))
|
|
||||||
|
|
||||||
@mock.patch('sys.stdin', spec=six.StringIO)
|
@mock.patch('sys.stdin', spec=six.StringIO)
|
||||||
def test_stack_delete_prompt(self, mock_stdin):
|
def test_stack_delete_prompt(self, mock_stdin):
|
||||||
|
@@ -188,17 +188,6 @@ class ShellTest(testtools.TestCase):
|
|||||||
self.assertEqual(expected, utils.event_log_formatter(events_list))
|
self.assertEqual(expected, utils.event_log_formatter(events_list))
|
||||||
self.assertEqual('', utils.event_log_formatter([]))
|
self.assertEqual('', utils.event_log_formatter([]))
|
||||||
|
|
||||||
def test_wait_for_delete(self):
|
|
||||||
def status_f(id):
|
|
||||||
raise exc.HTTPNotFound
|
|
||||||
|
|
||||||
def bad_status_f(id):
|
|
||||||
return {'status': 'failed'}
|
|
||||||
|
|
||||||
self.assertTrue(utils.wait_for_delete(status_f, 123))
|
|
||||||
self.assertFalse(utils.wait_for_delete(status_f, 123, timeout=0))
|
|
||||||
self.assertFalse(utils.wait_for_delete(bad_status_f, 123))
|
|
||||||
|
|
||||||
|
|
||||||
class ShellTestParameterFiles(testtools.TestCase):
|
class ShellTestParameterFiles(testtools.TestCase):
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user