From 8885ce78189ac2239ec0400f6fec734627bf1218 Mon Sep 17 00:00:00 2001 From: shizhihui Date: Wed, 2 Nov 2016 16:09:48 +0800 Subject: [PATCH 001/246] Make method import_versioned_module work Oslo.utils 3.17 provides similar function, just use it. For more information: [1] http://docs.openstack.org/developer/oslo.utils/history.html Closes-Bug: #1627313 Change-Id: Ie738e910cc72535fe97295d7e118eda2412ecd15 --- heatclient/client.py | 5 +++-- heatclient/common/utils.py | 8 -------- heatclient/shell.py | 3 ++- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/heatclient/client.py b/heatclient/client.py index 8237b8e1..77444f43 100644 --- a/heatclient/client.py +++ b/heatclient/client.py @@ -10,10 +10,11 @@ # License for the specific language governing permissions and limitations # under the License. -from heatclient.common import utils +from oslo_utils import importutils def Client(version, *args, **kwargs): - module = utils.import_versioned_module(version, 'client') + module = importutils.import_versioned_module('heatclient', + version, 'client') client_class = getattr(module, 'Client') return client_class(*args, **kwargs) diff --git a/heatclient/common/utils.py b/heatclient/common/utils.py index 91410acd..dfc8378e 100644 --- a/heatclient/common/utils.py +++ b/heatclient/common/utils.py @@ -21,7 +21,6 @@ from oslo_serialization import jsonutils from oslo_utils import encodeutils -from oslo_utils import importutils import prettytable import six from six.moves.urllib import error @@ -239,13 +238,6 @@ def find_resource(manager, name_or_id): raise exc.CommandError(msg) -def import_versioned_module(version, submodule=None): - module = 'heatclient.v%s' % version - if submodule: - module = '.'.join((module, submodule)) - return importutils.import_module(module) - - def format_parameters(params, parse_semicolon=True): '''Reformat parameters into dict of format expected by the API.''' diff --git a/heatclient/shell.py b/heatclient/shell.py index 558fb8a8..be9cf4e2 100644 --- a/heatclient/shell.py +++ b/heatclient/shell.py @@ -348,7 +348,8 @@ def get_subcommand_parser(self, version): self.subcommands = {} subparsers = parser.add_subparsers(metavar='') - submodule = utils.import_versioned_module(version, 'shell') + submodule = importutils.import_versioned_module('heatclient', + version, 'shell') self._find_actions(subparsers, submodule) self._find_actions(subparsers, self) self._add_bash_completion_subparser(subparsers) From 52b8935be4809d1ad3c9e2cdf5d33f98ffb1d106 Mon Sep 17 00:00:00 2001 From: shizhihui Date: Wed, 16 Nov 2016 13:06:10 +0800 Subject: [PATCH 002/246] Replace mox3 with mock In heatclient, some unit tests still use mox3, which will make the test speed much slow, so I decide to remove it. The first file: heatclient/tests/unit/test_events.py Change-Id: Ife264461d64b0cb89b251a0aefe0e5f255b39ce3 Implements: bp remove-mox3-in-heatclient --- heatclient/tests/unit/test_events.py | 120 +++++++++++++-------------- 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/heatclient/tests/unit/test_events.py b/heatclient/tests/unit/test_events.py index f6071a5b..1d4d84d7 100644 --- a/heatclient/tests/unit/test_events.py +++ b/heatclient/tests/unit/test_events.py @@ -13,7 +13,6 @@ # under the License. import mock -import mox import testtools from heatclient.common import utils @@ -22,39 +21,39 @@ class EventManagerTest(testtools.TestCase): - def setUp(self): - super(EventManagerTest, self).setUp() - self.m = mox.Mox() - self.addCleanup(self.m.VerifyAll) - self.addCleanup(self.m.UnsetStubs) - def test_list_event(self): stack_id = 'teststack', resource_name = 'testresource' manager = events.EventManager(None) - self.m.StubOutWithMock(manager, '_resolve_stack_id') - manager._resolve_stack_id(stack_id).AndReturn('teststack/abcd1234') - self.m.ReplayAll() - manager._list = mock.MagicMock() - manager.list(stack_id, resource_name) - # Make sure url is correct. - manager._list.assert_called_once_with('/stacks/teststack%2Fabcd1234/' - 'resources/testresource/events', - "events") + with mock.patch('heatclient.v1.events.EventManager._resolve_stack_id')\ + as mock_re: + mock_re.return_value = 'teststack/abcd1234' + + manager._list = mock.MagicMock() + manager.list(stack_id, resource_name) + # Make sure url is correct. + manager._list.assert_called_once_with( + '/stacks/teststack%2Fabcd1234/' + 'resources/testresource/events', + "events") + mock_re.assert_called_once_with(stack_id) def test_list_event_with_unicode_resource_name(self): stack_id = 'teststack', resource_name = u'\u5de5\u4f5c' manager = events.EventManager(None) - self.m.StubOutWithMock(manager, '_resolve_stack_id') - manager._resolve_stack_id(stack_id).AndReturn('teststack/abcd1234') - self.m.ReplayAll() - manager._list = mock.MagicMock() - manager.list(stack_id, resource_name) - # Make sure url is correct. - manager._list.assert_called_once_with('/stacks/teststack%2Fabcd1234/' - 'resources/%E5%B7%A5%E4%BD%9C/' - 'events', "events") + with mock.patch('heatclient.v1.events.EventManager._resolve_stack_id')\ + as mock_re: + mock_re.return_value = 'teststack/abcd1234' + + manager._list = mock.MagicMock() + manager.list(stack_id, resource_name) + # Make sure url is correct. + manager._list.assert_called_once_with( + '/stacks/teststack%2Fabcd1234/' + 'resources/%E5%B7%A5%E4%BD%9C/' + 'events', "events") + mock_re.assert_called_once_with(stack_id) def test_list_event_with_none_resource_name(self): stack_id = 'teststack', @@ -75,28 +74,33 @@ def test_list_event_with_kwargs(self): 'resource_status': 'COMPLETE' }} manager = events.EventManager(None) - self.m.StubOutWithMock(manager, '_resolve_stack_id') - manager._resolve_stack_id(stack_id).AndReturn('teststack/abcd1234') - self.m.ReplayAll() - manager._list = mock.MagicMock() - manager.list(stack_id, resource_name, **kwargs) - # Make sure url is correct. - self.assertEqual(1, manager._list.call_count) - args = manager._list.call_args - self.assertEqual(2, len(args[0])) - url, param = args[0] - self.assertEqual("events", param) - base_url, query_params = utils.parse_query_url(url) - expected_base_url = ('/stacks/teststack%2Fabcd1234/' - 'resources/testresource/events') - self.assertEqual(expected_base_url, base_url) - expected_query_dict = {'marker': ['6d6935f4-0ae5'], - 'limit': ['2'], - 'resource_action': ['CREATE'], - 'resource_status': ['COMPLETE']} - self.assertEqual(expected_query_dict, query_params) - - def test_get_event(self): + manager = events.EventManager(None) + with mock.patch('heatclient.v1.events.EventManager._resolve_stack_id')\ + as mock_re: + mock_re.return_value = 'teststack/abcd1234' + + manager._list = mock.MagicMock() + manager.list(stack_id, resource_name, **kwargs) + # Make sure url is correct. + self.assertEqual(1, manager._list.call_count) + args = manager._list.call_args + self.assertEqual(2, len(args[0])) + url, param = args[0] + self.assertEqual("events", param) + base_url, query_params = utils.parse_query_url(url) + expected_base_url = ('/stacks/teststack%2Fabcd1234/' + 'resources/testresource/events') + self.assertEqual(expected_base_url, base_url) + expected_query_dict = {'marker': ['6d6935f4-0ae5'], + 'limit': ['2'], + 'resource_action': ['CREATE'], + 'resource_status': ['COMPLETE']} + self.assertEqual(expected_query_dict, query_params) + mock_re.assert_called_once_with(stack_id) + + @mock.patch('heatclient.v1.events.EventManager._resolve_stack_id') + @mock.patch('heatclient.common.utils.get_response_body') + def test_get_event(self, mock_utils, mock_re): fields = {'stack_id': 'teststack', 'resource_name': 'testresource', 'event_id': '1'} @@ -116,15 +120,14 @@ def get(self, *args, **kwargs): manager = events.EventManager(FakeAPI()) with mock.patch('heatclient.v1.events.Event'): - self.m.StubOutWithMock(manager, '_resolve_stack_id') - self.m.StubOutWithMock(utils, 'get_response_body') - utils.get_response_body(mox.IgnoreArg()).AndReturn({'event': []}) - manager._resolve_stack_id('teststack').AndReturn( - 'teststack/abcd1234') - self.m.ReplayAll() + mock_utils.return_value = {'event': []} + mock_re.return_value = 'teststack/abcd1234' manager.get(**fields) + mock_re.assert_called_once_with('teststack') - def test_get_event_with_unicode_resource_name(self): + @mock.patch('heatclient.v1.events.EventManager._resolve_stack_id') + @mock.patch('heatclient.common.utils.get_response_body') + def test_get_event_with_unicode_resource_name(self, mock_utils, mock_re): fields = {'stack_id': 'teststack', 'resource_name': u'\u5de5\u4f5c', 'event_id': '1'} @@ -144,10 +147,7 @@ def get(self, *args, **kwargs): manager = events.EventManager(FakeAPI()) with mock.patch('heatclient.v1.events.Event'): - self.m.StubOutWithMock(manager, '_resolve_stack_id') - self.m.StubOutWithMock(utils, 'get_response_body') - utils.get_response_body(mox.IgnoreArg()).AndReturn({'event': []}) - manager._resolve_stack_id('teststack').AndReturn( - 'teststack/abcd1234') - self.m.ReplayAll() + mock_utils.return_value = {'event': []} + mock_re.return_value = 'teststack/abcd1234' manager.get(**fields) + mock_re.assert_called_once_with('teststack') From 16d86599c31d611a4c32ee5ff07eeefac50ac2a4 Mon Sep 17 00:00:00 2001 From: shizhihui Date: Wed, 16 Nov 2016 13:13:49 +0800 Subject: [PATCH 003/246] Replace mox3 with mock In heatclient, some unit tests still use mox3, which will make the test speed much slow, so I decide to remove it. The modify file: heatclient/tests/unit/test_common_http.py Change-Id: I220fabedb8e9fcc8eeea9d5e98b3ddeed68f8a43 Implements: bp remove-mox3-in-heatclient --- heatclient/tests/unit/test_common_http.py | 679 ++++++++++------------ 1 file changed, 297 insertions(+), 382 deletions(-) diff --git a/heatclient/tests/unit/test_common_http.py b/heatclient/tests/unit/test_common_http.py index 9ccc24a3..8ce6f18e 100644 --- a/heatclient/tests/unit/test_common_http.py +++ b/heatclient/tests/unit/test_common_http.py @@ -12,15 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -import logging -import os import socket from keystoneauth1 import adapter import mock -import mox from oslo_serialization import jsonutils -import requests import six import testtools @@ -30,138 +26,108 @@ from heatclient.tests.unit import fakes +@mock.patch('heatclient.common.http.requests.request') class HttpClientTest(testtools.TestCase): - # Patch os.environ to avoid required auth info. - def setUp(self): - super(HttpClientTest, self).setUp() - self.m = mox.Mox() - self.m.StubOutWithMock(requests, 'request') - self.addCleanup(self.m.VerifyAll) - self.addCleanup(self.m.UnsetStubs) - - def test_http_raw_request(self): + def test_http_raw_request(self, mock_request): headers = {'Content-Type': 'application/octet-stream', 'User-Agent': 'python-heatclient'} # Record a 200 - mock_conn = http.requests.request('GET', 'http://example.com:8004', - allow_redirects=False, - headers=headers) - mock_conn.AndReturn( - fakes.FakeHTTPResponse( - 200, 'OK', - {'content-type': 'application/octet-stream'}, - '')) + mock_request.return_value = fakes.FakeHTTPResponse( + 200, 'OK', + {'content-type': 'application/octet-stream'}, + '') # Replay, create client, assert - self.m.ReplayAll() client = http.HTTPClient('http://example.com:8004') resp = client.raw_request('GET', '') self.assertEqual(200, resp.status_code) self.assertEqual('', ''.join([x for x in resp.content])) + mock_request.assert_called_with('GET', 'http://example.com:8004', + allow_redirects=False, + headers=headers) - def test_token_or_credentials(self): + def test_token_or_credentials(self, mock_request): # Record a 200 fake200 = fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/octet-stream'}, '') + mock_request.return_value = fake200 # no token or credentials - mock_conn = http.requests.request( - 'GET', 'http://example.com:8004', - allow_redirects=False, - headers={'Content-Type': 'application/octet-stream', - 'User-Agent': 'python-heatclient'}) - mock_conn.AndReturn(fake200) - - # credentials - mock_conn = http.requests.request( - 'GET', 'http://example.com:8004', - allow_redirects=False, - headers={'Content-Type': 'application/octet-stream', - 'User-Agent': 'python-heatclient', - 'X-Auth-Key': 'pass', - 'X-Auth-User': 'user'}) - mock_conn.AndReturn(fake200) - - # token suppresses credentials - mock_conn = http.requests.request( - 'GET', 'http://example.com:8004', - allow_redirects=False, - headers={'Content-Type': 'application/octet-stream', - 'User-Agent': 'python-heatclient', - 'X-Auth-Token': 'abcd1234'}) - mock_conn.AndReturn(fake200) - - # Replay, create client, assert - self.m.ReplayAll() client = http.HTTPClient('http://example.com:8004') resp = client.raw_request('GET', '') self.assertEqual(200, resp.status_code) - + # credentials client.username = 'user' client.password = 'pass' resp = client.raw_request('GET', '') self.assertEqual(200, resp.status_code) - + # token suppresses credentials client.auth_token = 'abcd1234' resp = client.raw_request('GET', '') self.assertEqual(200, resp.status_code) - - def test_include_pass(self): + mock_request.assert_has_calls([ + mock.call('GET', 'http://example.com:8004', + allow_redirects=False, + headers={'Content-Type': 'application/octet-stream', + 'User-Agent': 'python-heatclient'}), + mock.call('GET', 'http://example.com:8004', + allow_redirects=False, + headers={'Content-Type': 'application/octet-stream', + 'User-Agent': 'python-heatclient', + 'X-Auth-Key': 'pass', + 'X-Auth-User': 'user'}), + mock.call('GET', 'http://example.com:8004', + allow_redirects=False, + headers={'Content-Type': 'application/octet-stream', + 'User-Agent': 'python-heatclient', + 'X-Auth-Token': 'abcd1234'}) + ]) + + def test_include_pass(self, mock_request): # Record a 200 fake200 = fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/octet-stream'}, '') - + mock_request.return_value = fake200 # no token or credentials - mock_conn = http.requests.request( - 'GET', 'http://example.com:8004', - allow_redirects=False, - headers={'Content-Type': 'application/octet-stream', - 'User-Agent': 'python-heatclient'}) - mock_conn.AndReturn(fake200) - - # credentials - mock_conn = http.requests.request( - 'GET', 'http://example.com:8004', - allow_redirects=False, - headers={'Content-Type': 'application/octet-stream', - 'User-Agent': 'python-heatclient', - 'X-Auth-Key': 'pass', - 'X-Auth-User': 'user'}) - mock_conn.AndReturn(fake200) - - # token suppresses credentials - mock_conn = http.requests.request( - 'GET', 'http://example.com:8004', - allow_redirects=False, - headers={'Content-Type': 'application/octet-stream', - 'User-Agent': 'python-heatclient', - 'X-Auth-Token': 'abcd1234', - 'X-Auth-Key': 'pass', - 'X-Auth-User': 'user'}) - mock_conn.AndReturn(fake200) - - # Replay, create client, assert - self.m.ReplayAll() client = http.HTTPClient('http://example.com:8004') resp = client.raw_request('GET', '') self.assertEqual(200, resp.status_code) - + # credentials client.username = 'user' client.password = 'pass' client.include_pass = True resp = client.raw_request('GET', '') self.assertEqual(200, resp.status_code) - + # token suppresses credentials client.auth_token = 'abcd1234' resp = client.raw_request('GET', '') self.assertEqual(200, resp.status_code) - - def test_not_include_pass(self): + mock_request.assert_has_calls([ + mock.call('GET', 'http://example.com:8004', + allow_redirects=False, + headers={'Content-Type': 'application/octet-stream', + 'User-Agent': 'python-heatclient'}), + mock.call('GET', 'http://example.com:8004', + allow_redirects=False, + headers={'Content-Type': 'application/octet-stream', + 'User-Agent': 'python-heatclient', + 'X-Auth-Key': 'pass', + 'X-Auth-User': 'user'}), + mock.call('GET', 'http://example.com:8004', + allow_redirects=False, + headers={'Content-Type': 'application/octet-stream', + 'User-Agent': 'python-heatclient', + 'X-Auth-Token': 'abcd1234', + 'X-Auth-Key': 'pass', + 'X-Auth-User': 'user'}) + ]) + + def test_not_include_pass(self, mock_request): # Record a 200 fake500 = fakes.FakeHTTPResponse( 500, 'ERROR', @@ -169,21 +135,20 @@ def test_not_include_pass(self): '(HTTP 401)') # no token or credentials - mock_conn = http.requests.request( - 'GET', 'http://example.com:8004', - allow_redirects=False, - headers={'Content-Type': 'application/octet-stream', - 'User-Agent': 'python-heatclient'}) - mock_conn.AndReturn(fake500) + mock_request.return_value = fake500 # Replay, create client, assert - self.m.ReplayAll() client = http.HTTPClient('http://example.com:8004') e = self.assertRaises(exc.HTTPUnauthorized, client.raw_request, 'GET', '') self.assertIn('Authentication failed', str(e)) + mock_request.assert_called_with( + 'GET', 'http://example.com:8004', + allow_redirects=False, + headers={'Content-Type': 'application/octet-stream', + 'User-Agent': 'python-heatclient'}) - def test_region_name(self): + def test_region_name(self, mock_request): # Record a 200 fake200 = fakes.FakeHTTPResponse( 200, 'OK', @@ -191,61 +156,46 @@ def test_region_name(self): '') # Specify region name - mock_conn = http.requests.request( - 'GET', 'http://example.com:8004', - allow_redirects=False, - headers={'Content-Type': 'application/octet-stream', - 'X-Region-Name': 'RegionOne', - 'User-Agent': 'python-heatclient'}) - mock_conn.AndReturn(fake200) + mock_request.return_value = fake200 # Replay, create client, assert - self.m.ReplayAll() client = http.HTTPClient('http://example.com:8004') client.region_name = 'RegionOne' resp = client.raw_request('GET', '') self.assertEqual(200, resp.status_code) - - def test_http_json_request(self): - # Record a 200 - mock_conn = http.requests.request( + mock_request.assert_called_with( 'GET', 'http://example.com:8004', allow_redirects=False, - headers={'Content-Type': 'application/json', - 'Accept': 'application/json', + headers={'Content-Type': 'application/octet-stream', + 'X-Region-Name': 'RegionOne', 'User-Agent': 'python-heatclient'}) - mock_conn.AndReturn( - fakes.FakeHTTPResponse( - 200, 'OK', - {'content-type': 'application/json'}, - '{}')) + + def test_http_json_request(self, mock_request): + # Record a 200 + mock_request.return_value = fakes.FakeHTTPResponse( + 200, 'OK', + {'content-type': 'application/json'}, + '{}') # Replay, create client, assert - self.m.ReplayAll() client = http.HTTPClient('http://example.com:8004') resp, body = client.json_request('GET', '') self.assertEqual(200, resp.status_code) self.assertEqual({}, body) - - def test_http_json_request_argument_passed_to_requests(self): - """Check that we have sent the proper arguments to requests.""" - # Record a 200 - mock_conn = http.requests.request( + mock_request.assert_called_with( 'GET', 'http://example.com:8004', allow_redirects=False, - cert=('RANDOM_CERT_FILE', 'RANDOM_KEY_FILE'), - verify=True, - data='"text"', headers={'Content-Type': 'application/json', 'Accept': 'application/json', - 'X-Auth-Url': 'http://AUTH_URL', 'User-Agent': 'python-heatclient'}) - mock_conn.AndReturn( - fakes.FakeHTTPResponse( - 200, 'OK', - {'content-type': 'application/json'}, - '{}')) - # Replay, create client, assert - self.m.ReplayAll() + + def test_http_json_request_argument_passed_to_requests(self, mock_request): + """Check that we have sent the proper arguments to requests.""" + # Record a 200 + mock_request.return_value = fakes.FakeHTTPResponse( + 200, 'OK', + {'content-type': 'application/json'}, + '{}') + client = http.HTTPClient('http://example.com:8004') client.verify_cert = True client.cert_file = 'RANDOM_CERT_FILE' @@ -254,395 +204,360 @@ def test_http_json_request_argument_passed_to_requests(self): resp, body = client.json_request('GET', '', data='text') self.assertEqual(200, resp.status_code) self.assertEqual({}, body) - - def test_http_json_request_w_req_body(self): - # Record a 200 - mock_conn = http.requests.request( + mock_request.assert_called_with( 'GET', 'http://example.com:8004', - body='test-body', allow_redirects=False, + cert=('RANDOM_CERT_FILE', 'RANDOM_KEY_FILE'), + verify=True, + data='"text"', headers={'Content-Type': 'application/json', 'Accept': 'application/json', + 'X-Auth-Url': 'http://AUTH_URL', 'User-Agent': 'python-heatclient'}) - mock_conn.AndReturn( - fakes.FakeHTTPResponse( - 200, 'OK', - {'content-type': 'application/json'}, - '{}')) + + def test_http_json_request_w_req_body(self, mock_request): + # Record a 200 + mock_request.return_value = fakes.FakeHTTPResponse( + 200, 'OK', + {'content-type': 'application/json'}, + '{}') # Replay, create client, assert - self.m.ReplayAll() client = http.HTTPClient('http://example.com:8004') resp, body = client.json_request('GET', '', body='test-body') self.assertEqual(200, resp.status_code) - self.assertEqual({}, body) - - def test_http_json_request_non_json_resp_cont_type(self): - # Record a 200 - mock_conn = http.requests.request( - 'GET', 'http://example.com:8004', body='test-body', + mock_request.assert_called_with( + 'GET', 'http://example.com:8004', + body='test-body', allow_redirects=False, headers={'Content-Type': 'application/json', 'Accept': 'application/json', 'User-Agent': 'python-heatclient'}) - mock_conn.AndReturn( - fakes.FakeHTTPResponse( - 200, 'OK', - {'content-type': 'not/json'}, - {})) + + def test_http_json_request_non_json_resp_cont_type(self, mock_request): + # Record a 200i + mock_request.return_value = fakes.FakeHTTPResponse( + 200, 'OK', + {'content-type': 'not/json'}, + '{}') # Replay, create client, assert - self.m.ReplayAll() client = http.HTTPClient('http://example.com:8004') resp, body = client.json_request('GET', '', body='test-body') self.assertEqual(200, resp.status_code) self.assertIsNone(body) - - def test_http_json_request_invalid_json(self): - # Record a 200 - mock_conn = http.requests.request( + mock_request.assert_called_with( 'GET', 'http://example.com:8004', + body='test-body', allow_redirects=False, headers={'Content-Type': 'application/json', 'Accept': 'application/json', 'User-Agent': 'python-heatclient'}) - mock_conn.AndReturn( - fakes.FakeHTTPResponse( - 200, 'OK', - {'content-type': 'application/json'}, - 'invalid-json')) + + def test_http_json_request_invalid_json(self, mock_request): + # Record a 200 + mock_request.return_value = fakes.FakeHTTPResponse( + 200, 'OK', + {'content-type': 'application/json'}, + 'invalid-json') + # Replay, create client, assert - self.m.ReplayAll() client = http.HTTPClient('http://example.com:8004') resp, body = client.json_request('GET', '') self.assertEqual(200, resp.status_code) self.assertEqual('invalid-json', body) - - def test_http_manual_redirect_delete(self): - mock_conn = http.requests.request( - 'DELETE', 'http://example.com:8004/foo', + mock_request.assert_called_with( + 'GET', 'http://example.com:8004', allow_redirects=False, headers={'Content-Type': 'application/json', 'Accept': 'application/json', 'User-Agent': 'python-heatclient'}) - mock_conn.AndReturn( + + def test_http_manual_redirect_delete(self, mock_request): + mock_request.side_effect = [ fakes.FakeHTTPResponse( 302, 'Found', {'location': 'http://example.com:8004/foo/bar'}, - '')) - mock_conn = http.requests.request( - 'DELETE', 'http://example.com:8004/foo/bar', - allow_redirects=False, - headers={'Content-Type': 'application/json', - 'Accept': 'application/json', - 'User-Agent': 'python-heatclient'}) - mock_conn.AndReturn( + ''), fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/json'}, - '{}')) - - self.m.ReplayAll() + 'invalid-json') + ] client = http.HTTPClient('http://example.com:8004/foo') resp, body = client.json_request('DELETE', '') - self.assertEqual(200, resp.status_code) - - def test_http_manual_redirect_post(self): - mock_conn = http.requests.request( - 'POST', 'http://example.com:8004/foo', - allow_redirects=False, - headers={'Content-Type': 'application/json', - 'Accept': 'application/json', - 'User-Agent': 'python-heatclient'}) - mock_conn.AndReturn( + mock_request.assert_has_calls([ + mock.call('DELETE', 'http://example.com:8004/foo', + allow_redirects=False, + headers={'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'python-heatclient'}), + mock.call('DELETE', 'http://example.com:8004/foo/bar', + allow_redirects=False, + headers={'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'python-heatclient'}) + ]) + + def test_http_manual_redirect_post(self, mock_request): + mock_request.side_effect = [ fakes.FakeHTTPResponse( 302, 'Found', {'location': 'http://example.com:8004/foo/bar'}, - '')) - mock_conn = http.requests.request( - 'POST', 'http://example.com:8004/foo/bar', - allow_redirects=False, - headers={'Content-Type': 'application/json', - 'Accept': 'application/json', - 'User-Agent': 'python-heatclient'}) - mock_conn.AndReturn( + ''), fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/json'}, - '{}')) - - self.m.ReplayAll() + 'invalid-json') + ] client = http.HTTPClient('http://example.com:8004/foo') resp, body = client.json_request('POST', '') - self.assertEqual(200, resp.status_code) - - def test_http_manual_redirect_put(self): - mock_conn = http.requests.request( - 'PUT', 'http://example.com:8004/foo', - allow_redirects=False, - headers={'Content-Type': 'application/json', - 'Accept': 'application/json', - 'User-Agent': 'python-heatclient'}) - mock_conn.AndReturn( + mock_request.assert_has_calls([ + mock.call('POST', 'http://example.com:8004/foo', + allow_redirects=False, + headers={'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'python-heatclient'}), + mock.call('POST', 'http://example.com:8004/foo/bar', + allow_redirects=False, + headers={'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'python-heatclient'}) + ]) + + def test_http_manual_redirect_put(self, mock_request): + mock_request.side_effect = [ fakes.FakeHTTPResponse( 302, 'Found', {'location': 'http://example.com:8004/foo/bar'}, - '')) - mock_conn = http.requests.request( - 'PUT', 'http://example.com:8004/foo/bar', - allow_redirects=False, - headers={'Content-Type': 'application/json', - 'Accept': 'application/json', - 'User-Agent': 'python-heatclient'}) - mock_conn.AndReturn( + ''), fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/json'}, - '{}')) - - self.m.ReplayAll() + 'invalid-json') + ] client = http.HTTPClient('http://example.com:8004/foo') resp, body = client.json_request('PUT', '') - self.assertEqual(200, resp.status_code) - - def test_http_manual_redirect_put_uppercase(self): - mock_conn = http.requests.request( - 'PUT', 'http://EXAMPLE.com:8004/foo', - allow_redirects=False, - headers={'Content-Type': 'application/json', - 'Accept': 'application/json', - 'User-Agent': 'python-heatclient'}) - mock_conn.AndReturn( + mock_request.assert_has_calls([ + mock.call('PUT', 'http://example.com:8004/foo', + allow_redirects=False, + headers={'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'python-heatclient'}), + mock.call('PUT', 'http://example.com:8004/foo/bar', + allow_redirects=False, + headers={'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'python-heatclient'}) + ]) + + def test_http_manual_redirect_put_uppercase(self, mock_request): + mock_request.side_effect = [ fakes.FakeHTTPResponse( 302, 'Found', {'location': 'http://example.com:8004/foo/bar'}, - '')) - mock_conn = http.requests.request( - 'PUT', 'http://example.com:8004/foo/bar', - allow_redirects=False, - headers={'Content-Type': 'application/json', - 'Accept': 'application/json', - 'User-Agent': 'python-heatclient'}) - mock_conn.AndReturn( + ''), fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/json'}, - '{}')) - - self.m.ReplayAll() - + 'invalid-json') + ] client = http.HTTPClient('http://EXAMPLE.com:8004/foo') resp, body = client.json_request('PUT', '') - self.assertEqual(200, resp.status_code) - def test_http_manual_redirect_error_without_location(self): - mock_conn = http.requests.request( - 'DELETE', 'http://example.com:8004/foo', - allow_redirects=False, - headers={'Content-Type': 'application/json', - 'Accept': 'application/json', - 'User-Agent': 'python-heatclient'}) - mock_conn.AndReturn( - fakes.FakeHTTPResponse( - 302, 'Found', - {}, - '')) - self.m.ReplayAll() + mock_request.assert_has_calls([ + mock.call('PUT', 'http://EXAMPLE.com:8004/foo', + allow_redirects=False, + headers={'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'python-heatclient'}), + mock.call('PUT', 'http://example.com:8004/foo/bar', + allow_redirects=False, + headers={'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'python-heatclient'}) + ]) + + def test_http_manual_redirect_error_without_location(self, mock_request): + mock_request.return_value = fakes.FakeHTTPResponse( + 302, 'Found', + {}, + '') client = http.HTTPClient('http://example.com:8004/foo') self.assertRaises(exc.InvalidEndpoint, client.json_request, 'DELETE', '') - - def test_http_json_request_redirect(self): - # Record the 302 - mock_conn = http.requests.request( - 'GET', 'http://example.com:8004', + mock_request.assert_called_once_with( + 'DELETE', 'http://example.com:8004/foo', allow_redirects=False, headers={'Content-Type': 'application/json', 'Accept': 'application/json', 'User-Agent': 'python-heatclient'}) - mock_conn.AndReturn( + + def test_http_json_request_redirect(self, mock_request): + # Record the 302 + mock_request.side_effect = [ fakes.FakeHTTPResponse( 302, 'Found', {'location': 'http://example.com:8004'}, - '')) - # Record the following 200 - mock_conn = http.requests.request( - 'GET', 'http://example.com:8004', - allow_redirects=False, - headers={'Content-Type': 'application/json', - 'Accept': 'application/json', - 'User-Agent': 'python-heatclient'}) - mock_conn.AndReturn( + ''), fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/json'}, - '{}')) - # Replay, create client, assert - self.m.ReplayAll() + '{}') + ] client = http.HTTPClient('http://example.com:8004') resp, body = client.json_request('GET', '') self.assertEqual(200, resp.status_code) self.assertEqual({}, body) - - def test_http_404_json_request(self): + mock_request.assert_has_calls([ + mock.call('GET', 'http://example.com:8004', + allow_redirects=False, + headers={'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'python-heatclient'}), + mock.call('GET', 'http://example.com:8004', + allow_redirects=False, + headers={'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'python-heatclient'}) + ]) + + def test_http_404_json_request(self, mock_request): # Record a 404 - mock_conn = http.requests.request( - 'GET', 'http://example.com:8004', - allow_redirects=False, - headers={'Content-Type': 'application/json', - 'Accept': 'application/json', - 'User-Agent': 'python-heatclient'}) - mock_conn.AndReturn( - fakes.FakeHTTPResponse( - 404, 'OK', {'content-type': 'application/json'}, - '{}')) - # Replay, create client, assert - self.m.ReplayAll() + mock_request.return_value = fakes.FakeHTTPResponse( + 404, 'OK', {'content-type': 'application/json'}, + '{}') client = http.HTTPClient('http://example.com:8004') e = self.assertRaises(exc.HTTPNotFound, client.json_request, 'GET', '') # Assert that the raised exception can be converted to string self.assertIsNotNone(str(e)) - - def test_http_300_json_request(self): - # Record a 300 - mock_conn = http.requests.request( + mock_request.assert_called_with( 'GET', 'http://example.com:8004', allow_redirects=False, headers={'Content-Type': 'application/json', 'Accept': 'application/json', 'User-Agent': 'python-heatclient'}) - mock_conn.AndReturn( - fakes.FakeHTTPResponse( - 300, 'OK', {'content-type': 'application/json'}, - '{}')) - # Replay, create client, assert - self.m.ReplayAll() + + def test_http_300_json_request(self, mock_request): + # Record a 300 + mock_request.return_value = fakes.FakeHTTPResponse( + 300, 'OK', {'content-type': 'application/json'}, + '{}') + # Assert that the raised exception can be converted to string client = http.HTTPClient('http://example.com:8004') e = self.assertRaises( exc.HTTPMultipleChoices, client.json_request, 'GET', '') - # Assert that the raised exception can be converted to string self.assertIsNotNone(str(e)) + mock_request.assert_called_with( + 'GET', 'http://example.com:8004', + allow_redirects=False, + headers={'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'python-heatclient'}) - def test_fake_json_request(self): + def test_fake_json_request(self, mock_request): headers = {'User-Agent': 'python-heatclient'} - mock_conn = http.requests.request('GET', 'fake://example.com:8004/', - allow_redirects=False, - headers=headers) - mock_conn.AndRaise(socket.gaierror) - self.m.ReplayAll() - + mock_request.side_effect = [socket.gaierror] client = http.HTTPClient('fake://example.com:8004') self.assertRaises(exc.InvalidEndpoint, client._http_request, "/", "GET") - def test_debug_curl_command(self): - self.m.StubOutWithMock(logging.Logger, 'debug') - - ssl_connection_params = {'ca_file': 'TEST_CA', - 'cert_file': 'TEST_CERT', - 'key_file': 'TEST_KEY', - 'insecure': 'TEST_NSA'} - - headers = {'key': 'value'} - - mock_logging_debug = logging.Logger.debug( - "curl -g -i -X GET -H 'key: value' --key TEST_KEY " - "--cert TEST_CERT --cacert TEST_CA " - "-k -d 'text' http://foo/bar" - ) - mock_logging_debug.AndReturn(None) - self.m.ReplayAll() - - client = http.HTTPClient('http://foo') - client.ssl_connection_params = ssl_connection_params - client.log_curl_request('GET', '/bar', {'headers': headers, - 'data': 'text'}) - - def test_http_request_socket_error(self): + mock_request.assert_called_with('GET', 'fake://example.com:8004/', + allow_redirects=False, + headers=headers) + + def test_debug_curl_command(self, mock_request): + with mock.patch('logging.Logger.debug') as mock_logging_debug: + + ssl_connection_params = {'ca_file': 'TEST_CA', + 'cert_file': 'TEST_CERT', + 'key_file': 'TEST_KEY', + 'insecure': 'TEST_NSA'} + + headers = {'key': 'value'} + mock_logging_debug.return_value = None + client = http.HTTPClient('http://foo') + client.ssl_connection_params = ssl_connection_params + client.log_curl_request('GET', '/bar', {'headers': headers, + 'data': 'text'}) + mock_logging_debug.assert_called_with( + "curl -g -i -X GET -H 'key: value' --key TEST_KEY " + "--cert TEST_CERT --cacert TEST_CA " + "-k -d 'text' http://foo/bar" + ) + + def test_http_request_socket_error(self, mock_request): headers = {'User-Agent': 'python-heatclient'} - mock_conn = http.requests.request('GET', 'http://example.com:8004/', - allow_redirects=False, - headers=headers) - mock_conn.AndRaise(socket.error) - self.m.ReplayAll() + mock_request.side_effect = [socket.error] client = http.HTTPClient('http://example.com:8004') self.assertRaises(exc.CommunicationError, client._http_request, "/", "GET") + mock_request.assert_called_with('GET', 'http://example.com:8004/', + allow_redirects=False, + headers=headers) - def test_http_request_socket_timeout(self): + def test_http_request_socket_timeout(self, mock_request): headers = {'User-Agent': 'python-heatclient'} - mock_conn = http.requests.request('GET', 'http://example.com:8004/', - allow_redirects=False, - headers=headers) - mock_conn.AndRaise(socket.timeout) - self.m.ReplayAll() + mock_request.side_effect = [socket.timeout] client = http.HTTPClient('http://example.com:8004') self.assertRaises(exc.CommunicationError, client._http_request, "/", "GET") + mock_request.assert_called_with('GET', 'http://example.com:8004/', + allow_redirects=False, + headers=headers) - def test_http_request_specify_timeout(self): - mock_conn = http.requests.request( + def test_http_request_specify_timeout(self, mock_request): + mock_request.return_value = fakes.FakeHTTPResponse( + 200, 'OK', + {'content-type': 'application/json'}, + '{}') + client = http.HTTPClient('http://example.com:8004', timeout='123') + resp, body = client.json_request('GET', '') + self.assertEqual(200, resp.status_code) + mock_request.assert_called_with( 'GET', 'http://example.com:8004', allow_redirects=False, headers={'Content-Type': 'application/json', 'Accept': 'application/json', 'User-Agent': 'python-heatclient'}, timeout=float(123)) - mock_conn.AndReturn( - fakes.FakeHTTPResponse( - 200, 'OK', - {'content-type': 'application/json'}, - '{}')) - # Replay, create client, assert - self.m.ReplayAll() - client = http.HTTPClient('http://example.com:8004', timeout='123') - resp, body = client.json_request('GET', '') - self.assertEqual(200, resp.status_code) - self.assertEqual({}, body) - def test_get_system_ca_file(self): + def test_get_system_ca_file(self, mock_request): chosen = '/etc/ssl/certs/ca-certificates.crt' - self.m.StubOutWithMock(os.path, 'exists') - os.path.exists(chosen).AndReturn(chosen) - self.m.ReplayAll() - - ca = http.get_system_ca_file() - self.assertEqual(chosen, ca) + with mock.patch('os.path.exists') as mock_os: + mock_os.return_value = chosen + ca = http.get_system_ca_file() + self.assertEqual(chosen, ca) + mock_os.assert_called_once_with(chosen) - def test_insecure_verify_cert_None(self): + def test_insecure_verify_cert_None(self, mock_request): client = http.HTTPClient('https://foo', insecure=True) self.assertFalse(client.verify_cert) - def test_passed_cert_to_verify_cert(self): + def test_passed_cert_to_verify_cert(self, mock_request): client = http.HTTPClient('https://foo', ca_file="NOWHERE") self.assertEqual("NOWHERE", client.verify_cert) - self.m.StubOutWithMock(http, 'get_system_ca_file') - http.get_system_ca_file().AndReturn("SOMEWHERE") - self.m.ReplayAll() - client = http.HTTPClient('https://foo') - self.assertEqual("SOMEWHERE", client.verify_cert) + with mock.patch('heatclient.common.http.get_system_ca_file') as gsf: + gsf.return_value = "SOMEWHERE" + client = http.HTTPClient('https://foo') + self.assertEqual("SOMEWHERE", client.verify_cert) - def test_curl_log_i18n_headers(self): - self.m.StubOutWithMock(logging.Logger, 'debug') + @mock.patch('logging.Logger.debug', return_value=None) + def test_curl_log_i18n_headers(self, mock_log, mock_request): kwargs = {'headers': {'Key': b'foo\xe3\x8a\x8e'}} - mock_logging_debug = logging.Logger.debug( - u"curl -g -i -X GET -H 'Key: foo㊎' http://somewhere" - ) - mock_logging_debug.AndReturn(None) - - self.m.ReplayAll() - client = http.HTTPClient('http://somewhere') client.log_curl_request("GET", '', kwargs=kwargs) + mock_log.assert_called_once_with( + u"curl -g -i -X GET -H 'Key: foo㊎' http://somewhere") class SessionClientTest(testtools.TestCase): From 16b50f84dad258467a64ec02b39847ec4451fb62 Mon Sep 17 00:00:00 2001 From: shizhihui Date: Mon, 14 Nov 2016 22:45:47 +0800 Subject: [PATCH 004/246] Add functinoal tests for 'openstack stack snapshot delete' For now, there are several heat osc CLIs which do not have own functional tests in heatclient/tests/functional/osc/v1. I think it is time to add some for heatclient. Add functional test for 'openstack stack snapshot delete' Change-Id: I21257b4f6126b9693256c953ed9335edad53a844 Implements: bp add-functional --- heatclient/tests/functional/osc/v1/test_stack.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/heatclient/tests/functional/osc/v1/test_stack.py b/heatclient/tests/functional/osc/v1/test_stack.py index e62d2b0e..2f985f76 100644 --- a/heatclient/tests/functional/osc/v1/test_stack.py +++ b/heatclient/tests/functional/osc/v1/test_stack.py @@ -66,3 +66,12 @@ def test_stack_delete(self): self._stack_delete(stack['id']) stacks_raw = self.openstack('stack list') self.assertNotIn(stack['id'], stacks_raw) + + def test_stack_snapshot_delete(self): + snapshot_name = utils.rand_name(name='test-stack-snapshot') + stack = self._stack_create_minimal() + snapshot = self._stack_snapshot_create(stack['id'], snapshot_name) + self._stack_snapshot_delete(stack['id'], snapshot['id']) + stacks_raw = self.openstack( + 'stack snapshot list' + ' ' + self.stack_name) + self.assertNotIn(snapshot['id'], stacks_raw) From f6ec0af054c9f7926fcf2e67f8ca229af8ce48df Mon Sep 17 00:00:00 2001 From: shizhihui Date: Sun, 20 Nov 2016 14:32:00 +0800 Subject: [PATCH 005/246] Trivial:remove unused code Change-Id: I8eac6869f0fe05ca348452e67d2b4855f11fbe60 --- heatclient/shell.py | 1 - 1 file changed, 1 deletion(-) diff --git a/heatclient/shell.py b/heatclient/shell.py index 558fb8a8..25938ac9 100644 --- a/heatclient/shell.py +++ b/heatclient/shell.py @@ -32,7 +32,6 @@ from heatclient.common import utils from heatclient import exc -logger = logging.getLogger(__name__) osprofiler_profiler = importutils.try_import("osprofiler.profiler") From eaf87e99dcc48e62e99cbded227e83a0656a4a2d Mon Sep 17 00:00:00 2001 From: PanFengyun Date: Tue, 20 Dec 2016 21:10:40 +0800 Subject: [PATCH 006/246] Replace mox3 with mock In heatclient, some unit tests still use mox3, which will make the test speed much slow, so I decide to remove it. The file: heatclient/tests/unit/test_template_utils.py Implements: bp remove-mox3-in-heatclient Change-Id: I1aa556630ff88b4708834549ce9dc31967e0bb32 Co-author-by: PanFengyun --- heatclient/tests/unit/test_template_utils.py | 508 +++++++++---------- 1 file changed, 230 insertions(+), 278 deletions(-) diff --git a/heatclient/tests/unit/test_template_utils.py b/heatclient/tests/unit/test_template_utils.py index 7de72b57..a48e0b36 100644 --- a/heatclient/tests/unit/test_template_utils.py +++ b/heatclient/tests/unit/test_template_utils.py @@ -15,10 +15,9 @@ import json import tempfile -import mox +import mock import six from six.moves.urllib import error -from six.moves.urllib import request import testtools from testtools import matchers import yaml @@ -32,30 +31,24 @@ class ShellEnvironmentTest(testtools.TestCase): template_a = b'{"heat_template_version": "2013-05-23"}' - def setUp(self): - super(ShellEnvironmentTest, self).setUp() - self.m = mox.Mox() - - self.addCleanup(self.m.VerifyAll) - self.addCleanup(self.m.UnsetStubs) - def collect_links(self, env, content, url, env_base_url=''): - jenv = yaml.safe_load(env) files = {} if url: - self.m.StubOutWithMock(request, 'urlopen') - request.urlopen(url).AndReturn(six.BytesIO(content)) - request.urlopen(url).AndReturn(six.BytesIO(content)) - self.m.ReplayAll() - - template_utils.resolve_environment_urls( - jenv.get('resource_registry'), files, env_base_url) - if url: - self.assertEqual(content.decode('utf-8'), files[url]) + def side_effect(args): + if url == args: + return six.BytesIO(content) + with mock.patch('six.moves.urllib.request.urlopen') as mock_url: + mock_url.side_effect = side_effect + template_utils.resolve_environment_urls( + jenv.get('resource_registry'), files, env_base_url) + self.assertEqual(content.decode('utf-8'), files[url]) + else: + template_utils.resolve_environment_urls( + jenv.get('resource_registry'), files, env_base_url) - def test_ignore_env_keys(self): - self.m.StubOutWithMock(request, 'urlopen') + @mock.patch('six.moves.urllib.request.urlopen') + def test_ignore_env_keys(self, mock_url): env_file = '/home/my/dir/env.yaml' env = b''' resource_registry: @@ -64,9 +57,7 @@ def test_ignore_env_keys(self): hooks: pre_create restricted_actions: replace ''' - request.urlopen('file://%s' % env_file).AndReturn( - six.BytesIO(env)) - self.m.ReplayAll() + mock_url.return_value = six.BytesIO(env) _, env_dict = template_utils.process_environment_and_files( env_file) self.assertEqual( @@ -74,24 +65,18 @@ def test_ignore_env_keys(self): u'bar': {u'hooks': u'pre_create', u'restricted_actions': u'replace'}}}}, env_dict) - self.m.VerifyAll() + mock_url.assert_called_with('file://%s' % env_file) - def test_process_environment_file(self): + @mock.patch('six.moves.urllib.request.urlopen') + def test_process_environment_file(self, mock_url): - self.m.StubOutWithMock(request, 'urlopen') env_file = '/home/my/dir/env.yaml' env = b''' resource_registry: "OS::Thingy": "file:///home/b/a.yaml" ''' - - request.urlopen('file://%s' % env_file).AndReturn( - six.BytesIO(env)) - request.urlopen('file:///home/b/a.yaml').AndReturn( - six.BytesIO(self.template_a)) - request.urlopen('file:///home/b/a.yaml').AndReturn( - six.BytesIO(self.template_a)) - self.m.ReplayAll() + mock_url.side_effect = [six.BytesIO(env), six.BytesIO(self.template_a), + six.BytesIO(self.template_a)] files, env_dict = template_utils.process_environment_and_files( env_file) @@ -101,10 +86,15 @@ def test_process_environment_file(self): env_dict) self.assertEqual(self.template_a.decode('utf-8'), files['file:///home/b/a.yaml']) + mock_url.assert_has_calls([ + mock.call('file://%s' % env_file), + mock.call('file:///home/b/a.yaml'), + mock.call('file:///home/b/a.yaml') + ]) - def test_process_environment_relative_file(self): + @mock.patch('six.moves.urllib.request.urlopen') + def test_process_environment_relative_file(self, mock_url): - self.m.StubOutWithMock(request, 'urlopen') env_file = '/home/my/dir/env.yaml' env_url = 'file:///home/my/dir/env.yaml' env = b''' @@ -112,13 +102,8 @@ def test_process_environment_relative_file(self): "OS::Thingy": a.yaml ''' - request.urlopen(env_url).AndReturn( - six.BytesIO(env)) - request.urlopen('file:///home/my/dir/a.yaml').AndReturn( - six.BytesIO(self.template_a)) - request.urlopen('file:///home/my/dir/a.yaml').AndReturn( - six.BytesIO(self.template_a)) - self.m.ReplayAll() + mock_url.side_effect = [six.BytesIO(env), six.BytesIO(self.template_a), + six.BytesIO(self.template_a)] self.assertEqual( env_url, @@ -136,24 +121,23 @@ def test_process_environment_relative_file(self): env_dict) self.assertEqual(self.template_a.decode('utf-8'), files['file:///home/my/dir/a.yaml']) + mock_url.assert_has_calls([ + mock.call(env_url), + mock.call('file:///home/my/dir/a.yaml'), + mock.call('file:///home/my/dir/a.yaml') + ]) - def test_process_environment_relative_file_up(self): + @mock.patch('six.moves.urllib.request.urlopen') + def test_process_environment_relative_file_up(self, mock_url): - self.m.StubOutWithMock(request, 'urlopen') env_file = '/home/my/dir/env.yaml' env_url = 'file:///home/my/dir/env.yaml' env = b''' resource_registry: "OS::Thingy": ../bar/a.yaml ''' - - request.urlopen(env_url).AndReturn( - six.BytesIO(env)) - request.urlopen('file:///home/my/bar/a.yaml').AndReturn( - six.BytesIO(self.template_a)) - request.urlopen('file:///home/my/bar/a.yaml').AndReturn( - six.BytesIO(self.template_a)) - self.m.ReplayAll() + mock_url.side_effect = [six.BytesIO(env), six.BytesIO(self.template_a), + six.BytesIO(self.template_a)] env_url = 'file://%s' % env_file self.assertEqual( @@ -172,20 +156,22 @@ def test_process_environment_relative_file_up(self): env_dict) self.assertEqual(self.template_a.decode('utf-8'), files['file:///home/my/bar/a.yaml']) - - def test_process_environment_url(self): + mock_url.assert_has_calls([ + mock.call(env_url), + mock.call('file:///home/my/bar/a.yaml'), + mock.call('file:///home/my/bar/a.yaml') + ]) + + @mock.patch('six.moves.urllib.request.urlopen') + def test_process_environment_url(self, mock_url): env = b''' resource_registry: "OS::Thingy": "a.yaml" ''' url = 'http://no.where/some/path/to/file.yaml' tmpl_url = 'http://no.where/some/path/to/a.yaml' - - self.m.StubOutWithMock(request, 'urlopen') - request.urlopen(url).AndReturn(six.BytesIO(env)) - request.urlopen(tmpl_url).AndReturn(six.BytesIO(self.template_a)) - request.urlopen(tmpl_url).AndReturn(six.BytesIO(self.template_a)) - self.m.ReplayAll() + mock_url.side_effect = [six.BytesIO(env), six.BytesIO(self.template_a), + six.BytesIO(self.template_a)] files, env_dict = template_utils.process_environment_and_files( url) @@ -193,30 +179,34 @@ def test_process_environment_url(self): self.assertEqual({'resource_registry': {'OS::Thingy': tmpl_url}}, env_dict) self.assertEqual(self.template_a.decode('utf-8'), files[tmpl_url]) + mock_url.assert_has_calls([ + mock.call(url), + mock.call(tmpl_url), + mock.call(tmpl_url) + ]) - def test_process_environment_empty_file(self): + @mock.patch('six.moves.urllib.request.urlopen') + def test_process_environment_empty_file(self, mock_url): - self.m.StubOutWithMock(request, 'urlopen') env_file = '/home/my/dir/env.yaml' env = b'' - - request.urlopen('file://%s' % env_file).AndReturn(six.BytesIO(env)) - self.m.ReplayAll() + mock_url.return_value = six.BytesIO(env) files, env_dict = template_utils.process_environment_and_files( env_file) self.assertEqual({}, env_dict) self.assertEqual({}, files) + mock_url.assert_called_with('file://%s' % env_file) def test_no_process_environment_and_files(self): files, env = template_utils.process_environment_and_files() self.assertEqual({}, env) self.assertEqual({}, files) - def test_process_multiple_environments_and_files(self): + @mock.patch('six.moves.urllib.request.urlopen') + def test_process_multiple_environments_and_files(self, mock_url): - self.m.StubOutWithMock(request, 'urlopen') env_file1 = '/home/my/dir/env1.yaml' env_file2 = '/home/my/dir/env2.yaml' @@ -233,19 +223,12 @@ def test_process_multiple_environments_and_files(self): "OS::Thingy2": "file:///home/b/b.yaml" ''' - request.urlopen('file://%s' % env_file1).AndReturn( - six.BytesIO(env1)) - request.urlopen('file:///home/b/a.yaml').AndReturn( - six.BytesIO(self.template_a)) - request.urlopen('file:///home/b/a.yaml').AndReturn( - six.BytesIO(self.template_a)) - request.urlopen('file://%s' % env_file2).AndReturn( - six.BytesIO(env2)) - request.urlopen('file:///home/b/b.yaml').AndReturn( - six.BytesIO(self.template_a)) - request.urlopen('file:///home/b/b.yaml').AndReturn( - six.BytesIO(self.template_a)) - self.m.ReplayAll() + mock_url.side_effect = [six.BytesIO(env1), + six.BytesIO(self.template_a), + six.BytesIO(self.template_a), + six.BytesIO(env2), + six.BytesIO(self.template_a), + six.BytesIO(self.template_a)] files, env = template_utils.process_multiple_environments_and_files( [env_file1, env_file2]) @@ -263,10 +246,18 @@ def test_process_multiple_environments_and_files(self): files['file:///home/b/a.yaml']) self.assertEqual(self.template_a.decode('utf-8'), files['file:///home/b/b.yaml']) + mock_url.assert_has_calls([ + mock.call('file://%s' % env_file1), + mock.call('file:///home/b/a.yaml'), + mock.call('file:///home/b/a.yaml'), + mock.call('file://%s' % env_file2), + mock.call('file:///home/b/b.yaml'), + mock.call('file:///home/b/b.yaml') + ]) + + @mock.patch('six.moves.urllib.request.urlopen') + def test_process_multiple_environments_default_resources(self, mock_url): - def test_process_multiple_environments_default_resources(self): - - self.m.StubOutWithMock(request, 'urlopen') env_file1 = '/home/my/dir/env1.yaml' env_file2 = '/home/my/dir/env2.yaml' @@ -286,28 +277,16 @@ def test_process_multiple_environments_default_resources(self): resource2: "OS::Thingy4": "file:///home/b/b.yaml" ''' - - request.urlopen('file://%s' % env_file1).InAnyOrder().AndReturn( - six.BytesIO(env1)) - request.urlopen('file:///home/b/a.yaml').InAnyOrder().AndReturn( - six.BytesIO(self.template_a)) - request.urlopen('file:///home/b/b.yaml').InAnyOrder().AndReturn( - six.BytesIO(self.template_a)) - request.urlopen('file:///home/b/a.yaml').InAnyOrder().AndReturn( - six.BytesIO(self.template_a)) - request.urlopen('file:///home/b/b.yaml').InAnyOrder().AndReturn( - six.BytesIO(self.template_a)) - request.urlopen('file://%s' % env_file2).InAnyOrder().AndReturn( - six.BytesIO(env2)) - request.urlopen('file:///home/b/a.yaml').InAnyOrder().AndReturn( - six.BytesIO(self.template_a)) - request.urlopen('file:///home/b/b.yaml').InAnyOrder().AndReturn( - six.BytesIO(self.template_a)) - request.urlopen('file:///home/b/a.yaml').InAnyOrder().AndReturn( - six.BytesIO(self.template_a)) - request.urlopen('file:///home/b/b.yaml').InAnyOrder().AndReturn( - six.BytesIO(self.template_a)) - self.m.ReplayAll() + mock_url.side_effect = [six.BytesIO(env1), + six.BytesIO(self.template_a), + six.BytesIO(self.template_a), + six.BytesIO(self.template_a), + six.BytesIO(self.template_a), + six.BytesIO(env2), + six.BytesIO(self.template_a), + six.BytesIO(self.template_a), + six.BytesIO(self.template_a), + six.BytesIO(self.template_a)] files, env = template_utils.process_multiple_environments_and_files( [env_file1, env_file2]) @@ -331,6 +310,19 @@ def test_process_multiple_environments_default_resources(self): files['file:///home/b/a.yaml']) self.assertEqual(self.template_a.decode('utf-8'), files['file:///home/b/b.yaml']) + mock_url.assert_has_calls([ + mock.call('file://%s' % env_file1), + mock.call('file:///home/b/a.yaml'), + mock.call('file:///home/b/b.yaml'), + mock.call('file:///home/b/a.yaml'), + mock.call('file:///home/b/b.yaml'), + mock.call('file://%s' % env_file2), + mock.call('file:///home/b/a.yaml'), + mock.call('file:///home/b/b.yaml'), + mock.call('file:///home/b/a.yaml'), + mock.call('file:///home/b/b.yaml'), + + ], any_order=True) def test_no_process_multiple_environments_and_files(self): files, env = template_utils.process_multiple_environments_and_files() @@ -347,8 +339,6 @@ def test_process_multiple_environments_and_files_from_object(self): "OS::Thingy1": "b/a.yaml" ''' - self.m.ReplayAll() - self.object_requested = False def env_path_is_object(object_url): @@ -376,9 +366,9 @@ def object_request(method, object_url): self.assertEqual(self.template_a.decode('utf-8'), files['http://no.where/path/to/b/a.yaml']) - def test_process_multiple_environments_and_files_tracker(self): + @mock.patch('six.moves.urllib.request.urlopen') + def test_process_multiple_environments_and_files_tracker(self, mock_url): # Setup - self.m.StubOutWithMock(request, 'urlopen') env_file1 = '/home/my/dir/env1.yaml' env1 = b''' @@ -387,13 +377,9 @@ def test_process_multiple_environments_and_files_tracker(self): resource_registry: "OS::Thingy1": "file:///home/b/a.yaml" ''' - request.urlopen('file://%s' % env_file1).AndReturn( - six.BytesIO(env1)) - request.urlopen('file:///home/b/a.yaml').AndReturn( - six.BytesIO(self.template_a)) - request.urlopen('file:///home/b/a.yaml').AndReturn( - six.BytesIO(self.template_a)) - self.m.ReplayAll() + mock_url.side_effect = [six.BytesIO(env1), + six.BytesIO(self.template_a), + six.BytesIO(self.template_a)] # Test env_file_list = [] @@ -414,24 +400,25 @@ def test_process_multiple_environments_and_files_tracker(self): self.assertIn('file:///home/my/dir/env1.yaml', files) self.assertEqual(expected_env, json.loads(files['file:///home/my/dir/env1.yaml'])) + mock_url.assert_has_calls([ + mock.call('file://%s' % env_file1), + mock.call('file:///home/b/a.yaml'), + mock.call('file:///home/b/a.yaml'), - def test_process_environment_relative_file_tracker(self): + ]) + + @mock.patch('six.moves.urllib.request.urlopen') + def test_process_environment_relative_file_tracker(self, mock_url): - self.m.StubOutWithMock(request, 'urlopen') env_file = '/home/my/dir/env.yaml' env_url = 'file:///home/my/dir/env.yaml' env = b''' resource_registry: "OS::Thingy": a.yaml ''' - - request.urlopen(env_url).AndReturn( - six.BytesIO(env)) - request.urlopen('file:///home/my/dir/a.yaml').AndReturn( - six.BytesIO(self.template_a)) - request.urlopen('file:///home/my/dir/a.yaml').AndReturn( - six.BytesIO(self.template_a)) - self.m.ReplayAll() + mock_url.side_effect = [six.BytesIO(env), + six.BytesIO(self.template_a), + six.BytesIO(self.template_a)] self.assertEqual( env_url, @@ -454,6 +441,12 @@ def test_process_environment_relative_file_tracker(self): self.assertEqual(['file:///home/my/dir/env.yaml'], env_file_list) self.assertEqual(json.dumps(expected_env), files['file:///home/my/dir/env.yaml']) + mock_url.assert_has_calls([ + mock.call(env_url), + mock.call('file:///home/my/dir/a.yaml'), + mock.call('file:///home/my/dir/a.yaml'), + + ]) def test_global_files(self): url = 'file:///home/b/a.yaml' @@ -538,13 +531,6 @@ def test_unsupported_protocol(self): class TestGetTemplateContents(testtools.TestCase): - def setUp(self): - super(TestGetTemplateContents, self).setUp() - self.m = mox.Mox() - - self.addCleanup(self.m.VerifyAll) - self.addCleanup(self.m.UnsetStubs) - def test_get_template_contents_file(self): with tempfile.NamedTemporaryFile() as tmpl_file: tmpl = (b'{"AWSTemplateFormatVersion" : "2010-09-09",' @@ -611,23 +597,22 @@ def test_get_template_contents_parse_error(self): matchers.MatchesRegex( 'Error parsing template file://%s ' % tmpl_file.name)) - def test_get_template_contents_url(self): + @mock.patch('six.moves.urllib.request.urlopen') + def test_get_template_contents_url(self, mock_url): tmpl = b'{"AWSTemplateFormatVersion" : "2010-09-09", "foo": "bar"}' url = 'http://no.where/path/to/a.yaml' - self.m.StubOutWithMock(request, 'urlopen') - request.urlopen(url).AndReturn(six.BytesIO(tmpl)) - self.m.ReplayAll() + mock_url.return_value = six.BytesIO(tmpl) files, tmpl_parsed = template_utils.get_template_contents( template_url=url) self.assertEqual({"AWSTemplateFormatVersion": "2010-09-09", "foo": "bar"}, tmpl_parsed) self.assertEqual({}, files) + mock_url.assert_called_with(url) def test_get_template_contents_object(self): tmpl = '{"AWSTemplateFormatVersion" : "2010-09-09", "foo": "bar"}' url = 'http://no.where/path/to/a.yaml' - self.m.ReplayAll() self.object_requested = False @@ -652,7 +637,6 @@ def test_get_nested_stack_template_contents_object(self): '"FooBar": {' '"type": "foo/bar.yaml"}}}') url = 'http://no.where/path/to/a.yaml' - self.m.ReplayAll() self.object_requested = False @@ -685,15 +669,15 @@ def check_non_utf8_content(self, filename, content): [{'path': '/tmp/%s' % filename, 'content': {'get_file': url}, 'encoding': 'b64'}]}}}}} - self.m.StubOutWithMock(request, 'urlopen') - raw_content = base64.decodestring(content) - response = six.BytesIO(raw_content) - request.urlopen(url).AndReturn(response) - self.m.ReplayAll() - files = {} - template_utils.resolve_template_get_files( - template, files, base_url) - self.assertEqual({url: content}, files) + with mock.patch('six.moves.urllib.request.urlopen') as mock_url: + raw_content = base64.decodestring(content) + response = six.BytesIO(raw_content) + mock_url.return_value = response + files = {} + template_utils.resolve_template_get_files( + template, files, base_url) + self.assertEqual({url: content}, files) + mock_url.assert_called_with(url) def test_get_zip_content(self): filename = 'heat.zip' @@ -746,6 +730,7 @@ def test_get_gb18030_content(self): self.check_non_utf8_content(filename=filename, content=content) +@mock.patch('six.moves.urllib.request.urlopen') class TestTemplateGetFileFunctions(testtools.TestCase): hot_template = b'''heat_template_version: 2013-05-23 @@ -769,48 +754,20 @@ class TestTemplateGetFileFunctions(testtools.TestCase): ignored_none: {get_file: } ''' - def setUp(self): - super(TestTemplateGetFileFunctions, self).setUp() - self.m = mox.Mox() - - self.addCleanup(self.m.VerifyAll) - self.addCleanup(self.m.UnsetStubs) - - def test_hot_template(self): - self.m.StubOutWithMock(request, 'urlopen') + def test_hot_template(self, mock_url): tmpl_file = '/home/my/dir/template.yaml' url = 'file:///home/my/dir/template.yaml' - request.urlopen(url).AndReturn( - six.BytesIO(self.hot_template)) - request.urlopen( - 'http://localhost/bar.yaml').InAnyOrder().AndReturn( - six.BytesIO(b'bar contents')) - request.urlopen( - 'file:///home/my/dir/foo.yaml').InAnyOrder().AndReturn( - six.BytesIO(b'foo contents')) - request.urlopen( - 'file:///home/my/dir/baz/baz1.yaml').InAnyOrder().AndReturn( - six.BytesIO(b'baz1 contents')) - request.urlopen( - 'file:///home/my/dir/baz/baz2.yaml').InAnyOrder().AndReturn( - six.BytesIO(b'baz2 contents')) - request.urlopen( - 'file:///home/my/dir/baz/baz3.yaml').InAnyOrder().AndReturn( - six.BytesIO(b'baz3 contents')) - - self.m.ReplayAll() + mock_url.side_effect = [six.BytesIO(self.hot_template), + six.BytesIO(b'bar contents'), + six.BytesIO(b'foo contents'), + six.BytesIO(b'baz1 contents'), + six.BytesIO(b'baz2 contents'), + six.BytesIO(b'baz3 contents')] files, tmpl_parsed = template_utils.get_template_contents( template_file=tmpl_file) - self.assertEqual({ - 'http://localhost/bar.yaml': b'bar contents', - 'file:///home/my/dir/foo.yaml': b'foo contents', - 'file:///home/my/dir/baz/baz1.yaml': b'baz1 contents', - 'file:///home/my/dir/baz/baz2.yaml': b'baz2 contents', - 'file:///home/my/dir/baz/baz3.yaml': b'baz3 contents', - }, files) self.assertEqual({ 'heat_template_version': '2013-05-23', 'resources': { @@ -836,9 +793,16 @@ def test_hot_template(self): } } }, tmpl_parsed) - - def test_hot_template_outputs(self): - self.m.StubOutWithMock(request, 'urlopen') + mock_url.assert_has_calls([ + mock.call(url), + mock.call('http://localhost/bar.yaml'), + mock.call('file:///home/my/dir/foo.yaml'), + mock.call('file:///home/my/dir/baz/baz1.yaml'), + mock.call('file:///home/my/dir/baz/baz2.yaml'), + mock.call('file:///home/my/dir/baz/baz3.yaml') + ], any_order=True) + + def test_hot_template_outputs(self, mock_url): tmpl_file = '/home/my/dir/template.yaml' url = 'file://%s' % tmpl_file foo_url = 'file:///home/my/dir/foo.yaml' @@ -848,15 +812,17 @@ def test_hot_template_outputs(self): contents:\n\ value:\n\ get_file: foo.yaml\n''' - request.urlopen(url).AndReturn(six.BytesIO(contents)) - request.urlopen(foo_url).AndReturn(six.BytesIO(b'foo contents')) - self.m.ReplayAll() + mock_url.side_effect = [six.BytesIO(contents), + six.BytesIO(b'foo contents')] files = template_utils.get_template_contents( template_file=tmpl_file)[0] self.assertEqual({foo_url: b'foo contents'}, files) + mock_url.assert_has_calls([ + mock.call(url), + mock.call(foo_url) + ]) - def test_hot_template_same_file(self): - self.m.StubOutWithMock(request, 'urlopen') + def test_hot_template_same_file(self, mock_url): tmpl_file = '/home/my/dir/template.yaml' url = 'file://%s' % tmpl_file foo_url = 'file:///home/my/dir/foo.yaml' @@ -869,14 +835,17 @@ def test_hot_template_same_file(self): template:\n\ value:\n\ get_file: foo.yaml\n''' - request.urlopen(url).AndReturn(six.BytesIO(contents)) + mock_url.side_effect = [six.BytesIO(contents), + six.BytesIO(b'foo contents')] # asserts that is fetched only once even though it is # referenced in the template twice - request.urlopen(foo_url).AndReturn(six.BytesIO(b'foo contents')) - self.m.ReplayAll() files = template_utils.get_template_contents( template_file=tmpl_file)[0] self.assertEqual({foo_url: b'foo contents'}, files) + mock_url.assert_has_calls([ + mock.call(url), + mock.call(foo_url) + ]) class TestTemplateTypeFunctions(testtools.TestCase): @@ -909,38 +878,26 @@ class TestTemplateTypeFunctions(testtools.TestCase): type: string ''' - def setUp(self): - super(TestTemplateTypeFunctions, self).setUp() - self.m = mox.Mox() - - self.addCleanup(self.m.VerifyAll) - self.addCleanup(self.m.UnsetStubs) - - def test_hot_template(self): - self.m.StubOutWithMock(request, 'urlopen') + @mock.patch('six.moves.urllib.request.urlopen') + def test_hot_template(self, mock_url): tmpl_file = '/home/my/dir/template.yaml' url = 'file:///home/my/dir/template.yaml' - request.urlopen( - 'file:///home/my/dir/foo.yaml').InAnyOrder().AndReturn( - six.BytesIO(self.foo_template)) - request.urlopen( - 'file:///home/my/dir/foo.yaml').InAnyOrder().AndReturn( - six.BytesIO(self.foo_template)) - request.urlopen(url).InAnyOrder().AndReturn( - six.BytesIO(self.hot_template)) - request.urlopen( - 'file:///home/my/dir/spam/egg.yaml').InAnyOrder().AndReturn( - six.BytesIO(self.egg_template)) - request.urlopen( - 'file:///home/my/dir/spam/egg.yaml').InAnyOrder().AndReturn( - six.BytesIO(self.egg_template)) - self.m.ReplayAll() + + def side_effect(args): + if url == args: + return six.BytesIO(self.hot_template) + if 'file:///home/my/dir/foo.yaml' == args: + return six.BytesIO(self.foo_template) + if 'file:///home/my/dir/spam/egg.yaml' == args: + return six.BytesIO(self.egg_template) + mock_url.side_effect = side_effect files, tmpl_parsed = template_utils.get_template_contents( template_file=tmpl_file) self.assertEqual(yaml.safe_load(self.foo_template.decode('utf-8')), json.loads(files.get('file:///home/my/dir/foo.yaml'))) + self.assertEqual( yaml.safe_load(self.egg_template.decode('utf-8')), json.loads(files.get('file:///home/my/dir/spam/egg.yaml'))) @@ -968,6 +925,12 @@ def test_hot_template(self): } }, tmpl_parsed) + mock_url.assert_has_calls([ + mock.call('file:///home/my/dir/foo.yaml'), + mock.call(url), + mock.call('file:///home/my/dir/spam/egg.yaml'), + ], any_order=True) + class TestTemplateInFileFunctions(testtools.TestCase): @@ -993,30 +956,21 @@ class TestTemplateInFileFunctions(testtools.TestCase): type: string ''' - def setUp(self): - super(TestTemplateInFileFunctions, self).setUp() - self.m = mox.Mox() - - self.addCleanup(self.m.VerifyAll) - self.addCleanup(self.m.UnsetStubs) - - def test_hot_template(self): - self.m.StubOutWithMock(request, 'urlopen') + @mock.patch('six.moves.urllib.request.urlopen') + def test_hot_template(self, mock_url): tmpl_file = '/home/my/dir/template.yaml' url = 'file:///home/my/dir/template.yaml' foo_url = 'file:///home/my/dir/foo.yaml' bar_url = 'file:///home/my/dir/bar.yaml' - request.urlopen(url).InAnyOrder().AndReturn( - six.BytesIO(self.hot_template)) - request.urlopen(foo_url).InAnyOrder().AndReturn( - six.BytesIO(self.foo_template)) - request.urlopen(foo_url).InAnyOrder().AndReturn( - six.BytesIO(self.foo_template)) - request.urlopen(bar_url).InAnyOrder().AndReturn( - six.BytesIO(self.bar_template)) - request.urlopen(bar_url).InAnyOrder().AndReturn( - six.BytesIO(self.bar_template)) - self.m.ReplayAll() + + def side_effect(args): + if url == args: + return six.BytesIO(self.hot_template) + if foo_url == args: + return six.BytesIO(self.foo_template) + if bar_url == args: + return six.BytesIO(self.bar_template) + mock_url.side_effect = side_effect files, tmpl_parsed = template_utils.get_template_contents( template_file=tmpl_file) @@ -1052,6 +1006,12 @@ def test_hot_template(self): } }, tmpl_parsed) + mock_url.assert_has_calls([ + mock.call(foo_url), + mock.call(url), + mock.call(bar_url), + ], any_order=True) + class TestNestedIncludes(testtools.TestCase): @@ -1095,15 +1055,8 @@ class TestNestedIncludes(testtools.TestCase): type: string ''' - def setUp(self): - super(TestNestedIncludes, self).setUp() - self.m = mox.Mox() - - self.addCleanup(self.m.VerifyAll) - self.addCleanup(self.m.UnsetStubs) - - def test_env_nested_includes(self): - self.m.StubOutWithMock(request, 'urlopen') + @mock.patch('six.moves.urllib.request.urlopen') + def test_env_nested_includes(self, mock_url): env_file = '/home/my/dir/env.yaml' env_url = 'file:///home/my/dir/env.yaml' env = b''' @@ -1118,35 +1071,24 @@ def test_env_nested_includes(self): two_url = u'file:///home/my/dir/spam/two.yaml' three_url = u'file:///home/my/dir/spam/three.yaml' - request.urlopen(env_url).AndReturn( - six.BytesIO(env)) - request.urlopen(template_url).AndReturn( - six.BytesIO(self.hot_template)) - request.urlopen(template_url).AndReturn( - six.BytesIO(self.hot_template)) - - request.urlopen(foo_url).InAnyOrder().AndReturn( - six.BytesIO(self.foo_template)) - request.urlopen(egg_url).InAnyOrder().AndReturn( - six.BytesIO(self.egg_template)) - request.urlopen(ham_url).InAnyOrder().AndReturn( - six.BytesIO(b'ham contents')) - request.urlopen(one_url).InAnyOrder().AndReturn( - six.BytesIO(self.foo_template)) - request.urlopen(two_url).InAnyOrder().AndReturn( - six.BytesIO(self.foo_template)) - request.urlopen(three_url).InAnyOrder().AndReturn( - six.BytesIO(b'three contents')) - request.urlopen(foo_url).InAnyOrder().AndReturn( - six.BytesIO(self.foo_template)) - request.urlopen(egg_url).InAnyOrder().AndReturn( - six.BytesIO(self.egg_template)) - request.urlopen(one_url).InAnyOrder().AndReturn( - six.BytesIO(self.foo_template)) - request.urlopen(two_url).InAnyOrder().AndReturn( - six.BytesIO(self.foo_template)) - - self.m.ReplayAll() + def side_effect(args): + if env_url == args: + return six.BytesIO(env) + if template_url == args: + return six.BytesIO(self.hot_template) + if foo_url == args: + return six.BytesIO(self.foo_template) + if egg_url == args: + return six.BytesIO(self.egg_template) + if ham_url == args: + return six.BytesIO(b'ham contents') + if one_url == args: + return six.BytesIO(self.foo_template) + if two_url == args: + return six.BytesIO(self.foo_template) + if three_url == args: + return six.BytesIO(b'three contents') + mock_url.side_effect = side_effect files, env_dict = template_utils.process_environment_and_files( env_file) @@ -1201,3 +1143,13 @@ def test_env_nested_includes(self): json.loads(files.get(two_url))) self.assertEqual(b'three contents', files.get(three_url)) + mock_url.assert_has_calls([ + mock.call(env_url), + mock.call(template_url), + mock.call(foo_url), + mock.call(egg_url), + mock.call(ham_url), + mock.call(one_url), + mock.call(two_url), + mock.call(three_url), + ], any_order=True) From 2cd3db111151ac327d3434f1c2f1a055b6714e23 Mon Sep 17 00:00:00 2001 From: Jason Dunsmore Date: Thu, 5 Jan 2017 10:54:47 -0600 Subject: [PATCH 007/246] Make sure the --heat-url option is respected, part 2 If the endpoint is passed in, make sure keystone uses it instead of looking up the endpoint in the auth plugin. Original fix: 9e28993ee6fb814f014bc73a5611681ca3bb2c65 Closes-Bug: #1654344 Change-Id: I4b42ab9d87c790eef1953f995260b6b232a63445 --- heatclient/shell.py | 3 ++- heatclient/tests/unit/test_shell.py | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/heatclient/shell.py b/heatclient/shell.py index 558fb8a8..5774aac4 100644 --- a/heatclient/shell.py +++ b/heatclient/shell.py @@ -544,7 +544,8 @@ def main(self, argv): 'region_name': args.os_region_name, 'username': args.os_username, 'password': args.os_password, - 'include_pass': args.include_password + 'include_pass': args.include_password, + 'endpoint_override': args.heat_url, } client = heat_client.Client(api_version, **kwargs) diff --git a/heatclient/tests/unit/test_shell.py b/heatclient/tests/unit/test_shell.py index 383ee2c0..efa124e3 100644 --- a/heatclient/tests/unit/test_shell.py +++ b/heatclient/tests/unit/test_shell.py @@ -618,7 +618,8 @@ def test_endpoint_type_public_url(self): 'region_name': '', 'username': 'username', 'password': 'password', - 'include_pass': False + 'include_pass': False, + 'endpoint_override': mox.IgnoreArg(), } http._construct_http_client(**kwargs) heatclient.v1.shell.do_stack_list(mox.IgnoreArg(), mox.IgnoreArg()) @@ -637,7 +638,8 @@ def test_endpoint_type_admin_url(self): 'region_name': '', 'username': 'username', 'password': 'password', - 'include_pass': False + 'include_pass': False, + 'endpoint_override': mox.IgnoreArg(), } http._construct_http_client(**kwargs) heatclient.v1.shell.do_stack_list(mox.IgnoreArg(), mox.IgnoreArg()) @@ -658,7 +660,8 @@ def test_endpoint_type_internal_url(self): 'region_name': '', 'username': 'username', 'password': 'password', - 'include_pass': False + 'include_pass': False, + 'endpoint_override': mox.IgnoreArg(), } http._construct_http_client(**kwargs) heatclient.v1.shell.do_stack_list(mox.IgnoreArg(), mox.IgnoreArg()) From 3e2bb477172410425b6a078a689dc3e43ce8a485 Mon Sep 17 00:00:00 2001 From: Zane Bitter Date: Mon, 9 Jan 2017 15:23:05 -0500 Subject: [PATCH 008/246] Distinguish between stack and resource events when polling Don't accidentally treat a resource event like a stack event. Change-Id: I0e4d97f9954adfac3124f6ed684b608f4da668b4 Closes-Bug: #1655142 --- heatclient/common/event_utils.py | 13 ++++- heatclient/tests/unit/test_event_utils.py | 69 +++++++++++++++++++---- heatclient/tests/unit/test_shell.py | 4 +- 3 files changed, 72 insertions(+), 14 deletions(-) diff --git a/heatclient/common/event_utils.py b/heatclient/common/event_utils.py index 309a1939..df000f73 100644 --- a/heatclient/common/event_utils.py +++ b/heatclient/common/event_utils.py @@ -181,6 +181,17 @@ def poll_for_events(hc, stack_name, action=None, poll_period=5, marker=None, if not out: out = sys.stdout event_log_context = utils.EventLogContext() + + def is_stack_event(event): + if getattr(event, 'resource_name', '') != stack_name: + return False + + phys_id = getattr(event, 'physical_resource_id', '') + links = dict((l.get('rel'), + l.get('href')) for l in getattr(event, 'links', [])) + stack_id = links.get('stack', phys_id).rsplit('/', 1)[-1] + return stack_id == phys_id + while True: events = get_events(hc, stack_id=stack_name, nested_depth=nested_depth, event_args={'sort_dir': 'asc', @@ -198,7 +209,7 @@ def poll_for_events(hc, stack_name, action=None, poll_period=5, marker=None, for event in events: # check if stack event was also received - if getattr(event, 'resource_name', '') == stack_name: + if is_stack_event(event): stack_status = getattr(event, 'resource_status', '') msg = msg_template % dict( name=stack_name, status=stack_status) diff --git a/heatclient/tests/unit/test_event_utils.py b/heatclient/tests/unit/test_event_utils.py index 58f2e288..a9e89122 100644 --- a/heatclient/tests/unit/test_event_utils.py +++ b/heatclient/tests/unit/test_event_utils.py @@ -47,7 +47,8 @@ def _mock_resource(resource_id, nested_id=None): @staticmethod def _mock_event(event_id, resource_id, resource_status='CREATE_COMPLETE'): - ev_info = {"links": [{"href": "http://heat/foo", "rel": "self"}], + ev_info = {"links": [{"href": "http://heat/foo", "rel": "self"}, + {"href": "http://heat/stacks/a", "rel": "stack"}], "logical_resource_id": resource_id, "physical_resource_id": resource_id, "resource_name": resource_id, @@ -57,6 +58,23 @@ def _mock_event(event_id, resource_id, "id": event_id} return hc_ev.Event(manager=None, info=ev_info) + @staticmethod + def _mock_stack_event(event_id, stack_name, + stack_status='CREATE_COMPLETE'): + stack_id = 'abcdef' + ev_info = {"links": [{"href": "http://heat/foo", "rel": "self"}, + {"href": "http://heat/stacks/%s/%s" % (stack_name, + stack_id), + "rel": "stack"}], + "logical_resource_id": stack_name, + "physical_resource_id": stack_id, + "resource_name": stack_name, + "resource_status": stack_status, + "resource_status_reason": "state changed", + "event_time": "2014-12-05T14:14:30Z", + "id": event_id} + return hc_ev.Event(manager=None, info=ev_info) + def test_get_nested_ids(self): def list_stub(stack_id): return [self._mock_resource('aresource', 'foo3/3id')] @@ -143,7 +161,7 @@ def event_list_stub(stack_id, argfoo): @mock.patch('heatclient.common.event_utils.get_events') def test_poll_for_events(self, ge): ge.side_effect = [[ - self._mock_event('1', 'astack', 'CREATE_IN_PROGRESS'), + self._mock_stack_event('1', 'astack', 'CREATE_IN_PROGRESS'), self._mock_event('2', 'res_child1', 'CREATE_IN_PROGRESS'), self._mock_event('3', 'res_child2', 'CREATE_IN_PROGRESS'), self._mock_event('4', 'res_child3', 'CREATE_IN_PROGRESS') @@ -151,7 +169,7 @@ def test_poll_for_events(self, ge): self._mock_event('5', 'res_child1', 'CREATE_COMPLETE'), self._mock_event('6', 'res_child2', 'CREATE_COMPLETE'), self._mock_event('7', 'res_child3', 'CREATE_COMPLETE'), - self._mock_event('8', 'astack', 'CREATE_COMPLETE') + self._mock_stack_event('8', 'astack', 'CREATE_COMPLETE') ]] stack_status, msg = event_utils.poll_for_events( @@ -167,13 +185,42 @@ def test_poll_for_events(self, ge): }) ]) + @mock.patch('heatclient.common.event_utils.get_events') + def test_poll_for_events_same_name(self, ge): + ge.side_effect = [[ + self._mock_stack_event('1', 'mything', 'CREATE_IN_PROGRESS'), + self._mock_event('2', 'res_child1', 'CREATE_IN_PROGRESS'), + self._mock_event('3', 'mything', 'CREATE_IN_PROGRESS'), + ], [ + self._mock_event('4', 'mything', 'CREATE_COMPLETE'), + ], [ + self._mock_event('5', 'res_child1', 'CREATE_COMPLETE'), + self._mock_stack_event('6', 'mything', 'CREATE_COMPLETE'), + ]] + + stack_status, msg = event_utils.poll_for_events( + None, 'mything', action='CREATE', poll_period=0) + self.assertEqual('CREATE_COMPLETE', stack_status) + self.assertEqual('\n Stack mything CREATE_COMPLETE \n', msg) + ge.assert_has_calls([ + mock.call(None, stack_id='mything', nested_depth=0, event_args={ + 'sort_dir': 'asc', 'marker': None + }), + mock.call(None, stack_id='mything', nested_depth=0, event_args={ + 'sort_dir': 'asc', 'marker': '3' + }), + mock.call(None, stack_id='mything', nested_depth=0, event_args={ + 'sort_dir': 'asc', 'marker': '4' + }) + ]) + @mock.patch('heatclient.common.event_utils.get_events') def test_poll_for_events_with_marker(self, ge): ge.side_effect = [[ self._mock_event('5', 'res_child1', 'CREATE_COMPLETE'), self._mock_event('6', 'res_child2', 'CREATE_COMPLETE'), self._mock_event('7', 'res_child3', 'CREATE_COMPLETE'), - self._mock_event('8', 'astack', 'CREATE_COMPLETE') + self._mock_stack_event('8', 'astack', 'CREATE_COMPLETE') ]] stack_status, msg = event_utils.poll_for_events( @@ -190,9 +237,9 @@ def test_poll_for_events_with_marker(self, ge): @mock.patch('heatclient.common.event_utils.get_events') def test_poll_for_events_in_progress_resource(self, ge): ge.side_effect = [[ - self._mock_event('1', 'astack', 'CREATE_IN_PROGRESS'), + self._mock_stack_event('1', 'astack', 'CREATE_IN_PROGRESS'), self._mock_event('2', 'res_child1', 'CREATE_IN_PROGRESS'), - self._mock_event('3', 'astack', 'CREATE_COMPLETE') + self._mock_stack_event('3', 'astack', 'CREATE_COMPLETE') ]] stack_status, msg = event_utils.poll_for_events( @@ -203,7 +250,7 @@ def test_poll_for_events_in_progress_resource(self, ge): @mock.patch('heatclient.common.event_utils.get_events') def test_poll_for_events_failed(self, ge): ge.side_effect = [[ - self._mock_event('1', 'astack', 'CREATE_IN_PROGRESS'), + self._mock_stack_event('1', 'astack', 'CREATE_IN_PROGRESS'), self._mock_event('2', 'res_child1', 'CREATE_IN_PROGRESS'), self._mock_event('3', 'res_child2', 'CREATE_IN_PROGRESS'), self._mock_event('4', 'res_child3', 'CREATE_IN_PROGRESS') @@ -211,7 +258,7 @@ def test_poll_for_events_failed(self, ge): self._mock_event('5', 'res_child1', 'CREATE_COMPLETE'), self._mock_event('6', 'res_child2', 'CREATE_FAILED'), self._mock_event('7', 'res_child3', 'CREATE_COMPLETE'), - self._mock_event('8', 'astack', 'CREATE_FAILED') + self._mock_stack_event('8', 'astack', 'CREATE_FAILED') ]] stack_status, msg = event_utils.poll_for_events( @@ -222,7 +269,7 @@ def test_poll_for_events_failed(self, ge): @mock.patch('heatclient.common.event_utils.get_events') def test_poll_for_events_no_action(self, ge): ge.side_effect = [[ - self._mock_event('1', 'astack', 'CREATE_IN_PROGRESS'), + self._mock_stack_event('1', 'astack', 'CREATE_IN_PROGRESS'), self._mock_event('2', 'res_child1', 'CREATE_IN_PROGRESS'), self._mock_event('3', 'res_child2', 'CREATE_IN_PROGRESS'), self._mock_event('4', 'res_child3', 'CREATE_IN_PROGRESS') @@ -230,7 +277,7 @@ def test_poll_for_events_no_action(self, ge): self._mock_event('5', 'res_child1', 'CREATE_COMPLETE'), self._mock_event('6', 'res_child2', 'CREATE_FAILED'), self._mock_event('7', 'res_child3', 'CREATE_COMPLETE'), - self._mock_event('8', 'astack', 'FOO_FAILED') + self._mock_stack_event('8', 'astack', 'FOO_FAILED') ]] stack_status, msg = event_utils.poll_for_events( @@ -244,7 +291,7 @@ def test_poll_for_events_stack_get(self, ge): mock_client.stacks.get.return_value.stack_status = 'CREATE_FAILED' ge.side_effect = [[ - self._mock_event('1', 'astack', 'CREATE_IN_PROGRESS'), + self._mock_stack_event('1', 'astack', 'CREATE_IN_PROGRESS'), self._mock_event('2', 'res_child1', 'CREATE_IN_PROGRESS'), self._mock_event('3', 'res_child2', 'CREATE_IN_PROGRESS'), self._mock_event('4', 'res_child3', 'CREATE_IN_PROGRESS') diff --git a/heatclient/tests/unit/test_shell.py b/heatclient/tests/unit/test_shell.py index efa124e3..18173ded 100644 --- a/heatclient/tests/unit/test_shell.py +++ b/heatclient/tests/unit/test_shell.py @@ -211,7 +211,7 @@ def event_list_resp_dict( {"href": "http://heat.example.com:8004/foo3", "rel": "stack"}], "logical_resource_id": "aResource", - "physical_resource_id": None, + "physical_resource_id": 'foo3', "resource_name": stack_name, "resource_status": "%s_IN_PROGRESS" % action, "resource_status_reason": "state changed"}) @@ -225,7 +225,7 @@ def event_list_resp_dict( {"href": "http://heat.example.com:8004/foo3", "rel": "stack"}], "logical_resource_id": "aResource", - "physical_resource_id": None, + "physical_resource_id": 'foo3', "resource_name": stack_name, "resource_status": "%s_%s" % (action, final_state), "resource_status_reason": "state changed"}) From a0f2089c56bd2a0de6717429d3a4b2709fda74c8 Mon Sep 17 00:00:00 2001 From: Kevin_Zheng Date: Wed, 11 Jan 2017 10:23:44 +0800 Subject: [PATCH 009/246] Enable coverage report in console output The current coverage test job doesn't provide any result to developer, we could enable the coverage report in console output to let the developers know the current coverage status when run coverage test job. Change-Id: Icef8c3b42f087a20c48b5308185f6be8c8f5240d --- tox.ini | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 0b67fa04..7e1eb4d8 100644 --- a/tox.ini +++ b/tox.ini @@ -42,7 +42,9 @@ setenv = passenv = OS_* [testenv:cover] -commands = python setup.py testr --coverage --testr-args='{posargs}' +commands = + python setup.py testr --coverage --testr-args='{posargs}' + coverage report [testenv:docs] commands= From ef375e09dded0d11137c06d1cabe20444655cde0 Mon Sep 17 00:00:00 2001 From: Russell Bryant Date: Thu, 12 Jan 2017 21:36:27 -0500 Subject: [PATCH 010/246] Support -y for --yes. Add short form '-y' for '--yes', mainly because I'm lazy. Change-Id: Ia82f64246a191a1615ba1d886f80caa5f52cc835 Signed-off-by: Russell Bryant --- heatclient/osc/v1/stack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/heatclient/osc/v1/stack.py b/heatclient/osc/v1/stack.py index 719c0de7..cf03cec6 100644 --- a/heatclient/osc/v1/stack.py +++ b/heatclient/osc/v1/stack.py @@ -656,7 +656,7 @@ def get_parser(self, prog_name): help=_('Stack(s) to delete (name or ID)') ) parser.add_argument( - '--yes', + '-y', '--yes', action='store_true', help=_('Skip yes/no prompt (assume yes)') ) From 1f639d79914964dbcf0c37af392db0b2111085f9 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Mon, 16 Jan 2017 17:27:30 +0000 Subject: [PATCH 011/246] Updated from global requirements Change-Id: I7c1f701a8792757109b72a10b4ec31f870fe00a8 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a25ff2d4..bac126ae 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ PrettyTable<0.8,>=0.7.1 # BSD oslo.i18n>=2.1.0 # Apache-2.0 oslo.serialization>=1.10.0 # Apache-2.0 oslo.utils>=3.18.0 # Apache-2.0 -keystoneauth1>=2.16.0 # Apache-2.0 +keystoneauth1>=2.17.0 # Apache-2.0 python-swiftclient>=3.2.0 # Apache-2.0 PyYAML>=3.10.0 # MIT requests!=2.12.2,>=2.10.0 # Apache-2.0 From c6afa68ea4b9adc312032486a97213019f37bf4c Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 20 Jan 2017 01:54:24 +0000 Subject: [PATCH 012/246] Updated from global requirements Change-Id: I3388fa28a6b3bab64589fdc5c64985deed361cb2 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 7eb9c2d1..ed21859f 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -8,7 +8,7 @@ coverage>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD requests-mock>=1.1 # Apache-2.0 mock>=2.0 # BSD -mox3>=0.7.0 # Apache-2.0 +mox3!=0.19.0,>=0.7.0 # Apache-2.0 oslosphinx>=4.7.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 python-openstackclient>=3.3.0 # Apache-2.0 From 17dd3068e4d6fc10236290cc082908c439bcfead Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sat, 21 Jan 2017 15:56:21 +0000 Subject: [PATCH 013/246] Updated from global requirements Change-Id: I7183b0ca1062b96e64ad8f32bcd8efe047d7998d --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index bac126ae..d95661e1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ PrettyTable<0.8,>=0.7.1 # BSD oslo.i18n>=2.1.0 # Apache-2.0 oslo.serialization>=1.10.0 # Apache-2.0 oslo.utils>=3.18.0 # Apache-2.0 -keystoneauth1>=2.17.0 # Apache-2.0 +keystoneauth1>=2.18.0 # Apache-2.0 python-swiftclient>=3.2.0 # Apache-2.0 PyYAML>=3.10.0 # MIT requests!=2.12.2,>=2.10.0 # Apache-2.0 From 3ee70cf4e8c2d8505230f73ef0b6b44185567dc2 Mon Sep 17 00:00:00 2001 From: huangtianhua Date: Sat, 24 Dec 2016 11:28:35 +0800 Subject: [PATCH 014/246] Show 'project' info if heat server returns Show 'project'/'Project' info in heatclient if heat server returns 'project' info of stacks. Change-Id: I7d95ae96a678ef41bcd3d5379f204db83e4b585c Closes-Bug: #1652412 --- heatclient/osc/v1/stack.py | 7 ++-- heatclient/tests/unit/osc/v1/test_stack.py | 21 +++++++++++ heatclient/tests/unit/test_shell.py | 42 +++++++++++----------- heatclient/v1/shell.py | 7 ++-- 4 files changed, 53 insertions(+), 24 deletions(-) diff --git a/heatclient/osc/v1/stack.py b/heatclient/osc/v1/stack.py index cf03cec6..4b04dff4 100644 --- a/heatclient/osc/v1/stack.py +++ b/heatclient/osc/v1/stack.py @@ -623,8 +623,6 @@ def _list(client, args=None): columns.pop() if args.long: columns.insert(2, 'Stack Owner') - if args.long or args.all_projects: - columns.insert(2, 'Project') if args.nested: columns.append('Parent') @@ -634,6 +632,11 @@ def _list(client, args=None): columns.append('Deletion Time') data = client.stacks.list(**kwargs) + data = list(data) + for stk in data: + if hasattr(stk, 'project'): + columns.insert(2, 'Project') + break data = utils.sort_items(data, args.sort if args else None) return ( diff --git a/heatclient/tests/unit/osc/v1/test_stack.py b/heatclient/tests/unit/osc/v1/test_stack.py index c40bdf19..7481acb8 100644 --- a/heatclient/tests/unit/osc/v1/test_stack.py +++ b/heatclient/tests/unit/osc/v1/test_stack.py @@ -453,6 +453,9 @@ class TestStackList(TestStack): 'deletion_time': '2015-10-21T07:50:00Z', } + data_with_project = copy.deepcopy(data) + data_with_project['project'] = 'test_project' + def setUp(self): super(TestStackList, self).setUp() self.cmd = stack.ListStack(self.app, None) @@ -495,6 +498,8 @@ def test_stack_list_deleted(self): self.assertEqual(cols, columns) def test_stack_list_all_projects(self): + self.stack_client.list.return_value = [ + stacks.Stack(None, self.data_with_project)] kwargs = copy.deepcopy(self.defaults) kwargs['global_tenant'] = True cols = copy.deepcopy(self.columns) @@ -507,7 +512,23 @@ def test_stack_list_all_projects(self): self.stack_client.list.assert_called_with(**kwargs) self.assertEqual(cols, columns) + def test_stack_list_with_project(self): + self.stack_client.list.return_value = [ + stacks.Stack(None, self.data_with_project)] + kwargs = copy.deepcopy(self.defaults) + cols = copy.deepcopy(self.columns) + cols.insert(2, 'Project') + arglist = [] + parsed_args = self.check_parser(self.cmd, arglist, []) + + columns, data = self.cmd.take_action(parsed_args) + + self.stack_client.list.assert_called_with(**kwargs) + self.assertEqual(cols, columns) + def test_stack_list_long(self): + self.stack_client.list.return_value = [ + stacks.Stack(None, self.data_with_project)] kwargs = copy.deepcopy(self.defaults) kwargs['global_tenant'] = True cols = copy.deepcopy(self.columns) diff --git a/heatclient/tests/unit/test_shell.py b/heatclient/tests/unit/test_shell.py index 18173ded..abb58456 100644 --- a/heatclient/tests/unit/test_shell.py +++ b/heatclient/tests/unit/test_shell.py @@ -126,25 +126,25 @@ def patch(self, target, **kwargs): mockfixture = self.useFixture(mockpatch.Patch(target, **kwargs)) return mockfixture.mock - def stack_list_resp_dict(self, show_nested=False): - resp_dict = {"stacks": [ - { - "id": "1", - "stack_name": "teststack", - "stack_owner": "testowner", - "project": "testproject", - "stack_status": 'CREATE_COMPLETE', - "creation_time": "2012-10-25T01:58:47Z" - }, - { - "id": "2", - "stack_name": "teststack2", - "stack_owner": "testowner", - "project": "testproject", - "stack_status": 'IN_PROGRESS', - "creation_time": "2012-10-25T01:58:47Z" - }] - } + 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", @@ -153,6 +153,8 @@ def stack_list_resp_dict(self, show_nested=False): "creation_time": "2012-10-25T01:58:47Z", "parent": "theparentof3" } + if include_project: + nested['project'] = 'testproject' resp_dict["stacks"].append(nested) return resp_dict @@ -4209,7 +4211,7 @@ def _set_fake_env(self): def test_stack_list_with_args(self): self.register_keystone_auth_fixture() - resp_dict = self.stack_list_resp_dict() + resp_dict = self.stack_list_resp_dict(include_project=True) resp = fakes.FakeHTTPResponse( 200, 'success, you', diff --git a/heatclient/v1/shell.py b/heatclient/v1/shell.py index a3179b31..a38c32a4 100644 --- a/heatclient/v1/shell.py +++ b/heatclient/v1/shell.py @@ -689,13 +689,16 @@ def do_stack_list(hc, args=None): if args.global_tenant or args.show_owner: fields.append('stack_owner') - if args.global_tenant: - fields.append('project') if args.show_deleted: fields.append('deletion_time') stacks = hc.stacks.list(**kwargs) + stacks = list(stacks) + for stk in stacks: + if hasattr(stk, 'project'): + fields.append('project') + break utils.print_list(stacks, fields, sortby_index=sortby_index) From c1e793f3b81403d32d665679001264716aa38505 Mon Sep 17 00:00:00 2001 From: Crag Wolfe Date: Sat, 28 Jan 2017 00:37:47 -0500 Subject: [PATCH 015/246] Don't always resolve outputs when showing a stack * Whenever a stack is created/updated/adopted, a call to show the stack is made. Do not resolve the outputs when showing the stack in this case because: * If we are not waiting (there is no --wait arg) for the stack to complete after a create/update/adopt command and immediately showing the stack, resolving the outputs is just incurring a pointless processing hit on the server (ultimately heat-engine) and delaying a response to the client. * Whether we --wait or or not, we only show "short" stack info which doesn't include outputs anyway. So, let's avoid the processing/time overhead of resolving the outputs. (In theory, with --wait we might want to show "long" output with stack outputs afterwards, but that would be additional functionality that should be handled in a different patch) * Add the --no-resolve-outputs option to "stack show" which already exists in the legacy heat stack-show command. Change-Id: Id0661b11fd3cece0df3981488de6976219556d7e Closes-Bug: #1659896 Closes-Bug: #1659899 --- heatclient/osc/v1/stack.py | 26 ++++++++++++++-------- heatclient/tests/unit/osc/v1/test_stack.py | 19 +++++++++++++--- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/heatclient/osc/v1/stack.py b/heatclient/osc/v1/stack.py index cf03cec6..f8cf9e33 100644 --- a/heatclient/osc/v1/stack.py +++ b/heatclient/osc/v1/stack.py @@ -380,19 +380,28 @@ def get_parser(self, prog_name): metavar='', help='Stack to display (name or ID)', ) + parser.add_argument( + '--no-resolve-outputs', action="store_true", + help=_('Do not resolve outputs of the stack.') + ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) heat_client = self.app.client_manager.orchestration - return _show_stack(heat_client, stack_id=parsed_args.stack, - format=parsed_args.formatter) + return _show_stack( + heat_client, stack_id=parsed_args.stack, + format=parsed_args.formatter, + resolve_outputs=(not parsed_args.no_resolve_outputs)) -def _show_stack(heat_client, stack_id, format='', short=False): +def _show_stack(heat_client, stack_id, format='', short=False, + resolve_outputs=True): try: - data = heat_client.stacks.get(stack_id=stack_id) + _resolve_outputs = not short and resolve_outputs + data = heat_client.stacks.get(stack_id=stack_id, + resolve_outputs=_resolve_outputs) except heat_exc.HTTPNotFound: raise exc.CommandError('Stack not found: %s' % stack_id) else: @@ -408,11 +417,10 @@ def _show_stack(heat_client, stack_id, format='', short=False): ] if not short: - columns += [ - 'parameters', - 'outputs', - 'links', - ] + columns.append('parameters') + if _resolve_outputs: + columns.append('outputs') + columns.append('links') exclude_columns = ('template_description',) for key in data.to_dict(): diff --git a/heatclient/tests/unit/osc/v1/test_stack.py b/heatclient/tests/unit/osc/v1/test_stack.py index c40bdf19..66a69929 100644 --- a/heatclient/tests/unit/osc/v1/test_stack.py +++ b/heatclient/tests/unit/osc/v1/test_stack.py @@ -149,7 +149,8 @@ def test_stack_create_wait(self, mock_poll): self.cmd.take_action(parsed_args) self.stack_client.create.assert_called_with(**self.defaults) - self.stack_client.get.assert_called_with(**{'stack_id': '1234'}) + self.stack_client.get.assert_called_with(**{'stack_id': '1234', + 'resolve_outputs': False}) @mock.patch('heatclient.common.event_utils.poll_for_events', return_value=('CREATE_FAILED', @@ -347,7 +348,8 @@ def test_stack_update_wait(self, ge, mock_poll): self.cmd.take_action(parsed_args) self.stack_client.update.assert_called_with(**self.defaults) - self.stack_client.get.assert_called_with(**{'stack_id': 'my_stack'}) + self.stack_client.get.assert_called_with(**{'stack_id': 'my_stack', + 'resolve_outputs': False}) @mock.patch('heatclient.common.event_utils.poll_for_events', return_value=('UPDATE_FAILED', @@ -414,6 +416,16 @@ def test_stack_show(self): self.cmd.take_action(parsed_args) self.stack_client.get.assert_called_with(**{ 'stack_id': 'my_stack', + 'resolve_outputs': True, + }) + + def test_stack_show_explicit_no_resolve(self): + arglist = ['--no-resolve-outputs', '--format', self.format, 'my_stack'] + parsed_args = self.check_parser(self.cmd, arglist, []) + self.cmd.take_action(parsed_args) + self.stack_client.get.assert_called_with(**{ + 'stack_id': 'my_stack', + 'resolve_outputs': False, }) def test_stack_show_short(self): @@ -729,7 +741,8 @@ def test_stack_adopt_wait(self, mock_poll): self.cmd.take_action(parsed_args) self.stack_client.create.assert_called_with(**self.defaults) - self.stack_client.get.assert_called_with(**{'stack_id': '1234'}) + self.stack_client.get.assert_called_with(**{'stack_id': '1234', + 'resolve_outputs': False}) @mock.patch('heatclient.common.event_utils.poll_for_events', return_value=('ADOPT_FAILED', From 28d8082f9ba50b95c699ab9a2473cd3421dc8048 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Antal?= Date: Tue, 7 Feb 2017 15:48:37 +0100 Subject: [PATCH 016/246] Handle log message interpolation by the logger According to OpenStack Guideline[1], logged string message should be interpolated by the logger. [1]: http://docs.openstack.org/developer/oslo.i18n/guidelines.html#adding-variables-to-log-messages Change-Id: Icbf550eecc0108e928aeb9e39dd28dfe482abe4b Closes-Bug: #1661262 --- heatclient/osc/v1/snapshot.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/heatclient/osc/v1/snapshot.py b/heatclient/osc/v1/snapshot.py index b5cc781d..13818f8e 100644 --- a/heatclient/osc/v1/snapshot.py +++ b/heatclient/osc/v1/snapshot.py @@ -40,7 +40,7 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) + self.log.debug('take_action(%s)', parsed_args) heat_client = self.app.client_manager.orchestration return self._list_snapshot(heat_client, parsed_args) @@ -80,7 +80,7 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) + self.log.debug('take_action(%s)', parsed_args) heat_client = self.app.client_manager.orchestration return self._show_snapshot(heat_client, parsed_args.stack, parsed_args.snapshot) @@ -119,7 +119,7 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) + self.log.debug('take_action(%s)', parsed_args) heat_client = self.app.client_manager.orchestration return self._restore_snapshot(heat_client, parsed_args) @@ -155,7 +155,7 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) + self.log.debug('take_action(%s)', parsed_args) heat_client = self.app.client_manager.orchestration try: @@ -195,7 +195,7 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) + self.log.debug('take_action(%s)', parsed_args) heat_client = self.app.client_manager.orchestration try: heat_client.stacks.snapshot_delete(parsed_args.stack, From cffc3d017a63947f0824ab9f6c05d7f5301f1d52 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 10 Feb 2017 04:52:39 +0000 Subject: [PATCH 017/246] Updated from global requirements Change-Id: Iecbae190ac430a919d61d67a92d27085503d0c18 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index ed21859f..c6d54837 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -13,7 +13,7 @@ oslosphinx>=4.7.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 python-openstackclient>=3.3.0 # Apache-2.0 sphinx!=1.3b1,<1.4,>=1.2.1 # BSD -tempest>=12.1.0 # Apache-2.0 +tempest>=14.0.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testscenarios>=0.4 # Apache-2.0/BSD testtools>=1.4.0 # MIT From 0b6aeafbf9a1873bb503f0482e4aeafd95ebfd57 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sat, 11 Feb 2017 17:51:19 +0000 Subject: [PATCH 018/246] Updated from global requirements Change-Id: I7e2324393ca2c5d0bfc1e0bb569b40b37f47b08a --- requirements.txt | 2 +- test-requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index d95661e1..58bf8038 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,5 +14,5 @@ oslo.utils>=3.18.0 # Apache-2.0 keystoneauth1>=2.18.0 # Apache-2.0 python-swiftclient>=3.2.0 # Apache-2.0 PyYAML>=3.10.0 # MIT -requests!=2.12.2,>=2.10.0 # Apache-2.0 +requests!=2.12.2,!=2.13.0,>=2.10.0 # Apache-2.0 six>=1.9.0 # MIT diff --git a/test-requirements.txt b/test-requirements.txt index c6d54837..fa2d8d4a 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -12,7 +12,7 @@ mox3!=0.19.0,>=0.7.0 # Apache-2.0 oslosphinx>=4.7.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 python-openstackclient>=3.3.0 # Apache-2.0 -sphinx!=1.3b1,<1.4,>=1.2.1 # BSD +sphinx>=1.5.1 # BSD tempest>=14.0.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testscenarios>=0.4 # Apache-2.0/BSD From dedc3ac0af9797f8d7d8897380b0a457a226c00d Mon Sep 17 00:00:00 2001 From: huangtianhua Date: Mon, 13 Feb 2017 18:15:24 +0800 Subject: [PATCH 019/246] Support openstack stack export Heat supports exporting data of stack preparing for abandon stack. This change supports 'openstack stack export' in client. Change-Id: I5d3778f307d10174dbb738548282f7495bd9c825 --- heatclient/osc/v1/stack.py | 44 ++++++++++++++++++++++ heatclient/tests/unit/osc/v1/test_stack.py | 36 ++++++++++++++++++ heatclient/v1/stacks.py | 7 ++++ setup.cfg | 1 + 4 files changed, 88 insertions(+) diff --git a/heatclient/osc/v1/stack.py b/heatclient/osc/v1/stack.py index 1c044aed..26da6139 100644 --- a/heatclient/osc/v1/stack.py +++ b/heatclient/osc/v1/stack.py @@ -874,6 +874,50 @@ def take_action(self, parsed_args): return columns, data +class ExportStack(format_utils.JsonFormat): + """Export stack data json.""" + + log = logging.getLogger(__name__ + '.ExportStack') + + def get_parser(self, prog_name): + parser = super(ExportStack, self).get_parser(prog_name) + parser.add_argument( + 'stack', + metavar='', + help=_('Name or ID of stack to export') + ) + parser.add_argument( + '--output-file', + metavar='', + help=_('File to output export data') + ) + + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + + client = self.app.client_manager.orchestration + + try: + data_info = client.stacks.export(stack_id=parsed_args.stack) + except heat_exc.HTTPNotFound: + msg = _('Stack not found: %s') % parsed_args.stack + raise exc.CommandError(msg) + + if parsed_args.output_file is not None: + try: + with open(parsed_args.output_file, 'w') as f: + f.write(jsonutils.dumps(data_info, indent=2)) + return [], None + except IOError as e: + raise exc.CommandError(str(e)) + + data = list(six.itervalues(data_info)) + columns = list(six.iterkeys(data_info)) + return columns, data + + class OutputShowStack(command.ShowOne): """Show stack output.""" diff --git a/heatclient/tests/unit/osc/v1/test_stack.py b/heatclient/tests/unit/osc/v1/test_stack.py index 8239f480..54fe9be5 100644 --- a/heatclient/tests/unit/osc/v1/test_stack.py +++ b/heatclient/tests/unit/osc/v1/test_stack.py @@ -777,6 +777,42 @@ def test_stack_adopt_wait_fail(self, mock_poll): self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) +class TestStackExport(TestStack): + + columns = ['stack_name', 'stack_status', 'id'] + data = ['my_stack', 'ABANDONED', '1234'] + + response = dict(zip(columns, data)) + + def setUp(self): + super(TestStackExport, self).setUp() + self.cmd = stack.ExportStack(self.app, None) + self.stack_client.export.return_value = self.response + + def test_stack_export(self): + arglist = ['my_stack'] + parsed_args = self.check_parser(self.cmd, arglist, []) + + columns, data = self.cmd.take_action(parsed_args) + + for column in self.columns: + self.assertIn(column, columns) + for datum in self.data: + self.assertIn(datum, data) + + @mock.patch('heatclient.osc.v1.stack.open', create=True) + def test_stack_export_output_file(self, mock_open): + arglist = ['my_stack', '--output-file', 'file.json'] + mock_open.return_value = mock.MagicMock(spec=io.IOBase) + parsed_args = self.check_parser(self.cmd, arglist, []) + + columns, data = self.cmd.take_action(parsed_args) + + mock_open.assert_called_once_with('file.json', 'w') + self.assertEqual([], columns) + self.assertIsNone(data) + + class TestStackAbandon(TestStack): columns = ['stack_name', 'stack_status', 'id'] diff --git a/heatclient/v1/stacks.py b/heatclient/v1/stacks.py index 2f02825a..b6e6bf6f 100644 --- a/heatclient/v1/stacks.py +++ b/heatclient/v1/stacks.py @@ -208,6 +208,13 @@ def abandon(self, stack_id): body = utils.get_response_body(resp) return body + def export(self, stack_id): + """Export data of a stack.""" + stack_identifier = self._resolve_stack_id(stack_id) + resp = self.client.get('/stacks/%s/export' % stack_identifier) + body = utils.get_response_body(resp) + return body + def snapshot(self, stack_id, name=None): """Snapshot a stack.""" stack_identifier = self._resolve_stack_id(stack_id) diff --git a/setup.cfg b/setup.cfg index 4f42c807..3955766b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -50,6 +50,7 @@ openstack.orchestration.v1 = software_deployment_output_show = heatclient.osc.v1.software_deployment:ShowOutputDeployment software_deployment_show = heatclient.osc.v1.software_deployment:ShowDeployment stack_abandon = heatclient.osc.v1.stack:AbandonStack + stack_export = heatclient.osc.v1.stack:ExportStack stack_adopt = heatclient.osc.v1.stack:AdoptStack stack_cancel = heatclient.osc.v1.stack:CancelStack stack_check = heatclient.osc.v1.stack:CheckStack From b1c819f7d9faac2b702bfc505ac706fd436484a4 Mon Sep 17 00:00:00 2001 From: rabi Date: Sat, 18 Feb 2017 11:44:59 +0530 Subject: [PATCH 020/246] Use endpoint_type with session client if endpoint_type is provided, map it to interface for SessionClient as this endpoint_type is ignored. Change-Id: If5bd96578e5bc4389b9eee6e53c598f6ffaf0d8d Closes-Buf: #1665858 --- heatclient/common/http.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/heatclient/common/http.py b/heatclient/common/http.py index dc405f4a..68d2908b 100644 --- a/heatclient/common/http.py +++ b/heatclient/common/http.py @@ -349,9 +349,13 @@ def _construct_http_client(endpoint=None, username=None, password=None, if session: if 'endpoint_override' not in kwargs and endpoint: kwargs['endpoint_override'] = endpoint - elif 'service_type' not in kwargs: + + if 'service_type' not in kwargs: kwargs['service_type'] = 'orchestration' + if 'interface' not in kwargs and endpoint_type: + kwargs['interface'] = endpoint_type + return SessionClient(session, auth=auth, **kwargs) else: return HTTPClient(endpoint=endpoint, username=username, From 23537d16ee34cfa3b2f70379a0c7a014e90ca694 Mon Sep 17 00:00:00 2001 From: ricolin Date: Thu, 2 Mar 2017 13:29:14 +0800 Subject: [PATCH 021/246] Update test requirement Since pbr already landed and the old version of hacking seems not work very well with pbr>=2, we should update it to match global requirement. Partial-Bug: #1669276 Partial-Bug: #1668848 Change-Id: Id74bd427bbb7f1ffb364dd24ede18d920fe89c8c --- test-requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-requirements.txt b/test-requirements.txt index fa2d8d4a..0c1c7ed3 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -3,12 +3,12 @@ # process, which may cause wedges in the gate later. # Hacking already pins down pep8, pyflakes and flake8 -hacking<0.11,>=0.10.0 +hacking>=0.12.0,!=0.13.0,<0.14 # Apache-2.0 coverage>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD requests-mock>=1.1 # Apache-2.0 mock>=2.0 # BSD -mox3!=0.19.0,>=0.7.0 # Apache-2.0 +mox3>=0.7.0,!=0.19.0 # Apache-2.0 oslosphinx>=4.7.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 python-openstackclient>=3.3.0 # Apache-2.0 From 60fe267b538c7ad47f2e5ce2608f3f8ef6111782 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 2 Mar 2017 11:54:27 +0000 Subject: [PATCH 022/246] Updated from global requirements Change-Id: I48095c8e2d9f8d6888e7994cfcdf6a3c0913cf5a --- requirements.txt | 4 ++-- setup.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 58bf8038..374c6888 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,14 +3,14 @@ # process, which may cause wedges in the gate later. Babel>=2.3.4 # BSD -pbr>=1.8 # Apache-2.0 +pbr>=2.0.0 # Apache-2.0 cliff>=2.3.0 # Apache-2.0 iso8601>=0.1.11 # MIT osc-lib>=1.2.0 # Apache-2.0 PrettyTable<0.8,>=0.7.1 # BSD oslo.i18n>=2.1.0 # Apache-2.0 oslo.serialization>=1.10.0 # Apache-2.0 -oslo.utils>=3.18.0 # Apache-2.0 +oslo.utils>=3.20.0 # Apache-2.0 keystoneauth1>=2.18.0 # Apache-2.0 python-swiftclient>=3.2.0 # Apache-2.0 PyYAML>=3.10.0 # MIT diff --git a/setup.py b/setup.py index 782bb21f..566d8443 100644 --- a/setup.py +++ b/setup.py @@ -25,5 +25,5 @@ pass setuptools.setup( - setup_requires=['pbr>=1.8'], + setup_requires=['pbr>=2.0.0'], pbr=True) From a4f67a194685afb75d4609916087f5c64b905c96 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 3 Mar 2017 22:58:26 +0000 Subject: [PATCH 023/246] Updated from global requirements Change-Id: I31979a0c8f7a69943437e5cb409a6c149e0ef57d --- test-requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-requirements.txt b/test-requirements.txt index 0c1c7ed3..f83f167a 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -3,12 +3,12 @@ # process, which may cause wedges in the gate later. # Hacking already pins down pep8, pyflakes and flake8 -hacking>=0.12.0,!=0.13.0,<0.14 # Apache-2.0 +hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0 coverage>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD requests-mock>=1.1 # Apache-2.0 mock>=2.0 # BSD -mox3>=0.7.0,!=0.19.0 # Apache-2.0 +mox3!=0.19.0,>=0.7.0 # Apache-2.0 oslosphinx>=4.7.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 python-openstackclient>=3.3.0 # Apache-2.0 From fb5e3908917199810c54ffbdc433e9cd327133b1 Mon Sep 17 00:00:00 2001 From: liyi Date: Tue, 21 Mar 2017 13:01:13 +0800 Subject: [PATCH 024/246] Remove log translations Log messages are no longer being translated. This removes all use of the _LE, _LI, and _LW translation markers to simplify logging and to avoid confusion with new contributions. See: http://lists.openstack.org/pipermail/openstack-i18n/2016-November/002574.html http://lists.openstack.org/pipermail/openstack-dev/2017-March/113365.html Change-Id: I601eee9f1fca65d5fb53e6b75482b74283908a29 --- heatclient/_i18n.py | 10 ---------- heatclient/common/hook_utils.py | 5 ++--- heatclient/common/http.py | 3 +-- heatclient/common/utils.py | 3 +-- heatclient/osc/v1/stack.py | 13 ++++++------- heatclient/v1/shell.py | 26 ++++++++++++-------------- 6 files changed, 22 insertions(+), 38 deletions(-) diff --git a/heatclient/_i18n.py b/heatclient/_i18n.py index 1177be41..f8c62691 100644 --- a/heatclient/_i18n.py +++ b/heatclient/_i18n.py @@ -28,16 +28,6 @@ # The primary translation function using the well-known name "_" _ = _translators.primary -# Translators for log levels. -# -# The abbreviated names are meant to reflect the usual use of a short -# name like '_'. The "L" is for "log" and the other letter comes from -# the level. -_LI = _translators.log_info -_LW = _translators.log_warning -_LE = _translators.log_error -_LC = _translators.log_critical - def get_available_languages(): return oslo_i18n.get_available_languages('heatclient') diff --git a/heatclient/common/hook_utils.py b/heatclient/common/hook_utils.py index 66b980a6..e9706908 100644 --- a/heatclient/common/hook_utils.py +++ b/heatclient/common/hook_utils.py @@ -15,7 +15,6 @@ from oslo_utils import fnmatch from heatclient._i18n import _ -from heatclient._i18n import _LE from heatclient import exc logger = logging.getLogger(__name__) @@ -29,8 +28,8 @@ def clear_hook(hc, stack_id, resource_name, hook_type): data={'unset_hook': hook_type}) except exc.HTTPNotFound: logger.error( - _LE("Stack %(stack)s or resource %(resource)s " - "not found for hook %(hook_type)"), + "Stack %(stack)s or resource %(resource)s " + "not found for hook %(hook_type)", {'resource': resource_name, 'stack': stack_id, 'hook_type': hook_type}) diff --git a/heatclient/common/http.py b/heatclient/common/http.py index 68d2908b..f5c50214 100644 --- a/heatclient/common/http.py +++ b/heatclient/common/http.py @@ -28,7 +28,6 @@ from six.moves.urllib import parse from heatclient._i18n import _ -from heatclient._i18n import _LW from heatclient.common import utils from heatclient import exc @@ -67,7 +66,7 @@ def get_system_ca_file(): if os.path.exists(ca): LOG.debug("Using ca file %s", ca) return ca - LOG.warning(_LW("System ca file could not be found.")) + LOG.warning("System ca file could not be found.") class HTTPClient(object): diff --git a/heatclient/common/utils.py b/heatclient/common/utils.py index 047b7092..013e6928 100644 --- a/heatclient/common/utils.py +++ b/heatclient/common/utils.py @@ -29,7 +29,6 @@ import yaml from heatclient._i18n import _ -from heatclient._i18n import _LE from heatclient import exc LOG = logging.getLogger(__name__) @@ -444,7 +443,7 @@ def get_response_body(resp): try: body = resp.json() except ValueError: - LOG.error(_LE('Could not decode response body as JSON')) + LOG.error('Could not decode response body as JSON') else: body = None return body diff --git a/heatclient/osc/v1/stack.py b/heatclient/osc/v1/stack.py index 26da6139..4a64221a 100644 --- a/heatclient/osc/v1/stack.py +++ b/heatclient/osc/v1/stack.py @@ -24,7 +24,6 @@ from six.moves.urllib import request from heatclient._i18n import _ -from heatclient._i18n import _LI from heatclient.common import event_utils from heatclient.common import format_utils from heatclient.common import hook_utils @@ -689,16 +688,16 @@ def take_action(self, parsed_args): _("Are you sure you want to delete this stack(s) [y/N]? ")) prompt_response = sys.stdin.readline().lower() if not prompt_response.startswith('y'): - self.log.info(_LI('User did not confirm stack delete so ' - 'taking no action.')) + self.log.info('User did not confirm stack delete so ' + 'taking no action.') return except KeyboardInterrupt: # ctrl-c - self.log.info(_LI('User did not confirm stack delete ' - '(ctrl-c) so taking no action.')) + self.log.info('User did not confirm stack delete ' + '(ctrl-c) so taking no action.') return except EOFError: # ctrl-d - self.log.info(_LI('User did not confirm stack delete ' - '(ctrl-d) so taking no action.')) + self.log.info('User did not confirm stack delete ' + '(ctrl-d) so taking no action.') return failure_count = 0 diff --git a/heatclient/v1/shell.py b/heatclient/v1/shell.py index a38c32a4..70ffc9f7 100644 --- a/heatclient/v1/shell.py +++ b/heatclient/v1/shell.py @@ -23,8 +23,6 @@ import yaml from heatclient._i18n import _ -from heatclient._i18n import _LI -from heatclient._i18n import _LW from heatclient.common import deployment_utils from heatclient.common import event_utils from heatclient.common import hook_utils @@ -38,8 +36,8 @@ def show_deprecated(deprecated, recommended): - logger.warning(_LW('"%(old)s" is deprecated, ' - 'please use "%(new)s" instead'), + logger.warning('"%(old)s" is deprecated, ' + 'please use "%(new)s" instead', {'old': deprecated, 'new': recommended} ) @@ -105,8 +103,8 @@ def do_stack_create(hc, args): env_paths=args.environment_file, env_list_tracker=env_files_list) if args.create_timeout: - logger.warning(_LW('%(arg1)s is deprecated, ' - 'please use %(arg2)s instead'), + logger.warning('%(arg1)s is deprecated, ' + 'please use %(arg2)s instead', { 'arg1': '-c/--create-timeout', 'arg2': '-t/--timeout'}) @@ -192,8 +190,8 @@ def do_stack_adopt(hc, args): raise exc.CommandError('Invalid adopt-file, no data!') if args.create_timeout: - logger.warning(_LW('%(arg1)s is deprecated, ' - 'please use %(arg2)s instead'), + logger.warning('%(arg1)s is deprecated, ' + 'please use %(arg2)s instead', { 'arg1': '-c/--create-timeout', 'arg2': '-t/--timeout'}) @@ -308,16 +306,16 @@ def do_stack_delete(hc, args): _("Are you sure you want to delete this stack(s) [y/N]? ")) prompt_response = sys.stdin.readline().lower() if not prompt_response.startswith('y'): - logger.info(_LI( - 'User did not confirm stack delete so taking no action.')) + logger.info( + 'User did not confirm stack delete so taking no action.') return except KeyboardInterrupt: # ctrl-c - logger.info(_LI( - 'User did not confirm stack delete (ctrl-c) so taking no action.')) + logger.info( + 'User did not confirm stack delete (ctrl-c) so taking no action.') return except EOFError: # ctrl-d - logger.info(_LI( - 'User did not confirm stack delete (ctrl-d) so taking no action.')) + logger.info( + 'User did not confirm stack delete (ctrl-d) so taking no action.') return for sid in args.id: From 34d3a000d29b8a51b69dd3e48f78f29e6a3bb57b Mon Sep 17 00:00:00 2001 From: Thomas Herve Date: Fri, 24 Mar 2017 16:42:32 +0100 Subject: [PATCH 025/246] Fix error message when deployment not found This fixes the error messages when trying to do a openstack software deployment show with a wrong id. Change-Id: I23c427e33b28b327563769106fcadd20ff599454 --- heatclient/osc/v1/software_deployment.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/heatclient/osc/v1/software_deployment.py b/heatclient/osc/v1/software_deployment.py index 37abcb46..eccab42b 100644 --- a/heatclient/osc/v1/software_deployment.py +++ b/heatclient/osc/v1/software_deployment.py @@ -246,7 +246,9 @@ def take_action(self, parsed_args): data = heat_client.software_deployments.get( deployment_id=parsed_args.deployment) except heat_exc.HTTPNotFound: - raise exc.CommandError(_('Software Deployment not found: %s') % id) + raise exc.CommandError( + _('Software Deployment not found: %s') + % parsed_args.deployment) else: columns = [ 'id', From e805091d62eb593913f371ed8fa753fca01aca7e Mon Sep 17 00:00:00 2001 From: Thomas Herve Date: Tue, 28 Mar 2017 14:31:48 +0200 Subject: [PATCH 026/246] Pass event filters to the server side This passes event filters to the server side. It only matters for event_time with limit now, but this fixes that case, and the case for resource_type as well. Change-Id: Ide8f0481cf031cc61d4bb43d1d92109d42326e95 --- heatclient/osc/v1/event.py | 20 ++++++++++++++------ heatclient/tests/unit/osc/v1/test_event.py | 12 +++++++++--- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/heatclient/osc/v1/event.py b/heatclient/osc/v1/event.py index b577e941..8f25ea34 100644 --- a/heatclient/osc/v1/event.py +++ b/heatclient/osc/v1/event.py @@ -175,6 +175,20 @@ def take_action(self, parsed_args): else: nested_depth = 0 + if parsed_args.sort: + sorts = [] + sort_keys = [] + for sort in parsed_args.sort: + if sort.startswith(":"): + sorts.append(":".join(["event_time", sort.lstrip(":")])) + else: + sorts.append(sort) + sort_keys.append(sort.split(":")[0]) + kwargs['sort_keys'] = sort_keys + + if ":" in parsed_args.sort[0]: + kwargs['sort_dir'] = parsed_args.sort[0].split(":")[1] + if parsed_args.follow: if parsed_args.formatter != 'log': msg = _('--follow can only be specified with --format log') @@ -207,12 +221,6 @@ def take_action(self, parsed_args): limit=parsed_args.limit) if parsed_args.sort: - sorts = [] - for sort in parsed_args.sort: - if sort.startswith(":"): - sorts.append(":".join(["event_time", sort.lstrip(":")])) - else: - sorts.append(sort) events = utils.sort_items(events, ','.join(sorts)) if parsed_args.formatter == 'log': diff --git a/heatclient/tests/unit/osc/v1/test_event.py b/heatclient/tests/unit/osc/v1/test_event.py index 3c64b920..d7ecb600 100644 --- a/heatclient/tests/unit/osc/v1/test_event.py +++ b/heatclient/tests/unit/osc/v1/test_event.py @@ -194,7 +194,9 @@ def test_event_list_sort(self, mock_sort_items): mock_sort_items.assert_called_with(mock.ANY, "resource_name:desc") - self.event_client.list.assert_called_with(**self.defaults) + self.event_client.list.assert_called_with( + filters={}, resource_name=None, sort_dir='desc', + sort_keys=['resource_name'], stack_id='my_stack') self.assertEqual(self.fields, columns) @mock.patch('osc_lib.utils.sort_items') @@ -209,7 +211,9 @@ def test_event_list_sort_multiple(self, mock_sort_items): mock_sort_items.assert_called_with(mock.ANY, "resource_name:desc,id:asc") - self.event_client.list.assert_called_with(**self.defaults) + self.event_client.list.assert_called_with( + filters={}, resource_name=None, sort_dir='desc', + sort_keys=['resource_name', 'id'], stack_id='my_stack') self.assertEqual(self.fields, columns) @mock.patch('osc_lib.utils.sort_items') @@ -223,7 +227,9 @@ def test_event_list_sort_default_key(self, mock_sort_items): columns, data = self.cmd.take_action(parsed_args) mock_sort_items.assert_called_with(mock.ANY, "event_time:desc") - self.event_client.list.assert_called_with(**self.defaults) + self.event_client.list.assert_called_with( + filters={}, resource_name=None, sort_dir='desc', sort_keys=[], + stack_id='my_stack') self.assertEqual(self.fields, columns) @mock.patch('time.sleep') From 954e475a6a0a12432ec325d7579460fabcf3f40a Mon Sep 17 00:00:00 2001 From: Thomas Herve Date: Tue, 28 Mar 2017 15:19:00 +0200 Subject: [PATCH 027/246] Build event stack_name from links When retrieving events from nested stacks, we used to do a query per stack and thus get the stack that way. We changed it to do only one API call, but didn't fix the way the stack name was set. We need to use the event links to retrieve the stack name, instead of the root stack. Closes-Bug: #1676896 Change-Id: I09ab1cc2c6aaab02036b124b1dfa561dd6132083 --- heatclient/common/event_utils.py | 12 +++++++++++- heatclient/tests/unit/test_event_utils.py | 5 +++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/heatclient/common/event_utils.py b/heatclient/common/event_utils.py index df000f73..8551b56c 100644 --- a/heatclient/common/event_utils.py +++ b/heatclient/common/event_utils.py @@ -151,6 +151,15 @@ def _get_nested_events(hc, nested_depth, stack_id, event_args): return nested_events +def _get_stack_name_from_links(event): + links = dict((l.get('rel'), + l.get('href')) for l in getattr(event, 'links', [])) + href = links.get('stack') + if not href: + return + return href.split('/stacks/', 1)[-1].split('/')[0] + + def _get_stack_events(hc, stack_id, event_args): event_args['stack_id'] = stack_id try: @@ -160,9 +169,10 @@ def _get_stack_events(hc, stack_id, event_args): # just use the message that the server sent us. raise exc.CommandError(str(ex)) else: + stack_name = stack_id.split("/")[0] # Show which stack the event comes from (for nested events) for e in events: - e.stack_name = stack_id.split("/")[0] + e.stack_name = _get_stack_name_from_links(e) or stack_name return events diff --git a/heatclient/tests/unit/test_event_utils.py b/heatclient/tests/unit/test_event_utils.py index a9e89122..b080e000 100644 --- a/heatclient/tests/unit/test_event_utils.py +++ b/heatclient/tests/unit/test_event_utils.py @@ -47,8 +47,9 @@ def _mock_resource(resource_id, nested_id=None): @staticmethod def _mock_event(event_id, resource_id, resource_status='CREATE_COMPLETE'): - ev_info = {"links": [{"href": "http://heat/foo", "rel": "self"}, - {"href": "http://heat/stacks/a", "rel": "stack"}], + ev_info = {"links": [ + {"href": "http://heat/foo", "rel": "self"}, + {"href": "http://heat/stacks/astack", "rel": "stack"}], "logical_resource_id": resource_id, "physical_resource_id": resource_id, "resource_name": resource_id, From 61aa092ce5cdee495e6faf63c36419e5583d3627 Mon Sep 17 00:00:00 2001 From: M V P Nitesh Date: Fri, 31 Mar 2017 15:15:17 +0530 Subject: [PATCH 028/246] Replace six.iteritems() with .items() 1.As mentioned in [1], we should avoid using six.iteritems to achieve iterators. We can use dict.items instead, as it will return iterators in PY3 as well. And dict.items/keys will more readable. 2.In py2, the performance about list should be negligible, see the link [2]. [1] https://wiki.openstack.org/wiki/Python3 [2] http://lists.openstack.org/pipermail/openstack-dev/2015-June/066391.html Change-Id: I3913e8876b34e112140788d7a405da6eedfb5f29 --- heatclient/common/base.py | 4 ++-- heatclient/common/template_utils.py | 4 ++-- heatclient/common/utils.py | 2 +- heatclient/tests/functional/osc/v1/base.py | 4 ++-- heatclient/v1/events.py | 3 +-- heatclient/v1/resources.py | 3 +-- heatclient/v1/software_configs.py | 3 +-- heatclient/v1/stacks.py | 3 +-- 8 files changed, 11 insertions(+), 15 deletions(-) diff --git a/heatclient/common/base.py b/heatclient/common/base.py index f631603d..cca6a43a 100644 --- a/heatclient/common/base.py +++ b/heatclient/common/base.py @@ -293,7 +293,7 @@ def build_url(self, base_url=None, **kwargs): def _filter_kwargs(self, kwargs): """Drop null values and handle ids.""" - for key, ref in six.iteritems(kwargs.copy()): + for key, ref in kwargs.copy().items(): if ref is None: kwargs.pop(key) else: @@ -451,7 +451,7 @@ def human_id(self): return None def _add_details(self, info): - for (k, v) in six.iteritems(info): + for (k, v) in info.items(): try: setattr(self, k, v) self._info[k] = v diff --git a/heatclient/common/template_utils.py b/heatclient/common/template_utils.py index c81da43d..9e9c85bf 100644 --- a/heatclient/common/template_utils.py +++ b/heatclient/common/template_utils.py @@ -144,7 +144,7 @@ def get_file_contents(from_data, files, base_url=None, is_object, object_request) if isinstance(from_data, dict): - for key, value in six.iteritems(from_data): + for key, value in from_data.items(): if ignore_if and ignore_if(key, value): continue @@ -351,7 +351,7 @@ def ignore_if(key, value): get_file_contents(rr, files, base_url, ignore_if, is_object=is_object, object_request=object_request) - for res_name, res_dict in six.iteritems(rr.get('resources', {})): + for res_name, res_dict in rr.get('resources', {}).items(): res_base_url = res_dict.get('base_url', base_url) get_file_contents( res_dict, files, res_base_url, ignore_if, diff --git a/heatclient/common/utils.py b/heatclient/common/utils.py index 013e6928..c9d1ec05 100644 --- a/heatclient/common/utils.py +++ b/heatclient/common/utils.py @@ -376,7 +376,7 @@ def format_parameter_file(param_files, template_file=None, template_file, template_url)) param_file = {} - for key, value in six.iteritems(params): + for key, value in params.items(): param_file[key] = resolve_param_get_file(value, template_base_url) return param_file diff --git a/heatclient/tests/functional/osc/v1/base.py b/heatclient/tests/functional/osc/v1/base.py index 31a9cd7f..b17fb4c0 100644 --- a/heatclient/tests/functional/osc/v1/base.py +++ b/heatclient/tests/functional/osc/v1/base.py @@ -48,7 +48,7 @@ def show_to_dict(self, output): items = self.parser.listing(output) for item in items: obj[item['Field']] = six.text_type(item['Value']) - return dict((self._key_name(k), v) for k, v in six.iteritems(obj)) + return dict((self._key_name(k), v) for k, v in obj.items()) def _key_name(self, key): return key.lower().replace(' ', '_') @@ -60,7 +60,7 @@ def list_to_dict(self, output, id): if item['ID'] == id: obj = item break - return dict((self._key_name(k), v) for k, v in six.iteritems(obj)) + return dict((self._key_name(k), v) for k, v in obj.items()) def _stack_create(self, name, template, parameters=[], wait=True): cmd = 'stack create ' + name diff --git a/heatclient/v1/events.py b/heatclient/v1/events.py index 29517122..8430d75c 100644 --- a/heatclient/v1/events.py +++ b/heatclient/v1/events.py @@ -15,7 +15,6 @@ import collections from oslo_utils import encodeutils -import six from six.moves.urllib import parse from heatclient.common import base @@ -54,7 +53,7 @@ def list(self, stack_id, resource_name=None, **kwargs): filters = kwargs.pop('filters') params.update(filters) - for key, value in six.iteritems(kwargs): + for key, value in kwargs.items(): if value: params[key] = value diff --git a/heatclient/v1/resources.py b/heatclient/v1/resources.py index ca229ef2..dcc71a66 100644 --- a/heatclient/v1/resources.py +++ b/heatclient/v1/resources.py @@ -14,7 +14,6 @@ # under the License. from oslo_utils import encodeutils -import six from six.moves.urllib import parse from heatclient.common import base @@ -60,7 +59,7 @@ def list(self, stack_id, **kwargs): filters = kwargs.pop('filters') params.update(filters) - for key, value in six.iteritems(kwargs): + for key, value in kwargs.items(): if value: params[key] = value diff --git a/heatclient/v1/software_configs.py b/heatclient/v1/software_configs.py index 79fbddde..8d5c9e12 100644 --- a/heatclient/v1/software_configs.py +++ b/heatclient/v1/software_configs.py @@ -10,7 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -import six from six.moves.urllib import parse from heatclient.common import base @@ -38,7 +37,7 @@ def list(self, **kwargs): """ qparams = {} - for opt, val in six.iteritems(kwargs): + for opt, val in kwargs.items(): if val: qparams[opt] = val diff --git a/heatclient/v1/stacks.py b/heatclient/v1/stacks.py index b6e6bf6f..1a762867 100644 --- a/heatclient/v1/stacks.py +++ b/heatclient/v1/stacks.py @@ -13,7 +13,6 @@ # License for the specific language governing permissions and limitations # under the License. -import six from six.moves.urllib import parse from heatclient._i18n import _ @@ -151,7 +150,7 @@ def paginate(params): filters = kwargs.pop('filters') params.update(filters) - for key, value in six.iteritems(kwargs): + for key, value in kwargs.items(): if value: params[key] = value From 88fae590c6ff5d6e39695a5805823a595ef2c82f Mon Sep 17 00:00:00 2001 From: rabi Date: Thu, 2 Mar 2017 11:48:44 +0530 Subject: [PATCH 029/246] Use upper-constraints for all jobs Change-Id: I17aae5f4b5c72425d72a3d9ba0a44110e2e6a65c --- tools/tox_install.sh | 28 ++++++++++++++++++++++++++++ tox.ini | 9 +++------ 2 files changed, 31 insertions(+), 6 deletions(-) create mode 100755 tools/tox_install.sh diff --git a/tools/tox_install.sh b/tools/tox_install.sh new file mode 100755 index 00000000..e93c73b4 --- /dev/null +++ b/tools/tox_install.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +# Client constraint file contains this client version pin that is in conflict +# with installing the client from source. We should remove the version pin in +# the constraints file before applying it for from-source installation. + +CONSTRAINTS_FILE=$1 +shift 1 + +set -e + +localfile="$VIRTUAL_ENV/log/upper-constraints.txt" + +if [[ $CONSTRAINTS_FILE != http* ]]; then + CONSTRAINTS_FILE=file://$CONSTRAINTS_FILE +fi + +curl $CONSTRAINTS_FILE --insecure --progress-bar --output $localfile + +pip install -c$localfile openstack-requirements + +# This is the main purpose of the script: Allow local installation of +# the current repo. It is listed in constraints file and thus any +# install will be constrained and we need to unconstrain it. +edit-constraints $localfile -- $CLIENT_NAME + +pip install -c$localfile -U $* +exit $? diff --git a/tox.ini b/tox.ini index 7e1eb4d8..c300addd 100644 --- a/tox.ini +++ b/tox.ini @@ -5,19 +5,15 @@ skipsdist = True [testenv] setenv = VIRTUAL_ENV={envdir} + CLIENT_NAME=python-heatclient usedevelop = True -install_command = pip install -U {opts} {packages} +install_command = {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages} deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = find . -type f -name "*.py[c|o]" -delete python setup.py testr --slowest --testr-args='{posargs}' whitelist_externals = find -[testenv:pypy] -deps = setuptools<3.2 - -r{toxinidir}/requirements.txt - -r{toxinidir}/test-requirements.txt - [testenv:debug] commands = oslo_debug_helper -t heatclient/tests {posargs} @@ -39,6 +35,7 @@ commands = {posargs} [testenv:functional] setenv = OS_TEST_PATH = ./heatclient/tests/functional + CLIENT_NAME=python-heatclient passenv = OS_* [testenv:cover] From b80166d848dd26718cfe0627bfec22774213616b Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 7 Apr 2017 06:15:40 +0000 Subject: [PATCH 030/246] Updated from global requirements Change-Id: I66c8b6b16e9af96ce8a3b0434ff27e4924619a86 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 374c6888..8ae83585 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -Babel>=2.3.4 # BSD +Babel!=2.4.0,>=2.3.4 # BSD pbr>=2.0.0 # Apache-2.0 cliff>=2.3.0 # Apache-2.0 iso8601>=0.1.11 # MIT From 02b6f451a330580a606b420d966f448f8567126e Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 12 Apr 2017 04:21:07 +0000 Subject: [PATCH 031/246] Updated from global requirements Change-Id: I4f27c635c490567f856e70d03844a5498531f474 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8ae83585..e766c158 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ # process, which may cause wedges in the gate later. Babel!=2.4.0,>=2.3.4 # BSD -pbr>=2.0.0 # Apache-2.0 +pbr!=2.1.0,>=2.0.0 # Apache-2.0 cliff>=2.3.0 # Apache-2.0 iso8601>=0.1.11 # MIT osc-lib>=1.2.0 # Apache-2.0 From 7611ee7a3c5414f5f5308bf830ca75319f8a2263 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 19 Apr 2017 10:27:06 +0100 Subject: [PATCH 032/246] Explicitly set 'builders' option An upcoming release of pbr will require explicitly stating which builders are requested, rather than defaulting to html and man. Head off any potential impact this may cause by explicitly setting this configuration now. Change-Id: I3ebd7d2086a47bcedc6fb35ca90bc1bb6e077250 --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 3955766b..8dad909d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -103,6 +103,7 @@ setup-hooks = pbr.hooks.setup_hook [build_sphinx] +builders = html,man source-dir = doc/source build-dir = doc/build all_files = 1 From e2d92f7930a3f493510c098111d287c71ed9d96b Mon Sep 17 00:00:00 2001 From: ricolin Date: Wed, 16 Nov 2016 11:48:54 +0800 Subject: [PATCH 033/246] Add '--yes' for openstack stack snapshot delete Add optional arguments '--yes' or '-y' in osc cli `openstack stack snapshot delete`. There is no judgement before use that cli So propose to add it in case that we delete the snapshot unintentionally. Closes-Bug: #1642874 Change-Id: If7b515dff64a18f56046b890279c2c59b0ab9dc7 --- heatclient/osc/v1/snapshot.py | 24 ++++++++++++++++ heatclient/tests/unit/osc/v1/test_snapshot.py | 28 ++++++++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/heatclient/osc/v1/snapshot.py b/heatclient/osc/v1/snapshot.py index b5cc781d..dd404ad0 100644 --- a/heatclient/osc/v1/snapshot.py +++ b/heatclient/osc/v1/snapshot.py @@ -14,6 +14,7 @@ """Orchestration v1 Stack Snapshot implementations.""" import logging +import sys from osc_lib.command import command from osc_lib import exceptions as exc @@ -192,11 +193,34 @@ def get_parser(self, prog_name): metavar='', help=_('ID of stack snapshot') ) + parser.add_argument( + '-y', '--yes', + action='store_true', + help=_('Skip yes/no prompt (assume yes)') + ) return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) heat_client = self.app.client_manager.orchestration + msg = ('User did not confirm snapshot delete ' + '%sso taking no action.') + try: + if not parsed_args.yes and sys.stdin.isatty(): + sys.stdout.write( + _('Are you sure you want to delete the snapshot of this ' + 'stack [Y/N]?')) + prompt_response = sys.stdin.readline().lower() + if not prompt_response.startswith('y'): + self.log.info(msg, '') + return + except KeyboardInterrupt: # ctrl-c + self.log.info(msg, '(ctrl-c) ') + return + except EOFError: # ctrl-d + self.log.info(msg, '(ctrl-d) ') + return + try: heat_client.stacks.snapshot_delete(parsed_args.stack, parsed_args.snapshot) diff --git a/heatclient/tests/unit/osc/v1/test_snapshot.py b/heatclient/tests/unit/osc/v1/test_snapshot.py index f51a6ed3..7f69c610 100644 --- a/heatclient/tests/unit/osc/v1/test_snapshot.py +++ b/heatclient/tests/unit/osc/v1/test_snapshot.py @@ -10,8 +10,9 @@ # License for the specific language governing permissions and limitations # under the License. # - +import mock from osc_lib import exceptions as exc +import six from heatclient import exc as heat_exc from heatclient.osc.v1 import snapshot @@ -157,3 +158,28 @@ def test_snapshot_delete_not_found(self): exc.CommandError, self.cmd.take_action, parsed_args) + + @mock.patch('sys.stdin', spec=six.StringIO) + def test_snapshot_delete_prompt(self, mock_stdin): + arglist = ['my_stack', 'snapshot_id'] + mock_stdin.isatty.return_value = True + mock_stdin.readline.return_value = 'y' + parsed_args = self.check_parser(self.cmd, arglist, []) + + self.cmd.take_action(parsed_args) + + mock_stdin.readline.assert_called_with() + self.stack_client.snapshot_delete.assert_called_with('my_stack', + 'snapshot_id') + + @mock.patch('sys.stdin', spec=six.StringIO) + def test_snapshot_delete_prompt_no(self, mock_stdin): + arglist = ['my_stack', 'snapshot_id'] + mock_stdin.isatty.return_value = True + mock_stdin.readline.return_value = 'n' + parsed_args = self.check_parser(self.cmd, arglist, []) + + self.cmd.take_action(parsed_args) + + mock_stdin.readline.assert_called_with() + self.stack_client.snapshot_delete.assert_not_called() From 241265c5a38be0db3a018139daf270b9b2fd9043 Mon Sep 17 00:00:00 2001 From: huangtianhua Date: Thu, 9 Feb 2017 10:24:19 +0800 Subject: [PATCH 034/246] Remove support for py34 The gating on python 3.4 is restricted to <= Mitaka. This is due to the change from Ubuntu Trusty to Xenial, where only python3.5 is available. There is no need to continue to keep these settings. Change-Id: I3a2fc7779fa0c806827d9eda2ffdbc207788b22d --- setup.cfg | 1 - tox.ini | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 3955766b..2a5fcc39 100644 --- a/setup.cfg +++ b/setup.cfg @@ -17,7 +17,6 @@ classifier = Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 - Programming Language :: Python :: 3.4 Programming Language :: Python :: 3.5 [files] diff --git a/tox.ini b/tox.ini index c300addd..8b6026ba 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = pypy,py35,py34,py27,pep8 +envlist = pypy,py35,py27,pep8 minversion = 1.6 skipsdist = True From a625d3bb93a18193058a09cb7887e9cfa4ea9df0 Mon Sep 17 00:00:00 2001 From: rabi Date: Tue, 9 May 2017 14:39:36 +0530 Subject: [PATCH 035/246] Don't encode path separators Encoding the path separators results in urls refused by by apache with 404 unless AllowEncodedSlashes is turned on, which is not the default. The existing encoding of path separators seems unnecessary. Change-Id: I3b77aafc6104119503363e5db36b0379964daba5 --- heatclient/tests/unit/test_events.py | 10 +++---- heatclient/tests/unit/test_resources.py | 12 ++++---- heatclient/tests/unit/test_shell.py | 40 ++++++++++++------------- heatclient/v1/events.py | 8 ++--- heatclient/v1/resource_types.py | 4 +-- heatclient/v1/resources.py | 18 +++++------ heatclient/v1/software_deployments.py | 2 +- heatclient/v1/template_versions.py | 2 +- 8 files changed, 48 insertions(+), 48 deletions(-) diff --git a/heatclient/tests/unit/test_events.py b/heatclient/tests/unit/test_events.py index 1d4d84d7..b63aebe3 100644 --- a/heatclient/tests/unit/test_events.py +++ b/heatclient/tests/unit/test_events.py @@ -33,7 +33,7 @@ def test_list_event(self): manager.list(stack_id, resource_name) # Make sure url is correct. manager._list.assert_called_once_with( - '/stacks/teststack%2Fabcd1234/' + '/stacks/teststack/abcd1234/' 'resources/testresource/events', "events") mock_re.assert_called_once_with(stack_id) @@ -50,7 +50,7 @@ def test_list_event_with_unicode_resource_name(self): manager.list(stack_id, resource_name) # Make sure url is correct. manager._list.assert_called_once_with( - '/stacks/teststack%2Fabcd1234/' + '/stacks/teststack/abcd1234/' 'resources/%E5%B7%A5%E4%BD%9C/' 'events', "events") mock_re.assert_called_once_with(stack_id) @@ -88,7 +88,7 @@ def test_list_event_with_kwargs(self): url, param = args[0] self.assertEqual("events", param) base_url, query_params = utils.parse_query_url(url) - expected_base_url = ('/stacks/teststack%2Fabcd1234/' + expected_base_url = ('/stacks/teststack/abcd1234/' 'resources/testresource/events') self.assertEqual(expected_base_url, base_url) expected_query_dict = {'marker': ['6d6935f4-0ae5'], @@ -110,7 +110,7 @@ class FakeAPI(object): def json_request(self, *args, **kwargs): expect = ('GET', - '/stacks/teststack%2Fabcd1234/resources' + '/stacks/teststack/abcd1234/resources' '/testresource/events/1') assert args == expect return {}, {'event': []} @@ -137,7 +137,7 @@ class FakeAPI(object): def json_request(self, *args, **kwargs): expect = ('GET', - '/stacks/teststack%2Fabcd1234/resources' + '/stacks/teststack/abcd1234/resources' '/%E5%B7%A5%E4%BD%9C/events/1') assert args == expect return {}, {'event': []} diff --git a/heatclient/tests/unit/test_resources.py b/heatclient/tests/unit/test_resources.py index b3c5c307..96e82035 100644 --- a/heatclient/tests/unit/test_resources.py +++ b/heatclient/tests/unit/test_resources.py @@ -73,7 +73,7 @@ def test_get(self): fields = {'stack_id': 'teststack', 'resource_name': 'testresource'} expect = ('GET', - '/stacks/teststack%2Fabcd1234/resources' + '/stacks/teststack/abcd1234/resources' '/testresource') key = 'resource' @@ -86,7 +86,7 @@ def test_get_with_attr(self): 'resource_name': 'testresource', 'with_attr': ['attr_a', 'attr_b']} expect = ('GET', - '/stacks/teststack%2Fabcd1234/resources' + '/stacks/teststack/abcd1234/resources' '/testresource?with_attr=attr_a&with_attr=attr_b') key = 'resource' @@ -98,7 +98,7 @@ def test_get_with_unicode_resource_name(self): fields = {'stack_id': 'teststack', 'resource_name': u'\u5de5\u4f5c'} expect = ('GET', - '/stacks/teststack%2Fabcd1234/resources' + '/stacks/teststack/abcd1234/resources' '/%E5%B7%A5%E4%BD%9C') key = 'resource' @@ -154,7 +154,7 @@ def test_metadata(self): fields = {'stack_id': 'teststack', 'resource_name': 'testresource'} expect = ('GET', - '/stacks/teststack%2Fabcd1234/resources' + '/stacks/teststack/abcd1234/resources' '/testresource/metadata') key = 'metadata' @@ -192,7 +192,7 @@ def test_signal(self): 'resource_name': 'testresource', 'data': 'Some content'} expect = ('POST', - '/stacks/teststack%2Fabcd1234/resources' + '/stacks/teststack/abcd1234/resources' '/testresource/signal') key = 'signal' @@ -206,7 +206,7 @@ def test_mark_unhealthy(self): 'mark_unhealthy': 'True', 'resource_status_reason': 'Anything'} expect = ('PATCH', - '/stacks/teststack%2Fabcd1234/resources' + '/stacks/teststack/abcd1234/resources' '/testresource') key = 'mark_unhealthy' diff --git a/heatclient/tests/unit/test_shell.py b/heatclient/tests/unit/test_shell.py index abb58456..cdf6a745 100644 --- a/heatclient/tests/unit/test_shell.py +++ b/heatclient/tests/unit/test_shell.py @@ -563,7 +563,7 @@ def test_event_list(self): rsrc_eventid2=eventid2 ) - self.requests.get('http://heat.example.com/stacks/myStack%2F60f83b5e/' + self.requests.get('http://heat.example.com/stacks/myStack/60f83b5e/' 'resources/myDeployment/events', headers={'Content-Type': 'application/json'}, json=resp_dict) @@ -2622,9 +2622,9 @@ def test_event_list(self): resource_name = 'testresource/1' self.mock_request_get( '/stacks/%s/resources/%s/events?sort_dir=asc' % ( - parse.quote(stack_id, ''), + parse.quote(stack_id), parse.quote(encodeutils.safe_encode( - resource_name), '')), + resource_name))), resp_dict) self.m.ReplayAll() @@ -2698,10 +2698,10 @@ def test_event_show(self): self.mock_request_get( '/stacks/%s/resources/%s/events/%s' % ( - parse.quote(stack_id, ''), + parse.quote(stack_id), parse.quote(encodeutils.safe_encode( - resource_name), ''), - parse.quote(self.event_id_one, '') + resource_name)), + parse.quote(self.event_id_one) ), resp_dict) @@ -3281,9 +3281,9 @@ def test_resource_show_with_attrs(self): self.mock_request_get( '/stacks/%s/resources/%s?with_attr=attr_a&with_attr=attr_b' % ( - parse.quote(stack_id, ''), + parse.quote(stack_id), parse.quote(encodeutils.safe_encode( - resource_name), '') + resource_name)) ), resp_dict) self.m.ReplayAll() @@ -3323,9 +3323,9 @@ def test_resource_signal(self): self.mock_request_post( '/stacks/%s/resources/%s/signal' % ( - parse.quote(stack_id, ''), + parse.quote(stack_id), parse.quote(encodeutils.safe_encode( - resource_name), '') + resource_name)) ), '', data={'message': 'Content'} @@ -3345,9 +3345,9 @@ def test_resource_signal_no_data(self): self.mock_request_post( '/stacks/%s/resources/%s/signal' % ( - parse.quote(stack_id, ''), + parse.quote(stack_id), parse.quote(encodeutils.safe_encode( - resource_name), '') + resource_name)) ), '', data=None @@ -3405,9 +3405,9 @@ def test_resource_signal_data_file(self): self.mock_request_post( '/stacks/%s/resources/%s/signal' % ( - parse.quote(stack_id, ''), + parse.quote(stack_id), parse.quote(encodeutils.safe_encode( - resource_name), '') + resource_name)) ), '', data={'message': 'Content'} @@ -3430,9 +3430,9 @@ def test_resource_mark_unhealthy(self): self.mock_request_patch( '/stacks/%s/resources/%s' % ( - parse.quote(stack_id, ''), + parse.quote(stack_id), parse.quote(encodeutils.safe_encode( - resource_name), '') + resource_name)) ), '', req_headers=False, @@ -3453,9 +3453,9 @@ def test_resource_mark_unhealthy_reset(self): self.mock_request_patch( '/stacks/%s/resources/%s' % ( - parse.quote(stack_id, ''), + parse.quote(stack_id), parse.quote(encodeutils.safe_encode( - resource_name), '') + resource_name)) ), '', req_headers=False, @@ -3476,9 +3476,9 @@ def test_resource_mark_unhealthy_no_reason(self): self.mock_request_patch( '/stacks/%s/resources/%s' % ( - parse.quote(stack_id, ''), + parse.quote(stack_id), parse.quote(encodeutils.safe_encode( - resource_name), '') + resource_name)) ), '', req_headers=False, diff --git a/heatclient/v1/events.py b/heatclient/v1/events.py index 29517122..fd5a571c 100644 --- a/heatclient/v1/events.py +++ b/heatclient/v1/events.py @@ -63,8 +63,8 @@ def list(self, stack_id, resource_name=None, **kwargs): else: stack_id = self._resolve_stack_id(stack_id) url = '/stacks/%s/resources/%s/events' % ( - parse.quote(stack_id, ''), - parse.quote(encodeutils.safe_encode(resource_name), '')) + parse.quote(stack_id), + parse.quote(encodeutils.safe_encode(resource_name))) if params: # convert to a sorted dict for python3 predictible order params = collections.OrderedDict(sorted(params.items())) @@ -81,8 +81,8 @@ def get(self, stack_id, resource_name, event_id): """ stack_id = self._resolve_stack_id(stack_id) url_str = '/stacks/%s/resources/%s/events/%s' % ( - parse.quote(stack_id, ''), - parse.quote(encodeutils.safe_encode(resource_name), ''), + parse.quote(stack_id), + parse.quote(encodeutils.safe_encode(resource_name)), parse.quote(event_id, '')) resp = self.client.get(url_str) body = utils.get_response_body(resp) diff --git a/heatclient/v1/resource_types.py b/heatclient/v1/resource_types.py index 86d09b8c..b2265413 100644 --- a/heatclient/v1/resource_types.py +++ b/heatclient/v1/resource_types.py @@ -68,7 +68,7 @@ def get(self, resource_type, with_description=False): """ url_str = '/%s/%s' % ( self.KEY, - parse.quote(encodeutils.safe_encode(resource_type), '')) + parse.quote(encodeutils.safe_encode(resource_type))) resp = self.client.get(url_str, params={'with_description': with_description}) body = utils.get_response_body(resp) @@ -77,7 +77,7 @@ def get(self, resource_type, with_description=False): def generate_template(self, resource_type, template_type='cfn'): url_str = '/%s/%s/template' % ( self.KEY, - parse.quote(encodeutils.safe_encode(resource_type), '')) + parse.quote(encodeutils.safe_encode(resource_type))) if template_type: url_str += '?%s' % parse.urlencode( {'template_type': template_type}, True) diff --git a/heatclient/v1/resources.py b/heatclient/v1/resources.py index ca229ef2..85b743c3 100644 --- a/heatclient/v1/resources.py +++ b/heatclient/v1/resources.py @@ -79,8 +79,8 @@ def get(self, stack_id, resource_name, with_attr=None): """ stack_id = self._resolve_stack_id(stack_id) url_str = '/stacks/%s/resources/%s' % ( - parse.quote(stack_id, ''), - parse.quote(encodeutils.safe_encode(resource_name), '')) + parse.quote(stack_id), + parse.quote(encodeutils.safe_encode(resource_name))) if with_attr: params = {'with_attr': with_attr} url_str += '?%s' % parse.urlencode(params, True) @@ -97,8 +97,8 @@ def metadata(self, stack_id, resource_name): """ stack_id = self._resolve_stack_id(stack_id) url_str = '/stacks/%s/resources/%s/metadata' % ( - parse.quote(stack_id, ''), - parse.quote(encodeutils.safe_encode(resource_name), '')) + parse.quote(stack_id), + parse.quote(encodeutils.safe_encode(resource_name))) resp = self.client.get(url_str) body = utils.get_response_body(resp) return body.get('metadata') @@ -111,8 +111,8 @@ def signal(self, stack_id, resource_name, data=None): """ stack_id = self._resolve_stack_id(stack_id) url_str = '/stacks/%s/resources/%s/signal' % ( - parse.quote(stack_id, ''), - parse.quote(encodeutils.safe_encode(resource_name), '')) + parse.quote(stack_id), + parse.quote(encodeutils.safe_encode(resource_name))) resp = self.client.post(url_str, data=data) body = utils.get_response_body(resp) return body @@ -128,8 +128,8 @@ def mark_unhealthy(self, stack_id, resource_name, """ stack_id = self._resolve_stack_id(stack_id) url_str = '/stacks/%s/resources/%s' % ( - parse.quote(stack_id, ''), - parse.quote(encodeutils.safe_encode(resource_name), '')) + parse.quote(stack_id), + parse.quote(encodeutils.safe_encode(resource_name))) resp = self.client.patch( url_str, data={"mark_unhealthy": mark_unhealthy, @@ -142,7 +142,7 @@ def generate_template(self, resource_name): """Deprecated in favor of generate_template in ResourceTypeManager.""" url_str = '/resource_types/%s/template' % ( - parse.quote(encodeutils.safe_encode(resource_name), '')) + parse.quote(encodeutils.safe_encode(resource_name))) resp = self.client.get(url_str) body = utils.get_response_body(resp) return body diff --git a/heatclient/v1/software_deployments.py b/heatclient/v1/software_deployments.py index ae3ae30f..966838d5 100644 --- a/heatclient/v1/software_deployments.py +++ b/heatclient/v1/software_deployments.py @@ -44,7 +44,7 @@ def metadata(self, server_id): :rtype: list of :class:`SoftwareDeployment` """ url = '/software_deployments/metadata/%s' % parse.quote( - server_id, '') + server_id) resp = self.client.get(url) body = utils.get_response_body(resp) return body.get('metadata') diff --git a/heatclient/v1/template_versions.py b/heatclient/v1/template_versions.py index f6adefb4..2dab4ad5 100644 --- a/heatclient/v1/template_versions.py +++ b/heatclient/v1/template_versions.py @@ -41,7 +41,7 @@ def get(self, template_version, **kwargs): :param template_version: template version to get the functions for """ url_str = '/template_versions/%s/functions' % ( - parse.quote(encodeutils.safe_encode(template_version), '')) + parse.quote(encodeutils.safe_encode(template_version))) params = {} if 'with_condition_func' in kwargs: From 6072003f1794aed33d270975a48b66d7496711ba Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 17 May 2017 03:57:40 +0000 Subject: [PATCH 036/246] Updated from global requirements Change-Id: If4c2ffc9df345c1d73928aa3530c4b418fdbd1c6 --- requirements.txt | 6 +++--- test-requirements.txt | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/requirements.txt b/requirements.txt index e766c158..3e8376a0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,14 +4,14 @@ Babel!=2.4.0,>=2.3.4 # BSD pbr!=2.1.0,>=2.0.0 # Apache-2.0 -cliff>=2.3.0 # Apache-2.0 +cliff>=2.6.0 # Apache-2.0 iso8601>=0.1.11 # MIT -osc-lib>=1.2.0 # Apache-2.0 +osc-lib>=1.5.1 # Apache-2.0 PrettyTable<0.8,>=0.7.1 # BSD oslo.i18n>=2.1.0 # Apache-2.0 oslo.serialization>=1.10.0 # Apache-2.0 oslo.utils>=3.20.0 # Apache-2.0 -keystoneauth1>=2.18.0 # Apache-2.0 +keystoneauth1>=2.20.0 # Apache-2.0 python-swiftclient>=3.2.0 # Apache-2.0 PyYAML>=3.10.0 # MIT requests!=2.12.2,!=2.13.0,>=2.10.0 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index f83f167a..7e65c0d4 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -4,15 +4,15 @@ # Hacking already pins down pep8, pyflakes and flake8 hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0 -coverage>=4.0 # Apache-2.0 +coverage!=4.4,>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD requests-mock>=1.1 # Apache-2.0 mock>=2.0 # BSD mox3!=0.19.0,>=0.7.0 # Apache-2.0 oslosphinx>=4.7.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 -python-openstackclient>=3.3.0 # Apache-2.0 -sphinx>=1.5.1 # BSD +python-openstackclient!=3.10.0,>=3.3.0 # Apache-2.0 +sphinx!=1.6.1,>=1.5.1 # BSD tempest>=14.0.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testscenarios>=0.4 # Apache-2.0/BSD From 7998d7bb3fdce093df2c88e5b82b1bfa9db5560b Mon Sep 17 00:00:00 2001 From: ricolin Date: Tue, 15 Nov 2016 08:39:07 +0800 Subject: [PATCH 037/246] =?UTF-8?q?Add=20optional=20arguments=20'-y'=20in?= =?UTF-8?q?=20CLI=EF=BC=9Asnapshot-delete?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There is no judgement before use the cli: heat snapshot-delete. So I add it. Partial-Bug: #1642490 Depends-On: If7b515dff64a18f56046b890279c2c59b0ab9dc7 Change-Id: I6e82630816e54aa2d98c3653ab43b865f445e881 --- heatclient/tests/unit/test_shell.py | 64 ++++++++++++++++++++++++++++- heatclient/v1/shell.py | 23 ++++++++++- 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/heatclient/tests/unit/test_shell.py b/heatclient/tests/unit/test_shell.py index abb58456..8ac91d3b 100644 --- a/heatclient/tests/unit/test_shell.py +++ b/heatclient/tests/unit/test_shell.py @@ -32,6 +32,7 @@ import testtools import yaml +from heatclient._i18n import _ from heatclient.common import http from heatclient.common import utils from heatclient import exc @@ -2225,6 +2226,65 @@ def test_snapshot_show(self): 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=six.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) + + self.m.ReplayAll() + + 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) + self.m.ReplayAll() + + 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=six.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) + + self.m.ReplayAll() + # -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() @@ -2236,7 +2296,9 @@ def test_snapshot_delete(self): self.m.ReplayAll() resp = self.shell('snapshot-delete teststack/1 2') - self.assertEqual("", resp) + 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() diff --git a/heatclient/v1/shell.py b/heatclient/v1/shell.py index 70ffc9f7..97484caf 100644 --- a/heatclient/v1/shell.py +++ b/heatclient/v1/shell.py @@ -1634,13 +1634,34 @@ def do_snapshot_show(hc, args): help=_('Name or ID of the stack containing the snapshot.')) @utils.arg('snapshot', metavar='', help=_('The ID of the snapshot to delete.')) +@utils.arg('-y', '--yes', default=False, action="store_true", + help=_('Skip yes/no prompt (assume yes).')) def do_snapshot_delete(hc, args): '''Delete a snapshot of a stack.''' show_deprecated('heat snapshot-delete', 'openstack stack snapshot delete') - + msg = "User did not confirm snapshot delete %sso taking no action." + try: + if not args.yes and sys.stdin.isatty(): + sys.stdout.write( + _('Are you sure you want to delete the snapshot of this ' + 'stack [Y/N]?')) + prompt_response = sys.stdin.readline().lower() + if not prompt_response.startswith('y'): + logger.info(msg, '') + return + except KeyboardInterrupt: # ctrl-c + logger.info(msg, '(ctrl-c) ') + return + except EOFError: # ctrl-d + logger.info(msg, '(ctrl-d) ') + return fields = {'stack_id': args.id, 'snapshot_id': args.snapshot} try: hc.stacks.snapshot_delete(**fields) + success_msg = _("Request to delete the snapshot %(snapshot_id)s of " + "the stack %(stack_id)s has been accepted.") + print(success_msg % {'stack_id': args.id, + 'snapshot_id': args.snapshot}) except exc.HTTPNotFound: raise exc.CommandError(_('Stack or snapshot not found')) From d98021b209d9eb514bc65f0fc5aa7626e65891dc Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 23 May 2017 11:58:59 +0000 Subject: [PATCH 038/246] Updated from global requirements Change-Id: Ib5e42136f62792a7bf11444b8447b2bca874631c --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3e8376a0..f986d3cb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ cliff>=2.6.0 # Apache-2.0 iso8601>=0.1.11 # MIT osc-lib>=1.5.1 # Apache-2.0 PrettyTable<0.8,>=0.7.1 # BSD -oslo.i18n>=2.1.0 # Apache-2.0 +oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0 oslo.serialization>=1.10.0 # Apache-2.0 oslo.utils>=3.20.0 # Apache-2.0 keystoneauth1>=2.20.0 # Apache-2.0 From ab2f7f680af49bb534dc9c96467fe84de4c196d1 Mon Sep 17 00:00:00 2001 From: KATO Tomoyuki Date: Fri, 14 Oct 2016 13:58:59 +0900 Subject: [PATCH 039/246] Add plug-in summary for osc doc Stevedore Sphinx extension handles this comment. http://docs.openstack.org/developer/python-openstackclient/plugin-commands.html Change-Id: I4958ec1dfda96f54b75963a04b5a4fc9eec09d84 --- heatclient/osc/plugin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/heatclient/osc/plugin.py b/heatclient/osc/plugin.py index ea1b69e4..8bc3f15f 100644 --- a/heatclient/osc/plugin.py +++ b/heatclient/osc/plugin.py @@ -11,6 +11,8 @@ # under the License. # +"""OpenStackClient plugin for Orchestration service.""" + import logging from osc_lib import utils From e3ba7960c2a48f35f4474277a13b35e9f2e7fd92 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sat, 10 Jun 2017 21:47:55 +0000 Subject: [PATCH 040/246] Updated from global requirements Change-Id: Icc3f70f236f1c1f4c63cae3683480bd5b86ea23c --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index f986d3cb..6ae87994 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,8 +11,8 @@ PrettyTable<0.8,>=0.7.1 # BSD oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0 oslo.serialization>=1.10.0 # Apache-2.0 oslo.utils>=3.20.0 # Apache-2.0 -keystoneauth1>=2.20.0 # Apache-2.0 +keystoneauth1>=2.21.0 # Apache-2.0 python-swiftclient>=3.2.0 # Apache-2.0 PyYAML>=3.10.0 # MIT -requests!=2.12.2,!=2.13.0,>=2.10.0 # Apache-2.0 +requests>=2.14.2 # Apache-2.0 six>=1.9.0 # MIT From a0dfe0703b5966bfc9fb9f4c88564e4f527430fb Mon Sep 17 00:00:00 2001 From: blue55 Date: Thu, 22 Jun 2017 11:18:45 +0800 Subject: [PATCH 041/246] Enable some off-by-default checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some of the available checks are disabled by default, like: [H106] Don’t put vim configuration in source files [H203] Use assertIs(Not)None to check for None Change-Id: I815f189258356cb911d1e232b6f53c864e53c602 --- heatclient/tests/unit/test_utils.py | 3 +-- tox.ini | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/heatclient/tests/unit/test_utils.py b/heatclient/tests/unit/test_utils.py index 34918162..0cb1462d 100644 --- a/heatclient/tests/unit/test_utils.py +++ b/heatclient/tests/unit/test_utils.py @@ -415,8 +415,7 @@ def test_get_template_url(self): tmpl_url) self.assertEqual(utils.get_template_url(None, tmpl_url), tmpl_url) - self.assertEqual(utils.get_template_url(None, None), - None) + self.assertIsNone(utils.get_template_url(None, None)) def test_base_url_for_url(self): self.assertEqual( diff --git a/tox.ini b/tox.ini index 8b6026ba..4ac50ea7 100644 --- a/tox.ini +++ b/tox.ini @@ -49,6 +49,7 @@ commands= [flake8] show-source = True +enable-extensions = H203,H106 exclude=.venv,.git,.tox,dist,*lib/python*,*egg,build max-complexity=20 From 2663e8d75ca1c990f8b34710520baafb7c0df0d1 Mon Sep 17 00:00:00 2001 From: Van Hung Pham Date: Wed, 28 Jun 2017 14:25:36 +0700 Subject: [PATCH 042/246] Switch from oslosphinx to openstackdocstheme As part of the docs migration work[0] for Pike we need to switch to use the openstackdocstheme. [0]https://review.openstack.org/#/c/472275/ Change-Id: I730996eb74d16ede3725494b561dc15493741c75 --- doc/source/conf.py | 22 ++++++++-------------- test-requirements.txt | 4 ++-- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 8bb3fe46..e391a58e 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -24,8 +24,6 @@ # serve to show the default. import os -import subprocess -import warnings # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -38,7 +36,7 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'oslosphinx'] +extensions = ['sphinx.ext.autodoc', 'openstackdocstheme'] # Add any paths that contain templates here, relative to this directory. if os.getenv('HUDSON_PUBLISH_DOCS'): @@ -112,7 +110,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme_path = ['.'] -# html_theme = '_theme' +html_theme = 'openstackdocs' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -147,15 +145,7 @@ # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -# html_last_updated_fmt = '%b %d, %Y' -git_cmd = ["git", "log", "--pretty=format:'%ad, commit %h'", "--date=local", - "-n1"] -try: - html_last_updated_fmt = subprocess.Popen( - git_cmd, stdout=subprocess.PIPE).communicate()[0] -except Exception: - warnings.warn('Cannot get last updated time from git repository. ' - 'Not setting "html_last_updated_fmt".') +html_last_updated_fmt = '%Y-%m-%d %H:%M' # If true, SmartyPants will be used to convert quotes and dashes to @@ -198,7 +188,6 @@ # Output file base name for HTML help builder. htmlhelp_basename = 'python-heatclientdoc' - # -- Options for LaTeX output ------------------------------------------------- latex_elements = { @@ -273,3 +262,8 @@ # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' + +# -- Options for openstackdocstheme ------------------------------------------- +repository_name = 'openstack/python-heatclient' +bug_project = 'python-heatclient' +bug_tag = '' diff --git a/test-requirements.txt b/test-requirements.txt index 7e65c0d4..c22449fb 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -9,10 +9,10 @@ fixtures>=3.0.0 # Apache-2.0/BSD requests-mock>=1.1 # Apache-2.0 mock>=2.0 # BSD mox3!=0.19.0,>=0.7.0 # Apache-2.0 -oslosphinx>=4.7.0 # Apache-2.0 +openstackdocstheme>=1.11.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 python-openstackclient!=3.10.0,>=3.3.0 # Apache-2.0 -sphinx!=1.6.1,>=1.5.1 # BSD +sphinx>=1.6.2 # BSD tempest>=14.0.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testscenarios>=0.4 # Apache-2.0/BSD From ad40def943589cadd4c615daf61721be61a0046a Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 13 Jul 2017 14:23:46 +0000 Subject: [PATCH 043/246] Updated from global requirements Change-Id: I0174be41a51756c08f71a10bbf3b0252e964e55e --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index c22449fb..743509f9 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -13,7 +13,7 @@ openstackdocstheme>=1.11.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 python-openstackclient!=3.10.0,>=3.3.0 # Apache-2.0 sphinx>=1.6.2 # BSD -tempest>=14.0.0 # Apache-2.0 +tempest>=16.1.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testscenarios>=0.4 # Apache-2.0/BSD testtools>=1.4.0 # MIT From 4c2774f74698bda5c0a52818f87578006987efa4 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 19 Jul 2017 14:36:17 +0000 Subject: [PATCH 044/246] Updated from global requirements Change-Id: I6913016702874819b1af06976a2d9277fecc3992 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6ae87994..ee2f7646 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ iso8601>=0.1.11 # MIT osc-lib>=1.5.1 # Apache-2.0 PrettyTable<0.8,>=0.7.1 # BSD oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0 -oslo.serialization>=1.10.0 # Apache-2.0 +oslo.serialization!=2.19.1,>=1.10.0 # Apache-2.0 oslo.utils>=3.20.0 # Apache-2.0 keystoneauth1>=2.21.0 # Apache-2.0 python-swiftclient>=3.2.0 # Apache-2.0 From 6462e5183fd45c94d91e168c709959af4e6782ef Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 21 Jul 2017 00:21:08 +0000 Subject: [PATCH 045/246] Updated from global requirements Change-Id: I127b3d340a5feec3ce0511e315aaa130727864ba --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ee2f7646..1977fe00 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ Babel!=2.4.0,>=2.3.4 # BSD pbr!=2.1.0,>=2.0.0 # Apache-2.0 -cliff>=2.6.0 # Apache-2.0 +cliff>=2.8.0 # Apache-2.0 iso8601>=0.1.11 # MIT osc-lib>=1.5.1 # Apache-2.0 PrettyTable<0.8,>=0.7.1 # BSD From 0c408c5f776b42da2e96519b88393b6a2dbcdc0e Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sun, 23 Jul 2017 13:51:44 +0000 Subject: [PATCH 046/246] Updated from global requirements Change-Id: I0c52fd620e32503ddd5ee054f6998c38c01e069d --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 1977fe00..ee88d71c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,12 +6,12 @@ Babel!=2.4.0,>=2.3.4 # BSD pbr!=2.1.0,>=2.0.0 # Apache-2.0 cliff>=2.8.0 # Apache-2.0 iso8601>=0.1.11 # MIT -osc-lib>=1.5.1 # Apache-2.0 +osc-lib>=1.7.0 # Apache-2.0 PrettyTable<0.8,>=0.7.1 # BSD oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0 oslo.serialization!=2.19.1,>=1.10.0 # Apache-2.0 oslo.utils>=3.20.0 # Apache-2.0 -keystoneauth1>=2.21.0 # Apache-2.0 +keystoneauth1>=3.0.1 # Apache-2.0 python-swiftclient>=3.2.0 # Apache-2.0 PyYAML>=3.10.0 # MIT requests>=2.14.2 # Apache-2.0 From 485a679f3e87d5e38a72c4da07f5fb3ecd6597c8 Mon Sep 17 00:00:00 2001 From: rabi Date: Fri, 2 Jun 2017 10:43:25 +0530 Subject: [PATCH 047/246] Decode content before checking Decode before checking for py3 compatibility. Change-Id: Ic62ebc385146ab1406dd64384044d92be8171052 Related-Bug: #1695144 --- heatclient/common/http.py | 4 ++-- heatclient/tests/unit/test_common_http.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/heatclient/common/http.py b/heatclient/common/http.py index f5c50214..4b765dfc 100644 --- a/heatclient/common/http.py +++ b/heatclient/common/http.py @@ -221,10 +221,10 @@ def _http_request(self, url, method, **kwargs): raise exc.CommunicationError(message=message) self.log_http_response(resp) - + txt_content = encodeutils.safe_decode(resp.content, 'utf-8') if not ('X-Auth-Key' in kwargs['headers']) and ( resp.status_code == 401 or - (resp.status_code == 500 and "(HTTP 401)" in resp.content)): + (resp.status_code == 500 and "(HTTP 401)" in txt_content)): raise exc.HTTPUnauthorized(_("Authentication failed: %s") % resp.content) elif 400 <= resp.status_code < 600: diff --git a/heatclient/tests/unit/test_common_http.py b/heatclient/tests/unit/test_common_http.py index 8ce6f18e..79cb5512 100644 --- a/heatclient/tests/unit/test_common_http.py +++ b/heatclient/tests/unit/test_common_http.py @@ -132,7 +132,7 @@ def test_not_include_pass(self, mock_request): fake500 = fakes.FakeHTTPResponse( 500, 'ERROR', {'content-type': 'application/octet-stream'}, - '(HTTP 401)') + b'(HTTP 401)') # no token or credentials mock_request.return_value = fake500 From b45c792f95006987d19b8a97c553573327979700 Mon Sep 17 00:00:00 2001 From: Joe Borg Date: Tue, 25 Jul 2017 15:30:02 +0100 Subject: [PATCH 048/246] Fixing read before prompt bug Due to Python buffering sys.stdin and sys.stdout, the read can be shown before text prompt. Change-Id: I86d9ec62f7791da276585ce74a48162315effa7b Signed-off-by: Joe Borg --- heatclient/osc/v1/stack.py | 6 +++--- heatclient/tests/unit/test_shell.py | 4 +++- heatclient/v1/shell.py | 6 +++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/heatclient/osc/v1/stack.py b/heatclient/osc/v1/stack.py index 4a64221a..7206e71e 100644 --- a/heatclient/osc/v1/stack.py +++ b/heatclient/osc/v1/stack.py @@ -684,9 +684,9 @@ def take_action(self, parsed_args): try: if not parsed_args.yes and sys.stdin.isatty(): - sys.stdout.write( - _("Are you sure you want to delete this stack(s) [y/N]? ")) - prompt_response = sys.stdin.readline().lower() + prompt_response = six.moves.input( + _("Are you sure you want to delete this stack(s) [y/N]? ") + ).lower() if not prompt_response.startswith('y'): self.log.info('User did not confirm stack delete so ' 'taking no action.') diff --git a/heatclient/tests/unit/test_shell.py b/heatclient/tests/unit/test_shell.py index b9ec014f..57bd1ff7 100644 --- a/heatclient/tests/unit/test_shell.py +++ b/heatclient/tests/unit/test_shell.py @@ -2067,6 +2067,7 @@ def test_stack_delete_prompt_with_tty(self, ms): 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) @@ -2078,7 +2079,7 @@ def test_stack_delete_prompt_with_tty(self, ms): self.assertEqual(resp_text, resp) self.m.ReplayAll() - mock_stdin.readline.return_value = 'Y' + 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) @@ -2093,6 +2094,7 @@ def test_stack_delete_prompt_with_tty_y(self, ms): 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') diff --git a/heatclient/v1/shell.py b/heatclient/v1/shell.py index 97484caf..6f6ff085 100644 --- a/heatclient/v1/shell.py +++ b/heatclient/v1/shell.py @@ -302,9 +302,9 @@ def do_stack_delete(hc, args): try: if not args.yes and sys.stdin.isatty(): - sys.stdout.write( - _("Are you sure you want to delete this stack(s) [y/N]? ")) - prompt_response = sys.stdin.readline().lower() + prompt_response = six.moves.input( + _("Are you sure you want to delete this stack(s) [y/N]? ") + ).lower() if not prompt_response.startswith('y'): logger.info( 'User did not confirm stack delete so taking no action.') From 23ae4d96acf88ba06d0739bd47119e9d81b5a803 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 18 Aug 2017 04:51:19 +0000 Subject: [PATCH 049/246] Updated from global requirements Change-Id: I7ff393a7015b2abafbe6b2a5baf1bbf346ef7c35 --- requirements.txt | 4 ++-- test-requirements.txt | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/requirements.txt b/requirements.txt index ee88d71c..a76c2073 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,8 +11,8 @@ PrettyTable<0.8,>=0.7.1 # BSD oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0 oslo.serialization!=2.19.1,>=1.10.0 # Apache-2.0 oslo.utils>=3.20.0 # Apache-2.0 -keystoneauth1>=3.0.1 # Apache-2.0 +keystoneauth1>=3.1.0 # Apache-2.0 python-swiftclient>=3.2.0 # Apache-2.0 -PyYAML>=3.10.0 # MIT +PyYAML>=3.10 # MIT requests>=2.14.2 # Apache-2.0 six>=1.9.0 # MIT diff --git a/test-requirements.txt b/test-requirements.txt index 743509f9..703a183d 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -6,10 +6,10 @@ hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD -requests-mock>=1.1 # Apache-2.0 -mock>=2.0 # BSD +requests-mock>=1.1.0 # Apache-2.0 +mock>=2.0.0 # BSD mox3!=0.19.0,>=0.7.0 # Apache-2.0 -openstackdocstheme>=1.11.0 # Apache-2.0 +openstackdocstheme>=1.16.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 python-openstackclient!=3.10.0,>=3.3.0 # Apache-2.0 sphinx>=1.6.2 # BSD From 20cae9b7fba62334872c28cbc244fe3b00a854c8 Mon Sep 17 00:00:00 2001 From: rajat29 Date: Tue, 22 Aug 2017 12:12:15 +0530 Subject: [PATCH 050/246] Using fixtures instead of deprecated mockpatch module This module mockpatch of oslotest[1] is deprecated since version 1.13 and may be removed in version 2.0. Use fixtures.Mock* classes instead[2] [1]OpenStack Testing Framework and Utilities [2]https://docs.openstack.org/developer/oslotest/api/oslotest.mockpatch.html#module-oslotest.mockpatch Change-Id: I6ab2f326cecb95fa7f9e6cbb01be1b2b498abdb1 --- heatclient/tests/unit/test_shell.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/heatclient/tests/unit/test_shell.py b/heatclient/tests/unit/test_shell.py index 57bd1ff7..175eda13 100644 --- a/heatclient/tests/unit/test_shell.py +++ b/heatclient/tests/unit/test_shell.py @@ -22,7 +22,6 @@ import mox from oslo_serialization import jsonutils from oslo_utils import encodeutils -from oslotest import mockpatch import requests from requests_mock.contrib import fixture as rm_fixture import six @@ -124,7 +123,7 @@ def register_keystone_auth_fixture(self): # 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(mockpatch.Patch(target, **kwargs)) + mockfixture = self.useFixture(fixtures.MockPatch(target, **kwargs)) return mockfixture.mock def stack_list_resp_dict(self, show_nested=False, include_project=False): From 42ccce303950249ecb95f6021d9f2d8845d10feb Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 23 Aug 2017 09:36:36 +0000 Subject: [PATCH 051/246] Imported Translations from Zanata For more information about this automatic import see: https://docs.openstack.org/i18n/latest/reviewing-translation-import.html Change-Id: I8109cffba95bedacc33d24cf8c10ddaf4eb85488 --- .../zh_CN/LC_MESSAGES/heatclient-log-error.po | 25 ------------------ .../zh_CN/LC_MESSAGES/heatclient-log-info.po | 24 ----------------- .../LC_MESSAGES/heatclient-log-warning.po | 26 ------------------- 3 files changed, 75 deletions(-) delete mode 100644 heatclient/locale/zh_CN/LC_MESSAGES/heatclient-log-error.po delete mode 100644 heatclient/locale/zh_CN/LC_MESSAGES/heatclient-log-info.po delete mode 100644 heatclient/locale/zh_CN/LC_MESSAGES/heatclient-log-warning.po diff --git a/heatclient/locale/zh_CN/LC_MESSAGES/heatclient-log-error.po b/heatclient/locale/zh_CN/LC_MESSAGES/heatclient-log-error.po deleted file mode 100644 index 8ab8300b..00000000 --- a/heatclient/locale/zh_CN/LC_MESSAGES/heatclient-log-error.po +++ /dev/null @@ -1,25 +0,0 @@ -# zzxwill , 2016. #zanata -msgid "" -msgstr "" -"Project-Id-Version: python-heatclient 1.2.1.dev53\n" -"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2016-06-27 08:52+0000\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2016-06-27 11:57+0000\n" -"Last-Translator: zzxwill \n" -"Language-Team: Chinese (China)\n" -"Language: zh-CN\n" -"X-Generator: Zanata 3.7.3\n" -"Plural-Forms: nplurals=1; plural=0\n" - -msgid "Could not decode response body as JSON" -msgstr "无法将响应主题编码为JSON" - -#, python-format -msgid "Stack %(stack)s or resource %(resource)snot found for hook %(hook_type)" -msgstr "" -"Postpone to translate this message as we Need to add a space between " -"'%(resource)s' and 'not'. Submitted a fix for the original string. (https://" -"review.openstack.org/334428)" diff --git a/heatclient/locale/zh_CN/LC_MESSAGES/heatclient-log-info.po b/heatclient/locale/zh_CN/LC_MESSAGES/heatclient-log-info.po deleted file mode 100644 index 188e2c83..00000000 --- a/heatclient/locale/zh_CN/LC_MESSAGES/heatclient-log-info.po +++ /dev/null @@ -1,24 +0,0 @@ -# zzxwill , 2016. #zanata -msgid "" -msgstr "" -"Project-Id-Version: python-heatclient 1.2.1.dev53\n" -"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2016-06-27 08:52+0000\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2016-06-27 11:59+0000\n" -"Last-Translator: zzxwill \n" -"Language-Team: Chinese (China)\n" -"Language: zh-CN\n" -"X-Generator: Zanata 3.7.3\n" -"Plural-Forms: nplurals=1; plural=0\n" - -msgid "User did not confirm stack delete (ctrl-c) so taking no action." -msgstr "因为用户没有确认栈删除操作(ctrl-c),所以不执行任何动作。" - -msgid "User did not confirm stack delete (ctrl-d) so taking no action." -msgstr "因为用户没有确认栈删除操作(ctrl-d),所以不执行任何动作。" - -msgid "User did not confirm stack delete so taking no action." -msgstr "因为用户没有确认栈删除操作,所以不执行任何动作。" diff --git a/heatclient/locale/zh_CN/LC_MESSAGES/heatclient-log-warning.po b/heatclient/locale/zh_CN/LC_MESSAGES/heatclient-log-warning.po deleted file mode 100644 index 6a18c250..00000000 --- a/heatclient/locale/zh_CN/LC_MESSAGES/heatclient-log-warning.po +++ /dev/null @@ -1,26 +0,0 @@ -# zzxwill , 2016. #zanata -msgid "" -msgstr "" -"Project-Id-Version: python-heatclient 1.2.1.dev53\n" -"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2016-06-27 08:52+0000\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2016-06-27 12:01+0000\n" -"Last-Translator: zzxwill \n" -"Language-Team: Chinese (China)\n" -"Language: zh-CN\n" -"X-Generator: Zanata 3.7.3\n" -"Plural-Forms: nplurals=1; plural=0\n" - -#, python-format -msgid "\"%(old)s\" is deprecated, please use \"%(new)s\" instead" -msgstr "\"%(old)s\"已弃用,请使用\"%(new)s\"。" - -#, python-format -msgid "%(arg1)s is deprecated, please use %(arg2)s instead" -msgstr "\"%(arg1)s\"已弃用,请使用\"%(arg2)s\"。" - -msgid "System ca file could not be found." -msgstr "找不到系统CA文件。" From a669d59cc7252b5dcafd73717a348722babe62cc Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 1 Sep 2017 12:46:18 +0000 Subject: [PATCH 052/246] Updated from global requirements Change-Id: I746570c03c4b1f0be907411a69aa20cdb51dd282 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a76c2073..85a54bfe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ PrettyTable<0.8,>=0.7.1 # BSD oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0 oslo.serialization!=2.19.1,>=1.10.0 # Apache-2.0 oslo.utils>=3.20.0 # Apache-2.0 -keystoneauth1>=3.1.0 # Apache-2.0 +keystoneauth1>=3.2.0 # Apache-2.0 python-swiftclient>=3.2.0 # Apache-2.0 PyYAML>=3.10 # MIT requests>=2.14.2 # Apache-2.0 From 551e6bec3dc5c87fdd16e0d4741c8940f00c1971 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Sat, 2 Sep 2017 09:00:08 -0500 Subject: [PATCH 053/246] Remove vestigate HUDSON_PUBLISH_DOCS reference This isn't actually used and is a leftover from a thing we did seven years ago that survives today through copy-pasta. Change-Id: Ic77d867ee1afaf4586a9fcbab5c36166e38604b6 --- doc/source/conf.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index e391a58e..e2083596 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -39,10 +39,7 @@ extensions = ['sphinx.ext.autodoc', 'openstackdocstheme'] # Add any paths that contain templates here, relative to this directory. -if os.getenv('HUDSON_PUBLISH_DOCS'): - templates_path = ['_ga', '_templates'] -else: - templates_path = ['_templates'] +templates_path = [] # The suffix of source filenames. source_suffix = '.rst' From 4661e4d696ec1218de7abc924f21c65e8ad15191 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 21 Sep 2017 02:15:57 +0000 Subject: [PATCH 054/246] Updated from global requirements Change-Id: I60c22a8f75499e95561de7427c91759943b829d5 --- requirements.txt | 6 +++--- test-requirements.txt | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/requirements.txt b/requirements.txt index 85a54bfe..734ec746 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,9 +8,9 @@ cliff>=2.8.0 # Apache-2.0 iso8601>=0.1.11 # MIT osc-lib>=1.7.0 # Apache-2.0 PrettyTable<0.8,>=0.7.1 # BSD -oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0 -oslo.serialization!=2.19.1,>=1.10.0 # Apache-2.0 -oslo.utils>=3.20.0 # Apache-2.0 +oslo.i18n>=3.15.3 # Apache-2.0 +oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0 +oslo.utils>=3.28.0 # Apache-2.0 keystoneauth1>=3.2.0 # Apache-2.0 python-swiftclient>=3.2.0 # Apache-2.0 PyYAML>=3.10 # MIT diff --git a/test-requirements.txt b/test-requirements.txt index 703a183d..a3f21c7c 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -8,10 +8,10 @@ coverage!=4.4,>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD requests-mock>=1.1.0 # Apache-2.0 mock>=2.0.0 # BSD -mox3!=0.19.0,>=0.7.0 # Apache-2.0 -openstackdocstheme>=1.16.0 # Apache-2.0 +mox3>=0.20.0 # Apache-2.0 +openstackdocstheme>=1.17.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 -python-openstackclient!=3.10.0,>=3.3.0 # Apache-2.0 +python-openstackclient>=3.12.0 # Apache-2.0 sphinx>=1.6.2 # BSD tempest>=16.1.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD From d7dd30a63104143981f3662a647713670b2b566a Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Mon, 7 Aug 2017 16:22:17 +0100 Subject: [PATCH 055/246] Use Sphinx 1.5 warning-is-error With pbr 2.0 and Sphinx 1.5, the setting for treat sphinx warnings as errors is setting warning-is-error in build_sphinx section. Enable this Change-Id: I3712c681cedae30c189d975f6b319e278c1bd419 --- setup.cfg | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index f63d35b7..4c22966b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -103,9 +103,10 @@ setup-hooks = [build_sphinx] builders = html,man +all-files = 1 +warning-is-error = 1 source-dir = doc/source build-dir = doc/build -all_files = 1 [upload_sphinx] upload-dir = doc/build/html From 2a39275ec3cbd44cd12b46b90db5d249a762a164 Mon Sep 17 00:00:00 2001 From: Roberto Polli Date: Fri, 15 Sep 2017 15:01:00 +0200 Subject: [PATCH 056/246] Support --show-nested in openstack stack update --dry-run Added --show-nested support in `openstack stack update`. The work is based on `heat stack-update` implementation. Closes-Bug: #1720085 Change-Id: I50c8f5ac9ba4cb7e478af03ff4c1f099270ae226 --- heatclient/osc/v1/stack.py | 7 +++++++ heatclient/tests/unit/osc/v1/test_stack.py | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/heatclient/osc/v1/stack.py b/heatclient/osc/v1/stack.py index 7206e71e..d0b97e09 100644 --- a/heatclient/osc/v1/stack.py +++ b/heatclient/osc/v1/stack.py @@ -232,6 +232,10 @@ def get_parser(self, prog_name): help=_('Do not actually perform the stack update, but show what ' 'would be changed') ) + parser.add_argument( + '--show-nested', default=False, action="store_true", + help=_('Show nested stacks when performing --dry-run') + ) parser.add_argument( '--parameter', metavar='', help=_('Parameter values used to create the stack. ' @@ -335,6 +339,9 @@ def take_action(self, parsed_args): fields['disable_rollback'] = rollback == 'disabled' if parsed_args.dry_run: + if parsed_args.show_nested: + fields['show_nested'] = parsed_args.show_nested + changes = client.stacks.preview_update(**fields) fields = ['state', 'resource_name', 'resource_type', diff --git a/heatclient/tests/unit/osc/v1/test_stack.py b/heatclient/tests/unit/osc/v1/test_stack.py index 54fe9be5..159056a7 100644 --- a/heatclient/tests/unit/osc/v1/test_stack.py +++ b/heatclient/tests/unit/osc/v1/test_stack.py @@ -335,6 +335,17 @@ def test_stack_update_dry_run(self): self.stack_client.preview_update.assert_called_with(**self.defaults) self.stack_client.update.assert_not_called() + def test_stack_update_dry_run_show_nested(self): + arglist = ['my_stack', '-t', self.template_path, '--dry-run', + '--show-nested'] + parsed_args = self.check_parser(self.cmd, arglist, []) + + self.cmd.take_action(parsed_args) + + self.stack_client.preview_update.assert_called_with( + show_nested=True, **self.defaults) + self.stack_client.update.assert_not_called() + @mock.patch('heatclient.common.event_utils.poll_for_events', return_value=('UPDATE_COMPLETE', 'Stack my_stack UPDATE_COMPLETE')) From 2d5bae0755a8578012528dbf244684ed8e56b90f Mon Sep 17 00:00:00 2001 From: ricolin Date: Fri, 29 Sep 2017 17:54:47 +0800 Subject: [PATCH 057/246] Mark python-heatclient as stable Mark python-heatclient as stable in classifier. See more detail in https://packaging.python.org/tutorials/ distributing-packages/#classifiers Change-Id: I751a6f31943ee33e9d3af08e3380848bf1410f1a --- setup.cfg | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.cfg b/setup.cfg index f63d35b7..e873d705 100644 --- a/setup.cfg +++ b/setup.cfg @@ -8,6 +8,8 @@ author = OpenStack author-email = openstack-dev@lists.openstack.org home-page = http://docs.openstack.org/developer/python-heatclient/ classifier = + Development Status :: 5 - Production/Stable + Environment :: Console Environment :: OpenStack Intended Audience :: Information Technology Intended Audience :: System Administrators From 4b397ae7eb82915093d02e09a796c28b608b095f Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 5 Oct 2017 06:12:45 +0000 Subject: [PATCH 058/246] Updated from global requirements Change-Id: I7842746a72bdc8065b58fc731929b79c6b3bc837 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 734ec746..785f6b22 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ Babel!=2.4.0,>=2.3.4 # BSD pbr!=2.1.0,>=2.0.0 # Apache-2.0 -cliff>=2.8.0 # Apache-2.0 +cliff!=2.9.0,>=2.8.0 # Apache-2.0 iso8601>=0.1.11 # MIT osc-lib>=1.7.0 # Apache-2.0 PrettyTable<0.8,>=0.7.1 # BSD From 2a8ce0d0656f47331a164163f9e3b302de4d3c88 Mon Sep 17 00:00:00 2001 From: Thomas Herve Date: Tue, 3 Oct 2017 16:10:29 +0200 Subject: [PATCH 059/246] Don't override sections in deep_update When you comment all the elements in a YAML mapping, you end up with None instead of an empty mapping, which can have bad side effect when handling multiple environemnents. Let's handle that by ignoring the latest None value. Change-Id: I77ffabeb8d4cd2886ef4f41351e42ebe487b5d4b --- heatclient/common/template_utils.py | 3 ++ heatclient/tests/unit/test_template_utils.py | 45 ++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/heatclient/common/template_utils.py b/heatclient/common/template_utils.py index 9e9c85bf..b38710be 100644 --- a/heatclient/common/template_utils.py +++ b/heatclient/common/template_utils.py @@ -198,6 +198,9 @@ def deep_update(old, new): if isinstance(v, collections.Mapping): r = deep_update(old.get(k, {}), v) old[k] = r + elif v is None and isinstance(old.get(k), collections.Mapping): + # Don't override empty data, to work around yaml syntax issue + pass else: old[k] = new[k] return old diff --git a/heatclient/tests/unit/test_template_utils.py b/heatclient/tests/unit/test_template_utils.py index a48e0b36..01cfa13a 100644 --- a/heatclient/tests/unit/test_template_utils.py +++ b/heatclient/tests/unit/test_template_utils.py @@ -448,6 +448,51 @@ def test_process_environment_relative_file_tracker(self, mock_url): ]) + @mock.patch('six.moves.urllib.request.urlopen') + def test_process_multiple_environments_empty_registry(self, mock_url): + # Setup + env_file1 = '/home/my/dir/env1.yaml' + env_file2 = '/home/my/dir/env2.yaml' + + env1 = b''' + resource_registry: + "OS::Thingy1": "file:///home/b/a.yaml" + ''' + env2 = b''' + resource_registry: + ''' + mock_url.side_effect = [six.BytesIO(env1), + six.BytesIO(self.template_a), + six.BytesIO(self.template_a), + six.BytesIO(env2)] + + # Test + env_file_list = [] + files, env = template_utils.process_multiple_environments_and_files( + [env_file1, env_file2], env_list_tracker=env_file_list) + + # Verify + expected_env = { + 'resource_registry': {'OS::Thingy1': 'file:///home/b/a.yaml'}} + self.assertEqual(expected_env, env) + + self.assertEqual(self.template_a.decode('utf-8'), + files['file:///home/b/a.yaml']) + + self.assertEqual(['file:///home/my/dir/env1.yaml', + 'file:///home/my/dir/env2.yaml'], env_file_list) + self.assertIn('file:///home/my/dir/env1.yaml', files) + self.assertIn('file:///home/my/dir/env2.yaml', files) + self.assertEqual(expected_env, + json.loads(files['file:///home/my/dir/env1.yaml'])) + mock_url.assert_has_calls([ + mock.call('file://%s' % env_file1), + mock.call('file:///home/b/a.yaml'), + mock.call('file:///home/b/a.yaml'), + mock.call('file://%s' % env_file2), + + ]) + def test_global_files(self): url = 'file:///home/b/a.yaml' env = ''' From 7ed9834e10e87d58605a5fa09ce4670d913551d8 Mon Sep 17 00:00:00 2001 From: Thomas Bechtold Date: Sun, 20 Aug 2017 08:44:19 +0200 Subject: [PATCH 060/246] No longer use oslotest.mockpatch It is deprecated[1] and will be removed soon. [1] I3efb3c64ebce396e756a47d110a74fe8604b6b72 Closes-Bug: #1712017 Change-Id: If6406da4c940686cbda696f760d06d45690912a1 --- heatclient/tests/unit/test_shell.py | 3 +-- test-requirements.txt | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/heatclient/tests/unit/test_shell.py b/heatclient/tests/unit/test_shell.py index 57bd1ff7..175eda13 100644 --- a/heatclient/tests/unit/test_shell.py +++ b/heatclient/tests/unit/test_shell.py @@ -22,7 +22,6 @@ import mox from oslo_serialization import jsonutils from oslo_utils import encodeutils -from oslotest import mockpatch import requests from requests_mock.contrib import fixture as rm_fixture import six @@ -124,7 +123,7 @@ def register_keystone_auth_fixture(self): # 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(mockpatch.Patch(target, **kwargs)) + mockfixture = self.useFixture(fixtures.MockPatch(target, **kwargs)) return mockfixture.mock def stack_list_resp_dict(self, show_nested=False, include_project=False): diff --git a/test-requirements.txt b/test-requirements.txt index a3f21c7c..5c6d8d30 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -10,7 +10,6 @@ requests-mock>=1.1.0 # Apache-2.0 mock>=2.0.0 # BSD mox3>=0.20.0 # Apache-2.0 openstackdocstheme>=1.17.0 # Apache-2.0 -oslotest>=1.10.0 # Apache-2.0 python-openstackclient>=3.12.0 # Apache-2.0 sphinx>=1.6.2 # BSD tempest>=16.1.0 # Apache-2.0 From 046fbf8aa36abd5cbeb68eb63d5f69db31243c2d Mon Sep 17 00:00:00 2001 From: Nam Nguyen Hoai Date: Wed, 18 Oct 2017 13:54:04 +0700 Subject: [PATCH 061/246] Use generic user for both zuul v2 and v3 Zuul v2 uses 'jenkins' as user, but Zuul v3 uses 'zuul'. Using $USER solves it for both cases. Change-Id: If6b023e3be4a4555b656660a8522271668496ead --- heatclient/tests/functional/hooks/post_test_hook.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/heatclient/tests/functional/hooks/post_test_hook.sh b/heatclient/tests/functional/hooks/post_test_hook.sh index 505a7afa..afce9af6 100755 --- a/heatclient/tests/functional/hooks/post_test_hook.sh +++ b/heatclient/tests/functional/hooks/post_test_hook.sh @@ -21,7 +21,7 @@ function generate_testr_results { sudo /usr/os-testr-env/bin/subunit2html $BASE/logs/testrepository.subunit $BASE/logs/testr_results.html sudo gzip -9 $BASE/logs/testrepository.subunit sudo gzip -9 $BASE/logs/testr_results.html - sudo chown jenkins:jenkins $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz + sudo chown $USER:$USER $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz sudo chmod a+r $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz fi } @@ -35,13 +35,13 @@ source openrc admin admin # Go to the heatclient dir cd $HEATCLIENT_DIR -sudo chown -R jenkins:stack $HEATCLIENT_DIR +sudo chown -R $USER:stack $HEATCLIENT_DIR # Run tests echo "Running heatclient functional test suite" set +e # Preserve env for OS_ credentials -sudo -E -H -u jenkins tox -efunctional +sudo -E -H -u $USER tox -efunctional EXIT_CODE=$? set -e From e74181b490b16cdba92282fe5ec59a37682ea82c Mon Sep 17 00:00:00 2001 From: ricolin Date: Wed, 20 Sep 2017 02:33:11 +0800 Subject: [PATCH 062/246] [policy in code] Change policy description Now we also support get policy from policies in code. Depends-On: I610115dc1974b2182ce673bb086a1da15b022de3 Change-Id: I46d03c3303b3116e749effeecefe48949473daeb --- heatclient/v1/shell.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/heatclient/v1/shell.py b/heatclient/v1/shell.py index 6f6ff085..e5bf19c9 100644 --- a/heatclient/v1/shell.py +++ b/heatclient/v1/shell.py @@ -633,7 +633,8 @@ def do_stack_cancel_update(hc, args): 'keys.')) @utils.arg('-g', '--global-tenant', action='store_true', default=False, help=_('Display stacks from all tenants. Operation only authorized ' - 'for users who match the policy in heat\'s policy.json.')) + 'for users who match the policy (default or explicitly ' + 'configured in policy.json) in heat.')) @utils.arg('-o', '--show-owner', action='store_true', default=False, help=_('Display stack owner information. This is automatically ' 'enabled when using %(arg)s.') % {'arg': '--global-tenant'}) From 58e357908ea1617e7fdf758766b1813b42487803 Mon Sep 17 00:00:00 2001 From: ricolin Date: Mon, 1 May 2017 03:09:55 +0800 Subject: [PATCH 063/246] Add `--converge` argument for osc stack update This patch add `--converge` argument for osc stack update. This flag will be used as the trigger for observe on reality. implements bp get-reality-for-resources depends-on: I151b575b714dcc9a5971a1573c126152ecd7ea93 Change-Id: If35e1b9234cab173b3c46601bbd331aceebcbcb3 --- heatclient/osc/v1/stack.py | 8 ++++++++ heatclient/tests/unit/osc/v1/test_stack.py | 10 ++++++++++ ...-converge-in-osc-stack-update-10f256589f628d13.yaml | 8 ++++++++ 3 files changed, 26 insertions(+) create mode 100644 releasenotes/notes/add-converge-in-osc-stack-update-10f256589f628d13.yaml diff --git a/heatclient/osc/v1/stack.py b/heatclient/osc/v1/stack.py index 4a64221a..4490d1ed 100644 --- a/heatclient/osc/v1/stack.py +++ b/heatclient/osc/v1/stack.py @@ -278,6 +278,11 @@ def get_parser(self, prog_name): help=_('Wait until stack goes to UPDATE_COMPLETE or ' 'UPDATE_FAILED') ) + parser.add_argument( + '--converge', + action='store_true', + help=_('Stack update with observe on reality.') + ) return parser @@ -354,6 +359,9 @@ def take_action(self, parsed_args): limit=1) marker = events[0].id if events else None + if parsed_args.converge: + fields['converge'] = True + client.stacks.update(**fields) if parsed_args.wait: diff --git a/heatclient/tests/unit/osc/v1/test_stack.py b/heatclient/tests/unit/osc/v1/test_stack.py index 54fe9be5..2eb3ab9b 100644 --- a/heatclient/tests/unit/osc/v1/test_stack.py +++ b/heatclient/tests/unit/osc/v1/test_stack.py @@ -304,6 +304,16 @@ def test_stack_update_timeout(self): self.stack_client.update.assert_called_with(**kwargs) + def test_stack_update_converge(self): + arglist = ['my_stack', '-t', self.template_path, '--converge'] + kwargs = copy.deepcopy(self.defaults) + kwargs['converge'] = True + parsed_args = self.check_parser(self.cmd, arglist, []) + + self.cmd.take_action(parsed_args) + + self.stack_client.update.assert_called_with(**kwargs) + def test_stack_update_pre_update(self): arglist = ['my_stack', '-t', self.template_path, '--pre-update', 'a'] kwargs = copy.deepcopy(self.defaults) diff --git a/releasenotes/notes/add-converge-in-osc-stack-update-10f256589f628d13.yaml b/releasenotes/notes/add-converge-in-osc-stack-update-10f256589f628d13.yaml new file mode 100644 index 00000000..70f65d37 --- /dev/null +++ b/releasenotes/notes/add-converge-in-osc-stack-update-10f256589f628d13.yaml @@ -0,0 +1,8 @@ +--- +features: + - Adds '--converge' option for stack update with openstackclient. If + specified, existing resource attributes (which may have diverged) are + compared with the resource properties in the template used for update. + + Usage: + # openstack stack update --converge ... \ No newline at end of file From f73c2a4177377b710a02577feea38560b00a24bf Mon Sep 17 00:00:00 2001 From: PriyaDuggirala Date: Thu, 26 Oct 2017 19:54:07 +0530 Subject: [PATCH 064/246] Modify error message encountered during stack update Parameters of an existing stack can be updated with openstack stack update --existing --parameter = Example : openstack stack update stack1 --parameter p1=v1 If the --existing option is skipped, it leads to the below error Need to specify exactly one of --template-file, --template-url or --template-object This error is misleading as the user might think that stack update cannot be performed without specifying a new template. Modify the error message with --existing option. Change-Id: Idce88bde848378e00b1c873245600ba205939668 Closes-Bug: #1723864 --- heatclient/common/template_utils.py | 6 ++++-- heatclient/tests/unit/test_template_utils.py | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/heatclient/common/template_utils.py b/heatclient/common/template_utils.py index b38710be..cdb330a1 100644 --- a/heatclient/common/template_utils.py +++ b/heatclient/common/template_utils.py @@ -74,11 +74,13 @@ def get_template_contents(template_file=None, template_url=None, return {}, None else: raise exc.CommandError(_('Need to specify exactly one of ' - '%(arg1)s, %(arg2)s or %(arg3)s') % + '[%(arg1)s, %(arg2)s or %(arg3)s]' + ' or %(arg4)s') % { 'arg1': '--template-file', 'arg2': '--template-url', - 'arg3': '--template-object'}) + 'arg3': '--template-object', + 'arg4': '--existing'}) if not tpl: raise exc.CommandError(_('Could not fetch template from %s') diff --git a/heatclient/tests/unit/test_template_utils.py b/heatclient/tests/unit/test_template_utils.py index 01cfa13a..e4303c64 100644 --- a/heatclient/tests/unit/test_template_utils.py +++ b/heatclient/tests/unit/test_template_utils.py @@ -616,8 +616,8 @@ def test_get_template_contents_file_none(self): exc.CommandError, template_utils.get_template_contents) self.assertEqual( - ('Need to specify exactly one of --template-file, ' - '--template-url or --template-object'), + ('Need to specify exactly one of [--template-file, ' + '--template-url or --template-object] or --existing'), str(ex)) def test_get_template_contents_file_none_existing(self): From 187fc54249982d2f7213c750d98c6602a08b758b Mon Sep 17 00:00:00 2001 From: rabi Date: Tue, 8 Aug 2017 13:25:20 +0530 Subject: [PATCH 065/246] Add --no-rollback option for stack cancel This adds a new --no-rollback option for stack cancel. Usage: openstack stack cancel --no-rollback Change-Id: I46252823701779e3a3c6867bc470d5532cf9d7a7 Related-Bug: #1709041 --- heatclient/osc/v1/stack.py | 18 ++++++++++++++---- heatclient/tests/unit/osc/v1/test_stack.py | 12 ++++++++++++ heatclient/tests/unit/test_actions.py | 9 +++++++++ heatclient/v1/actions.py | 5 +++++ 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/heatclient/osc/v1/stack.py b/heatclient/osc/v1/stack.py index d0b97e09..e4148d1f 100644 --- a/heatclient/osc/v1/stack.py +++ b/heatclient/osc/v1/stack.py @@ -1220,11 +1220,17 @@ class CancelStack(StackActionBase): log = logging.getLogger(__name__ + '.CancelStack') def get_parser(self, prog_name): - return self._get_parser( + parser = self._get_parser( prog_name, _('Stack(s) to cancel (name or ID)'), - _('Wait for check to complete') + _('Wait for cancel to complete') + ) + parser.add_argument( + '--no-rollback', + action='store_true', + help=_('Cancel without rollback') ) + return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) @@ -1237,7 +1243,10 @@ def take_action(self, parsed_args): 'Updated Time' ] heat_client = self.app.client_manager.orchestration - + if parsed_args.no_rollback: + action = heat_client.actions.cancel_without_rollback + else: + action = heat_client.actions.cancel_update for stack in parsed_args.stack: try: data = heat_client.stacks.get(stack_id=stack) @@ -1246,11 +1255,12 @@ def take_action(self, parsed_args): status = getattr(data, 'stack_status').lower() if status == 'update_in_progress': + data = _stack_action( stack, parsed_args, heat_client, - heat_client.actions.cancel_update + action ) rows += [utils.get_dict_properties(data.to_dict(), columns)] else: diff --git a/heatclient/tests/unit/osc/v1/test_stack.py b/heatclient/tests/unit/osc/v1/test_stack.py index 159056a7..ebe53290 100644 --- a/heatclient/tests/unit/osc/v1/test_stack.py +++ b/heatclient/tests/unit/osc/v1/test_stack.py @@ -1221,6 +1221,18 @@ def setUp(self): def test_stack_cancel(self): self._test_stack_action(2) + def test_stack_cancel_no_rollback(self): + self.action = self.mock_client.actions.cancel_without_rollback + arglist = ['my_stack', '--no-rollback'] + parsed_args = self.check_parser(self.cmd, arglist, []) + columns, rows = self.cmd.take_action(parsed_args) + self.action.assert_called_once_with('my_stack') + self.mock_client.stacks.get.assert_called_with('my_stack') + self.assertEqual(2, + self.mock_client.stacks.get.call_count) + self.assertEqual(self.columns, columns) + self.assertEqual(1, len(rows)) + def test_stack_cancel_multi(self): self._test_stack_action_multi(4) diff --git a/heatclient/tests/unit/test_actions.py b/heatclient/tests/unit/test_actions.py index 93b6e491..512d5665 100644 --- a/heatclient/tests/unit/test_actions.py +++ b/heatclient/tests/unit/test_actions.py @@ -98,6 +98,15 @@ def test_cancel_update(self): manager = self._base_test(expect_args, expect_kwargs) manager.cancel_update(**fields) + def test_cancel_without_rollback(self): + fields = {'stack_id': 'teststack%2Fabcd1234'} + expect_args = ('POST', + '/stacks/teststack%2Fabcd1234/actions') + expect_kwargs = {'data': {'cancel_without_rollback': None}} + + manager = self._base_test(expect_args, expect_kwargs) + manager.cancel_without_rollback(**fields) + def test_check(self): fields = {'stack_id': 'teststack%2Fabcd1234'} expect_args = ('POST', diff --git a/heatclient/v1/actions.py b/heatclient/v1/actions.py index 0331866b..81857e2e 100644 --- a/heatclient/v1/actions.py +++ b/heatclient/v1/actions.py @@ -48,6 +48,11 @@ def cancel_update(self, stack_id): body = {'cancel_update': None} self.client.post('/stacks/%s/actions' % stack_id, data=body) + def cancel_without_rollback(self, stack_id): + """Cancel running update of a stack.""" + body = {'cancel_without_rollback': None} + self.client.post('/stacks/%s/actions' % stack_id, data=body) + def check(self, stack_id): """Check a stack.""" body = {'check': None} From f0d3d9f1f9380f632e5fd2a66941eb58f79c797b Mon Sep 17 00:00:00 2001 From: rabi Date: Thu, 24 Aug 2017 12:10:41 +0530 Subject: [PATCH 066/246] Allow cancelling create_in_progress stacks with --no-rollback Change-Id: Ib107c82f341f4d271859ca7681d65f7ce4c5d0b1 Related-Bug: #1709041 --- heatclient/osc/v1/stack.py | 8 +++++--- heatclient/tests/unit/osc/v1/test_stack.py | 19 +++++++++++++++++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/heatclient/osc/v1/stack.py b/heatclient/osc/v1/stack.py index e4148d1f..cb685dd4 100644 --- a/heatclient/osc/v1/stack.py +++ b/heatclient/osc/v1/stack.py @@ -1215,6 +1215,7 @@ class CancelStack(StackActionBase): Supported tasks for cancellation: * update + * create """ log = logging.getLogger(__name__ + '.CancelStack') @@ -1245,17 +1246,18 @@ def take_action(self, parsed_args): heat_client = self.app.client_manager.orchestration if parsed_args.no_rollback: action = heat_client.actions.cancel_without_rollback + allowed_statuses = ['create_in_progress', + 'update_in_progress'] else: action = heat_client.actions.cancel_update + allowed_statuses = ['update_in_progress'] for stack in parsed_args.stack: try: data = heat_client.stacks.get(stack_id=stack) except heat_exc.HTTPNotFound: raise exc.CommandError('Stack not found: %s' % stack) - status = getattr(data, 'stack_status').lower() - if status == 'update_in_progress': - + if status in allowed_statuses: data = _stack_action( stack, parsed_args, diff --git a/heatclient/tests/unit/osc/v1/test_stack.py b/heatclient/tests/unit/osc/v1/test_stack.py index ebe53290..04c9da43 100644 --- a/heatclient/tests/unit/osc/v1/test_stack.py +++ b/heatclient/tests/unit/osc/v1/test_stack.py @@ -1221,18 +1221,21 @@ def setUp(self): def test_stack_cancel(self): self._test_stack_action(2) - def test_stack_cancel_no_rollback(self): + def _test_stack_cancel_no_rollback(self, call_count): self.action = self.mock_client.actions.cancel_without_rollback arglist = ['my_stack', '--no-rollback'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, rows = self.cmd.take_action(parsed_args) self.action.assert_called_once_with('my_stack') self.mock_client.stacks.get.assert_called_with('my_stack') - self.assertEqual(2, + self.assertEqual(call_count, self.mock_client.stacks.get.call_count) self.assertEqual(self.columns, columns) self.assertEqual(1, len(rows)) + def test_stack_cancel_no_rollback(self): + self._test_stack_cancel_no_rollback(2) + def test_stack_cancel_multi(self): self._test_stack_action_multi(4) @@ -1246,6 +1249,7 @@ def test_stack_cancel_exception(self): self._test_stack_action_exception() def test_stack_cancel_unsupported_state(self): + self.stack.stack_status = "CREATE_COMPLETE" self.mock_client.stacks.get.return_value = self.stack error = self.assertRaises(exc.CommandError, self._test_stack_action, @@ -1254,6 +1258,17 @@ def test_stack_cancel_unsupported_state(self): 'not in cancelable state', str(error)) + def test_stack_cancel_create_in_progress(self): + self.stack.stack_status = "CREATE_IN_PROGRESS" + self.mock_client.stacks.get.return_value = self.stack + error = self.assertRaises(exc.CommandError, + self._test_stack_action, + 2) + self.assertEqual('Stack my_stack with status \'create_in_progress\' ' + 'not in cancelable state', + str(error)) + self._test_stack_cancel_no_rollback(3) + class TestStackCheck(_TestStackCheckBase, TestStack): From 9db1e6ef512224e930a3303d08fdcb4b8bfb4cd0 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sun, 12 Nov 2017 21:16:41 +0000 Subject: [PATCH 067/246] Updated from global requirements Change-Id: I54ac7275af9bbd217fcd050937b24a8cba1a2eb7 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 785f6b22..3f66c331 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ osc-lib>=1.7.0 # Apache-2.0 PrettyTable<0.8,>=0.7.1 # BSD oslo.i18n>=3.15.3 # Apache-2.0 oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0 -oslo.utils>=3.28.0 # Apache-2.0 +oslo.utils>=3.31.0 # Apache-2.0 keystoneauth1>=3.2.0 # Apache-2.0 python-swiftclient>=3.2.0 # Apache-2.0 PyYAML>=3.10 # MIT From 0521faba6e50b04762fe15cb21cb0d1dd75fb8cd Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 16 Nov 2017 11:23:47 +0000 Subject: [PATCH 068/246] Updated from global requirements Change-Id: Iab36aaedd85c08c579fdf48eca93337bea32842d --- requirements.txt | 2 +- test-requirements.txt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 3f66c331..30cac66d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,4 +15,4 @@ keystoneauth1>=3.2.0 # Apache-2.0 python-swiftclient>=3.2.0 # Apache-2.0 PyYAML>=3.10 # MIT requests>=2.14.2 # Apache-2.0 -six>=1.9.0 # MIT +six>=1.10.0 # MIT diff --git a/test-requirements.txt b/test-requirements.txt index 5c6d8d30..bf5ca5f2 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -12,7 +12,7 @@ mox3>=0.20.0 # Apache-2.0 openstackdocstheme>=1.17.0 # Apache-2.0 python-openstackclient>=3.12.0 # Apache-2.0 sphinx>=1.6.2 # BSD -tempest>=16.1.0 # Apache-2.0 +tempest>=17.1.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testscenarios>=0.4 # Apache-2.0/BSD -testtools>=1.4.0 # MIT +testtools>=2.2.0 # MIT From 05e0dd2422fe3c9db66ddac8243967e9de861d74 Mon Sep 17 00:00:00 2001 From: Steven Hardy Date: Tue, 21 Nov 2017 18:20:37 +0000 Subject: [PATCH 069/246] Don't preformat stack output show list/map values This breaks the built-in --format json and --format yaml options Change-Id: I01d46ace380756ae57c8c7c9f65c5a686a06818f Closes-Bug: #1733664 --- heatclient/osc/v1/stack.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/heatclient/osc/v1/stack.py b/heatclient/osc/v1/stack.py index cb685dd4..89540d22 100644 --- a/heatclient/osc/v1/stack.py +++ b/heatclient/osc/v1/stack.py @@ -1001,11 +1001,6 @@ def take_action(self, parsed_args): msg = _('Output error: %s') % output['output_error'] raise exc.CommandError(msg) - if (isinstance(output['output_value'], list) or - isinstance(output['output_value'], dict)): - output['output_value'] = heat_utils.json_formatter( - output['output_value']) - return self.dict2columns(output) From bc9b1d926781a56a3fe94beb73f8a4210c3a1746 Mon Sep 17 00:00:00 2001 From: ricolin Date: Thu, 19 Oct 2017 23:27:41 +0800 Subject: [PATCH 070/246] Migrate functional job to Zuul v3 Migrate functional job to zuul v3. Migrating guideline: https://docs.openstack.org/infra/manual/zuulv3.html Needed-By: I611d21791d3dfd877e37fa02b5893e18b1becd76 Change-Id: If8c88568dbc6be1bb04e75a6b8b63e44f4cf7d40 --- .zuul.yaml | 21 +++++++ playbooks/devstack/functional-post.yaml | 80 +++++++++++++++++++++++++ playbooks/devstack/functional-run.yaml | 56 +++++++++++++++++ 3 files changed, 157 insertions(+) create mode 100644 .zuul.yaml create mode 100644 playbooks/devstack/functional-post.yaml create mode 100644 playbooks/devstack/functional-run.yaml diff --git a/.zuul.yaml b/.zuul.yaml new file mode 100644 index 00000000..5a6b6a7e --- /dev/null +++ b/.zuul.yaml @@ -0,0 +1,21 @@ +- job: + name: heatclient-functional + parent: legacy-dsvm-base + run: playbooks/devstack/functional-run.yaml + post-run: playbooks/devstack/functional-post.yaml + timeout: 4200 + required-projects: + - openstack-infra/devstack-gate + - openstack/heat + - openstack/python-heatclient + vars: + branch_override: "default" + +- project: + name: openstack/python-heatclient + check: + jobs: + - heatclient-functional + gate: + jobs: + - heatclient-functional diff --git a/playbooks/devstack/functional-post.yaml b/playbooks/devstack/functional-post.yaml new file mode 100644 index 00000000..dac87534 --- /dev/null +++ b/playbooks/devstack/functional-post.yaml @@ -0,0 +1,80 @@ +- hosts: primary + tasks: + + - name: Copy files from {{ ansible_user_dir }}/workspace/ on node + synchronize: + src: '{{ ansible_user_dir }}/workspace/' + dest: '{{ zuul.executor.log_root }}' + mode: pull + copy_links: true + verify_host: true + rsync_opts: + - --include=**/*nose_results.html + - --include=*/ + - --exclude=* + - --prune-empty-dirs + + - name: Copy files from {{ ansible_user_dir }}/workspace/ on node + synchronize: + src: '{{ ansible_user_dir }}/workspace/' + dest: '{{ zuul.executor.log_root }}' + mode: pull + copy_links: true + verify_host: true + rsync_opts: + - --include=**/*testr_results.html.gz + - --include=*/ + - --exclude=* + - --prune-empty-dirs + + - name: Copy files from {{ ansible_user_dir }}/workspace/ on node + synchronize: + src: '{{ ansible_user_dir }}/workspace/' + dest: '{{ zuul.executor.log_root }}' + mode: pull + copy_links: true + verify_host: true + rsync_opts: + - --include=/.testrepository/tmp* + - --include=*/ + - --exclude=* + - --prune-empty-dirs + + - name: Copy files from {{ ansible_user_dir }}/workspace/ on node + synchronize: + src: '{{ ansible_user_dir }}/workspace/' + dest: '{{ zuul.executor.log_root }}' + mode: pull + copy_links: true + verify_host: true + rsync_opts: + - --include=**/*testrepository.subunit.gz + - --include=*/ + - --exclude=* + - --prune-empty-dirs + + - name: Copy files from {{ ansible_user_dir }}/workspace/ on node + synchronize: + src: '{{ ansible_user_dir }}/workspace/' + dest: '{{ zuul.executor.log_root }}/tox' + mode: pull + copy_links: true + verify_host: true + rsync_opts: + - --include=/.tox/*/log/* + - --include=*/ + - --exclude=* + - --prune-empty-dirs + + - name: Copy files from {{ ansible_user_dir }}/workspace/ on node + synchronize: + src: '{{ ansible_user_dir }}/workspace/' + dest: '{{ zuul.executor.log_root }}' + mode: pull + copy_links: true + verify_host: true + rsync_opts: + - --include=/logs/** + - --include=*/ + - --exclude=* + - --prune-empty-dirs diff --git a/playbooks/devstack/functional-run.yaml b/playbooks/devstack/functional-run.yaml new file mode 100644 index 00000000..477d2178 --- /dev/null +++ b/playbooks/devstack/functional-run.yaml @@ -0,0 +1,56 @@ +- hosts: all + name: Job for heatclient-functional + tasks: + + - name: Ensure legacy workspace directory + file: + path: '{{ ansible_user_dir }}/workspace' + state: directory + + - shell: + cmd: | + set -e + set -x + cat > clonemap.yaml << EOF + clonemap: + - name: openstack-infra/devstack-gate + dest: devstack-gate + EOF + /usr/zuul-env/bin/zuul-cloner -m clonemap.yaml --cache-dir /opt/git \ + git://git.openstack.org \ + openstack-infra/devstack-gate + executable: /bin/bash + chdir: '{{ ansible_user_dir }}/workspace' + environment: '{{ zuul | zuul_legacy_vars }}' + + - shell: + cmd: | + set -e + set -x + cat << 'EOF' >>"/tmp/dg-local.conf" + [[local|localrc]] + enable_plugin heat git://git.openstack.org/openstack/heat + EOF + executable: /bin/bash + chdir: '{{ ansible_user_dir }}/workspace' + environment: '{{ zuul | zuul_legacy_vars }}' + + - shell: + cmd: | + set -e + set -x + export PYTHONUNBUFFERED=true + export DEVSTACK_PROJECT_FROM_GIT=python-heatclient + if [ "{{ branch_override }}" != "default" ] ; then + export OVERRIDE_ZUUL_BRANCH=$BRANCH_OVERRIDE + fi + function post_test_hook { + # Configure and run functional tests + $BASE/new/python-heatclient/heatclient/tests/functional/hooks/post_test_hook.sh + } + export -f post_test_hook + cp devstack-gate/devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh + ./safe-devstack-vm-gate-wrap.sh + executable: /bin/bash + chdir: '{{ ansible_user_dir }}/workspace' + environment: '{{ zuul | zuul_legacy_vars }}' From 79d886106671bfb8e5dca74c733007e03388fab5 Mon Sep 17 00:00:00 2001 From: Andreas Jaeger Date: Sat, 2 Dec 2017 09:25:04 +0100 Subject: [PATCH 071/246] Avoid tox_install.sh for constraints support We do not need tox_install.sh, pip can handle constraints itself and install the project correctly. Thus update tox.ini and remove the now obsolete tools/tox_install.sh file. This follows https://review.openstack.org/#/c/508061 to remove tools/tox_install.sh. Change-Id: I84cee5a9a728b9d6dc7909ac28a60b9c20c2348c --- tools/tox_install.sh | 28 ---------------------------- tox.ini | 6 ++++-- 2 files changed, 4 insertions(+), 30 deletions(-) delete mode 100755 tools/tox_install.sh diff --git a/tools/tox_install.sh b/tools/tox_install.sh deleted file mode 100755 index e93c73b4..00000000 --- a/tools/tox_install.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bash - -# Client constraint file contains this client version pin that is in conflict -# with installing the client from source. We should remove the version pin in -# the constraints file before applying it for from-source installation. - -CONSTRAINTS_FILE=$1 -shift 1 - -set -e - -localfile="$VIRTUAL_ENV/log/upper-constraints.txt" - -if [[ $CONSTRAINTS_FILE != http* ]]; then - CONSTRAINTS_FILE=file://$CONSTRAINTS_FILE -fi - -curl $CONSTRAINTS_FILE --insecure --progress-bar --output $localfile - -pip install -c$localfile openstack-requirements - -# This is the main purpose of the script: Allow local installation of -# the current repo. It is listed in constraints file and thus any -# install will be constrained and we need to unconstrain it. -edit-constraints $localfile -- $CLIENT_NAME - -pip install -c$localfile -U $* -exit $? diff --git a/tox.ini b/tox.ini index 8b6026ba..94a7b6cf 100644 --- a/tox.ini +++ b/tox.ini @@ -7,8 +7,10 @@ skipsdist = True setenv = VIRTUAL_ENV={envdir} CLIENT_NAME=python-heatclient usedevelop = True -install_command = {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages} -deps = -r{toxinidir}/requirements.txt +install_command = pip install {opts} {packages} +deps = + -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} + -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = find . -type f -name "*.py[c|o]" -delete python setup.py testr --slowest --testr-args='{posargs}' From 1618d952ccf449016ed4b65c15b215efc69b73d3 Mon Sep 17 00:00:00 2001 From: Zane Bitter Date: Thu, 7 Dec 2017 11:49:11 -0500 Subject: [PATCH 072/246] Fix release note formatting This broke the building of the release notes. Change-Id: I6d9519aaf591e817e71a777a75fdc1d6caa7c17a --- .../add-converge-in-osc-stack-update-10f256589f628d13.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/releasenotes/notes/add-converge-in-osc-stack-update-10f256589f628d13.yaml b/releasenotes/notes/add-converge-in-osc-stack-update-10f256589f628d13.yaml index 70f65d37..4c5e495c 100644 --- a/releasenotes/notes/add-converge-in-osc-stack-update-10f256589f628d13.yaml +++ b/releasenotes/notes/add-converge-in-osc-stack-update-10f256589f628d13.yaml @@ -1,8 +1,9 @@ --- features: - - Adds '--converge' option for stack update with openstackclient. If + - | + Adds '--converge' option for stack update with openstackclient. If specified, existing resource attributes (which may have diverged) are compared with the resource properties in the template used for update. Usage: - # openstack stack update --converge ... \ No newline at end of file + # openstack stack update --converge ... From c173246b4557f64629bb4e41ad24e24186134249 Mon Sep 17 00:00:00 2001 From: ricolin Date: Fri, 8 Dec 2017 02:49:01 +0800 Subject: [PATCH 073/246] Add reno for release notes management Change-Id: Ia6d1c66602ac2845d30d9f95914445890a51329c --- .gitignore | 3 + releasenotes/source/conf.py | 180 +++++++++++++++++++++++++++++ releasenotes/source/index.rst | 10 ++ releasenotes/source/unreleased.rst | 5 + test-requirements.txt | 1 + tox.ini | 3 + 6 files changed, 202 insertions(+) create mode 100644 releasenotes/source/conf.py create mode 100644 releasenotes/source/index.rst create mode 100644 releasenotes/source/unreleased.rst diff --git a/.gitignore b/.gitignore index 42bff162..aa83571f 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,6 @@ heatclient/versioninfo *.egg-info *.log .testrepository + +# Files created by releasenotes build +releasenotes/build diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py new file mode 100644 index 00000000..63545a12 --- /dev/null +++ b/releasenotes/source/conf.py @@ -0,0 +1,180 @@ +# -*- coding: utf-8 -*- +# 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. +# +# Python-heatclient releasenotes documentation build configuration file, +# created by sphinx-quickstart on Fri Dec 8 02:08:36 2017. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'openstackdocstheme', + 'reno.sphinxext', +] + +# openstackdocstheme options +repository_name = 'openstack/python-heatclient' +bug_project = 'heat' +bug_tag = '' + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'Python-heatclient releasenotes' +copyright = u'2017, Heat team' +author = u'Heat team' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# + +# The short X.Y version. +version = '' +# The full version, including alpha/beta/rc tags. +release = '' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = [] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'openstackdocs' + +html_last_updated_fmt = '%Y-%m-%d %H:%M' +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +# html_static_path = ['_static'] + + +# -- Options for HTMLHelp output ------------------------------------------ + +# Output file base name for HTML help builder. +htmlhelp_basename = 'Python-heatclientreleasenotesdoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'Python-heatclientreleasenotes.tex', + u'Python-heatclient releasenotes Documentation', + u'Heat team', 'manual'), +] + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'python-heatclientreleasenotes', + u'Python-heatclient releasenotes Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'Python-heatclientreleasenotes', + u'Python-heatclient releasenotes Documentation', + author, 'Python-heatclientreleasenotes', + 'One line description of project.', + 'Miscellaneous'), +] + +# -- Options for Internationalization output ------------------------------ +locale_dirs = ['locale/'] diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst new file mode 100644 index 00000000..60142aea --- /dev/null +++ b/releasenotes/source/index.rst @@ -0,0 +1,10 @@ + +Welcome to Python-heatclient releasenotes's documentation! +========================================================== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + unreleased + diff --git a/releasenotes/source/unreleased.rst b/releasenotes/source/unreleased.rst new file mode 100644 index 00000000..b7be79ea --- /dev/null +++ b/releasenotes/source/unreleased.rst @@ -0,0 +1,5 @@ +============================== +Current Series Release Notes +============================== + +.. release-notes:: diff --git a/test-requirements.txt b/test-requirements.txt index bf5ca5f2..f2e0cae7 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -11,6 +11,7 @@ mock>=2.0.0 # BSD mox3>=0.20.0 # Apache-2.0 openstackdocstheme>=1.17.0 # Apache-2.0 python-openstackclient>=3.12.0 # Apache-2.0 +reno>=2.5.0 # Apache-2.0 sphinx>=1.6.2 # BSD tempest>=17.1.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD diff --git a/tox.ini b/tox.ini index 94a7b6cf..5f8d8b51 100644 --- a/tox.ini +++ b/tox.ini @@ -56,3 +56,6 @@ max-complexity=20 [hacking] import_exceptions = heatclient._i18n + +[testenv:releasenotes] +commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html From d1d4387babd7b249fc1fc3871a5756f88263c7e1 Mon Sep 17 00:00:00 2001 From: Zane Bitter Date: Wed, 29 Nov 2017 17:08:46 -0500 Subject: [PATCH 074/246] Document osc CLI Change-Id: Idafe730021f272360ac0588f8b35e7d392c96d4b Closes-Bug: #1735019 --- doc/source/cli/index.rst | 9 ++ doc/source/cli/orchestration.rst | 24 +++++ doc/source/cli/software_config.rst | 16 ++++ doc/source/cli/software_deployment.rst | 22 +++++ doc/source/cli/stack.rst | 123 +++++++++++++++++++++++++ doc/source/conf.py | 2 +- doc/source/index.rst | 43 +++++++-- heatclient/osc/v1/stack.py | 19 ++-- 8 files changed, 238 insertions(+), 20 deletions(-) create mode 100644 doc/source/cli/index.rst create mode 100644 doc/source/cli/orchestration.rst create mode 100644 doc/source/cli/software_config.rst create mode 100644 doc/source/cli/software_deployment.rst create mode 100644 doc/source/cli/stack.rst diff --git a/doc/source/cli/index.rst b/doc/source/cli/index.rst new file mode 100644 index 00000000..d58a3202 --- /dev/null +++ b/doc/source/cli/index.rst @@ -0,0 +1,9 @@ +============ +Command List +============ + +.. toctree:: + :glob: + :maxdepth: 3 + + * diff --git a/doc/source/cli/orchestration.rst b/doc/source/cli/orchestration.rst new file mode 100644 index 00000000..1c20e4c6 --- /dev/null +++ b/doc/source/cli/orchestration.rst @@ -0,0 +1,24 @@ +============= +orchestration +============= + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: orchestration template validate + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: orchestration template version list + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: orchestration template function list + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: orchestration resource_type list + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: orchestration resource_type show + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: orchestration build info + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: orchestration service list diff --git a/doc/source/cli/software_config.rst b/doc/source/cli/software_config.rst new file mode 100644 index 00000000..b7543080 --- /dev/null +++ b/doc/source/cli/software_config.rst @@ -0,0 +1,16 @@ +=============== +software config +=============== + + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: software config list + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: software config create + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: software config show + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: software config delete diff --git a/doc/source/cli/software_deployment.rst b/doc/source/cli/software_deployment.rst new file mode 100644 index 00000000..b8ccbe14 --- /dev/null +++ b/doc/source/cli/software_deployment.rst @@ -0,0 +1,22 @@ +=================== +software deployment +=================== + + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: software deployment list + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: software deployment create + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: software deployment show + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: software deployment output show + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: software deployment delete + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: software deployment metadata show diff --git a/doc/source/cli/stack.rst b/doc/source/cli/stack.rst new file mode 100644 index 00000000..175867e9 --- /dev/null +++ b/doc/source/cli/stack.rst @@ -0,0 +1,123 @@ +===== +stack +===== + +Basic actions +------------- + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: stack list + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: stack create + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: stack update + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: stack cancel + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: stack show + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: stack delete + +Outputs +------- + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: stack output list + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: stack output show + +Resources +--------- + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: stack resource list + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: stack resource show + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: stack resource metadata + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: stack resource mark unhealthy + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: stack resource signal + +Events +------ + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: stack event list + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: stack event show + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: stack failures list + +Input data +---------- + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: stack template show + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: stack environment show + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: stack file list + +User hooks +---------- + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: stack hook poll + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: stack hook clear + +Other actions +------------- + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: stack check + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: stack suspend + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: stack resume + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: stack export + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: stack abandon + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: stack adopt + +Snapshots +--------- + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: stack snapshot list + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: stack snapshot create + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: stack snapshot restore + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: stack snapshot show + +.. autoprogram-cliff:: openstack.orchestration.v1 + :command: stack snapshot delete diff --git a/doc/source/conf.py b/doc/source/conf.py index e2083596..78a716f2 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -36,7 +36,7 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'openstackdocstheme'] +extensions = ['sphinx.ext.autodoc', 'openstackdocstheme', 'cliff.sphinxext'] # Add any paths that contain templates here, relative to this directory. templates_path = [] diff --git a/doc/source/index.rst b/doc/source/index.rst index 0080645e..bf9ec8ae 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -1,10 +1,11 @@ Python bindings to the OpenStack Heat API ========================================= -This is a client for OpenStack Heat API. There's a Python API -(the :mod:`heatclient` module), and a command-line script -(installed as :program:`heat`). +This is a client for OpenStack Heat API. There's a Python API (the +:mod:`heatclient` module), a `python-openstackclient`_ plugin for command-line +use, and a legacy command-line script (installed as :program:`heat`). +========== Python API ========== @@ -52,8 +53,28 @@ Reference ref/index ref/v1/index -Command-line Tool -================= +============================ +OpenStackClient Command Line +============================ + +The preferred way of accessing Heat via the command line is using the +python-heatclient's plugin for `python-openstackclient`_. Heat commands are +available through the ``openstack`` CLI command when both python-heatclient and +python-openstackclient are installed. + +.. toctree:: + :maxdepth: 2 + + cli/index + +.. _python-openstackclient: https://docs.openstack.org/python-openstackclient + +======================== +Legacy Command-line Tool +======================== + +The ``heat`` command is provided as a legacy CLI option. Users should prefer +using the python-openstackclient plugin via the ``openstack`` command instead. In order to use the CLI, you must provide your OpenStack username, password, tenant, and auth endpoint. Use the corresponding @@ -79,20 +100,22 @@ Once you've configured your authentication parameters, you can run ``heat help`` to see a complete listing of available commands. Man Pages -========= +--------- .. toctree:: :maxdepth: 1 man/heat + +============ Contributing ============ -Code is hosted `on GitHub`_. Submit bugs to the Heat project on -`Launchpad`_. Submit code to the openstack/python-heatclient project -using `Gerrit`_. +Code is mirrored `on GitHub`_. Submit bugs to the python-heatclient project on +`Launchpad`_. Submit code to the openstack/python-heatclient project using +`Gerrit`_. .. _on GitHub: https://github.com/openstack/python-heatclient .. _Launchpad: https://launchpad.net/python-heatclient -.. _Gerrit: http://docs.openstack.org/infra/manual/developers.html#development-workflow +.. _Gerrit: https://docs.openstack.org/infra/manual/developers.html#development-workflow diff --git a/heatclient/osc/v1/stack.py b/heatclient/osc/v1/stack.py index 4a0c52e8..3817bea0 100644 --- a/heatclient/osc/v1/stack.py +++ b/heatclient/osc/v1/stack.py @@ -59,9 +59,9 @@ def get_parser(self, prog_name): action='append', help=_('Name of a resource to set a pre-create hook to. Resources ' 'in nested stacks can be set using slash as a separator: ' - 'nested_stack/another/my_resource. You can use wildcards ' - 'to match multiple stacks or resources: ' - 'nested_stack/an*/*_resource. This can be specified ' + '``nested_stack/another/my_resource``. You can use ' + 'wildcards to match multiple stacks or resources: ' + '``nested_stack/an*/*_resource``. This can be specified ' 'multiple times') ) parser.add_argument( @@ -210,9 +210,9 @@ def get_parser(self, prog_name): '--pre-update', metavar='', action='append', help=_('Name of a resource to set a pre-update hook to. Resources ' 'in nested stacks can be set using slash as a separator: ' - 'nested_stack/another/my_resource. You can use wildcards ' - 'to match multiple stacks or resources: ' - 'nested_stack/an*/*_resource. This can be specified ' + '``nested_stack/another/my_resource``. You can use ' + 'wildcards to match multiple stacks or resources: ' + '``nested_stack/an*/*_resource``. This can be specified ' 'multiple times') ) parser.add_argument( @@ -1217,6 +1217,7 @@ class CancelStack(StackActionBase): """Cancel current task for a stack. Supported tasks for cancellation: + * update * create """ @@ -1379,9 +1380,9 @@ def get_parser(self, prog_name): nargs='+', help=_('Resource names with hooks to clear. Resources ' 'in nested stacks can be set using slash as a separator: ' - 'nested_stack/another/my_resource. You can use wildcards ' - 'to match multiple stacks or resources: ' - 'nested_stack/an*/*_resource') + '``nested_stack/another/my_resource``. You can use ' + 'wildcards to match multiple stacks or resources: ' + '``nested_stack/an*/*_resource``') ) return parser From 7c4f191f99a3c9bd174cf50cb5246278f65bc067 Mon Sep 17 00:00:00 2001 From: "James E. Blair" Date: Wed, 24 Jan 2018 16:32:55 -0800 Subject: [PATCH 075/246] Zuul: Remove project name Zuul no longer requires the project-name for in-repo configuration. Omitting it makes forking or renaming projects easier. Change-Id: Iaf6080f75d690efb3941fbfef953df3d3351dccf --- .zuul.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index 5a6b6a7e..ab4d643d 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -9,10 +9,9 @@ - openstack/heat - openstack/python-heatclient vars: - branch_override: "default" + branch_override: default - project: - name: openstack/python-heatclient check: jobs: - heatclient-functional From 2ce6aa19a3a8936bc7c9737efadacbb4ae737dab Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 31 Jan 2018 14:09:28 +0000 Subject: [PATCH 076/246] Updated from global requirements Change-Id: I3618291ee2c3db35ab0e0d5e9c962ed49546588a --- requirements.txt | 6 +++--- test-requirements.txt | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/requirements.txt b/requirements.txt index 30cac66d..2b0cd7b0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,12 +6,12 @@ Babel!=2.4.0,>=2.3.4 # BSD pbr!=2.1.0,>=2.0.0 # Apache-2.0 cliff!=2.9.0,>=2.8.0 # Apache-2.0 iso8601>=0.1.11 # MIT -osc-lib>=1.7.0 # Apache-2.0 +osc-lib>=1.8.0 # Apache-2.0 PrettyTable<0.8,>=0.7.1 # BSD oslo.i18n>=3.15.3 # Apache-2.0 oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0 -oslo.utils>=3.31.0 # Apache-2.0 -keystoneauth1>=3.2.0 # Apache-2.0 +oslo.utils>=3.33.0 # Apache-2.0 +keystoneauth1>=3.3.0 # Apache-2.0 python-swiftclient>=3.2.0 # Apache-2.0 PyYAML>=3.10 # MIT requests>=2.14.2 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index f2e0cae7..22a30c98 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -9,10 +9,10 @@ fixtures>=3.0.0 # Apache-2.0/BSD requests-mock>=1.1.0 # Apache-2.0 mock>=2.0.0 # BSD mox3>=0.20.0 # Apache-2.0 -openstackdocstheme>=1.17.0 # Apache-2.0 +openstackdocstheme>=1.18.1 # Apache-2.0 python-openstackclient>=3.12.0 # Apache-2.0 reno>=2.5.0 # Apache-2.0 -sphinx>=1.6.2 # BSD +sphinx!=1.6.6,>=1.6.2 # BSD tempest>=17.1.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testscenarios>=0.4 # Apache-2.0/BSD From 6a27997abb1944f19feedf52b9f1415c15157861 Mon Sep 17 00:00:00 2001 From: liyi Date: Thu, 1 Feb 2018 14:56:03 +0800 Subject: [PATCH 077/246] Fix log_http_request function in http module Change-Id: I043f90541c8d2d604f3edb4e7ea082ec8adb4b25 Closes-Bug: #1746669 --- heatclient/common/http.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/heatclient/common/http.py b/heatclient/common/http.py index 4b765dfc..bb2f0c1e 100644 --- a/heatclient/common/http.py +++ b/heatclient/common/http.py @@ -137,7 +137,10 @@ def log_curl_request(self, method, url, kwargs): if 'data' in kwargs: curl.append('-d \'%s\'' % kwargs['data']) - curl.append('%s%s' % (self.endpoint, url)) + if not parse.urlparse(url).netloc: + url = self.endpoint + url + + curl.append(url) LOG.debug(' '.join(curl)) @staticmethod From dea778abda6d5c7f8fb21dfdb842cb7ea7f92bff Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Thu, 1 Feb 2018 16:34:15 +0000 Subject: [PATCH 078/246] Update reno for stable/queens Change-Id: Ie5d57e22855cbfcb2dd1e3eee30612dfa6e202e9 --- releasenotes/source/index.rst | 1 + releasenotes/source/queens.rst | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 releasenotes/source/queens.rst diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index 60142aea..44adadfb 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -7,4 +7,5 @@ Welcome to Python-heatclient releasenotes's documentation! :caption: Contents: unreleased + queens diff --git a/releasenotes/source/queens.rst b/releasenotes/source/queens.rst new file mode 100644 index 00000000..36ac6160 --- /dev/null +++ b/releasenotes/source/queens.rst @@ -0,0 +1,6 @@ +=================================== + Queens Series Release Notes +=================================== + +.. release-notes:: + :branch: stable/queens From f0f7226b249cdfc762a7682e0fd318debdd332b7 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sat, 17 Feb 2018 10:12:24 +0000 Subject: [PATCH 079/246] Updated from global requirements Change-Id: I71422dc69b237ddf39adbe2cb9fc0c8ebd00970f --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 2b0cd7b0..72ec44c1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ PrettyTable<0.8,>=0.7.1 # BSD oslo.i18n>=3.15.3 # Apache-2.0 oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 -keystoneauth1>=3.3.0 # Apache-2.0 +keystoneauth1>=3.4.0 # Apache-2.0 python-swiftclient>=3.2.0 # Apache-2.0 PyYAML>=3.10 # MIT requests>=2.14.2 # Apache-2.0 From 55a71609cb68ad6e883855e81bca4f9d70d05e2b Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sun, 4 Mar 2018 10:25:01 +0000 Subject: [PATCH 080/246] Updated from global requirements Change-Id: I03a19c6c7acf6138b261e99e66b7d30811d7f410 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 72ec44c1..849c11db 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,6 +13,6 @@ oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 keystoneauth1>=3.4.0 # Apache-2.0 python-swiftclient>=3.2.0 # Apache-2.0 -PyYAML>=3.10 # MIT +PyYAML>=3.12 # MIT requests>=2.14.2 # Apache-2.0 six>=1.10.0 # MIT From 79242e2e4f7bf86cfeb1f3318147da6525f6a7e8 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 23 Mar 2018 01:45:57 +0000 Subject: [PATCH 081/246] Updated from global requirements Change-Id: I631b07a7187a67042f094683da64b663d3c4a40b --- test-requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-requirements.txt b/test-requirements.txt index 22a30c98..2d055edb 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -6,13 +6,13 @@ hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD -requests-mock>=1.1.0 # Apache-2.0 +requests-mock>=1.2.0 # Apache-2.0 mock>=2.0.0 # BSD mox3>=0.20.0 # Apache-2.0 openstackdocstheme>=1.18.1 # Apache-2.0 python-openstackclient>=3.12.0 # Apache-2.0 reno>=2.5.0 # Apache-2.0 -sphinx!=1.6.6,>=1.6.2 # BSD +sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD tempest>=17.1.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testscenarios>=0.4 # Apache-2.0/BSD From f41ad78cd49788bc8589b849a888aeb9bb916997 Mon Sep 17 00:00:00 2001 From: ricolin Date: Tue, 19 Dec 2017 15:36:06 +0800 Subject: [PATCH 082/246] Follow the new PTI for document build For compliance with the Project Testing Interface as described in: https://governance.openstack.org/tc/reference/project-testing-interface.html Refer to: http://lists.openstack.org/pipermail/openstack-dev/2017-November/124815.html Change-Id: Ibb35b13fe121fa2489ef31f51e1a662fbd01cc8b Co-Authored-By: Nguyen Hai --- doc/requirements.txt | 4 ++++ setup.cfg | 7 ------- test-requirements.txt | 3 --- tox.ini | 9 +++++++-- 4 files changed, 11 insertions(+), 12 deletions(-) create mode 100644 doc/requirements.txt diff --git a/doc/requirements.txt b/doc/requirements.txt new file mode 100644 index 00000000..6889f206 --- /dev/null +++ b/doc/requirements.txt @@ -0,0 +1,4 @@ +openstackdocstheme>=1.18.1 # Apache-2.0 +reno>=2.5.0 # Apache-2.0 +sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD +sphinxcontrib-httpdomain>=1.3.0 # BSD diff --git a/setup.cfg b/setup.cfg index e52e6013..c35e7770 100644 --- a/setup.cfg +++ b/setup.cfg @@ -103,13 +103,6 @@ heatclient.resource.formatter.list = setup-hooks = pbr.hooks.setup_hook -[build_sphinx] -builders = html,man -all-files = 1 -warning-is-error = 1 -source-dir = doc/source -build-dir = doc/build - [upload_sphinx] upload-dir = doc/build/html diff --git a/test-requirements.txt b/test-requirements.txt index 2d055edb..5fb01ef9 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -9,10 +9,7 @@ fixtures>=3.0.0 # Apache-2.0/BSD requests-mock>=1.2.0 # Apache-2.0 mock>=2.0.0 # BSD mox3>=0.20.0 # Apache-2.0 -openstackdocstheme>=1.18.1 # Apache-2.0 python-openstackclient>=3.12.0 # Apache-2.0 -reno>=2.5.0 # Apache-2.0 -sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD tempest>=17.1.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testscenarios>=0.4 # Apache-2.0/BSD diff --git a/tox.ini b/tox.ini index 5f8d8b51..c7250451 100644 --- a/tox.ini +++ b/tox.ini @@ -46,8 +46,10 @@ commands = coverage report [testenv:docs] -commands= - python setup.py build_sphinx +deps = + -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} + -r{toxinidir}/doc/requirements.txt +commands = sphinx-build -W -b html doc/source doc/build/html [flake8] show-source = True @@ -58,4 +60,7 @@ max-complexity=20 import_exceptions = heatclient._i18n [testenv:releasenotes] +deps = + -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} + -r{toxinidir}/doc/requirements.txt commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html From 08a925f18403ebb561775c4a589bab7f04d3b465 Mon Sep 17 00:00:00 2001 From: Georgy Dyuldin Date: Wed, 26 Oct 2016 11:54:56 +0300 Subject: [PATCH 083/246] Add heat CLI test for stack-show Change-Id: I35b17be14438e6de6fffd0396bab9989ef812a2a --- heatclient/tests/functional/osc/v1/base.py | 5 +++++ heatclient/tests/functional/osc/v1/test_stack.py | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/heatclient/tests/functional/osc/v1/base.py b/heatclient/tests/functional/osc/v1/base.py index b17fb4c0..c9a946e3 100644 --- a/heatclient/tests/functional/osc/v1/base.py +++ b/heatclient/tests/functional/osc/v1/base.py @@ -114,3 +114,8 @@ def _stack_snapshot_delete(self, id, snapshot_id): def _stack_snapshot_restore(self, id, snapshot_id): cmd = 'stack snapshot restore ' + id + ' ' + snapshot_id self.openstack(cmd) + + def _stack_show(self, stack_id): + cmd = 'stack show ' + stack_id + stack_raw = self.openstack(cmd) + return self.show_to_dict(stack_raw) diff --git a/heatclient/tests/functional/osc/v1/test_stack.py b/heatclient/tests/functional/osc/v1/test_stack.py index 2f985f76..d5bc6535 100644 --- a/heatclient/tests/functional/osc/v1/test_stack.py +++ b/heatclient/tests/functional/osc/v1/test_stack.py @@ -75,3 +75,10 @@ def test_stack_snapshot_delete(self): stacks_raw = self.openstack( 'stack snapshot list' + ' ' + self.stack_name) self.assertNotIn(snapshot['id'], stacks_raw) + + def test_stack_show(self): + stack = self._stack_create_minimal() + stack_info = self._stack_show(stack['id']) + stack_props = {k: v for k, v in stack_info.items() + if k in stack.keys()} + self.assertEqual(stack, stack_props) From bf2b57a92b338899e29bdfd62bb74a456da10ee8 Mon Sep 17 00:00:00 2001 From: Zane Bitter Date: Fri, 6 Apr 2018 15:10:28 -0400 Subject: [PATCH 084/246] Improve titles of generated API documentation Change-Id: I1a5820e979418063b7e203acc5e07ce0e7be30bd --- doc/source/ext/gen_ref.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/doc/source/ext/gen_ref.py b/doc/source/ext/gen_ref.py index 15ff8182..cbe936ba 100644 --- a/doc/source/ext/gen_ref.py +++ b/doc/source/ext/gen_ref.py @@ -37,7 +37,7 @@ def gen_ref(ver, title, names): ".. toctree::\n" " :maxdepth: 1\n" "\n") % {"title": title, "signs": "=" * len(title)}) - for name in names: + for name, sec_title in names.items(): idx.write(" %s\n" % name) rstpath = os.path.join(refdir, "%s.rst" % name) with open(rstpath, "w") as rst: @@ -49,11 +49,15 @@ def gen_ref(ver, title, names): " :undoc-members:\n" " :show-inheritance:\n" " :noindex:\n") - % {"title": name.capitalize(), - "signs": "=" * len(name), + % {"title": sec_title, + "signs": "=" * len(sec_title), "pkg": pkg, "name": name}) -gen_ref("", "Client Reference", ["client", "exc"]) +gen_ref("", "Client Reference", {"client": "Client", "exc": "Exceptions"}) gen_ref("v1", "Version 1 API Reference", - ["stacks", "resources", "events", "actions", - "software_configs", "software_deployments"]) + {"stacks": "Stacks", + "resources": "Resources", + "events": "Events", + "actions": "Actions", + "software_configs": "Software Configs", + "software_deployments": "Software Deployments"}) From d7bd59b9b32dd1dfdec29a86519bce21d3e9a9dd Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Thu, 22 Mar 2018 17:48:18 -0400 Subject: [PATCH 085/246] add lower-constraints job Create a tox environment for running the unit tests against the lower bounds of the dependencies. Create a lower-constraints.txt to be used to enforce the lower bounds in those tests. Add openstack-tox-lower-constraints job to the zuul configuration. Update PrettyTable dependency so the tests pass. See http://lists.openstack.org/pipermail/openstack-dev/2018-March/128352.html for more details. Change-Id: I59c771ca583fb0ebc9f39314ee68264410f8a173 Depends-On: https://review.openstack.org/555034 Signed-off-by: Doug Hellmann --- .zuul.yaml | 2 + lower-constraints.txt | 98 +++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 2 +- tox.ini | 7 ++++ 4 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 lower-constraints.txt diff --git a/.zuul.yaml b/.zuul.yaml index ab4d643d..57ad3814 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -15,6 +15,8 @@ check: jobs: - heatclient-functional + - openstack-tox-lower-constraints gate: jobs: - heatclient-functional + - openstack-tox-lower-constraints diff --git a/lower-constraints.txt b/lower-constraints.txt new file mode 100644 index 00000000..02fa291b --- /dev/null +++ b/lower-constraints.txt @@ -0,0 +1,98 @@ +alabaster==0.7.10 +appdirs==1.3.0 +asn1crypto==0.23.0 +Babel==2.3.4 +cffi==1.7.0 +cliff==2.8.0 +cmd2==0.8.0 +coverage==4.0 +cryptography==2.1 +debtcollector==1.2.0 +decorator==3.4.0 +deprecation==1.0 +docutils==0.11 +dogpile.cache==0.6.2 +dulwich==0.15.0 +extras==1.0.0 +fasteners==0.7.0 +fixtures==3.0.0 +flake8==2.5.5 +future==0.16.0 +hacking==0.12.0 +idna==2.6 +imagesize==0.7.1 +iso8601==0.1.11 +Jinja2==2.10 +jmespath==0.9.0 +jsonpatch==1.16 +jsonpointer==1.13 +jsonschema==2.6.0 +keystoneauth1==3.4.0 +linecache2==1.0.0 +MarkupSafe==1.0 +mccabe==0.2.1 +mock==2.0.0 +monotonic==0.6 +mox3==0.20.0 +msgpack-python==0.4.0 +munch==2.1.0 +netaddr==0.7.18 +netifaces==0.10.4 +openstackdocstheme==1.18.1 +openstacksdk==0.11.2 +os-client-config==1.28.0 +os-service-types==1.2.0 +os-testr==1.0.0 +osc-lib==1.8.0 +oslo.concurrency==3.25.0 +oslo.config==5.2.0 +oslo.context==2.19.2 +oslo.i18n==3.15.3 +oslo.log==3.36.0 +oslo.serialization==2.18.0 +oslo.utils==3.33.0 +paramiko==2.0.0 +pbr==2.0.0 +pep8==1.5.7 +positional==1.2.1 +prettytable==0.7.2 +pyasn1==0.1.8 +pycparser==2.18 +pyflakes==0.8.1 +Pygments==2.2.0 +pyinotify==0.9.6 +pyOpenSSL==17.1.0 +pyparsing==2.1.0 +pyperclip==1.5.27 +python-cinderclient==3.3.0 +python-dateutil==2.5.3 +python-glanceclient==2.8.0 +python-keystoneclient==3.8.0 +python-mimeparse==1.6.0 +python-novaclient==9.1.0 +python-openstackclient==3.12.0 +python-subunit==1.0.0 +python-swiftclient==3.2.0 +pytz==2013.6 +PyYAML==3.12 +reno==2.5.0 +requests-mock==1.2.0 +requests==2.14.2 +requestsexceptions==1.2.0 +rfc3986==0.3.1 +simplejson==3.5.1 +six==1.10.0 +snowballstemmer==1.2.1 +Sphinx==1.6.2 +sphinxcontrib-websupport==1.0.1 +stestr==1.0.0 +stevedore==1.20.0 +tempest==17.1.0 +testrepository==0.0.18 +testscenarios==0.4 +testtools==2.2.0 +traceback2==1.4.0 +unittest2==1.1.0 +urllib3==1.21.1 +warlock==1.2.0 +wrapt==1.7.0 diff --git a/requirements.txt b/requirements.txt index 849c11db..a81bcea9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ pbr!=2.1.0,>=2.0.0 # Apache-2.0 cliff!=2.9.0,>=2.8.0 # Apache-2.0 iso8601>=0.1.11 # MIT osc-lib>=1.8.0 # Apache-2.0 -PrettyTable<0.8,>=0.7.1 # BSD +PrettyTable<0.8,>=0.7.2 # BSD oslo.i18n>=3.15.3 # Apache-2.0 oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 diff --git a/tox.ini b/tox.ini index c7250451..3b206264 100644 --- a/tox.ini +++ b/tox.ini @@ -64,3 +64,10 @@ deps = -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} -r{toxinidir}/doc/requirements.txt commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html + +[testenv:lower-constraints] +basepython = python3 +deps = + -c{toxinidir}/lower-constraints.txt + -r{toxinidir}/test-requirements.txt + -r{toxinidir}/requirements.txt From dcdacd2f56dda3c5581fc4b27fec0a288caa5156 Mon Sep 17 00:00:00 2001 From: Vu Cong Tuan Date: Wed, 1 Nov 2017 16:35:34 +0700 Subject: [PATCH 086/246] Use assertRegex instead of assertRegexpMatches In Python3, assertRegexpMatches & assertNotRegexpMatches are deprecated in favor of assertRegex and assertNotRegex Change-Id: Ied56eda65800d72105ef2e2b683c6ce71cda6c4c --- heatclient/tests/unit/test_shell.py | 34 ++++++++++++++--------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/heatclient/tests/unit/test_shell.py b/heatclient/tests/unit/test_shell.py index 175eda13..2ea4fd4b 100644 --- a/heatclient/tests/unit/test_shell.py +++ b/heatclient/tests/unit/test_shell.py @@ -774,7 +774,7 @@ def test_stack_list(self): ] for r in required: self.assertRegex(list_text, r) - self.assertNotRegexpMatches(list_text, 'parent') + self.assertNotRegex(list_text, 'parent') def test_stack_list_show_nested(self): self.register_keystone_auth_fixture() @@ -2918,7 +2918,7 @@ def test_shell_nested_depth_marker_old_api(self): for r in required: self.assertRegex(list_text, r) - self.assertNotRegexpMatches(list_text, 'p_eventid1') + self.assertNotRegex(list_text, 'p_eventid1') self.assertRegex(list_text, "%s.*\n.*%s.*\n.*%s.*" % timestamps[1:]) @@ -2942,8 +2942,8 @@ def test_shell_nested_depth_limit_old_api(self): 'stack_name', 'teststack', 'nested'] for r in required: self.assertRegex(list_text, r) - self.assertNotRegexpMatches(list_text, 'p_eventid2') - self.assertNotRegexpMatches(list_text, 'n_eventid2') + self.assertNotRegex(list_text, 'p_eventid2') + self.assertNotRegex(list_text, 'n_eventid2') self.assertRegex(list_text, "%s.*\n.*%s.*\n" % timestamps[:2]) @@ -3121,9 +3121,9 @@ def test_hook_poll_pre_create(self): required = ['id', 'p_eventid2', 'stack_name', 'teststack', hook_reason] for r in required: self.assertRegex(list_text, r) - self.assertNotRegexpMatches(list_text, 'p_eventid1') - self.assertNotRegexpMatches(list_text, 'n_eventid1') - self.assertNotRegexpMatches(list_text, 'n_eventid2') + 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() @@ -3136,9 +3136,9 @@ def test_hook_poll_pre_update(self): required = ['id', 'p_eventid2', 'stack_name', 'teststack', hook_reason] for r in required: self.assertRegex(list_text, r) - self.assertNotRegexpMatches(list_text, 'p_eventid1') - self.assertNotRegexpMatches(list_text, 'n_eventid1') - self.assertNotRegexpMatches(list_text, 'n_eventid2') + 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() @@ -3151,9 +3151,9 @@ def test_hook_poll_pre_delete(self): required = ['id', 'p_eventid2', 'stack_name', 'teststack', hook_reason] for r in required: self.assertRegex(list_text, r) - self.assertNotRegexpMatches(list_text, 'p_eventid1') - self.assertNotRegexpMatches(list_text, 'n_eventid1') - self.assertNotRegexpMatches(list_text, 'n_eventid2') + 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() @@ -3887,13 +3887,13 @@ def test_deploy_list(self): ] for r in required: self.assertRegex(list_text, r) - self.assertNotRegexpMatches(list_text, 'parent') + self.assertNotRegex(list_text, 'parent') list_text = self.shell('deployment-list -s 123') for r in required: self.assertRegex(list_text, r) - self.assertNotRegexpMatches(list_text, 'parent') + self.assertNotRegex(list_text, 'parent') def test_deploy_show(self): self.register_keystone_auth_fixture() @@ -4227,7 +4227,7 @@ def test_commandline_args_passed_to_requests(self): ] for r in required: self.assertRegex(list_text, r) - self.assertNotRegexpMatches(list_text, 'parent') + self.assertNotRegex(list_text, 'parent') class MockShellBase(TestCase): @@ -4308,7 +4308,7 @@ def test_stack_list_with_args(self): ] for r in required: self.assertRegex(list_text, r) - self.assertNotRegexpMatches(list_text, 'parent') + self.assertNotRegex(list_text, 'parent') if self.jreq_mock.call_args is None: self.assertEqual(1, self.session_jreq_mock.call_count) From aab5b85811c4a23dc0b66e27c771a59a91a2bec0 Mon Sep 17 00:00:00 2001 From: Nguyen Hai Date: Fri, 16 Mar 2018 15:35:38 +0900 Subject: [PATCH 087/246] Change testenv from py34 to py35 Change testenv:debug34 to testenv:debug35 Change-Id: I49a63aabb33ff117842455d475fb8eab0e086912 --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index c7250451..cbba4368 100644 --- a/tox.ini +++ b/tox.ini @@ -23,8 +23,8 @@ commands = oslo_debug_helper -t heatclient/tests {posargs} basepython = python2.7 commands = oslo_debug_helper -t heatclient/tests {posargs} -[testenv:debug-py34] -basepython = python3.4 +[testenv:debug-py35] +basepython = python3.5 commands = oslo_debug_helper -t heatclient/tests {posargs} [testenv:pep8] From 36df27d61bc82eceeaf69cfda390d8097677af7a Mon Sep 17 00:00:00 2001 From: rabi Date: Mon, 4 Jun 2018 12:48:16 +0530 Subject: [PATCH 088/246] Fix osc cli docs In d1d4387babd7b249fc1fc3871a5756f88263c7e1 we changed to auto generate osc cli docs. But it seems some of them use incorrect commands. Change-Id: I650b09a1d998269cce767a59aaf92e0cc5797bc7 --- doc/source/cli/orchestration.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/cli/orchestration.rst b/doc/source/cli/orchestration.rst index 1c20e4c6..0aac0ef7 100644 --- a/doc/source/cli/orchestration.rst +++ b/doc/source/cli/orchestration.rst @@ -12,10 +12,10 @@ orchestration :command: orchestration template function list .. autoprogram-cliff:: openstack.orchestration.v1 - :command: orchestration resource_type list + :command: orchestration resource type list .. autoprogram-cliff:: openstack.orchestration.v1 - :command: orchestration resource_type show + :command: orchestration resource type show .. autoprogram-cliff:: openstack.orchestration.v1 :command: orchestration build info From 4c782b2817b9519cafe8e7c0f96469e6b97453e5 Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Wed, 6 Jun 2018 17:58:17 -0400 Subject: [PATCH 089/246] fix tox python3 overrides We want to default to running all tox environments under python 3, so set the basepython value in each environment. We do not want to specify a minor version number, because we do not want to have to update the file every time we upgrade python. We do not want to set the override once in testenv, because that breaks the more specific versions used in default environments like py35 and py36. Change-Id: Ia20d2ef56af2da00b374b3a3e5d79d7228c6a763 Signed-off-by: Doug Hellmann --- tox.ini | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tox.ini b/tox.ini index f1371282..271fe11a 100644 --- a/tox.ini +++ b/tox.ini @@ -17,6 +17,7 @@ commands = find . -type f -name "*.py[c|o]" -delete whitelist_externals = find [testenv:debug] +basepython = python3 commands = oslo_debug_helper -t heatclient/tests {posargs} [testenv:debug-py27] @@ -28,10 +29,12 @@ basepython = python3.5 commands = oslo_debug_helper -t heatclient/tests {posargs} [testenv:pep8] +basepython = python3 commands = flake8 [testenv:venv] +basepython = python3 commands = {posargs} [testenv:functional] @@ -41,11 +44,13 @@ setenv = passenv = OS_* [testenv:cover] +basepython = python3 commands = python setup.py testr --coverage --testr-args='{posargs}' coverage report [testenv:docs] +basepython = python3 deps = -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} -r{toxinidir}/doc/requirements.txt @@ -60,6 +65,7 @@ max-complexity=20 import_exceptions = heatclient._i18n [testenv:releasenotes] +basepython = python3 deps = -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} -r{toxinidir}/doc/requirements.txt From 5cfbe76e80e680dfc4ca3afb4aa7acdd02fd8f95 Mon Sep 17 00:00:00 2001 From: Chen Date: Thu, 7 Jun 2018 22:38:41 +0800 Subject: [PATCH 090/246] Remove PyPI downloads According to official site, https://packaging.python.org/guides/analyzing-pypi-package-downloads/ PyPI package download statistics is no longer maintained and thus should be removed. Change-Id: I70c9814f8ce67a46628d2101dc638783715160db --- README.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.rst b/README.rst index dab599c8..51c6ff3d 100644 --- a/README.rst +++ b/README.rst @@ -15,10 +15,6 @@ python-heatclient :target: https://pypi.python.org/pypi/python-heatclient/ :alt: Latest Version -.. image:: https://img.shields.io/pypi/dm/python-heatclient.svg - :target: https://pypi.python.org/pypi/python-heatclient/ - :alt: Downloads - OpenStack Orchestration API Client Library From 0b46b2089c8ec26a2e662538f513d76cb05ca47a Mon Sep 17 00:00:00 2001 From: Thomas Herve Date: Thu, 7 Jun 2018 18:13:48 +0200 Subject: [PATCH 091/246] Mark stacks returned by get loaded When calling stacks.get, mark the returning stacks as loaded, so that attribute retrieval works right away. If not, any getattr on the Stack will call get() which will request the stack again (and ignore things like resolve_outputs). Change-Id: I3a3f25aa0c6f88e7e4274107c0578fd30dc9f155 Related-Bug: #1774958 --- heatclient/v1/stacks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/heatclient/v1/stacks.py b/heatclient/v1/stacks.py index 1a762867..2484ff45 100644 --- a/heatclient/v1/stacks.py +++ b/heatclient/v1/stacks.py @@ -279,7 +279,7 @@ def get(self, stack_id, resolve_outputs=True): kwargs['params'] = {"resolve_outputs": False} resp = self.client.get('/stacks/%s' % stack_id, **kwargs) body = utils.get_response_body(resp) - return Stack(self, body.get('stack')) + return Stack(self, body.get('stack'), loaded=True) def template(self, stack_id): """Get template content for a specific stack as a parsed JSON object. From 1cc69c027225d1e9fe58cc1cd50b1e786f9328b0 Mon Sep 17 00:00:00 2001 From: lingyongxu Date: Wed, 2 Aug 2017 16:07:35 +0800 Subject: [PATCH 092/246] Update the documentation link for doc migration This patch is proposed according to the Direction 10 of doc migration. https://etherpad.openstack.org/p/doc-migration-tracking Co-Authored-By: Nguyen Hai Change-Id: I9675af92149ab126534f541534b50a02688e5337 --- CONTRIBUTING.rst | 4 ++-- README.rst | 10 +++++----- doc/source/index.rst | 2 +- heatclient/_i18n.py | 2 +- setup.cfg | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index d7f2b8c8..82659e8e 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -1,13 +1,13 @@ If you would like to contribute to the development of OpenStack, you must follow the steps documented at: - http://docs.openstack.org/infra/manual/developers.html#development-workflow + https://docs.openstack.org/infra/manual/developers.html#development-workflow Once those steps have been completed, changes to OpenStack should be submitted for review via the Gerrit tool, following the workflow documented at: - http://docs.openstack.org/infra/manual/developers.html#development-workflow + https://docs.openstack.org/infra/manual/developers.html#development-workflow Pull requests submitted through GitHub will be ignored. diff --git a/README.rst b/README.rst index dab599c8..a7e3d3f8 100644 --- a/README.rst +++ b/README.rst @@ -2,8 +2,8 @@ Team and repository tags ======================== -.. image:: http://governance.openstack.org/badges/python-heatclient.svg - :target: http://governance.openstack.org/reference/tags/index.html +.. image:: https://governance.openstack.org/tc/badges/python-heatclient.svg + :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on @@ -38,11 +38,11 @@ provides a Python API (the ``heatclient`` module) and a command-line tool * `How to Contribute`_ .. _PyPi: https://pypi.python.org/pypi/python-heatclient -.. _Online Documentation: http://docs.openstack.org/developer/python-heatclient +.. _Online Documentation: https://docs.openstack.org/python-heatclient/latest .. _Launchpad project: https://launchpad.net/python-heatclient .. _Blueprints: https://blueprints.launchpad.net/python-heatclient .. _Bugs: https://bugs.launchpad.net/python-heatclient .. _Source: https://git.openstack.org/cgit/openstack/python-heatclient -.. _How to Contribute: http://docs.openstack.org/infra/manual/developers.html -.. _Specs: http://specs.openstack.org/openstack/heat-specs/ +.. _How to Contribute: https://docs.openstack.org/infra/manual/developers.html +.. _Specs: https://specs.openstack.org/openstack/heat-specs/ .. _Template: https://git.openstack.org/cgit/openstack/heat-templates/ diff --git a/doc/source/index.rst b/doc/source/index.rst index bf9ec8ae..bb610d03 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -42,7 +42,7 @@ PROJECT_NAME also provide the domain information as `project_domain_(name|id)`. For more information on keystoneauth API, see `Using Sessions`_. -.. _Using Sessions: http://docs.openstack.org/developer/keystoneauth/using-sessions.html +.. _Using Sessions: https://docs.openstack.org/keystoneauth/latest/using-sessions.html Reference --------- diff --git a/heatclient/_i18n.py b/heatclient/_i18n.py index f8c62691..4abe87e5 100644 --- a/heatclient/_i18n.py +++ b/heatclient/_i18n.py @@ -12,7 +12,7 @@ """oslo.i18n integration module. -See http://docs.openstack.org/developer/oslo.i18n/usage.html +See https://docs.openstack.org/oslo.i18n/latest/user/index.html """ diff --git a/setup.cfg b/setup.cfg index c35e7770..b4bd84e6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -6,7 +6,7 @@ description-file = README.rst author = OpenStack author-email = openstack-dev@lists.openstack.org -home-page = http://docs.openstack.org/developer/python-heatclient/ +home-page = https://docs.openstack.org/python-heatclient/latest classifier = Development Status :: 5 - Production/Stable Environment :: Console From c000dd1d0940b47c13761bb09e0cb50a2adc6a2e Mon Sep 17 00:00:00 2001 From: rabi Date: Thu, 20 Oct 2016 14:05:23 +0530 Subject: [PATCH 093/246] Handle token_endpoint auth type in osc plugin There can be situations where the auth_type=token_endpoint, we should use the appropriate attributes from the auth plugin when creating heatclient. Change-Id: Ic929f6ceaa92002eb2b3443934d1f541758e4dda Closes-Bug: #1632341 --- heatclient/osc/plugin.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/heatclient/osc/plugin.py b/heatclient/osc/plugin.py index 8bc3f15f..859a13e8 100644 --- a/heatclient/osc/plugin.py +++ b/heatclient/osc/plugin.py @@ -41,13 +41,15 @@ def make_client(instance): if instance.session: kwargs.update({'session': instance.session, 'service_type': API_NAME}) + elif instance.auth_plugin_name == 'token_endpoint': + kwargs.update({'endpoint': instance.auth.url, + 'token': instance.auth.token}) else: endpoint = instance.get_endpoint_for_service_type( API_NAME, region_name=instance.region_name, interface=instance.interface, ) - kwargs.update({'endpoint': endpoint, 'auth_url': instance.auth.auth_url, 'username': instance.auth_ref.username, From 597e98fec079c839831036495950f5cba2778d1d Mon Sep 17 00:00:00 2001 From: ricolin Date: Sat, 23 Sep 2017 02:13:40 +0800 Subject: [PATCH 094/246] Load files from adopt file when adopt stack Add files from adopt file into create fields. This allow us to adopt nested stack which the template defined in files. Story: #1700744 Task: #17263 Change-Id: I2b473791186949d49be59ee790185b3e394c7000 --- heatclient/osc/v1/stack.py | 7 +++- .../tests/test_templates/adopt_with_file.json | 32 +++++++++++++++ heatclient/tests/unit/osc/v1/test_stack.py | 40 ++++++++++++++++--- heatclient/v1/shell.py | 6 ++- 4 files changed, 76 insertions(+), 9 deletions(-) create mode 100644 heatclient/tests/test_templates/adopt_with_file.json diff --git a/heatclient/osc/v1/stack.py b/heatclient/osc/v1/stack.py index 3817bea0..bdf7f5f0 100644 --- a/heatclient/osc/v1/stack.py +++ b/heatclient/osc/v1/stack.py @@ -22,6 +22,7 @@ from oslo_serialization import jsonutils import six from six.moves.urllib import request +import yaml from heatclient._i18n import _ from heatclient.common import event_utils @@ -822,13 +823,15 @@ def take_action(self, parsed_args): adopt_url = heat_utils.normalise_file_path_to_url( parsed_args.adopt_file) adopt_data = request.urlopen(adopt_url).read().decode('utf-8') - + yaml_adopt_data = yaml.safe_load(adopt_data) or {} + files = yaml_adopt_data.get('files', {}) + files.update(env_files) fields = { 'stack_name': parsed_args.name, 'disable_rollback': not parsed_args.enable_rollback, 'adopt_stack_data': adopt_data, 'parameters': heat_utils.format_parameters(parsed_args.parameter), - 'files': dict(list(env_files.items())), + 'files': files, 'environment': env, 'timeout': parsed_args.timeout } diff --git a/heatclient/tests/test_templates/adopt_with_file.json b/heatclient/tests/test_templates/adopt_with_file.json new file mode 100644 index 00000000..15190432 --- /dev/null +++ b/heatclient/tests/test_templates/adopt_with_file.json @@ -0,0 +1,32 @@ +{ + "files": { + "file://empty.yaml": "{\"heat_template_version\": \"2015-10-15\"}" + }, + "status": "COMPLETE", + "name": "test", + "tags": null, + "stack_user_project_id": "8f51847805ee4994a1ac1d6d1a9bcd9b", + "environment": { + "event_sinks": [], + "parameter_defaults": {}, + "parameters": {}, + "resource_registry": { + "file://empty.yaml": "file://empty.yaml", + "resources": {} + } + }, + "template": { + "heat_template_version": "2015-04-30", + "resources": { + "empty_child": { + "type": "file://empty.yaml" + } + } + }, + "action": "CREATE", + "project_id": "02228cd21ae24ed7966768a9a41be507", + "id": "25b97c3b-2e9a-4222-bc30-da116ae74a08", + "resources": { + "empty_child": {} + } +} diff --git a/heatclient/tests/unit/osc/v1/test_stack.py b/heatclient/tests/unit/osc/v1/test_stack.py index bcc9f69d..146c1bc2 100644 --- a/heatclient/tests/unit/osc/v1/test_stack.py +++ b/heatclient/tests/unit/osc/v1/test_stack.py @@ -730,9 +730,12 @@ def test_stack_delete_prompt_no(self, mock_stdin): class TestStackAdopt(TestStack): adopt_file = 'heatclient/tests/test_templates/adopt.json' + adopt_with_files = ('heatclient/tests/test_templates/adopt_with_file.json') with open(adopt_file, 'r') as f: adopt_data = f.read() + with open(adopt_with_files, 'r') as f: + adopt_with_files_data = f.read() defaults = { 'stack_name': 'my_stack', @@ -744,6 +747,18 @@ class TestStackAdopt(TestStack): 'timeout': None } + child_stack_yaml = "{\"heat_template_version\": \"2015-10-15\"}" + + expected_with_files = { + 'stack_name': 'my_stack', + 'disable_rollback': True, + 'adopt_stack_data': adopt_with_files_data, + 'parameters': {}, + 'files': {'file://empty.yaml': child_stack_yaml}, + 'environment': {}, + 'timeout': None + } + def setUp(self): super(TestStackAdopt, self).setUp() self.cmd = stack.AdoptStack(self.app, None) @@ -776,8 +791,8 @@ def test_stack_adopt_enable_rollback(self): 'Stack my_stack ADOPT_COMPLETE')) def test_stack_adopt_wait(self, mock_poll): arglist = ['my_stack', '--adopt-file', self.adopt_file, '--wait'] - self.stack_client.get.return_value = \ - stacks.Stack(None, {'stack_status': 'ADOPT_COMPLETE'}) + self.stack_client.get.return_value = stacks.Stack( + None, {'stack_status': 'ADOPT_COMPLETE'}) parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) @@ -791,12 +806,25 @@ def test_stack_adopt_wait(self, mock_poll): 'Stack my_stack ADOPT_FAILED')) def test_stack_adopt_wait_fail(self, mock_poll): arglist = ['my_stack', '--adopt-file', self.adopt_file, '--wait'] - self.stack_client.get.return_value = \ - stacks.Stack(None, {'stack_status': 'ADOPT_FAILED'}) + self.stack_client.get.return_value = stacks.Stack( + None, {'stack_status': 'ADOPT_FAILED'}) parsed_args = self.check_parser(self.cmd, arglist, []) self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) + def test_stack_adopt_with_adopt_files(self): + # Make sure when we adopt with files in adopt script, we will load + # those files as part of input when calling adopt. + arglist = ['my_stack', '--adopt-file', self.adopt_with_files] + cols = ['id', 'stack_name', 'description', 'creation_time', + 'updated_time', 'stack_status', 'stack_status_reason'] + parsed_args = self.check_parser(self.cmd, arglist, []) + + columns, data = self.cmd.take_action(parsed_args) + + self.stack_client.create.assert_called_with(**self.expected_with_files) + self.assertEqual(cols, columns) + class TestStackExport(TestStack): @@ -1225,8 +1253,8 @@ def setUp(self): self.mock_client.actions.cancel_update, 'ROLLBACK' ) - self.mock_client.stacks.get.return_value = \ - self.stack_update_in_progress + self.mock_client.stacks.get.return_value = ( + self.stack_update_in_progress) def test_stack_cancel(self): self._test_stack_action(2) diff --git a/heatclient/v1/shell.py b/heatclient/v1/shell.py index e5bf19c9..66d96512 100644 --- a/heatclient/v1/shell.py +++ b/heatclient/v1/shell.py @@ -185,6 +185,10 @@ def do_stack_adopt(hc, args): adopt_url = utils.normalise_file_path_to_url(args.adopt_file) adopt_data = request.urlopen(adopt_url).read() + yaml_adopt_data = yaml.safe_load(adopt_data) or {} + + files = yaml_adopt_data.get('files', {}) + files.update(env_files) if not len(adopt_data): raise exc.CommandError('Invalid adopt-file, no data!') @@ -201,7 +205,7 @@ def do_stack_adopt(hc, args): 'disable_rollback': not(args.enable_rollback), 'adopt_stack_data': adopt_data, 'parameters': utils.format_parameters(args.parameters), - 'files': dict(list(env_files.items())), + 'files': files, 'environment': env } From 09475c2c1d7398b29342fb66747ecce1db7aad29 Mon Sep 17 00:00:00 2001 From: Vu Cong Tuan Date: Tue, 10 Jul 2018 13:41:19 +0700 Subject: [PATCH 095/246] Switch to stestr According to Openstack summit session [1], stestr is maintained project to which all Openstack projects should migrate. Let's switch to stestr as other projects have already moved to it. [1] https://etherpad.openstack.org/p/YVR-python-pti Change-Id: I7079a92f972f5cb21e0d2077dd029701930a3590 --- .gitignore | 2 +- .stestr.conf | 3 +++ .testr.conf | 4 ---- lower-constraints.txt | 3 +-- test-requirements.txt | 2 +- tox.ini | 9 +++++++-- 6 files changed, 13 insertions(+), 10 deletions(-) create mode 100644 .stestr.conf delete mode 100644 .testr.conf diff --git a/.gitignore b/.gitignore index aa83571f..73321c8b 100644 --- a/.gitignore +++ b/.gitignore @@ -16,7 +16,7 @@ doc/build heatclient/versioninfo *.egg-info *.log -.testrepository +.stestr/ # Files created by releasenotes build releasenotes/build diff --git a/.stestr.conf b/.stestr.conf new file mode 100644 index 00000000..eb18d61c --- /dev/null +++ b/.stestr.conf @@ -0,0 +1,3 @@ +[DEFAULT] +test_path=${OS_TEST_PATH:-./heatclient/tests/unit} +top_dir=./ diff --git a/.testr.conf b/.testr.conf deleted file mode 100644 index 6e77f6be..00000000 --- a/.testr.conf +++ /dev/null @@ -1,4 +0,0 @@ -[DEFAULT] -test_command=${PYTHON:-python} -m subunit.run discover -t ./ ${OS_TEST_PATH:-./heatclient/tests/unit} $LISTOPT $IDOPTION -test_id_option=--load-list $IDFILE -test_list_option=--list diff --git a/lower-constraints.txt b/lower-constraints.txt index 02fa291b..8a1c4eee 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -85,10 +85,9 @@ six==1.10.0 snowballstemmer==1.2.1 Sphinx==1.6.2 sphinxcontrib-websupport==1.0.1 -stestr==1.0.0 +stestr==2.0.0 stevedore==1.20.0 tempest==17.1.0 -testrepository==0.0.18 testscenarios==0.4 testtools==2.2.0 traceback2==1.4.0 diff --git a/test-requirements.txt b/test-requirements.txt index 5fb01ef9..39cdce82 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -10,7 +10,7 @@ requests-mock>=1.2.0 # Apache-2.0 mock>=2.0.0 # BSD mox3>=0.20.0 # Apache-2.0 python-openstackclient>=3.12.0 # Apache-2.0 +stestr>=2.0.0 # Apache-2.0 tempest>=17.1.0 # Apache-2.0 -testrepository>=0.0.18 # Apache-2.0/BSD testscenarios>=0.4 # Apache-2.0/BSD testtools>=2.2.0 # MIT diff --git a/tox.ini b/tox.ini index 42d0e30f..4eb4ca8b 100644 --- a/tox.ini +++ b/tox.ini @@ -13,7 +13,7 @@ deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = find . -type f -name "*.py[c|o]" -delete - python setup.py testr --slowest --testr-args='{posargs}' + stestr run --slowest {posargs} whitelist_externals = find [testenv:debug] @@ -45,8 +45,13 @@ passenv = OS_* [testenv:cover] basepython = python3 +setenv = + PYTHON=coverage run --source heatclient --parallel-mode commands = - python setup.py testr --coverage --testr-args='{posargs}' + stestr run '{posargs}' + coverage combine + coverage html -d cover + coverage xml -o cover/coverage.xml coverage report [testenv:docs] From 29093c8a5616941ae8111d22dab0afe3c3b2cd88 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Wed, 8 Aug 2018 09:35:28 +0000 Subject: [PATCH 096/246] Update reno for stable/rocky Change-Id: If443f0176132d0c03db4c0ffa3e59c35c851e8f3 --- releasenotes/source/index.rst | 1 + releasenotes/source/rocky.rst | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 releasenotes/source/rocky.rst diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index 44adadfb..6cf33274 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -7,5 +7,6 @@ Welcome to Python-heatclient releasenotes's documentation! :caption: Contents: unreleased + rocky queens diff --git a/releasenotes/source/rocky.rst b/releasenotes/source/rocky.rst new file mode 100644 index 00000000..40dd517b --- /dev/null +++ b/releasenotes/source/rocky.rst @@ -0,0 +1,6 @@ +=================================== + Rocky Series Release Notes +=================================== + +.. release-notes:: + :branch: stable/rocky From 2b127dd02f39d139abe81e336f84847d1aef4055 Mon Sep 17 00:00:00 2001 From: ZhijunWei Date: Tue, 28 Aug 2018 09:23:34 -0400 Subject: [PATCH 097/246] fix bug link in readme This patch fixes the bug tracker link in the readme. It is set to depend on a job definition change in project-config so we can use this patch to test the new release jobs. Change-Id: Id976a5fc4ba8fede726a80097fde8c0ce3c2019d --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 8adb259e..1a1200cf 100644 --- a/README.rst +++ b/README.rst @@ -37,7 +37,7 @@ provides a Python API (the ``heatclient`` module) and a command-line tool .. _Online Documentation: https://docs.openstack.org/python-heatclient/latest .. _Launchpad project: https://launchpad.net/python-heatclient .. _Blueprints: https://blueprints.launchpad.net/python-heatclient -.. _Bugs: https://bugs.launchpad.net/python-heatclient +.. _Bugs: https://storyboard.openstack.org/#!/project/openstack/python-heatclient .. _Source: https://git.openstack.org/cgit/openstack/python-heatclient .. _How to Contribute: https://docs.openstack.org/infra/manual/developers.html .. _Specs: https://specs.openstack.org/openstack/heat-specs/ From 38e03316b12201e20dca1ed3307853babd6acb8f Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Tue, 28 Aug 2018 17:19:46 -0400 Subject: [PATCH 098/246] import zuul job settings from project-config This is a mechanically generated patch to complete step 1 of moving the zuul job settings out of project-config and into each project repository. Because there will be a separate patch on each branch, the branch specifiers for branch-specific jobs have been removed. Because this patch is generated by a script, there may be some cosmetic changes to the layout of the YAML file(s) as the contents are normalized. See the python3-first goal document for details: https://governance.openstack.org/tc/goals/stein/python3-first.html Change-Id: I357a23343b00362ca7a2a9942d02c31ad73fae2f Story: #2002586 Task: #24298 --- .zuul.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.zuul.yaml b/.zuul.yaml index 57ad3814..1d1b6b80 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -12,6 +12,13 @@ branch_override: default - project: + templates: + - openstack-python-jobs + - openstack-python35-jobs + - check-requirements + - openstackclient-plugin-jobs + - publish-openstack-sphinx-docs + - release-notes-jobs check: jobs: - heatclient-functional @@ -20,3 +27,6 @@ jobs: - heatclient-functional - openstack-tox-lower-constraints + post: + jobs: + - openstack-tox-cover From 74526063b82dd00e92e718f559782552ed87a659 Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Tue, 28 Aug 2018 17:20:01 -0400 Subject: [PATCH 099/246] switch documentation job to new PTI This is a mechanically generated patch to switch the documentation jobs to use the new PTI versions of the jobs as part of the python3-first goal. See the python3-first goal document for details: https://governance.openstack.org/tc/goals/stein/python3-first.html Change-Id: Icaba946c1c82afcfe822b9bdd2866d24cbc3755f Story: #2002586 Task: #24298 --- .zuul.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index 1d1b6b80..910a9ccf 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -17,8 +17,8 @@ - openstack-python35-jobs - check-requirements - openstackclient-plugin-jobs - - publish-openstack-sphinx-docs - - release-notes-jobs + - publish-openstack-docs-pti + - release-notes-jobs-python3 check: jobs: - heatclient-functional From dcab4084c3151e8ec646223e4587a8ab09162edb Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Tue, 28 Aug 2018 17:20:09 -0400 Subject: [PATCH 100/246] add python 3.6 unit test job This is a mechanically generated patch to add a unit test job running under Python 3.6 as part of the python3-first goal. See the python3-first goal document for details: https://governance.openstack.org/tc/goals/stein/python3-first.html Change-Id: I95c8641c42934db5306b35382f1be84da3603645 Story: #2002586 Task: #24298 --- .zuul.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.zuul.yaml b/.zuul.yaml index 910a9ccf..07553506 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -15,6 +15,7 @@ templates: - openstack-python-jobs - openstack-python35-jobs + - openstack-python36-jobs - check-requirements - openstackclient-plugin-jobs - publish-openstack-docs-pti From bd2bfaa514cfc6769b74bd0c213c58bcc1eef6ea Mon Sep 17 00:00:00 2001 From: rabi Date: Mon, 12 Mar 2018 07:52:33 +0530 Subject: [PATCH 101/246] Add files-container option for stack create and update If files-container option is specified: - All template/env files would be fetched by the heat engine relative to the files_container and no local files other than the root template would be sent to server. - Relative path of environment files would be sent in the environment_files list. Also adds the option to template validate. Change-Id: I1a703ab8798a003365be650886bb78be5af472b7 Story: #1755453 Task: 19319 --- heatclient/common/template_utils.py | 62 +++++++++++-------- heatclient/osc/v1/stack.py | 32 ++++++++-- heatclient/osc/v1/template.py | 16 ++++- heatclient/tests/unit/test_template_utils.py | 12 ++++ ...-stack-create-update-324b931f8f474b3d.yaml | 8 +++ 5 files changed, 99 insertions(+), 31 deletions(-) create mode 100644 releasenotes/notes/add-files-container-stack-create-update-324b931f8f474b3d.yaml diff --git a/heatclient/common/template_utils.py b/heatclient/common/template_utils.py index cdb330a1..2c66f2b2 100644 --- a/heatclient/common/template_utils.py +++ b/heatclient/common/template_utils.py @@ -27,7 +27,8 @@ from heatclient import exc -def process_template_path(template_path, object_request=None, existing=False): +def process_template_path(template_path, object_request=None, + existing=False, fetch_child=True): """Read template from template path. Attempt to read template first as a file or url. If that is unsuccessful, @@ -37,17 +38,20 @@ def process_template_path(template_path, object_request=None, existing=False): :param object_request: custom object request function used to get template if local or uri path fails :param existing: if the current stack's template should be used + :param fetch_child: Whether to fetch the child templates :returns: get_file dict and template contents :raises: error.URLError """ try: return get_template_contents(template_file=template_path, - existing=existing) + existing=existing, + fetch_child=fetch_child) except error.URLError as template_file_exc: try: return get_template_contents(template_object=template_path, object_request=object_request, - existing=existing) + existing=existing, + fetch_child=fetch_child) except exc.HTTPNotFound: # The initial exception gives the user better failure context. raise template_file_exc @@ -55,7 +59,8 @@ def process_template_path(template_path, object_request=None, existing=False): def get_template_contents(template_file=None, template_url=None, template_object=None, object_request=None, - files=None, existing=False): + files=None, existing=False, + fetch_child=True): is_object = False # Transform a bare file path to a file:// URL. @@ -93,12 +98,13 @@ def get_template_contents(template_file=None, template_url=None, except ValueError as e: raise exc.CommandError(_('Error parsing template %(url)s %(error)s') % {'url': template_url, 'error': e}) - - tmpl_base_url = utils.base_url_for_url(template_url) if files is None: files = {} - resolve_template_get_files(template, files, tmpl_base_url, is_object, - object_request) + + if fetch_child: + tmpl_base_url = utils.base_url_for_url(template_url) + resolve_template_get_files(template, files, tmpl_base_url, is_object, + object_request) return files, template @@ -212,7 +218,8 @@ def process_multiple_environments_and_files(env_paths=None, template=None, template_url=None, env_path_is_object=None, object_request=None, - env_list_tracker=None): + env_list_tracker=None, + fetch_env_files=True): """Reads one or more environment files. Reads in each specified environment file and returns a dictionary @@ -239,6 +246,7 @@ def process_multiple_environments_and_files(env_paths=None, template=None, :type env_list_tracker: list or None :return: tuple of files dict and a dict of the consolidated environment :rtype: tuple + :param fetch_env_files: fetch env_files or leave it to server """ merged_files = {} merged_env = {} @@ -249,24 +257,28 @@ def process_multiple_environments_and_files(env_paths=None, template=None, if env_paths: for env_path in env_paths: - files, env = process_environment_and_files( - env_path=env_path, - template=template, - template_url=template_url, - env_path_is_object=env_path_is_object, - object_request=object_request, - include_env_in_files=include_env_in_files) - - # 'files' looks like {"filename1": contents, "filename2": contents} - # so a simple update is enough for merging - merged_files.update(files) - - # 'env' can be a deeply nested dictionary, so a simple update is - # not enough - merged_env = deep_update(merged_env, env) + if fetch_env_files: + files, env = process_environment_and_files( + env_path=env_path, + template=template, + template_url=template_url, + env_path_is_object=env_path_is_object, + object_request=object_request, + include_env_in_files=include_env_in_files) + + # 'files' looks like: + # {"filename1": contents, "filename2": contents} + # so a simple update is enough for merging + merged_files.update(files) + + # 'env' can be a deeply nested dictionary, so a simple + # update is not enough + merged_env = deep_update(merged_env, env) + env_url = utils.normalise_file_path_to_url(env_path) + else: + env_url = env_path if env_list_tracker is not None: - env_url = utils.normalise_file_path_to_url(env_path) env_list_tracker.append(env_url) return merged_files, merged_env diff --git a/heatclient/osc/v1/stack.py b/heatclient/osc/v1/stack.py index 3817bea0..318777a9 100644 --- a/heatclient/osc/v1/stack.py +++ b/heatclient/osc/v1/stack.py @@ -46,6 +46,13 @@ def get_parser(self, prog_name): action='append', help=_('Path to the environment. Can be specified multiple times') ) + parser.add_argument( + '-s', '--files-container', + metavar='', + help=_('Swift files container name. Local files other than ' + 'root template would be ignored. If other files are not ' + 'found in swift, heat engine would raise an error.') + ) parser.add_argument( '--timeout', metavar='', @@ -121,13 +128,15 @@ def take_action(self, parsed_args): tpl_files, template = template_utils.process_template_path( parsed_args.template, - object_request=http.authenticated_fetcher(client)) + object_request=http.authenticated_fetcher(client), + fetch_child=parsed_args.files_container is None) env_files_list = [] env_files, env = ( template_utils.process_multiple_environments_and_files( env_paths=parsed_args.environment, - env_list_tracker=env_files_list)) + env_list_tracker=env_files_list, + fetch_env_files=parsed_args.files_container is None)) parameters = heat_utils.format_all_parameters( parsed_args.parameter, @@ -151,6 +160,9 @@ def take_action(self, parsed_args): if env_files_list: fields['environment_files'] = env_files_list + if parsed_args.files_container: + fields['files_container'] = parsed_args.files_container + if parsed_args.tags: fields['tags'] = parsed_args.tags if parsed_args.timeout: @@ -201,6 +213,13 @@ def get_parser(self, prog_name): '-t', '--template', metavar='