# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import os import sys import tempfile from unittest import mock import uuid import fixtures import io from keystoneauth1 import fixture as keystone_fixture from oslo_serialization import jsonutils from oslo_utils import encodeutils from requests_mock.contrib import fixture as rm_fixture import testscenarios import testtools from urllib import parse from urllib import request import yaml from heatclient._i18n import _ from heatclient.common import http from heatclient.common import utils from heatclient import exc import heatclient.shell from heatclient.tests.unit import fakes import heatclient.v1.shell load_tests = testscenarios.load_tests_apply_scenarios TEST_VAR_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), 'var')) BASE_HOST = 'http://keystone.example.com' BASE_URL = "%s:5000/" % BASE_HOST V2_URL = "%sv2.0" % BASE_URL V3_URL = "%sv3" % BASE_URL FAKE_ENV_KEYSTONE_V2 = { 'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_TENANT_NAME': 'tenant_name', 'OS_AUTH_URL': BASE_URL, } FAKE_ENV_KEYSTONE_V3 = { 'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_TENANT_NAME': 'tenant_name', 'OS_AUTH_URL': BASE_URL, 'OS_USER_DOMAIN_ID': 'default', 'OS_PROJECT_DOMAIN_ID': 'default', } class TestCase(testtools.TestCase): tokenid = uuid.uuid4().hex def setUp(self): super(TestCase, self).setUp() self.requests = self.useFixture(rm_fixture.Fixture()) # httpretty doesn't work as expected if http proxy environmen # variable is set. self.useFixture(fixtures.EnvironmentVariable('http_proxy')) self.useFixture(fixtures.EnvironmentVariable('https_proxy')) self.patch('heatclient.v1.shell.show_deprecated') def set_fake_env(self, fake_env): client_env = ('OS_USERNAME', 'OS_PASSWORD', 'OS_TENANT_ID', 'OS_TENANT_NAME', 'OS_AUTH_URL', 'OS_REGION_NAME', 'OS_AUTH_TOKEN', 'OS_NO_CLIENT_AUTH', 'OS_SERVICE_TYPE', 'OS_ENDPOINT_TYPE', 'HEAT_URL') for key in client_env: self.useFixture( fixtures.EnvironmentVariable(key, fake_env.get(key))) def shell_error(self, argstr, error_match, exception): _shell = heatclient.shell.HeatShell() e = self.assertRaises(exception, _shell.main, argstr.split()) self.assertRegex(e.__str__(), error_match) def register_keystone_v2_token_fixture(self): v2_token = keystone_fixture.V2Token(token_id=self.tokenid) service = v2_token.add_service('orchestration') service.add_endpoint('http://heat.example.com', admin='http://heat-admin.localdomain', internal='http://heat.localdomain', region='RegionOne') self.requests.post('%s/tokens' % V2_URL, json=v2_token) def register_keystone_v3_token_fixture(self): v3_token = keystone_fixture.V3Token() service = v3_token.add_service('orchestration') service.add_standard_endpoints(public='http://heat.example.com', admin='http://heat-admin.localdomain', internal='http://heat.localdomain') self.requests.post('%s/auth/tokens' % V3_URL, json=v3_token, headers={'X-Subject-Token': self.tokenid}) def register_keystone_auth_fixture(self): self.register_keystone_v2_token_fixture() self.register_keystone_v3_token_fixture() version_list = keystone_fixture.DiscoveryList(href=BASE_URL) self.requests.get(BASE_URL, json=version_list) # NOTE(tlashchova): this overrides the testtools.TestCase.patch method # that does simple monkey-patching in favor of mock's patching def patch(self, target, **kwargs): mockfixture = self.useFixture(fixtures.MockPatch(target, **kwargs)) return mockfixture.mock def stack_list_resp_dict(self, show_nested=False, include_project=False): stack1 = { "id": "1", "stack_name": "teststack", "stack_owner": "testowner", "stack_status": 'CREATE_COMPLETE', "creation_time": "2012-10-25T01:58:47Z"} stack2 = { "id": "2", "stack_name": "teststack2", "stack_owner": "testowner", "stack_status": 'IN_PROGRESS', "creation_time": "2012-10-25T01:58:47Z" } if include_project: stack1['project'] = 'testproject' stack1['project'] = 'testproject' resp_dict = {"stacks": [stack1, stack2]} if show_nested: nested = { "id": "3", "stack_name": "teststack_nested", "stack_status": 'IN_PROGRESS', "creation_time": "2012-10-25T01:58:47Z", "parent": "theparentof3" } if include_project: nested['project'] = 'testproject' resp_dict["stacks"].append(nested) return resp_dict def event_list_resp_dict( self, stack_name="teststack", resource_name=None, rsrc_eventid1="7fecaeed-d237-4559-93a5-92d5d9111205", rsrc_eventid2="e953547a-18f8-40a7-8e63-4ec4f509648b", final_state="COMPLETE"): action = "CREATE" rn = resource_name if resource_name else "testresource" resp_dict = {"events": [ {"event_time": "2013-12-05T14:14:31", "id": rsrc_eventid1, "links": [{"href": "http://heat.example.com:8004/foo", "rel": "self"}, {"href": "http://heat.example.com:8004/foo2", "rel": "resource"}, {"href": "http://heat.example.com:8004/foo3", "rel": "stack"}], "logical_resource_id": "myDeployment", "physical_resource_id": None, "resource_name": rn, "resource_status": "%s_IN_PROGRESS" % action, "resource_status_reason": "state changed"}, {"event_time": "2013-12-05T14:14:32", "id": rsrc_eventid2, "links": [{"href": "http://heat.example.com:8004/foo", "rel": "self"}, {"href": "http://heat.example.com:8004/foo2", "rel": "resource"}, {"href": "http://heat.example.com:8004/foo3", "rel": "stack"}], "logical_resource_id": "myDeployment", "physical_resource_id": "bce15ec4-8919-4a02-8a90-680960fb3731", "resource_name": rn, "resource_status": "%s_%s" % (action, final_state), "resource_status_reason": "state changed"}]} if resource_name is None: # if resource_name is not specified, # then request is made for stack events. Hence include the stack # event stack_event1 = "0159dccd-65e1-46e8-a094-697d20b009e5" stack_event2 = "8f591a36-7190-4adb-80da-00191fe22388" resp_dict["events"].insert( 0, {"event_time": "2013-12-05T14:14:30", "id": stack_event1, "links": [{"href": "http://heat.example.com:8004/foo", "rel": "self"}, {"href": "http://heat.example.com:8004/foo2", "rel": "resource"}, {"href": "http://heat.example.com:8004/foo3", "rel": "stack"}], "logical_resource_id": "aResource", "physical_resource_id": 'foo3', "resource_name": stack_name, "resource_status": "%s_IN_PROGRESS" % action, "resource_status_reason": "state changed"}) resp_dict["events"].append( {"event_time": "2013-12-05T14:14:33", "id": stack_event2, "links": [{"href": "http://heat.example.com:8004/foo", "rel": "self"}, {"href": "http://heat.example.com:8004/foo2", "rel": "resource"}, {"href": "http://heat.example.com:8004/foo3", "rel": "stack"}], "logical_resource_id": "aResource", "physical_resource_id": 'foo3', "resource_name": stack_name, "resource_status": "%s_%s" % (action, final_state), "resource_status_reason": "state changed"}) return resp_dict class EnvVarTest(TestCase): scenarios = [ ('username', dict( remove='OS_USERNAME', err='You must provide a username')), ('password', dict( remove='OS_PASSWORD', err='You must provide a password')), ('tenant_name', dict( remove='OS_TENANT_NAME', err='You must provide a tenant id')), ('auth_url', dict( remove='OS_AUTH_URL', err='You must provide an auth url')), ] def test_missing_auth(self): fake_env = { 'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_TENANT_NAME': 'tenant_name', 'OS_AUTH_URL': 'http://no.where', } fake_env[self.remove] = None self.set_fake_env(fake_env) self.shell_error('stack-list', self.err, exception=exc.CommandError) class EnvVarTestToken(TestCase): scenarios = [ ('tenant_id', dict( remove='OS_TENANT_ID', err='You must provide a tenant id')), ('auth_url', dict( remove='OS_AUTH_URL', err='You must provide an auth url')), ] def test_missing_auth(self): fake_env = { 'OS_AUTH_TOKEN': 'atoken', 'OS_TENANT_ID': 'tenant_id', 'OS_AUTH_URL': 'http://no.where', } fake_env[self.remove] = None self.set_fake_env(fake_env) self.shell_error('stack-list', self.err, exception=exc.CommandError) class ShellParamValidationTest(TestCase): scenarios = [ ('stack-create', dict( command='stack-create ts -P "ab"', with_tmpl=True, err='Malformed parameter')), ('stack-update', dict( command='stack-update ts -P "a-b"', with_tmpl=True, err='Malformed parameter')), ('stack-list-with-sort-dir', dict( command='stack-list --sort-dir up', with_tmpl=False, err='Sorting direction must be one of')), ('stack-list-with-sort-key', dict( command='stack-list --sort-keys owner', with_tmpl=False, err='Sorting key \'owner\' not one of')), ] def test_bad_parameters(self): self.register_keystone_auth_fixture() fake_env = { 'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_TENANT_NAME': 'tenant_name', 'OS_AUTH_URL': BASE_URL, } self.set_fake_env(fake_env) cmd = self.command if self.with_tmpl: template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') cmd = '%s --template-file=%s ' % (self.command, template_file) self.shell_error(cmd, self.err, exception=exc.CommandError) class ShellValidationTest(TestCase): def test_failed_auth(self): self.register_keystone_auth_fixture() failed_msg = 'Unable to authenticate user with credentials provided' with mock.patch.object(http.SessionClient, 'request', side_effect=exc.Unauthorized(failed_msg)) as sc: self.set_fake_env(FAKE_ENV_KEYSTONE_V2) self.shell_error('stack-list', failed_msg, exception=exc.Unauthorized) sc.assert_called_once_with('/stacks?', 'GET') def test_stack_create_validation(self): self.register_keystone_auth_fixture() self.set_fake_env(FAKE_ENV_KEYSTONE_V2) self.shell_error( 'stack-create teststack ' '--parameters="InstanceType=m1.large;DBUsername=wp;' 'DBPassword=verybadpassword;KeyName=heat_key;' 'LinuxDistribution=F17"', 'Need to specify exactly one of', exception=exc.CommandError) def test_stack_create_with_paramfile_validation(self): self.register_keystone_auth_fixture() self.set_fake_env(FAKE_ENV_KEYSTONE_V2) self.shell_error( 'stack-create teststack ' '--parameter-file private_key=private_key.env ' '--parameters="InstanceType=m1.large;DBUsername=wp;' 'DBPassword=verybadpassword;KeyName=heat_key;' 'LinuxDistribution=F17"', 'Need to specify exactly one of', exception=exc.CommandError) def test_stack_create_validation_keystone_v3(self): self.register_keystone_auth_fixture() self.set_fake_env(FAKE_ENV_KEYSTONE_V3) self.shell_error( 'stack-create teststack ' '--parameters="InstanceType=m1.large;DBUsername=wp;' 'DBPassword=verybadpassword;KeyName=heat_key;' 'LinuxDistribution=F17"', 'Need to specify exactly one of', exception=exc.CommandError ) class ShellBase(TestCase): (JSON, RAW, SESSION) = ('json', 'raw', 'session') def setUp(self): super(ShellBase, self).setUp() self._calls = {self.JSON: [], self.RAW: [], self.SESSION: []} self._results = {self.JSON: [], self.RAW: [], self.SESSION: []} self.useFixture(fixtures.MockPatchObject( http.HTTPClient, 'json_request', side_effect=self._results[self.JSON])) self.useFixture(fixtures.MockPatchObject( http.HTTPClient, 'raw_request', side_effect=self._results[self.RAW])) self.useFixture(fixtures.MockPatchObject( http.SessionClient, 'request', side_effect=self._results[self.SESSION])) self.client = http.SessionClient # Some tests set exc.verbose = 1, so reset on cleanup def unset_exc_verbose(): exc.verbose = 0 self.addCleanup(unset_exc_verbose) def tearDown(self): http.HTTPClient.json_request.assert_has_calls(self._calls[self.JSON]) http.HTTPClient.raw_request.assert_has_calls(self._calls[self.RAW]) http.SessionClient.request.assert_has_calls(self._calls[self.SESSION]) super(ShellBase, self).tearDown() def shell(self, argstr): orig = sys.stdout try: sys.stdout = io.StringIO() _shell = heatclient.shell.HeatShell() _shell.main(argstr.split()) self.subcommands = _shell.subcommands.keys() except SystemExit: exc_type, exc_value, exc_traceback = sys.exc_info() self.assertEqual(0, exc_value.code) finally: out = sys.stdout.getvalue() sys.stdout.close() sys.stdout = orig return out def mock_request_error(self, path, verb, error): raw = verb == 'DELETE' if self.client == http.SessionClient: request = self.SESSION self._expect_call(request, path, verb) else: if raw: request = self.RAW else: request = self.JSON self._expect_call(request, verb, path) self._results[request].append(error) def mock_request_get(self, path, response, raw=False, **kwargs): self.mock_request(path, 'GET', response, raw=raw, **kwargs) def mock_request_delete(self, path, response=None): self.mock_request(path, 'DELETE', response, raw=True, status_code=204) def mock_request_post(self, path, response, req_headers=False, status_code=200, **kwargs): self.mock_request(path, 'POST', response=response, raw=False, status_code=status_code, req_headers=req_headers, **kwargs) def mock_request_put(self, path, response, status_code=202, **kwargs): self.mock_request(path, 'PUT', response=response, raw=False, status_code=status_code, req_headers=True, **kwargs) def mock_request_patch(self, path, response, req_headers=True, status_code=202, **kwargs): self.mock_request(path, 'PATCH', response=response, raw=False, status_code=status_code, req_headers=req_headers, **kwargs) def mock_request(self, path, verb, response=None, raw=False, status_code=200, req_headers=False, **kwargs): kwargs = dict(kwargs) if req_headers: if self.client is http.HTTPClient: kwargs['headers'] = {'X-Auth-Key': 'password', 'X-Auth-User': 'username'} else: kwargs['headers'] = {} reason = 'OK' if response: headers = {'content-type': 'application/json'} content = jsonutils.dumps(response) else: headers = {} content = None if status_code == 201: headers['location'] = 'http://heat.example.com/stacks/myStack' resp = fakes.FakeHTTPResponse(status_code, reason, headers, content) if self.client == http.SessionClient: request = self.SESSION self._results[request].append(resp) self._expect_call(request, path, verb, **kwargs) else: if raw: request = self.RAW self._results[request].append(resp) else: request = self.JSON self._results[request].append((resp, response)) self._expect_call(request, verb, path, **kwargs) def _expect_call(self, request, *args, **kwargs): self._calls[request].append(mock.call(*args, **kwargs)) def mock_stack_list(self, path=None, show_nested=False): if path is None: path = '/stacks?' resp_dict = self.stack_list_resp_dict(show_nested) self.mock_request_get(path, resp_dict) class ShellTestNoMoxBase(TestCase): # NOTE(dhu): This class is reserved for no Mox usage. Instead, # use requests_mock to expose errors from json_request. def setUp(self): super(ShellTestNoMoxBase, self).setUp() self._set_fake_env() def _set_fake_env(self): self.set_fake_env({ 'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_TENANT_NAME': 'tenant_name', 'HEAT_URL': 'http://heat.example.com', 'OS_AUTH_URL': BASE_URL, 'OS_NO_CLIENT_AUTH': 'True' }) def shell(self, argstr): orig = sys.stdout try: sys.stdout = io.StringIO() _shell = heatclient.shell.HeatShell() _shell.main(argstr.split()) self.subcommands = _shell.subcommands.keys() except SystemExit: exc_type, exc_value, exc_traceback = sys.exc_info() self.assertEqual(0, exc_value.code) finally: out = sys.stdout.getvalue() sys.stdout.close() sys.stdout = orig return out class ShellTestNoMox(ShellTestNoMoxBase): # This function tests err msg handling def test_stack_create_parameter_missing_err_msg(self): self.register_keystone_auth_fixture() resp_dict = {"error": {"message": 'The Parameter (key_name) was not provided.', "type": "UserParameterMissing"}} self.requests.post('http://heat.example.com/stacks', status_code=400, headers={'Content-Type': 'application/json'}, json=resp_dict) template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') self.shell_error('stack-create -f %s stack' % template_file, r'The Parameter \(key_name\) was not provided.', exception=exc.HTTPBadRequest) def test_event_list(self): eventid1 = uuid.uuid4().hex eventid2 = uuid.uuid4().hex self.register_keystone_auth_fixture() h = {'Content-Type': 'text/plain; charset=UTF-8', 'location': 'http://heat.example.com/stacks/myStack/60f83b5e'} self.requests.get('http://heat.example.com/stacks/myStack', status_code=302, headers=h) resp_dict = self.event_list_resp_dict( resource_name="myDeployment", rsrc_eventid1=eventid1, rsrc_eventid2=eventid2 ) self.requests.get('http://heat.example.com/stacks/myStack/60f83b5e/' 'resources/myDeployment/events', headers={'Content-Type': 'application/json'}, json=resp_dict) list_text = self.shell('event-list -r myDeployment myStack') required = [ 'resource_name', 'id', 'resource_status_reason', 'resource_status', 'event_time', 'myDeployment', eventid1, eventid2, 'state changed', 'CREATE_IN_PROGRESS', '2013-12-05T14:14:31', '2013-12-05T14:14:32', ] for r in required: self.assertRegex(list_text, r) class ShellTestNoMoxV3(ShellTestNoMox): def _set_fake_env(self): fake_env_kwargs = {'OS_NO_CLIENT_AUTH': 'True', 'HEAT_URL': 'http://heat.example.com'} fake_env_kwargs.update(FAKE_ENV_KEYSTONE_V3) self.set_fake_env(fake_env_kwargs) class ShellTestEndpointType(TestCase): def setUp(self): super(ShellTestEndpointType, self).setUp() self.useFixture(fixtures.MockPatchObject(http, '_construct_http_client')) self.useFixture(fixtures.MockPatchObject(heatclient.v1.shell, 'do_stack_list')) self.set_fake_env(FAKE_ENV_KEYSTONE_V2) def test_endpoint_type_public_url(self): self.register_keystone_auth_fixture() kwargs = { 'auth_url': 'http://keystone.example.com:5000/', 'session': mock.ANY, 'auth': mock.ANY, 'service_type': 'orchestration', 'endpoint_type': 'publicURL', 'region_name': '', 'username': 'username', 'password': 'password', 'include_pass': False, 'endpoint_override': mock.ANY, } heatclient.shell.main(('stack-list',)) http._construct_http_client.assert_called_once_with(**kwargs) heatclient.v1.shell.do_stack_list.assert_called_once() def test_endpoint_type_admin_url(self): self.register_keystone_auth_fixture() kwargs = { 'auth_url': 'http://keystone.example.com:5000/', 'session': mock.ANY, 'auth': mock.ANY, 'service_type': 'orchestration', 'endpoint_type': 'adminURL', 'region_name': '', 'username': 'username', 'password': 'password', 'include_pass': False, 'endpoint_override': mock.ANY, } heatclient.shell.main(('--os-endpoint-type=adminURL', 'stack-list',)) http._construct_http_client.assert_called_once_with(**kwargs) heatclient.v1.shell.do_stack_list.assert_called_once() def test_endpoint_type_internal_url(self): self.register_keystone_auth_fixture() self.useFixture(fixtures.EnvironmentVariable('OS_ENDPOINT_TYPE', 'internalURL')) kwargs = { 'auth_url': 'http://keystone.example.com:5000/', 'session': mock.ANY, 'auth': mock.ANY, 'service_type': 'orchestration', 'endpoint_type': 'internalURL', 'region_name': '', 'username': 'username', 'password': 'password', 'include_pass': False, 'endpoint_override': mock.ANY, } heatclient.shell.main(('stack-list',)) http._construct_http_client.assert_called_once_with(**kwargs) heatclient.v1.shell.do_stack_list.assert_called_once() class ShellTestCommon(ShellBase): def setUp(self): super(ShellTestCommon, self).setUp() self.client = http.SessionClient self.set_fake_env(FAKE_ENV_KEYSTONE_V2) def test_help_unknown_command(self): self.assertRaises(exc.CommandError, self.shell, 'help foofoo') def test_help(self): required = [ '^usage: heat', '(?m)^See "heat help COMMAND" for help on a specific command', ] for argstr in ['--help', 'help']: help_text = self.shell(argstr) for r in required: self.assertRegex(help_text, r) def test_command_help(self): output = self.shell('help help') self.assertIn('usage: heat help []', output) subcommands = list(self.subcommands) for command in subcommands: if command.replace('_', '-') == 'bash-completion': continue output1 = self.shell('help %s' % command) output2 = self.shell('%s --help' % command) self.assertEqual(output1, output2) self.assertRegex(output1, '^usage: heat %s' % command) def test_debug_switch_raises_error(self): self.register_keystone_auth_fixture() self.mock_request_error('/stacks?', 'GET', exc.Unauthorized("FAIL")) args = ['--debug', 'stack-list'] self.assertRaises(exc.Unauthorized, heatclient.shell.main, args) def test_dash_d_switch_raises_error(self): self.register_keystone_auth_fixture() self.mock_request_error('/stacks?', 'GET', exc.CommandError("FAIL")) args = ['-d', 'stack-list'] self.assertRaises(exc.CommandError, heatclient.shell.main, args) def test_no_debug_switch_no_raises_errors(self): self.register_keystone_auth_fixture() self.mock_request_error('/stacks?', 'GET', exc.Unauthorized("FAIL")) args = ['stack-list'] self.assertRaises(SystemExit, heatclient.shell.main, args) def test_help_on_subcommand(self): required = [ '^usage: heat stack-list', "(?m)^List the user's stacks", ] argstrings = [ 'help stack-list', ] for argstr in argstrings: help_text = self.shell(argstr) for r in required: self.assertRegex(help_text, r) class ShellTestUserPass(ShellBase): def setUp(self): super(ShellTestUserPass, self).setUp() if self.client is None: self.client = http.SessionClient self._set_fake_env() def _set_fake_env(self): self.set_fake_env(FAKE_ENV_KEYSTONE_V2) def test_stack_list(self): self.register_keystone_auth_fixture() self.mock_stack_list() list_text = self.shell('stack-list') required = [ 'id', 'stack_status', 'creation_time', 'teststack', '1', 'CREATE_COMPLETE', 'IN_PROGRESS', ] for r in required: self.assertRegex(list_text, r) self.assertNotRegex(list_text, 'parent') def test_stack_list_show_nested(self): self.register_keystone_auth_fixture() expected_url = '/stacks?%s' % parse.urlencode({ 'show_nested': True, }, True) self.mock_stack_list(expected_url, show_nested=True) list_text = self.shell('stack-list' ' --show-nested') required = [ 'teststack', 'teststack2', 'teststack_nested', 'parent', 'theparentof3' ] for r in required: self.assertRegex(list_text, r) def test_stack_list_show_owner(self): self.register_keystone_auth_fixture() self.mock_stack_list() list_text = self.shell('stack-list --show-owner') required = [ 'stack_owner', 'testowner', ] for r in required: self.assertRegex(list_text, r) def test_parsable_error(self): self.register_keystone_auth_fixture() message = "The Stack (bad) could not be found." self.mock_request_error('/stacks/bad', 'GET', exc.HTTPBadRequest(message)) e = self.assertRaises(exc.HTTPException, self.shell, "stack-show bad") self.assertEqual("ERROR: " + message, str(e)) def test_parsable_verbose(self): self.register_keystone_auth_fixture() message = "The Stack (bad) could not be found." self.mock_request_error('/stacks/bad', 'GET', exc.HTTPBadRequest(message)) exc.verbose = 1 e = self.assertRaises(exc.HTTPException, self.shell, "stack-show bad") self.assertIn(message, str(e)) def test_parsable_malformed_error(self): self.register_keystone_auth_fixture() invalid_json = "ERROR: {Invalid JSON Error." self.mock_request_error('/stacks/bad', 'GET', exc.HTTPBadRequest(invalid_json)) e = self.assertRaises(exc.HTTPException, self.shell, "stack-show bad") self.assertEqual("ERROR: " + invalid_json, str(e)) def test_parsable_malformed_error_missing_message(self): self.register_keystone_auth_fixture() message = 'Internal Error' self.mock_request_error('/stacks/bad', 'GET', exc.HTTPBadRequest(message)) e = self.assertRaises(exc.HTTPException, self.shell, "stack-show bad") self.assertEqual("ERROR: Internal Error", str(e)) def test_parsable_malformed_error_missing_traceback(self): self.register_keystone_auth_fixture() message = "The Stack (bad) could not be found." self.mock_request_error('/stacks/bad', 'GET', exc.HTTPBadRequest(message)) exc.verbose = 1 e = self.assertRaises(exc.HTTPException, self.shell, "stack-show bad") self.assertEqual("ERROR: The Stack (bad) could not be found.\n", str(e)) def test_stack_show(self): self.register_keystone_auth_fixture() resp_dict = {"stack": { "id": "1", "stack_name": "teststack", "stack_status": 'CREATE_COMPLETE', "creation_time": "2012-10-25T01:58:47Z", "tags": [u'tag1', u'tag2'] }} self.mock_request_get('/stacks/teststack/1', resp_dict) list_text = self.shell('stack-show teststack/1') required = [ 'id', 'stack_name', 'stack_status', 'creation_time', 'tags', 'teststack', 'CREATE_COMPLETE', '2012-10-25T01:58:47Z', "['tag1', 'tag2']", ] for r in required: self.assertRegex(list_text, r) def test_stack_show_without_outputs(self): self.register_keystone_auth_fixture() resp_dict = {"stack": { "id": "1", "stack_name": "teststack", "stack_status": 'CREATE_COMPLETE', "creation_time": "2012-10-25T01:58:47Z" }} params = {'resolve_outputs': False} self.mock_request_get('/stacks/teststack/1', resp_dict, params=params) list_text = self.shell( 'stack-show teststack/1 --no-resolve-outputs') required = [ 'id', 'stack_name', 'stack_status', 'creation_time', 'teststack', 'CREATE_COMPLETE', '2012-10-25T01:58:47Z' ] for r in required: self.assertRegex(list_text, r) def _output_fake_response(self, output_key): outputs = [ { "output_value": "value1", "output_key": "output1", "description": "test output 1", }, { "output_value": ["output", "value", "2"], "output_key": "output2", "description": "test output 2", }, { "output_value": u"test\u2665", "output_key": "output_uni", "description": "test output unicode", }, ] def find_output(key): for out in outputs: if out['output_key'] == key: return {'output': out} self.mock_request_get('/stacks/teststack/1/outputs/%s' % output_key, find_output(output_key)) def _error_output_fake_response(self, output_key): resp_dict = { "output": { "output_value": "null", "output_key": "output1", "description": "test output 1", "output_error": "The Referenced Attribute (0 PublicIP) " "is incorrect." } } self.mock_request_get('/stacks/teststack/1/outputs/%s' % output_key, resp_dict) def test_template_show_cfn(self): self.register_keystone_auth_fixture() template_data = open(os.path.join(TEST_VAR_DIR, 'minimal.template')).read() resp_dict = jsonutils.loads(template_data) self.mock_request_get('/stacks/teststack/template', resp_dict) show_text = self.shell('template-show teststack') required = [ '{', ' "AWSTemplateFormatVersion": "2010-09-09"', ' "Outputs": {}', ' "Resources": {}', ' "Parameters": {}', '}' ] for r in required: self.assertRegex(show_text, r) def test_template_show_cfn_unicode(self): self.register_keystone_auth_fixture() resp_dict = {"AWSTemplateFormatVersion": "2010-09-09", "Description": u"test\u2665", "Outputs": {}, "Resources": {}, "Parameters": {}} self.mock_request_get('/stacks/teststack/template', resp_dict) show_text = self.shell('template-show teststack') required = [ '{', ' "AWSTemplateFormatVersion": "2010-09-09"', ' "Outputs": {}', ' "Parameters": {}', u' "Description": "test\u2665"', ' "Resources": {}', '}' ] for r in required: self.assertRegex(show_text, r) def test_template_show_hot(self): self.register_keystone_auth_fixture() resp_dict = {"heat_template_version": "2013-05-23", "parameters": {}, "resources": {}, "outputs": {}} self.mock_request_get('/stacks/teststack/template', resp_dict) show_text = self.shell('template-show teststack') required = [ "heat_template_version: '2013-05-23'", "outputs: {}", "parameters: {}", "resources: {}" ] for r in required: self.assertRegex(show_text, r) def test_template_validate(self): self.register_keystone_auth_fixture() resp_dict = {"heat_template_version": "2013-05-23", "parameters": {}, "resources": {}, "outputs": {}} self.mock_request_post('/validate', resp_dict, data=mock.ANY) template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') cmd = 'template-validate -f %s -P foo=bar' % template_file show_text = self.shell(cmd) required = [ 'heat_template_version', 'outputs', 'parameters', 'resources' ] for r in required: self.assertRegex(show_text, r) def _test_stack_preview(self, timeout=None, enable_rollback=False, tags=None): self.register_keystone_auth_fixture() resp_dict = {"stack": { "id": "1", "stack_name": "teststack", "stack_status": 'CREATE_COMPLETE', "resources": {'1': {'name': 'r1'}}, "creation_time": "2012-10-25T01:58:47Z", "timeout_mins": timeout, "disable_rollback": not(enable_rollback), "tags": tags }} self.mock_request_post('/stacks/preview', resp_dict, data=mock.ANY, req_headers=True) template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') cmd = ('stack-preview teststack ' '--template-file=%s ' '--parameters="InstanceType=m1.large;DBUsername=wp;' 'DBPassword=verybadpassword;KeyName=heat_key;' 'LinuxDistribution=F17" ' % template_file) if enable_rollback: cmd += '-r ' if timeout: cmd += '--timeout=%d ' % timeout if tags: cmd += '--tags=%s ' % tags preview_text = self.shell(cmd) required = [ 'stack_name', 'id', 'teststack', '1', 'resources', 'timeout_mins', 'disable_rollback', 'tags' ] for r in required: self.assertRegex(preview_text, r) def test_stack_preview(self): self._test_stack_preview() def test_stack_preview_timeout(self): self._test_stack_preview(300, True) def test_stack_preview_tags(self): self._test_stack_preview(tags='tag1,tag2') def test_stack_create(self): self.register_keystone_auth_fixture() self.mock_request_post('/stacks', None, data=mock.ANY, status_code=201, req_headers=True) self.mock_stack_list() template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') create_text = self.shell( 'stack-create teststack ' '--template-file=%s ' '--parameters="InstanceType=m1.large;DBUsername=wp;' 'DBPassword=verybadpassword;KeyName=heat_key;' 'LinuxDistribution=F17"' % template_file) required = [ 'stack_name', 'id', 'teststack', '1' ] for r in required: self.assertRegex(create_text, r) def test_create_success_with_poll(self): self.register_keystone_auth_fixture() stack_create_resp_dict = {"stack": { "id": "teststack2/2", "stack_name": "teststack2", "stack_status": 'CREATE_IN_PROGRESS', "creation_time": "2012-10-25T01:58:47Z" }} self.mock_request_post('/stacks', stack_create_resp_dict, data=mock.ANY, req_headers=True, status_code=201) self.mock_stack_list() stack_show_resp_dict = {"stack": { "id": "1", "stack_name": "teststack", "stack_status": 'CREATE_COMPLETE', "creation_time": "2012-10-25T01:58:47Z" }} event_list_resp_dict = self.event_list_resp_dict( stack_name="teststack2") stack_id = 'teststack2' self.mock_request_get('/stacks/teststack2', stack_show_resp_dict) self.mock_request_get('/stacks/%s/events?sort_dir=asc' % stack_id, event_list_resp_dict) self.mock_request_get('/stacks/teststack2', stack_show_resp_dict) template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') create_text = self.shell( 'stack-create teststack2 ' '--poll 4 ' '--template-file=%s ' '--parameters="InstanceType=m1.large;DBUsername=wp;' 'DBPassword=verybadpassword;KeyName=heat_key;' 'LinuxDistribution=F17"' % template_file) required = [ 'id', 'stack_name', 'stack_status', '2', 'teststack2', 'IN_PROGRESS', '14:14:30', '2013-12-05', 'CREATE_IN_PROGRESS', 'state changed', '14:14:31', 'testresource', '14:14:32', 'CREATE_COMPLETE', '14:14:33', ] for r in required: self.assertRegex(create_text, r) def test_create_failed_with_poll(self): self.register_keystone_auth_fixture() stack_create_resp_dict = {"stack": { "id": "teststack2/2", "stack_name": "teststack2", "stack_status": 'CREATE_IN_PROGRESS', "creation_time": "2012-10-25T01:58:47Z" }} self.mock_request_post('/stacks', stack_create_resp_dict, data=mock.ANY, req_headers=True, status_code=201) self.mock_stack_list() stack_show_resp_dict = {"stack": { "id": "1", "stack_name": "teststack", "stack_status": 'CREATE_COMPLETE', "creation_time": "2012-10-25T01:58:47Z" }} event_list_resp_dict = self.event_list_resp_dict( stack_name="teststack2", final_state="FAILED") stack_id = 'teststack2' self.mock_request_get('/stacks/teststack2', stack_show_resp_dict) self.mock_request_get('/stacks/%s/events?sort_dir=asc' % stack_id, event_list_resp_dict) self.mock_request_get('/stacks/teststack2', stack_show_resp_dict) template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') e = self.assertRaises(exc.StackFailure, self.shell, 'stack-create teststack2 --poll ' '--template-file=%s --parameters="InstanceType=' 'm1.large;DBUsername=wp;DBPassword=password;' 'KeyName=heat_key;LinuxDistribution=F17' % template_file) self.assertEqual("\n Stack teststack2 CREATE_FAILED \n", str(e)) def test_stack_create_param_file(self): self.register_keystone_auth_fixture() self.mock_request_post('/stacks', None, data=mock.ANY, status_code=201, req_headers=True) self.mock_stack_list() self.useFixture(fixtures.MockPatchObject(utils, 'read_url_content', return_value='xxxxxx')) url = 'file://%s/private_key.env' % TEST_VAR_DIR template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') create_text = self.shell( 'stack-create teststack ' '--template-file=%s ' '--parameter-file private_key=private_key.env ' '--parameters="InstanceType=m1.large;DBUsername=wp;' 'DBPassword=verybadpassword;KeyName=heat_key;' 'LinuxDistribution=F17"' % template_file) required = [ 'stack_name', 'id', 'teststack', '1' ] for r in required: self.assertRegex(create_text, r) utils.read_url_content.assert_called_once_with(url) def test_stack_create_only_param_file(self): self.register_keystone_auth_fixture() self.mock_request_post('/stacks', None, data=mock.ANY, status_code=201, req_headers=True) self.mock_stack_list() self.useFixture(fixtures.MockPatchObject(utils, 'read_url_content', return_value='xxxxxx')) url = 'file://%s/private_key.env' % TEST_VAR_DIR template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') create_text = self.shell( 'stack-create teststack ' '--template-file=%s ' '--parameter-file private_key=private_key.env ' % template_file) required = [ 'stack_name', 'id', 'teststack', '1' ] for r in required: self.assertRegex(create_text, r) utils.read_url_content.assert_called_once_with(url) def test_stack_create_timeout(self): self.register_keystone_auth_fixture() template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') template_data = open(template_file).read() expected_data = { 'files': {}, 'disable_rollback': True, 'parameters': {'DBUsername': 'wp', 'KeyName': 'heat_key', 'LinuxDistribution': 'F17"', '"InstanceType': 'm1.large', 'DBPassword': 'verybadpassword'}, 'stack_name': 'teststack', 'environment': {}, 'template': jsonutils.loads(template_data), 'timeout_mins': 123} self.mock_request_post('/stacks', None, data=expected_data, status_code=201, req_headers=True) self.mock_stack_list() create_text = self.shell( 'stack-create teststack ' '--template-file=%s ' '--timeout=123 ' '--parameters="InstanceType=m1.large;DBUsername=wp;' 'DBPassword=verybadpassword;KeyName=heat_key;' 'LinuxDistribution=F17"' % template_file) required = [ 'stack_name', 'id', 'teststack', '1' ] for r in required: self.assertRegex(create_text, r) def test_stack_update_timeout(self): self.register_keystone_auth_fixture() template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') template_data = open(template_file).read() expected_data = { 'files': {}, 'environment': {}, 'template': jsonutils.loads(template_data), 'parameters': {'DBUsername': 'wp', 'KeyName': 'heat_key', 'LinuxDistribution': 'F17"', '"InstanceType': 'm1.large', 'DBPassword': 'verybadpassword'}, 'timeout_mins': 123, 'disable_rollback': True} self.mock_request_put( '/stacks/teststack2/2', 'The request is accepted for processing.', data=expected_data) self.mock_stack_list() update_text = self.shell( 'stack-update teststack2/2 ' '--template-file=%s ' '--timeout 123 ' '--rollback off ' '--parameters="InstanceType=m1.large;DBUsername=wp;' 'DBPassword=verybadpassword;KeyName=heat_key;' 'LinuxDistribution=F17"' % template_file) required = [ 'stack_name', 'id', 'teststack2', '1' ] for r in required: self.assertRegex(update_text, r) def test_stack_create_url(self): self.register_keystone_auth_fixture() url_content = io.StringIO( '{"AWSTemplateFormatVersion" : "2010-09-09"}') self.useFixture(fixtures.MockPatchObject(request, 'urlopen', return_value=url_content)) expected_data = { 'files': {}, 'disable_rollback': True, 'stack_name': 'teststack', 'environment': {}, 'template': {"AWSTemplateFormatVersion": "2010-09-09"}, 'parameters': {'DBUsername': 'wp', 'KeyName': 'heat_key', 'LinuxDistribution': 'F17"', '"InstanceType': 'm1.large', 'DBPassword': 'verybadpassword'}} self.mock_request_post('/stacks', None, data=expected_data, status_code=201, req_headers=True) self.mock_stack_list() create_text = self.shell( 'stack-create teststack ' '--template-url=http://no.where/minimal.template ' '--parameters="InstanceType=m1.large;DBUsername=wp;' 'DBPassword=verybadpassword;KeyName=heat_key;' 'LinuxDistribution=F17"') required = [ 'stack_name', 'id', 'teststack2', '2' ] for r in required: self.assertRegex(create_text, r) request.urlopen.assert_called_once_with( 'http://no.where/minimal.template') def test_stack_create_object(self): self.register_keystone_auth_fixture() template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') template_data = open(template_file).read() self.mock_request_get( 'http://no.where/container/minimal.template', template_data, raw=True) self.mock_request_post('/stacks', None, data=mock.ANY, status_code=201, req_headers=True) self.mock_stack_list() create_text = self.shell( 'stack-create teststack2 ' '--template-object=http://no.where/container/minimal.template ' '--parameters="InstanceType=m1.large;DBUsername=wp;' 'DBPassword=verybadpassword;KeyName=heat_key;' 'LinuxDistribution=F17"') required = [ 'stack_name', 'id', 'teststack2', '2' ] for r in required: self.assertRegex(create_text, r) def test_stack_create_with_tags(self): self.register_keystone_auth_fixture() template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') template_data = open(template_file).read() expected_data = { 'files': {}, 'disable_rollback': True, 'parameters': {'DBUsername': 'wp', 'KeyName': 'heat_key', 'LinuxDistribution': 'F17"', '"InstanceType': 'm1.large', 'DBPassword': 'verybadpassword'}, 'stack_name': 'teststack', 'environment': {}, 'template': jsonutils.loads(template_data), 'tags': 'tag1,tag2'} self.mock_request_post('/stacks', None, data=expected_data, status_code=201, req_headers=True) self.mock_stack_list() create_text = self.shell( 'stack-create teststack ' '--template-file=%s ' '--tags=tag1,tag2 ' '--parameters="InstanceType=m1.large;DBUsername=wp;' 'DBPassword=verybadpassword;KeyName=heat_key;' 'LinuxDistribution=F17"' % template_file) required = [ 'stack_name', 'id', 'teststack', '1' ] for r in required: self.assertRegex(create_text, r) def test_stack_abandon(self): self.register_keystone_auth_fixture() abandoned_stack = { "action": "CREATE", "status": "COMPLETE", "name": "teststack", "id": "1", "resources": { "foo": { "name": "foo", "resource_id": "test-res-id", "action": "CREATE", "status": "COMPLETE", "resource_data": {}, "metadata": {}, } } } self.mock_request_delete('/stacks/teststack/1/abandon', abandoned_stack) abandon_resp = self.shell('stack-abandon teststack/1') self.assertEqual(abandoned_stack, jsonutils.loads(abandon_resp)) def test_stack_abandon_with_outputfile(self): self.register_keystone_auth_fixture() abandoned_stack = { "action": "CREATE", "status": "COMPLETE", "name": "teststack", "id": "1", "resources": { "foo": { "name": "foo", "resource_id": "test-res-id", "action": "CREATE", "status": "COMPLETE", "resource_data": {}, "metadata": {}, } } } self.mock_request_delete('/stacks/teststack/1/abandon', abandoned_stack) with tempfile.NamedTemporaryFile() as file_obj: self.shell('stack-abandon teststack/1 -O %s' % file_obj.name) result = jsonutils.loads(file_obj.read().decode()) self.assertEqual(abandoned_stack, result) def test_stack_adopt(self): self.register_keystone_auth_fixture() self.mock_request_post('/stacks', None, data=mock.ANY, status_code=201, req_headers=True) self.mock_stack_list() adopt_data_file = os.path.join(TEST_VAR_DIR, 'adopt_stack_data.json') adopt_text = self.shell( 'stack-adopt teststack ' '--adopt-file=%s ' '--parameters="InstanceType=m1.large;DBUsername=wp;' 'DBPassword=verybadpassword;KeyName=heat_key;' 'LinuxDistribution=F17"' % (adopt_data_file)) required = [ 'stack_name', 'id', 'teststack', '1' ] for r in required: self.assertRegex(adopt_text, r) def test_stack_adopt_with_environment(self): self.register_keystone_auth_fixture() self.mock_request_post('/stacks', None, data=mock.ANY, status_code=201, req_headers=True) self.mock_stack_list() adopt_data_file = os.path.join(TEST_VAR_DIR, 'adopt_stack_data.json') environment_file = os.path.join(TEST_VAR_DIR, 'environment.json') self.shell( 'stack-adopt teststack ' '--adopt-file=%s ' '--environment-file=%s' % (adopt_data_file, environment_file)) def test_stack_adopt_without_data(self): self.register_keystone_auth_fixture() failed_msg = 'Need to specify --adopt-file' self.shell_error('stack-adopt teststack ', failed_msg, exception=exc.CommandError) def test_stack_adopt_empty_data_file(self): failed_msg = 'Invalid adopt-file, no data!' self.register_keystone_auth_fixture() with tempfile.NamedTemporaryFile() as file_obj: self.shell_error( 'stack-adopt teststack ' '--adopt-file=%s ' % (file_obj.name), failed_msg, exception=exc.CommandError) def test_stack_update_enable_rollback(self): self.register_keystone_auth_fixture() template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') with open(template_file, 'rb') as f: template_data = jsonutils.load(f) expected_data = {'files': {}, 'environment': {}, 'template': template_data, 'disable_rollback': False, 'parameters': mock.ANY } self.mock_request_put( '/stacks/teststack2/2', 'The request is accepted for processing.', data=expected_data) self.mock_stack_list() update_text = self.shell( 'stack-update teststack2/2 ' '--rollback on ' '--template-file=%s ' '--parameters="InstanceType=m1.large;DBUsername=wp;' 'DBPassword=verybadpassword;KeyName=heat_key;' 'LinuxDistribution=F17"' % template_file) required = [ 'stack_name', 'id', 'teststack2', '1' ] for r in required: self.assertRegex(update_text, r) def test_stack_update_disable_rollback(self): self.register_keystone_auth_fixture() template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') with open(template_file, 'rb') as f: template_data = jsonutils.load(f) expected_data = {'files': {}, 'environment': {}, 'template': template_data, 'disable_rollback': True, 'parameters': mock.ANY } self.mock_request_put( '/stacks/teststack2', 'The request is accepted for processing.', data=expected_data) self.mock_stack_list() update_text = self.shell( 'stack-update teststack2 ' '--template-file=%s ' '--rollback off ' '--parameters="InstanceType=m1.large;DBUsername=wp;' 'DBPassword=verybadpassword;KeyName=heat_key;' 'LinuxDistribution=F17"' % template_file) required = [ 'stack_name', 'id', 'teststack2', '1' ] for r in required: self.assertRegex(update_text, r) def test_stack_update_fault_rollback_value(self): self.register_keystone_auth_fixture() template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') self.shell_error('stack-update teststack2/2 ' '--rollback Foo ' '--template-file=%s' % template_file, "Unrecognized value 'Foo', acceptable values are:", exception=exc.CommandError ) def test_stack_update_rollback_default(self): self.register_keystone_auth_fixture() template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') with open(template_file, 'rb') as f: template_data = jsonutils.load(f) expected_data = {'files': {}, 'environment': {}, 'template': template_data, 'parameters': mock.ANY } self.mock_request_put( '/stacks/teststack2', 'The request is accepted for processing.', data=expected_data) self.mock_stack_list() update_text = self.shell( 'stack-update teststack2 ' '--template-file=%s ' '--parameters="InstanceType=m1.large;DBUsername=wp;' 'DBPassword=verybadpassword;KeyName=heat_key;' 'LinuxDistribution=F17"' % template_file) required = [ 'stack_name', 'id', 'teststack2', '2' ] for r in required: self.assertRegex(update_text, r) def test_stack_update_with_existing_parameters(self): self.register_keystone_auth_fixture() template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') template_data = open(template_file).read() expected_data = { 'files': {}, 'environment': {}, 'template': jsonutils.loads(template_data), 'parameters': {}, 'disable_rollback': False} self.mock_request_patch( '/stacks/teststack2/2', 'The request is accepted for processing.', data=expected_data ) self.mock_stack_list() update_text = self.shell( 'stack-update teststack2/2 ' '--template-file=%s ' '--enable-rollback ' '--existing' % template_file) required = [ 'stack_name', 'id', 'teststack2', '1' ] for r in required: self.assertRegex(update_text, r) def test_stack_update_with_patched_existing_parameters(self): self.register_keystone_auth_fixture() template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') template_data = open(template_file).read() expected_data = { 'files': {}, 'environment': {}, 'template': jsonutils.loads(template_data), 'parameters': {'"KeyPairName': 'updated_key"'}, 'disable_rollback': False} self.mock_request_patch( '/stacks/teststack2/2', 'The request is accepted for processing.', data=expected_data ) self.mock_stack_list() update_text = self.shell( 'stack-update teststack2/2 ' '--template-file=%s ' '--enable-rollback ' '--parameters="KeyPairName=updated_key" ' '--existing' % template_file) required = [ 'stack_name', 'id', 'teststack2', '1' ] for r in required: self.assertRegex(update_text, r) def test_stack_update_with_existing_and_default_parameters(self): self.register_keystone_auth_fixture() template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') template_data = open(template_file).read() expected_data = { 'files': {}, 'environment': {}, 'template': jsonutils.loads(template_data), 'parameters': {}, 'clear_parameters': ['InstanceType', 'DBUsername', 'DBPassword', 'KeyPairName', 'LinuxDistribution'], 'disable_rollback': False} self.mock_request_patch( '/stacks/teststack2/2', 'The request is accepted for processing.', data=expected_data ) self.mock_stack_list() update_text = self.shell( 'stack-update teststack2/2 ' '--template-file=%s ' '--enable-rollback ' '--existing ' '--clear-parameter=InstanceType ' '--clear-parameter=DBUsername ' '--clear-parameter=DBPassword ' '--clear-parameter=KeyPairName ' '--clear-parameter=LinuxDistribution' % template_file) required = [ 'stack_name', 'id', 'teststack2', '1' ] for r in required: self.assertRegex(update_text, r) def test_stack_update_with_patched_and_default_parameters(self): self.register_keystone_auth_fixture() template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') template_data = open(template_file).read() expected_data = { 'files': {}, 'environment': {}, 'template': jsonutils.loads(template_data), 'parameters': {'"KeyPairName': 'updated_key"'}, 'clear_parameters': ['InstanceType', 'DBUsername', 'DBPassword', 'KeyPairName', 'LinuxDistribution'], 'disable_rollback': False} self.mock_request_patch( '/stacks/teststack2/2', 'The request is accepted for processing.', data=expected_data ) self.mock_stack_list() update_text = self.shell( 'stack-update teststack2/2 ' '--template-file=%s ' '--enable-rollback ' '--existing ' '--parameters="KeyPairName=updated_key" ' '--clear-parameter=InstanceType ' '--clear-parameter=DBUsername ' '--clear-parameter=DBPassword ' '--clear-parameter=KeyPairName ' '--clear-parameter=LinuxDistribution' % template_file) required = [ 'stack_name', 'id', 'teststack2', '1' ] for r in required: self.assertRegex(update_text, r) def test_stack_update_with_existing_template(self): self.register_keystone_auth_fixture() expected_data = { 'files': {}, 'environment': {}, 'template': None, 'parameters': {}} self.mock_request_patch( '/stacks/teststack2/2', 'The request is accepted for processing.', data=expected_data ) self.mock_stack_list() update_text = self.shell( 'stack-update teststack2/2 ' '--existing') required = [ 'stack_name', 'id', 'teststack2', '1' ] for r in required: self.assertRegex(update_text, r) def test_stack_update_with_tags(self): self.register_keystone_auth_fixture() template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') template_data = open(template_file).read() expected_data = { 'files': {}, 'environment': {}, 'template': jsonutils.loads(template_data), 'parameters': {'"KeyPairName': 'updated_key"'}, 'disable_rollback': False, 'tags': 'tag1,tag2'} self.mock_request_patch( '/stacks/teststack2/2', 'The request is accepted for processing.', data=expected_data ) self.mock_stack_list() update_text = self.shell( 'stack-update teststack2/2 ' '--template-file=%s ' '--enable-rollback ' '--existing ' '--parameters="KeyPairName=updated_key" ' '--tags=tag1,tag2 ' % template_file) required = [ 'stack_name', 'id', 'teststack2', '1' ] for r in required: self.assertRegex(update_text, r) def _setup_stubs_update_dry_run(self, template_file, existing=False, show_nested=False): self.register_keystone_auth_fixture() template_data = open(template_file).read() replaced_res = {"resource_name": "my_res", "resource_identity": {"stack_name": "teststack2", "stack_id": "2", "tenant": "1234", "path": "/resources/my_res"}, "description": "", "stack_identity": {"stack_name": "teststack2", "stack_id": "2", "tenant": "1234", "path": ""}, "stack_name": "teststack2", "creation_time": "2015-08-19T19:43:34.025507", "resource_status": "COMPLETE", "updated_time": "2015-08-19T19:43:34.025507", "resource_type": "OS::Heat::RandomString", "required_by": [], "resource_status_reason": "", "physical_resource_id": "", "attributes": {"value": None}, "resource_action": "INIT", "metadata": {}} resp_dict = {"resource_changes": {"deleted": [], "unchanged": [], "added": [], "replaced": [replaced_res], "updated": []}} expected_data = { 'files': {}, 'environment': {}, 'template': jsonutils.loads(template_data), 'parameters': {'"KeyPairName': 'updated_key"'}, 'disable_rollback': False} if show_nested: path = '/stacks/teststack2/2/preview?show_nested=True' else: path = '/stacks/teststack2/2/preview' if existing: self.mock_request_patch(path, resp_dict, data=expected_data) else: self.mock_request_put(path, resp_dict, data=expected_data) def test_stack_update_dry_run(self): template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') self._setup_stubs_update_dry_run(template_file) update_preview_text = self.shell( 'stack-update teststack2/2 ' '--template-file=%s ' '--enable-rollback ' '--parameters="KeyPairName=updated_key" ' '--dry-run ' % template_file) required = [ 'stack_name', 'id', 'teststack2', '2', 'state', 'replaced' ] for r in required: self.assertRegex(update_preview_text, r) def test_stack_update_dry_run_show_nested(self): template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') self._setup_stubs_update_dry_run(template_file, show_nested=True) update_preview_text = self.shell( 'stack-update teststack2/2 ' '--template-file=%s ' '--enable-rollback ' '--show-nested ' '--parameters="KeyPairName=updated_key" ' '--dry-run ' % template_file) required = [ 'stack_name', 'id', 'teststack2', '2', 'state', 'replaced' ] for r in required: self.assertRegex(update_preview_text, r) def test_stack_update_dry_run_patch(self): template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') self._setup_stubs_update_dry_run(template_file, existing=True) update_preview_text = self.shell( 'stack-update teststack2/2 ' '--template-file=%s ' '--enable-rollback ' '--existing ' '--parameters="KeyPairName=updated_key" ' '--dry-run ' % template_file) required = [ 'stack_name', 'id', 'teststack2', '2', 'state', 'replaced' ] for r in required: self.assertRegex(update_preview_text, r) # the main thing this @mock.patch is doing here is keeping # sys.stdin untouched for later tests @mock.patch('sys.stdin', new_callable=io.StringIO) def test_stack_delete_prompt_with_tty(self, ms): self.register_keystone_auth_fixture() mock_stdin = mock.Mock() mock_stdin.isatty = mock.Mock() mock_stdin.isatty.return_value = True mock_stdin.readline = mock.Mock() mock_stdin.readline.return_value = 'n' mock_stdin.fileno.return_value = 0 sys.stdin = mock_stdin self.mock_request_delete('/stacks/teststack2/2', None) resp = self.shell('stack-delete teststack2/2') resp_text = 'Are you sure you want to delete this stack(s) [y/N]? ' self.assertEqual(resp_text, resp) mock_stdin.readline.return_value = 'y' resp = self.shell('stack-delete teststack2/2') msg = 'Request to delete stack teststack2/2 has been accepted.' self.assertRegex(resp, msg) # the main thing this @mock.patch is doing here is keeping # sys.stdin untouched for later tests @mock.patch('sys.stdin', new_callable=io.StringIO) def test_stack_delete_prompt_with_tty_y(self, ms): self.register_keystone_auth_fixture() mock_stdin = mock.Mock() mock_stdin.isatty = mock.Mock() mock_stdin.isatty.return_value = True mock_stdin.readline = mock.Mock() mock_stdin.readline.return_value = '' mock_stdin.fileno.return_value = 0 sys.stdin = mock_stdin self.mock_request_delete('/stacks/teststack2/2') # -y from the shell should skip the n/y prompt resp = self.shell('stack-delete -y teststack2/2') msg = 'Request to delete stack teststack2/2 has been accepted.' self.assertRegex(resp, msg) def test_stack_delete(self): self.register_keystone_auth_fixture() self.mock_request_delete('/stacks/teststack2/2') resp = self.shell('stack-delete teststack2/2') msg = 'Request to delete stack teststack2/2 has been accepted.' self.assertRegex(resp, msg) def test_stack_delete_multiple(self): self.register_keystone_auth_fixture() self.mock_request_delete('/stacks/teststack/1') self.mock_request_delete('/stacks/teststack2/2') resp = self.shell('stack-delete teststack/1 teststack2/2') msg1 = 'Request to delete stack teststack/1 has been accepted.' msg2 = 'Request to delete stack teststack2/2 has been accepted.' self.assertRegex(resp, msg1) self.assertRegex(resp, msg2) def test_stack_delete_failed_on_notfound(self): self.register_keystone_auth_fixture() self.mock_request_error('/stacks/teststack1/1', 'DELETE', exc.HTTPNotFound()) error = self.assertRaises( exc.CommandError, self.shell, 'stack-delete teststack1/1') self.assertIn('Unable to delete 1 of the 1 stacks.', str(error)) def test_stack_delete_failed_on_forbidden(self): self.register_keystone_auth_fixture() self.mock_request_error('/stacks/teststack1/1', 'DELETE', exc.Forbidden()) error = self.assertRaises( exc.CommandError, self.shell, 'stack-delete teststack1/1') self.assertIn('Unable to delete 1 of the 1 stacks.', str(error)) def test_build_info(self): self.register_keystone_auth_fixture() resp_dict = { 'build_info': { 'api': {'revision': 'api_revision'}, 'engine': {'revision': 'engine_revision'} } } self.mock_request_get('/build_info', resp_dict) build_info_text = self.shell('build-info') required = [ 'api', 'engine', 'revision', 'api_revision', 'engine_revision', ] for r in required: self.assertRegex(build_info_text, r) def test_stack_snapshot(self): self.register_keystone_auth_fixture() resp_dict = {"snapshot": { "id": "1", "creation_time": "2012-10-25T01:58:47Z" }} self.mock_request_post('/stacks/teststack/1/snapshots', resp_dict, data={}) resp = self.shell('stack-snapshot teststack/1') self.assertEqual(resp_dict, jsonutils.loads(resp)) def test_snapshot_list(self): self.register_keystone_auth_fixture() resp_dict = {"snapshots": [{ "id": "2", "name": "snap1", "status": "COMPLETE", "status_reason": "", "creation_time": "2014-12-05T01:25:52Z" }]} self.mock_request_get('/stacks/teststack/1/snapshots', resp_dict) list_text = self.shell('snapshot-list teststack/1') required = [ 'id', 'name', 'status', 'status_reason', 'creation_time', '2', 'COMPLETE', '2014-12-05T01:25:52Z', ] for r in required: self.assertRegex(list_text, r) def test_snapshot_show(self): self.register_keystone_auth_fixture() resp_dict = {"snapshot": { "id": "2", "creation_time": "2012-10-25T01:58:47Z" }} self.mock_request_get('/stacks/teststack/1/snapshots/2', resp_dict) resp = self.shell('snapshot-show teststack/1 2') self.assertEqual(resp_dict, jsonutils.loads(resp)) # the main thing this @mock.patch is doing here is keeping # sys.stdin untouched for later tests @mock.patch('sys.stdin', new_callable=io.StringIO) def test_snapshot_delete_prompt_with_tty(self, ms): self.register_keystone_auth_fixture() resp_dict = {"snapshot": { "id": "2", "creation_time": "2012-10-25T01:58:47Z" }} mock_stdin = mock.Mock() mock_stdin.isatty = mock.Mock() mock_stdin.isatty.return_value = True mock_stdin.readline = mock.Mock() mock_stdin.readline.return_value = 'n' sys.stdin = mock_stdin self.mock_request_delete('/stacks/teststack/1/snapshots/2', resp_dict) resp = self.shell('snapshot-delete teststack/1 2') resp_text = ('Are you sure you want to delete the snapshot of ' 'this stack [Y/N]?') self.assertEqual(resp_text, resp) mock_stdin.readline.return_value = 'Y' resp = self.shell('snapshot-delete teststack/1 2') msg = _("Request to delete the snapshot 2 of the stack " "teststack/1 has been accepted.") self.assertRegex(resp, msg) # the main thing this @mock.patch is doing here is keeping # sys.stdin untouched for later tests @mock.patch('sys.stdin', new_callable=io.StringIO) def test_snapshot_delete_prompt_with_tty_y(self, ms): self.register_keystone_auth_fixture() resp_dict = {"snapshot": { "id": "2", "creation_time": "2012-10-25T01:58:47Z" }} mock_stdin = mock.Mock() mock_stdin.isatty = mock.Mock() mock_stdin.isatty.return_value = True mock_stdin.readline = mock.Mock() mock_stdin.readline.return_value = '' sys.stdin = mock_stdin self.mock_request_delete('/stacks/teststack/1/snapshots/2', resp_dict) # -y from the shell should skip the n/y prompt resp = self.shell('snapshot-delete -y teststack/1 2') msg = _("Request to delete the snapshot 2 of the stack " "teststack/1 has been accepted.") self.assertRegex(resp, msg) def test_snapshot_delete(self): self.register_keystone_auth_fixture() resp_dict = {"snapshot": { "id": "2", "creation_time": "2012-10-25T01:58:47Z" }} self.mock_request_delete('/stacks/teststack/1/snapshots/2', resp_dict) resp = self.shell('snapshot-delete teststack/1 2') msg = _("Request to delete the snapshot 2 of the stack " "teststack/1 has been accepted.") self.assertRegex(resp, msg) def test_stack_restore(self): self.register_keystone_auth_fixture() self.mock_request_post('/stacks/teststack/1/snapshots/2/restore', None, status_code=204) resp = self.shell('stack-restore teststack/1 2') self.assertEqual("", resp) def test_output_list(self): self.register_keystone_auth_fixture() resp_dict = {"outputs": [{ "output_key": "key", "description": "description" }, { "output_key": "key1", "description": "description1" }]} self.mock_request_get('/stacks/teststack/1/outputs', resp_dict) list_text = self.shell('output-list teststack/1') required = [ 'output_key', 'description', 'key', 'description', 'key1', 'description1' ] for r in required: self.assertRegex(list_text, r) def test_output_list_api_400_error(self): self.register_keystone_auth_fixture() outputs = [{ "output_key": "key", "description": "description" }, { "output_key": "key1", "description": "description1" }] stack_dict = {"stack": { "id": "1", "stack_name": "teststack", "stack_status": 'CREATE_COMPLETE', "creation_time": "2012-10-25T01:58:47Z", "outputs": outputs }} self.mock_request_error('/stacks/teststack/1/outputs', 'GET', exc.HTTPNotFound()) self.mock_request_get('/stacks/teststack/1', stack_dict) list_text = self.shell('output-list teststack/1') required = [ 'output_key', 'description', 'key', 'description', 'key1', 'description1' ] for r in required: self.assertRegex(list_text, r) def test_output_show_all(self): self.register_keystone_auth_fixture() resp_dict = {'outputs': [ { 'output_key': 'key', 'description': 'description' } ]} resp_dict1 = {"output": { "output_key": "key", "output_value": "value", 'description': 'description' }} self.mock_request_get('/stacks/teststack/1/outputs', resp_dict) self.mock_request_get('/stacks/teststack/1/outputs/key', resp_dict1) list_text = self.shell('output-show --with-detail teststack/1 --all') required = [ 'output_key', 'output_value', 'description', 'key', 'value', 'description', ] for r in required: self.assertRegex(list_text, r) def test_output_show(self): self.register_keystone_auth_fixture() resp_dict = {"output": { "output_key": "key", "output_value": "value", 'description': 'description' }} self.mock_request_get('/stacks/teststack/1/outputs/key', resp_dict) resp = self.shell('output-show --with-detail teststack/1 key') required = [ 'output_key', 'output_value', 'description', 'key', 'value', 'description', ] for r in required: self.assertRegex(resp, r) def test_output_show_api_400_error(self): self.register_keystone_auth_fixture() output = { "output_key": "key", "output_value": "value", 'description': 'description' } stack_dict = {"stack": { "id": "1", "stack_name": "teststack", "stack_status": 'CREATE_COMPLETE', "creation_time": "2012-10-25T01:58:47Z", 'outputs': [output] }} self.mock_request_error('/stacks/teststack/1/outputs/key', 'GET', exc.HTTPNotFound()) self.mock_request_get('/stacks/teststack/1', stack_dict) resp = self.shell('output-show --with-detail teststack/1 key') required = [ 'output_key', 'output_value', 'description', 'key', 'value', 'description', ] for r in required: self.assertRegex(resp, r) def test_output_show_output1_with_detail(self): self.register_keystone_auth_fixture() self._output_fake_response('output1') list_text = self.shell('output-show teststack/1 output1 --with-detail') required = [ 'output_key', 'output_value', 'description', 'output1', 'value1', 'test output 1', ] for r in required: self.assertRegex(list_text, r) def test_output_show_output1(self): self.register_keystone_auth_fixture() self._output_fake_response('output1') list_text = self.shell('output-show -F raw teststack/1 output1') self.assertEqual('value1\n', list_text) def test_output_show_output2_raw(self): self.register_keystone_auth_fixture() self._output_fake_response('output2') list_text = self.shell('output-show -F raw teststack/1 output2') self.assertEqual('[\n "output", \n "value", \n "2"\n]\n', list_text) def test_output_show_output2_raw_with_detail(self): self.register_keystone_auth_fixture() self._output_fake_response('output2') list_text = self.shell('output-show -F raw --with-detail ' 'teststack/1 output2') required = [ 'output_key', 'output_value', 'description', 'output2', "[u'output', u'value', u'2']", 'test output 2', ] for r in required: self.assertRegex(list_text, r) def test_output_show_output2_json(self): self.register_keystone_auth_fixture() self._output_fake_response('output2') list_text = self.shell('output-show -F json teststack/1 output2') required = [ '{', '"output_key": "output2"', '"description": "test output 2"', r'"output_value": \[', '"output"', '"value"', '"2"', '}' ] for r in required: self.assertRegex(list_text, r) def test_output_show_output2_json_with_detail(self): self.register_keystone_auth_fixture() self._output_fake_response('output2') list_text = self.shell('output-show -F json --with-detail ' 'teststack/1 output2') required = [ 'output_key', 'output_value', 'description', 'output2', '[\n "output", \n "value", \n "2"\n ]' 'test output 2', ] for r in required: self.assertRegex(list_text, r) def test_output_show_unicode_output(self): self.register_keystone_auth_fixture() self._output_fake_response('output_uni') list_text = self.shell('output-show teststack/1 output_uni') self.assertEqual(u'test\u2665\n', list_text) def test_output_show_error(self): self.register_keystone_auth_fixture() self._error_output_fake_response('output1') error = self.assertRaises( exc.CommandError, self.shell, 'output-show teststack/1 output1') self.assertIn('The Referenced Attribute (0 PublicIP) is incorrect.', str(error)) class ShellTestActions(ShellBase): def setUp(self): super(ShellTestActions, self).setUp() self.set_fake_env(FAKE_ENV_KEYSTONE_V2) def test_stack_cancel_update(self): self.register_keystone_auth_fixture() expected_data = {'cancel_update': None} self.mock_request_post( '/stacks/teststack2/actions', 'The request is accepted for processing.', data=expected_data, status_code=202) self.mock_stack_list() update_text = self.shell('stack-cancel-update teststack2') required = [ 'stack_name', 'id', 'teststack2', '1' ] for r in required: self.assertRegex(update_text, r) def test_stack_check(self): self.register_keystone_auth_fixture() expected_data = {'check': None} self.mock_request_post( '/stacks/teststack2/actions', 'The request is accepted for processing.', data=expected_data, status_code=202) self.mock_stack_list() check_text = self.shell('action-check teststack2') required = [ 'stack_name', 'id', 'teststack2', '1' ] for r in required: self.assertRegex(check_text, r) def test_stack_suspend(self): self.register_keystone_auth_fixture() expected_data = {'suspend': None} self.mock_request_post( '/stacks/teststack2/actions', 'The request is accepted for processing.', data=expected_data, status_code=202) self.mock_stack_list() suspend_text = self.shell('action-suspend teststack2') required = [ 'stack_name', 'id', 'teststack2', '1' ] for r in required: self.assertRegex(suspend_text, r) def test_stack_resume(self): self.register_keystone_auth_fixture() expected_data = {'resume': None} self.mock_request_post( '/stacks/teststack2/actions', 'The request is accepted for processing.', data=expected_data, status_code=202) self.mock_stack_list() resume_text = self.shell('action-resume teststack2') required = [ 'stack_name', 'id', 'teststack2', '1' ] for r in required: self.assertRegex(resume_text, r) class ShellTestEvents(ShellBase): def setUp(self): super(ShellTestEvents, self).setUp() self.set_fake_env(FAKE_ENV_KEYSTONE_V2) scenarios = [ ('integer_id', dict( event_id_one='24', event_id_two='42')), ('uuid_id', dict( event_id_one='3d68809e-c4aa-4dc9-a008-933823d2e44f', event_id_two='43b68bae-ed5d-4aed-a99f-0b3d39c2418a'))] def test_event_list(self): self.register_keystone_auth_fixture() resp_dict = self.event_list_resp_dict( resource_name="aResource", rsrc_eventid1=self.event_id_one, rsrc_eventid2=self.event_id_two ) stack_id = 'teststack/1' resource_name = 'testresource/1' self.mock_request_get( '/stacks/%s/resources/%s/events?sort_dir=asc' % ( parse.quote(stack_id), parse.quote(encodeutils.safe_encode( resource_name))), resp_dict) event_list_text = self.shell('event-list {0} --resource {1}'.format( stack_id, resource_name)) required = [ 'resource_name', 'id', 'resource_status_reason', 'resource_status', 'event_time', 'aResource', self.event_id_one, self.event_id_two, 'state changed', 'CREATE_IN_PROGRESS', 'CREATE_COMPLETE', '2013-12-05T14:14:31', '2013-12-05T14:14:32', ] for r in required: self.assertRegex(event_list_text, r) def test_stack_event_list_log(self): self.register_keystone_auth_fixture() resp_dict = self.event_list_resp_dict( resource_name="aResource", rsrc_eventid1=self.event_id_one, rsrc_eventid2=self.event_id_two ) stack_id = 'teststack/1' self.mock_request_get('/stacks/%s/events?sort_dir=asc' % stack_id, resp_dict) event_list_text = self.shell('event-list {0} --format log'.format( stack_id)) expected = ('2013-12-05 14:14:31 [aResource]: ' 'CREATE_IN_PROGRESS state changed\n' '2013-12-05 14:14:32 [aResource]: CREATE_COMPLETE ' 'state changed\n') self.assertEqual(expected, event_list_text) def test_event_show(self): self.register_keystone_auth_fixture() resp_dict = {"event": {"event_time": "2013-12-05T14:14:30Z", "id": self.event_id_one, "links": [{"href": "http://heat.example.com:8004/foo", "rel": "self"}, {"href": "http://heat.example.com:8004/foo2", "rel": "resource"}, {"href": "http://heat.example.com:8004/foo3", "rel": "stack"}], "logical_resource_id": "aResource", "physical_resource_id": None, "resource_name": "aResource", "resource_properties": {"admin_user": "im_powerful", "availability_zone": "nova"}, "resource_status": "CREATE_IN_PROGRESS", "resource_status_reason": "state changed", "resource_type": "OS::Nova::Server" }} stack_id = 'teststack/1' resource_name = 'testresource/1' self.mock_request_get( '/stacks/%s/resources/%s/events/%s' % ( parse.quote(stack_id), parse.quote(encodeutils.safe_encode( resource_name)), parse.quote(self.event_id_one) ), resp_dict) event_list_text = self.shell('event-show {0} {1} {2}'.format( stack_id, resource_name, self.event_id_one)) required = [ 'Property', 'Value', 'event_time', '2013-12-05T14:14:30Z', 'id', self.event_id_one, 'links', 'http://heat.example.com:8004/foo[0-9]', 'logical_resource_id', 'physical_resource_id', 'resource_name', 'aResource', 'resource_properties', 'admin_user', 'availability_zone', 'resource_status', 'CREATE_IN_PROGRESS', 'resource_status_reason', 'state changed', 'resource_type', 'OS::Nova::Server', ] for r in required: self.assertRegex(event_list_text, r) class ShellTestEventsNested(ShellBase): def setUp(self): super(ShellTestEventsNested, self).setUp() self.set_fake_env(FAKE_ENV_KEYSTONE_V2) def test_shell_nested_depth_invalid_xor(self): self.register_keystone_auth_fixture() stack_id = 'teststack/1' resource_name = 'aResource' error = self.assertRaises( exc.CommandError, self.shell, 'event-list {0} --resource {1} --nested-depth 5'.format( stack_id, resource_name)) self.assertIn('--nested-depth cannot be specified with --resource', str(error)) def test_shell_nested_depth_invalid_value(self): self.register_keystone_auth_fixture() stack_id = 'teststack/1' resource_name = 'aResource' error = self.assertRaises( exc.CommandError, self.shell, 'event-list {0} --nested-depth Z'.format( stack_id, resource_name)) self.assertIn('--nested-depth invalid value Z', str(error)) def test_shell_nested_depth_zero(self): self.register_keystone_auth_fixture() resp_dict = {"events": [{"id": 'eventid1'}, {"id": 'eventid2'}]} stack_id = 'teststack/1' self.mock_request_get('/stacks/%s/events?sort_dir=asc' % stack_id, resp_dict) list_text = self.shell('event-list %s --nested-depth 0' % stack_id) required = ['id', 'eventid1', 'eventid2'] for r in required: self.assertRegex(list_text, r) def _stub_event_list_response_old_api(self, stack_id, nested_id, timestamps, first_request): # Stub events for parent stack ev_resp_dict = {"events": [{"id": "p_eventid1", "event_time": timestamps[0]}, {"id": "p_eventid2", "event_time": timestamps[3]}]} self.mock_request_get(first_request, ev_resp_dict) # response lacks root_stack link, fetch nested events recursively self.mock_request_get('/stacks/%s/events?sort_dir=asc' % stack_id, ev_resp_dict) # Stub resources for parent, including one nested res_resp_dict = {"resources": [ {"links": [{"href": "http://heat/foo", "rel": "self"}, {"href": "http://heat/foo2", "rel": "resource"}, {"href": "http://heat/%s" % nested_id, "rel": "nested"}], "resource_type": "OS::Nested::Foo"}]} self.mock_request_get('/stacks/%s/resources' % stack_id, res_resp_dict) # Stub the events for the nested stack nev_resp_dict = {"events": [{"id": 'n_eventid1', "event_time": timestamps[1]}, {"id": 'n_eventid2', "event_time": timestamps[2]}]} self.mock_request_get('/stacks/%s/events?sort_dir=asc' % nested_id, nev_resp_dict) def test_shell_nested_depth_old_api(self): self.register_keystone_auth_fixture() stack_id = 'teststack/1' nested_id = 'nested/2' timestamps = ("2014-01-06T16:14:00Z", # parent p_eventid1 "2014-01-06T16:15:00Z", # nested n_eventid1 "2014-01-06T16:16:00Z", # nested n_eventid2 "2014-01-06T16:17:00Z") # parent p_eventid2 first_request = ('/stacks/%s/events?nested_depth=1&sort_dir=asc' % stack_id) self._stub_event_list_response_old_api( stack_id, nested_id, timestamps, first_request) list_text = self.shell('event-list %s --nested-depth 1' % stack_id) required = ['id', 'p_eventid1', 'p_eventid2', 'n_eventid1', 'n_eventid2', 'stack_name', 'teststack', 'nested'] for r in required: self.assertRegex(list_text, r) # Check event time sort/ordering self.assertRegex(list_text, "%s.*\n.*%s.*\n.*%s.*\n.*%s" % timestamps) def test_shell_nested_depth_marker_old_api(self): self.register_keystone_auth_fixture() stack_id = 'teststack/1' nested_id = 'nested/2' timestamps = ("2014-01-06T16:14:00Z", # parent p_eventid1 "2014-01-06T16:15:00Z", # nested n_eventid1 "2014-01-06T16:16:00Z", # nested n_eventid2 "2014-01-06T16:17:00Z") # parent p_eventid2 first_request = ('/stacks/%s/events?marker=n_eventid1&nested_depth=1' '&sort_dir=asc' % stack_id) self._stub_event_list_response_old_api( stack_id, nested_id, timestamps, first_request) list_text = self.shell( 'event-list %s --nested-depth 1 --marker n_eventid1' % stack_id) required = ['id', 'p_eventid2', 'n_eventid1', 'n_eventid2', 'stack_name', 'teststack', 'nested'] for r in required: self.assertRegex(list_text, r) self.assertNotRegex(list_text, 'p_eventid1') self.assertRegex(list_text, "%s.*\n.*%s.*\n.*%s.*" % timestamps[1:]) def test_shell_nested_depth_limit_old_api(self): self.register_keystone_auth_fixture() stack_id = 'teststack/1' nested_id = 'nested/2' timestamps = ("2014-01-06T16:14:00Z", # parent p_eventid1 "2014-01-06T16:15:00Z", # nested n_eventid1 "2014-01-06T16:16:00Z", # nested n_eventid2 "2014-01-06T16:17:00Z") # parent p_eventid2 first_request = ('/stacks/%s/events?limit=2&nested_depth=1' '&sort_dir=asc' % stack_id) self._stub_event_list_response_old_api( stack_id, nested_id, timestamps, first_request) list_text = self.shell( 'event-list %s --nested-depth 1 --limit 2' % stack_id) required = ['id', 'p_eventid1', 'n_eventid1', 'stack_name', 'teststack', 'nested'] for r in required: self.assertRegex(list_text, r) self.assertNotRegex(list_text, 'p_eventid2') self.assertNotRegex(list_text, 'n_eventid2') self.assertRegex(list_text, "%s.*\n.*%s.*\n" % timestamps[:2]) def _nested_events(self): links = [ {"rel": "self"}, {"rel": "resource"}, {"rel": "stack"}, {"rel": "root_stack"} ] return [ { "id": "p_eventid1", "event_time": '2014-01-06T16:14:00Z', "stack_id": '1', "resource_name": 'the_stack', "resource_status": 'CREATE_IN_PROGRESS', "resource_status_reason": 'Stack CREATE started', "links": links, }, { "id": 'n_eventid1', "event_time": '2014-01-06T16:15:00Z', "stack_id": '2', "resource_name": 'nested_stack', "resource_status": 'CREATE_IN_PROGRESS', "resource_status_reason": 'Stack CREATE started', "links": links, }, { "id": 'n_eventid2', "event_time": '2014-01-06T16:16:00Z', "stack_id": '2', "resource_name": 'nested_stack', "resource_status": 'CREATE_COMPLETE', "resource_status_reason": 'Stack CREATE completed', "links": links, }, { "id": "p_eventid2", "event_time": '2014-01-06T16:17:00Z', "stack_id": '1', "resource_name": 'the_stack', "resource_status": 'CREATE_COMPLETE', "resource_status_reason": 'Stack CREATE completed', "links": links, }, ] def test_shell_nested_depth(self): self.register_keystone_auth_fixture() stack_id = 'teststack/1' nested_events = self._nested_events() ev_resp_dict = {'events': nested_events} url = '/stacks/%s/events?nested_depth=1&sort_dir=asc' % stack_id self.mock_request_get(url, ev_resp_dict) list_text = self.shell('event-list %s --nested-depth 1 --format log' % stack_id) self.assertEqual('''\ 2014-01-06 16:14:00Z [the_stack]: CREATE_IN_PROGRESS Stack CREATE started 2014-01-06 16:15:00Z [nested_stack]: CREATE_IN_PROGRESS Stack CREATE started 2014-01-06 16:16:00Z [nested_stack]: CREATE_COMPLETE Stack CREATE completed 2014-01-06 16:17:00Z [the_stack]: CREATE_COMPLETE Stack CREATE completed ''', list_text) def test_shell_nested_depth_marker(self): self.register_keystone_auth_fixture() stack_id = 'teststack/1' nested_events = self._nested_events() ev_resp_dict = {'events': nested_events[1:]} url = ('/stacks/%s/events?marker=n_eventid1&nested_depth=1' '&sort_dir=asc' % stack_id) self.mock_request_get(url, ev_resp_dict) list_text = self.shell('event-list %s --nested-depth 1 --format log ' '--marker n_eventid1' % stack_id) self.assertEqual('''\ 2014-01-06 16:15:00Z [nested_stack]: CREATE_IN_PROGRESS Stack CREATE started 2014-01-06 16:16:00Z [nested_stack]: CREATE_COMPLETE Stack CREATE completed 2014-01-06 16:17:00Z [the_stack]: CREATE_COMPLETE Stack CREATE completed ''', list_text) def test_shell_nested_depth_limit(self): self.register_keystone_auth_fixture() stack_id = 'teststack/1' nested_events = self._nested_events() ev_resp_dict = {'events': nested_events[:2]} url = ('/stacks/%s/events?limit=2&nested_depth=1&sort_dir=asc' % stack_id) self.mock_request_get(url, ev_resp_dict) list_text = self.shell('event-list %s --nested-depth 1 --format log ' '--limit 2' % stack_id) self.assertEqual('''\ 2014-01-06 16:14:00Z [the_stack]: CREATE_IN_PROGRESS Stack CREATE started 2014-01-06 16:15:00Z [nested_stack]: CREATE_IN_PROGRESS Stack CREATE started ''', list_text) class ShellTestHookFunctions(ShellBase): def setUp(self): super(ShellTestHookFunctions, self).setUp() self.set_fake_env(FAKE_ENV_KEYSTONE_V2) def _stub_stack_response(self, stack_id, action='CREATE', status='IN_PROGRESS'): # Stub parent stack show for status resp_dict = {"stack": { "id": stack_id.split("/")[1], "stack_name": stack_id.split("/")[0], "stack_status": '%s_%s' % (action, status), "creation_time": "2014-01-06T16:14:00Z", }} self.mock_request_get('/stacks/teststack/1', resp_dict) def _stub_responses(self, stack_id, nested_id, action='CREATE'): action_reason = 'Stack %s started' % action hook_reason = ('%s paused until Hook pre-%s is cleared' % (action, action.lower())) hook_clear_reason = 'Hook pre-%s is cleared' % action.lower() self._stub_stack_response(stack_id, action) # Stub events for parent stack ev_resp_dict = {"events": [{"id": "p_eventid1", "event_time": "2014-01-06T16:14:00Z", "resource_name": None, "resource_status_reason": action_reason}, {"id": "p_eventid2", "event_time": "2014-01-06T16:17:00Z", "resource_name": "p_res", "resource_status_reason": hook_reason}]} url = '/stacks/%s/events?nested_depth=1&sort_dir=asc' % stack_id self.mock_request_get(url, ev_resp_dict) # this api doesn't support nested_depth, fetch events recursively self.mock_request_get('/stacks/%s/events?sort_dir=asc' % stack_id, ev_resp_dict) # Stub resources for parent, including one nested res_resp_dict = {"resources": [ {"links": [{"href": "http://heat/foo", "rel": "self"}, {"href": "http://heat/foo2", "rel": "resource"}, {"href": "http://heat/%s" % nested_id, "rel": "nested"}], "resource_type": "OS::Nested::Foo"}]} self.mock_request_get('/stacks/%s/resources' % stack_id, res_resp_dict) # Stub the events for the nested stack nev_resp_dict = {"events": [{"id": 'n_eventid1', "event_time": "2014-01-06T16:15:00Z", "resource_name": "n_res", "resource_status_reason": hook_reason}, {"id": 'n_eventid2', "event_time": "2014-01-06T16:16:00Z", "resource_name": "n_res", "resource_status_reason": hook_clear_reason}]} self.mock_request_get('/stacks/%s/events?sort_dir=asc' % nested_id, nev_resp_dict) def test_hook_poll_pre_create(self): self.register_keystone_auth_fixture() stack_id = 'teststack/1' nested_id = 'nested/2' self._stub_responses(stack_id, nested_id, 'CREATE') list_text = self.shell('hook-poll %s --nested-depth 1' % stack_id) hook_reason = 'CREATE paused until Hook pre-create is cleared' required = ['id', 'p_eventid2', 'stack_name', 'teststack', hook_reason] for r in required: self.assertRegex(list_text, r) self.assertNotRegex(list_text, 'p_eventid1') self.assertNotRegex(list_text, 'n_eventid1') self.assertNotRegex(list_text, 'n_eventid2') def test_hook_poll_pre_update(self): self.register_keystone_auth_fixture() stack_id = 'teststack/1' nested_id = 'nested/2' self._stub_responses(stack_id, nested_id, 'UPDATE') list_text = self.shell('hook-poll %s --nested-depth 1' % stack_id) hook_reason = 'UPDATE paused until Hook pre-update is cleared' required = ['id', 'p_eventid2', 'stack_name', 'teststack', hook_reason] for r in required: self.assertRegex(list_text, r) self.assertNotRegex(list_text, 'p_eventid1') self.assertNotRegex(list_text, 'n_eventid1') self.assertNotRegex(list_text, 'n_eventid2') def test_hook_poll_pre_delete(self): self.register_keystone_auth_fixture() stack_id = 'teststack/1' nested_id = 'nested/2' self._stub_responses(stack_id, nested_id, 'DELETE') list_text = self.shell('hook-poll %s --nested-depth 1' % stack_id) hook_reason = 'DELETE paused until Hook pre-delete is cleared' required = ['id', 'p_eventid2', 'stack_name', 'teststack', hook_reason] for r in required: self.assertRegex(list_text, r) self.assertNotRegex(list_text, 'p_eventid1') self.assertNotRegex(list_text, 'n_eventid1') self.assertNotRegex(list_text, 'n_eventid2') def test_hook_poll_bad_status(self): self.register_keystone_auth_fixture() stack_id = 'teststack/1' self._stub_stack_response(stack_id, status='COMPLETE') error = self.assertRaises( exc.CommandError, self.shell, 'hook-poll %s --nested-depth 1' % stack_id) self.assertIn('Stack status CREATE_COMPLETE not IN_PROGRESS', str(error)) def test_shell_nested_depth_invalid_value(self): self.register_keystone_auth_fixture() stack_id = 'teststack/1' error = self.assertRaises( exc.CommandError, self.shell, 'hook-poll %s --nested-depth Z' % stack_id) self.assertIn('--nested-depth invalid value Z', str(error)) def test_hook_poll_clear_bad_status(self): self.register_keystone_auth_fixture() stack_id = 'teststack/1' self._stub_stack_response(stack_id, status='COMPLETE') error = self.assertRaises( exc.CommandError, self.shell, 'hook-clear %s aresource' % stack_id) self.assertIn('Stack status CREATE_COMPLETE not IN_PROGRESS', str(error)) def test_hook_poll_clear_bad_action(self): self.register_keystone_auth_fixture() stack_id = 'teststack/1' self._stub_stack_response(stack_id, action='BADACTION') error = self.assertRaises( exc.CommandError, self.shell, 'hook-clear %s aresource' % stack_id) self.assertIn('Unexpected stack status BADACTION_IN_PROGRESS', str(error)) class ShellTestResources(ShellBase): def setUp(self): super(ShellTestResources, self).setUp() self.set_fake_env(FAKE_ENV_KEYSTONE_V2) def _test_resource_list(self, with_resource_name): self.register_keystone_auth_fixture() 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": "aLogicalResource", "physical_resource_id": "43b68bae-ed5d-4aed-a99f-0b3d39c2418a", "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" stack_id = 'teststack/1' self.mock_request_get('/stacks/%s/resources' % stack_id, resp_dict) resource_list_text = self.shell('resource-list {0}'.format(stack_id)) required = [ 'physical_resource_id', 'resource_type', 'resource_status', 'updated_time', '43b68bae-ed5d-4aed-a99f-0b3d39c2418a', '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.assertRegex(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.register_keystone_auth_fixture() resp_dict = {"resources": []} stack_id = 'teststack/1' self.mock_request_get('/stacks/%s/resources' % stack_id, resp_dict) resource_list_text = self.shell('resource-list {0}'.format(stack_id)) self.assertEqual('''\ +---------------+----------------------+---------------+-----------------+\ --------------+ | resource_name | physical_resource_id | resource_type | resource_status |\ updated_time | +---------------+----------------------+---------------+-----------------+\ --------------+ +---------------+----------------------+---------------+-----------------+\ --------------+ ''', resource_list_text) def _test_resource_list_more_args(self, query_args, cmd_args, response_args): self.register_keystone_auth_fixture() resp_dict = {"resources": [{ "resource_name": "foobar", "links": [{ "href": "http://heat.example.com:8004/foo/12/resources/foobar", "rel": "self" }, { "href": "http://heat.example.com:8004/foo/12", "rel": "stack" }], }]} stack_id = 'teststack/1' self.mock_request_get('/stacks/%s/resources?%s' % ( stack_id, query_args), resp_dict) shell_cmd = 'resource-list %s %s' % (stack_id, cmd_args) resource_list_text = self.shell(shell_cmd) for field in response_args: self.assertRegex(resource_list_text, field) def test_resource_list_nested(self): self._test_resource_list_more_args( query_args='nested_depth=99', cmd_args='--nested-depth 99', response_args=['resource_name', 'foobar', 'stack_name', 'foo']) def test_resource_list_filter(self): self._test_resource_list_more_args( query_args='name=foobar', cmd_args='--filter name=foobar', response_args=['resource_name', 'foobar']) def test_resource_list_detail(self): self._test_resource_list_more_args( query_args=parse.urlencode({'with_detail': True}, True), cmd_args='--with-detail', response_args=['resource_name', 'foobar', 'stack_name', 'foo']) def test_resource_show_with_attrs(self): self.register_keystone_auth_fixture() resp_dict = {"resource": {"description": "", "links": [{"href": "http://heat.example.com:8004/foo", "rel": "self"}, {"href": "http://heat.example.com:8004/foo2", "rel": "resource"}], "logical_resource_id": "aResource", "physical_resource_id": "43b68bae-ed5d-4aed-a99f-0b3d39c2418a", "required_by": [], "resource_name": "aResource", "resource_status": "CREATE_COMPLETE", "resource_status_reason": "state changed", "resource_type": "OS::Nova::Server", "updated_time": "2014-01-06T16:14:26Z", "creation_time": "2014-01-06T16:14:26Z", "attributes": { "attr_a": "value_of_attr_a", "attr_b": "value_of_attr_b"}}} stack_id = 'teststack/1' resource_name = 'aResource' self.mock_request_get( '/stacks/%s/resources/%s?with_attr=attr_a&with_attr=attr_b' % ( parse.quote(stack_id), parse.quote(encodeutils.safe_encode( resource_name)) ), resp_dict) resource_show_text = self.shell( 'resource-show {0} {1} --with-attr attr_a ' '--with-attr attr_b'.format( stack_id, resource_name)) required = [ 'description', 'links', 'http://heat.example.com:8004/foo[0-9]', 'logical_resource_id', 'aResource', 'physical_resource_id', '43b68bae-ed5d-4aed-a99f-0b3d39c2418a', 'required_by', 'resource_name', 'aResource', 'resource_status', 'CREATE_COMPLETE', 'resource_status_reason', 'state changed', 'resource_type', 'OS::Nova::Server', 'updated_time', '2014-01-06T16:14:26Z', ] for r in required: self.assertRegex(resource_show_text, r) def test_resource_signal(self): self.register_keystone_auth_fixture() stack_id = 'teststack/1' resource_name = 'aResource' self.mock_request_post( '/stacks/%s/resources/%s/signal' % ( parse.quote(stack_id), parse.quote(encodeutils.safe_encode( resource_name)) ), '', data={'message': 'Content'} ) text = self.shell( 'resource-signal {0} {1} -D {{"message":"Content"}}'.format( stack_id, resource_name)) self.assertEqual("", text) def test_resource_signal_no_data(self): self.register_keystone_auth_fixture() stack_id = 'teststack/1' resource_name = 'aResource' self.mock_request_post( '/stacks/%s/resources/%s/signal' % ( parse.quote(stack_id), parse.quote(encodeutils.safe_encode( resource_name)) ), '', data=None ) text = self.shell( 'resource-signal {0} {1}'.format(stack_id, resource_name)) self.assertEqual("", text) def test_resource_signal_no_json(self): self.register_keystone_auth_fixture() stack_id = 'teststack/1' resource_name = 'aResource' error = self.assertRaises( exc.CommandError, self.shell, 'resource-signal {0} {1} -D [2'.format( stack_id, resource_name)) self.assertIn('Data should be in JSON format', str(error)) def test_resource_signal_no_dict(self): self.register_keystone_auth_fixture() stack_id = 'teststack/1' resource_name = 'aResource' error = self.assertRaises( exc.CommandError, self.shell, 'resource-signal {0} {1} -D "message"'.format( stack_id, resource_name)) self.assertEqual('Data should be a JSON dict', str(error)) def test_resource_signal_both_data(self): self.register_keystone_auth_fixture() stack_id = 'teststack/1' resource_name = 'aResource' error = self.assertRaises( exc.CommandError, self.shell, 'resource-signal {0} {1} -D "message" -f foo'.format( stack_id, resource_name)) self.assertEqual('Can only specify one of data and data-file', str(error)) def test_resource_signal_data_file(self): self.register_keystone_auth_fixture() stack_id = 'teststack/1' resource_name = 'aResource' self.mock_request_post( '/stacks/%s/resources/%s/signal' % ( parse.quote(stack_id), parse.quote(encodeutils.safe_encode( resource_name)) ), '', data={'message': 'Content'} ) with tempfile.NamedTemporaryFile() as data_file: data_file.write(b'{"message":"Content"}') data_file.flush() text = self.shell( 'resource-signal {0} {1} -f {2}'.format( stack_id, resource_name, data_file.name)) self.assertEqual("", text) def test_resource_mark_unhealthy(self): self.register_keystone_auth_fixture() stack_id = 'teststack/1' resource_name = 'aResource' self.mock_request_patch( '/stacks/%s/resources/%s' % ( parse.quote(stack_id), parse.quote(encodeutils.safe_encode( resource_name)) ), '', req_headers=False, data={'mark_unhealthy': True, 'resource_status_reason': 'Any'}) text = self.shell( 'resource-mark-unhealthy {0} {1} Any'.format( stack_id, resource_name)) self.assertEqual("", text) def test_resource_mark_unhealthy_reset(self): self.register_keystone_auth_fixture() stack_id = 'teststack/1' resource_name = 'aResource' self.mock_request_patch( '/stacks/%s/resources/%s' % ( parse.quote(stack_id), parse.quote(encodeutils.safe_encode( resource_name)) ), '', req_headers=False, data={'mark_unhealthy': False, 'resource_status_reason': 'Any'}) text = self.shell( 'resource-mark-unhealthy --reset {0} {1} Any'.format( stack_id, resource_name)) self.assertEqual("", text) def test_resource_mark_unhealthy_no_reason(self): self.register_keystone_auth_fixture() stack_id = 'teststack/1' resource_name = 'aResource' self.mock_request_patch( '/stacks/%s/resources/%s' % ( parse.quote(stack_id), parse.quote(encodeutils.safe_encode( resource_name)) ), '', req_headers=False, data={'mark_unhealthy': True, 'resource_status_reason': ''}) text = self.shell( 'resource-mark-unhealthy {0} {1}'.format( stack_id, resource_name)) self.assertEqual("", text) class ShellTestResourceTypes(ShellBase): def setUp(self): super(ShellTestResourceTypes, self).setUp() self.set_fake_env(FAKE_ENV_KEYSTONE_V3) def test_resource_type_template_yaml(self): self.register_keystone_auth_fixture() resp_dict = {"heat_template_version": "2013-05-23", "parameters": {}, "resources": {}, "outputs": {}} self.mock_request_get( '/resource_types/OS%3A%3ANova%3A%3AKeyPair/template' '?template_type=hot', resp_dict) show_text = self.shell( 'resource-type-template -F yaml -t hot OS::Nova::KeyPair') required = [ "heat_template_version: '2013-05-23'", "outputs: {}", "parameters: {}", "resources: {}" ] for r in required: self.assertRegex(show_text, r) def test_resource_type_template_json(self): self.register_keystone_auth_fixture() resp_dict = {"AWSTemplateFormatVersion": "2013-05-23", "Parameters": {}, "Resources": {}, "Outputs": {}} self.mock_request_get( '/resource_types/OS%3A%3ANova%3A%3AKeyPair/template' '?template_type=cfn', resp_dict) show_text = self.shell( 'resource-type-template -F json OS::Nova::KeyPair') required = [ '{', ' "AWSTemplateFormatVersion": "2013-05-23"', ' "Outputs": {}', ' "Resources": {}', ' "Parameters": {}', '}' ] for r in required: self.assertRegex(show_text, r) class ShellTestConfig(ShellBase): def setUp(self): super(ShellTestConfig, self).setUp() self._set_fake_env() def _set_fake_env(self): '''Patch os.environ to avoid required auth info.''' self.set_fake_env(FAKE_ENV_KEYSTONE_V2) def test_config_create(self): self.register_keystone_auth_fixture() definition = { 'inputs': [ {'name': 'foo'}, {'name': 'bar'}, ], 'outputs': [ {'name': 'result'} ], 'options': {'a': 'b'} } validate_template = {'template': { 'heat_template_version': '2013-05-23', 'resources': { 'config_name': { 'type': 'OS::Heat::SoftwareConfig', 'properties': { 'config': 'the config script', 'group': 'script', 'inputs': [ {'name': 'foo'}, {'name': 'bar'}, ], 'outputs': [ {'name': 'result'} ], 'options': {'a': 'b'}, 'config': 'the config script' } } } }} create_dict = { 'group': 'script', 'name': 'config_name', 'inputs': [ {'name': 'foo'}, {'name': 'bar'}, ], 'outputs': [ {'name': 'result'} ], 'options': {'a': 'b'}, 'config': 'the config script' } resp_dict = {'software_config': { 'group': 'script', 'name': 'config_name', 'inputs': [ {'name': 'foo'}, {'name': 'bar'}, ], 'outputs': [ {'name': 'result'} ], 'options': {'a': 'b'}, 'config': 'the config script', 'id': 'abcd' }} output = [ io.StringIO(yaml.safe_dump(definition, indent=2)), io.StringIO('the config script'), ] self.useFixture(fixtures.MockPatchObject(request, 'urlopen', side_effect=output)) self.mock_request_post('/validate', resp_dict, data=validate_template) self.mock_request_post('/software_configs', resp_dict, data=create_dict) text = self.shell('config-create -c /tmp/config_script ' '-g script -f /tmp/defn config_name') self.assertEqual(resp_dict['software_config'], jsonutils.loads(text)) request.urlopen.assert_has_calls([ mock.call('file:///tmp/defn'), mock.call('file:///tmp/config_script'), ]) def test_config_show(self): self.register_keystone_auth_fixture() resp_dict = {'software_config': { 'inputs': [], 'group': 'script', 'name': 'config_name', 'outputs': [], 'options': {}, 'config': 'the config script', 'id': 'abcd'}} self.mock_request_get('/software_configs/abcd', resp_dict) self.mock_request_get('/software_configs/abcd', resp_dict) self.mock_request_error('/software_configs/abcde', 'GET', exc.HTTPNotFound()) text = self.shell('config-show abcd') required = [ 'inputs', 'group', 'name', 'outputs', 'options', 'config', 'id', ] for r in required: self.assertRegex(text, r) self.assertEqual( 'the config script\n', self.shell('config-show --config-only abcd')) self.assertRaises(exc.CommandError, self.shell, 'config-show abcde') def test_config_delete(self): self.register_keystone_auth_fixture() self.mock_request_delete('/software_configs/abcd') self.mock_request_delete('/software_configs/qwer') self.mock_request_error('/software_configs/abcd', 'DELETE', exc.HTTPNotFound()) self.mock_request_error('/software_configs/qwer', 'DELETE', exc.HTTPNotFound()) self.assertEqual('', self.shell('config-delete abcd qwer')) error = self.assertRaises( exc.CommandError, self.shell, 'config-delete abcd qwer') self.assertIn('Unable to delete 2 of the 2 configs.', str(error)) class ShellTestDeployment(ShellBase): def setUp(self): super(ShellTestDeployment, self).setUp() self.client = http.SessionClient self._set_fake_env() def _set_fake_env(self): '''Patch os.environ to avoid required auth info.''' self.set_fake_env(FAKE_ENV_KEYSTONE_V2) def test_deploy_create(self): self.register_keystone_auth_fixture() self.patch( 'heatclient.common.deployment_utils.build_derived_config_params') self.patch( 'heatclient.common.deployment_utils.build_signal_id') resp_dict = {'software_deployment': { 'status': 'INPROGRESS', 'server_id': '700115e5-0100-4ecc-9ef7-9e05f27d8803', 'config_id': '18c4fc03-f897-4a1d-aaad-2b7622e60257', 'output_values': { 'deploy_stdout': '', 'deploy_stderr': '', 'deploy_status_code': 0, 'result': 'The result value' }, 'input_values': {}, 'action': 'UPDATE', 'status_reason': 'Outputs received', 'id': 'abcd' }} config_dict = {'software_config': { 'inputs': [], 'group': 'script', 'name': 'config_name', 'outputs': [], 'options': {}, 'config': 'the config script', 'id': 'defg'}} derived_dict = {'software_config': { 'inputs': [], 'group': 'script', 'name': 'config_name', 'outputs': [], 'options': {}, 'config': 'the config script', 'id': 'abcd'}} deploy_data = {'action': 'UPDATE', 'config_id': u'abcd', 'server_id': 'inst01', 'status': 'IN_PROGRESS', 'tenant_id': 'asdf'} self.mock_request_get('/software_configs/defg', config_dict) self.mock_request_post('/software_configs', derived_dict, data={}) self.mock_request_post('/software_deployments', resp_dict, data=deploy_data) self.mock_request_post('/software_configs', derived_dict, data={}) self.mock_request_post('/software_deployments', resp_dict, data=deploy_data) self.mock_request_error('/software_configs/defgh', 'GET', exc.HTTPNotFound()) text = self.shell('deployment-create -c defg -sinst01 xxx') required = [ 'status', 'server_id', 'config_id', 'output_values', 'input_values', 'action', 'status_reason', 'id', ] for r in required: self.assertRegex(text, r) text = self.shell('deployment-create -sinst01 xxx') for r in required: self.assertRegex(text, r) self.assertRaises(exc.CommandError, self.shell, 'deployment-create -c defgh -s inst01 yyy') def test_deploy_list(self): self.register_keystone_auth_fixture() resp_dict = { 'software_deployments': [{'status': 'COMPLETE', 'server_id': '123', 'config_id': '18c4fc03-f897-4a1d-aaad-2b7622e60257', 'output_values': { 'deploy_stdout': '', 'deploy_stderr': '', 'deploy_status_code': 0, 'result': 'The result value' }, 'input_values': {}, 'action': 'CREATE', 'status_reason': 'Outputs received', 'id': 'defg'}, ] } self.mock_request_get('/software_deployments?', resp_dict) self.mock_request_get('/software_deployments?server_id=123', resp_dict) list_text = self.shell('deployment-list') required = [ 'id', 'config_id', 'server_id', 'action', 'status', 'creation_time', 'status_reason', ] for r in required: self.assertRegex(list_text, r) self.assertNotRegex(list_text, 'parent') list_text = self.shell('deployment-list -s 123') for r in required: self.assertRegex(list_text, r) self.assertNotRegex(list_text, 'parent') def test_deploy_show(self): self.register_keystone_auth_fixture() resp_dict = {'software_deployment': { 'status': 'COMPLETE', 'server_id': '700115e5-0100-4ecc-9ef7-9e05f27d8803', 'config_id': '18c4fc03-f897-4a1d-aaad-2b7622e60257', 'output_values': { 'deploy_stdout': '', 'deploy_stderr': '', 'deploy_status_code': 0, 'result': 'The result value' }, 'input_values': {}, 'action': 'CREATE', 'status_reason': 'Outputs received', 'id': 'defg' }} self.mock_request_get('/software_deployments/defg', resp_dict) self.mock_request_error('/software_deployments/defgh', 'GET', exc.HTTPNotFound()) text = self.shell('deployment-show defg') required = [ 'status', 'server_id', 'config_id', 'output_values', 'input_values', 'action', 'status_reason', 'id', ] for r in required: self.assertRegex(text, r) self.assertRaises(exc.CommandError, self.shell, 'deployment-show defgh') def test_deploy_delete(self): self.register_keystone_auth_fixture() deploy_resp_dict = {'software_deployment': { 'config_id': 'dummy_config_id' }} def _get_deployment_request_except(id): self.mock_request_error('/software_deployments/%s' % id, 'GET', exc.HTTPNotFound()) def _delete_deployment_request_except(id): self.mock_request_get('/software_deployments/%s' % id, deploy_resp_dict) self.mock_request_error('/software_deployments/%s' % id, 'DELETE', exc.HTTPNotFound()) def _delete_config_request_except(id): self.mock_request_get('/software_deployments/%s' % id, deploy_resp_dict) self.mock_request_delete('/software_deployments/%s' % id) self.mock_request_error('/software_configs/dummy_config_id', 'DELETE', exc.HTTPNotFound()) def _delete_request_success(id): self.mock_request_get('/software_deployments/%s' % id, deploy_resp_dict) self.mock_request_delete('/software_deployments/%s' % id) self.mock_request_delete('/software_configs/dummy_config_id') _get_deployment_request_except('defg') _get_deployment_request_except('qwer') _delete_deployment_request_except('defg') _delete_deployment_request_except('qwer') _delete_config_request_except('defg') _delete_config_request_except('qwer') _delete_request_success('defg') _delete_request_success('qwer') error = self.assertRaises( exc.CommandError, self.shell, 'deployment-delete defg qwer') self.assertIn('Unable to delete 2 of the 2 deployments.', str(error)) error2 = self.assertRaises( exc.CommandError, self.shell, 'deployment-delete defg qwer') self.assertIn('Unable to delete 2 of the 2 deployments.', str(error2)) output = self.shell('deployment-delete defg qwer') self.assertRegex(output, 'Failed to delete the correlative config ' 'dummy_config_id of deployment defg') self.assertRegex(output, 'Failed to delete the correlative config ' 'dummy_config_id of deployment qwer') self.assertEqual('', self.shell('deployment-delete defg qwer')) def test_deploy_metadata(self): self.register_keystone_auth_fixture() resp_dict = {'metadata': [ {'id': 'abcd'}, {'id': 'defg'} ]} self.mock_request_get('/software_deployments/metadata/aaaa', resp_dict) build_info_text = self.shell('deployment-metadata-show aaaa') required = [ 'abcd', 'defg', 'id', ] for r in required: self.assertRegex(build_info_text, r) def test_deploy_output_show(self): self.register_keystone_auth_fixture() resp_dict = {'software_deployment': { 'status': 'COMPLETE', 'server_id': '700115e5-0100-4ecc-9ef7-9e05f27d8803', 'config_id': '18c4fc03-f897-4a1d-aaad-2b7622e60257', 'output_values': { 'deploy_stdout': '', 'deploy_stderr': '', 'deploy_status_code': 0, 'result': 'The result value', 'dict_output': {'foo': 'bar'}, 'list_output': ['foo', 'bar'] }, 'input_values': {}, 'action': 'CREATE', 'status_reason': 'Outputs received', 'id': 'defg' }} self.mock_request_error('/software_deployments/defgh', 'GET', exc.HTTPNotFound()) for a in range(9): self.mock_request_get('/software_deployments/defg', resp_dict) self.assertRaises(exc.CommandError, self.shell, 'deployment-output-show defgh result') self.assertEqual( 'The result value\n', self.shell('deployment-output-show defg result')) self.assertEqual( '"The result value"\n', self.shell('deployment-output-show --format json defg result')) self.assertEqual( '{\n "foo": "bar"\n}\n', self.shell('deployment-output-show defg dict_output')) self.assertEqual( self.shell( 'deployment-output-show --format raw defg dict_output'), self.shell( 'deployment-output-show --format json defg dict_output')) self.assertEqual( '[\n "foo", \n "bar"\n]\n', self.shell('deployment-output-show defg list_output')) self.assertEqual( self.shell( 'deployment-output-show --format raw defg list_output'), self.shell( 'deployment-output-show --format json defg list_output')) self.assertEqual({ 'deploy_stdout': '', 'deploy_stderr': '', 'deploy_status_code': 0, 'result': 'The result value', 'dict_output': {'foo': 'bar'}, 'list_output': ['foo', 'bar']}, jsonutils.loads(self.shell( 'deployment-output-show --format json defg --all')) ) class ShellTestBuildInfo(ShellBase): def setUp(self): super(ShellTestBuildInfo, self).setUp() self._set_fake_env() def _set_fake_env(self): '''Patch os.environ to avoid required auth info.''' self.set_fake_env(FAKE_ENV_KEYSTONE_V2) def test_build_info(self): self.register_keystone_auth_fixture() resp_dict = { 'build_info': { 'api': {'revision': 'api_revision'}, 'engine': {'revision': 'engine_revision'} } } self.mock_request_get('/build_info', resp_dict) build_info_text = self.shell('build-info') required = [ 'api', 'engine', 'revision', 'api_revision', 'engine_revision', ] for r in required: self.assertRegex(build_info_text, r) class ShellTestToken(ShellTestUserPass): # Rerun all ShellTestUserPass test with token auth def setUp(self): self.token = 'a_token' super(ShellTestToken, self).setUp() def _set_fake_env(self): fake_env = { 'OS_AUTH_TOKEN': self.token, 'OS_TENANT_ID': 'tenant_id', 'OS_AUTH_URL': BASE_URL, # Note we also set username/password, because create/update # pass them even if we have a token to support storing credentials # Hopefully at some point we can remove this and move to only # storing trust id's in heat-engine instead.. 'OS_USERNAME': 'username', 'OS_PASSWORD': 'password' } self.set_fake_env(fake_env) class ShellTestUserPassKeystoneV3(ShellTestUserPass): def _set_fake_env(self): self.set_fake_env(FAKE_ENV_KEYSTONE_V3) class StandaloneTokenMixin(object): def setUp(self): self.token = 'a_token' super(StandaloneTokenMixin, self).setUp() self.client = http.HTTPClient def _set_fake_env(self): fake_env = { 'OS_AUTH_TOKEN': self.token, 'OS_NO_CLIENT_AUTH': 'True', 'HEAT_URL': 'http://no.where', 'OS_AUTH_URL': BASE_URL, # Note we also set username/password, because create/update # pass them even if we have a token to support storing credentials # Hopefully at some point we can remove this and move to only # storing trust id's in heat-engine instead.. 'OS_USERNAME': 'username', 'OS_PASSWORD': 'password' } self.set_fake_env(fake_env) class ShellTestStandaloneToken(StandaloneTokenMixin, ShellTestUserPass): # Rerun all ShellTestUserPass test in standalone mode, where we # specify --os-no-client-auth, a token and Heat endpoint def test_bad_template_file(self): self.register_keystone_auth_fixture() failed_msg = 'Error parsing template ' with tempfile.NamedTemporaryFile() as bad_json_file: bad_json_file.write(b"{foo:}") bad_json_file.flush() self.shell_error("stack-create ts -f %s" % bad_json_file.name, failed_msg, exception=exc.CommandError) with tempfile.NamedTemporaryFile() as bad_json_file: bad_json_file.write(b'{"foo": None}') bad_json_file.flush() self.shell_error("stack-create ts -f %s" % bad_json_file.name, failed_msg, exception=exc.CommandError) class ShellTestStandaloneTokenArgs(StandaloneTokenMixin, ShellTestNoMoxBase): def test_commandline_args_passed_to_requests(self): """Check that we have sent the proper arguments to requests.""" self.register_keystone_auth_fixture() resp_dict = {"stacks": [ { "id": "1", "stack_name": "teststack", "stack_owner": "testowner", "project": "testproject", "stack_status": 'CREATE_COMPLETE', "creation_time": "2014-10-15T01:58:47Z" }]} self.requests.get('http://no.where/stacks', status_code=200, headers={'Content-Type': 'application/json'}, json=resp_dict) # Replay, create client, assert list_text = self.shell('stack-list') required = [ 'id', 'stack_status', 'creation_time', 'teststack', '1', 'CREATE_COMPLETE', ] for r in required: self.assertRegex(list_text, r) self.assertNotRegex(list_text, 'parent') class MockShellBase(TestCase): def setUp(self): super(MockShellBase, self).setUp() self.jreq_mock = self.patch( 'heatclient.common.http.HTTPClient.json_request') self.session_jreq_mock = self.patch( 'heatclient.common.http.SessionClient.request') # Some tests set exc.verbose = 1, so reset on cleanup def unset_exc_verbose(): exc.verbose = 0 self.addCleanup(unset_exc_verbose) def shell(self, argstr): orig = sys.stdout try: sys.stdout = io.StringIO() _shell = heatclient.shell.HeatShell() _shell.main(argstr.split()) self.subcommands = _shell.subcommands.keys() except SystemExit: exc_type, exc_value, exc_traceback = sys.exc_info() self.assertEqual(0, exc_value.code) finally: out = sys.stdout.getvalue() sys.stdout.close() sys.stdout = orig return out class MockShellTestUserPass(MockShellBase): def setUp(self): super(MockShellTestUserPass, self).setUp() self._set_fake_env() def _set_fake_env(self): self.set_fake_env(FAKE_ENV_KEYSTONE_V2) def test_stack_list_with_args(self): self.register_keystone_auth_fixture() resp_dict = self.stack_list_resp_dict(include_project=True) resp = fakes.FakeHTTPResponse( 200, 'success, you', {'content-type': 'application/json'}, jsonutils.dumps(resp_dict)) self.session_jreq_mock.return_value = resp self.jreq_mock.return_value = (resp, resp_dict) list_text = self.shell('stack-list' ' --limit 2' ' --marker fake_id' ' --filters=status=COMPLETE' ' --filters=status=FAILED' ' --tags=tag1,tag2' ' --tags-any=tag3,tag4' ' --not-tags=tag5,tag6' ' --not-tags-any=tag7,tag8' ' --global-tenant' ' --show-deleted' ' --show-hidden' ' --sort-keys=stack_name;creation_time' ' --sort-keys=updated_time' ' --sort-dir=asc') required = [ 'stack_owner', 'project', 'testproject', 'teststack', 'teststack2', ] for r in required: self.assertRegex(list_text, r) self.assertNotRegex(list_text, 'parent') if self.jreq_mock.call_args is None: self.assertEqual(1, self.session_jreq_mock.call_count) url, method = self.session_jreq_mock.call_args[0] else: self.assertEqual(1, self.jreq_mock.call_count) method, url = self.jreq_mock.call_args[0] self.assertEqual('GET', method) base_url, query_params = utils.parse_query_url(url) self.assertEqual('/stacks', base_url) expected_query_dict = {'limit': ['2'], 'status': ['COMPLETE', 'FAILED'], 'marker': ['fake_id'], 'tags': ['tag1,tag2'], 'tags_any': ['tag3,tag4'], 'not_tags': ['tag5,tag6'], 'not_tags_any': ['tag7,tag8'], 'global_tenant': ['True'], 'show_deleted': ['True'], 'show_hidden': ['True'], 'sort_keys': ['stack_name', 'creation_time', 'updated_time'], 'sort_dir': ['asc']} self.assertEqual(expected_query_dict, query_params) class MockShellTestToken(MockShellTestUserPass): # Rerun all ShellTestUserPass test with token auth def setUp(self): self.token = 'a_token' super(MockShellTestToken, self).setUp() def _set_fake_env(self): fake_env = { 'OS_AUTH_TOKEN': self.token, 'OS_TENANT_ID': 'tenant_id', 'OS_AUTH_URL': BASE_URL, # Note we also set username/password, because create/update # pass them even if we have a token to support storing credentials # Hopefully at some point we can remove this and move to only # storing trust id's in heat-engine instead.. 'OS_USERNAME': 'username', 'OS_PASSWORD': 'password' } self.set_fake_env(fake_env) class MockShellTestUserPassKeystoneV3(MockShellTestUserPass): def _set_fake_env(self): self.set_fake_env(FAKE_ENV_KEYSTONE_V3) class MockShellTestStandaloneToken(MockShellTestUserPass): # Rerun all ShellTestUserPass test in standalone mode, where we # specify --os-no-client-auth, a token and Heat endpoint def setUp(self): self.token = 'a_token' super(MockShellTestStandaloneToken, self).setUp() def _set_fake_env(self): fake_env = { 'OS_AUTH_TOKEN': self.token, 'OS_NO_CLIENT_AUTH': 'True', 'HEAT_URL': 'http://no.where', 'OS_AUTH_URL': BASE_URL, # Note we also set username/password, because create/update # pass them even if we have a token to support storing credentials # Hopefully at some point we can remove this and move to only # storing trust id's in heat-engine instead.. 'OS_USERNAME': 'username', 'OS_PASSWORD': 'password' } self.set_fake_env(fake_env) class ShellTestManageService(ShellBase): def setUp(self): super(ShellTestManageService, self).setUp() self.set_fake_env(FAKE_ENV_KEYSTONE_V2) def _set_fake_env(self): '''Patch os.environ to avoid required auth info.''' self.set_fake_env(FAKE_ENV_KEYSTONE_V2) def _test_error_case(self, code, message): self.register_keystone_auth_fixture() resp_dict = { 'explanation': '', 'code': code, 'error': { 'message': message, 'type': '', 'traceback': '', }, 'title': 'test title' } resp_string = jsonutils.dumps(resp_dict) resp = fakes.FakeHTTPResponse( code, 'test reason', {'content-type': 'application/json'}, resp_string) self.mock_request_error('/services', 'GET', exc.from_response(resp)) exc.verbose = 1 e = self.assertRaises(exc.HTTPException, self.shell, "service-list") self.assertIn(message, str(e)) def test_service_list(self): self.register_keystone_auth_fixture() resp_dict = { 'services': [ { "status": "up", "binary": "heat-engine", "engine_id": "9d9242c3-4b9e-45e1-9e74-7615fbf20e5d", "hostname": "mrkanag", "updated_at": "2015-02-03T05:57:59.000000", "topic": "engine", "host": "engine-1" } ] } self.mock_request_get('/services', resp_dict) services_text = self.shell('service-list') required = [ 'hostname', 'binary', 'engine_id', 'host', 'topic', 'updated_at', 'status' ] for r in required: self.assertRegex(services_text, r) def test_service_list_503(self): self._test_error_case( message='All heat engines are down', code=503) def test_service_list_403(self): self._test_error_case( message=('You are not authorized to ' 'complete this action'), code=403)