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:
@@ -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']
|
||||
)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 =
|
||||
|
||||
Reference in New Issue
Block a user