Add openstack cli stack actions

Add suspend, resume, update_cancel and check
Allow multiple stack id/names
Allow the --wait option
Bumped required openstack client version to 2.0.0 to pickup
utils.wait_for_status enhancement for heat error status.

based upon heat clis:
  heat stack-cancel-update
  heat action-suspend
  heat action-resume
  heat action-check

Change-Id: I109bd155f081087c0fe0cbd8f6601d6ca3a4534e
Blueprint: heat-support-python-openstackclient
This commit is contained in:
Mark Vanderwiel
2015-11-19 15:42:27 -06:00
parent a364a8c51d
commit 9a7ed34aaf
3 changed files with 326 additions and 0 deletions

View File

@@ -835,3 +835,149 @@ class TemplateShowStack(format_utils.YamlFormat):
raise exc.CommandError(msg)
return self.dict2columns(template)
class StackActionBase(lister.Lister):
"""Stack actions base."""
log = logging.getLogger(__name__ + '.StackActionBase')
def _get_parser(self, prog_name, stack_help, wait_help):
parser = super(StackActionBase, self).get_parser(prog_name)
parser.add_argument(
'stack',
metavar='<stack>',
nargs="+",
help=stack_help
)
parser.add_argument(
'--wait',
action='store_true',
help=wait_help
)
return parser
def _take_action(self, parsed_args, action, good_status, bad_status):
self.log.debug("take_action(%s)", parsed_args)
heat_client = self.app.client_manager.orchestration
return _stack_action(
parsed_args,
heat_client,
action,
good_status,
bad_status
)
def _stack_action(parsed_args, heat_client, action, good_status, bad_status):
rows = []
for stack in parsed_args.stack:
try:
action(stack)
except heat_exc.HTTPNotFound:
msg = _('Stack not found: %s') % stack
raise exc.CommandError(msg)
if parsed_args.wait:
if not utils.wait_for_status(heat_client.stacks.get, stack,
status_field='stack_status',
success_status=good_status,
error_status=bad_status):
err = _("Error waiting for status from stack %s") % stack
raise exc.CommandError(err)
data = heat_client.stacks.get(stack)
columns = [
'ID',
'Stack Name',
'Stack Status',
'Creation Time',
'Updated Time'
]
rows += [utils.get_dict_properties(data.to_dict(), columns)]
return (columns, rows)
class SuspendStack(StackActionBase):
"""Suspend a stack."""
log = logging.getLogger(__name__ + '.SuspendStack')
def get_parser(self, prog_name):
return self._get_parser(
prog_name,
_('Stack(s) to suspend (name or ID)'),
_('Wait for suspend to complete')
)
def take_action(self, parsed_args):
return self._take_action(
parsed_args,
self.app.client_manager.orchestration.actions.suspend,
['suspend_complete'],
['suspend_failed']
)
class ResumeStack(StackActionBase):
"""Resume a stack."""
log = logging.getLogger(__name__ + '.ResumeStack')
def get_parser(self, prog_name):
return self._get_parser(
prog_name,
_('Stack(s) to resume (name or ID)'),
_('Wait for resume to complete')
)
def take_action(self, parsed_args):
return self._take_action(
parsed_args,
self.app.client_manager.orchestration.actions.resume,
['resume_complete'],
['resume_failed']
)
class UpdateCancelStack(StackActionBase):
"""Cancel update for a stack."""
log = logging.getLogger(__name__ + '.UpdateCancelStack')
def get_parser(self, prog_name):
return self._get_parser(
prog_name,
_('Stack(s) to cancel update (name or ID)'),
_('Wait for cancel update to complete')
)
def take_action(self, parsed_args):
return self._take_action(
parsed_args,
self.app.client_manager.orchestration.actions.cancel_update,
['cancel_update_complete'],
['cancel_update_failed']
)
class CheckStack(StackActionBase):
"""Check a stack."""
log = logging.getLogger(__name__ + '.CheckStack')
def get_parser(self, prog_name):
return self._get_parser(
prog_name,
_('Stack(s) to check update (name or ID)'),
_('Wait for check to complete')
)
def take_action(self, parsed_args):
return self._take_action(
parsed_args,
self.app.client_manager.orchestration.actions.check,
['check_complete'],
['check_failed']
)

View File

@@ -802,3 +802,179 @@ class TestStackTemplateShow(TestStack):
parsed_args = self.check_parser(self.cmd, arglist, [])
self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args)
class _TestStackCheckBase(object):
stack = stacks.Stack(None, {
"id": '1234',
"stack_name": 'my_stack',
"creation_time": "2013-08-04T20:57:55Z",
"updated_time": "2013-08-04T20:57:55Z",
"stack_status": "CREATE_COMPLETE"
})
columns = ['ID', 'Stack Name', 'Stack Status', 'Creation Time',
'Updated Time']
def _setUp(self, cmd, action):
self.cmd = cmd
self.action = action
self.mock_client.stacks.get = mock.Mock(
return_value=self.stack)
def _test_stack_action(self):
arglist = ['my_stack']
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, rows = self.cmd.take_action(parsed_args)
self.action.assert_called_once_with('my_stack')
self.mock_client.stacks.get.assert_called_once_with('my_stack')
self.assertEqual(self.columns, columns)
self.assertEqual(1, len(rows))
def _test_stack_action_multi(self):
arglist = ['my_stack1', 'my_stack2']
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, rows = self.cmd.take_action(parsed_args)
self.assertEqual(2, self.action.call_count)
self.assertEqual(2, self.mock_client.stacks.get.call_count)
self.action.assert_called_with('my_stack2')
self.mock_client.stacks.get.assert_called_with('my_stack2')
self.assertEqual(self.columns, columns)
self.assertEqual(2, len(rows))
@mock.patch('openstackclient.common.utils.wait_for_status',
return_value=True)
def _test_stack_action_wait(self, mock_wait):
arglist = ['my_stack', '--wait']
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, rows = self.cmd.take_action(parsed_args)
self.action.assert_called_with('my_stack')
self.mock_client.stacks.get.assert_called_once_with('my_stack')
self.assertEqual(self.columns, columns)
self.assertEqual(1, len(rows))
@mock.patch('openstackclient.common.utils.wait_for_status',
return_value=False)
def _test_stack_action_wait_error(self, mock_wait):
arglist = ['my_stack', '--wait']
parsed_args = self.check_parser(self.cmd, arglist, [])
error = self.assertRaises(exc.CommandError,
self.cmd.take_action,
parsed_args)
self.assertEqual('Error waiting for status from stack my_stack',
str(error))
def _test_stack_action_exception(self):
self.action.side_effect = heat_exc.HTTPNotFound
arglist = ['my_stack']
parsed_args = self.check_parser(self.cmd, arglist, [])
error = self.assertRaises(exc.CommandError,
self.cmd.take_action,
parsed_args)
self.assertEqual('Stack not found: my_stack',
str(error))
class TestStackSuspend(_TestStackCheckBase, TestStack):
def setUp(self):
super(TestStackSuspend, self).setUp()
self.mock_client.actions.suspend = mock.Mock()
self._setUp(
stack.SuspendStack(self.app, None),
self.mock_client.actions.suspend
)
def test_stack_suspend(self):
self._test_stack_action()
def test_stack_suspend_multi(self):
self._test_stack_action_multi()
def test_stack_suspend_wait(self):
self._test_stack_action_wait()
def test_stack_suspend_wait_error(self):
self._test_stack_action_wait_error()
def test_stack_suspend_exception(self):
self._test_stack_action_exception()
class TestStackResume(_TestStackCheckBase, TestStack):
def setUp(self):
super(TestStackResume, self).setUp()
self.mock_client.actions.resume = mock.Mock()
self._setUp(
stack.ResumeStack(self.app, None),
self.mock_client.actions.resume
)
def test_stack_resume(self):
self._test_stack_action()
def test_stack_resume_multi(self):
self._test_stack_action_multi()
def test_stack_resume_wait(self):
self._test_stack_action_wait()
def test_stack_resume_wait_error(self):
self._test_stack_action_wait_error()
def test_stack_resume_exception(self):
self._test_stack_action_exception()
class TestStackUpdateCancel(_TestStackCheckBase, TestStack):
def setUp(self):
super(TestStackUpdateCancel, self).setUp()
self.mock_client.actions.cancel_update = mock.Mock()
self._setUp(
stack.UpdateCancelStack(self.app, None),
self.mock_client.actions.cancel_update
)
def test_stack_cancel_update(self):
self._test_stack_action()
def test_stack_cancel_update_multi(self):
self._test_stack_action_multi()
def test_stack_cancel_update_wait(self):
self._test_stack_action_wait()
def test_stack_cancel_update_wait_error(self):
self._test_stack_action_wait_error()
def test_stack_cancel_update_exception(self):
self._test_stack_action_exception()
class TestStackCheck(_TestStackCheckBase, TestStack):
def setUp(self):
super(TestStackCheck, self).setUp()
self.mock_client.actions.check = mock.Mock()
self._setUp(
stack.CheckStack(self.app, None),
self.mock_client.actions.check
)
def test_stack_check(self):
self._test_stack_action()
def test_stack_check_multi(self):
self._test_stack_action_multi()
def test_stack_check_wait(self):
self._test_stack_action_wait()
def test_stack_check_wait_error(self):
self._test_stack_action_wait_error()
def test_stack_check_exception(self):
self._test_stack_action_exception()

View File

@@ -39,16 +39,20 @@ openstack.orchestration.v1 =
software_deployment_list = heatclient.osc.v1.software_deployment:ListDeployment
stack_abandon = heatclient.osc.v1.stack:AbandonStack
stack_adopt = heatclient.osc.v1.stack:AdoptStack
stack_check = heatclient.osc.v1.stack:CheckStack
stack_create = heatclient.osc.v1.stack:CreateStack
stack_list = heatclient.osc.v1.stack:ListStack
stack_output_list = heatclient.osc.v1.stack:OutputListStack
stack_output_show = heatclient.osc.v1.stack:OutputShowStack
stack_resource_metadata = heatclient.osc.v1.resources:ResourceMetadata
stack_resume = heatclient.osc.v1.stack:ResumeStack
stack_show = heatclient.osc.v1.stack:ShowStack
stack_snapshot_list = heatclient.osc.v1.snapshot:ListSnapshot
stack_snapshot_show = heatclient.osc.v1.snapshot:ShowSnapshot
stack_suspend = heatclient.osc.v1.stack:SuspendStack
stack_template_show = heatclient.osc.v1.stack:TemplateShowStack
stack_update = heatclient.osc.v1.stack:UpdateStack
stack_update_cancel = heatclient.osc.v1.stack:UpdateCancelStack
[global]
setup-hooks =