diff options
-rw-r--r-- | heatclient/common/http.py | 2 | ||||
-rw-r--r-- | heatclient/shell.py | 4 | ||||
-rw-r--r-- | heatclient/tests/test_shell.py | 62 | ||||
-rw-r--r-- | heatclient/v1/shell.py | 41 | ||||
-rw-r--r-- | requirements.txt | 4 | ||||
-rw-r--r-- | setup.cfg | 2 | ||||
-rwxr-xr-x | setup.py | 8 | ||||
-rw-r--r-- | test-requirements.txt | 2 | ||||
-rw-r--r-- | tools/heat.bash_completion | 2 | ||||
-rw-r--r-- | tox.ini | 5 |
10 files changed, 89 insertions, 43 deletions
diff --git a/heatclient/common/http.py b/heatclient/common/http.py index 48c83f6..cbf4540 100644 --- a/heatclient/common/http.py +++ b/heatclient/common/http.py @@ -27,8 +27,6 @@ from heatclient.openstack.common import jsonutils from heatclient.openstack.common import strutils LOG = logging.getLogger(__name__) -if not LOG.handlers: - LOG.addHandler(logging.StreamHandler()) USER_AGENT = 'python-heatclient' CHUNKSIZE = 1024 * 64 # 64kB diff --git a/heatclient/shell.py b/heatclient/shell.py index 22173f5..cd00ac6 100644 --- a/heatclient/shell.py +++ b/heatclient/shell.py @@ -283,8 +283,10 @@ class HeatShell(object): def _setup_logging(self, debug): log_lvl = logging.DEBUG if debug else logging.WARNING logging.basicConfig( - format="%(levelname)s (%(module)s:%(lineno)d) %(message)s", + format="%(levelname)s (%(module)s) %(message)s", level=log_lvl) + logging.getLogger('iso8601').setLevel(logging.WARNING) + logging.getLogger('urllib3.connectionpool').setLevel(logging.WARNING) def _setup_verbose(self, verbose): if verbose: diff --git a/heatclient/tests/test_shell.py b/heatclient/tests/test_shell.py index d5808c2..088fd81 100644 --- a/heatclient/tests/test_shell.py +++ b/heatclient/tests/test_shell.py @@ -143,12 +143,6 @@ class ShellParamValidationTest(TestCase): ('stack-update', dict( command='stack-update ts -P "a-b"', err='Malformed parameter')), - ('validate', dict( - command='validate -P "a=b;c"', - err='Malformed parameter')), - ('template-validate', dict( - command='template-validate -P "a$b"', - err='Malformed parameter')), ] def setUp(self): @@ -397,6 +391,8 @@ class ShellTestUserPass(ShellBase): 'limit': 2, 'status': ['COMPLETE', 'FAILED'], 'marker': 'fake_id', + 'global_tenant': True, + 'show_deleted': 'True', }, True) fakes.script_heat_list(expected_url) @@ -406,7 +402,9 @@ class ShellTestUserPass(ShellBase): ' --limit 2' ' --marker fake_id' ' --filters=status=COMPLETE' - ' --filters=status=FAILED') + ' --filters=status=FAILED' + ' --global-tenant' + ' --show-deleted') required = [ 'teststack', @@ -858,7 +856,8 @@ class ShellTestUserPass(ShellBase): 'LinuxDistribution': 'F17"', '"InstanceType': 'm1.large', 'DBPassword': 'verybadpassword'}, - 'timeout_mins': 123} + 'timeout_mins': 123, + 'disable_rollback': True} http.HTTPClient.json_request( 'PUT', '/stacks/teststack2/2', data=expected_data, @@ -1037,6 +1036,7 @@ class ShellTestUserPass(ShellBase): update_text = self.shell( 'stack-update teststack2/2 ' '--template-file=%s ' + '--enable-rollback ' '--parameters="InstanceType=m1.large;DBUsername=wp;' 'DBPassword=verybadpassword;KeyName=heat_key;' 'LinuxDistribution=F17"' % template_file) @@ -1311,21 +1311,22 @@ class ShellTestResources(ShellBase): def _script_keystone_client(self): fakes.script_keystone_client() - def test_resource_list(self): + def _test_resource_list(self, with_resource_name): self._script_keystone_client() resp_dict = {"resources": [ {"links": [{"href": "http://heat.example.com:8004/foo", "rel": "self"}, {"href": "http://heat.example.com:8004/foo2", "rel": "resource"}], - "logical_resource_id": "aResource", + "logical_resource_id": "aLogicalResource", "physical_resource_id": "43b68bae-ed5d-4aed-a99f-0b3d39c2418a", - "resource_name": "aResource", "resource_status": "CREATE_COMPLETE", "resource_status_reason": "state changed", "resource_type": "OS::Nova::Server", "updated_time": "2014-01-06T16:14:26Z"}]} + if with_resource_name: + resp_dict["resources"][0]["resource_name"] = "aResource" resp = fakes.FakeHTTPResponse( 200, 'OK', @@ -1341,18 +1342,53 @@ class ShellTestResources(ShellBase): resource_list_text = self.shell('resource-list {0}'.format(stack_id)) required = [ - 'resource_name', 'resource_type', 'resource_status', 'updated_time', - 'aResource', 'OS::Nova::Server', 'CREATE_COMPLETE', '2014-01-06T16:14:26Z' ] + if with_resource_name: + required.append('resource_name') + required.append('aResource') + else: + required.append('logical_resource_id') + required.append("aLogicalResource") + for r in required: self.assertRegexpMatches(resource_list_text, r) + def test_resource_list(self): + self._test_resource_list(True) + + def test_resource_list_no_resource_name(self): + self._test_resource_list(False) + + def test_resource_list_empty(self): + self._script_keystone_client() + resp_dict = {"resources": []} + resp = fakes.FakeHTTPResponse( + 200, + 'OK', + {'content-type': 'application/json'}, + jsonutils.dumps(resp_dict)) + stack_id = 'teststack/1' + http.HTTPClient.json_request( + 'GET', '/stacks/%s/resources' % ( + stack_id)).AndReturn((resp, resp_dict)) + + self.m.ReplayAll() + + resource_list_text = self.shell('resource-list {0}'.format(stack_id)) + + self.assertEqual('''\ ++---------------+---------------+-----------------+--------------+ +| resource_name | resource_type | resource_status | updated_time | ++---------------+---------------+-----------------+--------------+ ++---------------+---------------+-----------------+--------------+ +''', resource_list_text) + def test_resource_show(self): self._script_keystone_client() resp_dict = {"resource": diff --git a/heatclient/v1/shell.py b/heatclient/v1/shell.py index 18e9637..89ede40 100644 --- a/heatclient/v1/shell.py +++ b/heatclient/v1/shell.py @@ -329,6 +329,11 @@ def do_stack_show(hc, args): help='URL of template.') @utils.arg('-o', '--template-object', metavar='<URL>', help='URL to retrieve template object (e.g. from swift).') +@utils.arg('-t', '--timeout', metavar='<TIMEOUT>', + type=int, + help='Stack update timeout in minutes.') +@utils.arg('-r', '--enable-rollback', default=False, action="store_true", + help='Enable rollback on create/update failure.') @utils.arg('-P', '--parameters', metavar='<KEY1=VALUE1;KEY2=VALUE2...>', help='Parameter values used to create the stack. ' 'This can be specified multiple times, or once with parameters ' @@ -353,6 +358,8 @@ def do_update(hc, args): @utils.arg('-t', '--timeout', metavar='<TIMEOUT>', type=int, help='Stack update timeout in minutes.') +@utils.arg('-r', '--enable-rollback', default=False, action="store_true", + help='Enable rollback on create/update failure.') @utils.arg('-P', '--parameters', metavar='<KEY1=VALUE1;KEY2=VALUE2...>', help='Parameter values used to create the stack. ' 'This can be specified multiple times, or once with parameters ' @@ -374,6 +381,7 @@ def do_stack_update(hc, args): fields = { 'stack_id': args.id, + 'disable_rollback': not(args.enable_rollback), 'parameters': utils.format_parameters(args.parameters), 'template': template, 'files': dict(list(tpl_files.items()) + list(env_files.items())), @@ -393,6 +401,8 @@ def do_list(hc, args=None): do_stack_list(hc) +@utils.arg('-s', '--show-deleted', default=False, action="store_true", + help='Include soft-deleted stacks in the stack listing.') @utils.arg('-f', '--filters', metavar='<KEY1=VALUE1;KEY2=VALUE2...>', help='Filter parameters to apply on returned stacks. ' 'This can be specified multiple times, or once with parameters ' @@ -402,13 +412,20 @@ def do_list(hc, args=None): help='Limit the number of stacks returned.') @utils.arg('-m', '--marker', metavar='<ID>', help='Only return stacks that appear after the given stack ID.') +@utils.arg('-g', '--global-tenant', + action='store_true', + default=False, + help='Display stacks from all tenants. Operation only authorized ' + 'for users who match the policy in heat\'s policy.json.') def do_stack_list(hc, args=None): '''List the user's stacks.''' kwargs = {} if args: kwargs = {'limit': args.limit, 'marker': args.marker, - 'filters': utils.format_parameters(args.filters)} + 'filters': utils.format_parameters(args.filters), + 'global_tenant': args.global_tenant, + 'show_deleted': args.show_deleted} stacks = hc.stacks.list(**kwargs) fields = ['id', 'stack_name', 'stack_status', 'creation_time'] @@ -507,11 +524,6 @@ def do_template_show(hc, args): help='Path to the environment.') @utils.arg('-o', '--template-object', metavar='<URL>', help='URL to retrieve template object (e.g. from swift).') -@utils.arg('-P', '--parameters', metavar='<KEY1=VALUE1;KEY2=VALUE2...>', - help='Parameter values to validate. ' - 'This can be specified multiple times, or once with parameters ' - 'separated by a semicolon.', - action='append') def do_validate(hc, args): '''DEPRECATED! Use template-validate instead.''' logger.warning('DEPRECATED! Use template-validate instead.') @@ -526,11 +538,6 @@ def do_validate(hc, args): help='Path to the environment.') @utils.arg('-o', '--template-object', metavar='<URL>', help='URL to retrieve template object (e.g. from swift).') -@utils.arg('-P', '--parameters', metavar='<KEY1=VALUE1;KEY2=VALUE2...>', - help='Parameter values to validate. ' - 'This can be specified multiple times, or once with parameters ' - 'separated by a semicolon.', - action='append') def do_template_validate(hc, args): '''Validate a template with parameters.''' @@ -543,7 +550,6 @@ def do_template_validate(hc, args): env_files, env = template_utils.process_environment_and_files( env_path=args.environment_file) fields = { - 'parameters': utils.format_parameters(args.parameters), 'template': template, 'files': dict(list(tpl_files.items()) + list(env_files.items())), 'environment': env @@ -564,11 +570,10 @@ def do_resource_list(hc, args): raise exc.CommandError('Stack not found: %s' % args.id) else: fields = ['resource_type', 'resource_status', 'updated_time'] - if len(resources) >= 1: - if hasattr(resources[0], 'resource_name'): - fields.insert(0, 'resource_name') - else: - fields.insert(0, 'logical_resource_id') + if len(resources) >= 1 and not hasattr(resources[0], 'resource_name'): + fields.insert(0, 'logical_resource_id') + else: + fields.insert(0, 'resource_name') utils.print_list(resources, fields, sortby_index=3) @@ -698,7 +703,7 @@ def do_event_list(hc, args): fields.insert(0, 'resource_name') else: fields.insert(0, 'logical_resource_id') - utils.print_list(events, fields) + utils.print_list(events, fields, sortby_index=None) @utils.arg('id', metavar='<NAME or ID>', diff --git a/requirements.txt b/requirements.txt index 93e29c6..cb74bf1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ argparse iso8601>=0.1.9 pbr>=0.6,!=0.7,<1.0 PrettyTable>=0.7,<0.8 -python-keystoneclient>=0.7.0 +python-keystoneclient>=0.9.0 PyYAML>=3.1.0 requests>=1.1 -six>=1.5.2 +six>=1.7.0 @@ -39,3 +39,5 @@ all_files = 1 [upload_sphinx] upload-dir = doc/build/html +[wheel] +universal = 1 @@ -17,6 +17,14 @@ # THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT import setuptools +# In python < 2.7.4, a lazy loading of package `pbr` will break +# setuptools if some other modules registered functions in `atexit`. +# solution from: http://bugs.python.org/issue15881#msg170215 +try: + import multiprocessing # noqa +except ImportError: + pass + setuptools.setup( setup_requires=['pbr'], pbr=True) diff --git a/test-requirements.txt b/test-requirements.txt index d85a7b9..0fe6988 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -5,7 +5,7 @@ fixtures>=0.3.14 hacking>=0.8.0,<0.9 mock>=1.0 mox3>=0.7.0 -sphinx>=1.1.2,<1.2 +sphinx>=1.1.2,!=1.2.0,<1.3 testrepository>=0.0.18 testscenarios>=0.4 testtools>=0.9.34 diff --git a/tools/heat.bash_completion b/tools/heat.bash_completion index 457c305..a04de1a 100644 --- a/tools/heat.bash_completion +++ b/tools/heat.bash_completion @@ -24,4 +24,4 @@ _heat() fi return 0 } -complete -o default -o nospace -F _heat heat +complete -o default -F _heat heat @@ -24,11 +24,6 @@ commands = {posargs} [testenv:cover] commands = python setup.py testr --coverage --testr-args='{posargs}' -[testenv:pypy] -deps = setuptools<3.2 - -r{toxinidir}/requirements.txt - -r{toxinidir}/test-requirements.txt - [tox:jenkins] downloadcache = ~/cache/pip |