OpenstackClient plugin for stack adopt

This change implements the "openstack stack adopt" command.

Blueprint: heat-support-python-openstackclient

Change-Id: Id2e74970937c04f095b4f14be047cff6e2bf3891
This commit is contained in:
Steve Baker 2016-02-03 14:30:02 +13:00
parent 0a4f2af09c
commit f225c4e956
4 changed files with 184 additions and 0 deletions

View File

@ -20,6 +20,7 @@ from cliff import show
from openstackclient.common import exceptions as exc
from openstackclient.common import parseractions
from openstackclient.common import utils
from six.moves.urllib import request
from heatclient.common import http
from heatclient.common import template_utils
@ -566,3 +567,88 @@ def _list(client, args=None):
columns,
(utils.get_item_properties(s, columns) for s in data)
)
class AdoptStack(show.ShowOne):
"""Adopt a stack."""
log = logging.getLogger(__name__ + '.AdoptStack')
def get_parser(self, prog_name):
parser = super(AdoptStack, self).get_parser(prog_name)
parser.add_argument(
'name',
metavar='<STACK_NAME>',
help=_('Name of the stack to adopt')
)
parser.add_argument(
'-e', '--environment',
metavar='<FILE or URL>',
action='append',
help=_('Path to the environment. Can be specified multiple times')
)
parser.add_argument(
'--timeout',
metavar='<TIMEOUT>',
type=int,
help=_('Stack creation timeout in minutes')
)
parser.add_argument(
'--adopt-file',
metavar='<FILE or URL>',
required=True,
help=_('Path to adopt stack data file')
)
parser.add_argument(
'--enable-rollback',
action='store_true',
help=_('Enable rollback on create/update failure')
)
parser.add_argument(
'--parameter',
metavar='<KEY=VALUE>',
action='append',
help=_('Parameter values used to create the stack. Can be '
'specified multiple times')
)
parser.add_argument(
'--wait',
action='store_true',
help=_('Wait until stack adopt completes')
)
return parser
def take_action(self, parsed_args):
self.log.debug('take_action(%s)', parsed_args)
client = self.app.client_manager.orchestration
env_files, env = (
template_utils.process_multiple_environments_and_files(
env_paths=parsed_args.environment))
adopt_url = heat_utils.normalise_file_path_to_url(
parsed_args.adopt_file)
adopt_data = request.urlopen(adopt_url).read().decode('utf-8')
fields = {
'stack_name': parsed_args.name,
'disable_rollback': not parsed_args.enable_rollback,
'adopt_stack_data': adopt_data,
'parameters': heat_utils.format_parameters(parsed_args.parameter),
'files': dict(list(env_files.items())),
'environment': env,
'timeout': parsed_args.timeout
}
stack = client.stacks.create(**fields)['stack']
if parsed_args.wait:
if not utils.wait_for_status(client.stacks.get, parsed_args.name,
status_field='stack_status',
success_status='create_complete',
error_status=['create_failed']):
msg = _('Stack %s failed to create.') % parsed_args.name
raise exc.CommandError(msg)
return _show_stack(client, stack['id'], format='table', short=True)

View File

@ -0,0 +1,32 @@
{
'files': {},
'status': 'COMPLETE',
'name': 'my_stack',
'tags': None,
'stack_user_project_id': '123456',
'environment': {},
'template': {
'heat_template_version': '2016-04-08',
'resources': {
'thing': {
'type': 'OS::Heat::TestResource'
}
}
},
'action': 'CREATE',
'project_id': '56789',
'id': '2468',
'resources': {
'thing': {
'status': 'COMPLETE',
'name': 'thing',
'resource_data': {
'value': 'test_string',
},
'resource_id': 'my_stack-thing-1234',
'action': 'CREATE',
'type': 'OS::Heat::TestResource',
'metadata': {}
}
}
}

View File

@ -520,3 +520,68 @@ class TestStackList(TestStack):
parsed_args = self.check_parser(self.cmd, arglist, [])
self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args)
class TestStackAdopt(TestStack):
adopt_file = 'heatclient/tests/test_templates/adopt.json'
with open(adopt_file, 'r') as f:
adopt_data = f.read()
defaults = {
'stack_name': 'my_stack',
'disable_rollback': True,
'adopt_stack_data': adopt_data,
'parameters': {},
'files': {},
'environment': {},
'timeout': None
}
def setUp(self):
super(TestStackAdopt, self).setUp()
self.cmd = stack.AdoptStack(self.app, None)
self.stack_client.create = mock.MagicMock(
return_value={'stack': {'id': '1234'}})
def test_stack_adopt_defaults(self):
arglist = ['my_stack', '--adopt-file', self.adopt_file]
cols = ['id', 'stack_name', 'description', 'creation_time',
'updated_time', 'stack_status', 'stack_status_reason']
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
self.stack_client.create.assert_called_with(**self.defaults)
self.assertEqual(cols, columns)
def test_stack_adopt_enable_rollback(self):
arglist = ['my_stack', '--adopt-file', self.adopt_file,
'--enable-rollback']
kwargs = copy.deepcopy(self.defaults)
kwargs['disable_rollback'] = False
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.stack_client.create.assert_called_with(**kwargs)
def test_stack_adopt_wait(self):
arglist = ['my_stack', '--adopt-file', self.adopt_file, '--wait']
self.stack_client.get = mock.MagicMock(return_value=(
stacks.Stack(None, {'stack_status': 'CREATE_COMPLETE'})))
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.stack_client.create.assert_called_with(**self.defaults)
self.stack_client.get.assert_called_with(**{'stack_id': '1234'})
def test_stack_adopt_wait_fail(self):
arglist = ['my_stack', '--adopt-file', self.adopt_file, '--wait']
self.stack_client.get = mock.MagicMock(return_value=(
stacks.Stack(None, {'stack_status': 'CREATE_FAILED'})))
parsed_args = self.check_parser(self.cmd, arglist, [])
self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args)

View File

@ -35,6 +35,7 @@ openstack.orchestration.v1 =
stack_create = heatclient.osc.v1.stack:CreateStack
stack_update = heatclient.osc.v1.stack:UpdateStack
stack_snapshot_list = heatclient.osc.v1.snapshot:ListSnapshot
stack_adopt = heatclient.osc.v1.stack:AdoptStack
[global]