From 2b82aedb4d9f346c71b4460f634f49a7f7daafdf Mon Sep 17 00:00:00 2001 From: lvxianguo Date: Wed, 15 Aug 2018 17:57:53 +0800 Subject: [PATCH 001/167] update doc url to new Change-Id: I35e03b74f7ef79a0997dfd931f29b20350537189 --- glanceclient/v2/resource_type_schema.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/glanceclient/v2/resource_type_schema.py b/glanceclient/v2/resource_type_schema.py index 8ad04bfb8..00a071dcf 100644 --- a/glanceclient/v2/resource_type_schema.py +++ b/glanceclient/v2/resource_type_schema.py @@ -25,8 +25,8 @@ "name": { "type": "string", "description": "Resource type names should be aligned with Heat " - "resource types whenever possible: http://docs." - "openstack.org/developer/heat/template_guide/" + "resource types whenever possible: https://docs." + "openstack.org/heat/latest/template_guide/" "openstack.html", "maxLength": 80 From 0018ad6d3d2684ad7fb48dfe32557d4db1aab624 Mon Sep 17 00:00:00 2001 From: Nguyen Hai Date: Mon, 20 Aug 2018 15:35:44 +0900 Subject: [PATCH 002/167] 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: If6a88f851bf2509ae6e452aa27de34cd9a3b5f88 Story: #2002586 Task: #24297 --- .zuul.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.zuul.yaml b/.zuul.yaml index a8f7330b3..251a87b8f 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -122,6 +122,13 @@ USE_PYTHON3: true - project: + templates: + - openstack-python-jobs + - openstack-python35-jobs + - release-notes-jobs + - publish-openstack-sphinx-docs + - check-requirements + - lib-forward-testing check: jobs: - glanceclient-dsvm-functional-v1 @@ -141,3 +148,6 @@ experimental: jobs: - glanceclient-dsvm-functional-py3 + post: + jobs: + - openstack-tox-cover From 99c3fc13c9412e8b4e332ee639b3083ffee7a5d2 Mon Sep 17 00:00:00 2001 From: Nguyen Hai Date: Mon, 20 Aug 2018 15:35:45 +0900 Subject: [PATCH 003/167] 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: I15c2d1da21c05c6530e7c05e8e90c2660a041144 Story: #2002586 Task: #24297 --- .zuul.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index 251a87b8f..79532cb7b 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -125,8 +125,8 @@ templates: - openstack-python-jobs - openstack-python35-jobs - - release-notes-jobs - - publish-openstack-sphinx-docs + - release-notes-jobs-python3 + - publish-openstack-docs-pti - check-requirements - lib-forward-testing check: From 14599028d9c47c259272ff3b1fbaf8c6599dc768 Mon Sep 17 00:00:00 2001 From: Nguyen Hai Date: Mon, 20 Aug 2018 15:35:47 +0900 Subject: [PATCH 004/167] 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: I4530174cfa59bbaef474847276e4bc39c593f670 Story: #2002586 Task: #24297 --- .zuul.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.zuul.yaml b/.zuul.yaml index 79532cb7b..58ac7402d 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -125,6 +125,7 @@ templates: - openstack-python-jobs - openstack-python35-jobs + - openstack-python36-jobs - release-notes-jobs-python3 - publish-openstack-docs-pti - check-requirements From 5fa921a5c717c6168a6501783a7fd36c7b3c5fc8 Mon Sep 17 00:00:00 2001 From: Nguyen Hai Date: Mon, 20 Aug 2018 15:35:48 +0900 Subject: [PATCH 005/167] add lib-forward-testing-python3 test job This is a mechanically generated patch to add a functional test job running under Python 3 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: I6c76d04d28a610d3bf563f9cecffcdcbd432fc52 Story: #2002586 Task: #24297 --- .zuul.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.zuul.yaml b/.zuul.yaml index 58ac7402d..743ee1671 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -130,6 +130,7 @@ - publish-openstack-docs-pti - check-requirements - lib-forward-testing + - lib-forward-testing-python3 check: jobs: - glanceclient-dsvm-functional-v1 From 8fd7e8c664e82d805dc4a12534b3d7e3fcaac606 Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Tue, 21 Aug 2018 22:24:22 -0400 Subject: [PATCH 006/167] Use "multihash" for data download validation When the Glance "multihash" is available on an image, the glanceclient should use it instead of MD5 to validate data downloads. For cases in which the multihash specifies an algorithm not available to the client, an option is added to the image-download command that will allow fallback to the legacy MD5 checksum verification. Change-Id: I4ee6e5071eca08d3bbedceda2acc170e7ed21a6b Closes-bug: #1788323 --- glanceclient/common/utils.py | 20 ++ glanceclient/tests/unit/test_shell.py | 4 +- glanceclient/tests/unit/v2/fixtures.py | 7 +- glanceclient/tests/unit/v2/test_images.py | 243 +++++++++++++++++- glanceclient/tests/unit/v2/test_shell_v2.py | 26 +- glanceclient/v2/images.py | 63 ++++- glanceclient/v2/shell.py | 14 +- ...ownload-verification-596e91bf7b68e7db.yaml | 41 +++ 8 files changed, 394 insertions(+), 24 deletions(-) create mode 100644 releasenotes/notes/multihash-download-verification-596e91bf7b68e7db.yaml diff --git a/glanceclient/common/utils.py b/glanceclient/common/utils.py index dee997878..c92836e3f 100644 --- a/glanceclient/common/utils.py +++ b/glanceclient/common/utils.py @@ -449,6 +449,26 @@ def integrity_iter(iter, checksum): (md5sum, checksum)) +def serious_integrity_iter(iter, hasher, hash_value): + """Check image data integrity using the Glance "multihash". + + :param iter: iterable containing the image data + :param hasher: a hashlib object + :param hash_value: hexdigest of the image data + :raises: IOError if the hashdigest of the data is not hash_value + """ + for chunk in iter: + yield chunk + if isinstance(chunk, six.string_types): + chunk = six.b(chunk) + hasher.update(chunk) + computed = hasher.hexdigest() + if computed != hash_value: + raise IOError(errno.EPIPE, + 'Corrupt image download. Hash was %s expected %s' % + (computed, hash_value)) + + def memoized_property(fn): attr_name = '_lazy_once_' + fn.__name__ diff --git a/glanceclient/tests/unit/test_shell.py b/glanceclient/tests/unit/test_shell.py index 0f15007d8..3027e4604 100644 --- a/glanceclient/tests/unit/test_shell.py +++ b/glanceclient/tests/unit/test_shell.py @@ -965,7 +965,9 @@ def test_v2_download_has_no_stray_output_to_stdout(self): self.requests = self.useFixture(rm_fixture.Fixture()) self.requests.get('http://example.com/v2/images/%s/file' % id, headers=headers, raw=fake) - + self.requests.get('http://example.com/v2/images/%s' % id, + headers={'Content-type': 'application/json'}, + json=image_show_fixture) shell = openstack_shell.OpenStackImagesShell() argstr = ('--os-image-api-version 2 --os-auth-token faketoken ' '--os-image-url http://example.com ' diff --git a/glanceclient/tests/unit/v2/fixtures.py b/glanceclient/tests/unit/v2/fixtures.py index 5a603c0ce..22e1ff781 100644 --- a/glanceclient/tests/unit/v2/fixtures.py +++ b/glanceclient/tests/unit/v2/fixtures.py @@ -14,6 +14,9 @@ # License for the specific language governing permissions and limitations # under the License. +import hashlib + + UUID = "3fc2ba62-9a02-433e-b565-d493ffc69034" image_list_fixture = { @@ -65,7 +68,9 @@ "tags": [], "updated_at": "2015-07-24T12:18:13Z", "virtual_size": "null", - "visibility": "shared" + "visibility": "shared", + "os_hash_algo": "sha384", + "os_hash_value": hashlib.sha384(b'DATA').hexdigest() } image_create_fixture = { diff --git a/glanceclient/tests/unit/v2/test_images.py b/glanceclient/tests/unit/v2/test_images.py index 23cbb43d2..753f3c474 100644 --- a/glanceclient/tests/unit/v2/test_images.py +++ b/glanceclient/tests/unit/v2/test_images.py @@ -14,6 +14,7 @@ # under the License. import errno +import hashlib import mock import testtools @@ -193,7 +194,43 @@ 'A', ), }, - '/v2/images/66fb18d6-db27-11e1-a1eb-080027cbe205/file': { + '/v2/images/5cc4bebc-db27-11e1-a1eb-080027cbe205': { + 'GET': ( + {}, + {}, + ), + }, + '/v2/images/headeronly-db27-11e1-a1eb-080027cbe205/file': { + 'GET': ( + { + 'content-md5': 'wrong' + }, + 'BB', + ), + }, + '/v2/images/headeronly-db27-11e1-a1eb-080027cbe205': { + 'GET': ( + {}, + {}, + ), + }, + '/v2/images/chkonly-db27-11e1-a1eb-080027cbe205/file': { + 'GET': ( + { + 'content-md5': 'wrong' + }, + 'BB', + ), + }, + '/v2/images/chkonly-db27-11e1-a1eb-080027cbe205': { + 'GET': ( + {}, + { + 'checksum': 'wrong', + }, + ), + }, + '/v2/images/multihash-db27-11e1-a1eb-080027cbe205/file': { 'GET': ( { 'content-md5': 'wrong' @@ -201,7 +238,67 @@ 'BB', ), }, - '/v2/images/1b1c6366-dd57-11e1-af0f-02163e68b1d8/file': { + '/v2/images/multihash-db27-11e1-a1eb-080027cbe205': { + 'GET': ( + {}, + { + 'checksum': 'wrong', + 'os_hash_algo': 'md5', + 'os_hash_value': 'junk' + }, + ), + }, + '/v2/images/badalgo-db27-11e1-a1eb-080027cbe205/file': { + 'GET': ( + { + 'content-md5': hashlib.md5(b'BB').hexdigest() + }, + 'BB', + ), + }, + '/v2/images/badalgo-db27-11e1-a1eb-080027cbe205': { + 'GET': ( + {}, + { + 'checksum': hashlib.md5(b'BB').hexdigest(), + 'os_hash_algo': 'not_an_algo', + 'os_hash_value': 'whatever' + }, + ), + }, + '/v2/images/bad-multihash-value-good-checksum/file': { + 'GET': ( + { + 'content-md5': hashlib.md5(b'GOODCHECKSUM').hexdigest() + }, + 'GOODCHECKSUM', + ), + }, + '/v2/images/bad-multihash-value-good-checksum': { + 'GET': ( + {}, + { + 'checksum': hashlib.md5(b'GOODCHECKSUM').hexdigest(), + 'os_hash_algo': 'sha512', + 'os_hash_value': 'badmultihashvalue' + }, + ), + }, + '/v2/images/headeronly-dd57-11e1-af0f-02163e68b1d8/file': { + 'GET': ( + { + 'content-md5': 'defb99e69a9f1f6e06f15006b1f166ae' + }, + 'CCC', + ), + }, + '/v2/images/headeronly-dd57-11e1-af0f-02163e68b1d8': { + 'GET': ( + {}, + {}, + ), + }, + '/v2/images/chkonly-dd57-11e1-af0f-02163e68b1d8/file': { 'GET': ( { 'content-md5': 'defb99e69a9f1f6e06f15006b1f166ae' @@ -209,6 +306,32 @@ 'CCC', ), }, + '/v2/images/chkonly-dd57-11e1-af0f-02163e68b1d8': { + 'GET': ( + {}, + { + 'checksum': 'defb99e69a9f1f6e06f15006b1f166ae', + }, + ), + }, + '/v2/images/multihash-dd57-11e1-af0f-02163e68b1d8/file': { + 'GET': ( + { + 'content-md5': 'defb99e69a9f1f6e06f15006b1f166ae' + }, + 'CCC', + ), + }, + '/v2/images/multihash-dd57-11e1-af0f-02163e68b1d8': { + 'GET': ( + {}, + { + 'checksum': 'defb99e69a9f1f6e06f15006b1f166ae', + 'os_hash_algo': 'sha384', + 'os_hash_value': hashlib.sha384(b'CCC').hexdigest() + }, + ), + }, '/v2/images/87b634c1-f893-33c9-28a9-e5673c99239a/actions/reactivate': { 'POST': ({}, None) }, @@ -846,12 +969,24 @@ def test_data_without_checksum(self): self.assertEqual('A', body) def test_data_with_wrong_checksum(self): - body = self.controller.data('66fb18d6-db27-11e1-a1eb-080027cbe205', + body = self.controller.data('headeronly-db27-11e1-a1eb-080027cbe205', do_checksum=False) body = ''.join([b for b in body]) self.assertEqual('BB', body) + body = self.controller.data('headeronly-db27-11e1-a1eb-080027cbe205') + try: + body = ''.join([b for b in body]) + self.fail('data did not raise an error.') + except IOError as e: + self.assertEqual(errno.EPIPE, e.errno) + msg = 'was 9d3d9048db16a7eee539e93e3618cbe7 expected wrong' + self.assertIn(msg, str(e)) - body = self.controller.data('66fb18d6-db27-11e1-a1eb-080027cbe205') + body = self.controller.data('chkonly-db27-11e1-a1eb-080027cbe205', + do_checksum=False) + body = ''.join([b for b in body]) + self.assertEqual('BB', body) + body = self.controller.data('chkonly-db27-11e1-a1eb-080027cbe205') try: body = ''.join([b for b in body]) self.fail('data did not raise an error.') @@ -860,15 +995,103 @@ def test_data_with_wrong_checksum(self): msg = 'was 9d3d9048db16a7eee539e93e3618cbe7 expected wrong' self.assertIn(msg, str(e)) - def test_data_with_checksum(self): - body = self.controller.data('1b1c6366-dd57-11e1-af0f-02163e68b1d8', + body = self.controller.data('multihash-db27-11e1-a1eb-080027cbe205', + do_checksum=False) + body = ''.join([b for b in body]) + self.assertEqual('BB', body) + body = self.controller.data('multihash-db27-11e1-a1eb-080027cbe205') + try: + body = ''.join([b for b in body]) + self.fail('data did not raise an error.') + except IOError as e: + self.assertEqual(errno.EPIPE, e.errno) + msg = 'was 9d3d9048db16a7eee539e93e3618cbe7 expected junk' + self.assertIn(msg, str(e)) + + body = self.controller.data('badalgo-db27-11e1-a1eb-080027cbe205', do_checksum=False) body = ''.join([b for b in body]) - self.assertEqual('CCC', body) + self.assertEqual('BB', body) + try: + body = self.controller.data('badalgo-db27-11e1-a1eb-080027cbe205') + self.fail('bad os_hash_algo did not raise an error.') + except ValueError as e: + msg = 'unsupported hash type not_an_algo' + self.assertIn(msg, str(e)) + + def test_data_with_checksum(self): + for prefix in ['headeronly', 'chkonly', 'multihash']: + body = self.controller.data(prefix + + '-dd57-11e1-af0f-02163e68b1d8', + do_checksum=False) + body = ''.join([b for b in body]) + self.assertEqual('CCC', body) + + body = self.controller.data(prefix + + '-dd57-11e1-af0f-02163e68b1d8') + body = ''.join([b for b in body]) + self.assertEqual('CCC', body) + + def test_data_with_checksum_and_fallback(self): + # make sure the allow_md5_fallback option does not cause any + # incorrect behavior when fallback is not needed + for prefix in ['headeronly', 'chkonly', 'multihash']: + body = self.controller.data(prefix + + '-dd57-11e1-af0f-02163e68b1d8', + do_checksum=False, + allow_md5_fallback=True) + body = ''.join([b for b in body]) + self.assertEqual('CCC', body) - body = self.controller.data('1b1c6366-dd57-11e1-af0f-02163e68b1d8') + body = self.controller.data(prefix + + '-dd57-11e1-af0f-02163e68b1d8', + allow_md5_fallback=True) + body = ''.join([b for b in body]) + self.assertEqual('CCC', body) + + def test_data_with_bad_hash_algo_and_fallback(self): + # shouldn't matter when do_checksum is False + body = self.controller.data('badalgo-db27-11e1-a1eb-080027cbe205', + do_checksum=False, + allow_md5_fallback=True) + body = ''.join([b for b in body]) + self.assertEqual('BB', body) + + # default value for do_checksum is True + body = self.controller.data('badalgo-db27-11e1-a1eb-080027cbe205', + allow_md5_fallback=True) + body = ''.join([b for b in body]) + self.assertEqual('BB', body) + + def test_neg_data_with_bad_hash_value_and_fallback_enabled(self): + # make sure download fails when good hash_algo but bad hash_value + # even when correct checksum is present regardless of + # allow_md5_fallback setting + body = self.controller.data('bad-multihash-value-good-checksum', + allow_md5_fallback=False) + try: + body = ''.join([b for b in body]) + self.fail('bad os_hash_value did not raise an error.') + except IOError as e: + self.assertEqual(errno.EPIPE, e.errno) + msg = 'expected badmultihashvalue' + self.assertIn(msg, str(e)) + + body = self.controller.data('bad-multihash-value-good-checksum', + allow_md5_fallback=True) + try: + body = ''.join([b for b in body]) + self.fail('bad os_hash_value did not raise an error.') + except IOError as e: + self.assertEqual(errno.EPIPE, e.errno) + msg = 'expected badmultihashvalue' + self.assertIn(msg, str(e)) + + # download should succeed when do_checksum is off, though + body = self.controller.data('bad-multihash-value-good-checksum', + do_checksum=False) body = ''.join([b for b in body]) - self.assertEqual('CCC', body) + self.assertEqual('GOODCHECKSUM', body) def test_image_import(self): uri = 'http://example.com/image.qcow' @@ -883,7 +1106,7 @@ def test_image_import(self): def test_download_no_data(self): resp = utils.FakeResponse(headers={}, status_code=204) self.controller.controller.http_client.get = mock.Mock( - return_value=(resp, None)) + return_value=(resp, {})) self.controller.data('image_id') def test_download_forbidden(self): diff --git a/glanceclient/tests/unit/v2/test_shell_v2.py b/glanceclient/tests/unit/v2/test_shell_v2.py index 6eeca83f3..acf93bf46 100644 --- a/glanceclient/tests/unit/v2/test_shell_v2.py +++ b/glanceclient/tests/unit/v2/test_shell_v2.py @@ -1729,7 +1729,8 @@ def test_image_import_no_print_image(self, mocked_utils_print_image): def test_image_download(self): args = self._make_args( - {'id': 'IMG-01', 'file': 'test', 'progress': True}) + {'id': 'IMG-01', 'file': 'test', 'progress': True, + 'allow_md5_fallback': False}) with mock.patch.object(self.gc.images, 'data') as mocked_data, \ mock.patch.object(utils, '_extract_request_id'): @@ -1737,14 +1738,27 @@ def test_image_download(self): [c for c in 'abcdef']) test_shell.do_image_download(self.gc, args) - mocked_data.assert_called_once_with('IMG-01') + mocked_data.assert_called_once_with('IMG-01', + allow_md5_fallback=False) + + # check that non-default value is being passed correctly + args.allow_md5_fallback = True + with mock.patch.object(self.gc.images, 'data') as mocked_data, \ + mock.patch.object(utils, '_extract_request_id'): + mocked_data.return_value = utils.RequestIdProxy( + [c for c in 'abcdef']) + + test_shell.do_image_download(self.gc, args) + mocked_data.assert_called_once_with('IMG-01', + allow_md5_fallback=True) @mock.patch.object(utils, 'exit') @mock.patch('sys.stdout', autospec=True) def test_image_download_no_file_arg(self, mocked_stdout, mocked_utils_exit): # Indicate that no file name was given as command line argument - args = self._make_args({'id': '1234', 'file': None, 'progress': False}) + args = self._make_args({'id': '1234', 'file': None, 'progress': False, + 'allow_md5_fallback': False}) # Indicate that no file is specified for output redirection mocked_stdout.isatty = lambda: True test_shell.do_image_download(self.gc, args) @@ -1835,7 +1849,8 @@ def test_do_image_delete_deleted(self): def test_do_image_download_with_forbidden_id(self, mocked_print_err, mocked_stdout): args = self._make_args({'id': 'IMG-01', 'file': None, - 'progress': False}) + 'progress': False, + 'allow_md5_fallback': False}) mocked_stdout.isatty = lambda: False with mock.patch.object(self.gc.images, 'data') as mocked_data: mocked_data.side_effect = exc.HTTPForbidden @@ -1852,7 +1867,8 @@ def test_do_image_download_with_forbidden_id(self, mocked_print_err, @mock.patch.object(utils, 'print_err') def test_do_image_download_with_500(self, mocked_print_err, mocked_stdout): args = self._make_args({'id': 'IMG-01', 'file': None, - 'progress': False}) + 'progress': False, + 'allow_md5_fallback': False}) mocked_stdout.isatty = lambda: False with mock.patch.object(self.gc.images, 'data') as mocked_data: mocked_data.side_effect = exc.HTTPInternalServerError diff --git a/glanceclient/v2/images.py b/glanceclient/v2/images.py index be804a23e..09d46b9fd 100644 --- a/glanceclient/v2/images.py +++ b/glanceclient/v2/images.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +import hashlib import json from oslo_utils import encodeutils from requests import codes @@ -197,13 +198,39 @@ def get(self, image_id): return self._get(image_id) @utils.add_req_id_to_object() - def data(self, image_id, do_checksum=True): + def data(self, image_id, do_checksum=True, allow_md5_fallback=False): """Retrieve data of an image. - :param image_id: ID of the image to download. - :param do_checksum: Enable/disable checksum validation. - :returns: An iterable body or None + When do_checksum is enabled, validation proceeds as follows: + + 1. if the image has a 'os_hash_value' property, the algorithm + specified in the image's 'os_hash_algo' property will be used + to validate against the 'os_hash_value' value. If the + specified hash algorithm is not available AND allow_md5_fallback + is True, then continue to step #2 + 2. else if the image has a checksum property, MD5 is used to + validate against the 'checksum' value + 3. else if the download response has a 'content-md5' header, MD5 + is used to validate against the header value + 4. if none of 1-3 obtain, the data is **not validated** (this is + compatible with legacy behavior) + + :param image_id: ID of the image to download + :param do_checksum: Enable/disable checksum validation + :param allow_md5_fallback: + Use the MD5 checksum for validation if the algorithm specified by + the image's 'os_hash_algo' property is not available + :returns: An iterable body or ``None`` """ + if do_checksum: + # doing this first to prevent race condition if image record + # is deleted during the image download + url = '/v2/images/%s' % image_id + resp, image_meta = self.http_client.get(url) + meta_checksum = image_meta.get('checksum', None) + meta_hash_value = image_meta.get('os_hash_value', None) + meta_hash_algo = image_meta.get('os_hash_algo', None) + url = '/v2/images/%s/file' % image_id resp, body = self.http_client.get(url) if resp.status_code == codes.no_content: @@ -212,8 +239,32 @@ def data(self, image_id, do_checksum=True): checksum = resp.headers.get('content-md5', None) content_length = int(resp.headers.get('content-length', 0)) - if do_checksum and checksum is not None: - body = utils.integrity_iter(body, checksum) + check_md5sum = do_checksum + if do_checksum and meta_hash_value is not None: + try: + hasher = hashlib.new(str(meta_hash_algo)) + body = utils.serious_integrity_iter(body, + hasher, + meta_hash_value) + check_md5sum = False + except ValueError as ve: + if (str(ve).startswith('unsupported hash type') and + allow_md5_fallback): + check_md5sum = True + else: + raise + + if do_checksum and check_md5sum: + if meta_checksum is not None: + body = utils.integrity_iter(body, meta_checksum) + elif checksum is not None: + body = utils.integrity_iter(body, checksum) + else: + # NOTE(rosmaita): this preserves legacy behavior to return the + # image data when checksumming is requested but there's no + # 'content-md5' header in the response. Just want to make it + # clear that we're doing this on purpose. + pass return utils.IterableWithLength(body, content_length), resp diff --git a/glanceclient/v2/shell.py b/glanceclient/v2/shell.py index aaa85bb40..54dd78977 100644 --- a/glanceclient/v2/shell.py +++ b/glanceclient/v2/shell.py @@ -490,6 +490,17 @@ def do_stores_info(gc, args): utils.print_dict(stores_info) +@utils.arg('--allow-md5-fallback', action='store_true', + default=utils.env('OS_IMAGE_ALLOW_MD5_FALLBACK', default=False), + help=_('If os_hash_algo and os_hash_value properties are available ' + 'on the image, they will be used to validate the downloaded ' + 'image data. If the indicated secure hash algorithm is not ' + 'available on the client, the download will fail. Use this ' + 'flag to indicate that in such a case the legacy MD5 image ' + 'checksum should be used to validate the downloaded data. ' + 'You can also set the enviroment variable ' + 'OS_IMAGE_ALLOW_MD5_FALLBACK to any value to activate this ' + 'option.')) @utils.arg('--file', metavar='', help=_('Local file to save downloaded image data to. ' 'If this is not specified and there is no redirection ' @@ -506,7 +517,8 @@ def do_image_download(gc, args): utils.exit(msg) try: - body = gc.images.data(args.id) + body = gc.images.data(args.id, + allow_md5_fallback=args.allow_md5_fallback) except (exc.HTTPForbidden, exc.HTTPException) as e: msg = "Unable to download image '%s'. (%s)" % (args.id, e) utils.exit(msg) diff --git a/releasenotes/notes/multihash-download-verification-596e91bf7b68e7db.yaml b/releasenotes/notes/multihash-download-verification-596e91bf7b68e7db.yaml new file mode 100644 index 000000000..f32b4a9ea --- /dev/null +++ b/releasenotes/notes/multihash-download-verification-596e91bf7b68e7db.yaml @@ -0,0 +1,41 @@ +--- +features: + - | + This release adds verification of image data downloads using the Glance + "multihash" feature introduced in the OpenStack Rocky release. When + the ``os_hash_value`` is populated on an image, the glanceclient will + verify this value by computing the hexdigest of the downloaded data + using the algorithm specified by the image's ``os_hash_algo`` property. + + Because the secure hash algorithm specified is determined by the cloud + provider, it is possible that the ``os_hash_algo`` may identify an + algorithm not available in the version of the Python ``hashlib`` library + used by the client. In such a case the download will fail due to an + unsupported hash type. In the event this occurs, a new option, + ``--allow-md5-fallback``, is introduced to the ``image-download`` command. + When present, this option will allow the glanceclient to use the legacy + MD5 checksum to verify the downloaded data if the secure hash algorithm + specified by the ``os_hash_algo`` image property is not supported. + + Note that the fallback is *not* used in the case where the algorithm is + supported but the hexdigest of the downloaded data does not match the + ``os_hash_value``. In that case the download fails regardless of whether + the option is present or not. + + Whether using the ``--allow-md5-fallback`` option is a good idea depends + upon the user's expectations for the verification. MD5 is an insecure + hashing algorithm, so if you are interested in making sure that the + downloaded image data has not been replaced by a datastream carefully + crafted to have the same MD5 checksum, then you should not use the + fallback. If, however, you are using Glance in a trusted environment + and your interest is simply to verify that no bits have flipped during + the data transfer, the MD5 fallback is sufficient for that purpose. That + being said, it is our recommendation that the multihash should be used + whenever possible. +security: + - | + This release of the glanceclient uses the Glance "multihash" feature, + introduced in Rocky, to use a secure hashing algorithm to verify the + integrity of downloaded data. Legacy images without the "multihash" + image properties (``os_hash_algo`` and ``os_hash_value``) are verified + using the MD5 ``checksum`` image property. From 997e91feeac24ac7d98b6862389f8e08660021f7 Mon Sep 17 00:00:00 2001 From: Andreas Jaeger Date: Mon, 17 Sep 2018 19:47:04 +0200 Subject: [PATCH 007/167] Cleanup .zuul.yaml * Sort list of templates alphabetically * Use lower-constraints-jobs template, remove jobs * Use tox-cover template that runs job voting in gate instead of in post queue, remove job Change-Id: Ifdfa5c4b27cdeb1b4671188bf9fc30cb7f721c07 --- .zuul.yaml | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index 743ee1671..e7cdbf792 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -123,24 +123,24 @@ - project: templates: + - check-requirements + - lib-forward-testing + - lib-forward-testing-python3 + - openstack-cover-jobs + - openstack-lower-constraints-jobs - openstack-python-jobs - openstack-python35-jobs - openstack-python36-jobs - - release-notes-jobs-python3 - publish-openstack-docs-pti - - check-requirements - - lib-forward-testing - - lib-forward-testing-python3 + - release-notes-jobs-python3 check: jobs: - glanceclient-dsvm-functional-v1 - glanceclient-dsvm-functional - - openstack-tox-lower-constraints gate: jobs: - glanceclient-dsvm-functional-v1 - glanceclient-dsvm-functional - - openstack-tox-lower-constraints periodic: jobs: - glanceclient-tox-py27-keystone-tips @@ -150,6 +150,3 @@ experimental: jobs: - glanceclient-dsvm-functional-py3 - post: - jobs: - - openstack-tox-cover From 93636d6e25938617c9b1a58c737540c62d7f27c3 Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Tue, 4 Sep 2018 16:48:00 -0400 Subject: [PATCH 008/167] Refactor periodic "tips" jobs Two changes: 1 - declare the abstract base tips jobs 'abstract' 2 - add a branch matcher to where the periodic tips jobs are invoked in the project definition so that they only apply to the master branch Change-Id: If80ead2796c370b9539a0d7dd12bb8d35de8abcf --- .zuul.yaml | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index e7cdbf792..4bccd7c19 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -70,6 +70,7 @@ - job: name: glanceclient-tox-keystone-tips-base parent: tox + abstract: true description: Abstract job for glanceclient vs. keystone required-projects: - name: openstack/keystoneauth @@ -93,6 +94,7 @@ - job: name: glanceclient-tox-oslo-tips-base parent: tox + abstract: true description: Abstract job for glanceclient vs. oslo required-projects: - name: openstack/oslo.i18n @@ -143,10 +145,26 @@ - glanceclient-dsvm-functional periodic: jobs: - - glanceclient-tox-py27-keystone-tips - - glanceclient-tox-py35-keystone-tips - - glanceclient-tox-py27-oslo-tips - - glanceclient-tox-py35-oslo-tips + # NOTE(rosmaita): we only want the "tips" jobs to be run against + # master, hence the 'branches' qualifiers below. Without them, when + # a stable branch is cut, the tests would be run against the stable + # branch as well, which is pointless because these libraries are + # frozen (more or less) in the stable branches. + # + # The "tips" jobs can be removed from the stable branch .zuul.yaml + # files if someone is so inclined, but that would require manual + # maintenance, so we do not do it by default. Another option is + # to define these jobs in the openstack-infra/project-config repo. + # That would make us less agile in adjusting these tests, so we + # aren't doing that either. + - glanceclient-tox-py27-keystone-tips: + branches: master + - glanceclient-tox-py35-keystone-tips: + branches: master + - glanceclient-tox-py27-oslo-tips: + branches: master + - glanceclient-tox-py35-oslo-tips: + branches: master experimental: jobs: - glanceclient-dsvm-functional-py3 From 3f7171dc1445f0647f15a735e5f4b55a986068e6 Mon Sep 17 00:00:00 2001 From: imacdonn Date: Fri, 14 Sep 2018 23:25:11 +0000 Subject: [PATCH 009/167] Embed validation data when adding location Add support for embedding of checksum, os_hash_algo and os_hash_value when adding a location to an image. Depends-On: https://review.openstack.org/597648 Change-Id: Ibbe2f2bb226f52cc6b2ab591913b1797d2b086c0 --- glanceclient/tests/unit/v2/test_shell_v2.py | 23 ++++++++++++------- glanceclient/v2/images.py | 5 +++- glanceclient/v2/shell.py | 16 ++++++++++++- ...idation-data-support-dfb2463914818cd2.yaml | 12 ++++++++++ 4 files changed, 46 insertions(+), 10 deletions(-) create mode 100644 releasenotes/notes/validation-data-support-dfb2463914818cd2.yaml diff --git a/glanceclient/tests/unit/v2/test_shell_v2.py b/glanceclient/tests/unit/v2/test_shell_v2.py index 6eeca83f3..a5b0a9be8 100644 --- a/glanceclient/tests/unit/v2/test_shell_v2.py +++ b/glanceclient/tests/unit/v2/test_shell_v2.py @@ -1428,18 +1428,25 @@ def test_do_explain(self): def test_do_location_add(self): gc = self.gc - loc = {'url': 'http://foo.com/', 'metadata': {'foo': 'bar'}} - args = self._make_args({'id': 'pass', - 'url': loc['url'], - 'metadata': json.dumps(loc['metadata'])}) + loc = {'url': 'http://foo.com/', + 'metadata': {'foo': 'bar'}, + 'validation_data': {'checksum': 'csum', + 'os_hash_algo': 'algo', + 'os_hash_value': 'value'}} + args = {'id': 'pass', + 'url': loc['url'], + 'metadata': json.dumps(loc['metadata']), + 'checksum': 'csum', + 'hash_algo': 'algo', + 'hash_value': 'value'} with mock.patch.object(gc.images, 'add_location') as mocked_addloc: expect_image = {'id': 'pass', 'locations': [loc]} mocked_addloc.return_value = expect_image - test_shell.do_location_add(self.gc, args) - mocked_addloc.assert_called_once_with('pass', - loc['url'], - loc['metadata']) + test_shell.do_location_add(self.gc, self._make_args(args)) + mocked_addloc.assert_called_once_with( + 'pass', loc['url'], loc['metadata'], + validation_data=loc['validation_data']) utils.print_dict.assert_called_once_with(expect_image) def test_do_location_delete(self): diff --git a/glanceclient/v2/images.py b/glanceclient/v2/images.py index be804a23e..296e2cc9f 100644 --- a/glanceclient/v2/images.py +++ b/glanceclient/v2/images.py @@ -381,7 +381,7 @@ def _send_image_update_request(self, image_id, patch_body): data=json.dumps(patch_body)) return (resp, body), resp - def add_location(self, image_id, url, metadata): + def add_location(self, image_id, url, metadata, validation_data=None): """Add a new location entry to an image's list of locations. It is an error to add a URL that is already present in the list of @@ -390,10 +390,13 @@ def add_location(self, image_id, url, metadata): :param image_id: ID of image to which the location is to be added. :param url: URL of the location to add. :param metadata: Metadata associated with the location. + :param validation_data: Validation data for the image. :returns: The updated image """ add_patch = [{'op': 'add', 'path': '/locations/-', 'value': {'url': url, 'metadata': metadata}}] + if validation_data: + add_patch[0]['value']['validation_data'] = validation_data response = self._send_image_update_request(image_id, add_patch) # Get request id from the above update request and pass the same to # following get request diff --git a/glanceclient/v2/shell.py b/glanceclient/v2/shell.py index aaa85bb40..f07e54f75 100644 --- a/glanceclient/v2/shell.py +++ b/glanceclient/v2/shell.py @@ -725,16 +725,30 @@ def do_image_tag_delete(gc, args): @utils.arg('--metadata', metavar='', default='{}', help=_('Metadata associated with the location. ' 'Must be a valid JSON object (default: %(default)s)')) +@utils.arg('--checksum', metavar='', + help=_('md5 checksum of image contents')) +@utils.arg('--hash-algo', metavar='', + help=_('Multihash algorithm')) +@utils.arg('--hash-value', metavar='', + help=_('Multihash value')) @utils.arg('id', metavar='', help=_('ID of image to which the location is to be added.')) def do_location_add(gc, args): """Add a location (and related metadata) to an image.""" + validation_data = {} + if args.checksum: + validation_data['checksum'] = args.checksum + if args.hash_algo: + validation_data['os_hash_algo'] = args.hash_algo + if args.hash_value: + validation_data['os_hash_value'] = args.hash_value try: metadata = json.loads(args.metadata) except ValueError: utils.exit('Metadata is not a valid JSON object.') else: - image = gc.images.add_location(args.id, args.url, metadata) + image = gc.images.add_location(args.id, args.url, metadata, + validation_data=validation_data) utils.print_dict(image) diff --git a/releasenotes/notes/validation-data-support-dfb2463914818cd2.yaml b/releasenotes/notes/validation-data-support-dfb2463914818cd2.yaml new file mode 100644 index 000000000..fdc09d5c7 --- /dev/null +++ b/releasenotes/notes/validation-data-support-dfb2463914818cd2.yaml @@ -0,0 +1,12 @@ +--- +features: + - | + Support for embedding validation data (checksum and multihash) when adding + a location to an image. Requires the Stein release server-side. + + The ``glance.images.add_location()`` method now accepts an optional + argument ``validation_data``, in the form of a dictionary containing + ``checksum``, ``os_hash_algo`` and ``os_hash_value``. + + The ``location-add`` command now accepts optional arguments ``--checksum``, + ``--hash-algo`` and ``--hash-value``. From 1156346dc243dc46bcc7c78a64454ff4bae7ddc5 Mon Sep 17 00:00:00 2001 From: imacdonn Date: Thu, 1 Nov 2018 21:36:11 +0000 Subject: [PATCH 010/167] Don't quote colon in HTTP headers Since the introduction of quoting of header content in https://review.openstack.org/568698, the 'x-image-meta-location' header has been broken, because urllib.quote() is really intended to be applied to only the path section of a URL, but in this case, it gets applied to the entire URL, and catches the colon that separates the scheme from the remainder of the URL. This change adds the colon to the list of characters that should not get quoted. Since a colon can be directly represented in ASCII, this should not invalidate the previous change. Change-Id: I76a1c9a361b6c9f6eb95ae766b8c3bcf2267703a Closes-Bug: #1788942 --- glanceclient/common/http.py | 6 +++++- glanceclient/tests/unit/test_http.py | 8 +++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/glanceclient/common/http.py b/glanceclient/common/http.py index a5fb15371..9ba806f12 100644 --- a/glanceclient/common/http.py +++ b/glanceclient/common/http.py @@ -66,7 +66,11 @@ def encode_headers(headers): for h, v in headers.items(): if v is not None: # if the item is token, do not quote '+' as well. - safe = '=+/' if h in TOKEN_HEADERS else '/' + # NOTE(imacdonn): urlparse.quote() is intended for quoting the + # path part of a URL, but headers like x-image-meta-location + # include an entire URL. We should avoid encoding the colon in + # this case (bug #1788942) + safe = '=+/' if h in TOKEN_HEADERS else '/:' if six.PY2: # incoming items may be unicode, so get them into something # the py2 version of urllib can handle before percent encoding diff --git a/glanceclient/tests/unit/test_http.py b/glanceclient/tests/unit/test_http.py index 6eee005c1..2f72b9a49 100644 --- a/glanceclient/tests/unit/test_http.py +++ b/glanceclient/tests/unit/test_http.py @@ -216,7 +216,11 @@ def test_http_encoding(self): def test_headers_encoding(self): value = u'ni\xf1o' - headers = {"test": value, "none-val": None, "Name": "value"} + fake_location = b'http://web_server:80/images/fake.img' + headers = {"test": value, + "none-val": None, + "Name": "value", + "x-image-meta-location": fake_location} encoded = http.encode_headers(headers) # Bug #1766235: According to RFC 8187, headers must be # encoded as 7-bit ASCII, so expect to see only displayable @@ -225,6 +229,8 @@ def test_headers_encoding(self): self.assertNotIn("none-val", encoded) self.assertNotIn(b"none-val", encoded) self.assertEqual(b"value", encoded[b"Name"]) + # Bug #1788942: Colons in URL should not get percent-encoded + self.assertEqual(fake_location, encoded[b"x-image-meta-location"]) @mock.patch('keystoneauth1.adapter.Adapter.request') def test_http_duplicate_content_type_headers(self, mock_ksarq): From 5fb14f5ebbea044ce63e49f5feec26d4c1c91730 Mon Sep 17 00:00:00 2001 From: Liang Fang Date: Tue, 25 Sep 2018 16:53:40 +0800 Subject: [PATCH 011/167] Show the backend store info When running "glance -v image-list" there's no store info listed, so user cannot know the type of the backend. This patch added an new option "--include-stores" and is to add the store id to the output of "glance image-list --include-stores". The final output may like: +-----+------+-------------+-----+----------+--------+-----+------+ | ID | Name | Disk_format | ... | Size | Status |Owner|Stores| +-----+------+-------------+-----+----------+--------+-----+------+ | xxx | img1 | raw | ... | 10737418 | active | xxx | ceph | | xxx | img2 | raw | ... | 5086345 | active | xxx | file | +-----+------+-------------+-----+----------+--------+-----+------+ Change-Id: If86ef714c3aa03ce43ef29f26892f431f4766560 Co-authored-by: Jack Ding Signed-off-by: Liang Fang --- glanceclient/tests/unit/v2/test_shell_v2.py | 82 +++++++++++++++++++++ glanceclient/v2/shell.py | 10 +++ 2 files changed, 92 insertions(+) diff --git a/glanceclient/tests/unit/v2/test_shell_v2.py b/glanceclient/tests/unit/v2/test_shell_v2.py index acf93bf46..919e6456c 100644 --- a/glanceclient/tests/unit/v2/test_shell_v2.py +++ b/glanceclient/tests/unit/v2/test_shell_v2.py @@ -264,6 +264,7 @@ def test_do_image_list(self): 'sort_dir': ['desc', 'asc'], 'sort': None, 'verbose': False, + 'include_stores': False, 'os_hidden': False } args = self._make_args(input) @@ -286,6 +287,83 @@ def test_do_image_list(self): filters=exp_img_filters) utils.print_list.assert_called_once_with({}, ['ID', 'Name']) + def test_do_image_list_verbose(self): + input = { + 'limit': None, + 'page_size': 18, + 'visibility': True, + 'member_status': 'Fake', + 'owner': 'test', + 'checksum': 'fake_checksum', + 'tag': 'fake tag', + 'properties': [], + 'sort_key': ['name', 'id'], + 'sort_dir': ['desc', 'asc'], + 'sort': None, + 'verbose': True, + 'include_stores': False, + 'os_hidden': False + } + args = self._make_args(input) + with mock.patch.object(self.gc.images, 'list') as mocked_list: + mocked_list.return_value = {} + + test_shell.do_image_list(self.gc, args) + utils.print_list.assert_called_once_with( + {}, ['ID', 'Name', 'Disk_format', 'Container_format', + 'Size', 'Status', 'Owner']) + + def test_do_image_list_with_include_stores_true(self): + input = { + 'limit': None, + 'page_size': 18, + 'visibility': True, + 'member_status': 'Fake', + 'owner': 'test', + 'checksum': 'fake_checksum', + 'tag': 'fake tag', + 'properties': [], + 'sort_key': ['name', 'id'], + 'sort_dir': ['desc', 'asc'], + 'sort': None, + 'verbose': False, + 'include_stores': True, + 'os_hidden': False + } + args = self._make_args(input) + with mock.patch.object(self.gc.images, 'list') as mocked_list: + mocked_list.return_value = {} + + test_shell.do_image_list(self.gc, args) + utils.print_list.assert_called_once_with( + {}, ['ID', 'Name', 'Stores']) + + def test_do_image_list_verbose_with_include_stores_true(self): + input = { + 'limit': None, + 'page_size': 18, + 'visibility': True, + 'member_status': 'Fake', + 'owner': 'test', + 'checksum': 'fake_checksum', + 'tag': 'fake tag', + 'properties': [], + 'sort_key': ['name', 'id'], + 'sort_dir': ['desc', 'asc'], + 'sort': None, + 'verbose': True, + 'include_stores': True, + 'os_hidden': False + } + args = self._make_args(input) + with mock.patch.object(self.gc.images, 'list') as mocked_list: + mocked_list.return_value = {} + + test_shell.do_image_list(self.gc, args) + utils.print_list.assert_called_once_with( + {}, ['ID', 'Name', 'Disk_format', 'Container_format', + 'Size', 'Status', 'Owner', 'Stores']) + def test_do_image_list_with_hidden_true(self): input = { 'limit': None, @@ -300,6 +378,7 @@ def test_do_image_list_with_hidden_true(self): 'sort_dir': ['desc', 'asc'], 'sort': None, 'verbose': False, + 'include_stores': False, 'os_hidden': True } args = self._make_args(input) @@ -336,6 +415,7 @@ def test_do_image_list_with_single_sort_key(self): 'sort_dir': ['desc'], 'sort': None, 'verbose': False, + 'include_stores': False, 'os_hidden': False } args = self._make_args(input) @@ -372,6 +452,7 @@ def test_do_image_list_new_sorting_syntax(self): 'sort_key': [], 'sort_dir': [], 'verbose': False, + 'include_stores': False, 'os_hidden': False } args = self._make_args(input) @@ -408,6 +489,7 @@ def test_do_image_list_with_property_filter(self): 'sort_dir': ['desc'], 'sort': None, 'verbose': False, + 'include_stores': False, 'os_hidden': False } args = self._make_args(input) diff --git a/glanceclient/v2/shell.py b/glanceclient/v2/shell.py index 54dd78977..544416ac4 100644 --- a/glanceclient/v2/shell.py +++ b/glanceclient/v2/shell.py @@ -348,6 +348,13 @@ def do_image_update(gc, args): const=True, nargs='?', help="Filters results by hidden status. Default=None.") +@utils.arg('--include-stores', + metavar='[True|False]', + default=None, + type=strutils.bool_from_string, + const=True, + nargs='?', + help="Print backend store id.") def do_image_list(gc, args): """List images you can access.""" filter_keys = ['visibility', 'member_status', 'owner', 'checksum', 'tag', @@ -384,6 +391,9 @@ def do_image_list(gc, args): columns += ['Disk_format', 'Container_format', 'Size', 'Status', 'Owner'] + if args.include_stores: + columns += ['Stores'] + images = gc.images.list(**kwargs) utils.print_list(images, columns) From d848d5a8504043ff7ecdf926fdfb5fddd151c1c0 Mon Sep 17 00:00:00 2001 From: qingszhao Date: Thu, 29 Nov 2018 09:30:10 +0000 Subject: [PATCH 012/167] Add Python 3.6 classifier to setup.cfg Change-Id: I96e9aad54e405395d2cfe7a8d30f6ae3bd72efdc --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 1cc748d72..6d3291cd4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -20,6 +20,7 @@ classifier = Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.5 + Programming Language :: Python :: 3.6 [files] packages = From 4aeddfe587f10e5cb4b6e29bf706872817b6d166 Mon Sep 17 00:00:00 2001 From: sunjia Date: Mon, 3 Dec 2018 21:45:29 -0500 Subject: [PATCH 013/167] Change openstack-dev to openstack-discuss Mailinglists have been updated. Openstack-discuss replaces openstack-dev. Change-Id: I0b00d5a692dfcc04b7f8f9fc3ccfaba41c2a84a7 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 1cc748d72..bce744684 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,7 +5,7 @@ description-file = README.rst license = Apache License, Version 2.0 author = OpenStack -author-email = openstack-dev@lists.openstack.org +author-email = openstack-discuss@lists.openstack.org home-page = https://docs.openstack.org/python-glanceclient/latest/ classifier = Development Status :: 5 - Production/Stable From 6a4b68bddc671807727416e92e745e3cc05c6b3a Mon Sep 17 00:00:00 2001 From: 98k <18552437190@163.com> Date: Wed, 9 Jan 2019 18:02:09 +0000 Subject: [PATCH 014/167] Add upper-constraints.txt to releasenotes tox environment Without these dependencies, the releasenotes build does not actually work. Change-Id: I76285c90efec07edc25b22a846379e6d600fa036 --- tox.ini | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 85f5f3ab2..ff2139e50 100644 --- a/tox.ini +++ b/tox.ini @@ -69,7 +69,9 @@ commands = [testenv:releasenotes] basepython = python3 -deps = -r{toxinidir}/doc/requirements.txt +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 4511a445d010345c2bbd41bec303fe6df6312f3c Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Tue, 21 Aug 2018 15:08:56 -0400 Subject: [PATCH 015/167] Add image-list filter for multihash This was missed when multihash support was added to the glanceclient. The os_hash_value is an indexed field in the API. Includes a release note. Closes-bug: #1788271 Change-Id: Ibfe28b8c644967b7e0295dfd3f55c3ae1b0cbb2d --- glanceclient/tests/unit/v2/test_images.py | 60 +++++++++++++++++++ glanceclient/tests/unit/v2/test_shell_v2.py | 8 +++ glanceclient/v2/shell.py | 5 +- .../multihash-filter-ef2a48dc48fae9dc.yaml | 13 ++++ 4 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/multihash-filter-ef2a48dc48fae9dc.yaml diff --git a/glanceclient/tests/unit/v2/test_images.py b/glanceclient/tests/unit/v2/test_images.py index 753f3c474..99926dee1 100644 --- a/glanceclient/tests/unit/v2/test_images.py +++ b/glanceclient/tests/unit/v2/test_images.py @@ -26,6 +26,10 @@ _CHKSUM = '93264c3edf5972c9f1cb309543d38a5c' _CHKSUM1 = '54264c3edf5972c9f1cb309453d38a46' +_HASHVAL = '54264c3edf93264c3edf5972c9f1cb309543d38a5c5972c9f1cb309453d38a46' +_HASHVAL1 = 'cb309543d38a5c5972c9f1cb309453d38a4654264c3edf93264c3edf5972c9f1' +_HASHBAD = '93264c3edf597254264c3edf5972c9f1cb309453d38a46c9f1cb309543d38a5c' + _TAG1 = 'power' _TAG2 = '64bit' @@ -457,6 +461,41 @@ {'images': []}, ), }, + '/v2/images?limit=%d&os_hash_value=%s' % (images.DEFAULT_PAGE_SIZE, + _HASHVAL): { + 'GET': ( + {}, + {'images': [ + { + 'id': '3a4560a1-e585-443e-9b39-553b46ec92d1', + 'name': 'image-1', + } + ]}, + ), + }, + '/v2/images?limit=%d&os_hash_value=%s' % (images.DEFAULT_PAGE_SIZE, + _HASHVAL1): { + 'GET': ( + {}, + {'images': [ + { + 'id': '2a4560b2-e585-443e-9b39-553b46ec92d1', + 'name': 'image-1', + }, + { + 'id': '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810', + 'name': 'image-2', + }, + ]}, + ), + }, + '/v2/images?limit=%d&os_hash_value=%s' % (images.DEFAULT_PAGE_SIZE, + _HASHBAD): { + 'GET': ( + {}, + {'images': []}, + ), + }, '/v2/images?limit=%d&tag=%s' % (images.DEFAULT_PAGE_SIZE, _TAG1): { 'GET': ( {}, @@ -754,6 +793,27 @@ def test_list_images_for_wrong_checksum(self): images = self.controller.list(**filters) self.assertEqual(0, len(images)) + def test_list_images_for_hash_single_image(self): + fake_id = '3a4560a1-e585-443e-9b39-553b46ec92d1' + filters = {'filters': {'os_hash_value': _HASHVAL}} + images = self.controller.list(**filters) + self.assertEqual(1, len(images)) + self.assertEqual('%s' % fake_id, images[0].id) + + def test_list_images_for_hash_multiple_images(self): + fake_id1 = '2a4560b2-e585-443e-9b39-553b46ec92d1' + fake_id2 = '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810' + filters = {'filters': {'os_hash_value': _HASHVAL1}} + images = self.controller.list(**filters) + self.assertEqual(2, len(images)) + self.assertEqual('%s' % fake_id1, images[0].id) + self.assertEqual('%s' % fake_id2, images[1].id) + + def test_list_images_for_wrong_hash(self): + filters = {'filters': {'os_hash_value': _HASHBAD}} + images = self.controller.list(**filters) + self.assertEqual(0, len(images)) + def test_list_images_for_bogus_owner(self): filters = {'filters': {'owner': _BOGUS_ID}} images = self.controller.list(**filters) diff --git a/glanceclient/tests/unit/v2/test_shell_v2.py b/glanceclient/tests/unit/v2/test_shell_v2.py index 919e6456c..bafa8e532 100644 --- a/glanceclient/tests/unit/v2/test_shell_v2.py +++ b/glanceclient/tests/unit/v2/test_shell_v2.py @@ -265,6 +265,7 @@ def test_do_image_list(self): 'sort': None, 'verbose': False, 'include_stores': False, + 'os_hash_value': None, 'os_hidden': False } args = self._make_args(input) @@ -302,6 +303,7 @@ def test_do_image_list_verbose(self): 'sort': None, 'verbose': True, 'include_stores': False, + 'os_hash_value': None, 'os_hidden': False } args = self._make_args(input) @@ -328,6 +330,7 @@ def test_do_image_list_with_include_stores_true(self): 'sort': None, 'verbose': False, 'include_stores': True, + 'os_hash_value': None, 'os_hidden': False } args = self._make_args(input) @@ -353,6 +356,7 @@ def test_do_image_list_verbose_with_include_stores_true(self): 'sort': None, 'verbose': True, 'include_stores': True, + 'os_hash_value': None, 'os_hidden': False } args = self._make_args(input) @@ -379,6 +383,7 @@ def test_do_image_list_with_hidden_true(self): 'sort': None, 'verbose': False, 'include_stores': False, + 'os_hash_value': None, 'os_hidden': True } args = self._make_args(input) @@ -416,6 +421,7 @@ def test_do_image_list_with_single_sort_key(self): 'sort': None, 'verbose': False, 'include_stores': False, + 'os_hash_value': None, 'os_hidden': False } args = self._make_args(input) @@ -453,6 +459,7 @@ def test_do_image_list_new_sorting_syntax(self): 'sort_dir': [], 'verbose': False, 'include_stores': False, + 'os_hash_value': None, 'os_hidden': False } args = self._make_args(input) @@ -490,6 +497,7 @@ def test_do_image_list_with_property_filter(self): 'sort': None, 'verbose': False, 'include_stores': False, + 'os_hash_value': None, 'os_hidden': False } args = self._make_args(input) diff --git a/glanceclient/v2/shell.py b/glanceclient/v2/shell.py index 544416ac4..75c3c0d87 100644 --- a/glanceclient/v2/shell.py +++ b/glanceclient/v2/shell.py @@ -327,6 +327,9 @@ def do_image_update(gc, args): action='append', dest='properties', default=[]) @utils.arg('--checksum', metavar='', help=_('Displays images that match the MD5 checksum.')) +@utils.arg('--hash', dest='os_hash_value', default=None, + metavar='', + help=_('Displays images that match the specified hash value.')) @utils.arg('--tag', metavar='', action='append', help=_("Filter images by a user-defined tag.")) @utils.arg('--sort-key', default=[], action='append', @@ -358,7 +361,7 @@ def do_image_update(gc, args): def do_image_list(gc, args): """List images you can access.""" filter_keys = ['visibility', 'member_status', 'owner', 'checksum', 'tag', - 'os_hidden'] + 'os_hidden', 'os_hash_value'] filter_items = [(key, getattr(args, key)) for key in filter_keys] if args.properties: diff --git a/releasenotes/notes/multihash-filter-ef2a48dc48fae9dc.yaml b/releasenotes/notes/multihash-filter-ef2a48dc48fae9dc.yaml new file mode 100644 index 000000000..6bb22aa9d --- /dev/null +++ b/releasenotes/notes/multihash-filter-ef2a48dc48fae9dc.yaml @@ -0,0 +1,13 @@ +--- +features: + - | + For parity with the old ``checksum`` field, this release adds the + ability for CLI users to filter the image list based upon a particular + multihash value using the ``--hash `` option. Issue the + command: + + .. code-block:: none + + glance help image-list + + for more information. From 332cc181696e7f7c685a8a5b52df01f089b4715b Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Thu, 14 Feb 2019 22:40:49 -0500 Subject: [PATCH 016/167] add python 3.7 unit test job This is a mechanically generated patch to add a unit test job running under Python 3.7. See ML discussion here [1] for context. [1] http://lists.openstack.org/pipermail/openstack-dev/2018-October/135626.html Change-Id: I4f764b17155bc6cbaf9f03bd9f295de178ed0272 Story: #2004073 Task: #27415 --- .zuul.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.zuul.yaml b/.zuul.yaml index 4bccd7c19..47426383b 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -133,6 +133,7 @@ - openstack-python-jobs - openstack-python35-jobs - openstack-python36-jobs + - openstack-python37-jobs - publish-openstack-docs-pti - release-notes-jobs-python3 check: From fab575d987170dd7544d4561b285e8a80d8e7774 Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Thu, 28 Feb 2019 14:01:48 -0500 Subject: [PATCH 017/167] Update irrelevant-files for dsvm-functional tests Update the irrelevant files so the expensive dsvm-functional test jobs are not run for documentation, release note, or other minor non-code changes. Change-Id: Ifbac5e891131b3f66d9332a33d818437fa67c0cc --- .zuul.yaml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.zuul.yaml b/.zuul.yaml index 4bccd7c19..198c4438e 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -36,6 +36,15 @@ s-proxy: true # Hardcode glanceclient path so the job can be run on glance patches zuul_work_dir: src/git.openstack.org/openstack/python-glanceclient + irrelevant-files: + - ^doc/.*$ + - ^releasenotes/.*$ + - ^.*\.rst$ + - ^(test-|)requirements.txt$ + - ^lower-constraints.txt$ + - ^setup.cfg$ + - ^tox.ini$ + - ^\.zuul\.yaml$ - job: name: glanceclient-dsvm-functional @@ -66,6 +75,15 @@ s-proxy: true # Hardcode glanceclient path so the job can be run on glance patches zuul_work_dir: src/git.openstack.org/openstack/python-glanceclient + irrelevant-files: + - ^doc/.*$ + - ^releasenotes/.*$ + - ^.*\.rst$ + - ^(test-|)requirements.txt$ + - ^lower-constraints.txt$ + - ^setup.cfg$ + - ^tox.ini$ + - ^\.zuul\.yaml$ - job: name: glanceclient-tox-keystone-tips-base From e3f4858094b423b3926d458021662b9a2f347879 Mon Sep 17 00:00:00 2001 From: "huang.zhiping" Date: Sun, 21 Oct 2018 02:11:13 +0000 Subject: [PATCH 018/167] Update min tox version to 2.0 The commands used by constraints need at least tox 2.0. Update to reflect reality, which should help with local running of constraints targets. Change-Id: Ic477c7af3687535d54d779cce071f3eb1fb5490c Closes-Bug: #1801676 --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 85f5f3ab2..b2b2865a5 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] envlist = py35,py27,pep8 -minversion = 1.6 +minversion = 2.0 skipsdist = True [testenv] From ae4355be01c0b4bcf5b6bdd2c1163b3f6df3e213 Mon Sep 17 00:00:00 2001 From: bhagyashris Date: Thu, 30 Jun 2016 18:34:14 +0530 Subject: [PATCH 019/167] Remove redundant information from error message Currently user get redundant HTTP error code in error message. Removed redundant HTTP error code from the message. For Example: Error message display when user trying to get the non existing image: $ glance image-show f433471a-53a8-4d31-bf8f-f0b6b594dfc Error message: 404 Not Found: No image found with ID f433471a-53a8-4d31-bf8f-f0b6b594dfc (HTTP 404) After this fix: HTTP 404 Not Found: No image found with ID f433471a-53a8-4d31-bf8f-f0b6b594dfc Closes-Bug: #1598714 Change-Id: I33971a2a16416c8538158299325471c2a69dbb3e --- glanceclient/exc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glanceclient/exc.py b/glanceclient/exc.py index c8616c3ef..eee47cacf 100644 --- a/glanceclient/exc.py +++ b/glanceclient/exc.py @@ -52,7 +52,7 @@ def __init__(self, details=None): self.details = details or self.__class__.__name__ def __str__(self): - return "%s (HTTP %s)" % (self.details, self.code) + return "HTTP %s" % (self.details) class HTTPMultipleChoices(HTTPException): From 44a4dbd6ce2642daeaca9f45ac99e2d1b39e805a Mon Sep 17 00:00:00 2001 From: Erno Kuvaja Date: Thu, 7 Mar 2019 17:38:59 +0000 Subject: [PATCH 020/167] Release notes for 2.16.0 Change-Id: Icd4dc29d6492053b90944f5a57435fc29c6147f2 --- .../notes/2.16.0_Release-43ebe06b74a272ba.yaml | 12 ++++++++++++ .../validation-data-support-dfb2463914818cd2.yaml | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/2.16.0_Release-43ebe06b74a272ba.yaml diff --git a/releasenotes/notes/2.16.0_Release-43ebe06b74a272ba.yaml b/releasenotes/notes/2.16.0_Release-43ebe06b74a272ba.yaml new file mode 100644 index 000000000..47b61e776 --- /dev/null +++ b/releasenotes/notes/2.16.0_Release-43ebe06b74a272ba.yaml @@ -0,0 +1,12 @@ +--- +prelude: > + This version of python-glanceclient adds Python 3.6 classifier and gating + on Python 3.7 environment. +fixes: + - | + * Bug 1788271_: Add image-list filter for multihash + * Bug 1598714_: Remove redundant information from error message + + .. _1788271: https://code.launchpad.net/bugs/1788271 + .. _1598714: https://code.launchpad.net/bugs/1598714 + diff --git a/releasenotes/notes/validation-data-support-dfb2463914818cd2.yaml b/releasenotes/notes/validation-data-support-dfb2463914818cd2.yaml index fdc09d5c7..499c1fb0d 100644 --- a/releasenotes/notes/validation-data-support-dfb2463914818cd2.yaml +++ b/releasenotes/notes/validation-data-support-dfb2463914818cd2.yaml @@ -3,7 +3,7 @@ features: - | Support for embedding validation data (checksum and multihash) when adding a location to an image. Requires the Stein release server-side. - + The ``glance.images.add_location()`` method now accepts an optional argument ``validation_data``, in the form of a dictionary containing ``checksum``, ``os_hash_algo`` and ``os_hash_value``. From 60a707e73a9ad52cdacd23933cb3a5ac4d419eef Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Mon, 18 Mar 2019 14:51:02 +0000 Subject: [PATCH 021/167] Update master for stable/stein Add file to the reno documentation build to show release notes for stable/stein. Use pbr instruction to increment the minor version number automatically so that master versions are higher than the versions on stable/stein. Change-Id: Iefe298a0e35fb2d92f3e4a15f32955ebd6e560ca Sem-Ver: feature --- releasenotes/source/index.rst | 1 + releasenotes/source/stein.rst | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 releasenotes/source/stein.rst diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index 97fc0eb0d..a67164dac 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ glanceclient Release Notes :maxdepth: 1 unreleased + stein rocky queens pike diff --git a/releasenotes/source/stein.rst b/releasenotes/source/stein.rst new file mode 100644 index 000000000..efaceb667 --- /dev/null +++ b/releasenotes/source/stein.rst @@ -0,0 +1,6 @@ +=================================== + Stein Series Release Notes +=================================== + +.. release-notes:: + :branch: stable/stein From b8ecb0bc0386fe4b001d6122004c4bb3f6e22a71 Mon Sep 17 00:00:00 2001 From: jacky06 Date: Wed, 6 Mar 2019 22:41:58 +0800 Subject: [PATCH 022/167] Update hacking version Use latest release 1.1.0 and compatible changes w.r.t pep8 Change-Id: Ifc3b96d98c1a7feff187f953d487e12135887fb9 --- glanceclient/common/utils.py | 8 ++++---- glanceclient/tests/unit/v1/test_shell.py | 8 ++++---- glanceclient/tests/unit/v2/test_shell_v2.py | 2 +- glanceclient/v1/shell.py | 8 ++++---- test-requirements.txt | 3 ++- tox.ini | 3 ++- 6 files changed, 17 insertions(+), 15 deletions(-) diff --git a/glanceclient/common/utils.py b/glanceclient/common/utils.py index c92836e3f..bc0c0eb2f 100644 --- a/glanceclient/common/utils.py +++ b/glanceclient/common/utils.py @@ -28,10 +28,10 @@ import six -if os.name == 'nt': - import msvcrt -else: - msvcrt = None +if os.name == 'nt': # noqa + import msvcrt # noqa +else: # noqa + msvcrt = None # noqa from oslo_utils import encodeutils from oslo_utils import strutils diff --git a/glanceclient/tests/unit/v1/test_shell.py b/glanceclient/tests/unit/v1/test_shell.py index 95bbd07c2..a3bd29b4f 100644 --- a/glanceclient/tests/unit/v1/test_shell.py +++ b/glanceclient/tests/unit/v1/test_shell.py @@ -334,8 +334,8 @@ def test_image_create_missing_disk_format(self, __): e = self.assertRaises(exc.CommandError, self.run_command, '--os-image-api-version 1 image-create ' + origin + ' fake_src --container-format bare') - self.assertEqual('error: Must provide --disk-format when using ' - + origin + '.', e.message) + self.assertEqual('error: Must provide --disk-format when using ' + + origin + '.', e.message) @mock.patch('sys.stderr') def test_image_create_missing_container_format(self, __): @@ -536,8 +536,8 @@ def test_image_update_closed_stdin(self): self._do_update() self.assertTrue( - 'data' not in self.collected_args[1] - or self.collected_args[1]['data'] is None + 'data' not in self.collected_args[1] or + self.collected_args[1]['data'] is None ) def test_image_update_opened_stdin(self): diff --git a/glanceclient/tests/unit/v2/test_shell_v2.py b/glanceclient/tests/unit/v2/test_shell_v2.py index b8161e37f..84ef1f02d 100644 --- a/glanceclient/tests/unit/v2/test_shell_v2.py +++ b/glanceclient/tests/unit/v2/test_shell_v2.py @@ -57,7 +57,7 @@ def schema_args(schema_getter, omit=None): return original_schema_args(my_schema_getter, omit) utils.schema_args = schema_args -from glanceclient.v2 import shell as test_shell +from glanceclient.v2 import shell as test_shell # noqa # Return original decorator. utils.schema_args = original_schema_args diff --git a/glanceclient/v1/shell.py b/glanceclient/v1/shell.py index fff74904c..c60ee3fa5 100644 --- a/glanceclient/v1/shell.py +++ b/glanceclient/v1/shell.py @@ -45,11 +45,11 @@ help='Filter images to those that changed since the given time' ', which will include the deleted images.') @utils.arg('--container-format', metavar='', - help='Filter images to those that have this container format. ' - + CONTAINER_FORMATS) + help='Filter images to those that have this container format. ' + + CONTAINER_FORMATS) @utils.arg('--disk-format', metavar='', - help='Filter images to those that have this disk format. ' - + DISK_FORMATS) + help='Filter images to those that have this disk format. ' + + DISK_FORMATS) @utils.arg('--size-min', metavar='', type=int, help='Filter images to those with a size greater than this.') @utils.arg('--size-max', metavar='', type=int, diff --git a/test-requirements.txt b/test-requirements.txt index 042439372..8e8541cac 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,7 +1,8 @@ # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0 + +hacking>=1.1.0,<1.2.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 mock>=2.0.0 # BSD diff --git a/tox.ini b/tox.ini index b2b2865a5..27170de9a 100644 --- a/tox.ini +++ b/tox.ini @@ -74,7 +74,8 @@ commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [flake8] -ignore = F403,F812,F821 +# E731 skipped as assign a lambda expression +ignore = E731,F403,F812,F821 show-source = True exclude = .venv*,.tox,dist,*egg,build,.git,doc,*lib/python*,.update-venv From d589aa257d3ab99a50444ff42f43b3b9d650d089 Mon Sep 17 00:00:00 2001 From: Cyril Roelandt Date: Tue, 9 Apr 2019 18:42:52 +0200 Subject: [PATCH 023/167] HTTPClient: actually set a timeout for requests The 'timeout' attribute was previously left unused. Change-Id: If615c390302425fe5a646b4651ec6f56aa08fd22 Closes-Bug: #1822052 --- glanceclient/common/http.py | 1 + 1 file changed, 1 insertion(+) diff --git a/glanceclient/common/http.py b/glanceclient/common/http.py index 9ba806f12..17d7cc793 100644 --- a/glanceclient/common/http.py +++ b/glanceclient/common/http.py @@ -270,6 +270,7 @@ def _request(self, method, url, **kwargs): conn_url, data=data, headers=headers, + timeout=self.timeout, **kwargs) except requests.exceptions.Timeout as e: message = ("Error communicating with %(url)s: %(e)s" % From bfec0e2083683b70e931cae6c650ce1aaca60489 Mon Sep 17 00:00:00 2001 From: OpenDev Sysadmins Date: Fri, 19 Apr 2019 19:39:30 +0000 Subject: [PATCH 024/167] OpenDev Migration Patch This commit was bulk generated and pushed by the OpenDev sysadmins as a part of the Git hosting and code review systems migration detailed in these mailing list posts: http://lists.openstack.org/pipermail/openstack-discuss/2019-March/003603.html http://lists.openstack.org/pipermail/openstack-discuss/2019-April/004920.html Attempts have been made to correct repository namespaces and hostnames based on simple pattern matching, but it's possible some were updated incorrectly or missed entirely. Please reach out to us via the contact information listed at https://opendev.org/ with any questions you may have. --- .gitreview | 2 +- .zuul.yaml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitreview b/.gitreview index a0e27a797..047ac5645 100644 --- a/.gitreview +++ b/.gitreview @@ -1,4 +1,4 @@ [gerrit] -host=review.openstack.org +host=review.opendev.org port=29418 project=openstack/python-glanceclient.git diff --git a/.zuul.yaml b/.zuul.yaml index 890cde2d2..c19edd283 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -35,7 +35,7 @@ s-object: true s-proxy: true # Hardcode glanceclient path so the job can be run on glance patches - zuul_work_dir: src/git.openstack.org/openstack/python-glanceclient + zuul_work_dir: src/opendev.org/openstack/python-glanceclient irrelevant-files: - ^doc/.*$ - ^releasenotes/.*$ @@ -74,7 +74,7 @@ s-object: true s-proxy: true # Hardcode glanceclient path so the job can be run on glance patches - zuul_work_dir: src/git.openstack.org/openstack/python-glanceclient + zuul_work_dir: src/opendev.org/openstack/python-glanceclient irrelevant-files: - ^doc/.*$ - ^releasenotes/.*$ @@ -173,7 +173,7 @@ # The "tips" jobs can be removed from the stable branch .zuul.yaml # files if someone is so inclined, but that would require manual # maintenance, so we do not do it by default. Another option is - # to define these jobs in the openstack-infra/project-config repo. + # to define these jobs in the openstack/project-config repo. # That would make us less agile in adjusting these tests, so we # aren't doing that either. - glanceclient-tox-py27-keystone-tips: From d61d1dd54655255be8e26b9a7c1e780ecb169ccf Mon Sep 17 00:00:00 2001 From: Abhishek Kekane Date: Mon, 13 May 2019 09:21:19 +0000 Subject: [PATCH 025/167] Rename --backend to --store As a part of vocabulary correction renamed --backend parameter to --store. Modfied corresponding tests as well. bp:multi-store-vocabulary-correction Change-Id: I3dc115f5f0f3c4edcca0209e139aa3d1d816357c --- glanceclient/tests/unit/v2/test_shell_v2.py | 44 ++++++++++----------- glanceclient/v2/shell.py | 42 ++++++++++---------- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/glanceclient/tests/unit/v2/test_shell_v2.py b/glanceclient/tests/unit/v2/test_shell_v2.py index b8161e37f..201fde0d3 100644 --- a/glanceclient/tests/unit/v2/test_shell_v2.py +++ b/glanceclient/tests/unit/v2/test_shell_v2.py @@ -95,7 +95,7 @@ def _make_args(self, args): # dict directly, it throws an AttributeError. class Args(object): def __init__(self, entries): - self.backend = None + self.store = None self.__dict__.update(entries) return Args(args) @@ -782,9 +782,9 @@ def test_do_image_create_with_user_props(self, mock_stdin): @mock.patch('glanceclient.common.utils.exit') @mock.patch('os.access') @mock.patch('sys.stdin', autospec=True) - def test_neg_do_image_create_no_file_and_stdin_with_backend( + def test_neg_do_image_create_no_file_and_stdin_with_store( self, mock_stdin, mock_access, mock_utils_exit): - expected_msg = ('--backend option should only be provided with --file ' + expected_msg = ('--store option should only be provided with --file ' 'option or stdin.') mock_utils_exit.side_effect = self._mock_utils_exit mock_stdin.isatty = lambda: True @@ -792,7 +792,7 @@ def test_neg_do_image_create_no_file_and_stdin_with_backend( args = self._make_args({'name': 'IMG-01', 'property': ['myprop=myval'], 'file': None, - 'backend': 'file1', + 'store': 'file1', 'container_format': 'bare', 'disk_format': 'qcow2'}) @@ -805,16 +805,16 @@ def test_neg_do_image_create_no_file_and_stdin_with_backend( mock_utils_exit.assert_called_once_with(expected_msg) @mock.patch('glanceclient.common.utils.exit') - def test_neg_do_image_create_invalid_backend( + def test_neg_do_image_create_invalid_store( self, mock_utils_exit): - expected_msg = ("Backend 'dummy' is not valid for this cloud. " + expected_msg = ("Store 'dummy' is not valid for this cloud. " "Valid values can be retrieved with stores-info " "command.") mock_utils_exit.side_effect = self._mock_utils_exit args = self._make_args({'name': 'IMG-01', 'property': ['myprop=myval'], 'file': "somefile.txt", - 'backend': 'dummy', + 'store': 'dummy', 'container_format': 'bare', 'disk_format': 'qcow2'}) @@ -873,13 +873,13 @@ def test_neg_image_create_via_import_no_method_with_file_and_stdin( @mock.patch('glanceclient.common.utils.exit') @mock.patch('os.access') @mock.patch('sys.stdin', autospec=True) - def test_neg_image_create_via_import_no_file_and_stdin_with_backend( + def test_neg_image_create_via_import_no_file_and_stdin_with_store( self, mock_stdin, mock_access, mock_utils_exit): - expected_msg = ('--backend option should only be provided with --file ' + expected_msg = ('--store option should only be provided with --file ' 'option or stdin for the glance-direct import method.') my_args = self.base_args.copy() my_args['import_method'] = 'glance-direct' - my_args['backend'] = 'file1' + my_args['store'] = 'file1' args = self._make_args(my_args) mock_stdin.isatty = lambda: True @@ -900,13 +900,13 @@ def test_neg_image_create_via_import_no_file_and_stdin_with_backend( @mock.patch('glanceclient.common.utils.exit') @mock.patch('sys.stdin', autospec=True) - def test_neg_image_create_via_import_no_uri_with_backend( + def test_neg_image_create_via_import_no_uri_with_store( self, mock_stdin, mock_utils_exit): - expected_msg = ('--backend option should only be provided with --uri ' + expected_msg = ('--store option should only be provided with --uri ' 'option for the web-download import method.') my_args = self.base_args.copy() my_args['import_method'] = 'web-download' - my_args['backend'] = 'file1' + my_args['store'] = 'file1' args = self._make_args(my_args) mock_utils_exit.side_effect = self._mock_utils_exit with mock.patch.object(self.gc.images, @@ -925,14 +925,14 @@ def test_neg_image_create_via_import_no_uri_with_backend( @mock.patch('glanceclient.common.utils.exit') @mock.patch('os.access') @mock.patch('sys.stdin', autospec=True) - def test_neg_image_create_via_import_invalid_backend( + def test_neg_image_create_via_import_invalid_store( self, mock_stdin, mock_access, mock_utils_exit): - expected_msg = ("Backend 'dummy' is not valid for this cloud. " + expected_msg = ("Store 'dummy' is not valid for this cloud. " "Valid values can be retrieved with stores-info" " command.") my_args = self.base_args.copy() my_args['import_method'] = 'glance-direct' - my_args['backend'] = 'dummy' + my_args['store'] = 'dummy' args = self._make_args(my_args) mock_stdin.isatty = lambda: True @@ -1576,15 +1576,15 @@ def test_image_upload(self): backend=None) @mock.patch('glanceclient.common.utils.exit') - def test_image_upload_invalid_backend(self, mock_utils_exit): - expected_msg = ("Backend 'dummy' is not valid for this cloud. " + def test_image_upload_invalid_store(self, mock_utils_exit): + expected_msg = ("Store 'dummy' is not valid for this cloud. " "Valid values can be retrieved with stores-info " "command.") mock_utils_exit.side_effect = self._mock_utils_exit args = self._make_args( {'id': 'IMG-01', 'file': 'test', 'size': 1024, 'progress': False, - 'backend': 'dummy'}) + 'store': 'dummy'}) with mock.patch.object(self.gc.images, 'get_stores_info') as mock_stores_info: @@ -1743,15 +1743,15 @@ def test_neg_image_import_image_no_disk_format( mock_utils_exit.assert_called_once_with(expected_msg) @mock.patch('glanceclient.common.utils.exit') - def test_image_import_invalid_backend(self, mock_utils_exit): - expected_msg = ("Backend 'dummy' is not valid for this cloud. " + def test_image_import_invalid_store(self, mock_utils_exit): + expected_msg = ("Store 'dummy' is not valid for this cloud. " "Valid values can be retrieved with stores-info " "command.") mock_utils_exit.side_effect = self._mock_utils_exit args = self._make_args( {'id': 'IMG-01', 'import_method': 'glance-direct', 'uri': None, - 'backend': 'dummy'}) + 'store': 'dummy'}) with mock.patch.object(self.gc.images, 'get') as mocked_get: with mock.patch.object(self.gc.images, diff --git a/glanceclient/v2/shell.py b/glanceclient/v2/shell.py index dec83148f..aeb668db7 100644 --- a/glanceclient/v2/shell.py +++ b/glanceclient/v2/shell.py @@ -71,8 +71,8 @@ def get_image_schema(): 'passed to the client via stdin.')) @utils.arg('--progress', action='store_true', default=False, help=_('Show upload progress bar.')) -@utils.arg('--backend', metavar='', - default=utils.env('OS_IMAGE_BACKEND', default=None), +@utils.arg('--store', metavar='', + default=utils.env('OS_IMAGE_STORE', default=None), help='Backend store to upload image to.') @utils.on_data_require_fields(DATA_FIELDS) def do_image_create(gc, args): @@ -90,12 +90,12 @@ def do_image_create(gc, args): key, value = datum.split('=', 1) fields[key] = value - backend = args.backend + backend = args.store file_name = fields.pop('file', None) using_stdin = not sys.stdin.isatty() - if args.backend and not (file_name or using_stdin): - utils.exit("--backend option should only be provided with --file " + if args.store and not (file_name or using_stdin): + utils.exit("--store option should only be provided with --file " "option or stdin.") if backend: @@ -108,7 +108,7 @@ def do_image_create(gc, args): image = gc.images.create(**fields) try: if utils.get_data_file(args) is not None: - backend = fields.get('backend', None) + backend = fields.get('store', None) args.id = image['id'] args.size = None do_image_upload(gc, args) @@ -147,8 +147,8 @@ def do_image_create(gc, args): 'record if no import-method and no data is supplied')) @utils.arg('--uri', metavar='', default=None, help=_('URI to download the external image.')) -@utils.arg('--backend', metavar='', - default=utils.env('OS_IMAGE_BACKEND', default=None), +@utils.arg('--store', metavar='', + default=utils.env('OS_IMAGE_STORE', default=None), help='Backend store to upload image to.') @utils.on_data_require_fields(DATA_FIELDS) def do_image_create_via_import(gc, args): @@ -198,8 +198,8 @@ def do_image_create_via_import(gc, args): # determine if backend is valid backend = None - if args.backend: - backend = args.backend + if args.store: + backend = args.store _validate_backend(backend, gc) # make sure we have all and only correct inputs for the requested method @@ -209,7 +209,7 @@ def do_image_create_via_import(gc, args): "method.") if args.import_method == 'glance-direct': if backend and not (file_name or using_stdin): - utils.exit("--backend option should only be provided with --file " + utils.exit("--store option should only be provided with --file " "option or stdin for the glance-direct import method.") if args.uri: utils.exit("You cannot specify a --uri with the glance-direct " @@ -225,7 +225,7 @@ def do_image_create_via_import(gc, args): "for the glance-direct import method.") if args.import_method == 'web-download': if backend and not args.uri: - utils.exit("--backend option should only be provided with --uri " + utils.exit("--store option should only be provided with --uri " "option for the web-download import method.") if not args.uri: utils.exit("URI is required for web-download import method. " @@ -267,7 +267,7 @@ def _validate_backend(backend, gc): break if not valid_backend: - utils.exit("Backend '%s' is not valid for this cloud. Valid " + utils.exit("Store '%s' is not valid for this cloud. Valid " "values can be retrieved with stores-info command." % backend) @@ -559,14 +559,14 @@ def do_image_download(gc, args): help=_('Show upload progress bar.')) @utils.arg('id', metavar='', help=_('ID of image to upload data to.')) -@utils.arg('--backend', metavar='', - default=utils.env('OS_IMAGE_BACKEND', default=None), +@utils.arg('--store', metavar='', + default=utils.env('OS_IMAGE_STORE', default=None), help='Backend store to upload image to.') def do_image_upload(gc, args): """Upload data for a specific image.""" backend = None - if args.backend: - backend = args.backend + if args.store: + backend = args.store # determine if backend is valid _validate_backend(backend, gc) @@ -614,14 +614,14 @@ def do_image_stage(gc, args): help=_('URI to download the external image.')) @utils.arg('id', metavar='', help=_('ID of image to import.')) -@utils.arg('--backend', metavar='', - default=utils.env('OS_IMAGE_BACKEND', default=None), +@utils.arg('--store', metavar='', + default=utils.env('OS_IMAGE_STORE', default=None), help='Backend store to upload image to.') def do_image_import(gc, args): """Initiate the image import taskflow.""" backend = None - if args.backend: - backend = args.backend + if args.store: + backend = args.store # determine if backend is valid _validate_backend(backend, gc) From 43e6338748d30f761199782c860a18f9013a843a Mon Sep 17 00:00:00 2001 From: pengyuesheng Date: Tue, 28 May 2019 15:16:11 +0800 Subject: [PATCH 026/167] Update sphinx dependency Sphinx 2.0 no longer works on python 2.7, so we need to start capping it there as well depend on https://review.opendev.org/#/c/657890/ Change-Id: Ic77e0db24ab7d7f068faf3e237beefa8856a710a --- doc/requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index 4faabc1b0..a8778bf1a 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -3,5 +3,6 @@ # process, which may cause wedges in the gate later. 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 +sphinx!=1.6.6,!=1.6.7,>=1.6.2,<2.0.0;python_version=='2.7' # BSD +sphinx!=1.6.6,!=1.6.7,>=1.6.2;python_version>='3.4' # BSD sphinxcontrib-apidoc>=0.2.0 # BSD From 039fa4c045c89d582cf5b1f81d644fc24d3a4818 Mon Sep 17 00:00:00 2001 From: pengyuesheng Date: Mon, 10 Jun 2019 11:12:41 +0800 Subject: [PATCH 027/167] Blacklist sphinx 2.1.0 (autodoc bug) See https://github.com/sphinx-doc/sphinx/issues/6440 for upstream details Depend-On: https://review.opendev.org/#/c/663060/ Change-Id: I8a96eea9d3d8f6d8beae9d86f307f0edd075392c --- doc/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index a8778bf1a..0e2ed7ad9 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -4,5 +4,5 @@ openstackdocstheme>=1.18.1 # Apache-2.0 reno>=2.5.0 # Apache-2.0 sphinx!=1.6.6,!=1.6.7,>=1.6.2,<2.0.0;python_version=='2.7' # BSD -sphinx!=1.6.6,!=1.6.7,>=1.6.2;python_version>='3.4' # BSD +sphinx!=1.6.6,!=1.6.7,!=2.1.0,>=1.6.2;python_version>='3.4' # BSD sphinxcontrib-apidoc>=0.2.0 # BSD From 69ef9d81ced4db4acac225ad81288c9f4d78c77b Mon Sep 17 00:00:00 2001 From: pengyuesheng Date: Wed, 19 Jun 2019 12:22:48 +0800 Subject: [PATCH 028/167] Modify the url of upper_constraints_file Depends-On: http://lists.openstack.org/pipermail/openstack-discuss/2019-May/006478.html Change-Id: I63be5a18a8221f0ca2b1f2a96367288833e4a096 --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index b2b2865a5..6b4b14df9 100644 --- a/tox.ini +++ b/tox.ini @@ -11,7 +11,7 @@ setenv = VIRTUAL_ENV={envdir} OS_STDERR_NOCAPTURE=False deps = - -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} + -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = stestr run --slowest {posargs} From fec518a6a38dbee5dd2e56cdcb78cbc566ba3de1 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Mon, 24 Jun 2019 09:30:02 -0400 Subject: [PATCH 029/167] Add Python 3 Train unit tests This is a mechanically generated patch to ensure unit testing is in place for all of the Tested Runtimes for Train. See the Train python3-updates goal document for details: https://governance.openstack.org/tc/goals/train/python3-updates.html Change-Id: I4f689ee8b9534952ca30db944e547cb7ca74a17f Story: #2005924 Task: #34208 --- .zuul.yaml | 4 +--- setup.cfg | 2 +- tox.ini | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index c19edd283..5a22fd3cb 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -149,9 +149,7 @@ - openstack-cover-jobs - openstack-lower-constraints-jobs - openstack-python-jobs - - openstack-python35-jobs - - openstack-python36-jobs - - openstack-python37-jobs + - openstack-python3-train-jobs - publish-openstack-docs-pti - release-notes-jobs-python3 check: diff --git a/setup.cfg b/setup.cfg index ffe6a5a43..d72221c96 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,8 +19,8 @@ classifier = Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 - Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 [files] packages = diff --git a/tox.ini b/tox.ini index 27170de9a..7e9b04100 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py35,py27,pep8 +envlist = py27,py37,pep8 minversion = 2.0 skipsdist = True From b79429aec6fa3e4fc41378be64a23ac5757b53cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BF=9F=E5=B0=8F=E5=90=9B?= Date: Thu, 6 Jun 2019 10:29:48 +0800 Subject: [PATCH 030/167] Bump openstackdocstheme to 1.20.0 Some options are now automatically configured by the version 1.20: - project - html_last_updated_fmt - latex_engine - latex_elements - version - release. Change-Id: I2577ca452a014402f1377474457095176d422b42 --- doc/requirements.txt | 2 +- doc/source/conf.py | 1 - lower-constraints.txt | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index a8778bf1a..4c5102a30 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1,7 +1,7 @@ # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -openstackdocstheme>=1.18.1 # Apache-2.0 +openstackdocstheme>=1.20.0 # Apache-2.0 reno>=2.5.0 # Apache-2.0 sphinx!=1.6.6,!=1.6.7,>=1.6.2,<2.0.0;python_version=='2.7' # BSD sphinx!=1.6.6,!=1.6.7,>=1.6.2;python_version>='3.4' # BSD diff --git a/doc/source/conf.py b/doc/source/conf.py index 7d181191d..74d659545 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -84,7 +84,6 @@ # Output file base name for HTML help builder. htmlhelp_basename = '%sdoc' % project -html_last_updated_fmt = '%Y-%m-%d %H:%M' # -- Options for man page output ---------------------------------------------- diff --git a/lower-constraints.txt b/lower-constraints.txt index a02f7fbd3..61be70719 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -32,7 +32,7 @@ monotonic==0.6 msgpack-python==0.4.0 netaddr==0.7.18 netifaces==0.10.4 -openstackdocstheme==1.18.1 +openstackdocstheme==1.20.0 ordereddict==1.1 os-client-config==1.28.0 os-testr==1.0.0 From c2465987894a538ca4ed0013623ae7c75c0eb9d2 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 30 Jul 2019 01:07:10 +0530 Subject: [PATCH 031/167] Correcting typo in shell.py - enviroment to environment. There is a typographical error in glanceclient/v2/shell.py. Correcting spelling from enviroment to environment. Change-Id: Ia19a4417cd05d228adc26f9d7c82f4aa3c24488c --- glanceclient/v2/shell.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glanceclient/v2/shell.py b/glanceclient/v2/shell.py index dec83148f..61e5ec3ae 100644 --- a/glanceclient/v2/shell.py +++ b/glanceclient/v2/shell.py @@ -511,7 +511,7 @@ def do_stores_info(gc, args): 'available on the client, the download will fail. Use this ' 'flag to indicate that in such a case the legacy MD5 image ' 'checksum should be used to validate the downloaded data. ' - 'You can also set the enviroment variable ' + 'You can also set the environment variable ' 'OS_IMAGE_ALLOW_MD5_FALLBACK to any value to activate this ' 'option.')) @utils.arg('--file', metavar='', From a56106092e521f811034041a4e2ee6f8f1295be1 Mon Sep 17 00:00:00 2001 From: Lucian Petrut Date: Tue, 30 Jul 2019 13:39:29 +0300 Subject: [PATCH 032/167] Trivial: fix image format typo The Glance V1 image format list contains "VDHX", which is a typo. This change fixes it, using the correct format name, which is "VHDX". Luckily, this seems to be used only as part of a help string. Change-Id: I392f25b3ee0ee9ac6024e1670053191e4bba937a --- glanceclient/v1/shell.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glanceclient/v1/shell.py b/glanceclient/v1/shell.py index c60ee3fa5..8a4d29dca 100644 --- a/glanceclient/v1/shell.py +++ b/glanceclient/v1/shell.py @@ -30,7 +30,7 @@ CONTAINER_FORMATS = ('Acceptable formats: ami, ari, aki, bare, ovf, ova,' 'docker.') -DISK_FORMATS = ('Acceptable formats: ami, ari, aki, vhd, vdhx, vmdk, raw, ' +DISK_FORMATS = ('Acceptable formats: ami, ari, aki, vhd, vhdx, vmdk, raw, ' 'qcow2, vdi, iso, and ploop.') DATA_FIELDS = ('location', 'copy_from', 'file') From 436f797e8db2fc11b7dace7cb7020e4d44a3d2d7 Mon Sep 17 00:00:00 2001 From: Alex Schultz Date: Thu, 1 Aug 2019 14:39:46 -0600 Subject: [PATCH 033/167] Cleanup session object If a session object is not provided to the get_http_client function (as is done via osc), the glance client uses it's own HTTPClient class. This class creates a session but does not properly close it when it is done. This can lead to resource warnings about unclosed sockets. This change adds a __del__() to the HTTPClient class to close out the session correctly. Change-Id: Idccff338fa84c46ca0e429bb533a2a5217205eef Closes-Bug: #1838694 --- glanceclient/common/http.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/glanceclient/common/http.py b/glanceclient/common/http.py index 17d7cc793..78c4bc550 100644 --- a/glanceclient/common/http.py +++ b/glanceclient/common/http.py @@ -183,6 +183,15 @@ def __init__(self, endpoint, **kwargs): self.session.cert = (kwargs.get('cert_file'), kwargs.get('key_file')) + def __del__(self): + if self.session: + try: + self.session.close() + except Exception as e: + LOG.exception(e) + finally: + self.session = None + @staticmethod def parse_endpoint(endpoint): return netutils.urlsplit(endpoint) From e8d554fde75275eb603a67a9c177357faf52b4e3 Mon Sep 17 00:00:00 2001 From: jacky06 Date: Tue, 23 Apr 2019 13:44:23 +0800 Subject: [PATCH 034/167] Replace git.openstack.org URLs with opendev.org URLs Change-Id: Id02ac765028673ecabcb76d3f3014bbb383bc098 --- README.rst | 4 ++-- releasenotes/notes/pike-relnote-2c77b01aa8799f35.yaml | 6 +++--- releasenotes/source/earlier.rst | 4 ++-- tox.ini | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.rst b/README.rst index 291d3260c..cf6f38454 100644 --- a/README.rst +++ b/README.rst @@ -26,7 +26,7 @@ Python bindings to the OpenStack Images API This is a client library for Glance built on the OpenStack Images API. It provides a Python API (the ``glanceclient`` module) and a command-line tool (``glance``). This library fully supports the v1 Images API, while support for the v2 API is in progress. -Development takes place via the usual OpenStack processes as outlined in the `developer guide `_. The master repository is in `Git `_. +Development takes place via the usual OpenStack processes as outlined in the `developer guide `_. The master repository is in `Git `_. See release notes and more at ``_. @@ -45,7 +45,7 @@ See release notes and more at ` Date: Thu, 12 Sep 2019 12:29:04 -0400 Subject: [PATCH 035/167] Add release note for glanceclient 2.17.0 Change-Id: Ib5cc95d4faad4847297c62ed6943dbc357a64234 --- .../2.17.0_Release-c67392be3b428d10.yaml | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 releasenotes/notes/2.17.0_Release-c67392be3b428d10.yaml diff --git a/releasenotes/notes/2.17.0_Release-c67392be3b428d10.yaml b/releasenotes/notes/2.17.0_Release-c67392be3b428d10.yaml new file mode 100644 index 000000000..d52fcc099 --- /dev/null +++ b/releasenotes/notes/2.17.0_Release-c67392be3b428d10.yaml @@ -0,0 +1,35 @@ +--- +prelude: | + This version of python-glanceclient finalizes client-side support for + the Glance multiple stores feature. See the `Multi Store Support + `_ + section of the Glance documentation for more information. + + Support for Glance multiple stores has been available on an EXPERIMENTAL + basis since release 2.12.0. For the Train release, the Image service + has finalized how API users interact with multiple stores. See the + "Upgrade Notes" section of this document for information about changes this + has necessitated in multistore support in the glanceclient. +fixes: + - | + Bug 1822052_: HTTPClient: actually set a timeout for requests + + .. _1822052: https://code.launchpad.net/bugs/1822052 +upgrade: + - | + The following Command Line Interface calls now take a ``--store`` + option: + + * ``glance image-create`` + * ``glance image-create-via-import`` + * ``glance image-upload`` + * ``glance image-import`` + + The value for this option is a store identifier. The list of + available stores may be obtained from the ``glance stores-info`` + command. + + - | + The ``--backend`` option, available on some commands on an experimental + basis since release 2.12.0, is no longer available. Use ``--store`` + instead. From 4767d9de228d09f9a5f85f5b6a12172c1a340108 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Fri, 13 Sep 2019 14:59:30 +0000 Subject: [PATCH 036/167] Update master for stable/train Add file to the reno documentation build to show release notes for stable/train. Use pbr instruction to increment the minor version number automatically so that master versions are higher than the versions on stable/train. Change-Id: I21107acca89fe8666e0981e1d7a6de62331b55f6 Sem-Ver: feature --- releasenotes/source/index.rst | 1 + releasenotes/source/train.rst | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 releasenotes/source/train.rst diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index a67164dac..1567c44c8 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ glanceclient Release Notes :maxdepth: 1 unreleased + train stein rocky queens diff --git a/releasenotes/source/train.rst b/releasenotes/source/train.rst new file mode 100644 index 000000000..7fa1088ac --- /dev/null +++ b/releasenotes/source/train.rst @@ -0,0 +1,6 @@ +=================================== + Train Series Release Notes +=================================== + +.. release-notes:: + :branch: stable/train From 1f9fe0319b20629938baf44ec8acb24522adf509 Mon Sep 17 00:00:00 2001 From: Daniel Bengtsson Date: Fri, 15 Nov 2019 10:18:38 +0100 Subject: [PATCH 037/167] Stop configuring install_command in tox. Currently, we are overriding 'install_command' to use 'pip'. This is considered poor behavior and 'python -m pip' should be used instead: https://snarky.ca/why-you-should-use-python-m-pip/ It turns out that this is the the default value provided by tox: https://tox.readthedocs.io/en/latest/config.html#conf-install_command So we can remove the line and simply use the default value. Change-Id: Idd3d92657a7c17370afc0d0c35cc4666025ab9d6 --- tox.ini | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index b9e681f5e..9961c5113 100644 --- a/tox.ini +++ b/tox.ini @@ -5,9 +5,7 @@ skipsdist = True [testenv] usedevelop = True -install_command = pip install {opts} {packages} -setenv = VIRTUAL_ENV={envdir} - OS_STDOUT_NOCAPTURE=False +setenv = OS_STDOUT_NOCAPTURE=False OS_STDERR_NOCAPTURE=False deps = From a4465dfc2268b2fc36f04a1a7e5455f6f8c92886 Mon Sep 17 00:00:00 2001 From: Ghanshyam Mann Date: Wed, 30 Oct 2019 06:55:09 +0000 Subject: [PATCH 038/167] Drop python 2.7 support and testing OpenStack is dropping the py2.7 support in ussuri cycle. python-glanceclient is ready with python 3 and ok to drop the python 2.7 support. Complete discussion & schedule can be found in - http://lists.openstack.org/pipermail/openstack-discuss/2019-October/010142.html - https://etherpad.openstack.org/p/drop-python2-support Ussuri Communtiy-wide goal - https://review.opendev.org/#/c/691178/ Change-Id: I029d10a2860c5ba371ff9c5df9b7e2c645a7dfbb --- .zuul.yaml | 23 +------------------ .../notes/drop-py-2-7-f10417b8d1dd38fb.yaml | 6 +++++ setup.cfg | 2 -- tox.ini | 2 +- 4 files changed, 8 insertions(+), 25 deletions(-) create mode 100644 releasenotes/notes/drop-py-2-7-f10417b8d1dd38fb.yaml diff --git a/.zuul.yaml b/.zuul.yaml index 5a22fd3cb..9cedac441 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -93,14 +93,6 @@ required-projects: - name: openstack/keystoneauth -- job: - name: glanceclient-tox-py27-keystone-tips - parent: glanceclient-tox-keystone-tips-base - description: | - glanceclient py27 unit tests vs. keystone masters - vars: - tox_envlist: py27 - - job: name: glanceclient-tox-py35-keystone-tips parent: glanceclient-tox-keystone-tips-base @@ -118,14 +110,6 @@ - name: openstack/oslo.i18n - name: openstack/oslo.utils -- job: - name: glanceclient-tox-py27-oslo-tips - parent: glanceclient-tox-oslo-tips-base - description: | - glanceclient py27 unit tests vs. oslo masters - vars: - tox_envlist: py27 - - job: name: glanceclient-tox-py35-oslo-tips parent: glanceclient-tox-oslo-tips-base @@ -148,8 +132,7 @@ - lib-forward-testing-python3 - openstack-cover-jobs - openstack-lower-constraints-jobs - - openstack-python-jobs - - openstack-python3-train-jobs + - openstack-python3-ussuri-jobs - publish-openstack-docs-pti - release-notes-jobs-python3 check: @@ -174,12 +157,8 @@ # to define these jobs in the openstack/project-config repo. # That would make us less agile in adjusting these tests, so we # aren't doing that either. - - glanceclient-tox-py27-keystone-tips: - branches: master - glanceclient-tox-py35-keystone-tips: branches: master - - glanceclient-tox-py27-oslo-tips: - branches: master - glanceclient-tox-py35-oslo-tips: branches: master experimental: diff --git a/releasenotes/notes/drop-py-2-7-f10417b8d1dd38fb.yaml b/releasenotes/notes/drop-py-2-7-f10417b8d1dd38fb.yaml new file mode 100644 index 000000000..8d2d16bc1 --- /dev/null +++ b/releasenotes/notes/drop-py-2-7-f10417b8d1dd38fb.yaml @@ -0,0 +1,6 @@ +--- +upgrade: + - | + Python 2.7 support has been dropped. Last release of python-glanceclient + to support py2.7 is OpenStack Train. The minimum version of Python now + supported by python-glanceclient is Python 3.6. diff --git a/setup.cfg b/setup.cfg index d72221c96..70bd85a96 100644 --- a/setup.cfg +++ b/setup.cfg @@ -16,8 +16,6 @@ classifier = License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python - Programming Language :: Python :: 2 - Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 diff --git a/tox.ini b/tox.ini index 9961c5113..ba64d58b0 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27,py37,pep8 +envlist = py37,pep8 minversion = 2.0 skipsdist = True From 583194fc8563f066941c1c3297e90e525b7ec032 Mon Sep 17 00:00:00 2001 From: Ghanshyam Date: Wed, 22 Jan 2020 10:45:05 -0600 Subject: [PATCH 039/167] Move py35 jobs to latest python3 glanceclient-tox-py35-keystone-tips and glanceclient-tox-py35-oslo-tips jobs run on py35 which should be moved the newer python3 version. Moving them to py3 env so that they can pickup the available python version on running node. Change-Id: I5fadbadf9eee2163ef6d17af2ceb49883151038c --- .zuul.yaml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index 9cedac441..ad82cd0ad 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -94,12 +94,12 @@ - name: openstack/keystoneauth - job: - name: glanceclient-tox-py35-keystone-tips + name: glanceclient-tox-py3-keystone-tips parent: glanceclient-tox-keystone-tips-base description: | - glanceclient py35 unit tests vs. keystone masters + glanceclient py3 unit tests vs. keystone masters vars: - tox_envlist: py35 + tox_envlist: py3 - job: name: glanceclient-tox-oslo-tips-base @@ -111,12 +111,12 @@ - name: openstack/oslo.utils - job: - name: glanceclient-tox-py35-oslo-tips + name: glanceclient-tox-py3-oslo-tips parent: glanceclient-tox-oslo-tips-base description: | - glanceclient py35 unit tests vs. oslo masters + glanceclient py3 unit tests vs. oslo masters vars: - tox_envlist: py35 + tox_envlist: py3 - job: name: glanceclient-dsvm-functional-py3 @@ -157,9 +157,9 @@ # to define these jobs in the openstack/project-config repo. # That would make us less agile in adjusting these tests, so we # aren't doing that either. - - glanceclient-tox-py35-keystone-tips: + - glanceclient-tox-py3-keystone-tips: branches: master - - glanceclient-tox-py35-oslo-tips: + - glanceclient-tox-py3-oslo-tips: branches: master experimental: jobs: From 23fb691dfb2e27f8fe76574899f6ca68e6602b18 Mon Sep 17 00:00:00 2001 From: Abhishek Kekane Date: Wed, 19 Feb 2020 05:25:12 +0000 Subject: [PATCH 040/167] Drop support for tempest-full Even after dropping support for py2, tempest-full job is still running using python2. Removed this job from the set of templates. Change-Id: If37a91a4a362f37f396c161b980a2db20838fe3b --- .zuul.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.zuul.yaml b/.zuul.yaml index 9cedac441..6fc2d4430 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -128,7 +128,6 @@ - project: templates: - check-requirements - - lib-forward-testing - lib-forward-testing-python3 - openstack-cover-jobs - openstack-lower-constraints-jobs From 993406d57d933cd4f789f97b48cf4d7b5b0b9bc4 Mon Sep 17 00:00:00 2001 From: Erno Kuvaja Date: Wed, 19 Feb 2020 09:33:51 +0000 Subject: [PATCH 041/167] Remove v1 tests Change-Id: Ic6cb15670a42d0cea424f58d6a1d85d5471e162e --- .zuul.yaml | 50 -------------------------------------------------- tox.ini | 12 ------------ 2 files changed, 62 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index 6fc2d4430..f5943356b 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -1,51 +1,3 @@ -- job: - name: glanceclient-dsvm-functional-v1 - parent: devstack-tox-functional - description: | - Devstack-based functional tests for glanceclient - against the Image API v1. - - The Image API v1 is removed from glance in Rocky, but - is still supported by glanceclient until the S cycle, - so we test it against glance stable/queens. - - THIS JOB SHOULD BE REMOVED AT THE BEGINNING OF THE S - CYCLE. - override-checkout: stable/queens - required-projects: - - name: openstack/python-glanceclient - override-checkout: master - timeout: 4200 - vars: - tox_envlist: functional-v1 - devstack_localrc: - GLANCE_V1_ENABLED: true - devstack_services: - # turn off ceilometer - ceilometer-acentral: false - ceilometer-acompute: false - ceilometer-alarm-evaluator: false - ceilometer-alarm-notifier: false - ceilometer-anotification: false - ceilometer-api: false - ceilometer-collector: false - # turn on swift - s-account: true - s-container: true - s-object: true - s-proxy: true - # Hardcode glanceclient path so the job can be run on glance patches - zuul_work_dir: src/opendev.org/openstack/python-glanceclient - irrelevant-files: - - ^doc/.*$ - - ^releasenotes/.*$ - - ^.*\.rst$ - - ^(test-|)requirements.txt$ - - ^lower-constraints.txt$ - - ^setup.cfg$ - - ^tox.ini$ - - ^\.zuul\.yaml$ - - job: name: glanceclient-dsvm-functional parent: devstack-tox-functional @@ -136,11 +88,9 @@ - release-notes-jobs-python3 check: jobs: - - glanceclient-dsvm-functional-v1 - glanceclient-dsvm-functional gate: jobs: - - glanceclient-dsvm-functional-v1 - glanceclient-dsvm-functional periodic: jobs: diff --git a/tox.ini b/tox.ini index ba64d58b0..0bc0076f1 100644 --- a/tox.ini +++ b/tox.ini @@ -37,18 +37,6 @@ commands = bash tools/fix_ca_bundle.sh stestr run --slowest {posargs} -[testenv:functional-v1] -# TODO(rosmaita): remove this testenv at the beginning -# of the 'S' cycle -setenv = - OS_TEST_PATH = ./glanceclient/tests/functional/v1 - OS_TESTENV_NAME = {envname} -whitelist_externals = - bash -commands = - bash tools/fix_ca_bundle.sh - stestr run --slowest {posargs} - [testenv:cover] basepython = python3 setenv = From 3ebc72f7af07eb2a5c99cc1da8c1c6b237777b02 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Sat, 22 Feb 2020 00:09:16 +0000 Subject: [PATCH 042/167] setup.cfg: Use better Python 3 hinting Make sure people are not using an incorrect version of Python and don't say we support universal wheels when we clearly do not. A bit of pbr configuration that hasn't been required since pre-1.0 is also dropped. Change-Id: I313df5b36ad908d55ae69421dfc527a67847c970 Signed-off-by: Stephen Finucane --- setup.cfg | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/setup.cfg b/setup.cfg index 70bd85a96..6d2ec6657 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,6 +7,7 @@ license = Apache License, Version 2.0 author = OpenStack author-email = openstack-discuss@lists.openstack.org home-page = https://docs.openstack.org/python-glanceclient/latest/ +python-requires = >=3.6 classifier = Development Status :: 5 - Production/Stable Environment :: Console @@ -24,13 +25,6 @@ classifier = packages = glanceclient -[global] -setup-hooks = - pbr.hooks.setup_hook - [entry_points] console_scripts = glance = glanceclient.shell:main - -[wheel] -universal = 1 From c23d86738fcfd55301471e8da2512642fd66eba3 Mon Sep 17 00:00:00 2001 From: Erno Kuvaja Date: Wed, 18 Dec 2019 10:30:54 +0000 Subject: [PATCH 043/167] Add support for multi-store import This change adds support for providing multiple target stores where image can be imported. Co-authored-by: Erno Kuvaja Co-authored-by: Abhishek Kekane bp: import-multi-stores Change-Id: I8730364263f1afd5d11fd56939851bda73a892bb --- glanceclient/tests/unit/v2/test_shell_v2.py | 170 +++++++++++++++++- glanceclient/v2/images.py | 11 +- glanceclient/v2/shell.py | 102 ++++++++++- .../multi-store-import-45d05a6193ef2c04.yaml | 5 + 4 files changed, 277 insertions(+), 11 deletions(-) create mode 100644 releasenotes/notes/multi-store-import-45d05a6193ef2c04.yaml diff --git a/glanceclient/tests/unit/v2/test_shell_v2.py b/glanceclient/tests/unit/v2/test_shell_v2.py index eb3750039..994993c14 100644 --- a/glanceclient/tests/unit/v2/test_shell_v2.py +++ b/glanceclient/tests/unit/v2/test_shell_v2.py @@ -870,6 +870,90 @@ def test_neg_image_create_via_import_no_method_with_file_and_stdin( pass mock_utils_exit.assert_called_once_with(expected_msg) + @mock.patch('glanceclient.common.utils.exit') + def test_neg_image_create_via_import_stores_all_stores_specified( + self, mock_utils_exit): + expected_msg = ('Only one of --store, --stores and --all-stores can ' + 'be provided') + mock_utils_exit.side_effect = self._mock_utils_exit + my_args = self.base_args.copy() + my_args.update( + {'id': 'IMG-01', 'import_method': 'glance-direct', + 'stores': 'file1,file2', 'os_all_stores': True, + 'file': 'some.mufile', + 'disk_format': 'raw', + 'container_format': 'bare', + }) + args = self._make_args(my_args) + + with mock.patch.object(self.gc.images, + 'get_import_info') as mocked_info: + mocked_info.return_value = self.import_info_response + try: + test_shell.do_image_create_via_import(self.gc, args) + self.fail("utils.exit should have been called") + except SystemExit: + pass + mock_utils_exit.assert_called_once_with(expected_msg) + + @mock.patch('glanceclient.common.utils.exit') + @mock.patch('sys.stdin', autospec=True) + def test_neg_image_create_via_import_stores_without_file( + self, mock_stdin, mock_utils_exit): + expected_msg = ('--stores option should only be provided with --file ' + 'option or stdin for the glance-direct import method.') + mock_utils_exit.side_effect = self._mock_utils_exit + mock_stdin.isatty = lambda: True + my_args = self.base_args.copy() + my_args.update( + {'id': 'IMG-01', 'import_method': 'glance-direct', + 'stores': 'file1,file2', + 'disk_format': 'raw', + 'container_format': 'bare', + }) + args = self._make_args(my_args) + + with mock.patch.object(self.gc.images, + 'get_import_info') as mocked_info: + with mock.patch.object(self.gc.images, + 'get_stores_info') as mocked_stores_info: + mocked_stores_info.return_value = self.stores_info_response + mocked_info.return_value = self.import_info_response + try: + test_shell.do_image_create_via_import(self.gc, args) + self.fail("utils.exit should have been called") + except SystemExit: + pass + mock_utils_exit.assert_called_once_with(expected_msg) + + @mock.patch('glanceclient.common.utils.exit') + @mock.patch('sys.stdin', autospec=True) + def test_neg_image_create_via_import_all_stores_without_file( + self, mock_stdin, mock_utils_exit): + expected_msg = ('--all-stores option should only be provided with ' + '--file option or stdin for the glance-direct import ' + 'method.') + mock_utils_exit.side_effect = self._mock_utils_exit + mock_stdin.isatty = lambda: True + my_args = self.base_args.copy() + my_args.update( + {'id': 'IMG-01', 'import_method': 'glance-direct', + 'os_all_stores': True, + 'disk_format': 'raw', + 'container_format': 'bare', + }) + args = self._make_args(my_args) + + with mock.patch.object(self.gc.images, + 'get_import_info') as mocked_info: + mocked_info.return_value = self.import_info_response + try: + test_shell.do_image_create_via_import(self.gc, args) + self.fail("utils.exit should have been called") + except SystemExit: + pass + mock_utils_exit.assert_called_once_with(expected_msg) + @mock.patch('glanceclient.common.utils.exit') @mock.patch('os.access') @mock.patch('sys.stdin', autospec=True) @@ -1083,6 +1167,60 @@ def test_neg_image_create_via_import_web_download_no_uri( pass mock_utils_exit.assert_called_once_with(expected_msg) + @mock.patch('glanceclient.common.utils.exit') + def test_neg_image_create_via_import_stores_without_uri( + self, mock_utils_exit): + expected_msg = ('--stores option should only be provided with --uri ' + 'option for the web-download import method.') + mock_utils_exit.side_effect = self._mock_utils_exit + my_args = self.base_args.copy() + my_args.update( + {'id': 'IMG-01', 'import_method': 'web-download', + 'stores': 'file1,file2', + 'disk_format': 'raw', + 'container_format': 'bare', + }) + args = self._make_args(my_args) + + with mock.patch.object(self.gc.images, + 'get_import_info') as mocked_info: + with mock.patch.object(self.gc.images, + 'get_stores_info') as mocked_stores_info: + mocked_stores_info.return_value = self.stores_info_response + mocked_info.return_value = self.import_info_response + try: + test_shell.do_image_create_via_import(self.gc, args) + self.fail("utils.exit should have been called") + except SystemExit: + pass + mock_utils_exit.assert_called_once_with(expected_msg) + + @mock.patch('glanceclient.common.utils.exit') + def test_neg_image_create_via_import_all_stores_without_uri( + self, mock_utils_exit): + expected_msg = ('--all-stores option should only be provided with ' + '--uri option for the web-download import ' + 'method.') + mock_utils_exit.side_effect = self._mock_utils_exit + my_args = self.base_args.copy() + my_args.update( + {'id': 'IMG-01', 'import_method': 'web-download', + 'os_all_stores': True, + 'disk_format': 'raw', + 'container_format': 'bare', + }) + args = self._make_args(my_args) + + with mock.patch.object(self.gc.images, + 'get_import_info') as mocked_info: + mocked_info.return_value = self.import_info_response + try: + test_shell.do_image_create_via_import(self.gc, args) + self.fail("utils.exit should have been called") + except SystemExit: + pass + mock_utils_exit.assert_called_once_with(expected_msg) + @mock.patch('glanceclient.common.utils.exit') @mock.patch('sys.stdin', autospec=True) def test_neg_image_create_via_import_web_download_no_uri_with_file( @@ -1785,7 +1923,8 @@ def test_image_import_glance_direct(self): mock_import.return_value = None test_shell.do_image_import(self.gc, args) mock_import.assert_called_once_with( - 'IMG-01', 'glance-direct', None, backend=None) + 'IMG-01', 'glance-direct', None, backend=None, + all_stores=None, allow_failure=True, stores=None) def test_image_import_web_download(self): args = self._make_args( @@ -1803,7 +1942,9 @@ def test_image_import_web_download(self): test_shell.do_image_import(self.gc, args) mock_import.assert_called_once_with( 'IMG-01', 'web-download', - 'http://example.com/image.qcow', backend=None) + 'http://example.com/image.qcow', + all_stores=None, allow_failure=True, + backend=None, stores=None) @mock.patch('glanceclient.common.utils.print_image') def test_image_import_no_print_image(self, mocked_utils_print_image): @@ -1821,9 +1962,32 @@ def test_image_import_no_print_image(self, mocked_utils_print_image): mock_import.return_value = None test_shell.do_image_import(self.gc, args) mock_import.assert_called_once_with( - 'IMG-02', 'glance-direct', None, backend=None) + 'IMG-02', 'glance-direct', None, stores=None, + all_stores=None, allow_failure=True, backend=None) mocked_utils_print_image.assert_not_called() + @mock.patch('glanceclient.common.utils.print_image') + @mock.patch('glanceclient.v2.shell._validate_backend') + def test_image_import_multiple_stores(self, mocked_utils_print_image, + msvb): + args = self._make_args( + {'id': 'IMG-02', 'uri': None, 'import_method': 'glance-direct', + 'from_create': False, 'stores': 'site1,site2'}) + with mock.patch.object(self.gc.images, 'image_import') as mock_import: + with mock.patch.object(self.gc.images, 'get') as mocked_get: + with mock.patch.object(self.gc.images, + 'get_import_info') as mocked_info: + mocked_get.return_value = {'status': 'uploading', + 'container_format': 'bare', + 'disk_format': 'raw'} + mocked_info.return_value = self.import_info_response + mock_import.return_value = None + test_shell.do_image_import(self.gc, args) + mock_import.assert_called_once_with( + 'IMG-02', 'glance-direct', None, all_stores=None, + allow_failure=True, stores=['site1', 'site2'], + backend=None) + def test_image_download(self): args = self._make_args( {'id': 'IMG-01', 'file': 'test', 'progress': True, diff --git a/glanceclient/v2/images.py b/glanceclient/v2/images.py index 5252ee3f7..69163fe8e 100644 --- a/glanceclient/v2/images.py +++ b/glanceclient/v2/images.py @@ -318,13 +318,22 @@ def stage(self, image_id, image_data, image_size=None): @utils.add_req_id_to_object() def image_import(self, image_id, method='glance-direct', uri=None, - backend=None): + backend=None, stores=None, allow_failure=True, + all_stores=None): """Import Image via method.""" headers = {} url = '/v2/images/%s/import' % image_id data = {'method': {'name': method}} + if stores: + data['stores'] = stores + if allow_failure: + data['all_stores_must_succeed'] = 'false' if backend is not None: headers['x-image-meta-store'] = backend + if all_stores: + data['all_stores'] = 'true' + if allow_failure: + data['all_stores_must_succeed'] = 'false' if uri: if method == 'web-download': diff --git a/glanceclient/v2/shell.py b/glanceclient/v2/shell.py index 8b2c13bcf..b3ac56f6e 100644 --- a/glanceclient/v2/shell.py +++ b/glanceclient/v2/shell.py @@ -150,6 +150,28 @@ def do_image_create(gc, args): @utils.arg('--store', metavar='', default=utils.env('OS_IMAGE_STORE', default=None), help='Backend store to upload image to.') +@utils.arg('--stores', metavar='', + default=utils.env('OS_IMAGE_STORES', default=None), + help=_('Stores to upload image to if multi-stores import ' + 'available. Comma separated list. Available stores can be ' + 'listed with "stores-info" call.')) +@utils.arg('--all-stores', type=strutils.bool_from_string, + metavar='[True|False]', + default=None, + dest='os_all_stores', + help=_('"all-stores" can be ued instead of "stores"-list to ' + 'indicate that image should be imported into all available ' + 'stores.')) +@utils.arg('--allow-failure', type=strutils.bool_from_string, + metavar='[True|False]', + dest='os_allow_failure', + default=utils.env('OS_IMAGE_ALLOW_FAILURE', default=True), + help=_('Indicator if all stores listed (or available) must ' + 'succeed. "True" by default meaning that we allow some ' + 'stores to fail and the status can be monitored from the ' + 'image metadata. If this is set to "False" the import will ' + 'be reverted should any of the uploads fail. Only usable ' + 'with "stores" or "all-stores".')) @utils.on_data_require_fields(DATA_FIELDS) def do_image_create_via_import(gc, args): """EXPERIMENTAL: Create a new image via image import. @@ -198,9 +220,21 @@ def do_image_create_via_import(gc, args): # determine if backend is valid backend = None - if args.store: + stores = getattr(args, "stores", None) + all_stores = getattr(args, "os_all_stores", None) + + if (args.store and (stores or all_stores)) or (stores and all_stores): + utils.exit("Only one of --store, --stores and --all-stores can be " + "provided") + elif args.store: backend = args.store + # determine if backend is valid _validate_backend(backend, gc) + elif stores: + stores = str(stores).split(',') + for store in stores: + # determine if backend is valid + _validate_backend(store, gc) # make sure we have all and only correct inputs for the requested method if args.import_method is None: @@ -211,6 +245,14 @@ def do_image_create_via_import(gc, args): if backend and not (file_name or using_stdin): utils.exit("--store option should only be provided with --file " "option or stdin for the glance-direct import method.") + if stores and not (file_name or using_stdin): + utils.exit("--stores option should only be provided with --file " + "option or stdin for the glance-direct import method.") + if all_stores and not (file_name or using_stdin): + utils.exit("--all-stores option should only be provided with " + "--file option or stdin for the glance-direct import " + "method.") + if args.uri: utils.exit("You cannot specify a --uri with the glance-direct " "import method.") @@ -227,6 +269,12 @@ def do_image_create_via_import(gc, args): if backend and not args.uri: utils.exit("--store option should only be provided with --uri " "option for the web-download import method.") + if stores and not args.uri: + utils.exit("--stores option should only be provided with --uri " + "option for the web-download import method.") + if all_stores and not args.uri: + utils.exit("--all-stores option should only be provided with " + "--uri option for the web-download import method.") if not args.uri: utils.exit("URI is required for web-download import method. " "Please use '--uri '.") @@ -246,6 +294,7 @@ def do_image_create_via_import(gc, args): args.size = None do_image_stage(gc, args) args.from_create = True + args.stores = stores do_image_import(gc, args) image = gc.images.get(args.id) finally: @@ -617,19 +666,56 @@ def do_image_stage(gc, args): @utils.arg('--store', metavar='', default=utils.env('OS_IMAGE_STORE', default=None), help='Backend store to upload image to.') +@utils.arg('--stores', metavar='', + default=utils.env('OS_IMAGE_STORES', default=None), + help='Stores to upload image to if multi-stores import available.') +@utils.arg('--all-stores', type=strutils.bool_from_string, + metavar='[True|False]', + default=None, + dest='os_all_stores', + help=_('"all-stores" can be ued instead of "stores"-list to ' + 'indicate that image should be imported all available ' + 'stores.')) +@utils.arg('--allow-failure', type=strutils.bool_from_string, + metavar='[True|False]', + dest='os_allow_failure', + default=utils.env('OS_IMAGE_ALLOW_FAILURE', default=True), + help=_('Indicator if all stores listed (or available) must ' + 'succeed. "True" by default meaning that we allow some ' + 'stores to fail and the status can be monitored from the ' + 'image metadata. If this is set to "False" the import will ' + 'be reverted should any of the uploads fail. Only usable ' + 'with "stores" or "all-stores".')) def do_image_import(gc, args): """Initiate the image import taskflow.""" - backend = None - if args.store: - backend = args.store + backend = getattr(args, "store", None) + stores = getattr(args, "stores", None) + all_stores = getattr(args, "os_all_stores", None) + allow_failure = getattr(args, "os_allow_failure", True) + + if not getattr(args, 'from_create', False): + if (args.store and (stores or all_stores)) or (stores and all_stores): + utils.exit("Only one of --store, --stores and --all-stores can be " + "provided") + elif args.store: + backend = args.store + # determine if backend is valid + _validate_backend(backend, gc) + elif stores: + stores = str(stores).split(',') + # determine if backend is valid - _validate_backend(backend, gc) + if stores: + for store in stores: + _validate_backend(store, gc) if getattr(args, 'from_create', False): # this command is being called "internally" so we can skip # validation -- just do the import and get out of here gc.images.image_import(args.id, args.import_method, args.uri, - backend=backend) + backend=backend, + stores=stores, all_stores=all_stores, + allow_failure=allow_failure) return # do input validation @@ -669,7 +755,9 @@ def do_image_import(gc, args): # finally, do the import gc.images.image_import(args.id, args.import_method, args.uri, - backend=backend) + backend=backend, + stores=stores, all_stores=all_stores, + allow_failure=allow_failure) image = gc.images.get(args.id) utils.print_image(image) diff --git a/releasenotes/notes/multi-store-import-45d05a6193ef2c04.yaml b/releasenotes/notes/multi-store-import-45d05a6193ef2c04.yaml new file mode 100644 index 000000000..8b483e375 --- /dev/null +++ b/releasenotes/notes/multi-store-import-45d05a6193ef2c04.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Adds support for multi-store import where user can import + image into multiple backend stores with single command. From 2e0396c0c9576924703d79429f5c3f14619d8b8d Mon Sep 17 00:00:00 2001 From: Abhishek Kekane Date: Fri, 21 Feb 2020 10:10:42 +0000 Subject: [PATCH 044/167] Add support for copy-image import method This change adds support for copy-image import method which will copy existing images into multiple stores. Change-Id: I748a8fb3dbaf9c2e4d887a2ecd4325e27a8429c4 bp: copy-image-multiple-stores --- glanceclient/tests/unit/v2/test_shell_v2.py | 99 ++++++++++++++++++- glanceclient/v2/shell.py | 12 +++ .../copy-existing-image-619b7e6bc3394446.yaml | 5 + 3 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/copy-existing-image-619b7e6bc3394446.yaml diff --git a/glanceclient/tests/unit/v2/test_shell_v2.py b/glanceclient/tests/unit/v2/test_shell_v2.py index 994993c14..f137d30fa 100644 --- a/glanceclient/tests/unit/v2/test_shell_v2.py +++ b/glanceclient/tests/unit/v2/test_shell_v2.py @@ -842,7 +842,7 @@ def test_neg_do_image_create_invalid_store( import_info_response = {'import-methods': { 'type': 'array', 'description': 'Import methods available.', - 'value': ['glance-direct', 'web-download']}} + 'value': ['glance-direct', 'web-download', 'copy-image']}} def _mock_utils_exit(self, msg): sys.exit(msg) @@ -870,6 +870,27 @@ def test_neg_image_create_via_import_no_method_with_file_and_stdin( pass mock_utils_exit.assert_called_once_with(expected_msg) + @mock.patch('glanceclient.common.utils.exit') + def test_neg_image_create_via_import_copy_image( + self, mock_utils_exit): + expected_msg = ("Import method 'copy-image' cannot be used " + "while creating the image.") + mock_utils_exit.side_effect = self._mock_utils_exit + my_args = self.base_args.copy() + my_args.update( + {'id': 'IMG-01', 'import_method': 'copy-image'}) + args = self._make_args(my_args) + + with mock.patch.object(self.gc.images, + 'get_import_info') as mocked_info: + mocked_info.return_value = self.import_info_response + try: + test_shell.do_image_create_via_import(self.gc, args) + self.fail("utils.exit should have been called") + except SystemExit: + pass + mock_utils_exit.assert_called_once_with(expected_msg) + @mock.patch('glanceclient.common.utils.exit') def test_neg_image_create_via_import_stores_all_stores_specified( self, mock_utils_exit): @@ -1988,6 +2009,82 @@ def test_image_import_multiple_stores(self, mocked_utils_print_image, allow_failure=True, stores=['site1', 'site2'], backend=None) + @mock.patch('glanceclient.common.utils.print_image') + @mock.patch('glanceclient.v2.shell._validate_backend') + def test_image_import_copy_image(self, mocked_utils_print_image, + msvb): + args = self._make_args( + {'id': 'IMG-02', 'uri': None, 'import_method': 'copy-image', + 'from_create': False, 'stores': 'file1,file2'}) + with mock.patch.object(self.gc.images, 'image_import') as mock_import: + with mock.patch.object(self.gc.images, 'get') as mocked_get: + with mock.patch.object(self.gc.images, + 'get_import_info') as mocked_info: + mocked_get.return_value = {'status': 'active', + 'container_format': 'bare', + 'disk_format': 'raw'} + mocked_info.return_value = self.import_info_response + mock_import.return_value = None + test_shell.do_image_import(self.gc, args) + mock_import.assert_called_once_with( + 'IMG-02', 'copy-image', None, all_stores=None, + allow_failure=True, stores=['file1', 'file2'], + backend=None) + + @mock.patch('glanceclient.common.utils.exit') + def test_neg_image_import_copy_image_not_active( + self, mock_utils_exit): + expected_msg = ("The 'copy-image' import method can only be used on " + "an image with status 'active'.") + mock_utils_exit.side_effect = self._mock_utils_exit + args = self._make_args( + {'id': 'IMG-02', 'uri': None, 'import_method': 'copy-image', + 'disk_format': 'raw', + 'container_format': 'bare', + 'from_create': False, 'stores': 'file1,file2'}) + with mock.patch.object( + self.gc.images, + 'get_stores_info') as mocked_stores_info: + with mock.patch.object(self.gc.images, 'get') as mocked_get: + with mock.patch.object(self.gc.images, + 'get_import_info') as mocked_info: + + mocked_stores_info.return_value = self.stores_info_response + mocked_get.return_value = {'status': 'uploading', + 'container_format': 'bare', + 'disk_format': 'raw'} + mocked_info.return_value = self.import_info_response + try: + test_shell.do_image_import(self.gc, args) + self.fail("utils.exit should have been called") + except SystemExit: + pass + mock_utils_exit.assert_called_once_with(expected_msg) + + @mock.patch('glanceclient.common.utils.exit') + def test_neg_image_import_stores_all_stores_not_specified( + self, mock_utils_exit): + expected_msg = ("Provide either --stores or --all-stores for " + "'copy-image' import method.") + mock_utils_exit.side_effect = self._mock_utils_exit + my_args = self.base_args.copy() + my_args.update( + {'id': 'IMG-01', 'import_method': 'copy-image', + 'disk_format': 'raw', + 'container_format': 'bare', + }) + args = self._make_args(my_args) + + with mock.patch.object(self.gc.images, + 'get_import_info') as mocked_info: + mocked_info.return_value = self.import_info_response + try: + test_shell.do_image_import(self.gc, args) + self.fail("utils.exit should have been called") + except SystemExit: + pass + mock_utils_exit.assert_called_once_with(expected_msg) + def test_image_download(self): args = self._make_args( {'id': 'IMG-01', 'file': 'test', 'progress': True, diff --git a/glanceclient/v2/shell.py b/glanceclient/v2/shell.py index b3ac56f6e..3193a8834 100644 --- a/glanceclient/v2/shell.py +++ b/glanceclient/v2/shell.py @@ -210,6 +210,10 @@ def do_image_create_via_import(gc, args): if args.import_method is None and (file_name or using_stdin): args.import_method = 'glance-direct' + if args.import_method == 'copy-image': + utils.exit("Import method 'copy-image' cannot be used " + "while creating the image.") + # determine whether the requested import method is valid import_methods = gc.images.get_import_info().get('import-methods') if args.import_method and args.import_method not in import_methods.get( @@ -735,6 +739,10 @@ def do_image_import(gc, args): utils.exit("Import method should be 'web-download' if URI is " "provided.") + if args.import_method == 'copy-image' and not (stores or all_stores): + utils.exit("Provide either --stores or --all-stores for " + "'copy-image' import method.") + # check image properties image = gc.images.get(args.id) container_format = image.get('container_format') @@ -752,6 +760,10 @@ def do_image_import(gc, args): if image_status != 'queued': utils.exit("The 'web-download' import method can only be applied " "to an image in status 'queued'") + if args.import_method == 'copy-image': + if image_status != 'active': + utils.exit("The 'copy-image' import method can only be used on " + "an image with status 'active'.") # finally, do the import gc.images.image_import(args.id, args.import_method, args.uri, diff --git a/releasenotes/notes/copy-existing-image-619b7e6bc3394446.yaml b/releasenotes/notes/copy-existing-image-619b7e6bc3394446.yaml new file mode 100644 index 000000000..fc7833c27 --- /dev/null +++ b/releasenotes/notes/copy-existing-image-619b7e6bc3394446.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Adds support for copy-image import method which will copy existing + images into multiple stores. From b03b29d8b298efc809f24ac8b5d4b6f2b90fba81 Mon Sep 17 00:00:00 2001 From: Cyril Roelandt Date: Tue, 17 Mar 2020 21:04:33 +0100 Subject: [PATCH 045/167] Remove .zuul.yaml from the list of irrelevant files Change-Id: Ibf038748df6e037df876c0e65d49aa79031275af --- .zuul.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.zuul.yaml b/.zuul.yaml index f5943356b..ee38388cf 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -35,7 +35,6 @@ - ^lower-constraints.txt$ - ^setup.cfg$ - ^tox.ini$ - - ^\.zuul\.yaml$ - job: name: glanceclient-tox-keystone-tips-base From d91bcae8a509235ec17a60233e2c3e252b9a317d Mon Sep 17 00:00:00 2001 From: Erno Kuvaja Date: Wed, 18 Dec 2019 09:57:30 +0000 Subject: [PATCH 046/167] Delete image from specific store Add support to delete image from specific store. bp: delete-from-store Change-Id: Ie57d7de5822264a5ea8a5f4587ab8cfb4afb79de --- glanceclient/tests/unit/v2/test_shell_v2.py | 23 +++++++++++++++++++ glanceclient/v2/images.py | 8 +++++++ glanceclient/v2/shell.py | 18 +++++++++++++++ .../del_from_store-2d807c3038283907.yaml | 4 ++++ 4 files changed, 53 insertions(+) create mode 100644 releasenotes/notes/del_from_store-2d807c3038283907.yaml diff --git a/glanceclient/tests/unit/v2/test_shell_v2.py b/glanceclient/tests/unit/v2/test_shell_v2.py index f137d30fa..c43f60615 100644 --- a/glanceclient/tests/unit/v2/test_shell_v2.py +++ b/glanceclient/tests/unit/v2/test_shell_v2.py @@ -2165,6 +2165,29 @@ def test_do_image_delete_with_invalid_ids(self, mocked_print_err, self.assertEqual(2, mocked_print_err.call_count) mocked_utils_exit.assert_called_once_with() + @mock.patch.object(utils, 'exit') + def test_do_image_delete_from_store_not_found(self, mocked_utils_exit): + args = argparse.Namespace(id='image1', store='store1') + with mock.patch.object(self.gc.images, + 'delete_from_store') as mocked_delete: + mocked_delete.side_effect = exc.HTTPNotFound + + test_shell.do_stores_delete(self.gc, args) + + self.assertEqual(1, mocked_delete.call_count) + mocked_utils_exit.assert_called_once_with('Multi Backend support ' + 'is not enabled or ' + 'Image/store not found.') + + def test_do_image_delete_from_store(self): + args = argparse.Namespace(id='image1', store='store1') + with mock.patch.object(self.gc.images, + 'delete_from_store') as mocked_delete: + test_shell.do_stores_delete(self.gc, args) + + mocked_delete.assert_called_once_with('store1', + 'image1') + @mock.patch.object(utils, 'exit') @mock.patch.object(utils, 'print_err') def test_do_image_delete_with_forbidden_ids(self, mocked_print_err, diff --git a/glanceclient/v2/images.py b/glanceclient/v2/images.py index 69163fe8e..1e8e621a0 100644 --- a/glanceclient/v2/images.py +++ b/glanceclient/v2/images.py @@ -302,6 +302,14 @@ def get_stores_info(self): resp, body = self.http_client.get(url) return body, resp + @utils.add_req_id_to_object() + def delete_from_store(self, store_id, image_id): + """Delete image data from specific store.""" + url = ('/v2/stores/%(store)s/%(image)s' % {'store': store_id, + 'image': image_id}) + resp, body = self.http_client.delete(url) + return body, resp + @utils.add_req_id_to_object() def stage(self, image_id, image_data, image_size=None): """Upload the data to image staging. diff --git a/glanceclient/v2/shell.py b/glanceclient/v2/shell.py index 3193a8834..b4dc811b0 100644 --- a/glanceclient/v2/shell.py +++ b/glanceclient/v2/shell.py @@ -556,6 +556,24 @@ def do_stores_info(gc, args): utils.print_dict(stores_info) +@utils.arg('id', metavar='', help=_('ID of image to update.')) +@utils.arg('--store', metavar='', required=True, + help=_('Store to delete image from.')) +def do_stores_delete(gc, args): + """Delete image from specific store.""" + try: + gc.images.delete_from_store(args.store, args.id) + except exc.HTTPNotFound: + utils.exit('Multi Backend support is not enabled or Image/store not ' + 'found.') + except (exc.HTTPForbidden, exc.HTTPException) as e: + msg = ("Unable to delete image '%s' from store '%s'. (%s)" % ( + args.id, + args.store, + e)) + utils.exit(msg) + + @utils.arg('--allow-md5-fallback', action='store_true', default=utils.env('OS_IMAGE_ALLOW_MD5_FALLBACK', default=False), help=_('If os_hash_algo and os_hash_value properties are available ' diff --git a/releasenotes/notes/del_from_store-2d807c3038283907.yaml b/releasenotes/notes/del_from_store-2d807c3038283907.yaml new file mode 100644 index 000000000..7c330a874 --- /dev/null +++ b/releasenotes/notes/del_from_store-2d807c3038283907.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Support for deleting the image data from single store. From 236a7beb3436196d5439f586fa7e9f3a171be955 Mon Sep 17 00:00:00 2001 From: James Page Date: Tue, 31 Mar 2020 16:20:22 +0100 Subject: [PATCH 047/167] Fix SyntaxWarning with Python 3.8 Under Python 3.8, the following SyntaxWarning is logged on every glanceclient invocation: SyntaxWarning: "is not" with a literal. Did you mean "!="? if kwargs.get('cacert', None) is not '': Make use of '!=' rather than 'is not' to avoid this warning. Change-Id: I11ed568932ec5ea0ffee629d6fd434433f262b67 --- glanceclient/common/http.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glanceclient/common/http.py b/glanceclient/common/http.py index 78c4bc550..6973f60ee 100644 --- a/glanceclient/common/http.py +++ b/glanceclient/common/http.py @@ -177,7 +177,7 @@ def __init__(self, endpoint, **kwargs): if kwargs.get('insecure', False) is True: self.session.verify = False else: - if kwargs.get('cacert', None) is not '': + if kwargs.get('cacert', None) != '': self.session.verify = kwargs.get('cacert', True) self.session.cert = (kwargs.get('cert_file'), From 82da2378eaf1e2283ed623760d065cc972dd0871 Mon Sep 17 00:00:00 2001 From: Andreas Jaeger Date: Thu, 2 Apr 2020 15:40:21 +0200 Subject: [PATCH 048/167] Update hacking for Python3 The repo is Python 3 now, so update hacking to version 3.0 which supports Python 3. Fix problems found. Remove hacking and friends from lower-constraints, they are not needed for installation. Change-Id: I5ae47a7b11ff29a301e440c15daf30db7738485b --- glanceclient/common/http.py | 2 +- .../functional/v1/test_readonly_glance.py | 2 +- .../functional/v2/test_readonly_glance.py | 2 +- glanceclient/tests/unit/test_http.py | 10 +++++----- .../tests/unit/v2/test_metadefs_namespaces.py | 1 + .../tests/unit/v2/test_metadefs_objects.py | 1 + glanceclient/tests/unit/v2/test_shell_v2.py | 2 ++ glanceclient/v2/shell.py | 18 +++++++++--------- lower-constraints.txt | 4 ---- test-requirements.txt | 2 +- tox.ini | 3 ++- 11 files changed, 24 insertions(+), 23 deletions(-) diff --git a/glanceclient/common/http.py b/glanceclient/common/http.py index 78c4bc550..6973f60ee 100644 --- a/glanceclient/common/http.py +++ b/glanceclient/common/http.py @@ -177,7 +177,7 @@ def __init__(self, endpoint, **kwargs): if kwargs.get('insecure', False) is True: self.session.verify = False else: - if kwargs.get('cacert', None) is not '': + if kwargs.get('cacert', None) != '': self.session.verify = kwargs.get('cacert', True) self.session.cert = (kwargs.get('cert_file'), diff --git a/glanceclient/tests/functional/v1/test_readonly_glance.py b/glanceclient/tests/functional/v1/test_readonly_glance.py index 122c61b15..a7024aaf3 100644 --- a/glanceclient/tests/functional/v1/test_readonly_glance.py +++ b/glanceclient/tests/functional/v1/test_readonly_glance.py @@ -52,7 +52,7 @@ def test_help(self): commands = [] cmds_start = lines.index('Positional arguments:') cmds_end = lines.index('Optional arguments:') - command_pattern = re.compile('^ {4}([a-z0-9\-\_]+)') + command_pattern = re.compile(r'^ {4}([a-z0-9\-\_]+)') for line in lines[cmds_start:cmds_end]: match = command_pattern.match(line) if match: diff --git a/glanceclient/tests/functional/v2/test_readonly_glance.py b/glanceclient/tests/functional/v2/test_readonly_glance.py index c024303a5..4d7f92d75 100644 --- a/glanceclient/tests/functional/v2/test_readonly_glance.py +++ b/glanceclient/tests/functional/v2/test_readonly_glance.py @@ -72,7 +72,7 @@ def test_help(self): commands = [] cmds_start = lines.index('Positional arguments:') cmds_end = lines.index('Optional arguments:') - command_pattern = re.compile('^ {4}([a-z0-9\-\_]+)') + command_pattern = re.compile(r'^ {4}([a-z0-9\-\_]+)') for line in lines[cmds_start:cmds_end]: match = command_pattern.match(line) if match: diff --git a/glanceclient/tests/unit/test_http.py b/glanceclient/tests/unit/test_http.py index 2f72b9a49..b2035c927 100644 --- a/glanceclient/tests/unit/test_http.py +++ b/glanceclient/tests/unit/test_http.py @@ -367,11 +367,11 @@ def test_log_curl_request_with_body_and_header(self, mock_log): self.assertTrue(mock_log.called, 'LOG.debug never called') self.assertTrue(mock_log.call_args[0], 'LOG.debug called with no arguments') - hd_regex = ".*\s-H\s+'\s*%s\s*:\s*%s\s*'.*" % (hd_name, hd_val) + hd_regex = r".*\s-H\s+'\s*%s\s*:\s*%s\s*'.*" % (hd_name, hd_val) self.assertThat(mock_log.call_args[0][0], matchers.MatchesRegex(hd_regex), 'header not found in curl command') - body_regex = ".*\s-d\s+'%s'\s.*" % body + body_regex = r".*\s-d\s+'%s'\s.*" % body self.assertThat(mock_log.call_args[0][0], matchers.MatchesRegex(body_regex), 'body not found in curl command') @@ -390,12 +390,12 @@ def _test_log_curl_request_with_certs(self, mock_log, key, cert, cacert): needles = {'key': key, 'cert': cert, 'cacert': cacert} for option, value in needles.items(): if value: - regex = ".*\s--%s\s+('%s'|%s).*" % (option, value, value) + regex = r".*\s--%s\s+('%s'|%s).*" % (option, value, value) self.assertThat(mock_log.call_args[0][0], matchers.MatchesRegex(regex), 'no --%s option in curl command' % option) else: - regex = ".*\s--%s\s+.*" % option + regex = r".*\s--%s\s+.*" % option self.assertThat(mock_log.call_args[0][0], matchers.Not(matchers.MatchesRegex(regex)), 'unexpected --%s option in curl command' % @@ -421,7 +421,7 @@ def test_log_curl_request_with_insecure_param(self, mock_log): self.assertTrue(mock_log.call_args[0], 'LOG.debug called with no arguments') self.assertThat(mock_log.call_args[0][0], - matchers.MatchesRegex('.*\s-k\s.*'), + matchers.MatchesRegex(r'.*\s-k\s.*'), 'no -k option in curl command') @mock.patch('glanceclient.common.http.LOG.debug') diff --git a/glanceclient/tests/unit/v2/test_metadefs_namespaces.py b/glanceclient/tests/unit/v2/test_metadefs_namespaces.py index 1c19d8bf2..35d71985e 100644 --- a/glanceclient/tests/unit/v2/test_metadefs_namespaces.py +++ b/glanceclient/tests/unit/v2/test_metadefs_namespaces.py @@ -58,6 +58,7 @@ def _get_namespace_fixture(ns_name, rt_name=RESOURCE_TYPE1, **kwargs): return ns + data_fixtures = { "/v2/metadefs/namespaces?limit=20": { "GET": ( diff --git a/glanceclient/tests/unit/v2/test_metadefs_objects.py b/glanceclient/tests/unit/v2/test_metadefs_objects.py index 5de311240..bc3b66940 100644 --- a/glanceclient/tests/unit/v2/test_metadefs_objects.py +++ b/glanceclient/tests/unit/v2/test_metadefs_objects.py @@ -58,6 +58,7 @@ def _get_object_fixture(ns_name, obj_name, **kwargs): return obj + data_fixtures = { "/v2/metadefs/namespaces/%s/objects" % NAMESPACE1: { "GET": ( diff --git a/glanceclient/tests/unit/v2/test_shell_v2.py b/glanceclient/tests/unit/v2/test_shell_v2.py index c43f60615..9d10ff279 100644 --- a/glanceclient/tests/unit/v2/test_shell_v2.py +++ b/glanceclient/tests/unit/v2/test_shell_v2.py @@ -55,6 +55,8 @@ def schema_args(schema_getter, omit=None): 'locations': {'type': 'string'}, 'copy_from': {'type': 'string'}}} return original_schema_args(my_schema_getter, omit) + + utils.schema_args = schema_args from glanceclient.v2 import shell as test_shell # noqa diff --git a/glanceclient/v2/shell.py b/glanceclient/v2/shell.py index b4dc811b0..8414308c0 100644 --- a/glanceclient/v2/shell.py +++ b/glanceclient/v2/shell.py @@ -840,15 +840,15 @@ def do_image_reactivate(gc, args): @utils.arg('tag_value', metavar='', help=_('Value of the tag.')) def do_image_tag_update(gc, args): - """Update an image with the given tag.""" - if not (args.image_id and args.tag_value): - utils.exit('Unable to update tag. Specify image_id and tag_value') - else: - gc.image_tags.update(args.image_id, args.tag_value) - image = gc.images.get(args.image_id) - image = [image] - columns = ['ID', 'Tags'] - utils.print_list(image, columns) + """Update an image with the given tag.""" + if not (args.image_id and args.tag_value): + utils.exit('Unable to update tag. Specify image_id and tag_value') + else: + gc.image_tags.update(args.image_id, args.tag_value) + image = gc.images.get(args.image_id) + image = [image] + columns = ['ID', 'Tags'] + utils.print_list(image, columns) @utils.arg('image_id', metavar='', diff --git a/lower-constraints.txt b/lower-constraints.txt index 61be70719..f5a18807e 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -13,9 +13,7 @@ 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 @@ -45,11 +43,9 @@ oslo.serialization==2.18.0 oslo.utils==3.33.0 paramiko==2.0.0 pbr==2.0.0 -pep8==1.5.7 prettytable==0.7.1 pyasn1==0.1.8 pycparser==2.18 -pyflakes==0.8.1 Pygments==2.2.0 pyinotify==0.9.6 pyOpenSSL==17.1.0 diff --git a/test-requirements.txt b/test-requirements.txt index 8e8541cac..3a6aa595f 100644 --- a/test-requirements.txt +++ b/test-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. -hacking>=1.1.0,<1.2.0 # Apache-2.0 +hacking>=3.0,<3.1.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 mock>=2.0.0 # BSD diff --git a/tox.ini b/tox.ini index 0cd66e0f6..0378f5be7 100644 --- a/tox.ini +++ b/tox.ini @@ -63,7 +63,8 @@ commands = [flake8] # E731 skipped as assign a lambda expression -ignore = E731,F403,F812,F821 +# W504 line break after binary operator +ignore = E731,F403,F812,F821,W504 show-source = True exclude = .venv*,.tox,dist,*egg,build,.git,doc,*lib/python*,.update-venv From baa89534a3568363fdc518a587a7679657a048c7 Mon Sep 17 00:00:00 2001 From: Andreas Jaeger Date: Sat, 4 Apr 2020 16:46:25 +0200 Subject: [PATCH 049/167] Cleanup py27 support Make a few cleanups: - Remove python 2.7 stanza from setup.py - Update classifiers - Update requirements, no need for python_version anymore Change-Id: I6b6dfb0959973abf0c2e8325006025db2a5d85d0 --- doc/requirements.txt | 3 +-- setup.cfg | 2 ++ setup.py | 9 --------- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index 4c33153ef..e8ba0fa5c 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -3,6 +3,5 @@ # process, which may cause wedges in the gate later. openstackdocstheme>=1.20.0 # Apache-2.0 reno>=2.5.0 # Apache-2.0 -sphinx!=1.6.6,!=1.6.7,>=1.6.2,<2.0.0;python_version=='2.7' # BSD -sphinx!=1.6.6,!=1.6.7,!=2.1.0,>=1.6.2;python_version>='3.4' # BSD +sphinx!=1.6.6,!=1.6.7,!=2.1.0,>=1.6.2 # BSD sphinxcontrib-apidoc>=0.2.0 # BSD diff --git a/setup.cfg b/setup.cfg index 6d2ec6657..170db57db 100644 --- a/setup.cfg +++ b/setup.cfg @@ -17,6 +17,8 @@ classifier = License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python + Programming Language :: Python :: Implementation :: CPython + Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 diff --git a/setup.py b/setup.py index 566d84432..cd35c3c35 100644 --- a/setup.py +++ b/setup.py @@ -13,17 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT import setuptools -# In python < 2.7.4, a lazy loading of package `pbr` will break -# setuptools if some other modules registered functions in `atexit`. -# solution from: http://bugs.python.org/issue15881#msg170215 -try: - import multiprocessing # noqa -except ImportError: - pass - setuptools.setup( setup_requires=['pbr>=2.0.0'], pbr=True) From 7e1460a1d055eafc6dee213040a16e6f2f4b6831 Mon Sep 17 00:00:00 2001 From: Abhishek Kekane Date: Mon, 6 Apr 2020 06:27:26 +0000 Subject: [PATCH 050/167] Add release note for glanceclient 3.0.0 Change-Id: I4556df86ab501362bdfae2d1457ba14dade52f22 --- .../notes/3.0.0_Release-1337ddc753b88905.yaml | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 releasenotes/notes/3.0.0_Release-1337ddc753b88905.yaml diff --git a/releasenotes/notes/3.0.0_Release-1337ddc753b88905.yaml b/releasenotes/notes/3.0.0_Release-1337ddc753b88905.yaml new file mode 100644 index 000000000..d4ef2de75 --- /dev/null +++ b/releasenotes/notes/3.0.0_Release-1337ddc753b88905.yaml @@ -0,0 +1,25 @@ +--- +prelude: | + This version of python-glanceclient finalizes client-side support for + the Glance import image in multiple stores, copy existing image in + multiple stores and delete image from single store. +fixes: + - | + Bug 1838694: glanceclient doesn't cleanup session it creates if one is not provided + + .. _1838694: https://bugs.launchpad.net/python-glanceclient/+bug/1838694 +upgrade: + - | + The following Command Line Interface calls now take ``--stores``, + ``--all-stores`` and ``--allow-failure`` option: + + * ``glance image-create-via-import`` + * ``glance image-import`` + + The value for ``--stores`` option is a list of store identifiers. The + list of available stores may be obtained from the ``glance stores-info`` + command. + + The value for ``--all-stores`` option could be True or False. + + The value for ``--allow-failure`` option could be True or False. From 3e91562b029711a6242e70f71fb623855ad56d86 Mon Sep 17 00:00:00 2001 From: Erno Kuvaja Date: Tue, 7 Apr 2020 11:26:40 +0100 Subject: [PATCH 051/167] Rename releasenotes to reflect correct version As python-glanceclient 3.0.0 was released earlier, this release will be version 3.1.0 Change-Id: I18f4d4e9d61b235ea7896ea97c1b480249d474e2 --- ...-1337ddc753b88905.yaml => 3.1.0_Release-1337ddc753b88905.yaml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename releasenotes/notes/{3.0.0_Release-1337ddc753b88905.yaml => 3.1.0_Release-1337ddc753b88905.yaml} (100%) diff --git a/releasenotes/notes/3.0.0_Release-1337ddc753b88905.yaml b/releasenotes/notes/3.1.0_Release-1337ddc753b88905.yaml similarity index 100% rename from releasenotes/notes/3.0.0_Release-1337ddc753b88905.yaml rename to releasenotes/notes/3.1.0_Release-1337ddc753b88905.yaml From 00bc25eeceff0804451594926ce53ad0a69b702c Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Tue, 7 Apr 2020 13:19:37 +0000 Subject: [PATCH 052/167] Update master for stable/ussuri Add file to the reno documentation build to show release notes for stable/ussuri. Use pbr instruction to increment the minor version number automatically so that master versions are higher than the versions on stable/ussuri. Change-Id: Ic9dab2380f698e30c5cb1327c5f22bdd7489df51 Sem-Ver: feature --- releasenotes/source/index.rst | 1 + releasenotes/source/ussuri.rst | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 releasenotes/source/ussuri.rst diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index 1567c44c8..6dae364e3 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ glanceclient Release Notes :maxdepth: 1 unreleased + ussuri train stein rocky diff --git a/releasenotes/source/ussuri.rst b/releasenotes/source/ussuri.rst new file mode 100644 index 000000000..e21e50e0c --- /dev/null +++ b/releasenotes/source/ussuri.rst @@ -0,0 +1,6 @@ +=========================== +Ussuri Series Release Notes +=========================== + +.. release-notes:: + :branch: stable/ussuri From dff5c881bdbc7f0cf03d88e974ae4234cb646aa1 Mon Sep 17 00:00:00 2001 From: Abhishek Kekane Date: Wed, 8 Apr 2020 17:11:18 +0000 Subject: [PATCH 053/167] Pass --all-stores, --allow-failure as bool to API Newly added command line options --all-stores, --allow-failure are boolean but we are passing it as a string to glance API. This will cause problem if those parameters are not converted to boolean at API side. Passing these parameters as boolean instead of string to API. Change-Id: I8d4eab9241fc9bb24bc40b47bf18d63c86a97d77 Closes-Bug: #1871674 --- glanceclient/v2/images.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/glanceclient/v2/images.py b/glanceclient/v2/images.py index 1e8e621a0..c062cb8ac 100644 --- a/glanceclient/v2/images.py +++ b/glanceclient/v2/images.py @@ -335,13 +335,13 @@ def image_import(self, image_id, method='glance-direct', uri=None, if stores: data['stores'] = stores if allow_failure: - data['all_stores_must_succeed'] = 'false' + data['all_stores_must_succeed'] = False if backend is not None: headers['x-image-meta-store'] = backend if all_stores: - data['all_stores'] = 'true' + data['all_stores'] = True if allow_failure: - data['all_stores_must_succeed'] = 'false' + data['all_stores_must_succeed'] = False if uri: if method == 'web-download': From 1b5c30926572c97072921b8795fef007a1ad6c7b Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Fri, 10 Apr 2020 13:30:55 -0500 Subject: [PATCH 054/167] Add Python3 victoria unit tests This is an automatically generated patch to ensure unit testing is in place for all the of the tested runtimes for victoria. See also the PTI in governance [1]. [1]: https://governance.openstack.org/tc/reference/project-testing-interface.html Change-Id: I644e0dcb7c82f7eb3d112f03c021aff8db904aa5 Signed-off-by: Sean McGinnis --- .zuul.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.zuul.yaml b/.zuul.yaml index c48d6f43e..14adc2b91 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -82,7 +82,7 @@ - lib-forward-testing-python3 - openstack-cover-jobs - openstack-lower-constraints-jobs - - openstack-python3-ussuri-jobs + - openstack-python3-victoria-jobs - publish-openstack-docs-pti - release-notes-jobs-python3 check: From 6a045884cb090bbc1daf1af853a243452a7aab16 Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Sat, 18 Apr 2020 11:58:08 -0500 Subject: [PATCH 055/167] Use unittest.mock instead of third party mock Now that we no longer support py27, we can use the standard library unittest.mock module instead of the third party mock lib. Change-Id: I446ee142c7a17446372c910f7f2a36d55df18e04 Signed-off-by: Sean McGinnis --- glanceclient/tests/unit/test_exc.py | 2 +- glanceclient/tests/unit/test_http.py | 2 +- glanceclient/tests/unit/test_shell.py | 2 +- glanceclient/tests/unit/test_ssl.py | 2 +- glanceclient/tests/unit/test_utils.py | 2 +- glanceclient/tests/unit/v1/test_shell.py | 3 ++- glanceclient/tests/unit/v2/test_images.py | 2 +- glanceclient/tests/unit/v2/test_shell_v2.py | 3 ++- test-requirements.txt | 1 - 9 files changed, 10 insertions(+), 9 deletions(-) diff --git a/glanceclient/tests/unit/test_exc.py b/glanceclient/tests/unit/test_exc.py index 9a2d01fd0..fa7bf0797 100644 --- a/glanceclient/tests/unit/test_exc.py +++ b/glanceclient/tests/unit/test_exc.py @@ -12,8 +12,8 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -import mock import testtools +from unittest import mock from glanceclient import exc diff --git a/glanceclient/tests/unit/test_http.py b/glanceclient/tests/unit/test_http.py index b2035c927..0cf8f5be2 100644 --- a/glanceclient/tests/unit/test_http.py +++ b/glanceclient/tests/unit/test_http.py @@ -15,12 +15,12 @@ import functools import json import logging +from unittest import mock import uuid import fixtures from keystoneauth1 import session from keystoneauth1 import token_endpoint -import mock from oslo_utils import encodeutils import requests from requests_mock.contrib import fixture diff --git a/glanceclient/tests/unit/test_shell.py b/glanceclient/tests/unit/test_shell.py index 3027e4604..b23591ff4 100644 --- a/glanceclient/tests/unit/test_shell.py +++ b/glanceclient/tests/unit/test_shell.py @@ -21,12 +21,12 @@ import os import sys import traceback +from unittest import mock import uuid import fixtures from keystoneauth1 import exceptions as ks_exc from keystoneauth1 import fixture as ks_fixture -import mock from requests_mock.contrib import fixture as rm_fixture import six diff --git a/glanceclient/tests/unit/test_ssl.py b/glanceclient/tests/unit/test_ssl.py index 323ac9de7..320dfde11 100644 --- a/glanceclient/tests/unit/test_ssl.py +++ b/glanceclient/tests/unit/test_ssl.py @@ -14,8 +14,8 @@ # under the License. import os +from unittest import mock -import mock import six import ssl import testtools diff --git a/glanceclient/tests/unit/test_utils.py b/glanceclient/tests/unit/test_utils.py index 3ef585a05..6128d6c9b 100644 --- a/glanceclient/tests/unit/test_utils.py +++ b/glanceclient/tests/unit/test_utils.py @@ -14,8 +14,8 @@ # under the License. import sys +from unittest import mock -import mock from oslo_utils import encodeutils from requests import Response import six diff --git a/glanceclient/tests/unit/v1/test_shell.py b/glanceclient/tests/unit/v1/test_shell.py index a3bd29b4f..5476a2faf 100644 --- a/glanceclient/tests/unit/v1/test_shell.py +++ b/glanceclient/tests/unit/v1/test_shell.py @@ -17,12 +17,13 @@ import argparse import json import os +from unittest import mock + import six import subprocess import tempfile import testtools -import mock from glanceclient import exc from glanceclient import shell diff --git a/glanceclient/tests/unit/v2/test_images.py b/glanceclient/tests/unit/v2/test_images.py index 99926dee1..fee7d4903 100644 --- a/glanceclient/tests/unit/v2/test_images.py +++ b/glanceclient/tests/unit/v2/test_images.py @@ -15,8 +15,8 @@ import errno import hashlib -import mock import testtools +from unittest import mock from glanceclient import exc from glanceclient.tests.unit.v2 import base diff --git a/glanceclient/tests/unit/v2/test_shell_v2.py b/glanceclient/tests/unit/v2/test_shell_v2.py index 9d10ff279..9c1955170 100644 --- a/glanceclient/tests/unit/v2/test_shell_v2.py +++ b/glanceclient/tests/unit/v2/test_shell_v2.py @@ -16,8 +16,9 @@ import argparse from copy import deepcopy import json -import mock import os +from unittest import mock + import six import sys import tempfile diff --git a/test-requirements.txt b/test-requirements.txt index 3a6aa595f..a2c2954c8 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -5,7 +5,6 @@ hacking>=3.0,<3.1.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 -mock>=2.0.0 # BSD os-client-config>=1.28.0 # Apache-2.0 stestr>=2.0.0 # Apache-2.0 testtools>=2.2.0 # MIT From 2f86299b3dba08af8fdd94a95512b8b4d48ea48f Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Fri, 24 Apr 2020 08:23:16 -0500 Subject: [PATCH 056/167] Add py38 package metadata Now that we are running the Victoria tests that include a voting py38, we can now add the Python 3.8 metadata to the package information to reflect that support. Change-Id: Ifbadd0cab7363e604c11b94df3eb466d5e9dfe95 Signed-off-by: Sean McGinnis --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 170db57db..73161d9ba 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,6 +22,7 @@ classifier = Programming Language :: Python :: 3 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 [files] packages = From 490f808c4f47bc41e5ef81af91dd0448e1312d52 Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Fri, 24 Apr 2020 10:25:54 -0500 Subject: [PATCH 057/167] Bump default tox env from py37 to py38 Python 3.8 is now our highest level supported python runtime. This updates the default tox target environments to swap out py37 for py38 to make sure local development testing is covering this version. This does not impact zuul jobs in any way, nor prevent local tests against py37. It just changes the default if none is explicitly provided. Change-Id: Id27c5ad8a9d4a00f37d4a3ff7f8a71780df50fb0 Signed-off-by: Sean McGinnis --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 0378f5be7..f001df98f 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py37,pep8 +envlist = py38,pep8 minversion = 2.0 skipsdist = True From 56186d6d5aa1a0c8fde99eeb535a650b0495925d Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Tue, 7 Apr 2020 00:13:49 -0400 Subject: [PATCH 058/167] Fail gracefully when MD5 is unavailable The glanceclient currently assumes that MD5 will always be available. This is not the case, however, in a FIPS-compliant environment. This patch enables the glanceclient to fail gracefully in such a case. Closes-bug: #1871675 Change-Id: Ibd89989e06cc5be7da71f5f21561d73b5abc4104 --- glanceclient/common/utils.py | 9 ++++++++- glanceclient/tests/unit/v2/test_images.py | 18 ++++++++++++++++++ glanceclient/v2/images.py | 6 ++++-- lower-constraints.txt | 1 + .../notes/check-for-md5-59db8fd67870b214.yaml | 13 +++++++++++++ test-requirements.txt | 1 + 6 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/check-for-md5-59db8fd67870b214.yaml diff --git a/glanceclient/common/utils.py b/glanceclient/common/utils.py index bc0c0eb2f..0fde763f1 100644 --- a/glanceclient/common/utils.py +++ b/glanceclient/common/utils.py @@ -436,7 +436,14 @@ def integrity_iter(iter, checksum): :raises: IOError """ - md5sum = hashlib.md5() + try: + md5sum = hashlib.new('md5') + except ValueError: + raise IOError(errno.EPIPE, + 'Corrupt image download. Expected checksum is %s ' + 'but md5 algorithm is not available on the client' % + checksum) + for chunk in iter: yield chunk if isinstance(chunk, six.string_types): diff --git a/glanceclient/tests/unit/v2/test_images.py b/glanceclient/tests/unit/v2/test_images.py index fee7d4903..55610d80a 100644 --- a/glanceclient/tests/unit/v2/test_images.py +++ b/glanceclient/tests/unit/v2/test_images.py @@ -18,6 +18,8 @@ import testtools from unittest import mock +import ddt + from glanceclient import exc from glanceclient.tests.unit.v2 import base from glanceclient.tests import utils @@ -704,6 +706,7 @@ } +@ddt.ddt class TestController(testtools.TestCase): def setUp(self): super(TestController, self).setUp() @@ -1092,6 +1095,21 @@ def test_data_with_checksum(self): body = ''.join([b for b in body]) self.assertEqual('CCC', body) + @ddt.data('headeronly', 'chkonly', 'multihash') + def test_data_with_checksum_but_no_md5_algo(self, prefix): + with mock.patch('hashlib.new', mock.MagicMock( + side_effect=ValueError('unsupported hash type'))): + body = self.controller.data(prefix + + '-dd57-11e1-af0f-02163e68b1d8', + allow_md5_fallback=True) + try: + body = ''.join([b for b in body]) + self.fail('missing md5 algo did not raise an error') + except IOError as e: + self.assertEqual(errno.EPIPE, e.errno) + msg = 'md5 algorithm is not available on the client' + self.assertIn(msg, str(e)) + def test_data_with_checksum_and_fallback(self): # make sure the allow_md5_fallback option does not cause any # incorrect behavior when fallback is not needed diff --git a/glanceclient/v2/images.py b/glanceclient/v2/images.py index c062cb8ac..b07eecc2a 100644 --- a/glanceclient/v2/images.py +++ b/glanceclient/v2/images.py @@ -209,9 +209,11 @@ def data(self, image_id, do_checksum=True, allow_md5_fallback=False): specified hash algorithm is not available AND allow_md5_fallback is True, then continue to step #2 2. else if the image has a checksum property, MD5 is used to - validate against the 'checksum' value + validate against the 'checksum' value. (If MD5 is not available + to the client, the download fails.) 3. else if the download response has a 'content-md5' header, MD5 - is used to validate against the header value + is used to validate against the header value. (If MD5 is not + available to the client, the download fails.) 4. if none of 1-3 obtain, the data is **not validated** (this is compatible with legacy behavior) diff --git a/lower-constraints.txt b/lower-constraints.txt index f5a18807e..ecb84ad40 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -7,6 +7,7 @@ cliff==2.8.0 cmd2==0.8.0 coverage==4.0 cryptography==2.1 +ddt==1.2.1 debtcollector==1.2.0 docutils==0.11 dulwich==0.15.0 diff --git a/releasenotes/notes/check-for-md5-59db8fd67870b214.yaml b/releasenotes/notes/check-for-md5-59db8fd67870b214.yaml new file mode 100644 index 000000000..a70176b50 --- /dev/null +++ b/releasenotes/notes/check-for-md5-59db8fd67870b214.yaml @@ -0,0 +1,13 @@ +--- +other: + -| + For legacy (pre-Rocky) images that do not contain "multihash" metadata, + or when the ``--allow-md5-fallback`` option is used in cases where the + multihash metadata is present but the specified algorithm is not available + to the glanceclient, the glanceclient uses an MD5 checksum to validate + the download. When operating in a FIPS-compliant environment, however, + the MD5 algorithm may be unavailable to the glanceclient. In such a case, + (that is, when the MD5 checksum information is available to the glanceclient + but the MD5 algorithm is not), the glanceclient will fail the download as + corrupt because it cannot prove otherwise. This is consistent with + current behavior. diff --git a/test-requirements.txt b/test-requirements.txt index a2c2954c8..29c9183de 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -9,6 +9,7 @@ os-client-config>=1.28.0 # Apache-2.0 stestr>=2.0.0 # Apache-2.0 testtools>=2.2.0 # MIT testscenarios>=0.4 # Apache-2.0/BSD +ddt>=1.2.1 # MIT fixtures>=3.0.0 # Apache-2.0/BSD requests-mock>=1.2.0 # Apache-2.0 tempest>=17.1.0 # Apache-2.0 From 04d2fa705869e4ad2b1aab763290743081d0a590 Mon Sep 17 00:00:00 2001 From: Ghanshyam Mann Date: Tue, 12 May 2020 18:03:40 -0500 Subject: [PATCH 059/167] Fix hacking min version to 3.0.1 flake8 new release 3.8.0 added new checks and gate pep8 job start failing. hacking 3.0.1 fix the pinning of flake8 to avoid bringing in a new version with new checks. Though it is fixed in latest hacking but 2.0 and 3.0 has cap for flake8 as <4.0.0 which mean flake8 new version 3.9.0 can also break the pep8 job if new check are added. To avoid similar gate break in future, we need to bump the hacking min version. - http://lists.openstack.org/pipermail/openstack-discuss/2020-May/014828.html Change-Id: I88e1652fa2f99d475efd7489dcb3c3c1bb06f015 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 29c9183de..e9f66220c 100644 --- a/test-requirements.txt +++ b/test-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. -hacking>=3.0,<3.1.0 # Apache-2.0 +hacking>=3.0.1,<3.1.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 os-client-config>=1.28.0 # Apache-2.0 From 17947fb6fc50ccd47f60623c45055040ef1301c3 Mon Sep 17 00:00:00 2001 From: fuzihao Date: Wed, 20 May 2020 10:34:58 +0800 Subject: [PATCH 060/167] Fix pygments style New theme of docs (Victoria+) respects pygments_style. Since we starts using Victoria reqs while being on Ussuri, this patch ensures proper rendering both in Ussuri and Victoria. Change-Id: Ic43d545349d8aff1603ed7f8bf3dcd1aa2b3538e --- doc/source/conf.py | 2 +- releasenotes/source/conf.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 74d659545..8539e09e6 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -68,7 +68,7 @@ add_module_names = True # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = 'native' # -- Options for HTML output -------------------------------------------------- diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py index 75aab4d59..3bfc4fe71 100644 --- a/releasenotes/source/conf.py +++ b/releasenotes/source/conf.py @@ -93,7 +93,7 @@ # show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = 'native' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] From de178ac4382716cc93022be06b93697936e816fc Mon Sep 17 00:00:00 2001 From: Andreas Jaeger Date: Mon, 18 May 2020 21:40:41 +0200 Subject: [PATCH 061/167] Switch to newer openstackdocstheme and reno versions Switch to openstackdocstheme 2.2.1 and reno 3.1.0 versions. Using these versions will allow especially: * Linking from HTML to PDF document * Allow parallel building of documents * Fix some rendering problems Update Sphinx version as well. Remove docs requirements from lower-constraints, they are not needed during install or test but only for docs building. openstackdocstheme renames some variables, so follow the renames before the next release removes them. A couple of variables are also not needed anymore, remove them. Change pygments_style to 'native' since old theme version always used 'native' and the theme now respects the setting and using 'sphinx' can lead to some strange rendering. See also http://lists.openstack.org/pipermail/openstack-discuss/2020-May/014971.html Change-Id: Ie4aec288c74b9bd8d8d117f4bc2e0282cea4db90 --- doc/requirements.txt | 6 +++--- doc/source/conf.py | 13 ++++--------- lower-constraints.txt | 4 ---- releasenotes/source/conf.py | 14 +++++++------- 4 files changed, 14 insertions(+), 23 deletions(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index e8ba0fa5c..97c1879c4 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1,7 +1,7 @@ # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -openstackdocstheme>=1.20.0 # Apache-2.0 -reno>=2.5.0 # Apache-2.0 -sphinx!=1.6.6,!=1.6.7,!=2.1.0,>=1.6.2 # BSD +openstackdocstheme>=2.2.1 # Apache-2.0 +reno>=3.1.0 # Apache-2.0 +sphinx>=2.0.0,!=2.1.0 # BSD sphinxcontrib-apidoc>=0.2.0 # BSD diff --git a/doc/source/conf.py b/doc/source/conf.py index 74d659545..bfe9b56ab 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -16,7 +16,6 @@ import os import sys -import openstackdocstheme sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))) @@ -39,9 +38,9 @@ apidoc_separate_modules = True # openstackdocstheme options -repository_name = 'openstack/python-glanceclient' -bug_project = 'python-glanceclient' -bug_tag = '' +openstackdocs_repo_name = 'openstack/python-glanceclient' +openstackdocs_bug_project = 'python-glanceclient' +openstackdocs_bug_tag = '' # autodoc generation is a bit aggressive and a nuisance when doing heavy # text edit cycles. @@ -68,7 +67,7 @@ add_module_names = True # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = 'native' # -- Options for HTML output -------------------------------------------------- @@ -77,10 +76,6 @@ #html_theme = 'nature' html_theme = 'openstackdocs' -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = ['_theme'] -html_theme_path = [openstackdocstheme.get_html_theme_path()] - # Output file base name for HTML help builder. htmlhelp_basename = '%sdoc' % project diff --git a/lower-constraints.txt b/lower-constraints.txt index ecb84ad40..d98e40b79 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -31,7 +31,6 @@ monotonic==0.6 msgpack-python==0.4.0 netaddr==0.7.18 netifaces==0.10.4 -openstackdocstheme==1.20.0 ordereddict==1.1 os-client-config==1.28.0 os-testr==1.0.0 @@ -57,15 +56,12 @@ python-mimeparse==1.6.0 python-subunit==1.0.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 six==1.10.0 snowballstemmer==1.2.1 -Sphinx==1.6.2 -sphinxcontrib-websupport==1.0.1 stestr==2.0.0 stevedore==1.20.0 tempest==17.1.0 diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py index 75aab4d59..d20e2f429 100644 --- a/releasenotes/source/conf.py +++ b/releasenotes/source/conf.py @@ -29,8 +29,6 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) -import openstackdocstheme - # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. @@ -41,6 +39,7 @@ # ones. extensions = [ 'reno.sphinxext', + 'openstackdocstheme' ] # Add any paths that contain templates here, relative to this directory. @@ -59,6 +58,11 @@ project = u'glanceclient Release Notes' copyright = u'2016, Glance Developers' +openstackdocs_repo_name = 'openstack/python-glanceclient' +openstackdocs_bug_project = 'python-glanceclient' +openstackdocs_bug_tag = '' +openstackdocs_auto_name = False + # Release notes are not versioned, so we don't need to set version or release version = '' release = '' @@ -93,7 +97,7 @@ # show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = 'native' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] @@ -113,10 +117,6 @@ # documentation. # html_theme_options = {} -# Add any paths that contain custom themes here, relative to this directory. -# html_theme_path = [] -html_theme_path = [openstackdocstheme.get_html_theme_path()] - # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None From e8455f5c1847638bbd1ca0d74a0003940bf78dc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Beraud?= Date: Tue, 2 Jun 2020 20:47:47 +0200 Subject: [PATCH 062/167] Stop to use the __future__ module. The __future__ module [1] was used in this context to ensure compatibility between python 2 and python 3. We previously dropped the support of python 2.7 [2] and now we only support python 3 so we don't need to continue to use this module and the imports listed below. Imports commonly used and their related PEPs: - `division` is related to PEP 238 [3] - `print_function` is related to PEP 3105 [4] - `unicode_literals` is related to PEP 3112 [5] - `with_statement` is related to PEP 343 [6] - `absolute_import` is related to PEP 328 [7] [1] https://docs.python.org/3/library/__future__.html [2] https://governance.openstack.org/tc/goals/selected/ussuri/drop-py27.html [3] https://www.python.org/dev/peps/pep-0238 [4] https://www.python.org/dev/peps/pep-3105 [5] https://www.python.org/dev/peps/pep-3112 [6] https://www.python.org/dev/peps/pep-0343 [7] https://www.python.org/dev/peps/pep-0328 Change-Id: I732a57361319687ca0a64693f0e60bc0d8f5b3d2 --- glanceclient/common/utils.py | 2 -- glanceclient/shell.py | 2 -- glanceclient/v1/shell.py | 2 -- 3 files changed, 6 deletions(-) diff --git a/glanceclient/common/utils.py b/glanceclient/common/utils.py index 0fde763f1..d6d2268fe 100644 --- a/glanceclient/common/utils.py +++ b/glanceclient/common/utils.py @@ -13,8 +13,6 @@ # License for the specific language governing permissions and limitations # under the License. -from __future__ import print_function - import errno import functools import hashlib diff --git a/glanceclient/shell.py b/glanceclient/shell.py index 3dfa14a6e..07bc39498 100644 --- a/glanceclient/shell.py +++ b/glanceclient/shell.py @@ -17,8 +17,6 @@ Command-line interface to the OpenStack Images API. """ -from __future__ import print_function - import argparse import copy import getpass diff --git a/glanceclient/v1/shell.py b/glanceclient/v1/shell.py index 8a4d29dca..682ca9800 100644 --- a/glanceclient/v1/shell.py +++ b/glanceclient/v1/shell.py @@ -13,8 +13,6 @@ # License for the specific language governing permissions and limitations # under the License. -from __future__ import print_function - import copy import functools import os From 2e0d6a839a94363d17e63d40cc92a1f4c78c3d60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Beraud?= Date: Tue, 9 Jun 2020 11:54:50 +0200 Subject: [PATCH 063/167] drop mock from lower-constraints The mock third party library was needed for mock support in py2 runtimes. Since we now only support py36 and later, we don't need it in lower-constraints. These changes will help us to drop `mock` from openstack/requirements Change-Id: I668a2f65431e8870268e067e1c0acd4d94a65c95 --- lower-constraints.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/lower-constraints.txt b/lower-constraints.txt index d98e40b79..f7b568c36 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -26,7 +26,6 @@ keystoneauth1==3.6.2 linecache2==1.0.0 MarkupSafe==1.0 mccabe==0.2.1 -mock==2.0.0 monotonic==0.6 msgpack-python==0.4.0 netaddr==0.7.18 From 928935e5c0b8b43827fdf08b212d59d92b660924 Mon Sep 17 00:00:00 2001 From: Cyril Roelandt Date: Mon, 15 Jun 2020 20:58:52 +0200 Subject: [PATCH 064/167] Do not use the six library in the tests. Change-Id: Ic8a2a736a733e0151ca82f19bfde428dc04cf255 --- glanceclient/tests/unit/test_http.py | 16 +++---- glanceclient/tests/unit/test_progressbar.py | 6 +-- glanceclient/tests/unit/test_shell.py | 52 +++++++++------------ glanceclient/tests/unit/test_ssl.py | 10 +--- glanceclient/tests/unit/test_utils.py | 29 +++++------- glanceclient/tests/unit/v1/test_images.py | 14 ++---- glanceclient/tests/unit/v1/test_shell.py | 16 ++----- glanceclient/tests/unit/v2/test_shell_v2.py | 14 +++--- glanceclient/tests/utils.py | 24 +++++----- 9 files changed, 76 insertions(+), 105 deletions(-) diff --git a/glanceclient/tests/unit/test_http.py b/glanceclient/tests/unit/test_http.py index 0cf8f5be2..689074b0f 100644 --- a/glanceclient/tests/unit/test_http.py +++ b/glanceclient/tests/unit/test_http.py @@ -19,13 +19,13 @@ import uuid import fixtures +import io from keystoneauth1 import session from keystoneauth1 import token_endpoint from oslo_utils import encodeutils import requests from requests_mock.contrib import fixture -import six -from six.moves.urllib import parse +from urllib import parse from testscenarios import load_tests_apply_scenarios as load_tests # noqa import testtools from testtools import matchers @@ -296,14 +296,14 @@ def test_get_connections_kwargs_http(self): def test__chunk_body_exact_size_chunk(self): test_client = http._BaseHTTPClient() bytestring = b'x' * http.CHUNKSIZE - data = six.BytesIO(bytestring) + data = io.BytesIO(bytestring) chunk = list(test_client._chunk_body(data)) self.assertEqual(1, len(chunk)) self.assertEqual([bytestring], chunk) def test_http_chunked_request(self): text = "Ok" - data = six.StringIO(text) + data = io.StringIO(text) path = '/v1/images/' self.mock.post(self.endpoint + path, text=text) @@ -322,13 +322,13 @@ def test_http_json(self): resp, body = self.client.post(path, headers=headers, data=data) self.assertEqual(text, resp.text) - self.assertIsInstance(self.mock.last_request.body, six.string_types) + self.assertIsInstance(self.mock.last_request.body, str) self.assertEqual(data, json.loads(self.mock.last_request.body)) def test_http_chunked_response(self): data = "TEST" path = '/v1/images/' - self.mock.get(self.endpoint + path, body=six.StringIO(data), + self.mock.get(self.endpoint + path, body=io.StringIO(data), headers={"Content-Type": "application/octet-stream"}) resp, body = self.client.get(path) @@ -341,7 +341,7 @@ def test_log_http_response_with_non_ascii_char(self): response = 'Ok' headers = {"Content-Type": "text/plain", "test": "value1\xa5\xa6"} - fake = utils.FakeResponse(headers, six.StringIO(response)) + fake = utils.FakeResponse(headers, io.StringIO(response)) self.client.log_http_response(fake) except UnicodeDecodeError as e: self.fail("Unexpected UnicodeDecodeError exception '%s'" % e) @@ -444,7 +444,7 @@ def test_log_request_id_once(self): logger = self.useFixture(fixtures.FakeLogger(level=logging.DEBUG)) data = "TEST" path = '/v1/images/' - self.mock.get(self.endpoint + path, body=six.StringIO(data), + self.mock.get(self.endpoint + path, body=io.StringIO(data), headers={"Content-Type": "application/octet-stream", 'x-openstack-request-id': "1234"}) diff --git a/glanceclient/tests/unit/test_progressbar.py b/glanceclient/tests/unit/test_progressbar.py index 76f96fb3a..d0596d5e8 100644 --- a/glanceclient/tests/unit/test_progressbar.py +++ b/glanceclient/tests/unit/test_progressbar.py @@ -13,10 +13,10 @@ # License for the specific language governing permissions and limitations # under the License. +import io import sys import requests -import six import testtools from glanceclient.common import progressbar @@ -49,7 +49,7 @@ def test_iter_iterator_display_progress_bar(self): def test_iter_file_display_progress_bar(self): size = 98304 - file_obj = six.StringIO('X' * size) + file_obj = io.StringIO('X' * size) saved_stdout = sys.stdout try: sys.stdout = output = test_utils.FakeTTYStdout() @@ -67,7 +67,7 @@ def test_iter_file_display_progress_bar(self): def test_iter_file_no_tty(self): size = 98304 - file_obj = six.StringIO('X' * size) + file_obj = io.StringIO('X' * size) saved_stdout = sys.stdout try: sys.stdout = output = test_utils.FakeNoTTYStdout() diff --git a/glanceclient/tests/unit/test_shell.py b/glanceclient/tests/unit/test_shell.py index b23591ff4..48232249b 100644 --- a/glanceclient/tests/unit/test_shell.py +++ b/glanceclient/tests/unit/test_shell.py @@ -17,6 +17,7 @@ import argparse from collections import OrderedDict import hashlib +import io import logging import os import sys @@ -28,7 +29,6 @@ from keystoneauth1 import exceptions as ks_exc from keystoneauth1 import fixture as ks_fixture from requests_mock.contrib import fixture as rm_fixture -import six from glanceclient.common import utils from glanceclient import exc @@ -144,8 +144,8 @@ def shell(self, argstr, exitcodes=(0,)): orig = sys.stdout orig_stderr = sys.stderr try: - sys.stdout = six.StringIO() - sys.stderr = six.StringIO() + sys.stdout = io.StringIO() + sys.stderr = io.StringIO() _shell = openstack_shell.OpenStackImagesShell() _shell.main(argstr.split()) except SystemExit: @@ -162,10 +162,7 @@ def shell(self, argstr, exitcodes=(0,)): def test_fixup_subcommand(self): arglist = [u'image-list', u'--help'] - if six.PY2: - expected_arglist = [b'image-list', u'--help'] - elif six.PY3: - expected_arglist = [u'image-list', u'--help'] + expected_arglist = [u'image-list', u'--help'] openstack_shell.OpenStackImagesShell._fixup_subcommand( arglist, arglist @@ -175,14 +172,9 @@ def test_fixup_subcommand(self): def test_fixup_subcommand_with_options_preceding(self): arglist = [u'--os-auth-token', u'abcdef', u'image-list', u'--help'] unknown = arglist[2:] - if six.PY2: - expected_arglist = [ - u'--os-auth-token', u'abcdef', b'image-list', u'--help' - ] - elif six.PY3: - expected_arglist = [ - u'--os-auth-token', u'abcdef', u'image-list', u'--help' - ] + expected_arglist = [ + u'--os-auth-token', u'abcdef', u'image-list', u'--help' + ] openstack_shell.OpenStackImagesShell._fixup_subcommand( unknown, arglist @@ -194,8 +186,8 @@ def test_help_unknown_command(self): argstr = '--os-image-api-version 2 help foofoo' self.assertRaises(exc.CommandError, shell.main, argstr.split()) - @mock.patch('sys.stdout', six.StringIO()) - @mock.patch('sys.stderr', six.StringIO()) + @mock.patch('sys.stdout', io.StringIO()) + @mock.patch('sys.stderr', io.StringIO()) @mock.patch('sys.argv', ['glance', 'help', 'foofoo']) def test_no_stacktrace_when_debug_disabled(self): with mock.patch.object(traceback, 'print_exc') as mock_print_exc: @@ -205,8 +197,8 @@ def test_no_stacktrace_when_debug_disabled(self): pass self.assertFalse(mock_print_exc.called) - @mock.patch('sys.stdout', six.StringIO()) - @mock.patch('sys.stderr', six.StringIO()) + @mock.patch('sys.stdout', io.StringIO()) + @mock.patch('sys.stderr', io.StringIO()) @mock.patch('sys.argv', ['glance', 'help', 'foofoo']) def test_stacktrace_when_debug_enabled_by_env(self): old_environment = os.environ.copy() @@ -221,8 +213,8 @@ def test_stacktrace_when_debug_enabled_by_env(self): finally: os.environ = old_environment - @mock.patch('sys.stdout', six.StringIO()) - @mock.patch('sys.stderr', six.StringIO()) + @mock.patch('sys.stdout', io.StringIO()) + @mock.patch('sys.stderr', io.StringIO()) @mock.patch('sys.argv', ['glance', '--debug', 'help', 'foofoo']) def test_stacktrace_when_debug_enabled(self): with mock.patch.object(traceback, 'print_exc') as mock_print_exc: @@ -589,8 +581,8 @@ def test_auth_plugin_invocation_without_tenant_with_v2(self, v2_client, self.assertRaises(exc.CommandError, glance_shell.main, args.split()) @mock.patch('sys.argv', ['glance']) - @mock.patch('sys.stdout', six.StringIO()) - @mock.patch('sys.stderr', six.StringIO()) + @mock.patch('sys.stdout', io.StringIO()) + @mock.patch('sys.stderr', io.StringIO()) def test_main_noargs(self): # Ensure that main works with no command-line arguments try: @@ -781,7 +773,7 @@ def __init__(self, entries): return Args(args) - @mock.patch('six.moves.builtins.open', new=mock.mock_open(), create=True) + @mock.patch('builtins.open', new=mock.mock_open(), create=True) @mock.patch('os.path.exists', return_value=True) def test_cache_schemas_gets_when_forced(self, exists_mock): options = { @@ -804,7 +796,7 @@ def test_cache_schemas_gets_when_forced(self, exists_mock): actual = json.loads(open.mock_calls[6][1][0]) self.assertEqual(schema_odict, actual) - @mock.patch('six.moves.builtins.open', new=mock.mock_open(), create=True) + @mock.patch('builtins.open', new=mock.mock_open(), create=True) @mock.patch('os.path.exists', side_effect=[True, False, False, False]) def test_cache_schemas_gets_when_not_exists(self, exists_mock): options = { @@ -827,7 +819,7 @@ def test_cache_schemas_gets_when_not_exists(self, exists_mock): actual = json.loads(open.mock_calls[6][1][0]) self.assertEqual(schema_odict, actual) - @mock.patch('six.moves.builtins.open', new=mock.mock_open(), create=True) + @mock.patch('builtins.open', new=mock.mock_open(), create=True) @mock.patch('os.path.exists', return_value=True) def test_cache_schemas_leaves_when_present_not_forced(self, exists_mock): options = { @@ -848,7 +840,7 @@ def test_cache_schemas_leaves_when_present_not_forced(self, exists_mock): self.assertEqual(4, exists_mock.call_count) self.assertEqual(0, open.mock_calls.__len__()) - @mock.patch('six.moves.builtins.open', new=mock.mock_open(), create=True) + @mock.patch('builtins.open', new=mock.mock_open(), create=True) @mock.patch('os.path.exists', return_value=True) def test_cache_schemas_leaves_auto_switch(self, exists_mock): options = { @@ -899,7 +891,7 @@ def test_download_has_no_stray_output_to_stdout(self): headers = {'Content-Length': '4', 'Content-type': 'application/octet-stream'} - fake = testutils.FakeResponse(headers, six.StringIO('DATA')) + fake = testutils.FakeResponse(headers, io.StringIO('DATA')) self.requests.get('http://example.com/v1/images/%s' % id, raw=fake) @@ -938,7 +930,7 @@ def test_v1_download_has_no_stray_output_to_stdout(self): headers = {'Content-Length': '4', 'Content-type': 'application/octet-stream'} - fake = testutils.FakeResponse(headers, six.StringIO('DATA')) + fake = testutils.FakeResponse(headers, io.StringIO('DATA')) self.requests.get('http://example.com/v1/images/%s' % id, headers=headers, raw=fake) @@ -960,7 +952,7 @@ def test_v2_download_has_no_stray_output_to_stdout(self): id = image_show_fixture['id'] headers = {'Content-Length': '4', 'Content-type': 'application/octet-stream'} - fake = testutils.FakeResponse(headers, six.StringIO('DATA')) + fake = testutils.FakeResponse(headers, io.StringIO('DATA')) self.requests = self.useFixture(rm_fixture.Fixture()) self.requests.get('http://example.com/v2/images/%s/file' % id, diff --git a/glanceclient/tests/unit/test_ssl.py b/glanceclient/tests/unit/test_ssl.py index 320dfde11..f95e77773 100644 --- a/glanceclient/tests/unit/test_ssl.py +++ b/glanceclient/tests/unit/test_ssl.py @@ -16,7 +16,6 @@ import os from unittest import mock -import six import ssl import testtools import threading @@ -26,10 +25,7 @@ from glanceclient import v1 from glanceclient import v2 -if six.PY3 is True: - import socketserver -else: - import SocketServer as socketserver +import socketserver TEST_VAR_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), @@ -217,9 +213,7 @@ def test_v2_requests_bad_cert(self, __): # starting from python 2.7.8 the way to handle loading private # keys into the SSL_CTX was changed and error message become # similar to the one in 3.X - if (six.PY2 and 'PrivateKey' not in e.message and - 'PEM lib' not in e.message or - six.PY3 and 'PEM lib' not in e.message): + if 'PEM lib' not in e.message: self.fail('No appropriate failure message is received') @mock.patch('sys.stderr') diff --git a/glanceclient/tests/unit/test_utils.py b/glanceclient/tests/unit/test_utils.py index 6128d6c9b..46cefbf6a 100644 --- a/glanceclient/tests/unit/test_utils.py +++ b/glanceclient/tests/unit/test_utils.py @@ -13,14 +13,13 @@ # License for the specific language governing permissions and limitations # under the License. +import io import sys from unittest import mock from oslo_utils import encodeutils from requests import Response -import six # NOTE(jokke): simplified transition to py3, behaves like py2 xrange -from six.moves import range import testtools from glanceclient.common import utils @@ -47,7 +46,7 @@ def test_make_size_human_readable(self): def test_get_new_file_size(self): size = 98304 - file_obj = six.StringIO('X' * size) + file_obj = io.StringIO('X' * size) try: self.assertEqual(size, utils.get_file_size(file_obj)) # Check that get_file_size didn't change original file position. @@ -57,7 +56,7 @@ def test_get_new_file_size(self): def test_get_consumed_file_size(self): size, consumed = 98304, 304 - file_obj = six.StringIO('X' * size) + file_obj = io.StringIO('X' * size) file_obj.seek(consumed) try: self.assertEqual(size, utils.get_file_size(file_obj)) @@ -79,10 +78,10 @@ def __init__(self, **entries): saved_stdout = sys.stdout try: - sys.stdout = output_list = six.StringIO() + sys.stdout = output_list = io.StringIO() utils.print_list(images, columns) - sys.stdout = output_dict = six.StringIO() + sys.stdout = output_dict = io.StringIO() utils.print_dict({'K': 'k', 'Key': 'veeeeeeeeeeeeeeeeeeeeeeee' 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' @@ -126,7 +125,7 @@ def __init__(self, **entries): 'tags': [u'Name1', u'Tag_123', u'veeeery long']})] saved_stdout = sys.stdout try: - sys.stdout = output_list = six.StringIO() + sys.stdout = output_list = io.StringIO() utils.print_list(images, columns) finally: @@ -145,7 +144,7 @@ def test_print_image_virtual_size_available(self): image = {'id': '42', 'virtual_size': 1337} saved_stdout = sys.stdout try: - sys.stdout = output_list = six.StringIO() + sys.stdout = output_list = io.StringIO() utils.print_image(image) finally: sys.stdout = saved_stdout @@ -164,7 +163,7 @@ def test_print_image_virtual_size_not_available(self): image = {'id': '42', 'virtual_size': None} saved_stdout = sys.stdout try: - sys.stdout = output_list = six.StringIO() + sys.stdout = output_list = io.StringIO() utils.print_image(image) finally: sys.stdout = saved_stdout @@ -181,13 +180,9 @@ def test_print_image_virtual_size_not_available(self): def test_unicode_key_value_to_string(self): src = {u'key': u'\u70fd\u7231\u5a77'} - expected = {'key': '\xe7\x83\xbd\xe7\x88\xb1\xe5\xa9\xb7'} - if six.PY2: - self.assertEqual(expected, utils.unicode_key_value_to_string(src)) - else: - # u'xxxx' in PY3 is str, we will not get extra 'u' from cli - # output in PY3 - self.assertEqual(src, utils.unicode_key_value_to_string(src)) + # u'xxxx' in PY3 is str, we will not get extra 'u' from cli + # output in PY3 + self.assertEqual(src, utils.unicode_key_value_to_string(src)) def test_schema_args_with_list_types(self): # NOTE(flaper87): Regression for bug @@ -240,7 +235,7 @@ def _iterate(i): for chunk in i: raise(IOError) - data = six.moves.StringIO('somestring') + data = io.StringIO('somestring') data.close = mock.Mock() i = utils.IterableWithLength(data, 10) self.assertRaises(IOError, _iterate, i) diff --git a/glanceclient/tests/unit/v1/test_images.py b/glanceclient/tests/unit/v1/test_images.py index 1f43b8370..1af74125d 100644 --- a/glanceclient/tests/unit/v1/test_images.py +++ b/glanceclient/tests/unit/v1/test_images.py @@ -14,11 +14,10 @@ # under the License. import errno +import io import json import testtools - -import six -from six.moves.urllib import parse +from urllib import parse from glanceclient.tests import utils from glanceclient.v1 import client @@ -634,7 +633,7 @@ def test_create_without_data(self): self.assertEqual({'a': 'b', 'c': 'd'}, image.properties) def test_create_with_data(self): - image_data = six.StringIO('XXX') + image_data = io.StringIO('XXX') self.mgr.create(data=image_data) expect_headers = {'x-image-meta-size': '3'} expect = [('POST', '/v1/images', expect_headers, image_data)] @@ -711,7 +710,7 @@ def test_update(self): self.assertEqual(10, image.min_disk) def test_update_with_data(self): - image_data = six.StringIO('XXX') + image_data = io.StringIO('XXX') self.mgr.update('1', data=image_data) expect_headers = {'x-image-meta-size': '3', 'x-glance-registry-purge-props': 'false'} @@ -744,10 +743,7 @@ def test_update_req_id(self): def test_image_meta_from_headers_encoding(self): value = u"ni\xf1o" - if six.PY2: - fields = {"x-image-meta-name": "ni\xc3\xb1o"} - else: - fields = {"x-image-meta-name": value} + fields = {"x-image-meta-name": value} headers = self.mgr._image_meta_from_headers(fields) self.assertEqual(value, headers["name"]) diff --git a/glanceclient/tests/unit/v1/test_shell.py b/glanceclient/tests/unit/v1/test_shell.py index 5476a2faf..46c6f68c7 100644 --- a/glanceclient/tests/unit/v1/test_shell.py +++ b/glanceclient/tests/unit/v1/test_shell.py @@ -15,11 +15,11 @@ # under the License. import argparse +import io import json import os from unittest import mock -import six import subprocess import tempfile import testtools @@ -34,12 +34,6 @@ from glanceclient.tests import utils -if six.PY3: - import io - file_type = io.IOBase -else: - file_type = file - fixtures = { '/v1/images/96d2c7e1-de4e-4612-8aa2-ba26610c804e': { 'PUT': ( @@ -351,7 +345,7 @@ def test_image_create_missing_container_format(self, __): @mock.patch('sys.stderr') def test_image_create_missing_container_format_stdin_data(self, __): # Fake that get_data_file method returns data - self.mock_get_data_file.return_value = six.StringIO() + self.mock_get_data_file.return_value = io.StringIO() e = self.assertRaises(exc.CommandError, self.run_command, '--os-image-api-version 1 image-create' ' --disk-format qcow2') @@ -361,7 +355,7 @@ def test_image_create_missing_container_format_stdin_data(self, __): @mock.patch('sys.stderr') def test_image_create_missing_disk_format_stdin_data(self, __): # Fake that get_data_file method returns data - self.mock_get_data_file.return_value = six.StringIO() + self.mock_get_data_file.return_value = io.StringIO() e = self.assertRaises(exc.CommandError, self.run_command, '--os-image-api-version 1 image-create' ' --container-format bare') @@ -574,7 +568,7 @@ def test_image_update_data_is_read_from_file(self): self._do_update('44d2c7e1-de4e-4612-8aa2-ba26610c444f') self.assertIn('data', self.collected_args[1]) - self.assertIsInstance(self.collected_args[1]['data'], file_type) + self.assertIsInstance(self.collected_args[1]['data'], io.IOBase) self.assertEqual(b'Some Data', self.collected_args[1]['data'].read()) @@ -599,7 +593,7 @@ def test_image_update_data_is_read_from_pipe(self): self._do_update('44d2c7e1-de4e-4612-8aa2-ba26610c444f') self.assertIn('data', self.collected_args[1]) - self.assertIsInstance(self.collected_args[1]['data'], file_type) + self.assertIsInstance(self.collected_args[1]['data'], io.IOBase) self.assertEqual(b'Some Data\n', self.collected_args[1]['data'].read()) diff --git a/glanceclient/tests/unit/v2/test_shell_v2.py b/glanceclient/tests/unit/v2/test_shell_v2.py index 9c1955170..4cf4d25dc 100644 --- a/glanceclient/tests/unit/v2/test_shell_v2.py +++ b/glanceclient/tests/unit/v2/test_shell_v2.py @@ -15,11 +15,11 @@ # under the License. import argparse from copy import deepcopy +import io import json import os from unittest import mock -import six import sys import tempfile import testtools @@ -196,7 +196,7 @@ def test_image_create_missing_container_format(self, __): @mock.patch('sys.stderr') def test_image_create_missing_container_format_stdin_data(self, __): # Fake that get_data_file method returns data - self.mock_get_data_file.return_value = six.StringIO() + self.mock_get_data_file.return_value = io.StringIO() e = self.assertRaises(exc.CommandError, self._run_command, '--os-image-api-version 2 image-create' ' --disk-format qcow2') @@ -206,7 +206,7 @@ def test_image_create_missing_container_format_stdin_data(self, __): @mock.patch('sys.stderr') def test_image_create_missing_disk_format_stdin_data(self, __): # Fake that get_data_file method returns data - self.mock_get_data_file.return_value = six.StringIO() + self.mock_get_data_file.return_value = io.StringIO() e = self.assertRaises(exc.CommandError, self._run_command, '--os-image-api-version 2 image-create' ' --container-format bare') @@ -618,7 +618,7 @@ def test_do_image_create_for_none_multi_hash(self, mock_stdin): 'os_hash_value': None}) def test_do_image_create_with_multihash(self): - self.mock_get_data_file.return_value = six.StringIO() + self.mock_get_data_file.return_value = io.StringIO() try: with open(tempfile.mktemp(), 'w+') as f: f.write('Some data here') @@ -694,7 +694,7 @@ def test_do_image_create_hidden_image(self, mock_stdin): 'container_format': 'bare', 'os_hidden': True}) def test_do_image_create_with_file(self): - self.mock_get_data_file.return_value = six.StringIO() + self.mock_get_data_file.return_value = io.StringIO() try: file_name = None with open(tempfile.mktemp(), 'w+') as f: @@ -1412,7 +1412,7 @@ def test_image_create_via_import_no_method_with_stdin( self, mock_stdin, mock_do_stage, mock_do_import): """Backward compat -> handle this like a glance-direct""" mock_stdin.isatty = lambda: False - self.mock_get_data_file.return_value = six.StringIO() + self.mock_get_data_file.return_value = io.StringIO() args = self._make_args(self.base_args) with mock.patch.object(self.gc.images, 'create') as mocked_create: with mock.patch.object(self.gc.images, 'get') as mocked_get: @@ -1447,7 +1447,7 @@ def test_image_create_via_import_no_method_passing_file( self, mock_stdin, mock_access, mock_do_stage, mock_do_import): """Backward compat -> handle this like a glance-direct""" mock_stdin.isatty = lambda: True - self.mock_get_data_file.return_value = six.StringIO() + self.mock_get_data_file.return_value = io.StringIO() mock_access.return_value = True my_args = self.base_args.copy() my_args['file'] = 'fake-image-file.browncow' diff --git a/glanceclient/tests/utils.py b/glanceclient/tests/utils.py index 3deddb1c3..730b92843 100644 --- a/glanceclient/tests/utils.py +++ b/glanceclient/tests/utils.py @@ -14,10 +14,10 @@ # under the License. import copy +import io import json -import six -import six.moves.urllib.parse as urlparse import testtools +from urllib import parse from glanceclient.v2 import schemas @@ -38,11 +38,11 @@ def _request(self, method, url, headers=None, data=None, fixture = self.fixtures[sort_url_by_query_keys(url)][method] data = fixture[1] - if isinstance(fixture[1], six.string_types): + if isinstance(fixture[1], str): try: data = json.loads(fixture[1]) except ValueError: - data = six.StringIO(fixture[1]) + data = io.StringIO(fixture[1]) return FakeResponse(fixture[0], fixture[1]), data @@ -141,7 +141,7 @@ def content(self): @property def text(self): - if isinstance(self.content, six.binary_type): + if isinstance(self.content, bytes): return self.content.decode('utf-8') return self.content @@ -166,7 +166,7 @@ class TestCase(testtools.TestCase): 'verify': True} -class FakeTTYStdout(six.StringIO): +class FakeTTYStdout(io.StringIO): """A Fake stdout that try to emulate a TTY device as much as possible.""" def isatty(self): @@ -177,7 +177,7 @@ def write(self, data): if data.startswith('\r'): self.seek(0) data = data[1:] - return six.StringIO.write(self, data) + return io.StringIO.write(self, data) class FakeNoTTYStdout(FakeTTYStdout): @@ -197,24 +197,24 @@ def sort_url_by_query_keys(url): :param url: url which will be ordered by query keys :returns url: url with ordered query keys """ - parsed = urlparse.urlparse(url) - queries = urlparse.parse_qsl(parsed.query, True) + parsed = parse.urlparse(url) + queries = parse.parse_qsl(parsed.query, True) sorted_query = sorted(queries, key=lambda x: x[0]) - encoded_sorted_query = urlparse.urlencode(sorted_query, True) + encoded_sorted_query = parse.urlencode(sorted_query, True) url_parts = (parsed.scheme, parsed.netloc, parsed.path, parsed.params, encoded_sorted_query, parsed.fragment) - return urlparse.urlunparse(url_parts) + return parse.urlunparse(url_parts) def build_call_record(method, url, headers, data): """Key the request body be ordered if it's a dict type.""" if isinstance(data, dict): data = sorted(data.items()) - if isinstance(data, six.string_types): + if isinstance(data, str): # NOTE(flwang): For image update, the data will be a 'list' which # contains operation dict, such as: [{"op": "remove", "path": "/a"}] try: From b513c8db4bf6a5b2eb0366117ca22e042dd3529e Mon Sep 17 00:00:00 2001 From: Cyril Roelandt Date: Mon, 15 Jun 2020 21:28:17 +0200 Subject: [PATCH 065/167] Do not use the six library. Change-Id: I3dbfcfa0f5f590a41ed549afd44537d8ed41433a --- glanceclient/common/http.py | 24 ++++++---------- glanceclient/common/https.py | 21 ++++---------- glanceclient/common/progressbar.py | 4 +-- glanceclient/common/utils.py | 21 ++++++-------- glanceclient/exc.py | 5 +--- glanceclient/shell.py | 38 ++----------------------- glanceclient/tests/unit/test_shell.py | 21 -------------- glanceclient/v1/apiclient/base.py | 12 ++++---- glanceclient/v1/apiclient/exceptions.py | 4 +-- glanceclient/v1/apiclient/utils.py | 6 +--- glanceclient/v1/images.py | 13 ++++----- glanceclient/v2/images.py | 15 +++++----- glanceclient/v2/metadefs.py | 9 +++--- glanceclient/v2/tasks.py | 7 +++-- lower-constraints.txt | 1 - requirements.txt | 1 - tox.ini | 2 +- 17 files changed, 57 insertions(+), 147 deletions(-) diff --git a/glanceclient/common/http.py b/glanceclient/common/http.py index 6973f60ee..76ad0c392 100644 --- a/glanceclient/common/http.py +++ b/glanceclient/common/http.py @@ -14,6 +14,7 @@ # under the License. import copy +import io import logging import socket @@ -23,8 +24,7 @@ from oslo_utils import importutils from oslo_utils import netutils import requests -import six -import six.moves.urllib.parse as urlparse +import urllib.parse try: import json @@ -66,19 +66,13 @@ def encode_headers(headers): for h, v in headers.items(): if v is not None: # if the item is token, do not quote '+' as well. - # NOTE(imacdonn): urlparse.quote() is intended for quoting the + # NOTE(imacdonn): urllib.parse.quote() is intended for quoting the # path part of a URL, but headers like x-image-meta-location # include an entire URL. We should avoid encoding the colon in # this case (bug #1788942) safe = '=+/' if h in TOKEN_HEADERS else '/:' - if six.PY2: - # incoming items may be unicode, so get them into something - # the py2 version of urllib can handle before percent encoding - key = urlparse.quote(encodeutils.safe_encode(h), safe) - value = urlparse.quote(encodeutils.safe_encode(v), safe) - else: - key = urlparse.quote(h, safe) - value = urlparse.quote(v, safe) + key = urllib.parse.quote(h, safe) + value = urllib.parse.quote(v, safe) encoded_dict[key] = value return dict((encodeutils.safe_encode(h, encoding='ascii'), encodeutils.safe_encode(v, encoding='ascii')) @@ -105,7 +99,7 @@ def _set_common_request_kwargs(self, headers, kwargs): # NOTE(jamielennox): remove this later. Managers should pass json= if # they want to send json data. data = kwargs.pop("data", None) - if data is not None and not isinstance(data, six.string_types): + if data is not None and not isinstance(data, str): try: data = json.dumps(data) content_type = 'application/json' @@ -143,7 +137,7 @@ def _handle_response(self, resp): # response encoding body_iter = resp.json() else: - body_iter = six.StringIO(content) + body_iter = io.StringIO(content) try: body_iter = json.loads(''.join([c for c in body_iter])) except ValueError: @@ -209,13 +203,13 @@ def log_curl_request(self, method, url, headers, data, kwargs): if not self.session.verify: curl.append('-k') else: - if isinstance(self.session.verify, six.string_types): + if isinstance(self.session.verify, str): curl.append(' --cacert %s' % self.session.verify) if self.session.cert: curl.append(' --cert %s --key %s' % self.session.cert) - if data and isinstance(data, six.string_types): + if data and isinstance(data, str): curl.append('-d \'%s\'' % data) curl.append(url) diff --git a/glanceclient/common/https.py b/glanceclient/common/https.py index deb7eb03f..94aeeb32d 100644 --- a/glanceclient/common/https.py +++ b/glanceclient/common/https.py @@ -20,10 +20,6 @@ import OpenSSL -import six -# NOTE(jokke): simplified transition to py3, behaves like py2 xrange -from six.moves import range - try: from eventlet import patcher # Handle case where we are running in a monkey patched environment @@ -33,9 +29,9 @@ else: raise ImportError except ImportError: + import http.client from OpenSSL import SSL - from six.moves import http_client - HTTPSConnection = http_client.HTTPSConnection + HTTPSConnection = http.client.HTTPSConnection Connection = SSL.Connection @@ -120,8 +116,8 @@ def check_match(name): def to_bytes(s): - if isinstance(s, six.string_types): - return six.b(s) + if isinstance(s, str): + return bytes(s, 'latin-1') else: return s @@ -161,14 +157,7 @@ def __init__(self, host, port=None, key_file=None, cert_file=None, ssl_compression=True): # List of exceptions reported by Python3 instead of # SSLConfigurationError - if six.PY3: - excp_lst = (TypeError, FileNotFoundError, ssl.SSLError) - else: - # NOTE(jamespage) - # Accommodate changes in behaviour for pep-0467, introduced - # in python 2.7.9. - # https://github.com/python/peps/blob/master/pep-0476.txt - excp_lst = (TypeError, IOError, ssl.SSLError) + excp_lst = (TypeError, FileNotFoundError, ssl.SSLError) try: HTTPSConnection.__init__(self, host, port, key_file=key_file, diff --git a/glanceclient/common/progressbar.py b/glanceclient/common/progressbar.py index 6cf0df393..bb8b21b46 100644 --- a/glanceclient/common/progressbar.py +++ b/glanceclient/common/progressbar.py @@ -15,8 +15,6 @@ import sys -import six - class _ProgressBarBase(object): """A progress bar provider for a wrapped obect. @@ -83,7 +81,7 @@ def __iter__(self): def next(self): try: - data = six.next(self._wrapped) + data = next(self._wrapped) # NOTE(mouad): Assuming that data is a string b/c otherwise calling # len function will not make any sense. self._display_progress_bar(len(data)) diff --git a/glanceclient/common/utils.py b/glanceclient/common/utils.py index 0fde763f1..20256b8ae 100644 --- a/glanceclient/common/utils.py +++ b/glanceclient/common/utils.py @@ -21,12 +21,11 @@ import json import os import re -import six.moves.urllib.parse as urlparse import sys import threading +import urllib.parse import uuid -import six if os.name == 'nt': # noqa import msvcrt # noqa @@ -161,7 +160,7 @@ def _decorator(func): # for the `join` to succeed. Enum types can also be `None` # therefore, join's call would fail without the following # list comprehension - vals = [six.text_type(val) for val in property.get('enum')] + vals = [str(val) for val in property.get('enum')] description += ('Valid values: ' + ', '.join(vals)) kwargs['help'] = description @@ -216,8 +215,6 @@ def print_list(objs, fields, formatters=None, field_settings=None): def _encode(src): """remove extra 'u' in PY2.""" - if six.PY2 and isinstance(src, unicode): - return src.encode('utf-8') return src @@ -345,7 +342,7 @@ def get_file_size(file_obj): :retval: The file's size or None if it cannot be determined. """ if (hasattr(file_obj, 'seek') and hasattr(file_obj, 'tell') and - (six.PY2 or six.PY3 and file_obj.seekable())): + file_obj.seekable()): try: curr = file_obj.tell() file_obj.seek(0, os.SEEK_END) @@ -401,13 +398,13 @@ def strip_version(endpoint): # we make endpoint the first argument. However, we # can't do that just yet because we need to keep # backwards compatibility. - if not isinstance(endpoint, six.string_types): + if not isinstance(endpoint, str): raise ValueError("Expected endpoint") version = None # Get rid of trailing '/' if present endpoint = endpoint.rstrip('/') - url_parts = urlparse.urlparse(endpoint) + url_parts = urllib.parse.urlparse(endpoint) (scheme, netloc, path, __, __, __) = url_parts path = path.lstrip('/') # regex to match 'v1' or 'v2.0' etc @@ -446,8 +443,8 @@ def integrity_iter(iter, checksum): for chunk in iter: yield chunk - if isinstance(chunk, six.string_types): - chunk = six.b(chunk) + if isinstance(chunk, str): + chunk = bytes(chunk, 'latin-1') md5sum.update(chunk) md5sum = md5sum.hexdigest() if md5sum != checksum: @@ -466,8 +463,8 @@ def serious_integrity_iter(iter, hasher, hash_value): """ for chunk in iter: yield chunk - if isinstance(chunk, six.string_types): - chunk = six.b(chunk) + if isinstance(chunk, str): + chunk = bytes(chunk, 'latin-1') hasher.update(chunk) computed = hasher.hexdigest() if computed != hash_value: diff --git a/glanceclient/exc.py b/glanceclient/exc.py index eee47cacf..5efe8056e 100644 --- a/glanceclient/exc.py +++ b/glanceclient/exc.py @@ -16,8 +16,6 @@ import re import sys -import six - class BaseException(Exception): """An error occurred.""" @@ -179,8 +177,7 @@ def from_response(response, body=None): details = ': '.join(details_temp) return cls(details=details) elif body: - if six.PY3: - body = body.decode('utf-8') + body = body.decode('utf-8') details = body.replace('\n\n', '\n') return cls(details=details) diff --git a/glanceclient/shell.py b/glanceclient/shell.py index 3dfa14a6e..d0028fe24 100644 --- a/glanceclient/shell.py +++ b/glanceclient/shell.py @@ -31,8 +31,7 @@ from oslo_utils import encodeutils from oslo_utils import importutils -import six -import six.moves.urllib.parse as urlparse +import urllib.parse import glanceclient from glanceclient._i18n import _ @@ -259,7 +258,7 @@ def _discover_auth_versions(self, session, auth_url): except ks_exc.ClientException as e: # Identity service may not support discover API version. # Lets trying to figure out the API version from the original URL. - url_parts = urlparse.urlparse(auth_url) + url_parts = urllib.parse.urlparse(auth_url) (scheme, netloc, path, params, query, fragment) = url_parts path = path.lower() if path.startswith('/v3'): @@ -522,12 +521,6 @@ def _get_subparser(api_version): self.do_help(options, parser=parser) return 0 - # NOTE(sigmavirus24): Above, args is defined as the left over - # arguments from parser.parse_known_args(). This allows us to - # skip any parameters to command-line flags that may have been passed - # to glanceclient, e.g., --os-auth-token. - self._fixup_subcommand(args, argv) - # short-circuit and deal with help command right away. sub_parser = _get_subparser(api_version) args = sub_parser.parse_args(argv) @@ -598,33 +591,6 @@ def _get_subparser(api_version): print("To display trace use next command:\n" "osprofiler trace show --html %s " % trace_id) - @staticmethod - def _fixup_subcommand(unknown_args, argv): - # NOTE(sigmavirus24): Sometimes users pass the wrong subcommand name - # to glanceclient. If they're using Python 2 they will see an error: - # > invalid choice: u'imgae-list' (choose from ...) - # To avoid this, we look at the extra args already parsed from above - # and try to predict what the subcommand will be based on it being the - # first non - or -- prefixed argument in args. We then find that in - # argv and encode it from unicode so users don't see the pesky `u'` - # prefix. - for arg in unknown_args: - if not arg.startswith('-'): # This will cover both - and -- - subcommand_name = arg - break - else: - subcommand_name = '' - - if (subcommand_name and six.PY2 and - isinstance(subcommand_name, six.text_type)): - # NOTE(sigmavirus24): if we found a subcommand name, then let's - # find it in the argv list and replace it with a bytes object - # instead. Note, that if we encode the argument on Python 3, the - # user will instead see a pesky `b'` string instead of the `u'` - # string we mention above. - subcommand_index = argv.index(subcommand_name) - argv[subcommand_index] = encodeutils.safe_encode(subcommand_name) - @utils.arg('command', metavar='', nargs='?', help='Display help for .') def do_help(self, args, parser): diff --git a/glanceclient/tests/unit/test_shell.py b/glanceclient/tests/unit/test_shell.py index 48232249b..74d1c3339 100644 --- a/glanceclient/tests/unit/test_shell.py +++ b/glanceclient/tests/unit/test_shell.py @@ -160,27 +160,6 @@ def shell(self, argstr, exitcodes=(0,)): sys.stderr = orig_stderr return (stdout, stderr) - def test_fixup_subcommand(self): - arglist = [u'image-list', u'--help'] - expected_arglist = [u'image-list', u'--help'] - - openstack_shell.OpenStackImagesShell._fixup_subcommand( - arglist, arglist - ) - self.assertEqual(expected_arglist, arglist) - - def test_fixup_subcommand_with_options_preceding(self): - arglist = [u'--os-auth-token', u'abcdef', u'image-list', u'--help'] - unknown = arglist[2:] - expected_arglist = [ - u'--os-auth-token', u'abcdef', u'image-list', u'--help' - ] - - openstack_shell.OpenStackImagesShell._fixup_subcommand( - unknown, arglist - ) - self.assertEqual(expected_arglist, arglist) - def test_help_unknown_command(self): shell = openstack_shell.OpenStackImagesShell() argstr = '--os-image-api-version 2 help foofoo' diff --git a/glanceclient/v1/apiclient/base.py b/glanceclient/v1/apiclient/base.py index a12a8cc08..165e6de16 100644 --- a/glanceclient/v1/apiclient/base.py +++ b/glanceclient/v1/apiclient/base.py @@ -41,8 +41,7 @@ import copy from oslo_utils import strutils -import six -from six.moves.urllib import parse +import urllib.parse from glanceclient._i18n import _ from glanceclient.v1.apiclient import exceptions @@ -224,8 +223,7 @@ def _delete(self, url): return self.client.delete(url) -@six.add_metaclass(abc.ABCMeta) -class ManagerWithFind(BaseManager): +class ManagerWithFind(BaseManager, metaclass=abc.ABCMeta): """Manager with additional `find()`/`findall()` methods.""" @abc.abstractmethod @@ -350,10 +348,11 @@ def list(self, base_url=None, **kwargs): """ kwargs = self._filter_kwargs(kwargs) + query = urllib.parse.urlencode(kwargs) if kwargs else '', return self._list( '%(base_url)s%(query)s' % { 'base_url': self.build_url(base_url=base_url, **kwargs), - 'query': '?%s' % parse.urlencode(kwargs) if kwargs else '', + 'query': '?%s' % query, }, self.collection_key) @@ -389,10 +388,11 @@ def find(self, base_url=None, **kwargs): """ kwargs = self._filter_kwargs(kwargs) + query = urllib.parse.urlencode(kwargs) if kwargs else '', rl = self._list( '%(base_url)s%(query)s' % { 'base_url': self.build_url(base_url=base_url, **kwargs), - 'query': '?%s' % parse.urlencode(kwargs) if kwargs else '', + 'query': '?%s' % query, }, self.collection_key) num = len(rl) diff --git a/glanceclient/v1/apiclient/exceptions.py b/glanceclient/v1/apiclient/exceptions.py index f3d778cd9..27b3ebab2 100644 --- a/glanceclient/v1/apiclient/exceptions.py +++ b/glanceclient/v1/apiclient/exceptions.py @@ -36,8 +36,6 @@ import inspect import sys -import six - from glanceclient._i18n import _ @@ -461,7 +459,7 @@ def from_response(response, method, url): kwargs["message"] = (error.get("message") or error.get("faultstring")) kwargs["details"] = (error.get("details") or - six.text_type(body)) + str(body)) elif content_type.startswith("text/"): kwargs["details"] = getattr(response, 'text', '') diff --git a/glanceclient/v1/apiclient/utils.py b/glanceclient/v1/apiclient/utils.py index 814a37bff..7d3e4e057 100644 --- a/glanceclient/v1/apiclient/utils.py +++ b/glanceclient/v1/apiclient/utils.py @@ -26,7 +26,6 @@ from oslo_utils import encodeutils from oslo_utils import uuidutils -import six from glanceclient._i18n import _ from glanceclient.v1.apiclient import exceptions @@ -52,10 +51,7 @@ def _find_hypervisor(cs, hypervisor): # now try to get entity as uuid try: - if six.PY2: - tmp_id = encodeutils.safe_encode(name_or_id) - else: - tmp_id = encodeutils.safe_decode(name_or_id) + tmp_id = encodeutils.safe_decode(name_or_id) if uuidutils.is_uuid_like(tmp_id): return manager.get(tmp_id) diff --git a/glanceclient/v1/images.py b/glanceclient/v1/images.py index 966d45a4e..b8e2ee2c0 100644 --- a/glanceclient/v1/images.py +++ b/glanceclient/v1/images.py @@ -17,8 +17,7 @@ from oslo_utils import encodeutils from oslo_utils import strutils -import six -import six.moves.urllib.parse as urlparse +import urllib.parse from glanceclient.common import utils from glanceclient.v1.apiclient import base @@ -101,7 +100,7 @@ def _image_meta_to_headers(self, fields): # headers will be encoded later, before the # request is sent. def to_str(value): - if not isinstance(value, six.string_types): + if not isinstance(value, str): return str(value) return value @@ -129,7 +128,7 @@ def get(self, image, **kwargs): """ image_id = base.getid(image) resp, body = self.client.head('/v1/images/%s' - % urlparse.quote(str(image_id))) + % urllib.parse.quote(str(image_id))) meta = self._image_meta_from_headers(resp.headers) return_request_id = kwargs.get('return_req_id', None) if return_request_id is not None: @@ -145,7 +144,7 @@ def data(self, image, do_checksum=True, **kwargs): """ image_id = base.getid(image) resp, body = self.client.get('/v1/images/%s' - % urlparse.quote(str(image_id))) + % urllib.parse.quote(str(image_id))) content_length = int(resp.headers.get('content-length', 0)) checksum = resp.headers.get('x-image-meta-checksum', None) if do_checksum and checksum is not None: @@ -225,7 +224,7 @@ def filter_owner(owner, image): def paginate(qp, return_request_id=None): for param, value in qp.items(): - if isinstance(value, six.string_types): + if isinstance(value, str): # Note(flaper87) Url encoding should # be moved inside http utils, at least # shouldn't be here. @@ -234,7 +233,7 @@ def paginate(qp, return_request_id=None): # trying to encode them qp[param] = encodeutils.safe_decode(value) - url = '/v1/images/detail?%s' % urlparse.urlencode(qp) + url = '/v1/images/detail?%s' % urllib.parse.urlencode(qp) images, resp = self._list(url, "images") if return_request_id is not None: diff --git a/glanceclient/v2/images.py b/glanceclient/v2/images.py index b07eecc2a..341485db9 100644 --- a/glanceclient/v2/images.py +++ b/glanceclient/v2/images.py @@ -17,8 +17,7 @@ import json from oslo_utils import encodeutils from requests import codes -import six -from six.moves.urllib import parse +import urllib.parse import warlock from glanceclient.common import utils @@ -55,7 +54,7 @@ def unvalidated_model(self): @staticmethod def _wrap(value): - if isinstance(value, six.string_types): + if isinstance(value, str): return [value] return value @@ -142,19 +141,19 @@ def paginate(url, page_size, limit=None): tags_url_params = [] for tag in tags: - if not isinstance(tag, six.string_types): + if not isinstance(tag, str): raise exc.HTTPBadRequest("Invalid tag value %s" % tag) tags_url_params.append({'tag': encodeutils.safe_encode(tag)}) for param, value in filters.items(): - if isinstance(value, six.string_types): + if isinstance(value, str): filters[param] = encodeutils.safe_encode(value) - url = '/v2/images?%s' % parse.urlencode(filters) + url = '/v2/images?%s' % urllib.parse.urlencode(filters) for param in tags_url_params: - url = '%s&%s' % (url, parse.urlencode(param)) + url = '%s&%s' % (url, urllib.parse.urlencode(param)) if 'sort' in kwargs: if 'sort_key' in kwargs or 'sort_dir' in kwargs: @@ -178,7 +177,7 @@ def paginate(url, page_size, limit=None): for dir in sort_dir: url = '%s&sort_dir=%s' % (url, dir) - if isinstance(kwargs.get('marker'), six.string_types): + if isinstance(kwargs.get('marker'), str): url = '%s&marker=%s' % (url, kwargs['marker']) for image, resp in paginate(url, page_size, limit): diff --git a/glanceclient/v2/metadefs.py b/glanceclient/v2/metadefs.py index a6df87a17..1b641ac75 100644 --- a/glanceclient/v2/metadefs.py +++ b/glanceclient/v2/metadefs.py @@ -14,8 +14,7 @@ # under the License. from oslo_utils import encodeutils -import six -from six.moves.urllib import parse +import urllib.parse import warlock from glanceclient.common import utils @@ -89,7 +88,7 @@ def get(self, namespace, **kwargs): @utils.add_req_id_to_object() def _get(self, namespace, header=None, **kwargs): """Get one namespace.""" - query_params = parse.urlencode(kwargs) + query_params = urllib.parse.urlencode(kwargs) if kwargs: query_params = '?%s' % query_params @@ -179,10 +178,10 @@ def paginate(url): for param, value in filters.items(): if isinstance(value, list): filters[param] = encodeutils.safe_encode(','.join(value)) - elif isinstance(value, six.string_types): + elif isinstance(value, str): filters[param] = encodeutils.safe_encode(value) - url = '/v2/metadefs/namespaces?%s' % parse.urlencode(filters) + url = '/v2/metadefs/namespaces?%s' % urllib.parse.urlencode(filters) for namespace, resp in paginate(url): yield namespace, resp diff --git a/glanceclient/v2/tasks.py b/glanceclient/v2/tasks.py index 177c8bf7e..6649f4b48 100644 --- a/glanceclient/v2/tasks.py +++ b/glanceclient/v2/tasks.py @@ -14,8 +14,9 @@ # License for the specific language governing permissions and limitations # under the License. +import urllib.parse + from oslo_utils import encodeutils -import six import warlock from glanceclient.common import utils @@ -85,10 +86,10 @@ def paginate(url): % ', '.join(SORT_DIR_VALUES)) for param, value in filters.items(): - if isinstance(value, six.string_types): + if isinstance(value, str): filters[param] = encodeutils.safe_encode(value) - url = '/v2/tasks?%s' % six.moves.urllib.parse.urlencode(filters) + url = '/v2/tasks?%s' % urllib.parse.urlencode(filters) for task, resp in paginate(url): # NOTE(flwang): remove 'self' for now until we have an elegant # way to pass it into the model constructor without conflict diff --git a/lower-constraints.txt b/lower-constraints.txt index d98e40b79..6b4229ea3 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -60,7 +60,6 @@ requests-mock==1.2.0 requests==2.14.2 requestsexceptions==1.2.0 rfc3986==0.3.1 -six==1.10.0 snowballstemmer==1.2.1 stestr==2.0.0 stevedore==1.20.0 diff --git a/requirements.txt b/requirements.txt index 25b670a0c..ca8abafa7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,6 @@ PrettyTable<0.8,>=0.7.1 # BSD keystoneauth1>=3.6.2 # Apache-2.0 requests>=2.14.2 # Apache-2.0 warlock<2,>=1.2.0 # Apache-2.0 -six>=1.10.0 # MIT oslo.utils>=3.33.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 wrapt>=1.7.0 # BSD License diff --git a/tox.ini b/tox.ini index f001df98f..13238b1e0 100644 --- a/tox.ini +++ b/tox.ini @@ -69,7 +69,7 @@ show-source = True exclude = .venv*,.tox,dist,*egg,build,.git,doc,*lib/python*,.update-venv [hacking] -import_exceptions = six.moves,glanceclient._i18n +import_exceptions = glanceclient._i18n [testenv:lower-constraints] basepython = python3 From bae1d89cc767949fc61cef63dcacec200a2c236a Mon Sep 17 00:00:00 2001 From: Erno Kuvaja Date: Mon, 13 Jul 2020 22:35:15 +0100 Subject: [PATCH 066/167] Pass Global Request ID on with session client Closes-bug: #1886650 Change-Id: I3a08c1beb398ba9f2556b6779c925f679bdc2c49 --- glanceclient/common/http.py | 1 - glanceclient/tests/unit/test_http.py | 14 ++++++++++++++ .../notes/sess_client_grid-3c2101609110f413.yaml | 6 ++++++ 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/sess_client_grid-3c2101609110f413.yaml diff --git a/glanceclient/common/http.py b/glanceclient/common/http.py index 6973f60ee..247b92da6 100644 --- a/glanceclient/common/http.py +++ b/glanceclient/common/http.py @@ -352,7 +352,6 @@ class SessionClient(adapter.Adapter, _BaseHTTPClient): def __init__(self, session, **kwargs): kwargs.setdefault('user_agent', USER_AGENT) kwargs.setdefault('service_type', 'image') - self.global_request_id = kwargs.pop('global_request_id', None) super(SessionClient, self).__init__(session, **kwargs) def request(self, url, method, **kwargs): diff --git a/glanceclient/tests/unit/test_http.py b/glanceclient/tests/unit/test_http.py index 0cf8f5be2..0c93a8489 100644 --- a/glanceclient/tests/unit/test_http.py +++ b/glanceclient/tests/unit/test_http.py @@ -267,6 +267,20 @@ def test_http_duplicate_content_type_headers(self, mock_ksarq): self.assertEqual(b"application/openstack-images-v2.1-json-patch", ksarqh[b"Content-Type"]) + def test_request_id_header_session_client(self): + global_id = "req-%s" % uuid.uuid4() + kwargs = {'global_request_id': global_id} + auth = token_endpoint.Token(self.endpoint, self.token) + sess = session.Session(auth=auth) + http_client = http.SessionClient(sess, **kwargs) + + path = '/v2/images/my-image' + self.mock.get(self.endpoint + path) + http_client.get(path) + + headers = self.mock.last_request.headers + self.assertEqual(global_id, headers['X-OpenStack-Request-ID']) + def test_raw_request(self): """Verify the path being used for HTTP requests reflects accurately.""" headers = {"Content-Type": "text/plain"} diff --git a/releasenotes/notes/sess_client_grid-3c2101609110f413.yaml b/releasenotes/notes/sess_client_grid-3c2101609110f413.yaml new file mode 100644 index 000000000..4552c43de --- /dev/null +++ b/releasenotes/notes/sess_client_grid-3c2101609110f413.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + * Bug 1886650_: Glance client does not correctly forward global request IDs + + .. _1886650: https://code.launchpad.net/bugs/1886650 From 00a6eb5f39022a085fc6c7122769e6669628ec79 Mon Sep 17 00:00:00 2001 From: Cyril Roelandt Date: Mon, 27 Jul 2020 20:31:11 +0200 Subject: [PATCH 067/167] Remove F403, F812 and F821 from the ignorelist in tox.ini They are not needed since no such errors are triggered when running flake8. Change-Id: If6ba0627ca680167378543b4c38135aa2bd6a730 --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 13238b1e0..7d12799b7 100644 --- a/tox.ini +++ b/tox.ini @@ -64,7 +64,7 @@ commands = [flake8] # E731 skipped as assign a lambda expression # W504 line break after binary operator -ignore = E731,F403,F812,F821,W504 +ignore = E731,W504 show-source = True exclude = .venv*,.tox,dist,*egg,build,.git,doc,*lib/python*,.update-venv From 77eab17cf9f3cf3f2578f73163cb816e425effa2 Mon Sep 17 00:00:00 2001 From: Erno Kuvaja Date: Thu, 30 Jul 2020 18:39:31 +0100 Subject: [PATCH 068/167] Fixes "stores" property added to the image "stores" property gets added to the image when `glance image-create-via-import` is called with --stores Change-Id: I514e6e3ac2f3a1f56fb7883ed403a04b1e7f13b0 Closes-Bug: #1889666 --- glanceclient/tests/unit/v2/test_shell_v2.py | 50 +++++++++++++++++++ glanceclient/v2/shell.py | 3 ++ .../notes/fix_1889666-22dc97ce577eccc6.yaml | 6 +++ 3 files changed, 59 insertions(+) create mode 100644 releasenotes/notes/fix_1889666-22dc97ce577eccc6.yaml diff --git a/glanceclient/tests/unit/v2/test_shell_v2.py b/glanceclient/tests/unit/v2/test_shell_v2.py index 4cf4d25dc..3f1d77acf 100644 --- a/glanceclient/tests/unit/v2/test_shell_v2.py +++ b/glanceclient/tests/unit/v2/test_shell_v2.py @@ -1557,6 +1557,56 @@ def test_do_image_create_via_import_with_web_download( 'id': 'pass', 'name': 'IMG-01', 'disk_format': 'vhd', 'container_format': 'bare', 'status': 'queued'}) + @mock.patch('glanceclient.v2.shell.do_image_import') + @mock.patch('glanceclient.v2.shell.do_image_stage') + @mock.patch('sys.stdin', autospec=True) + def test_do_image_create_via_import_with_web_download_with_stores( + self, mock_stdin, mock_do_image_stage, mock_do_image_import): + temp_args = {'name': 'IMG-01', + 'disk_format': 'vhd', + 'container_format': 'bare', + 'uri': 'http://example.com/image.qcow', + 'import_method': 'web-download', + 'progress': False, + 'stores': 'file1,file2'} + tmp2_args = {'name': 'IMG-01', + 'disk_format': 'vhd', + 'container_format': 'bare', + 'uri': 'http://example.com/image.qcow', + 'import_method': 'web-download', + 'progress': False} + args = self._make_args(temp_args) + with mock.patch.object(self.gc.images, 'create') as mocked_create: + with mock.patch.object(self.gc.images, 'get') as mocked_get: + with mock.patch.object(self.gc.images, + 'get_import_info') as mocked_info: + with mock.patch.object(self.gc.images, + 'get_stores_info') as m_stores_info: + + ignore_fields = ['self', 'access', 'schema'] + expect_image = dict([(field, field) for field in + ignore_fields]) + expect_image['id'] = 'pass' + expect_image['name'] = 'IMG-01' + expect_image['disk_format'] = 'vhd' + expect_image['container_format'] = 'bare' + expect_image['status'] = 'queued' + mocked_create.return_value = expect_image + mocked_get.return_value = expect_image + mocked_info.return_value = self.import_info_response + m_stores_info.return_value = self.stores_info_response + mock_stdin.isatty = lambda: True + + test_shell.do_image_create_via_import(self.gc, args) + mock_do_image_stage.assert_not_called() + mock_do_image_import.assert_called_once() + mocked_create.assert_called_once_with(**tmp2_args) + mocked_get.assert_called_with('pass') + utils.print_dict.assert_called_with({ + 'id': 'pass', 'name': 'IMG-01', + 'disk_format': 'vhd', + 'container_format': 'bare', 'status': 'queued'}) + def test_do_image_update_no_user_props(self): args = self._make_args({'id': 'pass', 'name': 'IMG-01', 'disk_format': 'vhd', diff --git a/glanceclient/v2/shell.py b/glanceclient/v2/shell.py index 8414308c0..592b2da7b 100644 --- a/glanceclient/v2/shell.py +++ b/glanceclient/v2/shell.py @@ -235,6 +235,9 @@ def do_image_create_via_import(gc, args): # determine if backend is valid _validate_backend(backend, gc) elif stores: + # NOTE(jokke): Making sure here that we do not include the stores in + # the create call + fields.pop("stores") stores = str(stores).split(',') for store in stores: # determine if backend is valid diff --git a/releasenotes/notes/fix_1889666-22dc97ce577eccc6.yaml b/releasenotes/notes/fix_1889666-22dc97ce577eccc6.yaml new file mode 100644 index 000000000..553c377f1 --- /dev/null +++ b/releasenotes/notes/fix_1889666-22dc97ce577eccc6.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Bug 1889666_: 'stores' property added when using image-create-via-import --stores + + .. _1889666: https://bugs.launchpad.net/glance/+bug/1889666 From 5aca99d653ed457c288df939bbdc00b57c41ad93 Mon Sep 17 00:00:00 2001 From: James Page Date: Tue, 7 Apr 2020 13:48:59 +0100 Subject: [PATCH 069/167] Update test certificates to use strong signing Modern OpenSSL (and specifically on Ubuntu) will reject certificates signed using the sha1 algorithm. Refresh test CA and associated certificates using sha256. Change-Id: Ie22a4630eb0f5993a909efed27088321c1865ca8 --- glanceclient/tests/unit/var/ca.crt | 66 +++++++------- glanceclient/tests/unit/var/certificate.crt | 96 +++++++------------- glanceclient/tests/unit/var/privatekey.key | 98 ++++++++++----------- 3 files changed, 115 insertions(+), 145 deletions(-) diff --git a/glanceclient/tests/unit/var/ca.crt b/glanceclient/tests/unit/var/ca.crt index c149d8c76..cfdda7917 100644 --- a/glanceclient/tests/unit/var/ca.crt +++ b/glanceclient/tests/unit/var/ca.crt @@ -1,34 +1,36 @@ -----BEGIN CERTIFICATE----- -MIIF7jCCA9YCCQDbl9qx7iIeJDANBgkqhkiG9w0BAQUFADCBuDEZMBcGA1UEChMQ -T3BlbnN0YWNrIENBIE9yZzEaMBgGA1UECxMRT3BlbnN0YWNrIFRlc3QgQ0ExIzAh -BgkqhkiG9w0BCQEWFGFkbWluQGNhLmV4YW1wbGUuY29tMREwDwYDVQQHEwhTdGF0 -ZSBDQTELMAkGA1UECBMCQ0ExCzAJBgNVBAYTAkFVMS0wKwYDVQQDEyRPcGVuc3Rh -Y2sgVGVzdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMTIxMTE2MTI1MDE2WhcN -NDAwNDAzMTI1MDE2WjCBuDEZMBcGA1UEChMQT3BlbnN0YWNrIENBIE9yZzEaMBgG -A1UECxMRT3BlbnN0YWNrIFRlc3QgQ0ExIzAhBgkqhkiG9w0BCQEWFGFkbWluQGNh -LmV4YW1wbGUuY29tMREwDwYDVQQHEwhTdGF0ZSBDQTELMAkGA1UECBMCQ0ExCzAJ -BgNVBAYTAkFVMS0wKwYDVQQDEyRPcGVuc3RhY2sgVGVzdCBDZXJ0aWZpY2F0ZSBB -dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC94cpBjwj2 -MD0w5j1Jlcy8Ljmk3r7CRaoV5vhWUrAWpT7Thxr/Ti0qAfZZRSIVpvBM0RlseH0Q -toUJixuYMoNRPUQ74r/TRoO8HfjQDJfnXtWg2L7DRP8p4Zgj3vByBUCU+rKsbI/H -Nssl/AronADbZXCoL5hJRN8euMYZGrt/Gh1ZotKE5gQlEjylDFlA3s3pn+ABLgzf -7L7iufwV3zLdPRHCb6Ve8YvUmKfI6gy+WwTRhNhLz4Nj0uBthnj6QhnRXtxkNT7A -aAStqKH6TtYRnk2Owh8ITFbtLQ0/MSV8jHAxMXx9AloBhEKxv3cIpgLH6lOCnj// -Ql+H6/QWtmTUHzP1kBfMhTQnWTfR92QTcgEMiZ7a07VyVtLh+kp/G5IUqpM6Pyz/ -O6QDs7FF69bTpws7Ce916PPrGFZ9Gqvo/P0jXge8kYqO+a8QnTRldAxdUzPJCK9+ -Dyi2LWeHf8nPFYdwW9Ov6Jw1CKDYxjJg6KIwnrMPa2eUdPB6/OKkqr9/KemOoKQu -4KSaYadFZbaJwt7JPZaHy6TpkGxW7Af8RqGrW6a6nWEFcfO2POuHcAHWL5LiRmni -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 -----END CERTIFICATE----- diff --git a/glanceclient/tests/unit/var/certificate.crt b/glanceclient/tests/unit/var/certificate.crt index 06c02ab7b..64b2291f4 100644 --- a/glanceclient/tests/unit/var/certificate.crt +++ b/glanceclient/tests/unit/var/certificate.crt @@ -1,66 +1,34 @@ -# Certificate: -# Data: -# Version: 3 (0x2) -# Serial Number: 1 (0x1) -# Signature Algorithm: sha1WithRSAEncryption -# Issuer: O=Openstack CA Org, OU=Openstack Test CA/emailAddress=admin@ca.example.com, -# L=State CA, ST=CA, C=AU, CN=Openstack Test Certificate Authority -# Validity -# Not Before: Nov 16 12:50:19 2012 GMT -# Not After : Apr 3 12:50:19 2040 GMT -# Subject: O=Openstack Test Org, OU=Openstack Test Unit/emailAddress=admin@example.com, -# L=State1, ST=CA, C=US, CN=0.0.0.0 -# Subject Public Key Info: -# Public Key Algorithm: rsaEncryption -# RSA Public Key: (4096 bit) -# Modulus (4096 bit): -# 00:d4:bb:3a:c4:a0:06:54:31:23:5d:b0:78:5a:be: -# 45:44:ae:a1:89:86:11:d8:ca:a8:33:b0:4f:f3:e1: -# . -# . -# . -# Exponent: 65537 (0x10001) -# X509v3 extensions: -# X509v3 Subject Alternative Name: -# DNS:alt1.example.com, DNS:alt2.example.com -# Signature Algorithm: sha1WithRSAEncryption -# 2c:fc:5c:87:24:bd:4a:fa:40:d2:2e:35:a4:2a:f3:1c:b3:67: -# b0:e4:8a:cd:67:6b:55:50:d4:cb:dd:2d:26:a5:15:62:90:a3: -# . -# . -# . -----BEGIN CERTIFICATE----- -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 -tdc5rw== +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 +9l4gwJ1ojNB2abF4LOEf5bU= -----END CERTIFICATE----- diff --git a/glanceclient/tests/unit/var/privatekey.key b/glanceclient/tests/unit/var/privatekey.key index 5b47d44c3..6cd8725b4 100644 --- a/glanceclient/tests/unit/var/privatekey.key +++ b/glanceclient/tests/unit/var/privatekey.key @@ -1,51 +1,51 @@ -----BEGIN RSA PRIVATE KEY----- -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 -----END RSA PRIVATE KEY----- From ff70a0df131504f543646266a49ca71a63f59410 Mon Sep 17 00:00:00 2001 From: Ghanshyam Mann Date: Thu, 30 Jul 2020 18:04:59 -0500 Subject: [PATCH 070/167] [goal] Migrate testing to ubuntu focal As per victoria cycle testing runtime and community goal[1] we need to migrate upstream CI/CD to Ubuntu Focal(20.04). Fixing: - bug#1886298 Bump the lower constraints for required deps which added python3.8 support in their later version. Story: #2007865 Task: #40200 Closes-Bug: #1886298 [1] https://governance.openstack.org/tc/goals/selected/victoria/migrate-ci-cd-jobs-to-ubuntu-focal.h> Change-Id: I6f0dc11d4eb7a75d29da3eb5071b156bbe46ad01 --- lower-constraints.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lower-constraints.txt b/lower-constraints.txt index 6b4229ea3..8e63056bc 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -2,11 +2,11 @@ alabaster==0.7.10 appdirs==1.3.0 asn1crypto==0.23.0 Babel==2.3.4 -cffi==1.7.0 +cffi==1.14.0 cliff==2.8.0 cmd2==0.8.0 coverage==4.0 -cryptography==2.1 +cryptography==2.7 ddt==1.2.1 debtcollector==1.2.0 docutils==0.11 @@ -55,7 +55,7 @@ python-dateutil==2.5.3 python-mimeparse==1.6.0 python-subunit==1.0.0 pytz==2013.6 -PyYAML==3.12 +PyYAML==3.13 requests-mock==1.2.0 requests==2.14.2 requestsexceptions==1.2.0 From 4a80c36f9caf51597f1b810cf50fde01d9963bd7 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Wed, 9 Sep 2020 15:57:35 +0000 Subject: [PATCH 071/167] Update master for stable/victoria Add file to the reno documentation build to show release notes for stable/victoria. Use pbr instruction to increment the minor version number automatically so that master versions are higher than the versions on stable/victoria. Change-Id: Icbc3629218546143246c133ccaffaef7bfe0c243 Sem-Ver: feature --- releasenotes/source/index.rst | 1 + releasenotes/source/victoria.rst | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 releasenotes/source/victoria.rst diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index 6dae364e3..169a1a6fb 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ glanceclient Release Notes :maxdepth: 1 unreleased + victoria ussuri train stein diff --git a/releasenotes/source/victoria.rst b/releasenotes/source/victoria.rst new file mode 100644 index 000000000..4efc7b6f3 --- /dev/null +++ b/releasenotes/source/victoria.rst @@ -0,0 +1,6 @@ +============================= +Victoria Series Release Notes +============================= + +.. release-notes:: + :branch: stable/victoria From 23d389f15a75287fb9e0cb1e20196a0911e5debb Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Wed, 9 Sep 2020 15:57:37 +0000 Subject: [PATCH 072/167] Add Python3 wallaby unit tests This is an automatically generated patch to ensure unit testing is in place for all the of the tested runtimes for wallaby. See also the PTI in governance [1]. [1]: https://governance.openstack.org/tc/reference/project-testing-interface.html Change-Id: I11d4eb88594608fdf21e1219b4aafcef606bf6f1 --- .zuul.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.zuul.yaml b/.zuul.yaml index 14adc2b91..fc84aa0f4 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -82,7 +82,7 @@ - lib-forward-testing-python3 - openstack-cover-jobs - openstack-lower-constraints-jobs - - openstack-python3-victoria-jobs + - openstack-python3-wallaby-jobs - publish-openstack-docs-pti - release-notes-jobs-python3 check: From e8f427e1088b6de488bfa6af811d62415b073c34 Mon Sep 17 00:00:00 2001 From: Erno Kuvaja Date: Fri, 18 Dec 2020 10:14:30 +0000 Subject: [PATCH 073/167] bump mccabe in lc to unblock the gate Change-Id: Iba0929c3ab40290ffc99b65d985174bbf1a8cf65 --- lower-constraints.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lower-constraints.txt b/lower-constraints.txt index 9615cba1e..52d490440 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -25,7 +25,7 @@ jsonschema==2.6.0 keystoneauth1==3.6.2 linecache2==1.0.0 MarkupSafe==1.0 -mccabe==0.2.1 +mccabe==0.6.0 monotonic==0.6 msgpack-python==0.4.0 netaddr==0.7.18 From e0a35a1150a7afe1e28b8d9b59a9e41951276baa Mon Sep 17 00:00:00 2001 From: Abhishek Kekane Date: Thu, 18 Feb 2021 07:59:29 +0000 Subject: [PATCH 074/167] Get tasks associated with image Add support to get tasks associated with specific image. bp: messages-api Change-Id: Ia505cf6f47ca6c628e195be3ca5231d22d53040d --- glanceclient/common/utils.py | 28 ++++++++++ glanceclient/tests/unit/v2/base.py | 7 +++ glanceclient/tests/unit/v2/test_images.py | 30 +++++++++++ glanceclient/tests/unit/v2/test_shell_v2.py | 52 +++++++++++++++++++ glanceclient/v2/images.py | 19 +++++++ glanceclient/v2/shell.py | 18 +++++++ .../image-tasks-api-ee3ea043557a1dfa.yaml | 5 ++ 7 files changed, 159 insertions(+) create mode 100644 releasenotes/notes/image-tasks-api-ee3ea043557a1dfa.yaml diff --git a/glanceclient/common/utils.py b/glanceclient/common/utils.py index 4084e0ef5..169126401 100644 --- a/glanceclient/common/utils.py +++ b/glanceclient/common/utils.py @@ -173,6 +173,34 @@ def pretty_choice_list(l): return ', '.join("'%s'" % i for i in l) +def has_version(client, version): + versions = client.get('/versions')[1].get('versions') + supported = ['SUPPORTED', 'CURRENT'] + for version_struct in versions: + if version_struct['id'] == version: + return version_struct['status'] in supported + return False + + +def print_dict_list(objects, fields): + pt = prettytable.PrettyTable([f for f in fields], caching=False) + pt.align = 'l' + for o in objects: + row = [] + for field in fields: + field_name = field.lower().replace(' ', '_') + # NOTE (abhishekk) mapping field to actual name in the + # structure. + if field_name == 'task_id': + field_name = 'id' + data = o.get(field_name, '') + row.append(data) + + pt.add_row(row) + + print(encodeutils.safe_decode(pt.get_string())) + + def print_list(objs, fields, formatters=None, field_settings=None): '''Prints a list of objects. diff --git a/glanceclient/tests/unit/v2/base.py b/glanceclient/tests/unit/v2/base.py index d6f5cc593..694cd0fed 100644 --- a/glanceclient/tests/unit/v2/base.py +++ b/glanceclient/tests/unit/v2/base.py @@ -38,6 +38,13 @@ def list(self, *args, **kwargs): return resources + def get_associated_image_tasks(self, *args, **kwargs): + resource = self.controller.get_associated_image_tasks( + *args, **kwargs) + + self._assertRequestId(resource) + return resource + def get(self, *args, **kwargs): resource = self.controller.get(*args, **kwargs) diff --git a/glanceclient/tests/unit/v2/test_images.py b/glanceclient/tests/unit/v2/test_images.py index 55610d80a..199d6ec97 100644 --- a/glanceclient/tests/unit/v2/test_images.py +++ b/glanceclient/tests/unit/v2/test_images.py @@ -20,6 +20,7 @@ import ddt +from glanceclient.common import utils as common_utils from glanceclient import exc from glanceclient.tests.unit.v2 import base from glanceclient.tests import utils @@ -674,6 +675,19 @@ ]}, ), }, + '/v2/images/3a4560a1-e585-443e-9b39-553b46ec92d1/tasks': { + 'GET': ( + {}, + {'tasks': [ + { + 'id': '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810', + 'status': 'succeed', + 'message': 'Copied 44 MiB', + 'updated_at': '2021-03-01T18:28:26.000000' + } + ]}, + ), + }, } schema_fixtures = { @@ -715,6 +729,22 @@ def setUp(self): self.controller = base.BaseController(self.api, self.schema_api, images.Controller) + def test_image_tasks_supported(self): + with mock.patch.object(common_utils, + 'has_version') as mock_has_version: + mock_has_version.return_value = True + image_tasks = self.controller.get_associated_image_tasks( + '3a4560a1-e585-443e-9b39-553b46ec92d1') + self.assertEqual(1, len(image_tasks['tasks'])) + + def test_image_tasks_not_supported(self): + with mock.patch.object(common_utils, + 'has_version') as mock_has_version: + mock_has_version.return_value = False + self.assertRaises(exc.HTTPNotImplemented, + self.controller.get_associated_image_tasks, + '3a4560a1-e585-443e-9b39-553b46ec92d1') + def test_list_images(self): images = self.controller.list() self.assertEqual('3a4560a1-e585-443e-9b39-553b46ec92d1', images[0].id) diff --git a/glanceclient/tests/unit/v2/test_shell_v2.py b/glanceclient/tests/unit/v2/test_shell_v2.py index 3f1d77acf..c2aa58aef 100644 --- a/glanceclient/tests/unit/v2/test_shell_v2.py +++ b/glanceclient/tests/unit/v2/test_shell_v2.py @@ -113,6 +113,7 @@ def _mock_utils(self): utils.print_list = mock.Mock() utils.print_dict = mock.Mock() utils.save_image = mock.Mock() + utils.print_dict_list = mock.Mock() def assert_exits_with_msg(self, func, func_args, err_msg=None): with mock.patch.object(utils, 'exit') as mocked_utils_exit: @@ -562,6 +563,57 @@ def test_do_image_show(self): 'size': 1024}, max_column_width=120) + def _test_do_image_tasks(self, verbose=False, supported=True): + args = self._make_args({'id': 'pass', 'verbose': verbose}) + expected_columns = ["Message", "Status", "Updated at"] + expected_output = { + "tasks": [ + { + "image_id": "pass", + "id": "task_1", + "user_id": "user_1", + "request_id": "request_id_1", + "message": "fake_message", + "status": "status", + } + ] + } + + if verbose: + columns_to_prepend = ['Image Id', 'Task Id'] + columns_to_extend = ['User Id', 'Request Id', + 'Result', 'Owner', 'Input', 'Expires at'] + expected_columns = (columns_to_prepend + expected_columns + + columns_to_extend) + expected_output["tasks"][0]["Result"] = "Fake Result" + expected_output["tasks"][0]["Owner"] = "Fake Owner" + expected_output["tasks"][0]["Input"] = "Fake Input" + expected_output["tasks"][0]["Expires at"] = "Fake Expiry" + + with mock.patch.object(self.gc.images, + 'get_associated_image_tasks') as mocked_tasks: + if supported: + mocked_tasks.return_value = expected_output + else: + mocked_tasks.side_effect = exc.HTTPNotImplemented + test_shell.do_image_tasks(self.gc, args) + mocked_tasks.assert_called_once_with('pass') + if supported: + utils.print_dict_list.assert_called_once_with( + expected_output['tasks'], expected_columns) + + def test_do_image_tasks_without_verbose(self): + self._test_do_image_tasks() + + def test_do_image_tasks_with_verbose(self): + self._test_do_image_tasks(verbose=True) + + def test_do_image_tasks_unsupported(self): + with mock.patch('glanceclient.common.utils.exit') as mock_exit: + self._test_do_image_tasks(supported=False) + mock_exit.assert_called_once_with( + 'Server does not support image tasks API (v2.12)') + @mock.patch('sys.stdin', autospec=True) def test_do_image_create_no_user_props(self, mock_stdin): args = self._make_args({'name': 'IMG-01', 'disk_format': 'vhd', diff --git a/glanceclient/v2/images.py b/glanceclient/v2/images.py index 341485db9..b412c42d4 100644 --- a/glanceclient/v2/images.py +++ b/glanceclient/v2/images.py @@ -196,6 +196,25 @@ def _get(self, image_id, header=None): def get(self, image_id): return self._get(image_id) + @utils.add_req_id_to_object() + def get_associated_image_tasks(self, image_id): + """Get the tasks associated with an image. + + :param image_id: ID of the image + :raises: exc.HTTPNotImplemented if Glance is not new enough to support + this API (v2.12). + """ + # NOTE (abhishekk): Verify that /v2i/images/%s/tasks is supported by + # glance + if utils.has_version(self.http_client, 'v2.12'): + url = '/v2/images/%s/tasks' % image_id + resp, body = self.http_client.get(url) + body.pop('self', None) + return body, resp + else: + raise exc.HTTPNotImplemented( + 'This operation is not supported by Glance.') + @utils.add_req_id_to_object() def data(self, image_id, do_checksum=True, allow_md5_fallback=False): """Retrieve data of an image. diff --git a/glanceclient/v2/shell.py b/glanceclient/v2/shell.py index 592b2da7b..c38d046c9 100644 --- a/glanceclient/v2/shell.py +++ b/glanceclient/v2/shell.py @@ -468,6 +468,24 @@ def do_image_show(gc, args): utils.print_image(image, args.human_readable, int(args.max_column_width)) +@utils.arg('id', metavar='', help=_('ID of image to get tasks.')) +def do_image_tasks(gc, args): + """Get tasks associated with image""" + columns = ['Message', 'Status', 'Updated at'] + if args.verbose: + columns_to_prepend = ['Image Id', 'Task Id'] + columns_to_extend = ['User Id', 'Request Id', + 'Result', 'Owner', 'Input', 'Expires at'] + columns = columns_to_prepend + columns + columns_to_extend + try: + tasks = gc.images.get_associated_image_tasks(args.id) + utils.print_dict_list(tasks['tasks'], columns) + except exc.HTTPNotFound: + utils.exit('Image %s not found.' % args.id) + except exc.HTTPNotImplemented: + utils.exit('Server does not support image tasks API (v2.12)') + + @utils.arg('--image-id', metavar='', required=True, help=_('Image to display members of.')) def do_member_list(gc, args): diff --git a/releasenotes/notes/image-tasks-api-ee3ea043557a1dfa.yaml b/releasenotes/notes/image-tasks-api-ee3ea043557a1dfa.yaml new file mode 100644 index 000000000..3e42451bb --- /dev/null +++ b/releasenotes/notes/image-tasks-api-ee3ea043557a1dfa.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Support for showing tasks associated with given image. + From 2517e01c7c1abd5bc06d35fef928dfbbb6b9b8c7 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Thu, 11 Mar 2021 10:24:52 +0000 Subject: [PATCH 075/167] Update master for stable/wallaby Add file to the reno documentation build to show release notes for stable/wallaby. Use pbr instruction to increment the minor version number automatically so that master versions are higher than the versions on stable/wallaby. Sem-Ver: feature Change-Id: I7562fd0e795c2dab0f999729b467b05b0ad0875d --- releasenotes/source/index.rst | 1 + releasenotes/source/wallaby.rst | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 releasenotes/source/wallaby.rst diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index 169a1a6fb..0073cd164 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ glanceclient Release Notes :maxdepth: 1 unreleased + wallaby victoria ussuri train diff --git a/releasenotes/source/wallaby.rst b/releasenotes/source/wallaby.rst new file mode 100644 index 000000000..d77b56599 --- /dev/null +++ b/releasenotes/source/wallaby.rst @@ -0,0 +1,6 @@ +============================ +Wallaby Series Release Notes +============================ + +.. release-notes:: + :branch: stable/wallaby From 13bae28a6fb84029e689c0b2c049604bd3ef15c7 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Thu, 11 Mar 2021 10:24:57 +0000 Subject: [PATCH 076/167] Add Python3 xena unit tests This is an automatically generated patch to ensure unit testing is in place for all the of the tested runtimes for xena. See also the PTI in governance [1]. [1]: https://governance.openstack.org/tc/reference/project-testing-interface.html Change-Id: Ia7ed638cda0d3f1e0e96fec9bb73eb4315ed810b --- .zuul.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.zuul.yaml b/.zuul.yaml index fc84aa0f4..7dbf0ea8b 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -82,7 +82,7 @@ - lib-forward-testing-python3 - openstack-cover-jobs - openstack-lower-constraints-jobs - - openstack-python3-wallaby-jobs + - openstack-python3-xena-jobs - publish-openstack-docs-pti - release-notes-jobs-python3 check: From b443996524a55ee01af67ab7f667829a82ceacce Mon Sep 17 00:00:00 2001 From: XinxinShen Date: Wed, 5 May 2021 17:04:25 +0800 Subject: [PATCH 077/167] setup.cfg: Replace dashes with underscores Setuptools v54.1.0 introduces a warning that the use of dash-separated options in 'setup.cfg' will not be supported in a future version [1]. Get ahead of the issue by replacing the dashes with underscores. Without this, we see 'UserWarning' messages like the following on new enough versions of setuptools: UserWarning: Usage of dash-separated 'description-file' will not be supported in future versions. Please use the underscore name 'description_file' instead [1] https://github.com/pypa/setuptools/commit/a2e9ae4cb Change-Id: I5f8ed2d6b1ca8c26d7fafd280b135554ed597e99 --- setup.cfg | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/setup.cfg b/setup.cfg index 73161d9ba..a246af541 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,13 +1,13 @@ [metadata] name = python-glanceclient summary = OpenStack Image API Client Library -description-file = +description_file = README.rst license = Apache License, Version 2.0 author = OpenStack -author-email = openstack-discuss@lists.openstack.org -home-page = https://docs.openstack.org/python-glanceclient/latest/ -python-requires = >=3.6 +author_email = openstack-discuss@lists.openstack.org +home_page = https://docs.openstack.org/python-glanceclient/latest/ +python_requires = >=3.6 classifier = Development Status :: 5 - Production/Stable Environment :: Console From de0e9d9510e997c5d49fc28e7175e326f216b038 Mon Sep 17 00:00:00 2001 From: Wander Way Date: Thu, 18 Feb 2021 14:48:14 +0800 Subject: [PATCH 078/167] Uncap PrettyTable This is now maintained as a Jazzband project [1]. [1] https://github.com/jazzband/prettytable Change-Id: Ib85f2fa95b95e95ac5711b1dcd626a9a84ac8b0b --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ca8abafa7..e2a92e45c 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. pbr!=2.1.0,>=2.0.0 # Apache-2.0 -PrettyTable<0.8,>=0.7.1 # BSD +PrettyTable>=0.7.1 # BSD keystoneauth1>=3.6.2 # Apache-2.0 requests>=2.14.2 # Apache-2.0 warlock<2,>=1.2.0 # Apache-2.0 From 158d5f42486b3d5d7aaa65d86a23012298350a4c Mon Sep 17 00:00:00 2001 From: jayonlau Date: Wed, 7 Jul 2021 20:44:02 +0800 Subject: [PATCH 079/167] Clean up extra spaces Clean up extra spaces, although these errors are not important, they affect the code specification. Change-Id: I5cf3c668e89a11c7906e0b0aeeb85ebcef3c1352 --- run_tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run_tests.sh b/run_tests.sh index 1b6551ac8..7238da534 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -18,7 +18,7 @@ command -v tox > /dev/null 2>&1 if [ $? -ne 0 ]; then echo 'This script requires "tox" to run.' echo 'You can install it with "pip install tox".' - exit 1; + exit 1; fi just_pep8=0 From cb084f5289c5c23bdb9fabb413a81b32acb5a498 Mon Sep 17 00:00:00 2001 From: Mridula Joshi Date: Thu, 29 Jul 2021 12:25:37 +0000 Subject: [PATCH 080/167] Add member-get command It is observed that python-glanceclient was missing support for GET /v2/image/{image_id}/member/{member_id} API. This patch adds new command `member-get` to support this missing operation. Closes-Bug: #1938154 Change-Id: I3709f6a39535aa45bee70f468f015ac60a1375a8 --- .../add-member-get-command-11c15e0a94ecd39a.yaml | 5 +++++ glanceclient/tests/unit/v2/test_members.py | 16 ++++++++++++++++ glanceclient/tests/unit/v2/test_shell_v2.py | 10 ++++++++++ glanceclient/v2/image_members.py | 6 ++++++ glanceclient/v2/shell.py | 10 +++++++++- 5 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 glanceclient/tests/unit/v2/releasenotes/notes/add-member-get-command-11c15e0a94ecd39a.yaml diff --git a/glanceclient/tests/unit/v2/releasenotes/notes/add-member-get-command-11c15e0a94ecd39a.yaml b/glanceclient/tests/unit/v2/releasenotes/notes/add-member-get-command-11c15e0a94ecd39a.yaml new file mode 100644 index 000000000..d5fce0369 --- /dev/null +++ b/glanceclient/tests/unit/v2/releasenotes/notes/add-member-get-command-11c15e0a94ecd39a.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Bug 1938154_: Added member-get command + .. _1938154: https://bugs.launchpad.net/glance/+bug/1938154 diff --git a/glanceclient/tests/unit/v2/test_members.py b/glanceclient/tests/unit/v2/test_members.py index 7048a9717..240f2b2a2 100644 --- a/glanceclient/tests/unit/v2/test_members.py +++ b/glanceclient/tests/unit/v2/test_members.py @@ -45,6 +45,17 @@ ) }, '/v2/images/{image}/members/{mem}'.format(image=IMAGE, mem=MEMBER): { + 'GET': ( + {}, + { + 'image_id': IMAGE, + 'member_id': MEMBER, + 'status': 'pending', + 'created_at': '2013-11-26T07:21:21Z', + 'updated_at': '2013-11-26T07:21:21Z', + 'schema': "/v2/schemas/member" + }, + ), 'DELETE': ( {}, None, @@ -90,6 +101,11 @@ def test_list_image_members(self): self.assertEqual(IMAGE, image_members[0].image_id) self.assertEqual(MEMBER, image_members[0].member_id) + def test_get_image_members(self): + image_member = self.controller.get(IMAGE, MEMBER) + self.assertEqual(IMAGE, image_member.image_id) + self.assertEqual(MEMBER, image_member.member_id) + def test_delete_image_member(self): image_id = IMAGE member_id = MEMBER diff --git a/glanceclient/tests/unit/v2/test_shell_v2.py b/glanceclient/tests/unit/v2/test_shell_v2.py index c2aa58aef..83c472732 100644 --- a/glanceclient/tests/unit/v2/test_shell_v2.py +++ b/glanceclient/tests/unit/v2/test_shell_v2.py @@ -2378,6 +2378,16 @@ def test_do_member_list(self): columns = ['Image ID', 'Member ID', 'Status'] utils.print_list.assert_called_once_with({}, columns) + def test_do_member_get(self): + args = self._make_args({'image_id': 'IMG-01', 'member_id': 'MEM-01'}) + with mock.patch.object(self.gc.image_members, 'get') as mock_get: + mock_get.return_value = {} + + test_shell.do_member_get(self.gc, args) + + mock_get.assert_called_once_with('IMG-01', 'MEM-01') + utils.print_dict.assert_called_once_with({}) + def test_do_member_create(self): args = self._make_args({'image_id': 'IMG-01', 'member_id': 'MEM-01'}) with mock.patch.object(self.gc.image_members, 'create') as mock_create: diff --git a/glanceclient/v2/image_members.py b/glanceclient/v2/image_members.py index 780519a25..096429a54 100644 --- a/glanceclient/v2/image_members.py +++ b/glanceclient/v2/image_members.py @@ -39,6 +39,12 @@ def list(self, image_id): for member in body['members']: yield self.model(member), resp + @utils.add_req_id_to_object() + def get(self, image_id, member_id): + url = '/v2/images/%s/members/%s' % (image_id, member_id) + resp, member = self.http_client.get(url) + return self.model(member), resp + @utils.add_req_id_to_object() def delete(self, image_id, member_id): resp, body = self.http_client.delete('/v2/images/%s/members/%s' % diff --git a/glanceclient/v2/shell.py b/glanceclient/v2/shell.py index c38d046c9..5f83bd278 100644 --- a/glanceclient/v2/shell.py +++ b/glanceclient/v2/shell.py @@ -490,12 +490,20 @@ def do_image_tasks(gc, args): help=_('Image to display members of.')) def do_member_list(gc, args): """Describe sharing permissions by image.""" - members = gc.image_members.list(args.image_id) columns = ['Image ID', 'Member ID', 'Status'] utils.print_list(members, columns) +@utils.arg('image_id', metavar='', + help=_('Image from which to display member.')) +@utils.arg('member_id', metavar='', + help=_('Project to display.')) +def do_member_get(gc, args): + """Show details of an image member""" + member = gc.image_members.get(args.image_id, args.member_id) + utils.print_dict(member) + @utils.arg('image_id', metavar='', help=_('Image from which to remove member.')) @utils.arg('member_id', metavar='', From 1eb0bbbed7c5ce12aee5f26be7a7aec51ae9ef55 Mon Sep 17 00:00:00 2001 From: Mridula Joshi Date: Tue, 3 Aug 2021 10:35:40 +0000 Subject: [PATCH 081/167] Fix undesirable raw Python error Using the glanceclient without a subcommand while passing an optional argument triggers the raw Python error `ERROR: 'Namespace' object has no attribute 'func'`. This bug can be reproduced by issuing the command `glance --os-image-api-version 2`. Added a default value to `func` as placeholder so that a help message is shown instead of the Python error. Closes-Bug: #1903727 Change-Id: Ie4288262e408192310cbbc240bd1779b265a64fd --- glanceclient/shell.py | 2 ++ glanceclient/tests/unit/test_shell.py | 8 ++++++++ ...fix-undesirable-raw-python-error-66e3ddaca7b72ae2.yaml | 7 +++++++ 3 files changed, 17 insertions(+) create mode 100644 releasenotes/notes/fix-undesirable-raw-python-error-66e3ddaca7b72ae2.yaml diff --git a/glanceclient/shell.py b/glanceclient/shell.py index ca103598b..3a32bfb6f 100644 --- a/glanceclient/shell.py +++ b/glanceclient/shell.py @@ -166,6 +166,8 @@ def get_base_parser(self, argv): parser.add_argument('--os_image_api_version', help=argparse.SUPPRESS) + parser.set_defaults(func=self.do_help) + if osprofiler_profiler: parser.add_argument('--profile', metavar='HMAC_KEY', diff --git a/glanceclient/tests/unit/test_shell.py b/glanceclient/tests/unit/test_shell.py index 74d1c3339..129b1275c 100644 --- a/glanceclient/tests/unit/test_shell.py +++ b/glanceclient/tests/unit/test_shell.py @@ -211,6 +211,14 @@ def test_help(self): self.assertEqual(0, actual) self.assertFalse(et_mock.called) + def test_help_no_subcommand(self): + shell = openstack_shell.OpenStackImagesShell() + argstr = '--os-image-api-version 2' + with mock.patch.object(shell, '_get_keystone_auth_plugin') as et_mock: + actual = shell.main(argstr.split()) + self.assertEqual(0, actual) + self.assertFalse(et_mock.called) + def test_blank_call(self): shell = openstack_shell.OpenStackImagesShell() with mock.patch.object(shell, '_get_keystone_auth_plugin') as et_mock: diff --git a/releasenotes/notes/fix-undesirable-raw-python-error-66e3ddaca7b72ae2.yaml b/releasenotes/notes/fix-undesirable-raw-python-error-66e3ddaca7b72ae2.yaml new file mode 100644 index 000000000..d6fad7d29 --- /dev/null +++ b/releasenotes/notes/fix-undesirable-raw-python-error-66e3ddaca7b72ae2.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + `Bug #1903727 `_: + Fixed raw Python error message when using ``glance`` without + a subcommand while passing an optional argument, such as + ``--os-image-api-version``. From 9524fce9fe9e1f6732eed82b0898f89171f20c22 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Fri, 10 Sep 2021 14:33:05 +0000 Subject: [PATCH 082/167] Update master for stable/xena Add file to the reno documentation build to show release notes for stable/xena. Use pbr instruction to increment the minor version number automatically so that master versions are higher than the versions on stable/xena. Sem-Ver: feature Change-Id: Idad534678f8e66469b46e293e2aa3d965ff2c9cf --- releasenotes/source/index.rst | 1 + releasenotes/source/xena.rst | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 releasenotes/source/xena.rst diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index 0073cd164..8cd670d89 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ glanceclient Release Notes :maxdepth: 1 unreleased + xena wallaby victoria ussuri diff --git a/releasenotes/source/xena.rst b/releasenotes/source/xena.rst new file mode 100644 index 000000000..1be85be3e --- /dev/null +++ b/releasenotes/source/xena.rst @@ -0,0 +1,6 @@ +========================= +Xena Series Release Notes +========================= + +.. release-notes:: + :branch: stable/xena From ef995536c8eff1c1ea55938d36b793c2769511b5 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Fri, 10 Sep 2021 14:33:07 +0000 Subject: [PATCH 083/167] Add Python3 yoga unit tests This is an automatically generated patch to ensure unit testing is in place for all the of the tested runtimes for yoga. See also the PTI in governance [1]. [1]: https://governance.openstack.org/tc/reference/project-testing-interface.html Change-Id: I98553ae1ab9b8b3f530476629dc7f901d5d2bd67 --- .zuul.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.zuul.yaml b/.zuul.yaml index 7dbf0ea8b..285e19e8f 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -82,7 +82,7 @@ - lib-forward-testing-python3 - openstack-cover-jobs - openstack-lower-constraints-jobs - - openstack-python3-xena-jobs + - openstack-python3-yoga-jobs - publish-openstack-docs-pti - release-notes-jobs-python3 check: From ec0adb16b58cc8bfb526c941fa24ec4ab31657b9 Mon Sep 17 00:00:00 2001 From: jinyuanliu Date: Wed, 15 Sep 2021 04:32:36 -0400 Subject: [PATCH 084/167] Clean up extra spaces Although these errors are not important, they affect the code specification. Change-Id: I1f91dba079ecf60b1d4b57123048d8409476dde0 --- tools/with_venv.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/with_venv.sh b/tools/with_venv.sh index e6e44f599..1b09ad70a 100755 --- a/tools/with_venv.sh +++ b/tools/with_venv.sh @@ -4,7 +4,7 @@ command -v tox > /dev/null 2>&1 if [ $? -ne 0 ]; then echo 'This script requires "tox" to run.' echo 'You can install it with "pip install tox".' - exit 1; + exit 1; fi tox -evenv -- $@ From 5d714bed0b653b42b49b2c571b798a5e9b738539 Mon Sep 17 00:00:00 2001 From: Rajat Dhasmana Date: Wed, 22 Sep 2021 10:04:58 -0400 Subject: [PATCH 085/167] Correct releasenote path for member-get command This patch moves the releasenote of member-get command to correct path. Closes-Bug: #1944798 Change-Id: Ifa76fc993b0ff47131401ba233e77001cd74107c --- .../notes/add-member-get-command-11c15e0a94ecd39a.yaml | 1 + 1 file changed, 1 insertion(+) rename {glanceclient/tests/unit/v2/releasenotes => releasenotes}/notes/add-member-get-command-11c15e0a94ecd39a.yaml (99%) diff --git a/glanceclient/tests/unit/v2/releasenotes/notes/add-member-get-command-11c15e0a94ecd39a.yaml b/releasenotes/notes/add-member-get-command-11c15e0a94ecd39a.yaml similarity index 99% rename from glanceclient/tests/unit/v2/releasenotes/notes/add-member-get-command-11c15e0a94ecd39a.yaml rename to releasenotes/notes/add-member-get-command-11c15e0a94ecd39a.yaml index d5fce0369..45d523c99 100644 --- a/glanceclient/tests/unit/v2/releasenotes/notes/add-member-get-command-11c15e0a94ecd39a.yaml +++ b/releasenotes/notes/add-member-get-command-11c15e0a94ecd39a.yaml @@ -2,4 +2,5 @@ fixes: - | Bug 1938154_: Added member-get command + .. _1938154: https://bugs.launchpad.net/glance/+bug/1938154 From 98f4219b6e3cd51f3a6e0fba5e50aad867e59e7a Mon Sep 17 00:00:00 2001 From: Cyril Roelandt Date: Tue, 5 Oct 2021 20:11:26 +0200 Subject: [PATCH 086/167] Make "tox -edocs" generate the manpage Closes-Bug: #911805 Change-Id: Idbf5ddc56c608588cc30616f4a0cc12c2e698b9c --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index 7d12799b7..7b30f62fe 100644 --- a/tox.ini +++ b/tox.ini @@ -52,6 +52,7 @@ basepython = python3 deps = -r{toxinidir}/doc/requirements.txt commands = sphinx-build -W -b html doc/source doc/build/html + sphinx-build -W -b man doc/source doc/build/man [testenv:releasenotes] basepython = python3 From 3ba81da3259298fc44811093301d33966df64591 Mon Sep 17 00:00:00 2001 From: Cyril Roelandt Date: Fri, 10 Dec 2021 15:15:40 +0100 Subject: [PATCH 087/167] Remove lower-constraints.txt Change-Id: If4881229935d6f2ca0f1632cc9a9ad473f8de33e --- .zuul.yaml | 2 -- lower-constraints.txt | 72 ------------------------------------------- tox.ini | 7 ----- 3 files changed, 81 deletions(-) delete mode 100644 lower-constraints.txt diff --git a/.zuul.yaml b/.zuul.yaml index 285e19e8f..799f8f711 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -32,7 +32,6 @@ - ^releasenotes/.*$ - ^.*\.rst$ - ^(test-|)requirements.txt$ - - ^lower-constraints.txt$ - ^setup.cfg$ - ^tox.ini$ @@ -81,7 +80,6 @@ - check-requirements - lib-forward-testing-python3 - openstack-cover-jobs - - openstack-lower-constraints-jobs - openstack-python3-yoga-jobs - publish-openstack-docs-pti - release-notes-jobs-python3 diff --git a/lower-constraints.txt b/lower-constraints.txt deleted file mode 100644 index 52d490440..000000000 --- a/lower-constraints.txt +++ /dev/null @@ -1,72 +0,0 @@ -alabaster==0.7.10 -appdirs==1.3.0 -asn1crypto==0.23.0 -Babel==2.3.4 -cffi==1.14.0 -cliff==2.8.0 -cmd2==0.8.0 -coverage==4.0 -cryptography==2.7 -ddt==1.2.1 -debtcollector==1.2.0 -docutils==0.11 -dulwich==0.15.0 -extras==1.0.0 -fasteners==0.7.0 -fixtures==3.0.0 -future==0.16.0 -idna==2.6 -imagesize==0.7.1 -iso8601==0.1.11 -Jinja2==2.10 -jsonpatch==1.16 -jsonpointer==1.13 -jsonschema==2.6.0 -keystoneauth1==3.6.2 -linecache2==1.0.0 -MarkupSafe==1.0 -mccabe==0.6.0 -monotonic==0.6 -msgpack-python==0.4.0 -netaddr==0.7.18 -netifaces==0.10.4 -ordereddict==1.1 -os-client-config==1.28.0 -os-testr==1.0.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 -prettytable==0.7.1 -pyasn1==0.1.8 -pycparser==2.18 -Pygments==2.2.0 -pyinotify==0.9.6 -pyOpenSSL==17.1.0 -pyparsing==2.1.0 -pyperclip==1.5.27 -python-dateutil==2.5.3 -python-mimeparse==1.6.0 -python-subunit==1.0.0 -pytz==2013.6 -PyYAML==3.13 -requests-mock==1.2.0 -requests==2.14.2 -requestsexceptions==1.2.0 -rfc3986==0.3.1 -snowballstemmer==1.2.1 -stestr==2.0.0 -stevedore==1.20.0 -tempest==17.1.0 -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/tox.ini b/tox.ini index 7d12799b7..7115f06da 100644 --- a/tox.ini +++ b/tox.ini @@ -70,10 +70,3 @@ exclude = .venv*,.tox,dist,*egg,build,.git,doc,*lib/python*,.update-venv [hacking] import_exceptions = glanceclient._i18n - -[testenv:lower-constraints] -basepython = python3 -deps = - -c{toxinidir}/lower-constraints.txt - -r{toxinidir}/test-requirements.txt - -r{toxinidir}/requirements.txt From 51c5c674c5d54b1b9028ac0228903618730dae69 Mon Sep 17 00:00:00 2001 From: XinxinShen Date: Wed, 26 May 2021 09:52:47 +0800 Subject: [PATCH 088/167] Replace deprecated UPPER_CONSTRAINTS_FILE variable UPPER_CONSTRAINTS_FILE is old name and deprecated [1] https://zuul-ci.org/docs/zuul-jobs/python-roles.html#rolevar-tox.tox_constraints_file Co-Authored-By: Cyril Roelandt Change-Id: I8aec0f3ab5b33c6ee1ccd0fafafc7c3e447082db --- tox.ini | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 7d12799b7..f38dd5667 100644 --- a/tox.ini +++ b/tox.ini @@ -8,8 +8,12 @@ usedevelop = True setenv = OS_STDOUT_NOCAPTURE=False OS_STDERR_NOCAPTURE=False +# Nowadays, TOX_CONSTRAINTS_FILE should be used, but some older scripts might +# still be using UPPER_CONSTRAINTS_FILE, so we check both variables and use the +# first one that is defined. If none of them is defined, we fallback to the +# default value. deps = - -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -c{env:TOX_CONSTRAINTS_FILE:{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = stestr run --slowest {posargs} @@ -55,8 +59,12 @@ commands = [testenv:releasenotes] basepython = python3 +# Nowadays, TOX_CONSTRAINTS_FILE should be used, but some older scripts might +# still be using UPPER_CONSTRAINTS_FILE, so we check both variables and use the +# first one that is defined. If none of them is defined, we fallback to the +# default value. deps = - -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -c{env:TOX_CONSTRAINTS_FILE:{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}} -r{toxinidir}/doc/requirements.txt commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html From e1b5e721200127ea39028ca2c8c78f7f7414bab7 Mon Sep 17 00:00:00 2001 From: Ghanshyam Mann Date: Wed, 24 Nov 2021 19:27:19 -0600 Subject: [PATCH 089/167] Updating python testing classifier as per Yoga testing runtime Yoga testing runtime[1] has been updated to add py39 testing as voting. Unit tests update are handled by the job template change in openstack-zuul-job - https://review.opendev.org/c/openstack/openstack-zuul-jobs/+/820286 this commit updates the classifier in setup.cfg file. [1] https://governance.openstack.org/tc/reference/runtimes/yoga.html Change-Id: Ibde540f7950a5ea27af5327dfa662ee9187171c8 --- setup.cfg | 1 + tox.ini | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index a246af541..eea6b516d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -23,6 +23,7 @@ classifier = Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 [files] packages = diff --git a/tox.ini b/tox.ini index 7d12799b7..67c18d576 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py38,pep8 +envlist = py39,pep8 minversion = 2.0 skipsdist = True From 91ae9347dbc125a19fedd107df5cc013e018e7e1 Mon Sep 17 00:00:00 2001 From: Cyril Roelandt Date: Wed, 23 Jun 2021 23:18:49 +0200 Subject: [PATCH 090/167] glance help : Clearly specify which options are mandatory Earlier glance help was listing required arguments as optional arguments in help text. Added new argument group to list required argument properly. $ glance help stores-delete Example before this change: usage: glance stores-delete --store Delete image from specific store. Positional arguments: ID of image to update. Optional arguments: --store Store to delete image from. After this change: usage: glance stores-delete --store Delete image from specific store. Positional arguments: ID of image to update. Required arguments: --store Store to delete image from. Change-Id: I51ea4c43fa62164ed43e78d1ae0fb0cb2521fc83 Closes-Bug: #1933390 --- glanceclient/shell.py | 6 +++++- glanceclient/tests/unit/test_shell.py | 11 +++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/glanceclient/shell.py b/glanceclient/shell.py index 3a32bfb6f..4a505a5d7 100644 --- a/glanceclient/shell.py +++ b/glanceclient/shell.py @@ -224,8 +224,12 @@ def _find_actions(self, subparsers, actions_module): help=argparse.SUPPRESS, ) self.subcommands[command] = subparser + required_args = subparser.add_argument_group('Required arguments') for (args, kwargs) in arguments: - subparser.add_argument(*args, **kwargs) + if kwargs.get('required', False): + required_args.add_argument(*args, **kwargs) + else: + subparser.add_argument(*args, **kwargs) subparser.set_defaults(func=callback) def _add_bash_completion_subparser(self, subparsers): diff --git a/glanceclient/tests/unit/test_shell.py b/glanceclient/tests/unit/test_shell.py index 129b1275c..6b9472e33 100644 --- a/glanceclient/tests/unit/test_shell.py +++ b/glanceclient/tests/unit/test_shell.py @@ -600,6 +600,17 @@ def test_setup_debug(self, conf, func, v2_client): self.assertEqual(glance_logger.getEffectiveLevel(), logging.DEBUG) conf.assert_called_with(level=logging.DEBUG) + def test_subcommand_help(self): + # Ensure that main works with sub command help + stdout, stderr = self.shell('help stores-delete') + + expected = 'usage: glance stores-delete --store ' \ + '\n\nDelete image from specific store.' \ + '\n\nPositional arguments:\n ' \ + 'ID of image to update.\n\nRequired arguments:\n ' \ + '--store Store to delete image from.\n' + self.assertEqual(expected, stdout) + class ShellTestWithKeystoneV3Auth(ShellTest): # auth environment to use From 73aaef428689c2346aafa5b9542bd30f5633ab50 Mon Sep 17 00:00:00 2001 From: vihithad23 Date: Mon, 28 Sep 2020 18:16:26 +0530 Subject: [PATCH 091/167] Documentation: Add options for "glance image-import" Closes-bug:#1886537 Change-Id: I66d99be1ec06806494c5504c467dff759dd38f2d --- doc/source/cli/details.rst | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/doc/source/cli/details.rst b/doc/source/cli/details.rst index f446abac2..a4ae3f500 100644 --- a/doc/source/cli/details.rst +++ b/doc/source/cli/details.rst @@ -598,6 +598,10 @@ glance image-import .. code-block:: console usage: glance image-import [--import-method ] + [--uri ] + [--store ] [--stores ] + [--all-stores [True|False]] + [--allow-failure [True|False]] Initiate the image import taskflow. @@ -614,6 +618,29 @@ Initiate the image import taskflow. be retrieved with import-info command and the default "glance-direct" is used with "image-stage". +``--uri `` + URI to download the external image + +``--store `` + Backend store to upload image to. + +``--stores `` + List of comma separated stores to upload image if multi-stores are + enabled in the environment. + +``--all-stores [True|False]`` + "all-stores" can be used instead of "--stores " to indicate + that image should be imported all available stores. + +``--allow-failure [True|False]`` + Indicator if all stores listed (or available) must + succeed. "True" by default meaning that we allow some + stores to fail and the status can be monitored from + the image metadata. If this is set to "False" the + import will be reverted should any of the uploads + fail. Only usable with "stores" or "all-stores". + + .. _glance_image-create-via-import: glance image-create-via-import From 3f001f5f11941203cc6c2d8505e84ab31891bd0e Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Mon, 31 Jan 2022 12:57:18 -0800 Subject: [PATCH 092/167] Add support for usage API This is really a very simple activity of fetching and showing the results of the usage API in table form for the user. Depends-On: https://review.opendev.org/c/openstack/glance/+/794860 Change-Id: I3d9360785a759e4a6e7905710400baea80776052 --- glanceclient/tests/unit/v2/test_info.py | 37 +++++++++++++++++++++ glanceclient/tests/unit/v2/test_shell_v2.py | 10 ++++++ glanceclient/v2/client.py | 3 ++ glanceclient/v2/info.py | 23 +++++++++++++ glanceclient/v2/shell.py | 9 +++++ 5 files changed, 82 insertions(+) create mode 100644 glanceclient/tests/unit/v2/test_info.py create mode 100644 glanceclient/v2/info.py diff --git a/glanceclient/tests/unit/v2/test_info.py b/glanceclient/tests/unit/v2/test_info.py new file mode 100644 index 000000000..645c15cf6 --- /dev/null +++ b/glanceclient/tests/unit/v2/test_info.py @@ -0,0 +1,37 @@ +# Copyright 2022 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import testtools +from unittest import mock + +from glanceclient.v2 import info + + +class TestController(testtools.TestCase): + def setUp(self): + super(TestController, self).setUp() + self.fake_client = mock.MagicMock() + self.info_controller = info.Controller(self.fake_client, None) + + def test_get_usage(self): + fake_usage = { + 'usage': { + 'quota1': {'limit': 10, 'usage': 0}, + 'quota2': {'limit': 20, 'usage': 5}, + } + } + self.fake_client.get.return_value = (mock.MagicMock(), fake_usage) + usage = self.info_controller.get_usage() + self.assertEqual(fake_usage['usage'], usage) + self.fake_client.get.assert_called_once_with('/v2/info/usage') diff --git a/glanceclient/tests/unit/v2/test_shell_v2.py b/glanceclient/tests/unit/v2/test_shell_v2.py index 83c472732..5cc41e511 100644 --- a/glanceclient/tests/unit/v2/test_shell_v2.py +++ b/glanceclient/tests/unit/v2/test_shell_v2.py @@ -614,6 +614,16 @@ def test_do_image_tasks_unsupported(self): mock_exit.assert_called_once_with( 'Server does not support image tasks API (v2.12)') + def test_usage(self): + with mock.patch.object(self.gc.info, 'get_usage') as mock_usage: + mock_usage.return_value = {'quota1': {'limit': 10, 'usage': 0}, + 'quota2': {'limit': 20, 'usage': 5}} + test_shell.do_usage(self.gc, []) + utils.print_dict_list.assert_called_once_with( + [{'quota': 'quota1', 'limit': 10, 'usage': 0}, + {'quota': 'quota2', 'limit': 20, 'usage': 5}], + ['Quota', 'Limit', 'Usage']) + @mock.patch('sys.stdin', autospec=True) def test_do_image_create_no_user_props(self, mock_stdin): args = self._make_args({'name': 'IMG-01', 'disk_format': 'vhd', diff --git a/glanceclient/v2/client.py b/glanceclient/v2/client.py index 279be6364..8b96bc743 100644 --- a/glanceclient/v2/client.py +++ b/glanceclient/v2/client.py @@ -19,6 +19,7 @@ from glanceclient.v2 import image_members from glanceclient.v2 import image_tags from glanceclient.v2 import images +from glanceclient.v2 import info from glanceclient.v2 import metadefs from glanceclient.v2 import schemas from glanceclient.v2 import tasks @@ -48,6 +49,8 @@ def __init__(self, endpoint=None, **kwargs): self.image_members = image_members.Controller(self.http_client, self.schemas) + self.info = info.Controller(self.http_client, self.schemas) + self.tasks = tasks.Controller(self.http_client, self.schemas) self.metadefs_resource_type = ( diff --git a/glanceclient/v2/info.py b/glanceclient/v2/info.py new file mode 100644 index 000000000..1c40567d2 --- /dev/null +++ b/glanceclient/v2/info.py @@ -0,0 +1,23 @@ +# Copyright 2022 Red Hat, Inc. +# +# 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. + + +class Controller: + def __init__(self, http_client, schema_client): + self.http_client = http_client + self.schema_client = schema_client + + def get_usage(self, **kwargs): + resp, body = self.http_client.get('/v2/info/usage') + return body['usage'] diff --git a/glanceclient/v2/shell.py b/glanceclient/v2/shell.py index 5f83bd278..4f7ffd988 100644 --- a/glanceclient/v2/shell.py +++ b/glanceclient/v2/shell.py @@ -486,6 +486,15 @@ def do_image_tasks(gc, args): utils.exit('Server does not support image tasks API (v2.12)') +def do_usage(gc, args): + """Get quota usage information.""" + columns = ['Quota', 'Limit', 'Usage'] + usage = gc.info.get_usage() + utils.print_dict_list( + [dict(v, quota=k) for k, v in usage.items()], + columns) + + @utils.arg('--image-id', metavar='', required=True, help=_('Image to display members of.')) def do_member_list(gc, args): From 282ce9c209d60ecb496e3e66126b71791fe6a1d4 Mon Sep 17 00:00:00 2001 From: Mridula Joshi Date: Wed, 12 Jan 2022 13:28:07 +0000 Subject: [PATCH 093/167] Add an optional parameter --detail This patch appends th --detail parameter to the ``stores-info`` command. With sufficient permissions, display additional information about the stores. Depends-On: https://review.opendev.org/c/openstack/glance/+/824438 Change-Id: I6ae08ab3eaab0c2b118aa7607246214b28025dfe --- glanceclient/tests/unit/v2/test_shell_v2.py | 52 ++++++++++++++++++++- glanceclient/v2/images.py | 7 +++ glanceclient/v2/shell.py | 8 +++- 3 files changed, 63 insertions(+), 4 deletions(-) diff --git a/glanceclient/tests/unit/v2/test_shell_v2.py b/glanceclient/tests/unit/v2/test_shell_v2.py index 83c472732..a96c70e65 100644 --- a/glanceclient/tests/unit/v2/test_shell_v2.py +++ b/glanceclient/tests/unit/v2/test_shell_v2.py @@ -150,8 +150,44 @@ def _run_command(self, cmd): ] } + stores_info_detail_response = { + "stores": [ + { + "default": "true", + "id": "ceph1", + "type": "rbd", + "description": "RBD backend for glance.", + "properties": { + "pool": "pool1", + "chunk_size": "4" + } + }, + { + "id": "file2", + "type": "file", + "description": "Filesystem backend for glance.", + "properties": {} + }, + { + "id": "file1", + "type": "file", + "description": "Filesystem backend for gkance.", + "properties": {} + }, + { + "id": "ceph2", + "type": "rbd", + "description": "RBD backend for glance.", + "properties": { + "pool": "pool2", + "chunk_size": "4" + } + } + ] + } + def test_do_stores_info(self): - args = [] + args = self._make_args({'detail': False}) with mock.patch.object(self.gc.images, 'get_stores_info') as mocked_list: mocked_list.return_value = self.stores_info_response @@ -166,7 +202,7 @@ def test_do_stores_info(self): def test_neg_stores_info( self, mock_stdin, mock_utils_exit): expected_msg = ('Multi Backend support is not enabled') - args = [] + args = self._make_args({'detail': False}) mock_utils_exit.side_effect = self._mock_utils_exit with mock.patch.object(self.gc.images, 'get_stores_info') as mocked_info: @@ -178,6 +214,18 @@ def test_neg_stores_info( pass mock_utils_exit.assert_called_once_with(expected_msg) + def test_do_stores_info_with_detail(self): + args = self._make_args({'detail': True}) + with mock.patch.object(self.gc.images, + 'get_stores_info_detail') as mocked_list: + mocked_list.return_value = self.stores_info_detail_response + + test_shell.do_stores_info(self.gc, args) + + mocked_list.assert_called_once_with() + utils.print_dict.assert_called_once_with( + self.stores_info_detail_response) + @mock.patch('sys.stderr') def test_image_create_missing_disk_format(self, __): e = self.assertRaises(exc.CommandError, self._run_command, diff --git a/glanceclient/v2/images.py b/glanceclient/v2/images.py index b412c42d4..eeb5ee125 100644 --- a/glanceclient/v2/images.py +++ b/glanceclient/v2/images.py @@ -322,6 +322,13 @@ def get_stores_info(self): resp, body = self.http_client.get(url) return body, resp + @utils.add_req_id_to_object() + def get_stores_info_detail(self): + """Get available stores info from discovery endpoint.""" + url = '/v2/info/stores/detail' + resp, body = self.http_client.get(url) + return body, resp + @utils.add_req_id_to_object() def delete_from_store(self, store_id, image_id): """Delete image data from specific store.""" diff --git a/glanceclient/v2/shell.py b/glanceclient/v2/shell.py index 5f83bd278..3037d7245 100644 --- a/glanceclient/v2/shell.py +++ b/glanceclient/v2/shell.py @@ -574,11 +574,15 @@ def do_import_info(gc, args): else: utils.print_dict(import_info) - +@utils.arg('--detail', default=False, action='store_true', + help='Shows details of stores. Admin only.') def do_stores_info(gc, args): """Print available backends from Glance.""" try: - stores_info = gc.images.get_stores_info() + if args.detail: + stores_info = gc.images.get_stores_info_detail() + else: + stores_info = gc.images.get_stores_info() except exc.HTTPNotFound: utils.exit('Multi Backend support is not enabled') else: From b8863535a823ef2c271ebc4ddd1afd039bbc3f93 Mon Sep 17 00:00:00 2001 From: Mridula Joshi Date: Tue, 12 Oct 2021 07:32:36 +0000 Subject: [PATCH 094/167] Add an optional parameter --append This patch will add an optional parameter --append to the glanceclient command md-tag-create-multiple to provide the facility of appending the tags. If the parameter is present it will append the tags to existing one, else it will overwrite the existing tags. Depends-On: https://review.opendev.org/c/openstack/glance/+/804966 Change-Id: I1841e7146da76b13f4cd8925e19f59d0eaf08f7a --- glanceclient/tests/unit/v2/test_shell_v2.py | 26 +++++++++++++++++++-- glanceclient/v2/metadefs.py | 11 +++++---- glanceclient/v2/shell.py | 6 +++-- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/glanceclient/tests/unit/v2/test_shell_v2.py b/glanceclient/tests/unit/v2/test_shell_v2.py index 83c472732..057cacde4 100644 --- a/glanceclient/tests/unit/v2/test_shell_v2.py +++ b/glanceclient/tests/unit/v2/test_shell_v2.py @@ -3084,7 +3084,8 @@ def test_do_md_tag_list(self): def test_do_md_tag_create_multiple(self): args = self._make_args({'namespace': 'MyNamespace', 'delim': ',', - 'names': 'MyTag1, MyTag2'}) + 'names': 'MyTag1, MyTag2', + 'append': False}) with mock.patch.object( self.gc.metadefs_tag, 'create_multiple') as mocked_create_tags: expect_tags = [{'tags': [{'name': 'MyTag1'}, {'name': 'MyTag2'}]}] @@ -3094,7 +3095,28 @@ def test_do_md_tag_create_multiple(self): test_shell.do_md_tag_create_multiple(self.gc, args) mocked_create_tags.assert_called_once_with( - 'MyNamespace', tags=['MyTag1', 'MyTag2']) + 'MyNamespace', tags=['MyTag1', 'MyTag2'], append=False) + utils.print_list.assert_called_once_with( + expect_tags, + ['name'], + field_settings={ + 'description': {'align': 'l', 'max_width': 50}}) + + def test_do_md_tag_create_multiple_with_append(self): + args = self._make_args({'namespace': 'MyNamespace', + 'delim': ',', + 'names': 'MyTag1, MyTag2', + 'append': True}) + with mock.patch.object( + self.gc.metadefs_tag, 'create_multiple') as mocked_create_tags: + expect_tags = [{'tags': [{'name': 'MyTag1'}, {'name': 'MyTag2'}]}] + + mocked_create_tags.return_value = expect_tags + + test_shell.do_md_tag_create_multiple(self.gc, args) + + mocked_create_tags.assert_called_once_with( + 'MyNamespace', tags=['MyTag1', 'MyTag2'], append=True) utils.print_list.assert_called_once_with( expect_tags, ['name'], diff --git a/glanceclient/v2/metadefs.py b/glanceclient/v2/metadefs.py index 1b641ac75..a98a9fec5 100644 --- a/glanceclient/v2/metadefs.py +++ b/glanceclient/v2/metadefs.py @@ -490,9 +490,8 @@ def create_multiple(self, namespace, **kwargs): """Create the list of tags. :param namespace: Name of a namespace to which the Tags belong. - :param kwargs: list of tags. + :param kwargs: list of tags, optional parameter append. """ - tag_names = kwargs.pop('tags', []) md_tag_list = [] @@ -502,11 +501,15 @@ def create_multiple(self, namespace, **kwargs): except (warlock.InvalidOperation) as e: raise TypeError(encodeutils.exception_to_unicode(e)) tags = {'tags': md_tag_list} + headers = {} url = '/v2/metadefs/namespaces/%(namespace)s/tags' % { - 'namespace': namespace} + 'namespace': namespace} - resp, body = self.http_client.post(url, data=tags) + append = kwargs.pop('append', False) + if append: + headers['X-Openstack-Append'] = True + resp, body = self.http_client.post(url, headers=headers, data=tags) body.pop('self', None) for tag in body['tags']: yield self.model(tag), resp diff --git a/glanceclient/v2/shell.py b/glanceclient/v2/shell.py index 5f83bd278..407fa44f6 100644 --- a/glanceclient/v2/shell.py +++ b/glanceclient/v2/shell.py @@ -1383,10 +1383,12 @@ def do_md_tag_create(gc, args): @utils.arg('--delim', metavar='', required=False, help=_('The delimiter used to separate the names' ' (if none is provided then the default is a comma).')) +@utils.arg('--append', default=False, action='store_true', required=False, + help=_('Append the new tags to the existing ones instead of' + 'overwriting them')) def do_md_tag_create_multiple(gc, args): """Create new metadata definitions tags inside a namespace.""" delim = args.delim or ',' - tags = [] names_list = args.names.split(delim) for name in names_list: @@ -1398,7 +1400,7 @@ def do_md_tag_create_multiple(gc, args): utils.exit('Please supply at least one tag name. For example: ' '--names Tag1') - fields = {'tags': tags} + fields = {'tags': tags, 'append': args.append} new_tags = gc.metadefs_tag.create_multiple(args.namespace, **fields) columns = ['name'] column_settings = { From 62f4f67d1d3f1ad74418d1e8cd5bb946cc8425d9 Mon Sep 17 00:00:00 2001 From: Erno Kuvaja Date: Fri, 9 Jul 2021 10:41:22 +0100 Subject: [PATCH 095/167] Add support for Cache API This change provides support for the Cache API changes and deprecation path for glance-cache-manage command. Change-Id: I6fca9bbe6bc0bd9b14d8dba685405838131160af --- glanceclient/common/utils.py | 44 ++++- glanceclient/tests/unit/v2/test_cache.py | 135 +++++++++++++++ glanceclient/tests/unit/v2/test_shell_v2.py | 176 ++++++++++++++++++++ glanceclient/v2/cache.py | 62 +++++++ glanceclient/v2/client.py | 4 + glanceclient/v2/shell.py | 71 ++++++++ 6 files changed, 491 insertions(+), 1 deletion(-) create mode 100644 glanceclient/tests/unit/v2/test_cache.py create mode 100644 glanceclient/v2/cache.py diff --git a/glanceclient/common/utils.py b/glanceclient/common/utils.py index 169126401..fd0243cbf 100644 --- a/glanceclient/common/utils.py +++ b/glanceclient/common/utils.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +import datetime import errno import functools import hashlib @@ -175,13 +176,54 @@ def pretty_choice_list(l): def has_version(client, version): versions = client.get('/versions')[1].get('versions') - supported = ['SUPPORTED', 'CURRENT'] + supported = ['SUPPORTED', 'CURRENT', 'EXPERIMENTAL'] for version_struct in versions: if version_struct['id'] == version: return version_struct['status'] in supported return False +def print_cached_images(cached_images): + cache_pt = prettytable.PrettyTable(("ID", + "State", + "Last Accessed (UTC)", + "Last Modified (UTC)", + "Size", + "Hits")) + for item in cached_images: + state = "queued" + last_accessed = "N/A" + last_modified = "N/A" + size = "N/A" + hits = "N/A" + if item == 'cached_images': + state = "cached" + for image in cached_images[item]: + last_accessed = image['last_accessed'] + if last_accessed == 0: + last_accessed = "N/A" + else: + last_accessed = datetime.datetime.utcfromtimestamp( + last_accessed).isoformat() + + cache_pt.add_row((image['image_id'], state, + last_accessed, + datetime.datetime.utcfromtimestamp( + image['last_modified']).isoformat(), + image['size'], + image['hits'])) + else: + for image in cached_images[item]: + cache_pt.add_row((image, + state, + last_accessed, + last_modified, + size, + hits)) + + print(cache_pt.get_string()) + + def print_dict_list(objects, fields): pt = prettytable.PrettyTable([f for f in fields], caching=False) pt.align = 'l' diff --git a/glanceclient/tests/unit/v2/test_cache.py b/glanceclient/tests/unit/v2/test_cache.py new file mode 100644 index 000000000..a5a908f09 --- /dev/null +++ b/glanceclient/tests/unit/v2/test_cache.py @@ -0,0 +1,135 @@ +# Copyright 2021 Red Hat Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import testtools +from unittest import mock + +from glanceclient.common import utils as common_utils +from glanceclient import exc +from glanceclient.tests import utils +from glanceclient.v2 import cache + + +data_fixtures = { + '/v2/cache': { + 'GET': ( + {}, + { + 'cached_images': [ + { + 'id': 'b0aa672a-bc26-4fcb-8be1-f53ca361943d', + 'Last Accessed (UTC)': '2021-08-09T07:08:20.214543', + 'Last Modified (UTC)': '2021-08-09T07:08:20.214543', + 'Size': 13267968, + 'Hits': 0 + }, + { + 'id': 'df601a47-7251-4d20-84ae-07de335af424', + 'Last Accessed (UTC)': '2021-08-09T07:08:20.214543', + 'Last Modified (UTC)': '2021-08-09T07:08:20.214543', + 'Size': 13267968, + 'Hits': 0 + }, + ], + 'queued_images': [ + '3a4560a1-e585-443e-9b39-553b46ec92d1', + '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810' + ], + }, + ), + 'DELETE': ( + {}, + '', + ), + }, + '/v2/cache/3a4560a1-e585-443e-9b39-553b46ec92d1': { + 'PUT': ( + {}, + '', + ), + 'DELETE': ( + {}, + '', + ), + }, +} + + +class TestCacheController(testtools.TestCase): + def setUp(self): + super(TestCacheController, self).setUp() + self.api = utils.FakeAPI(data_fixtures) + self.controller = cache.Controller(self.api) + + @mock.patch.object(common_utils, 'has_version') + def test_list_cached(self, mock_has_version): + mock_has_version.return_value = True + images = self.controller.list() + # Verify that we have 2 cached and 2 queued images + self.assertEqual(2, len(images['cached_images'])) + self.assertEqual(2, len(images['queued_images'])) + + @mock.patch.object(common_utils, 'has_version') + def test_list_cached_empty_response(self, mock_has_version): + dummy_fixtures = { + '/v2/cache': { + 'GET': ( + {}, + { + 'cached_images': [], + 'queued_images': [], + }, + ), + } + } + dummy_api = utils.FakeAPI(dummy_fixtures) + dummy_controller = cache.Controller(dummy_api) + mock_has_version.return_value = True + images = dummy_controller.list() + # Verify that we have 0 cached and 0 queued images + self.assertEqual(0, len(images['cached_images'])) + self.assertEqual(0, len(images['queued_images'])) + + @mock.patch.object(common_utils, 'has_version') + def test_queue_image(self, mock_has_version): + mock_has_version.return_value = True + image_id = '3a4560a1-e585-443e-9b39-553b46ec92d1' + self.controller.queue(image_id) + expect = [('PUT', '/v2/cache/%s' % image_id, + {}, None)] + self.assertEqual(expect, self.api.calls) + + @mock.patch.object(common_utils, 'has_version') + def test_cache_clear_with_header(self, mock_has_version): + mock_has_version.return_value = True + self.controller.clear("cache") + expect = [('DELETE', '/v2/cache', + {'x-image-cache-clear-target': 'cache'}, None)] + self.assertEqual(expect, self.api.calls) + + @mock.patch.object(common_utils, 'has_version') + def test_cache_delete(self, mock_has_version): + mock_has_version.return_value = True + image_id = '3a4560a1-e585-443e-9b39-553b46ec92d1' + self.controller.delete(image_id) + expect = [('DELETE', '/v2/cache/%s' % image_id, + {}, None)] + self.assertEqual(expect, self.api.calls) + + @mock.patch.object(common_utils, 'has_version') + def test_cache_not_supported(self, mock_has_version): + mock_has_version.return_value = False + self.assertRaises(exc.HTTPNotImplemented, + self.controller.list) diff --git a/glanceclient/tests/unit/v2/test_shell_v2.py b/glanceclient/tests/unit/v2/test_shell_v2.py index e91045deb..9d3841650 100644 --- a/glanceclient/tests/unit/v2/test_shell_v2.py +++ b/glanceclient/tests/unit/v2/test_shell_v2.py @@ -114,6 +114,7 @@ def _mock_utils(self): utils.print_dict = mock.Mock() utils.save_image = mock.Mock() utils.print_dict_list = mock.Mock() + utils.print_cached_images = mock.Mock() def assert_exits_with_msg(self, func, func_args, err_msg=None): with mock.patch.object(utils, 'exit') as mocked_utils_exit: @@ -3180,3 +3181,178 @@ def test_do_md_tag_create_multiple_with_append(self): ['name'], field_settings={ 'description': {'align': 'l', 'max_width': 50}}) + + def _test_do_cache_list(self, supported=True): + args = self._make_args({}) + expected_output = { + "cached_images": [ + { + "image_id": "pass", + "last_accessed": 0, + "last_modified": 0, + "size": "fake_size", + "hits": "fake_hits", + } + ], + "queued_images": ['fake_image'] + } + + with mock.patch.object(self.gc.cache, 'list') as mocked_cache_list: + if supported: + mocked_cache_list.return_value = expected_output + else: + mocked_cache_list.side_effect = exc.HTTPNotImplemented + test_shell.do_cache_list(self.gc, args) + mocked_cache_list.assert_called() + if supported: + utils.print_cached_images.assert_called_once_with( + expected_output) + + def test_do_cache_list(self): + self._test_do_cache_list() + + def test_do_cache_list_unsupported(self): + self.assertRaises(exc.HTTPNotImplemented, + self._test_do_cache_list, supported=False) + + def test_do_cache_list_endpoint_not_provided(self): + args = self._make_args({}) + self.gc.endpoint_provided = False + with mock.patch('glanceclient.common.utils.exit') as mock_exit: + test_shell.do_cache_list(self.gc, args) + mock_exit.assert_called_once_with( + 'Direct server endpoint needs to be provided. Do ' + 'not use loadbalanced or catalog endpoints.') + + def _test_cache_queue(self, supported=True, forbidden=False,): + args = argparse.Namespace(id=['image1']) + with mock.patch.object(self.gc.cache, 'queue') as mocked_cache_queue: + if supported: + mocked_cache_queue.return_value = None + else: + mocked_cache_queue.side_effect = exc.HTTPNotImplemented + if forbidden: + mocked_cache_queue.side_effect = exc.HTTPForbidden + + test_shell.do_cache_queue(self.gc, args) + if supported: + mocked_cache_queue.assert_called_once_with('image1') + + def test_do_cache_queue(self): + self._test_cache_queue() + + def test_do_cache_queue_unsupported(self): + with mock.patch( + 'glanceclient.common.utils.print_err') as mock_print_err: + self._test_cache_queue(supported=False) + mock_print_err.assert_called_once_with( + "'HTTP HTTPNotImplemented': Unable to queue image " + "'image1' for caching.") + + def test_do_cache_queue_forbidden(self): + with mock.patch( + 'glanceclient.common.utils.print_err') as mock_print_err: + self._test_cache_queue(forbidden=True) + mock_print_err.assert_called_once_with( + "You are not permitted to queue the image 'image1' for " + "caching.") + + def test_do_cache_queue_endpoint_not_provided(self): + args = argparse.Namespace(id=['image1']) + self.gc.endpoint_provided = False + with mock.patch('glanceclient.common.utils.exit') as mock_exit: + test_shell.do_cache_queue(self.gc, args) + mock_exit.assert_called_once_with( + 'Direct server endpoint needs to be provided. Do ' + 'not use loadbalanced or catalog endpoints.') + + def _test_cache_delete(self, supported=True, forbidden=False,): + args = argparse.Namespace(id=['image1']) + with mock.patch.object(self.gc.cache, 'delete') as mocked_cache_delete: + if supported: + mocked_cache_delete.return_value = None + else: + mocked_cache_delete.side_effect = exc.HTTPNotImplemented + if forbidden: + mocked_cache_delete.side_effect = exc.HTTPForbidden + + test_shell.do_cache_delete(self.gc, args) + if supported: + mocked_cache_delete.assert_called_once_with('image1') + + def test_do_cache_delete(self): + self._test_cache_delete() + + def test_do_cache_delete_unsupported(self): + with mock.patch( + 'glanceclient.common.utils.print_err') as mock_print_err: + self._test_cache_delete(supported=False) + mock_print_err.assert_called_once_with( + "'HTTP HTTPNotImplemented': Unable to delete image " + "'image1' from cache.") + + def test_do_cache_delete_forbidden(self): + with mock.patch( + 'glanceclient.common.utils.print_err') as mock_print_err: + self._test_cache_delete(forbidden=True) + mock_print_err.assert_called_once_with( + "You are not permitted to " + "delete the image 'image1' from cache.") + + def test_do_cache_delete_endpoint_not_provided(self): + args = argparse.Namespace(id=['image1']) + self.gc.endpoint_provided = False + with mock.patch('glanceclient.common.utils.exit') as mock_exit: + test_shell.do_cache_delete(self.gc, args) + mock_exit.assert_called_once_with( + 'Direct server endpoint needs to be provided. Do ' + 'not use loadbalanced or catalog endpoints.') + + def _test_cache_clear(self, target='both', supported=True, + forbidden=False,): + args = self._make_args({'target': target}) + with mock.patch.object(self.gc.cache, 'clear') as mocked_cache_clear: + if supported: + mocked_cache_clear.return_value = None + else: + mocked_cache_clear.side_effect = exc.HTTPNotImplemented + if forbidden: + mocked_cache_clear.side_effect = exc.HTTPForbidden + + test_shell.do_cache_clear(self.gc, args) + if supported: + mocked_cache_clear.mocked_cache_clear(target) + + def test_do_cache_clear_all(self): + self._test_cache_clear() + + def test_do_cache_clear_queued_only(self): + self._test_cache_clear(target='queue') + + def test_do_cache_clear_cached_only(self): + self._test_cache_clear(target='cache') + + def test_do_cache_clear_unsupported(self): + with mock.patch( + 'glanceclient.common.utils.print_err') as mock_print_err: + self._test_cache_clear(supported=False) + mock_print_err.assert_called_once_with( + "'HTTP HTTPNotImplemented': Unable to delete image(s) " + "from cache.") + + def test_do_cache_clear_forbidden(self): + with mock.patch( + 'glanceclient.common.utils.print_err') as mock_print_err: + self._test_cache_clear(forbidden=True) + mock_print_err.assert_called_once_with( + "You are not permitted to " + "delete image(s) from cache.") + + def test_do_cache_clear_endpoint_not_provided(self): + args = self._make_args({'target': 'both'}) + self.gc.endpoint_provided = False + with mock.patch('glanceclient.common.utils.exit') as mock_exit: + test_shell.do_cache_clear(self.gc, args) + mock_exit.assert_called_once_with( + 'Direct server endpoint needs to be provided. Do ' + 'not use loadbalanced or catalog endpoints.') diff --git a/glanceclient/v2/cache.py b/glanceclient/v2/cache.py new file mode 100644 index 000000000..7631c4abc --- /dev/null +++ b/glanceclient/v2/cache.py @@ -0,0 +1,62 @@ +# Copyright 2021 OpenStack Foundation +# All Rights Reserved. +# +# 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. + +from glanceclient.common import utils +from glanceclient import exc + +TARGET_VALUES = ('both', 'cache', 'queue') + + +class Controller(object): + def __init__(self, http_client): + self.http_client = http_client + + def is_supported(self, version): + if utils.has_version(self.http_client, version): + return True + else: + raise exc.HTTPNotImplemented( + 'Glance does not support image caching API (v2.14)') + + @utils.add_req_id_to_object() + def list(self): + if self.is_supported('v2.14'): + url = '/v2/cache' + resp, body = self.http_client.get(url) + return body, resp + + @utils.add_req_id_to_object() + def delete(self, image_id): + if self.is_supported('v2.14'): + resp, body = self.http_client.delete('/v2/cache/%s' % + image_id) + return body, resp + + @utils.add_req_id_to_object() + def clear(self, target): + if self.is_supported('v2.14'): + url = '/v2/cache' + headers = {} + if target != "both": + headers = {'x-image-cache-clear-target': target} + resp, body = self.http_client.delete(url, headers=headers) + return body, resp + + @utils.add_req_id_to_object() + def queue(self, image_id): + if self.is_supported('v2.14'): + url = '/v2/cache/%s' % image_id + resp, body = self.http_client.put(url) + return body, resp diff --git a/glanceclient/v2/client.py b/glanceclient/v2/client.py index 8b96bc743..8b8bd61d9 100644 --- a/glanceclient/v2/client.py +++ b/glanceclient/v2/client.py @@ -16,6 +16,7 @@ from glanceclient.common import http from glanceclient.common import utils +from glanceclient.v2 import cache from glanceclient.v2 import image_members from glanceclient.v2 import image_tags from glanceclient.v2 import images @@ -39,6 +40,7 @@ class Client(object): """ def __init__(self, endpoint=None, **kwargs): + self.endpoint_provided = endpoint is not None endpoint, self.version = utils.endpoint_version_from_url(endpoint, 2.0) self.http_client = http.get_http_client(endpoint=endpoint, **kwargs) self.schemas = schemas.Controller(self.http_client) @@ -69,3 +71,5 @@ def __init__(self, endpoint=None, **kwargs): metadefs.NamespaceController(self.http_client, self.schemas)) self.versions = versions.VersionController(self.http_client) + + self.cache = cache.Controller(self.http_client) diff --git a/glanceclient/v2/shell.py b/glanceclient/v2/shell.py index 05fc4640a..be627f5d7 100644 --- a/glanceclient/v2/shell.py +++ b/glanceclient/v2/shell.py @@ -23,6 +23,7 @@ from glanceclient.common import progressbar from glanceclient.common import utils from glanceclient import exc +from glanceclient.v2 import cache from glanceclient.v2 import image_members from glanceclient.v2 import image_schema from glanceclient.v2 import images @@ -1479,6 +1480,76 @@ def do_md_tag_list(gc, args): utils.print_list(tags, columns, field_settings=column_settings) +@utils.arg('--target', default='both', + choices=cache.TARGET_VALUES, + help=_('Specify which target you want to clear')) +def do_cache_clear(gc, args): + """Clear all images from cache, queue or both""" + if not gc.endpoint_provided: + utils.exit("Direct server endpoint needs to be provided. Do not use " + "loadbalanced or catalog endpoints.") + try: + gc.cache.clear(args.target) + except exc.HTTPForbidden: + msg = _("You are not permitted to delete image(s) " + "from cache.") + utils.print_err(msg) + except exc.HTTPException as e: + msg = _("'%s': Unable to delete image(s) from cache." % e) + utils.print_err(msg) + + +@utils.arg('id', metavar='', nargs='+', + help=_('ID of image(s) to delete from cache/queue.')) +def do_cache_delete(gc, args): + """Delete image from cache/caching queue.""" + if not gc.endpoint_provided: + utils.exit("Direct server endpoint needs to be provided. Do not use " + "loadbalanced or catalog endpoints.") + + for args_id in args.id: + try: + gc.cache.delete(args_id) + except exc.HTTPForbidden: + msg = _("You are not permitted to delete the image '%s' " + "from cache." % args_id) + utils.print_err(msg) + except exc.HTTPException as e: + msg = _("'%s': Unable to delete image '%s' from cache." + % (e, args_id)) + utils.print_err(msg) + + +def do_cache_list(gc, args): + """Get cache state.""" + if not gc.endpoint_provided: + utils.exit("Direct server endpoint needs to be provided. Do not use " + "loadbalanced or catalog endpoints.") + cached_images = gc.cache.list() + utils.print_cached_images(cached_images) + + +@utils.arg('id', metavar='', nargs='+', + help=_('ID of image(s) to queue for caching.')) +def do_cache_queue(gc, args): + """Queue image(s) for caching.""" + if not gc.endpoint_provided: + utils.exit("Direct server endpoint needs to be provided. Do not use " + "loadbalanced or catalog endpoints.") + + for args_id in args.id: + try: + gc.cache.queue(args_id) + except exc.HTTPForbidden: + msg = _("You are not permitted to queue the image '%s' " + "for caching." % args_id) + utils.print_err(msg) + except exc.HTTPException as e: + msg = _("'%s': Unable to queue image '%s' for caching." + % (e, args_id)) + utils.print_err(msg) + + @utils.arg('--sort-key', default='status', choices=tasks.SORT_KEY_VALUES, help=_('Sort task list by specified field.')) From ac0324c10db29261706a42a74bf38066b4a126de Mon Sep 17 00:00:00 2001 From: Abhishek Kekane Date: Thu, 17 Feb 2022 07:12:57 +0000 Subject: [PATCH 096/167] Release notes for 3.6.0 Change-Id: I1b441808672c3e362175d20f1d1f282b7f5d70a9 --- .../notes/3.6.0_Release-04d3b5017747290b.yaml | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 releasenotes/notes/3.6.0_Release-04d3b5017747290b.yaml diff --git a/releasenotes/notes/3.6.0_Release-04d3b5017747290b.yaml b/releasenotes/notes/3.6.0_Release-04d3b5017747290b.yaml new file mode 100644 index 000000000..4ba1dd398 --- /dev/null +++ b/releasenotes/notes/3.6.0_Release-04d3b5017747290b.yaml @@ -0,0 +1,51 @@ +--- +prelude: | + This version of python-glanceclient introduces new commands ``usage`` + to fetch quota related usage information and ``cache-clear``, + ``cache-delete``, ``cache-list`` and ``cache-queue`` for cache related + operations. + +features: + - | + Adds support for ``usage`` command which will report the usage + of unified limits configured. The following commands have been added to + the command line interface: + + * ``usage`` - Get quota usage information. + + - | + Client support has been added for the new caching functionality + introduced into Glance in this cycle. This feature is only available in + the Images API version 2 when the caching middleware is enabled in the + Glance service that the client is contacting. The following commands have + been added to the command line interface: + + * ``cache-clear`` - Delete all the images from cache and queued for caching + * ``cache-delete`` - Delete the specified image from cache or from the queued + list + * ``cache-list`` - List all the images which are cached or being queued for + caching + * ``cache-queue`` - Queue specified image(s) for caching. + + - | + Client side support has been added to fetch store specific configuration + information. With sufficient permissions, this will display additional + information about the stores. + + - | + Client side support has been added to provide the facility of appending + the tags while creating new multiple tags rather than overwriting the + existing tags. + +upgrade: + - | + The following Command Line Interface call now takes ``--detail`` option: + + * | ``glance stores-info`` + | The value for ``--detail`` option could be True or False. + + - | + The following Command Line Interface call now takes ``--append`` option: + + * | ``glance md-tag-create-multiple`` + | The value for ``--append`` option could be True or False. From b369f75d9a246dd75097f3eb73b52e8abb17347b Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Thu, 24 Feb 2022 14:55:11 +0000 Subject: [PATCH 097/167] Update master for stable/yoga Add file to the reno documentation build to show release notes for stable/yoga. Use pbr instruction to increment the minor version number automatically so that master versions are higher than the versions on stable/yoga. Sem-Ver: feature Change-Id: Iffb4d1e400b000e4bcedb7e14515a9b9f09b9e05 --- releasenotes/source/index.rst | 1 + releasenotes/source/yoga.rst | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 releasenotes/source/yoga.rst diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index 8cd670d89..56571c3da 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ glanceclient Release Notes :maxdepth: 1 unreleased + yoga xena wallaby victoria diff --git a/releasenotes/source/yoga.rst b/releasenotes/source/yoga.rst new file mode 100644 index 000000000..7cd5e908a --- /dev/null +++ b/releasenotes/source/yoga.rst @@ -0,0 +1,6 @@ +========================= +Yoga Series Release Notes +========================= + +.. release-notes:: + :branch: stable/yoga From 6c8108ea3349d88f791fb715d1ee0853accf7224 Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Thu, 24 Feb 2022 09:13:58 -0500 Subject: [PATCH 098/167] Fix functional CI job The glanceclient-dsvm-functional job is currently running unit tests, whereas it should be running the functional tests. Fixed by honoring the path in .stestr.conf like other trees. Also removed tox.ini from the list of irrelevant-files for this job, because as it turns out, it is relevant. Change-Id: I59773caa00ff0dfc970c0e4d45aa5d8ae006b1c6 --- .stestr.conf | 2 +- .zuul.yaml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.stestr.conf b/.stestr.conf index 44d74329d..a0b3fc822 100644 --- a/.stestr.conf +++ b/.stestr.conf @@ -1,3 +1,3 @@ [DEFAULT] -test_path=./glanceclient/tests/unit +test_path=${OS_TEST_PATH:-./glanceclient/tests/unit} top_path=./ diff --git a/.zuul.yaml b/.zuul.yaml index 285e19e8f..6568f1fc3 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -34,7 +34,6 @@ - ^(test-|)requirements.txt$ - ^lower-constraints.txt$ - ^setup.cfg$ - - ^tox.ini$ - job: name: glanceclient-tox-keystone-tips-base From cf7504e79564e85d4d4a1b51510e48e8d4a85e1c Mon Sep 17 00:00:00 2001 From: whoami-rajat Date: Wed, 20 Apr 2022 11:30:16 +0530 Subject: [PATCH 099/167] Add doc and test for verbose parameter We currently have support to show verbose output for image-list with the command ``glance --verbose image-list`` but there is no documentation about it. This patch adds the documentation and a test to run it via CLI. Closes-Bug: #1969565 Change-Id: Ic6db4f5ab2fecded373b044aa002f9a9bc262513 --- doc/source/cli/glance.rst | 4 ++++ glanceclient/tests/unit/v2/test_shell_v2.py | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/doc/source/cli/glance.rst b/doc/source/cli/glance.rst index 27215ef71..256a969aa 100644 --- a/doc/source/cli/glance.rst +++ b/doc/source/cli/glance.rst @@ -69,6 +69,10 @@ See available images:: glance image-list +To get a verbose output including more fields in the image list response:: + + glance --verbose image-list + Create new image:: glance image-create --name foo --disk-format=qcow2 \ diff --git a/glanceclient/tests/unit/v2/test_shell_v2.py b/glanceclient/tests/unit/v2/test_shell_v2.py index 9d3841650..c24a1c98e 100644 --- a/glanceclient/tests/unit/v2/test_shell_v2.py +++ b/glanceclient/tests/unit/v2/test_shell_v2.py @@ -368,6 +368,12 @@ def test_do_image_list_verbose(self): {}, ['ID', 'Name', 'Disk_format', 'Container_format', 'Size', 'Status', 'Owner']) + def test_do_image_list_verbose_cmd(self): + self._run_command('--os-image-api-version 2 --verbose image-list') + utils.print_list.assert_called_once_with( + mock.ANY, ['ID', 'Name', 'Disk_format', 'Container_format', + 'Size', 'Status', 'Owner']) + def test_do_image_list_with_include_stores_true(self): input = { 'limit': None, From be8f394ab1b487ea771e2ae7410e27c374cb9cd8 Mon Sep 17 00:00:00 2001 From: Ghanshyam Mann Date: Tue, 10 May 2022 19:11:11 -0500 Subject: [PATCH 100/167] Update python testing as per zed cycle teting runtime In Zed cycle, we have dropped the python 3.6/3.7[1] testing and its support. Moving the py36 job to py38 based as well as updating the python classifier also to reflect the same. [1] https://governance.openstack.org/tc/reference/runtimes/zed.html Change-Id: I9b3a05c708d53d1e7775eefdac802392fd18bc24 --- .zuul.yaml | 2 +- .../notes/drop-python-3-6-and-3-7-0b299b4dc9673c6e.yaml | 5 +++++ setup.cfg | 4 +--- 3 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/drop-python-3-6-and-3-7-0b299b4dc9673c6e.yaml diff --git a/.zuul.yaml b/.zuul.yaml index 08733b858..6462959c0 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -79,7 +79,7 @@ - check-requirements - lib-forward-testing-python3 - openstack-cover-jobs - - openstack-python3-yoga-jobs + - openstack-python3-zed-jobs - publish-openstack-docs-pti - release-notes-jobs-python3 check: diff --git a/releasenotes/notes/drop-python-3-6-and-3-7-0b299b4dc9673c6e.yaml b/releasenotes/notes/drop-python-3-6-and-3-7-0b299b4dc9673c6e.yaml new file mode 100644 index 000000000..db420d739 --- /dev/null +++ b/releasenotes/notes/drop-python-3-6-and-3-7-0b299b4dc9673c6e.yaml @@ -0,0 +1,5 @@ +--- +upgrade: + - | + Python 3.6 & 3.7 support has been dropped. The minimum version of Python now + supported is Python 3.8. diff --git a/setup.cfg b/setup.cfg index eea6b516d..59a59f523 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,7 +7,7 @@ license = Apache License, Version 2.0 author = OpenStack author_email = openstack-discuss@lists.openstack.org home_page = https://docs.openstack.org/python-glanceclient/latest/ -python_requires = >=3.6 +python_requires = >=3.8 classifier = Development Status :: 5 - Production/Stable Environment :: Console @@ -20,8 +20,6 @@ classifier = Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3 - Programming Language :: Python :: 3.6 - Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 From be26283a3722fee89286687e43cbeddf0ec33d3e Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Sun, 22 May 2022 22:33:44 +0900 Subject: [PATCH 101/167] Bump tox minversion to 3.18.0 Since tox 3.18.0, the whitelist_externals option has been deprecated in favor of the new allow_list_externals option[1]. This change bumps the minversion of tox so that we can replace the deprecated option. [1] https://github.com/tox-dev/tox/blob/master/docs/changelog.rst#v3180-2020-07-23 Change-Id: I76f328b8bed1338ab5496d21e54a4b0ff9251147 --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 3f1fbb16f..717a2382c 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] envlist = py39,pep8 -minversion = 2.0 +minversion = 3.18.0 skipsdist = True [testenv] @@ -35,7 +35,7 @@ warnerror = True setenv = OS_TEST_PATH = ./glanceclient/tests/functional/v2 OS_TESTENV_NAME = {envname} -whitelist_externals = +allowlist_externals = bash commands = bash tools/fix_ca_bundle.sh From 8df9328a60045edae36dc64cee1f2cf3cd4a09fa Mon Sep 17 00:00:00 2001 From: Benedikt Loeffler Date: Wed, 13 Oct 2021 13:50:36 +0200 Subject: [PATCH 102/167] Check if stdin has isatty attribute When the stdin is closed, it has no isatty atrribute which leads to a KeyError. Closes-Bug: #1980890 Change-Id: If9a3bf68b8dfd953b346697241166578d18bb563 --- glanceclient/common/utils.py | 2 +- glanceclient/v2/shell.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/glanceclient/common/utils.py b/glanceclient/common/utils.py index fd0243cbf..c3f08de86 100644 --- a/glanceclient/common/utils.py +++ b/glanceclient/common/utils.py @@ -447,7 +447,7 @@ def get_data_file(args): except OSError: # (1) stdin is not valid (closed...) return None - if not sys.stdin.isatty(): + if hasattr(sys.stdin, 'isatty') and not sys.stdin.isatty(): # (2) image data is provided through standard input image = sys.stdin if hasattr(sys.stdin, 'buffer'): diff --git a/glanceclient/v2/shell.py b/glanceclient/v2/shell.py index be627f5d7..84e363958 100644 --- a/glanceclient/v2/shell.py +++ b/glanceclient/v2/shell.py @@ -94,7 +94,7 @@ def do_image_create(gc, args): backend = args.store file_name = fields.pop('file', None) - using_stdin = not sys.stdin.isatty() + using_stdin = hasattr(sys.stdin, 'isatty') and not sys.stdin.isatty() if args.store and not (file_name or using_stdin): utils.exit("--store option should only be provided with --file " "option or stdin.") @@ -205,7 +205,7 @@ def do_image_create_via_import(gc, args): fields[key] = value file_name = fields.pop('file', None) - using_stdin = not sys.stdin.isatty() + using_stdin = hasattr(sys.stdin, 'isatty') and not sys.stdin.isatty() # special processing for backward compatibility with image-create if args.import_method is None and (file_name or using_stdin): From 508914c18155f8ab7df27a249635d7a7250a767a Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Mon, 11 Jul 2022 09:49:42 +0100 Subject: [PATCH 103/167] Uncap warlock warlock recently had a 2.0.0 release that added support for jsonschema 4.x [1]. We no longer need to cap this. [1] https://github.com/bcwaldon/warlock/releases/tag/2.0.0 Change-Id: Idb4c33284ec36c6c5938bd9045f9729e4ed6f7ea Signed-off-by: Stephen Finucane Depends-On: https://review.opendev.org/c/openstack/requirements/+/849284 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e2a92e45c..def3ad78f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ pbr!=2.1.0,>=2.0.0 # Apache-2.0 PrettyTable>=0.7.1 # BSD keystoneauth1>=3.6.2 # Apache-2.0 requests>=2.14.2 # Apache-2.0 -warlock<2,>=1.2.0 # Apache-2.0 +warlock>=1.2.0 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 wrapt>=1.7.0 # BSD License From 1b854e1657d7d94c27aa0b1458d48da78ad8621c Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Tue, 12 Jul 2022 16:20:32 +0100 Subject: [PATCH 104/167] Remove incorrect note from requirements files This hasn't been true since pip 20.3 introduced the new dependency resolver [1] [1] https://pyfound.blogspot.com/2020/11/pip-20-3-new-resolver.html Change-Id: I394f1135a9f3073ebc17ae9662faf6071e868ca8 Signed-off-by: Stephen Finucane --- requirements.txt | 3 --- test-requirements.txt | 5 ----- 2 files changed, 8 deletions(-) diff --git a/requirements.txt b/requirements.txt index def3ad78f..424dcf065 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,3 @@ -# The order of packages is significant, because pip processes them in the order -# of appearance. Changing the order has an impact on the overall integration -# process, which may cause wedges in the gate later. pbr!=2.1.0,>=2.0.0 # Apache-2.0 PrettyTable>=0.7.1 # BSD keystoneauth1>=3.6.2 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index e9f66220c..d635495b3 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,9 +1,4 @@ -# The order of packages is significant, because pip processes them in the order -# of appearance. Changing the order has an impact on the overall integration -# process, which may cause wedges in the gate later. - hacking>=3.0.1,<3.1.0 # Apache-2.0 - coverage!=4.4,>=4.0 # Apache-2.0 os-client-config>=1.28.0 # Apache-2.0 stestr>=2.0.0 # Apache-2.0 From 75218d289ed27b4eddd645436275b1e51aed0fab Mon Sep 17 00:00:00 2001 From: Cyril Roelandt Date: Wed, 23 Feb 2022 21:25:29 +0100 Subject: [PATCH 105/167] Delete python bytecode before every test run Because python creates pyc files during tox runs, certain changes in the tree, like branch switching or file deletion, can create spurious errors. Closes-Bug: #1368661 Change-Id: I93917b051039506d99837028700bc03337cf68f6 --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index 717a2382c..4cb7a27b9 100644 --- a/tox.ini +++ b/tox.ini @@ -7,6 +7,7 @@ skipsdist = True usedevelop = True setenv = OS_STDOUT_NOCAPTURE=False OS_STDERR_NOCAPTURE=False + PYTHONDONTWRITEBYTECODE=1 # Nowadays, TOX_CONSTRAINTS_FILE should be used, but some older scripts might # still be using UPPER_CONSTRAINTS_FILE, so we check both variables and use the From 219568c2a4b21c26ca4c160a7cf4d71cae524255 Mon Sep 17 00:00:00 2001 From: Erno Kuvaja Date: Fri, 26 Aug 2022 12:46:20 +0100 Subject: [PATCH 106/167] Bump default pagesize Bumping default pagesize to 200. With the previous default value of 20 glanceclient was doing lots of extra requests and schema validations. Based on my tests no performance improvement was seen over the pagesize of 200. Change-Id: I6d740ca3a9b32bf5d064d3ea74273bb619b32ad4 Closes-Bug: #1987834 --- glanceclient/tests/unit/test_shell.py | 2 +- glanceclient/tests/unit/v2/test_client_requests.py | 2 +- glanceclient/v2/images.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/glanceclient/tests/unit/test_shell.py b/glanceclient/tests/unit/test_shell.py index 6b9472e33..fce8ce079 100644 --- a/glanceclient/tests/unit/test_shell.py +++ b/glanceclient/tests/unit/test_shell.py @@ -711,7 +711,7 @@ def test_endpoint_real_from_interface(self): glance_shell = openstack_shell.OpenStackImagesShell() glance_shell.main(args.split()) self.assertEqual(self.requests.request_history[2].url, - self.image_url + "v2/images?limit=20&" + self.image_url + "v2/images?limit=200&" "sort_key=name&sort_dir=asc") diff --git a/glanceclient/tests/unit/v2/test_client_requests.py b/glanceclient/tests/unit/v2/test_client_requests.py index 5c97689f3..b1c32f25b 100644 --- a/glanceclient/tests/unit/v2/test_client_requests.py +++ b/glanceclient/tests/unit/v2/test_client_requests.py @@ -35,7 +35,7 @@ def test_list_bad_image_schema(self): self.requests = self.useFixture(rm_fixture.Fixture()) self.requests.get('http://example.com/v2/schemas/image', json=schema_fixture) - self.requests.get('http://example.com/v2/images?limit=20', + self.requests.get('http://example.com/v2/images?limit=200', json=image_list_fixture) gc = client.Client(2.2, "http://example.com/v2.1") images = gc.images.list() diff --git a/glanceclient/v2/images.py b/glanceclient/v2/images.py index eeb5ee125..4bc98c5d1 100644 --- a/glanceclient/v2/images.py +++ b/glanceclient/v2/images.py @@ -24,7 +24,7 @@ from glanceclient import exc from glanceclient.v2 import schemas -DEFAULT_PAGE_SIZE = 20 +DEFAULT_PAGE_SIZE = 200 SORT_DIR_VALUES = ('asc', 'desc') SORT_KEY_VALUES = ('name', 'status', 'container_format', 'disk_format', From 92cd70a2240dd5106ebfffecd6007942e898903a Mon Sep 17 00:00:00 2001 From: Pranali Deore Date: Fri, 26 Aug 2022 04:18:31 +0000 Subject: [PATCH 107/167] Add support for glance-download import method Implements: blueprint glance-download-import-support Change-Id: Ia2bfad82bccf9acb6103b21112e680c44e295d39 --- glanceclient/tests/unit/v2/base.py | 4 +- glanceclient/tests/unit/v2/test_images.py | 20 ++- glanceclient/tests/unit/v2/test_shell_v2.py | 151 ++++++++++++++++-- glanceclient/v2/images.py | 12 +- glanceclient/v2/shell.py | 60 ++++++- ...wnload-import-method-10525254db3e8e7a.yaml | 5 + 6 files changed, 232 insertions(+), 20 deletions(-) create mode 100644 releasenotes/notes/add-support-for-glance-download-import-method-10525254db3e8e7a.yaml diff --git a/glanceclient/tests/unit/v2/base.py b/glanceclient/tests/unit/v2/base.py index 694cd0fed..043acb12b 100644 --- a/glanceclient/tests/unit/v2/base.py +++ b/glanceclient/tests/unit/v2/base.py @@ -113,8 +113,8 @@ def deassociate(self, *args): resp = self.controller.deassociate(*args) self._assertRequestId(resp) - def image_import(self, *args): - resp = self.controller.image_import(*args) + def image_import(self, *args, **kwargs): + resp = self.controller.image_import(*args, **kwargs) self._assertRequestId(resp) diff --git a/glanceclient/tests/unit/v2/test_images.py b/glanceclient/tests/unit/v2/test_images.py index 199d6ec97..60ddb3a7a 100644 --- a/glanceclient/tests/unit/v2/test_images.py +++ b/glanceclient/tests/unit/v2/test_images.py @@ -1201,7 +1201,7 @@ def test_neg_data_with_bad_hash_value_and_fallback_enabled(self): body = ''.join([b for b in body]) self.assertEqual('GOODCHECKSUM', body) - def test_image_import(self): + def test_image_import_web_download(self): uri = 'http://example.com/image.qcow' data = [('method', {'name': 'web-download', 'uri': uri})] @@ -1211,6 +1211,24 @@ def test_image_import(self): data)] self.assertEqual(expect, self.api.calls) + def test_image_import_glance_download(self): + region = 'REGION2' + remote_image_id = '75baf7b6-253a-11ed-8307-4b1057986a78' + image_id = '606b0e88-7c5a-4d54-b5bb-046105d4de6f' + service_interface = 'public' + data = [('method', + {'name': 'glance-download', + 'glance_region': region, + 'glance_image_id': remote_image_id, + 'glance_service_interface': service_interface})] + self.controller.image_import( + image_id, 'glance-download', remote_region=region, + remote_image_id=remote_image_id, + remote_service_interface=service_interface) + expect = [('POST', '/v2/images/%s/import' % image_id, {}, + data)] + self.assertEqual(expect, self.api.calls) + def test_download_no_data(self): resp = utils.FakeResponse(headers={}, status_code=204) self.controller.controller.http_client.get = mock.Mock( diff --git a/glanceclient/tests/unit/v2/test_shell_v2.py b/glanceclient/tests/unit/v2/test_shell_v2.py index c24a1c98e..b9ced5891 100644 --- a/glanceclient/tests/unit/v2/test_shell_v2.py +++ b/glanceclient/tests/unit/v2/test_shell_v2.py @@ -957,12 +957,14 @@ def test_neg_do_image_create_invalid_store( 'progress': False, 'file': None, 'uri': None, + 'remote_region': None, 'import_method': None} import_info_response = {'import-methods': { 'type': 'array', 'description': 'Import methods available.', - 'value': ['glance-direct', 'web-download', 'copy-image']}} + 'value': ['glance-direct', 'web-download', 'copy-image', + 'glance-download']}} def _mock_utils_exit(self, msg): sys.exit(msg) @@ -1451,6 +1453,100 @@ def test_neg_image_create_via_import_web_download_with_file_and_uri( pass mock_utils_exit.assert_called_once_with(expected_msg) + @mock.patch('glanceclient.common.utils.exit') + @mock.patch('sys.stdin', autospec=True) + def test_neg_image_create_via_import_glance_download_no_region_and_id( + self, mock_stdin, mock_utils_exit): + expected_msg = ('REMOTE GlANCE REGION and REMOTE IMAGE ID are ' + 'required for glance-download import method. ' + 'Please use --remote-region and ' + '--remote-image-id .') + my_args = self.base_args.copy() + my_args['import_method'] = 'glance-download' + args = self._make_args(my_args) + mock_stdin.isatty = lambda: True + mock_utils_exit.side_effect = self._mock_utils_exit + with mock.patch.object(self.gc.images, + 'get_import_info') as mocked_info: + mocked_info.return_value = self.import_info_response + try: + test_shell.do_image_create_via_import(self.gc, args) + self.fail("utils.exit should have been called") + except SystemExit: + pass + mock_utils_exit.assert_called_once_with(expected_msg) + + @mock.patch('glanceclient.common.utils.exit') + @mock.patch('sys.stdin', autospec=True) + def test_neg_image_create_via_import_glance_download_with_uri( + self, mock_stdin, mock_utils_exit): + expected_msg = ('You cannot specify a --uri with the ' + 'glance-download import method.') + my_args = self.base_args.copy() + my_args['import_method'] = 'glance-download' + my_args['remote_region'] = 'REGION2' + my_args['remote_image_id'] = 'IMG2' + my_args['uri'] = 'https://example.com/some/stuff' + args = self._make_args(my_args) + mock_stdin.isatty = lambda: True + mock_utils_exit.side_effect = self._mock_utils_exit + with mock.patch.object(self.gc.images, + 'get_import_info') as mocked_info: + mocked_info.return_value = self.import_info_response + try: + test_shell.do_image_create_via_import(self.gc, args) + self.fail("utils.exit should have been called") + except SystemExit: + pass + mock_utils_exit.assert_called_once_with(expected_msg) + + @mock.patch('glanceclient.common.utils.exit') + @mock.patch('sys.stdin', autospec=True) + def test_neg_image_create_via_import_glance_download_with_file( + self, mock_stdin, mock_utils_exit): + expected_msg = ('You cannot specify a --file with the ' + 'glance-download import method.') + my_args = self.base_args.copy() + my_args['import_method'] = 'glance-download' + my_args['remote_region'] = 'REGION2' + my_args['remote_image_id'] = 'IMG2' + my_args['file'] = 'my.browncow' + args = self._make_args(my_args) + mock_stdin.isatty = lambda: True + mock_utils_exit.side_effect = self._mock_utils_exit + with mock.patch.object(self.gc.images, + 'get_import_info') as mocked_info: + mocked_info.return_value = self.import_info_response + try: + test_shell.do_image_create_via_import(self.gc, args) + self.fail("utils.exit should have been called") + except SystemExit: + pass + mock_utils_exit.assert_called_once_with(expected_msg) + + @mock.patch('glanceclient.common.utils.exit') + @mock.patch('sys.stdin', autospec=True) + def test_neg_image_create_via_import_glance_download_with_data( + self, mock_stdin, mock_utils_exit): + expected_msg = ('You cannot pass data via stdin with the ' + 'glance-download import method.') + my_args = self.base_args.copy() + my_args['import_method'] = 'glance-download' + my_args['remote_region'] = 'REGION2' + my_args['remote_image_id'] = 'IMG2' + args = self._make_args(my_args) + mock_stdin.isatty = lambda: False + mock_utils_exit.side_effect = self._mock_utils_exit + with mock.patch.object(self.gc.images, + 'get_import_info') as mocked_info: + mocked_info.return_value = self.import_info_response + try: + test_shell.do_image_create_via_import(self.gc, args) + self.fail("utils.exit should have been called") + except SystemExit: + pass + mock_utils_exit.assert_called_once_with(expected_msg) + @mock.patch('glanceclient.common.utils.exit') @mock.patch('sys.stdin', autospec=True) def test_neg_image_create_via_import_bad_method( @@ -2114,13 +2210,16 @@ def test_image_import_glance_direct(self): mock_import.return_value = None test_shell.do_image_import(self.gc, args) mock_import.assert_called_once_with( - 'IMG-01', 'glance-direct', None, backend=None, - all_stores=None, allow_failure=True, stores=None) + 'IMG-01', 'glance-direct', uri=None, + remote_region=None, remote_image_id=None, + remote_service_interface=None, + backend=None, all_stores=None, + allow_failure=True, stores=None) def test_image_import_web_download(self): args = self._make_args( {'id': 'IMG-01', 'uri': 'http://example.com/image.qcow', - 'import_method': 'web-download'}) + 'import_method': 'web-download'}) with mock.patch.object(self.gc.images, 'image_import') as mock_import: with mock.patch.object(self.gc.images, 'get') as mocked_get: with mock.patch.object(self.gc.images, @@ -2133,7 +2232,33 @@ def test_image_import_web_download(self): test_shell.do_image_import(self.gc, args) mock_import.assert_called_once_with( 'IMG-01', 'web-download', - 'http://example.com/image.qcow', + uri='http://example.com/image.qcow', + remote_region=None, remote_image_id=None, + remote_service_interface=None, + all_stores=None, allow_failure=True, + backend=None, stores=None) + + def test_image_import_glance_download(self): + args = self._make_args( + {'id': 'IMG-01', 'uri': None, 'remote-region': 'REGION2', + 'remote-image-id': 'IMG-02', + 'import_method': 'glance-download', + 'remote-service-interface': 'public'}) + with mock.patch.object(self.gc.images, 'image_import') as mock_import: + with mock.patch.object(self.gc.images, 'get') as mocked_get: + with mock.patch.object(self.gc.images, + 'get_import_info') as mocked_info: + mocked_get.return_value = {'status': 'queued', + 'container_format': 'bare', + 'disk_format': 'raw'} + mocked_info.return_value = self.import_info_response + mock_import.return_value = None + test_shell.do_image_import(self.gc, args) + mock_import.assert_called_once_with( + 'IMG-01', 'glance-download', + uri=None, remote_region='REGION2', + remote_image_id='IMG-02', + remote_service_interface='public', all_stores=None, allow_failure=True, backend=None, stores=None) @@ -2175,9 +2300,11 @@ def test_image_import_multiple_stores(self, mocked_utils_print_image, mock_import.return_value = None test_shell.do_image_import(self.gc, args) mock_import.assert_called_once_with( - 'IMG-02', 'glance-direct', None, all_stores=None, - allow_failure=True, stores=['site1', 'site2'], - backend=None) + 'IMG-02', 'glance-direct', uri=None, + remote_region=None, remote_image_id=None, + remote_service_interface=None, + all_stores=None, allow_failure=True, + stores=['site1', 'site2'], backend=None) @mock.patch('glanceclient.common.utils.print_image') @mock.patch('glanceclient.v2.shell._validate_backend') @@ -2197,9 +2324,11 @@ def test_image_import_copy_image(self, mocked_utils_print_image, mock_import.return_value = None test_shell.do_image_import(self.gc, args) mock_import.assert_called_once_with( - 'IMG-02', 'copy-image', None, all_stores=None, - allow_failure=True, stores=['file1', 'file2'], - backend=None) + 'IMG-02', 'copy-image', uri=None, + remote_region=None, remote_image_id=None, + remote_service_interface=None, + all_stores=None, allow_failure=True, + stores=['file1', 'file2'], backend=None) @mock.patch('glanceclient.common.utils.exit') def test_neg_image_import_copy_image_not_active( diff --git a/glanceclient/v2/images.py b/glanceclient/v2/images.py index eeb5ee125..67e5f7e08 100644 --- a/glanceclient/v2/images.py +++ b/glanceclient/v2/images.py @@ -353,8 +353,9 @@ def stage(self, image_id, image_data, image_size=None): @utils.add_req_id_to_object() def image_import(self, image_id, method='glance-direct', uri=None, - backend=None, stores=None, allow_failure=True, - all_stores=None): + remote_region=None, remote_image_id=None, + remote_service_interface=None, backend=None, + stores=None, allow_failure=True, all_stores=None): """Import Image via method.""" headers = {} url = '/v2/images/%s/import' % image_id @@ -370,6 +371,13 @@ def image_import(self, image_id, method='glance-direct', uri=None, if allow_failure: data['all_stores_must_succeed'] = False + if remote_region and remote_image_id: + if remote_service_interface: + data['method']['glance_service_interface'] = \ + remote_service_interface + data['method']['glance_region'] = remote_region + data['method']['glance_image_id'] = remote_image_id + if uri: if method == 'web-download': data['method']['uri'] = uri diff --git a/glanceclient/v2/shell.py b/glanceclient/v2/shell.py index 84e363958..773c1986a 100644 --- a/glanceclient/v2/shell.py +++ b/glanceclient/v2/shell.py @@ -148,6 +148,14 @@ def do_image_create(gc, args): 'record if no import-method and no data is supplied')) @utils.arg('--uri', metavar='', default=None, help=_('URI to download the external image.')) +@utils.arg('--remote-region', metavar='', default=None, + help=_('REMOTE_GLANCE_REGION to download the image.')) +@utils.arg('--remote-image-id', metavar='', default=None, + help=_('The IMAGE ID of the image of remote glance, which needs' + 'to be imported with glance-download')) +@utils.arg('--remote-service-interface', metavar='', + default='public', + help=_('The Remote Glance Service Interface for glance-download')) @utils.arg('--store', metavar='', default=utils.env('OS_IMAGE_STORE', default=None), help='Backend store to upload image to.') @@ -293,6 +301,22 @@ def do_image_create_via_import(gc, args): utils.exit("You cannot pass data via stdin with the web-download " "import method.") + if args.import_method == 'glance-download': + if not (args.remote_region and args.remote_image_id): + utils.exit("REMOTE GlANCE REGION and REMOTE IMAGE ID are " + "required for glance-download import method. " + "Please use --remote-region and " + "--remote-image-id .") + if args.uri: + utils.exit("You cannot specify a --uri with the glance-download " + "import method.") + if file_name: + utils.exit("You cannot specify a --file with the glance-download " + "import method.") + if using_stdin: + utils.exit("You cannot pass data via stdin with the " + "glance-download import method.") + # process image = gc.images.create(**fields) try: @@ -726,6 +750,14 @@ def do_image_stage(gc, args): '"image-stage".')) @utils.arg('--uri', metavar='', default=None, help=_('URI to download the external image.')) +@utils.arg('--remote-region', metavar='', default=None, + help=_('REMOTE GLANCE REGION to download the image.')) +@utils.arg('--remote-image-id', metavar='', default=None, + help=_('The IMAGE ID of the image of remote glance, which needs' + 'to be imported with glance-download')) +@utils.arg('--remote-service-interface', metavar='', + default='public', + help=_('The Remote Glance Service Interface for glance-download')) @utils.arg('id', metavar='', help=_('ID of image to import.')) @utils.arg('--store', metavar='', @@ -757,6 +789,10 @@ def do_image_import(gc, args): stores = getattr(args, "stores", None) all_stores = getattr(args, "os_all_stores", None) allow_failure = getattr(args, "os_allow_failure", True) + uri = getattr(args, "uri", None) + remote_region = getattr(args, "remote-region", None) + remote_image_id = getattr(args, "remote-image-id", None) + remote_service_interface = getattr(args, "remote-service-interface", None) if not getattr(args, 'from_create', False): if (args.store and (stores or all_stores)) or (stores and all_stores): @@ -800,6 +836,20 @@ def do_image_import(gc, args): utils.exit("Import method should be 'web-download' if URI is " "provided.") + if args.import_method == 'glance-download' and \ + not (remote_region and remote_image_id): + utils.exit("Provide REMOTE_IMAGE_ID and remote-region for " + "'glance-download' import method.") + if remote_region and args.import_method != 'glance-download': + utils.exit("Import method should be 'glance-download' if " + "REMOTE REGION is provided.") + if remote_image_id and args.import_method != 'glance-download': + utils.exit("Import method should be 'glance-download' if " + "REMOTE IMAGE ID is provided.") + if remote_service_interface and args.import_method != 'glance-download': + utils.exit("Import method should be 'glance-download' if " + "REMOTE SERVICE INTERFACE is provided.") + if args.import_method == 'copy-image' and not (stores or all_stores): utils.exit("Provide either --stores or --all-stores for " "'copy-image' import method.") @@ -827,10 +877,12 @@ def do_image_import(gc, args): "an image with status 'active'.") # finally, do the import - gc.images.image_import(args.id, args.import_method, args.uri, - backend=backend, - stores=stores, all_stores=all_stores, - allow_failure=allow_failure) + gc.images.image_import(args.id, args.import_method, uri=uri, + remote_region=remote_region, + remote_image_id=remote_image_id, + remote_service_interface=remote_service_interface, + backend=backend, stores=stores, + all_stores=all_stores, allow_failure=allow_failure) image = gc.images.get(args.id) utils.print_image(image) diff --git a/releasenotes/notes/add-support-for-glance-download-import-method-10525254db3e8e7a.yaml b/releasenotes/notes/add-support-for-glance-download-import-method-10525254db3e8e7a.yaml new file mode 100644 index 000000000..d449e3140 --- /dev/null +++ b/releasenotes/notes/add-support-for-glance-download-import-method-10525254db3e8e7a.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add support for new ``glance-download`` image-import method to + import image from another glance/region in federated deployment. From a3f13bdcb4108933fe6d99e9c4573ca74b52c5e5 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Tue, 6 Sep 2022 13:13:24 +0000 Subject: [PATCH 108/167] Update master for stable/zed Add file to the reno documentation build to show release notes for stable/zed. Use pbr instruction to increment the minor version number automatically so that master versions are higher than the versions on stable/zed. Sem-Ver: feature Change-Id: I2c7a8c65882218e927af0ff9771df7276891178e --- releasenotes/source/index.rst | 1 + releasenotes/source/zed.rst | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 releasenotes/source/zed.rst diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index 56571c3da..7f762fb2c 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ glanceclient Release Notes :maxdepth: 1 unreleased + zed yoga xena wallaby diff --git a/releasenotes/source/zed.rst b/releasenotes/source/zed.rst new file mode 100644 index 000000000..9608c05e4 --- /dev/null +++ b/releasenotes/source/zed.rst @@ -0,0 +1,6 @@ +======================== +Zed Series Release Notes +======================== + +.. release-notes:: + :branch: stable/zed From 393e84d30a173e8a001a8c9d877726c37dec6c17 Mon Sep 17 00:00:00 2001 From: Cyril Roelandt Date: Wed, 7 Sep 2022 18:55:50 +0200 Subject: [PATCH 109/167] md-property-create: add a mandatory "--type" option The "type" property is required when using md-property-create, so there should be a mandatory option for it, as is the case for "name" and "title". Change-Id: I3a118b6f2e375ad60bd4170c5ce0ae284a0c9060 Closes-Bug: #1934626 --- glanceclient/tests/unit/v2/test_shell_v2.py | 8 ++++++-- glanceclient/v2/shell.py | 4 +++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/glanceclient/tests/unit/v2/test_shell_v2.py b/glanceclient/tests/unit/v2/test_shell_v2.py index b9ced5891..d4af96425 100644 --- a/glanceclient/tests/unit/v2/test_shell_v2.py +++ b/glanceclient/tests/unit/v2/test_shell_v2.py @@ -2940,13 +2940,15 @@ def test_do_md_property_create(self): args = self._make_args({'namespace': 'MyNamespace', 'name': "MyProperty", 'title': "Title", + 'type': 'boolean', 'schema': '{}'}) with mock.patch.object(self.gc.metadefs_property, 'create') as mocked_create: expect_property = { 'namespace': 'MyNamespace', 'name': 'MyProperty', - 'title': 'Title' + 'title': 'Title', + 'type': 'boolean', } mocked_create.return_value = expect_property @@ -2955,13 +2957,15 @@ def test_do_md_property_create(self): mocked_create.assert_called_once_with('MyNamespace', name='MyProperty', - title='Title') + title='Title', + type='boolean') utils.print_dict.assert_called_once_with(expect_property) def test_do_md_property_create_invalid_schema(self): args = self._make_args({'namespace': 'MyNamespace', 'name': "MyProperty", 'title': "Title", + 'type': "boolean", 'schema': 'Invalid'}) self.assertRaises(SystemExit, test_shell.do_md_property_create, self.gc, args) diff --git a/glanceclient/v2/shell.py b/glanceclient/v2/shell.py index 773c1986a..54ecdf4c4 100644 --- a/glanceclient/v2/shell.py +++ b/glanceclient/v2/shell.py @@ -1231,6 +1231,8 @@ def do_md_namespace_resource_type_list(gc, args): help=_('Property name displayed to the user.')) @utils.arg('--schema', metavar='', required=True, help=_('Valid JSON schema of a property.')) +@utils.arg('--type', metavar='', required=True, + help=_('Type of the property')) def do_md_property_create(gc, args): """Create a new metadata definitions property inside a namespace.""" try: @@ -1238,7 +1240,7 @@ def do_md_property_create(gc, args): except ValueError: utils.exit('Schema is not a valid JSON object.') else: - fields = {'name': args.name, 'title': args.title} + fields = {'name': args.name, 'title': args.title, 'type': args.type} fields.update(schema) new_property = gc.metadefs_property.create(args.namespace, **fields) utils.print_dict(new_property) From 9e8fcdb92ed41594fe458b0976e9e29387849262 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Tue, 6 Sep 2022 13:13:26 +0000 Subject: [PATCH 110/167] Switch to 2023.1 Python3 unit tests and generic template name This is an automatically generated patch to ensure unit testing is in place for all the of the tested runtimes for antelope. Also, updating the template name to generic one. See also the PTI in governance [1]. [1]: https://governance.openstack.org/tc/reference/project-testing-interface.html Change-Id: I0b5f6a1b7de16ea9a5ee50bdc72b0bb4ceef1e29 --- .zuul.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.zuul.yaml b/.zuul.yaml index 6462959c0..65ac4cc62 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -79,7 +79,7 @@ - check-requirements - lib-forward-testing-python3 - openstack-cover-jobs - - openstack-python3-zed-jobs + - openstack-python3-jobs - publish-openstack-docs-pti - release-notes-jobs-python3 check: From bbfe6cd3be1ecb2c16c9176d3d1e254803a1b665 Mon Sep 17 00:00:00 2001 From: Erno Kuvaja Date: Thu, 22 Sep 2022 14:02:13 +0100 Subject: [PATCH 111/167] Replace osc with glance commands the are glance client docs. OSC should have their own section for this. Change-Id: I27123793fdf8f8a82afb909f2cceec71693609ea Closes-Bug: #1990532 --- doc/source/cli/property-keys.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/source/cli/property-keys.rst b/doc/source/cli/property-keys.rst index 9e59124e4..f2367fecd 100644 --- a/doc/source/cli/property-keys.rst +++ b/doc/source/cli/property-keys.rst @@ -7,14 +7,14 @@ that can be consumed by other services to affect the behavior of those other services. Properties can be set on an image at the time of image creation or they -can be set on an existing image. Use the :command:`openstack image create` -and :command:`openstack image set` commands respectively. +can be set on an existing image. Use the :command:`glance image-create` +and :command:`glance image-update` commands respectively. For example: .. code-block:: console - $ openstack image set IMG-UUID --property architecture=x86_64 + $ glance image-update IMG-UUID --property architecture=x86_64 For a list of image properties that can be used to affect the behavior of other services, refer to `Useful image properties From 74fa43665719ddc830999fa15bb052a87d69dd14 Mon Sep 17 00:00:00 2001 From: Cyril Roelandt Date: Tue, 20 Sep 2022 18:05:33 +0200 Subject: [PATCH 112/167] schema_args: Do not generate option for read-only properties The schema_args decorator generates command line options based on the properties defined in a schema. This commit makes sure read-only properties are skipped during this process, since trying to modify their value would result in a Glance error. Closes-Bug: #1561828 Change-Id: I7ccc628a23c9ebdaeedcb9e6d43559f497ce9555 --- glanceclient/common/utils.py | 2 ++ glanceclient/tests/unit/test_utils.py | 9 ++++++++- glanceclient/v2/shell.py | 23 +++++------------------ 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/glanceclient/common/utils.py b/glanceclient/common/utils.py index c3f08de86..e131bd97b 100644 --- a/glanceclient/common/utils.py +++ b/glanceclient/common/utils.py @@ -123,6 +123,8 @@ def _decorator(func): for name, property in properties.items(): if name in omit: continue + if property.get('readOnly', False): + continue param = '--' + name.replace('_', '-') kwargs = {} diff --git a/glanceclient/tests/unit/test_utils.py b/glanceclient/tests/unit/test_utils.py index 46cefbf6a..db08a1c8f 100644 --- a/glanceclient/tests/unit/test_utils.py +++ b/glanceclient/tests/unit/test_utils.py @@ -191,12 +191,17 @@ def test_schema_args_with_list_types(self): def schema_getter(_type='string', enum=False): prop = { 'type': ['null', _type], - 'readOnly': True, 'description': 'Test schema', } + prop_readonly = { + 'type': ['null', _type], + 'readOnly': True, + 'description': 'Test schema read-only', + } if enum: prop['enum'] = [None, 'opt-1', 'opt-2'] + prop_readonly['enum'] = [None, 'opt-ro-1', 'opt-ro-2'] def actual_getter(): return { @@ -205,6 +210,7 @@ def actual_getter(): 'name': 'test_schema', 'properties': { 'test': prop, + 'readonly-test': prop_readonly, } } @@ -214,6 +220,7 @@ def dummy_func(): pass decorated = utils.schema_args(schema_getter())(dummy_func) + self.assertEqual(len(decorated.__dict__['arguments']), 1) arg, opts = decorated.__dict__['arguments'][0] self.assertIn('--test', arg) self.assertEqual(encodeutils.safe_decode, opts['type']) diff --git a/glanceclient/v2/shell.py b/glanceclient/v2/shell.py index 773c1986a..93a93776b 100644 --- a/glanceclient/v2/shell.py +++ b/glanceclient/v2/shell.py @@ -49,11 +49,7 @@ def get_image_schema(): return IMAGE_SCHEMA -@utils.schema_args(get_image_schema, omit=['created_at', 'updated_at', 'file', - 'checksum', 'virtual_size', 'size', - 'status', 'schema', 'direct_url', - 'locations', 'self', 'os_hidden', - 'os_hash_value', 'os_hash_algo']) +@utils.schema_args(get_image_schema, omit=['locations', 'os_hidden']) # NOTE(rosmaita): to make this option more intuitive for end users, we # do not use the Glance image property name 'os_hidden' here. This means # we must include 'os_hidden' in the 'omit' list above and handle the @@ -118,11 +114,7 @@ def do_image_create(gc, args): utils.print_image(image) -@utils.schema_args(get_image_schema, omit=['created_at', 'updated_at', 'file', - 'checksum', 'virtual_size', 'size', - 'status', 'schema', 'direct_url', - 'locations', 'self', 'os_hidden', - 'os_hash_value', 'os_hash_algo']) +@utils.schema_args(get_image_schema, omit=['locations', 'os_hidden']) # NOTE: --hidden requires special handling; see note at do_image_create @utils.arg('--hidden', type=strutils.bool_from_string, metavar='[True|False]', default=None, @@ -354,12 +346,8 @@ def _validate_backend(backend, gc): @utils.arg('id', metavar='', help=_('ID of image to update.')) -@utils.schema_args(get_image_schema, omit=['id', 'locations', 'created_at', - 'updated_at', 'file', 'checksum', - 'virtual_size', 'size', 'status', - 'schema', 'direct_url', 'tags', - 'self', 'os_hidden', - 'os_hash_value', 'os_hash_algo']) +@utils.schema_args(get_image_schema, omit=['id', 'locations', 'tags', + 'os_hidden']) # NOTE: --hidden requires special handling; see note at do_image_create @utils.arg('--hidden', type=strutils.bool_from_string, metavar='[True|False]', default=None, @@ -1105,8 +1093,7 @@ def do_md_namespace_import(gc, args): @utils.schema_args(get_namespace_schema, omit=['property_count', 'properties', 'tag_count', 'tags', 'object_count', 'objects', - 'resource_type_associations', - 'schema']) + 'resource_type_associations']) def do_md_namespace_update(gc, args): """Update an existing metadata definitions namespace.""" schema = gc.schemas.get('metadefs/namespace') From fc8f9ac2edfb4daa0c48a650515de3e8eba18232 Mon Sep 17 00:00:00 2001 From: Cyril Roelandt Date: Tue, 13 Sep 2022 16:15:13 +0200 Subject: [PATCH 113/167] Remove unicode-related Python2-only code This commit: - removes the old "u" prefix from all strings - removes the unicode_key_value_to_string function Change-Id: I1da347e31e828fd2359f0935a4da47257116d4cb --- doc/source/conf.py | 8 +++---- glanceclient/common/utils.py | 23 -------------------- glanceclient/tests/unit/test_http.py | 24 ++++++++++----------- glanceclient/tests/unit/test_utils.py | 8 +------ glanceclient/tests/unit/v1/test_images.py | 4 ++-- glanceclient/tests/unit/v2/test_images.py | 8 +++---- glanceclient/tests/unit/v2/test_shell_v2.py | 2 +- releasenotes/source/conf.py | 16 +++++++------- 8 files changed, 32 insertions(+), 61 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index bfe9b56ab..c2a406622 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -57,7 +57,7 @@ # General information about the project. project = 'python-glanceclient' -copyright = u'OpenStack Foundation' +copyright = 'OpenStack Foundation' # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True @@ -83,8 +83,8 @@ # -- Options for man page output ---------------------------------------------- # Grouping the document tree for man pages. -# List of tuples 'sourcefile', 'target', u'title', u'Authors name', 'manual' +# List of tuples 'sourcefile', 'target', 'title', 'Authors name', 'manual' man_pages = [ - ('cli/glance', 'glance', u'Client for OpenStack Images API', - [u'OpenStack Foundation'], 1), + ('cli/glance', 'glance', 'Client for OpenStack Images API', + ['OpenStack Foundation'], 1), ] diff --git a/glanceclient/common/utils.py b/glanceclient/common/utils.py index c3f08de86..598562acd 100644 --- a/glanceclient/common/utils.py +++ b/glanceclient/common/utils.py @@ -269,34 +269,11 @@ def print_list(objs, fields, formatters=None, field_settings=None): field_name = field.lower().replace(' ', '_') data = getattr(o, field_name, None) or '' row.append(data) - count = 0 - # Converts unicode values in list to string - for part in row: - count = count + 1 - if isinstance(part, list): - part = unicode_key_value_to_string(part) - row[count - 1] = part pt.add_row(row) print(encodeutils.safe_decode(pt.get_string())) -def _encode(src): - """remove extra 'u' in PY2.""" - return src - - -def unicode_key_value_to_string(src): - """Recursively converts dictionary keys to strings.""" - if isinstance(src, dict): - return dict((_encode(k), - _encode(unicode_key_value_to_string(v))) - for k, v in src.items()) - if isinstance(src, list): - return [unicode_key_value_to_string(l) for l in src] - return _encode(src) - - def print_dict(d, max_column_width=80): pt = prettytable.PrettyTable(['Property', 'Value'], caching=False) pt.align = 'l' diff --git a/glanceclient/tests/unit/test_http.py b/glanceclient/tests/unit/test_http.py index 5759ccd99..fef3b6a47 100644 --- a/glanceclient/tests/unit/test_http.py +++ b/glanceclient/tests/unit/test_http.py @@ -66,7 +66,7 @@ def setUp(self): self.endpoint = 'http://example.com:9292' self.ssl_endpoint = 'https://example.com:9292' - self.token = u'abc123' + self.token = 'abc123' self.client = getattr(self, self.create_client)() @@ -80,7 +80,7 @@ def test_identity_headers_and_token(self): 'X-Service-Catalog': 'service_catalog', } # with token - kwargs = {'token': u'fake-token', + kwargs = {'token': 'fake-token', 'identity_headers': identity_headers} http_client_object = http.HTTPClient(self.endpoint, **kwargs) self.assertEqual('auth_token', http_client_object.auth_token) @@ -96,10 +96,10 @@ def test_identity_headers_and_no_token_in_header(self): 'X-Service-Catalog': 'service_catalog', } # without X-Auth-Token in identity headers - kwargs = {'token': u'fake-token', + kwargs = {'token': 'fake-token', 'identity_headers': identity_headers} http_client_object = http.HTTPClient(self.endpoint, **kwargs) - self.assertEqual(u'fake-token', http_client_object.auth_token) + self.assertEqual('fake-token', http_client_object.auth_token) self.assertTrue(http_client_object.identity_headers. get('X-Auth-Token') is None) @@ -210,12 +210,12 @@ def test_http_encoding(self): self.mock.get(self.endpoint + path, text=text, headers={"Content-Type": "text/plain"}) - headers = {"test": u'ni\xf1o'} + headers = {"test": 'ni\xf1o'} resp, body = self.client.get(path, headers=headers) self.assertEqual(text, resp.text) def test_headers_encoding(self): - value = u'ni\xf1o' + value = 'ni\xf1o' fake_location = b'http://web_server:80/images/fake.img' headers = {"test": value, "none-val": None, @@ -262,7 +262,7 @@ def test_http_duplicate_content_type_headers(self, mock_ksarq): ksarqh = mock_ksarq.call_args[1]['headers'] # Only one Content-Type header (of any text-type) self.assertEqual(1, [encodeutils.safe_decode(key) - for key in ksarqh.keys()].count(u'Content-Type')) + for key in ksarqh.keys()].count('Content-Type')) # And it's the one we set self.assertEqual(b"application/openstack-images-v2.1-json-patch", ksarqh[b"Content-Type"]) @@ -295,7 +295,7 @@ def test_raw_request(self): def test_parse_endpoint(self): endpoint = 'http://example.com:9292' - test_client = http.HTTPClient(endpoint, token=u'adc123') + test_client = http.HTTPClient(endpoint, token='adc123') actual = test_client.parse_endpoint(endpoint) expected = parse.SplitResult(scheme='http', netloc='example.com:9292', path='', @@ -304,7 +304,7 @@ def test_parse_endpoint(self): def test_get_connections_kwargs_http(self): endpoint = 'http://example.com:9292' - test_client = http.HTTPClient(endpoint, token=u'adc123') + test_client = http.HTTPClient(endpoint, token='adc123') self.assertEqual(600.0, test_client.timeout) def test__chunk_body_exact_size_chunk(self): @@ -321,7 +321,7 @@ def test_http_chunked_request(self): path = '/v1/images/' self.mock.post(self.endpoint + path, text=text) - headers = {"test": u'chunked_request'} + headers = {"test": 'chunked_request'} resp, body = self.client.post(path, headers=headers, data=data) self.assertIsInstance(self.mock.last_request.body, types.GeneratorType) self.assertEqual(text, resp.text) @@ -332,7 +332,7 @@ def test_http_json(self): text = 'OK' self.mock.post(self.endpoint + path, text=text) - headers = {"test": u'chunked_request'} + headers = {"test": 'chunked_request'} resp, body = self.client.post(path, headers=headers, data=data) self.assertEqual(text, resp.text) @@ -487,7 +487,7 @@ def test_expired_token_has_changed(self): headers = self.mock.last_request.headers self.assertEqual(refreshed_token, headers['X-Auth-Token']) # regression check for bug 1448080 - unicode_token = u'ni\xf1o+==' + unicode_token = 'ni\xf1o+==' http_client.auth_token = unicode_token http_client.get(path) headers = self.mock.last_request.headers diff --git a/glanceclient/tests/unit/test_utils.py b/glanceclient/tests/unit/test_utils.py index 46cefbf6a..9d666bfd3 100644 --- a/glanceclient/tests/unit/test_utils.py +++ b/glanceclient/tests/unit/test_utils.py @@ -122,7 +122,7 @@ def __init__(self, **entries): # test for removing 'u' from lists in print_list output columns = ['ID', 'Tags'] images = [Struct(**{'id': 'b8e1c57e-907a-4239-aed8-0df8e54b8d2d', - 'tags': [u'Name1', u'Tag_123', u'veeeery long']})] + 'tags': ['Name1', 'Tag_123', 'veeeery long']})] saved_stdout = sys.stdout try: sys.stdout = output_list = io.StringIO() @@ -178,12 +178,6 @@ def test_print_image_virtual_size_not_available(self): ''', output_list.getvalue()) - def test_unicode_key_value_to_string(self): - src = {u'key': u'\u70fd\u7231\u5a77'} - # u'xxxx' in PY3 is str, we will not get extra 'u' from cli - # output in PY3 - self.assertEqual(src, utils.unicode_key_value_to_string(src)) - def test_schema_args_with_list_types(self): # NOTE(flaper87): Regression for bug # https://bugs.launchpad.net/python-glanceclient/+bug/1401032 diff --git a/glanceclient/tests/unit/v1/test_images.py b/glanceclient/tests/unit/v1/test_images.py index 1af74125d..4b2ae5ec5 100644 --- a/glanceclient/tests/unit/v1/test_images.py +++ b/glanceclient/tests/unit/v1/test_images.py @@ -504,7 +504,7 @@ def test_get(self): self.assertEqual(False, image.is_public) self.assertEqual(False, image.protected) self.assertEqual(False, image.deleted) - self.assertEqual({u'arch': u'x86_64'}, image.properties) + self.assertEqual({'arch': 'x86_64'}, image.properties) def test_get_int(self): image = self.mgr.get(1) @@ -515,7 +515,7 @@ def test_get_int(self): self.assertEqual(False, image.is_public) self.assertEqual(False, image.protected) self.assertEqual(False, image.deleted) - self.assertEqual({u'arch': u'x86_64'}, image.properties) + self.assertEqual({'arch': 'x86_64'}, image.properties) def test_get_encoding(self): image = self.mgr.get('3') diff --git a/glanceclient/tests/unit/v2/test_images.py b/glanceclient/tests/unit/v2/test_images.py index 60ddb3a7a..725120316 100644 --- a/glanceclient/tests/unit/v2/test_images.py +++ b/glanceclient/tests/unit/v2/test_images.py @@ -550,10 +550,10 @@ { 'id': 'a2b83adc-888e-11e3-8872-78acc0b951d8', 'name': 'image-location-tests', - 'locations': [{u'url': u'http://foo.com/', - u'metadata': {u'foo': u'foometa'}}, - {u'url': u'http://bar.com/', - u'metadata': {u'bar': u'barmeta'}}], + 'locations': [{'url': 'http://foo.com/', + 'metadata': {'foo': 'foometa'}}, + {'url': 'http://bar.com/', + 'metadata': {'bar': 'barmeta'}}], }, ), 'PATCH': ( diff --git a/glanceclient/tests/unit/v2/test_shell_v2.py b/glanceclient/tests/unit/v2/test_shell_v2.py index b9ced5891..8acceaecb 100644 --- a/glanceclient/tests/unit/v2/test_shell_v2.py +++ b/glanceclient/tests/unit/v2/test_shell_v2.py @@ -854,7 +854,7 @@ def test_do_image_create_with_file(self): @mock.patch('sys.stdin', autospec=True) def test_do_image_create_with_unicode(self, mock_stdin): - name = u'\u041f\u0420\u0418\u0412\u0415\u0422\u0418\u041a' + name = '\u041f\u0420\u0418\u0412\u0415\u0422\u0418\u041a' args = self._make_args({'name': name, 'file': None}) diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py index d20e2f429..907b784d8 100644 --- a/releasenotes/source/conf.py +++ b/releasenotes/source/conf.py @@ -55,8 +55,8 @@ master_doc = 'index' # General information about the project. -project = u'glanceclient Release Notes' -copyright = u'2016, Glance Developers' +project = 'glanceclient Release Notes' +copyright = '2016, Glance Developers' openstackdocs_repo_name = 'openstack/python-glanceclient' openstackdocs_bug_project = 'python-glanceclient' @@ -206,8 +206,8 @@ # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'glanceclientReleaseNotes.tex', - u'glanceclient Release Notes Documentation', - u'Glance Developers', 'manual'), + 'glanceclient Release Notes Documentation', + 'Glance Developers', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -237,8 +237,8 @@ # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'glanceclientreleasenotes', - u'glanceclient Release Notes Documentation', - [u'Glance Developers'], 1) + 'glanceclient Release Notes Documentation', + ['Glance Developers'], 1) ] # If true, show URL addresses after external links. @@ -252,8 +252,8 @@ # dir menu entry, description, category) texinfo_documents = [ ('index', 'glanceclientReleaseNotes', - u'glanceclient Release Notes Documentation', - u'Glance Developers', 'glanceclientReleaseNotes', + 'glanceclient Release Notes Documentation', + 'Glance Developers', 'glanceclientReleaseNotes', 'Python bindings for the OpenStack Image service.', 'Miscellaneous'), ] From 4bb406d3774be4ad40f561a583f213a7b2a9133e Mon Sep 17 00:00:00 2001 From: Cyril Roelandt Date: Wed, 12 Jan 2022 21:28:24 +0100 Subject: [PATCH 114/167] Unhardcode the value of DEFAULT_PAGE_SIZE from the tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The value of DEFAULT_PAGE_SIZE (20) was hardcoded in multiple places in the tests, which means all the tests would break should we ever want to change that value. Co-Authored-By: 韩春蕾 <1922361860@qq.com> Change-Id: I6e8dbae32c3a24d3fbeebcea5bfe0dd9ae247035 --- glanceclient/tests/unit/test_shell.py | 4 ++- glanceclient/tests/unit/v1/test_images.py | 31 ++++++++++--------- .../tests/unit/v2/test_client_requests.py | 6 +++- .../tests/unit/v2/test_metadefs_namespaces.py | 16 +++++----- 4 files changed, 34 insertions(+), 23 deletions(-) diff --git a/glanceclient/tests/unit/test_shell.py b/glanceclient/tests/unit/test_shell.py index fce8ce079..4a123ab1c 100644 --- a/glanceclient/tests/unit/test_shell.py +++ b/glanceclient/tests/unit/test_shell.py @@ -45,6 +45,7 @@ DEFAULT_IMAGE_URL = 'http://127.0.0.1:9292/' DEFAULT_IMAGE_URL_INTERNAL = 'http://127.0.0.1:9191/' DEFAULT_USERNAME = 'username' +DEFAULT_PAGE_SIZE = 200 DEFAULT_PASSWORD = 'password' DEFAULT_TENANT_ID = 'tenant_id' DEFAULT_TENANT_NAME = 'tenant_name' @@ -711,7 +712,8 @@ def test_endpoint_real_from_interface(self): glance_shell = openstack_shell.OpenStackImagesShell() glance_shell.main(args.split()) self.assertEqual(self.requests.request_history[2].url, - self.image_url + "v2/images?limit=200&" + self.image_url + "v2/images?" + f"limit={DEFAULT_PAGE_SIZE}&" "sort_key=name&sort_dir=asc") diff --git a/glanceclient/tests/unit/v1/test_images.py b/glanceclient/tests/unit/v1/test_images.py index 1af74125d..ef8256cb9 100644 --- a/glanceclient/tests/unit/v1/test_images.py +++ b/glanceclient/tests/unit/v1/test_images.py @@ -25,6 +25,9 @@ from glanceclient.v1 import shell +DEFAULT_PAGE_SIZE = 20 + + fixtures = { '/v1/images': { 'POST': ( @@ -67,7 +70,7 @@ ]}, ), }, - '/v1/images/detail?is_public=None&limit=20': { + f'/v1/images/detail?is_public=None&limit={DEFAULT_PAGE_SIZE}': { 'GET': ( {'x-openstack-request-id': 'req-1234'}, {'images': [ @@ -157,7 +160,7 @@ ]}, ), }, - '/v1/images/detail?limit=20&marker=a': { + f'/v1/images/detail?limit={DEFAULT_PAGE_SIZE}&marker=a': { 'GET': ( {}, {'images': [ @@ -227,7 +230,7 @@ ]}, ), }, - '/v1/images/detail?limit=20&name=foo': { + f'/v1/images/detail?limit={DEFAULT_PAGE_SIZE}&name=foo': { 'GET': ( {}, {'images': [ @@ -244,7 +247,7 @@ ]}, ), }, - '/v1/images/detail?limit=20&property-ping=pong': + f'/v1/images/detail?limit={DEFAULT_PAGE_SIZE}&property-ping=pong': { 'GET': ( {}, @@ -257,7 +260,7 @@ ]}, ), }, - '/v1/images/detail?limit=20&sort_dir=desc': { + f'/v1/images/detail?limit={DEFAULT_PAGE_SIZE}&sort_dir=desc': { 'GET': ( {}, {'images': [ @@ -274,7 +277,7 @@ ]}, ), }, - '/v1/images/detail?limit=20&sort_key=name': { + f'/v1/images/detail?limit={DEFAULT_PAGE_SIZE}&sort_key=name': { 'GET': ( {}, {'images': [ @@ -467,31 +470,31 @@ def test_list_with_limit_greater_than_page_size(self): def test_list_with_marker(self): list(self.mgr.list(marker='a')) - url = '/v1/images/detail?limit=20&marker=a' + url = f'/v1/images/detail?limit={DEFAULT_PAGE_SIZE}&marker=a' expect = [('GET', url, {}, None)] self.assertEqual(expect, self.api.calls) def test_list_with_filter(self): list(self.mgr.list(filters={'name': "foo"})) - url = '/v1/images/detail?limit=20&name=foo' + url = f'/v1/images/detail?limit={DEFAULT_PAGE_SIZE}&name=foo' expect = [('GET', url, {}, None)] self.assertEqual(expect, self.api.calls) def test_list_with_property_filters(self): list(self.mgr.list(filters={'properties': {'ping': 'pong'}})) - url = '/v1/images/detail?limit=20&property-ping=pong' + url = f'/v1/images/detail?limit={DEFAULT_PAGE_SIZE}&property-ping=pong' expect = [('GET', url, {}, None)] self.assertEqual(expect, self.api.calls) def test_list_with_sort_dir(self): list(self.mgr.list(sort_dir='desc')) - url = '/v1/images/detail?limit=20&sort_dir=desc' + url = f'/v1/images/detail?limit={DEFAULT_PAGE_SIZE}&sort_dir=desc' expect = [('GET', url, {}, None)] self.assertEqual(expect, self.api.calls) def test_list_with_sort_key(self): list(self.mgr.list(sort_key='name')) - url = '/v1/images/detail?limit=20&sort_key=name' + url = f'/v1/images/detail?limit={DEFAULT_PAGE_SIZE}&sort_key=name' expect = [('GET', url, {}, None)] self.assertEqual(expect, self.api.calls) @@ -748,7 +751,7 @@ def test_image_meta_from_headers_encoding(self): self.assertEqual(value, headers["name"]) def test_image_list_with_owner(self): - images = self.mgr.list(owner='A', page_size=20) + images = self.mgr.list(owner='A', page_size=DEFAULT_PAGE_SIZE) image_list = list(images) self.assertEqual('A', image_list[0].owner) self.assertEqual('a', image_list[0].id) @@ -764,11 +767,11 @@ def test_image_list_with_owner_req_id(self): self.assertEqual(['req-1234'], fields['return_req_id']) def test_image_list_with_notfound_owner(self): - images = self.mgr.list(owner='X', page_size=20) + images = self.mgr.list(owner='X', page_size=DEFAULT_PAGE_SIZE) self.assertEqual(0, len(list(images))) def test_image_list_with_empty_string_owner(self): - images = self.mgr.list(owner='', page_size=20) + images = self.mgr.list(owner='', page_size=DEFAULT_PAGE_SIZE) image_list = list(images) self.assertRaises(AttributeError, lambda: image_list[0].owner) self.assertEqual('c', image_list[0].id) diff --git a/glanceclient/tests/unit/v2/test_client_requests.py b/glanceclient/tests/unit/v2/test_client_requests.py index b1c32f25b..287a20183 100644 --- a/glanceclient/tests/unit/v2/test_client_requests.py +++ b/glanceclient/tests/unit/v2/test_client_requests.py @@ -25,6 +25,9 @@ from glanceclient.v2.image_schema import _BASE_SCHEMA +DEFAULT_PAGE_SIZE = 200 + + class ClientTestRequests(testutils.TestCase): """Client tests using the requests mock library.""" @@ -35,7 +38,8 @@ def test_list_bad_image_schema(self): self.requests = self.useFixture(rm_fixture.Fixture()) self.requests.get('http://example.com/v2/schemas/image', json=schema_fixture) - self.requests.get('http://example.com/v2/images?limit=200', + self.requests.get('http://example.com/v2/images?' + f'limit={DEFAULT_PAGE_SIZE}', json=image_list_fixture) gc = client.Client(2.2, "http://example.com/v2.1") images = gc.images.list() diff --git a/glanceclient/tests/unit/v2/test_metadefs_namespaces.py b/glanceclient/tests/unit/v2/test_metadefs_namespaces.py index 35d71985e..6ef74fb08 100644 --- a/glanceclient/tests/unit/v2/test_metadefs_namespaces.py +++ b/glanceclient/tests/unit/v2/test_metadefs_namespaces.py @@ -19,6 +19,7 @@ from glanceclient.tests import utils from glanceclient.v2 import metadefs +DEFAULT_PAGE_SIZE = 20 NAMESPACE1 = 'Namespace1' NAMESPACE2 = 'Namespace2' NAMESPACE3 = 'Namespace3' @@ -60,7 +61,7 @@ def _get_namespace_fixture(ns_name, rt_name=RESOURCE_TYPE1, **kwargs): data_fixtures = { - "/v2/metadefs/namespaces?limit=20": { + f"/v2/metadefs/namespaces?limit={DEFAULT_PAGE_SIZE}": { "GET": ( {}, { @@ -112,7 +113,7 @@ def _get_namespace_fixture(ns_name, rt_name=RESOURCE_TYPE1, **kwargs): } ) }, - "/v2/metadefs/namespaces?limit=20&sort_dir=asc": { + f"/v2/metadefs/namespaces?limit={DEFAULT_PAGE_SIZE}&sort_dir=asc": { "GET": ( {}, { @@ -124,7 +125,7 @@ def _get_namespace_fixture(ns_name, rt_name=RESOURCE_TYPE1, **kwargs): } ) }, - "/v2/metadefs/namespaces?limit=20&sort_key=created_at": { + f"/v2/metadefs/namespaces?limit={DEFAULT_PAGE_SIZE}&sort_key=created_at": { "GET": ( {}, { @@ -136,7 +137,8 @@ def _get_namespace_fixture(ns_name, rt_name=RESOURCE_TYPE1, **kwargs): } ) }, - "/v2/metadefs/namespaces?limit=20&resource_types=%s" % RESOURCE_TYPE1: { + "/v2/metadefs/namespaces?limit=%d&resource_types=%s" % ( + DEFAULT_PAGE_SIZE, RESOURCE_TYPE1): { "GET": ( {}, { @@ -148,8 +150,8 @@ def _get_namespace_fixture(ns_name, rt_name=RESOURCE_TYPE1, **kwargs): } ) }, - "/v2/metadefs/namespaces?limit=20&resource_types=" - "%s%%2C%s" % (RESOURCE_TYPE1, RESOURCE_TYPE2): { + "/v2/metadefs/namespaces?limit=%d&resource_types=" + "%s%%2C%s" % (DEFAULT_PAGE_SIZE, RESOURCE_TYPE1, RESOURCE_TYPE2): { "GET": ( {}, { @@ -161,7 +163,7 @@ def _get_namespace_fixture(ns_name, rt_name=RESOURCE_TYPE1, **kwargs): } ) }, - "/v2/metadefs/namespaces?limit=20&visibility=private": { + f"/v2/metadefs/namespaces?limit={DEFAULT_PAGE_SIZE}&visibility=private": { "GET": ( {}, { From 88e3b0ad984797ec1f856429698949e4781dff3a Mon Sep 17 00:00:00 2001 From: Cyril Roelandt Date: Wed, 21 Sep 2022 19:38:24 +0200 Subject: [PATCH 115/167] Boolean options: use strict checking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Boolean options (such as "--protected" for glance md-namespace-update) should accept a limited amount of valid values, rather than assuming an "invalid" value means "False". The following values (no matter the case) will now be interpreted as True: ‘t’,’true’, ‘on’, ‘y’, ‘yes’, or ‘1’. The following values (no matter the case) will now be interpreted as False: ‘f’, ‘false’, ‘off’, ‘n’, ‘no’, or ‘0’. Change-Id: I0e7942045d883ac398bab4a7a85f2b4ac9b1ed8c Closes-Bug: #1607317 --- doc/source/cli/property-keys.rst | 5 +++++ glanceclient/common/utils.py | 2 +- glanceclient/tests/unit/test_utils.py | 8 ++++++++ ...-properties-strict-checking-bdd624b5da81e723.yaml | 12 ++++++++++++ 4 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/boolean-properties-strict-checking-bdd624b5da81e723.yaml diff --git a/doc/source/cli/property-keys.rst b/doc/source/cli/property-keys.rst index 9e59124e4..5c3cee452 100644 --- a/doc/source/cli/property-keys.rst +++ b/doc/source/cli/property-keys.rst @@ -27,3 +27,8 @@ in the Glance Administration Guide. For more information, refer to `Manage images `_ in the Glance Administration Guide. + +.. note:: + + Boolean properties expect one of the following values: '0', '1', 'f', + 'false', 'n', 'no', 'off', 'on', 't', 'true', 'y', 'yes' (case-insensitive). diff --git a/glanceclient/common/utils.py b/glanceclient/common/utils.py index c3f08de86..0de575f5e 100644 --- a/glanceclient/common/utils.py +++ b/glanceclient/common/utils.py @@ -103,7 +103,7 @@ def schema_args(schema_getter, omit=None): typemap = { 'string': encodeutils.safe_decode, 'integer': int, - 'boolean': strutils.bool_from_string, + 'boolean': lambda x: strutils.bool_from_string(x, strict=True), 'array': list } diff --git a/glanceclient/tests/unit/test_utils.py b/glanceclient/tests/unit/test_utils.py index 46cefbf6a..d10c70de4 100644 --- a/glanceclient/tests/unit/test_utils.py +++ b/glanceclient/tests/unit/test_utils.py @@ -229,6 +229,14 @@ def dummy_func(): self.assertEqual(encodeutils.safe_decode, opts['type']) self.assertIn('None, opt-1, opt-2', opts['help']) + # Make sure we use strict checking for boolean values. + decorated = utils.schema_args(schema_getter('boolean'))(dummy_func) + arg, opts = decorated.__dict__['arguments'][0] + type_function = opts['type'] + self.assertEqual(type_function('False'), False) + self.assertEqual(type_function('True'), True) + self.assertRaises(ValueError, type_function, 'foo') + def test_iterable_closes(self): # Regression test for bug 1461678. def _iterate(i): diff --git a/releasenotes/notes/boolean-properties-strict-checking-bdd624b5da81e723.yaml b/releasenotes/notes/boolean-properties-strict-checking-bdd624b5da81e723.yaml new file mode 100644 index 000000000..bd0836d5f --- /dev/null +++ b/releasenotes/notes/boolean-properties-strict-checking-bdd624b5da81e723.yaml @@ -0,0 +1,12 @@ +--- +prelude: > +fixes: + - | + * Bug 1607317_: metadata def namespace update CLI is not working as expected for parameter "protected" + + .. _1607317: https://code.launchpad.net/bugs/1607317 +other: + - | + Boolean arguments now expect one of the following values: '0', '1', 'f', + 'false', 'n', 'no', 'off', 'on', 't', 'true', 'y', 'yes' + (case-insensitive). This will not change anything for most users. From 6c95122777c8449056115292b492ec3e1e0d6e50 Mon Sep 17 00:00:00 2001 From: Cyril Roelandt Date: Fri, 16 Dec 2022 04:56:02 +0100 Subject: [PATCH 116/167] Fix functional tests and docs generation First, fix test_help(). A commit[1], which first appeared in Python 3.10, changes the output of the help feature of argparse. Options used to be in a section named "Optional arguments:", and they are now in a section named "Options:". Second, tox 4 changes the behaviour of tox, and {toxinidir}/requirements.txt is no longer installed automagically in the docs virtual environment. This causes autodoc to fail on some imports. We explicitely add {toxinidir}/requirements.txt to the list of dependencies to fix this issue. These issues should be fixed in separate patches, but since they both block the CI, they depend on each other. [1] https://github.com/python/cpython/pull/23858 Change-Id: Ia7866390b31f469bdea95624325a13aaf45a496e Closes-Bug: #2002566 --- glanceclient/tests/functional/v1/test_readonly_glance.py | 9 ++++++++- glanceclient/tests/functional/v2/test_readonly_glance.py | 9 ++++++++- tox.ini | 4 +++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/glanceclient/tests/functional/v1/test_readonly_glance.py b/glanceclient/tests/functional/v1/test_readonly_glance.py index a7024aaf3..ffd8358da 100644 --- a/glanceclient/tests/functional/v1/test_readonly_glance.py +++ b/glanceclient/tests/functional/v1/test_readonly_glance.py @@ -51,7 +51,14 @@ def test_help(self): commands = [] cmds_start = lines.index('Positional arguments:') - cmds_end = lines.index('Optional arguments:') + try: + # Starting in Python 3.10, argparse displays options in the + # "Options:" section... + cmds_end = lines.index('Options:') + except ValueError: + # ... but before Python 3.10, options were displayed in the + # "Optional arguments:" section. + cmds_end = lines.index('Optional arguments:') command_pattern = re.compile(r'^ {4}([a-z0-9\-\_]+)') for line in lines[cmds_start:cmds_end]: match = command_pattern.match(line) diff --git a/glanceclient/tests/functional/v2/test_readonly_glance.py b/glanceclient/tests/functional/v2/test_readonly_glance.py index 4d7f92d75..7bc86d182 100644 --- a/glanceclient/tests/functional/v2/test_readonly_glance.py +++ b/glanceclient/tests/functional/v2/test_readonly_glance.py @@ -71,7 +71,14 @@ def test_help(self): commands = [] cmds_start = lines.index('Positional arguments:') - cmds_end = lines.index('Optional arguments:') + try: + # Starting in Python 3.10, argparse displays options in the + # "Options:" section... + cmds_end = lines.index('Options:') + except ValueError: + # ... but before Python 3.10, options were displayed in the + # "Optional arguments:" section. + cmds_end = lines.index('Optional arguments:') command_pattern = re.compile(r'^ {4}([a-z0-9\-\_]+)') for line in lines[cmds_start:cmds_end]: match = command_pattern.match(line) diff --git a/tox.ini b/tox.ini index 981ea8f2c..f51607039 100644 --- a/tox.ini +++ b/tox.ini @@ -54,7 +54,9 @@ commands = [testenv:docs] basepython = python3 -deps = -r{toxinidir}/doc/requirements.txt +deps = + -r{toxinidir}/requirements.txt + -r{toxinidir}/doc/requirements.txt commands = sphinx-build -W -b html doc/source doc/build/html sphinx-build -W -b man doc/source doc/build/man From 52fb6b29d84644c690c0b8566117c4bfb963b09b Mon Sep 17 00:00:00 2001 From: Pranali Deore Date: Thu, 16 Feb 2023 08:15:14 +0000 Subject: [PATCH 117/167] Release notes for 4.3.0 Change-Id: I346af48771466c3f9dbaf470f611910ff3dda271 --- releasenotes/notes/4.3.0_Release-1a7acbd472e16c72.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 releasenotes/notes/4.3.0_Release-1a7acbd472e16c72.yaml diff --git a/releasenotes/notes/4.3.0_Release-1a7acbd472e16c72.yaml b/releasenotes/notes/4.3.0_Release-1a7acbd472e16c72.yaml new file mode 100644 index 000000000..75c8ca3f0 --- /dev/null +++ b/releasenotes/notes/4.3.0_Release-1a7acbd472e16c72.yaml @@ -0,0 +1,9 @@ +--- +fixes: + - | + Bug 911805_: Scriptify the generation of man pages + - | + Bug 1561828_: unallowed and read-only parameters in namspace and resouce_type + + .. _911805: https://code.launchpad.net/bugs/911805 + .. _1561828: https://code.launchpad.net/bugs/1561828 From 23fe745077adcc2e4f368f1179e4f0617a2337aa Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Wed, 1 Mar 2023 13:19:13 +0000 Subject: [PATCH 118/167] Update master for stable/2023.1 Add file to the reno documentation build to show release notes for stable/2023.1. Use pbr instruction to increment the minor version number automatically so that master versions are higher than the versions on stable/2023.1. Sem-Ver: feature Change-Id: I4e1a0a7c870e51909aebdc16d02b0062554de49a --- releasenotes/source/2023.1.rst | 6 ++++++ releasenotes/source/index.rst | 1 + 2 files changed, 7 insertions(+) create mode 100644 releasenotes/source/2023.1.rst diff --git a/releasenotes/source/2023.1.rst b/releasenotes/source/2023.1.rst new file mode 100644 index 000000000..d1238479b --- /dev/null +++ b/releasenotes/source/2023.1.rst @@ -0,0 +1,6 @@ +=========================== +2023.1 Series Release Notes +=========================== + +.. release-notes:: + :branch: stable/2023.1 diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index 7f762fb2c..d0f7d5b87 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ glanceclient Release Notes :maxdepth: 1 unreleased + 2023.1 zed yoga xena From e2190c4feb7b237c55a5d7df49e0db340a9d342f Mon Sep 17 00:00:00 2001 From: Cyril Roelandt Date: Tue, 18 Apr 2023 02:40:54 +0200 Subject: [PATCH 119/167] do_image_import: fix argument retrieval MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The argparse module automatically replaces '-' characters with '_' characters when converting an option string to an attribute: «For optional argument actions, the value of dest is normally inferred from the option strings. ArgumentParser generates the value of dest by taking the first long option string and stripping away the initial -- string. If no long option strings were supplied, dest will be derived from the first short option string by stripping the initial - character. Any internal - characters will be converted to _ characters to make sure the string is a valid attribute name.»[1] This means that the value of the "--remote-region" option of the "image-import" command will be available as "args.remote_region"; "remote-region" would not be a valid attribute anyway. We make sure to retrieve the proper value for the following options: --remote-region, --remote-image-id and --remote-service-interface. [1] https://docs.python.org/3/library/argparse.html#dest Change-Id: I1d8c69acd5d61fdc426469cd87d1ace81871e60f Partial-Bug: #2012442 --- glanceclient/tests/unit/v2/test_shell_v2.py | 6 +++--- glanceclient/v2/shell.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/glanceclient/tests/unit/v2/test_shell_v2.py b/glanceclient/tests/unit/v2/test_shell_v2.py index 8acceaecb..b2a6415aa 100644 --- a/glanceclient/tests/unit/v2/test_shell_v2.py +++ b/glanceclient/tests/unit/v2/test_shell_v2.py @@ -2240,10 +2240,10 @@ def test_image_import_web_download(self): def test_image_import_glance_download(self): args = self._make_args( - {'id': 'IMG-01', 'uri': None, 'remote-region': 'REGION2', - 'remote-image-id': 'IMG-02', + {'id': 'IMG-01', 'uri': None, 'remote_region': 'REGION2', + 'remote_image_id': 'IMG-02', 'import_method': 'glance-download', - 'remote-service-interface': 'public'}) + 'remote_service_interface': 'public'}) with mock.patch.object(self.gc.images, 'image_import') as mock_import: with mock.patch.object(self.gc.images, 'get') as mocked_get: with mock.patch.object(self.gc.images, diff --git a/glanceclient/v2/shell.py b/glanceclient/v2/shell.py index 93a93776b..3b5b24892 100644 --- a/glanceclient/v2/shell.py +++ b/glanceclient/v2/shell.py @@ -778,9 +778,9 @@ def do_image_import(gc, args): all_stores = getattr(args, "os_all_stores", None) allow_failure = getattr(args, "os_allow_failure", True) uri = getattr(args, "uri", None) - remote_region = getattr(args, "remote-region", None) - remote_image_id = getattr(args, "remote-image-id", None) - remote_service_interface = getattr(args, "remote-service-interface", None) + remote_region = getattr(args, "remote_region", None) + remote_image_id = getattr(args, "remote_image_id", None) + remote_service_interface = getattr(args, "remote_service_interface", None) if not getattr(args, 'from_create', False): if (args.store and (stores or all_stores)) or (stores and all_stores): From 320ea4b7151ad0daaaa7b4305846db9130b738df Mon Sep 17 00:00:00 2001 From: Cyril Roelandt Date: Tue, 18 Apr 2023 18:01:23 +0200 Subject: [PATCH 120/167] Tox4: remove skipsdist This option causes the installation of the "glance" command to fail in the functional tests. We have removed it from glance and glance_store in the past to improve compatibility with tox>=4.0, so let us do the same in glanceclient. Change-Id: Ia5bf8d5f90e9cd98adc65c3fa80ce4c87eebad9c --- tox.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/tox.ini b/tox.ini index f51607039..d6d6916c2 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,6 @@ [tox] envlist = py39,pep8 minversion = 3.18.0 -skipsdist = True [testenv] usedevelop = True From 1ea41f042c2fe4d2184828ef5277d1c119bfbd48 Mon Sep 17 00:00:00 2001 From: Cyril Roelandt Date: Tue, 18 Apr 2023 03:25:39 +0200 Subject: [PATCH 121/167] do_image_import: always pass remote_* to gc.images.image_import In do_image_import, gc.images.image_import may be called from two different places, depending on the command used ("glance image-import" or "glance image-create-via-import"). Make sure we always pass the remote_* arguments to gc.images.import. Change-Id: I76c6ea00523d93bad900776866de6ba22bc516b4 Partial-Bug: #2012442 --- glanceclient/tests/unit/v2/test_shell_v2.py | 4 +++- glanceclient/v2/shell.py | 12 ++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/glanceclient/tests/unit/v2/test_shell_v2.py b/glanceclient/tests/unit/v2/test_shell_v2.py index b2a6415aa..0926a558c 100644 --- a/glanceclient/tests/unit/v2/test_shell_v2.py +++ b/glanceclient/tests/unit/v2/test_shell_v2.py @@ -2279,7 +2279,9 @@ def test_image_import_no_print_image(self, mocked_utils_print_image): test_shell.do_image_import(self.gc, args) mock_import.assert_called_once_with( 'IMG-02', 'glance-direct', None, stores=None, - all_stores=None, allow_failure=True, backend=None) + all_stores=None, allow_failure=True, + remote_region=None, remote_image_id=None, + remote_service_interface=None, backend=None) mocked_utils_print_image.assert_not_called() @mock.patch('glanceclient.common.utils.print_image') diff --git a/glanceclient/v2/shell.py b/glanceclient/v2/shell.py index 3b5b24892..a78902ada 100644 --- a/glanceclient/v2/shell.py +++ b/glanceclient/v2/shell.py @@ -801,10 +801,14 @@ def do_image_import(gc, args): if getattr(args, 'from_create', False): # this command is being called "internally" so we can skip # validation -- just do the import and get out of here - gc.images.image_import(args.id, args.import_method, args.uri, - backend=backend, - stores=stores, all_stores=all_stores, - allow_failure=allow_failure) + gc.images.image_import( + args.id, args.import_method, args.uri, + remote_region=remote_region, + remote_image_id=remote_image_id, + remote_service_interface=remote_service_interface, + backend=backend, + stores=stores, all_stores=all_stores, + allow_failure=allow_failure) return # do input validation From 7d78cc4b9d43f5abdf8c7fa05e37ab8c8122c325 Mon Sep 17 00:00:00 2001 From: Nobuto Murata Date: Fri, 19 May 2023 09:36:49 +0900 Subject: [PATCH 122/167] Bump the CHUNKSIZE to use CPU more efficiently The chunk size used for downloading images was 64KiB for some time. That is okay for relatively small images but the client side of CPU can be a bottleneck especially for large images. Bump the default chunk size from 64KiB to 1MiB so we can use the client side CPU more efficiently. [64KiB chunk size - current] INFO cinder.image.image_utils Image download 1907.35 MB at 68.61 MB/s -> ~ 549 Mbps [1MiB chunk size - patched] INFO cinder.image.image_utils Image download 1907.35 MB at 132.10 MB/s -> 1,057 Mbps Closes-Bug: #2020139 Change-Id: I8b6e19621fc989526b02319d88fcfde88a17eee0 --- glanceclient/common/http.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glanceclient/common/http.py b/glanceclient/common/http.py index e24ba3574..f8c48dc5c 100644 --- a/glanceclient/common/http.py +++ b/glanceclient/common/http.py @@ -40,7 +40,7 @@ LOG = logging.getLogger(__name__) USER_AGENT = 'python-glanceclient' -CHUNKSIZE = 1024 * 64 # 64kB +CHUNKSIZE = 1024 * 1024 # 1MiB REQ_ID_HEADER = 'X-OpenStack-Request-ID' TOKEN_HEADERS = ['X-Auth-Token', 'X-Service-Token'] From 7c2ab837aa0c5749ac84eb610ed13ac4fb6dfc12 Mon Sep 17 00:00:00 2001 From: Cyril Roelandt Date: Wed, 7 Jun 2023 17:19:28 +0200 Subject: [PATCH 123/167] Docs generation: mock the six module for autodoc Currently, tox-edocs fails with the following error message: Warning, treated as error: autodoc: failed to import module 'shell' from module 'glanceclient'; the following exception was raised: No module named 'six' We used to install six in the virtual environment through keystoneauth, but keystoneauth no longer depends on six. Let's mock six completely in the context of autodoc. Change-Id: Ibcf0b9698227a2c22fe991b7516ba5dceabfc607 --- doc/source/conf.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/source/conf.py b/doc/source/conf.py index c2a406622..185b74143 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -46,6 +46,9 @@ # text edit cycles. # execute "export SPHINX_DEBUG=1" in your terminal to disable +# TODO: remove this once no dependency uses six anymore +autodoc_mock_imports = ['six'] + # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] From 62e6fc8270a3e76e6b21cf2e6385f3c4bfe56cad Mon Sep 17 00:00:00 2001 From: Pranali Deore Date: Thu, 6 Jul 2023 09:31:39 +0000 Subject: [PATCH 124/167] Release notes for 4.4.0 Change-Id: I96283b493c60de69f4598fb4470fba5fb117d749 --- releasenotes/notes/4.4.0_Release-a3c89184f345e5a2.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 releasenotes/notes/4.4.0_Release-a3c89184f345e5a2.yaml diff --git a/releasenotes/notes/4.4.0_Release-a3c89184f345e5a2.yaml b/releasenotes/notes/4.4.0_Release-a3c89184f345e5a2.yaml new file mode 100644 index 000000000..aa006cdd1 --- /dev/null +++ b/releasenotes/notes/4.4.0_Release-a3c89184f345e5a2.yaml @@ -0,0 +1,9 @@ +--- +fixes: + - | + Bug 2012442_: import image with glance-download return 400 + - | + Bug 1934626_: glanceclient has no support to add type while creating md-property for namespace + + .. _2012442: https://code.launchpad.net/bugs/2012442 + .. _1934626: https://code.launchpad.net/bugs/1934626 From 5f2835fcf0b65946ea2ab75da2d51a61efe82921 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Fri, 15 Sep 2023 14:53:18 +0000 Subject: [PATCH 125/167] Update master for stable/2023.2 Add file to the reno documentation build to show release notes for stable/2023.2. Use pbr instruction to increment the minor version number automatically so that master versions are higher than the versions on stable/2023.2. Sem-Ver: feature Change-Id: I1d1f147333a10f0a526df83ad88b9cff56020cc3 --- releasenotes/source/2023.2.rst | 6 ++++++ releasenotes/source/index.rst | 1 + 2 files changed, 7 insertions(+) create mode 100644 releasenotes/source/2023.2.rst diff --git a/releasenotes/source/2023.2.rst b/releasenotes/source/2023.2.rst new file mode 100644 index 000000000..a4838d7d0 --- /dev/null +++ b/releasenotes/source/2023.2.rst @@ -0,0 +1,6 @@ +=========================== +2023.2 Series Release Notes +=========================== + +.. release-notes:: + :branch: stable/2023.2 diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index d0f7d5b87..d1f7db93f 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ glanceclient Release Notes :maxdepth: 1 unreleased + 2023.2 2023.1 zed yoga From 2952978676081d1abee8176a46518d08967927b8 Mon Sep 17 00:00:00 2001 From: Ghanshyam Mann Date: Mon, 8 Jan 2024 20:18:32 -0800 Subject: [PATCH 126/167] Update python classifier in setup.cfg As per the current release tested runtime, we test till python 3.11 so updating the same in python classifier in setup.cfg Change-Id: I83a7f6bb7a2069e221e6c8efee31886defcaa93a --- setup.cfg | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.cfg b/setup.cfg index 59a59f523..ad1bc5eef 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,6 +22,8 @@ classifier = Programming Language :: Python :: 3 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 [files] packages = From 597095ffca737747f0a6684bb8bb2e2eae408a0c Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Thu, 25 Jan 2024 23:51:30 +0900 Subject: [PATCH 127/167] Exclude tests directory from coverage calculation Change-Id: I4c9033033fc38a93044da6fb910302635b6c950a --- .coveragerc | 1 + 1 file changed, 1 insertion(+) diff --git a/.coveragerc b/.coveragerc index 457eb5043..492082981 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,6 +1,7 @@ [run] branch = True source = glanceclient +omit = glanceclient/tests/* [report] ignore_errors = True From 7ddca13fcb9d6d990ceb8a70f40676fe0111874f Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Sat, 27 Jan 2024 23:18:40 +0900 Subject: [PATCH 128/167] Bump hacking hackihg 3.0.x is too old. Change-Id: I9d0d19bc6ecf4cb934cce77ce25e89882202dbd6 --- glanceclient/common/utils.py | 4 ++-- glanceclient/tests/functional/v2/test_http_headers.py | 2 +- glanceclient/tests/unit/test_utils.py | 2 +- glanceclient/tests/unit/v2/test_images.py | 2 +- glanceclient/tests/unit/v2/test_schemas.py | 4 ++-- glanceclient/v2/images.py | 4 ++-- glanceclient/v2/shell.py | 2 ++ test-requirements.txt | 2 +- 8 files changed, 12 insertions(+), 10 deletions(-) diff --git a/glanceclient/common/utils.py b/glanceclient/common/utils.py index ff1ffd16c..cebc9dff4 100644 --- a/glanceclient/common/utils.py +++ b/glanceclient/common/utils.py @@ -172,8 +172,8 @@ def _decorator(func): return _decorator -def pretty_choice_list(l): - return ', '.join("'%s'" % i for i in l) +def pretty_choice_list(choices): + return ', '.join("'%s'" % choice for choice in choices) def has_version(client, version): diff --git a/glanceclient/tests/functional/v2/test_http_headers.py b/glanceclient/tests/functional/v2/test_http_headers.py index 159644426..d4c9d9cca 100644 --- a/glanceclient/tests/functional/v2/test_http_headers.py +++ b/glanceclient/tests/functional/v2/test_http_headers.py @@ -57,5 +57,5 @@ def test_encode_headers_python(self): image = glanceclient.glance.images.update(image.id, disk_format="qcow2") except Exception as e: - self.assertFalse("415 Unsupported Media Type" in e.details) + self.assertNotIn("415 Unsupported Media Type", e.details) time.sleep(5) diff --git a/glanceclient/tests/unit/test_utils.py b/glanceclient/tests/unit/test_utils.py index dfa7a44ab..805e75543 100644 --- a/glanceclient/tests/unit/test_utils.py +++ b/glanceclient/tests/unit/test_utils.py @@ -242,7 +242,7 @@ def test_iterable_closes(self): # Regression test for bug 1461678. def _iterate(i): for chunk in i: - raise(IOError) + raise IOError() data = io.StringIO('somestring') data.close = mock.Mock() diff --git a/glanceclient/tests/unit/v2/test_images.py b/glanceclient/tests/unit/v2/test_images.py index 725120316..3ce12c169 100644 --- a/glanceclient/tests/unit/v2/test_images.py +++ b/glanceclient/tests/unit/v2/test_images.py @@ -1468,7 +1468,7 @@ def test_update_location(self): headers = {'x-openstack-request-id': 'req-1234'} fixture_idx = '/v2/images/%s' % (image_id) orig_locations = data_fixtures[fixture_idx]['GET'][1]['locations'] - loc_map = dict([(l['url'], l) for l in orig_locations]) + loc_map = dict([(loc['url'], loc) for loc in orig_locations]) loc_map[new_loc['url']] = new_loc mod_patch = [{'path': '/locations', 'op': 'replace', 'value': list(loc_map.values())}] diff --git a/glanceclient/tests/unit/v2/test_schemas.py b/glanceclient/tests/unit/v2/test_schemas.py index c01d8bd5b..9e060d4f5 100644 --- a/glanceclient/tests/unit/v2/test_schemas.py +++ b/glanceclient/tests/unit/v2/test_schemas.py @@ -61,8 +61,8 @@ def compare_json_patches(a, b): """Return 0 if a and b describe the same JSON patch.""" - return(jsonpatch.JsonPatch.from_string(a) == - jsonpatch.JsonPatch.from_string(b)) + return (jsonpatch.JsonPatch.from_string(a) == + jsonpatch.JsonPatch.from_string(b)) class TestSchemaProperty(testtools.TestCase): diff --git a/glanceclient/v2/images.py b/glanceclient/v2/images.py index e19f86ef5..216837d03 100644 --- a/glanceclient/v2/images.py +++ b/glanceclient/v2/images.py @@ -514,7 +514,7 @@ def delete_locations(self, image_id, url_set): :returns: None """ image = self._get_image_with_locations_or_fail(image_id) - current_urls = [l['url'] for l in image.locations] + current_urls = [loc['url'] for loc in image.locations] missing_locs = url_set.difference(set(current_urls)) if missing_locs: @@ -541,7 +541,7 @@ def update_location(self, image_id, url, metadata): :returns: The updated image """ image = self._get_image_with_locations_or_fail(image_id) - url_map = dict([(l['url'], l) for l in image.locations]) + url_map = dict([(loc['url'], loc) for loc in image.locations]) if url not in url_map: raise exc.HTTPNotFound('Unknown URL: %s, the URL must be one of' ' existing locations of current image' % diff --git a/glanceclient/v2/shell.py b/glanceclient/v2/shell.py index 64c5cbd7f..db74b0b2f 100644 --- a/glanceclient/v2/shell.py +++ b/glanceclient/v2/shell.py @@ -526,6 +526,7 @@ def do_member_get(gc, args): member = gc.image_members.get(args.image_id, args.member_id) utils.print_dict(member) + @utils.arg('image_id', metavar='', help=_('Image from which to remove member.')) @utils.arg('member_id', metavar='', @@ -596,6 +597,7 @@ def do_import_info(gc, args): else: utils.print_dict(import_info) + @utils.arg('--detail', default=False, action='store_true', help='Shows details of stores. Admin only.') def do_stores_info(gc, args): diff --git a/test-requirements.txt b/test-requirements.txt index d635495b3..39b8dcd32 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,4 +1,4 @@ -hacking>=3.0.1,<3.1.0 # Apache-2.0 +hacking>=6.1.0,<6.2.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 os-client-config>=1.28.0 # Apache-2.0 stestr>=2.0.0 # Apache-2.0 From 3c5dd2381f4f577411ad1023ad1bd1a280626bb5 Mon Sep 17 00:00:00 2001 From: Pranali Deore Date: Wed, 31 Jan 2024 06:54:20 +0000 Subject: [PATCH 129/167] Remove incorrect validation for glance-download import method Since REMOTE SERVICE INTERFACE has default value to 'public', it doesn't make sense to have validation for the same. Removing this validation and added few missing tests for glance-download import method Closes-Bug: #2051761 Change-Id: I89adf23aac5db4f2c46379546def2f71d7c8e163 --- glanceclient/tests/unit/v2/test_shell_v2.py | 68 +++++++++++++++++++++ glanceclient/v2/shell.py | 3 - 2 files changed, 68 insertions(+), 3 deletions(-) diff --git a/glanceclient/tests/unit/v2/test_shell_v2.py b/glanceclient/tests/unit/v2/test_shell_v2.py index d6cef1226..bd37cf64e 100644 --- a/glanceclient/tests/unit/v2/test_shell_v2.py +++ b/glanceclient/tests/unit/v2/test_shell_v2.py @@ -2262,6 +2262,74 @@ def test_image_import_glance_download(self): all_stores=None, allow_failure=True, backend=None, stores=None) + @mock.patch('glanceclient.common.utils.exit') + def test_image_import_neg_no_glance_download_with_remote_region( + self, mock_utils_exit): + expected_msg = ("Import method should be 'glance-download' if " + "REMOTE REGION is provided.") + my_args = self.base_args.copy() + my_args['id'] = 'IMG-01' + my_args['remote_region'] = 'REGION2' + my_args['import_method'] = 'web-download' + my_args['uri'] = 'https://example.com/some/stuff' + args = self._make_args(my_args) + mock_utils_exit.side_effect = self._mock_utils_exit + with mock.patch.object(self.gc.images, + 'get_import_info') as mocked_info: + mocked_info.return_value = self.import_info_response + try: + test_shell.do_image_import(self.gc, args) + self.fail("utils.exit should have been called") + except SystemExit: + pass + mock_utils_exit.assert_called_once_with(expected_msg) + + @mock.patch('glanceclient.common.utils.exit') + def test_image_import_neg_no_glance_download_with_remote_id( + self, mock_utils_exit): + expected_msg = ("Import method should be 'glance-download' if " + "REMOTE IMAGE ID is provided.") + my_args = self.base_args.copy() + my_args['id'] = 'IMG-01' + my_args['remote_image_id'] = 'IMG-02' + my_args['import_method'] = 'web-download' + my_args['uri'] = 'https://example.com/some/stuff' + args = self._make_args(my_args) + mock_utils_exit.side_effect = self._mock_utils_exit + with mock.patch.object(self.gc.images, + 'get_import_info') as mocked_info: + mocked_info.return_value = self.import_info_response + try: + test_shell.do_image_import(self.gc, args) + self.fail("utils.exit should have been called") + except SystemExit: + pass + mock_utils_exit.assert_called_once_with(expected_msg) + + def test_image_import_glance_download_without_remote_service_interface( + self): + args = self._make_args( + {'id': 'IMG-01', 'uri': None, 'remote_region': 'REGION2', + 'remote_image_id': 'IMG-02', + 'import_method': 'glance-download'}) + with mock.patch.object(self.gc.images, 'image_import') as mock_import: + with mock.patch.object(self.gc.images, 'get') as mocked_get: + with mock.patch.object(self.gc.images, + 'get_import_info') as mocked_info: + mocked_get.return_value = {'status': 'queued', + 'container_format': 'bare', + 'disk_format': 'raw'} + mocked_info.return_value = self.import_info_response + mock_import.return_value = None + test_shell.do_image_import(self.gc, args) + mock_import.assert_called_once_with( + 'IMG-01', 'glance-download', + uri=None, remote_region='REGION2', + remote_image_id='IMG-02', + remote_service_interface=None, + all_stores=None, allow_failure=True, + backend=None, stores=None) + @mock.patch('glanceclient.common.utils.print_image') def test_image_import_no_print_image(self, mocked_utils_print_image): args = self._make_args( diff --git a/glanceclient/v2/shell.py b/glanceclient/v2/shell.py index 64c5cbd7f..1fce93575 100644 --- a/glanceclient/v2/shell.py +++ b/glanceclient/v2/shell.py @@ -838,9 +838,6 @@ def do_image_import(gc, args): if remote_image_id and args.import_method != 'glance-download': utils.exit("Import method should be 'glance-download' if " "REMOTE IMAGE ID is provided.") - if remote_service_interface and args.import_method != 'glance-download': - utils.exit("Import method should be 'glance-download' if " - "REMOTE SERVICE INTERFACE is provided.") if args.import_method == 'copy-image' and not (stores or all_stores): utils.exit("Provide either --stores or --all-stores for " From 42848e011820134d66686a922f2d78550782b7d7 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Fri, 9 Feb 2024 15:19:25 +0000 Subject: [PATCH 130/167] reno: Update master for yoga Unmaintained status The stable/yoga branch has been deleted, so reno can't find its release notes. Use the yoga-eom tag to indicate the end of the Glance project's maintenance of the Yoga series. This strategy was agreed upon at the 8 Feb Glance meeting: https://meetings.opendev.org/meetings/glance/2024/glance.2024-02-08-14.00.log.html#l-58 Change-Id: I604c960eb0b2614efc39d5b5508b15abc8f9d0f2 --- releasenotes/source/yoga.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/source/yoga.rst b/releasenotes/source/yoga.rst index 7cd5e908a..8f1932add 100644 --- a/releasenotes/source/yoga.rst +++ b/releasenotes/source/yoga.rst @@ -3,4 +3,4 @@ Yoga Series Release Notes ========================= .. release-notes:: - :branch: stable/yoga + :branch: yoga-eom From 7376aa67cc1bc21d55ef60124cbbf6e0f8861b30 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Thu, 7 Mar 2024 15:00:55 +0000 Subject: [PATCH 131/167] Update master for stable/2024.1 Add file to the reno documentation build to show release notes for stable/2024.1. Use pbr instruction to increment the minor version number automatically so that master versions are higher than the versions on stable/2024.1. Sem-Ver: feature Change-Id: I9856860976200d82384a39c16484167c1a774506 --- releasenotes/source/2024.1.rst | 6 ++++++ releasenotes/source/index.rst | 1 + 2 files changed, 7 insertions(+) create mode 100644 releasenotes/source/2024.1.rst diff --git a/releasenotes/source/2024.1.rst b/releasenotes/source/2024.1.rst new file mode 100644 index 000000000..4977a4f1a --- /dev/null +++ b/releasenotes/source/2024.1.rst @@ -0,0 +1,6 @@ +=========================== +2024.1 Series Release Notes +=========================== + +.. release-notes:: + :branch: stable/2024.1 diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index d1f7db93f..adff77cd5 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ glanceclient Release Notes :maxdepth: 1 unreleased + 2024.1 2023.2 2023.1 zed From fc467850390c55ec405f73c67859fda8b2d20aef Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Thu, 7 Mar 2024 15:49:10 +0000 Subject: [PATCH 132/167] reno: Update master for victoria Unmaintained status The stable/victoria branch has been deleted, so reno can't find its release notes. Use the victoria-eom tag to indicate the end of the Glance project's maintenance of the Victoria series. This strategy is consistent with the way we handled stable/yoga and was agreed upon at the 8 Feb Glance meeting: https://meetings.opendev.org/meetings/glance/2024/glance.2024-02-08-14.00.log.html#l-58 Change-Id: Ib1f22dc701d090e8540aefd995903af143a34594 --- releasenotes/source/victoria.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/source/victoria.rst b/releasenotes/source/victoria.rst index 4efc7b6f3..6d20e1553 100644 --- a/releasenotes/source/victoria.rst +++ b/releasenotes/source/victoria.rst @@ -3,4 +3,4 @@ Victoria Series Release Notes ============================= .. release-notes:: - :branch: stable/victoria + :branch: victoria-eom From 4ed3b7b5fbff210d92d3a58c39dd0455a6723712 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Thu, 7 Mar 2024 15:49:44 +0000 Subject: [PATCH 133/167] reno: Update master for wallaby Unmaintained status The stable/wallaby branch has been deleted, so reno can't find its release notes. Use the wallaby-eom tag to indicate the end of the Glance project's maintenance of the Wallaby series. This strategy is consistent with the way we handled stable/yoga and was agreed upon at the 8 Feb Glance meeting: https://meetings.opendev.org/meetings/glance/2024/glance.2024-02-08-14.00.log.html#l-58 Change-Id: I1869021ca2117bd74f13e49c0f76967f3ef42c65 --- releasenotes/source/wallaby.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/source/wallaby.rst b/releasenotes/source/wallaby.rst index d77b56599..018303da0 100644 --- a/releasenotes/source/wallaby.rst +++ b/releasenotes/source/wallaby.rst @@ -3,4 +3,4 @@ Wallaby Series Release Notes ============================ .. release-notes:: - :branch: stable/wallaby + :branch: wallaby-eom From 0e798609d822fb8cb93bce888251aaa236f68d43 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Thu, 7 Mar 2024 15:50:26 +0000 Subject: [PATCH 134/167] reno: Update master for xena Unmaintained status The stable/xena branch has been deleted, so reno can't find its release notes. Use the xena-eom tag to indicate the end of the Glance project's maintenance of the Xena series. This strategy is consistent with the way we handled stable/yoga and was agreed upon at the 8 Feb Glance meeting: https://meetings.opendev.org/meetings/glance/2024/glance.2024-02-08-14.00.log.html#l-58 Change-Id: I3473a36a473767531c19463611d3eddb1a676998 --- releasenotes/source/xena.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/source/xena.rst b/releasenotes/source/xena.rst index 1be85be3e..62e0f6c7d 100644 --- a/releasenotes/source/xena.rst +++ b/releasenotes/source/xena.rst @@ -3,4 +3,4 @@ Xena Series Release Notes ========================= .. release-notes:: - :branch: stable/xena + :branch: xena-eom From 98256e88c08c0fa818df1d59e8eb06f476e97eee Mon Sep 17 00:00:00 2001 From: Cyril Roelandt Date: Wed, 3 Apr 2024 21:54:52 +0200 Subject: [PATCH 135/167] Tests: Fix original_only wrapper The wrapper function was not actually returned from the decorator function. Change-Id: I046fc26b89fccaeb438132b8cf7b86c1fd0aebbb --- glanceclient/tests/unit/test_http.py | 1 + 1 file changed, 1 insertion(+) diff --git a/glanceclient/tests/unit/test_http.py b/glanceclient/tests/unit/test_http.py index fef3b6a47..31d21f9dd 100644 --- a/glanceclient/tests/unit/test_http.py +++ b/glanceclient/tests/unit/test_http.py @@ -43,6 +43,7 @@ def wrapper(self, *args, **kwargs): self.skipTest('Skip logging tests for session client') return f(self, *args, **kwargs) + return wrapper class TestClient(testtools.TestCase): From 300b8c8262d8dcdca7a8d753c3fb8a22cae02d40 Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Sun, 28 Apr 2024 22:19:24 +0900 Subject: [PATCH 136/167] Fix unit tests broken by requests-mock >= 1.12.0 requests-mock removed the old compatibility code in 1.12.0[1] and now usage of a bare dict for response header causes AttributeError. [1] https://github.com/jamielennox/requests-mock/commit/e3bd0d1a92e40fa9cb1d587c59157020a45de620 Closes-Bug: #2064011 Change-Id: Iee86fa3eae86102112987b8648ada73d37ac58e8 --- glanceclient/tests/utils.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/glanceclient/tests/utils.py b/glanceclient/tests/utils.py index 730b92843..750fb7e72 100644 --- a/glanceclient/tests/utils.py +++ b/glanceclient/tests/utils.py @@ -14,6 +14,7 @@ # under the License. import copy +from email.message import EmailMessage import io import json import testtools @@ -112,7 +113,12 @@ def __init__(self, headers=None, body=None, self.body = body self.reason = reason self.version = version - self.headers = headers + # NOTE(tkajinam): Make the FakeResponse class compatible with + # http.client.HTTPResponse + # https://docs.python.org/3/library/http.client.html + self.headers = EmailMessage() + for header_key in headers: + self.headers[header_key] = headers[header_key] self.headers['x-openstack-request-id'] = 'req-1234' self.status_code = status_code self.raw = RawRequest(headers, body=body, reason=reason, From 61e415004efa034b24c0d2e2633aaae57dd8ece3 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Mon, 29 Apr 2024 18:45:54 +0000 Subject: [PATCH 137/167] reno: Update master for unmaintained/zed Update the zed release notes configuration to build from unmaintained/zed. Change-Id: Ic14bc289a7a171afcc7b95c5c993b9d98b805c39 --- releasenotes/source/zed.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/source/zed.rst b/releasenotes/source/zed.rst index 9608c05e4..6cc2b1554 100644 --- a/releasenotes/source/zed.rst +++ b/releasenotes/source/zed.rst @@ -3,4 +3,4 @@ Zed Series Release Notes ======================== .. release-notes:: - :branch: stable/zed + :branch: unmaintained/zed From fe8a8cbce6b73112ccdf13c335a8ad5424379cd4 Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Sun, 28 Apr 2024 17:33:26 +0900 Subject: [PATCH 138/167] Remove unused fallback to simplejson The built-in json module is available in recent Python 3 versions so this fallback is not at all used. Change-Id: I7b24fd1e107b6bed7322faecdd04ff9d3336d761 --- glanceclient/common/http.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/glanceclient/common/http.py b/glanceclient/common/http.py index f8c48dc5c..661264fdd 100644 --- a/glanceclient/common/http.py +++ b/glanceclient/common/http.py @@ -15,6 +15,7 @@ import copy import io +import json import logging import socket @@ -26,11 +27,6 @@ import requests import urllib.parse -try: - import json -except ImportError: - import simplejson as json - from oslo_utils import encodeutils from glanceclient.common import utils From 28497adc33eadc53da9013ca9b805ead07619732 Mon Sep 17 00:00:00 2001 From: Cyril Roelandt Date: Wed, 27 Mar 2024 19:37:25 +0100 Subject: [PATCH 139/167] Do not leak X-Auth-Token when logging curl requests We pass *encoded* headers to log_curl_request, but then compare them to *unencoded* sensitive headers that must be redacted (basically comparing bytes to strings). This means no header is ever redacted. Store sensitive headers as bytes rather than strings to fix this issue. Change-Id: I06785704750e8c4b23d1276514949655e6dcb7ab Closes-Bug: #2051712 --- glanceclient/common/utils.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/glanceclient/common/utils.py b/glanceclient/common/utils.py index cebc9dff4..8d2b8006d 100644 --- a/glanceclient/common/utils.py +++ b/glanceclient/common/utils.py @@ -42,7 +42,10 @@ _memoized_property_lock = threading.Lock() -SENSITIVE_HEADERS = ('X-Auth-Token', ) +# NOTE(cyril): Sensitive headers must be bytes, not strings, because when we +# compare them to actual headers in safe_header, headers have already been +# encoded. +SENSITIVE_HEADERS = (b'X-Auth-Token', ) REQUIRED_FIELDS_ON_DATA = ('disk_format', 'container_format') From 59331d56eb9f7d4bb19f05c251e17161a3e8c98d Mon Sep 17 00:00:00 2001 From: Pranali Deore Date: Tue, 14 May 2024 06:11:39 +0000 Subject: [PATCH 140/167] Release notes for 4.6.0 Change-Id: I7c4758c78d2520346a0a89e4d4e0fb4895c79946 --- .../notes/4.6.0_releasenotes-99ed8ea49481ee01.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 releasenotes/notes/4.6.0_releasenotes-99ed8ea49481ee01.yaml diff --git a/releasenotes/notes/4.6.0_releasenotes-99ed8ea49481ee01.yaml b/releasenotes/notes/4.6.0_releasenotes-99ed8ea49481ee01.yaml new file mode 100644 index 000000000..279952728 --- /dev/null +++ b/releasenotes/notes/4.6.0_releasenotes-99ed8ea49481ee01.yaml @@ -0,0 +1,9 @@ +--- +fixes: + - | + Bug 2051712_: glanceclient leaks X-Auth-Token into debug log + - | + Bug 2064011_: Some unit test cases are broken with requests-mock >= 1.12.0 + + .. _2051712: https://code.launchpad.net/bugs/2051712 + .. _2064011: https://code.launchpad.net/bugs/2064011 From fa495fe34c0dc65ed235c6585b4109ebefc98ad8 Mon Sep 17 00:00:00 2001 From: Cyril Roelandt Date: Wed, 26 Jun 2024 20:54:37 +0200 Subject: [PATCH 141/167] Python3.12: do not use ssl.wrap_socket The ssl.wrap_socket method has been removed in 3.12. SSLContext.wrap_socket should now be used. Change-Id: Ided77a9c9b8e409105de18abf195405927a3684f --- glanceclient/tests/unit/test_ssl.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/glanceclient/tests/unit/test_ssl.py b/glanceclient/tests/unit/test_ssl.py index f95e77773..8641f3dae 100644 --- a/glanceclient/tests/unit/test_ssl.py +++ b/glanceclient/tests/unit/test_ssl.py @@ -45,12 +45,11 @@ def get_request(self): cert_file = os.path.join(TEST_VAR_DIR, 'certificate.crt') cacert = os.path.join(TEST_VAR_DIR, 'ca.crt') (_sock, addr) = socketserver.TCPServer.get_request(self) - sock = ssl.wrap_socket(_sock, - certfile=cert_file, - keyfile=key_file, - ca_certs=cacert, - server_side=True, - cert_reqs=ssl.CERT_REQUIRED) + context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + context.verify_mode = ssl.CERT_REQUIRED + context.load_verify_locations(cacert) + context.load_cert_chain(cert_file, key_file) + sock = context.wrap_socket(_sock, server_side=True) return sock, addr From 4439db2ff023bdd65b4e965c7a5670b4a3da2923 Mon Sep 17 00:00:00 2001 From: Pranali Deore Date: Mon, 7 Aug 2023 07:38:31 +0000 Subject: [PATCH 142/167] Add support for new location APIs Related blueprint new-location-apis Change-Id: Id378cd5b7d20775b5117ee3f509bd6bdd61702e3 --- glanceclient/tests/unit/v2/base.py | 9 +++ glanceclient/tests/unit/v2/test_images.py | 69 +++++++++++++++++++ glanceclient/tests/unit/v2/test_shell_v2.py | 62 +++++++++++++++++ glanceclient/v2/images.py | 30 ++++++++ glanceclient/v2/shell.py | 37 ++++++++++ ...cations_apis_support-1ceb47178d384d58.yaml | 23 +++++++ 6 files changed, 230 insertions(+) create mode 100644 releasenotes/notes/add_new_locations_apis_support-1ceb47178d384d58.yaml diff --git a/glanceclient/tests/unit/v2/base.py b/glanceclient/tests/unit/v2/base.py index 043acb12b..c595f41c7 100644 --- a/glanceclient/tests/unit/v2/base.py +++ b/glanceclient/tests/unit/v2/base.py @@ -117,6 +117,15 @@ def image_import(self, *args, **kwargs): resp = self.controller.image_import(*args, **kwargs) self._assertRequestId(resp) + def add_image_location(self, *args): + resp = self.controller.add_image_location(*args) + self._assertRequestId(resp) + + def get_image_locations(self, *args): + resource = self.controller.get_image_locations(*args) + self._assertRequestId(resource) + return resource + class BaseResourceTypeController(BaseController): def __init__(self, api, schema_api, controller_class): diff --git a/glanceclient/tests/unit/v2/test_images.py b/glanceclient/tests/unit/v2/test_images.py index 3ce12c169..8a82774b8 100644 --- a/glanceclient/tests/unit/v2/test_images.py +++ b/glanceclient/tests/unit/v2/test_images.py @@ -688,6 +688,18 @@ ]}, ), }, + '/v2/images/3a4560a1-e585-443e-9b39-553b46ec92d1/locations': { + 'POST': ({}, '') + }, + '/v2/images/a2b83adc-888e-11e3-8872-78acc0b951d8/locations': { + 'GET': ( + {}, + [{'url': 'http://foo.com/', + 'metadata': {'store': 'cheap'}}, + {'url': 'http://bar.com/', + 'metadata': {'store': 'fast'}}], '' + ), + }, } schema_fixtures = { @@ -1503,3 +1515,60 @@ def test_update_missing_location(self): self.controller.update_location, image_id, **new_loc) self.assertIn(err_str, str(err)) + + def test_add_image_location(self): + location_url = 'http://spam.com/' + image_id = '3a4560a1-e585-443e-9b39-553b46ec92d1' + expect = [ + ('POST', + '/v2/images/%s/locations' % (image_id), + {}, + [('url', location_url), ('validation_data', {})]), + ('GET', '/v2/images/%s' % (image_id), {}, None)] + with mock.patch.object(common_utils, + 'has_version') as mock_has_version: + mock_has_version.return_value = True + self.controller.add_image_location(image_id, location_url, {}) + self.assertEqual(expect, self.api.calls) + + def test_add_image_location_with_validation_data(self): + location_url = 'http://spam.com/' + image_id = '3a4560a1-e585-443e-9b39-553b46ec92d1' + expect = [ + ('POST', + '/v2/images/%s/locations' % (image_id), + {}, + [('url', location_url), ('validation_data', + {'os_hash_algo': 'sha512'})]), + ('GET', '/v2/images/%s' % (image_id), {}, None)] + with mock.patch.object(common_utils, + 'has_version') as mock_has_version: + mock_has_version.return_value = True + self.controller.add_image_location(image_id, location_url, + {'os_hash_algo': 'sha512'}) + self.assertEqual(expect, self.api.calls) + + def test_add_image_location_not_supported(self): + with mock.patch.object(common_utils, + 'has_version') as mock_has_version: + mock_has_version.return_value = False + self.assertRaises(exc.HTTPNotImplemented, + self.controller.add_image_location, + '3a4560a1-e585-443e-9b39-553b46ec92d1', + 'http://spam.com/') + + def test_get_image_locations(self): + image_id = 'a2b83adc-888e-11e3-8872-78acc0b951d8' + with mock.patch.object(common_utils, + 'has_version') as mock_has_version: + mock_has_version.return_value = True + locations = self.controller.get_image_locations(image_id) + self.assertEqual(2, len(locations)) + + def test_get_image_location_not_supported(self): + with mock.patch.object(common_utils, + 'has_version') as mock_has_version: + mock_has_version.return_value = False + self.assertRaises(exc.HTTPNotImplemented, + self.controller.get_image_locations, + '3a4560a1-e585-443e-9b39-553b46ec92d1') diff --git a/glanceclient/tests/unit/v2/test_shell_v2.py b/glanceclient/tests/unit/v2/test_shell_v2.py index bd37cf64e..e5a33b009 100644 --- a/glanceclient/tests/unit/v2/test_shell_v2.py +++ b/glanceclient/tests/unit/v2/test_shell_v2.py @@ -1989,6 +1989,68 @@ def test_do_location_update(self): loc['metadata']) utils.print_dict.assert_called_once_with(expect_image) + def test_do_add_location(self): + gc = self.gc + url = 'http://foo.com/', + validation_data = {'os_hash_algo': 'sha512', + 'os_hash_value': 'value'} + args = {'id': 'IMG-01', + 'url': url, + 'validation_data': json.dumps(validation_data)} + with mock.patch.object(gc.images, + 'add_image_location') as mock_addloc: + expect_image = {'id': 'pass'} + mock_addloc.return_value = expect_image + + test_shell.do_add_location(self.gc, self._make_args(args)) + mock_addloc.assert_called_once_with( + 'IMG-01', url, validation_data=validation_data) + utils.print_dict.assert_called_once_with(expect_image) + + @mock.patch('glanceclient.common.utils.exit') + def test_do_add_location_with_checksum_in_validation_data(self, + mock_exit): + validation_data = {'checksum': 'value', + 'os_hash_algo': 'sha512', + 'os_hash_value': 'value'} + + args = self._make_args( + {'id': 'IMG-01', 'url': 'http://foo.com/', + 'validation_data': json.dumps(validation_data)}) + expected_msg = ('Validation Data should contain only os_hash_algo' + ' and os_hash_value. `checksum` is not allowed') + mock_exit.side_effect = self._mock_utils_exit + try: + test_shell.do_add_location(self.gc, args) + self.fail("utils.exit should have been called") + except SystemExit: + pass + + mock_exit.assert_called_once_with(expected_msg) + + @mock.patch('glanceclient.common.utils.exit') + def test_do_add_location_with_invalid_algo_in_validation_data(self, + mock_exit): + validation_data = {'os_hash_algo': 'algo', + 'os_hash_value': 'value'} + + args = self._make_args( + {'id': 'IMG-01', 'url': 'http://foo.com/', + 'validation_data': json.dumps(validation_data)}) + allowed_hash_algo = ['sha512', 'sha256', 'sha1', 'md5'] + expected_msg = ('os_hash_algo: `%s` is incorrect, ' + 'allowed hashing algorithms: %s' % + (validation_data['os_hash_algo'], + allowed_hash_algo)) + mock_exit.side_effect = self._mock_utils_exit + try: + test_shell.do_add_location(self.gc, args) + self.fail("utils.exit should have been called") + except SystemExit: + pass + + mock_exit.assert_called_once_with(expected_msg) + def test_image_upload(self): args = self._make_args( {'id': 'IMG-01', 'file': 'test', 'size': 1024, 'progress': False}) diff --git a/glanceclient/v2/images.py b/glanceclient/v2/images.py index 216837d03..94c3d12c9 100644 --- a/glanceclient/v2/images.py +++ b/glanceclient/v2/images.py @@ -560,3 +560,33 @@ def update_location(self, image_id, url, metadata): req_id_hdr = {'x-openstack-request-id': response.request_ids[0]} return self._get(image_id, req_id_hdr) + + def add_image_location(self, image_id, location_url, validation_data={}): + """Add a new location to an image. + + :param image_id: ID of image to which the location is to be added. + :param location_url: URL of the location to add. + :param validation_data: Validation data for the image. + """ + if not utils.has_version(self.http_client, 'v2.17'): + raise exc.HTTPNotImplemented( + 'This operation is not supported by Glance.') + + url = '/v2/images/%s/locations' % image_id + data = {'url': location_url, + 'validation_data': validation_data} + resp, body = self.http_client.post(url, data=data) + return self._get(image_id) + + @utils.add_req_id_to_object() + def get_image_locations(self, image_id): + """Fetch list of locations associated to the Image. + + :param image_id: ID of image to which the location is to be fetched. + """ + if not utils.has_version(self.http_client, 'v2.17'): + raise exc.HTTPNotImplemented( + 'This operation is not supported by Glance.') + url = '/v2/images/%s/locations' % (image_id) + resp, locations = self.http_client.get(url) + return locations, resp diff --git a/glanceclient/v2/shell.py b/glanceclient/v2/shell.py index 974e7904e..0b6478761 100644 --- a/glanceclient/v2/shell.py +++ b/glanceclient/v2/shell.py @@ -1012,6 +1012,43 @@ def do_location_update(gc, args): utils.print_dict(image) +@utils.arg('--url', metavar='', required=True, + help=_('URL of location to add.')) +@utils.arg('--validation-data', metavar='', default='{}', + help=_('Validation data containing os_hash_algo and os_hash_value ' + 'only associated to the image. Must be a valid JSON object ' + '(default: %(default)s)')) +@utils.arg('id', metavar='', + help=_('ID of image whose location is to be added.')) +def do_add_location(gc, args): + """Add location to an image which is in `queued` state only. """ + try: + invalid_val_data = None + validation_data = json.loads(args.validation_data) + accepted_values = ['os_hash_algo', 'os_hash_value'] + invalid_val_data = list(set(validation_data.keys()).difference( + accepted_values)) + if invalid_val_data: + utils.exit('Validation Data should contain only os_hash_algo ' + 'and os_hash_value. `%s` is not allowed' % + (*invalid_val_data,)) + + allowed_hash_algo = ['sha512', 'sha256', 'sha1', 'md5'] + if validation_data and \ + validation_data['os_hash_algo'] not in allowed_hash_algo: + raise utils.exit('os_hash_algo: `%s` is incorrect, ' + 'allowed hashing algorithms: %s' % + (validation_data['os_hash_algo'], + allowed_hash_algo)) + + except ValueError: + utils.exit('validation-data is not a valid JSON object.') + else: + image = gc.images.add_image_location(args.id, args.url, + validation_data=validation_data) + utils.print_image(image) + + # Metadata - catalog NAMESPACE_SCHEMA = None diff --git a/releasenotes/notes/add_new_locations_apis_support-1ceb47178d384d58.yaml b/releasenotes/notes/add_new_locations_apis_support-1ceb47178d384d58.yaml new file mode 100644 index 000000000..801aa0c92 --- /dev/null +++ b/releasenotes/notes/add_new_locations_apis_support-1ceb47178d384d58.yaml @@ -0,0 +1,23 @@ +--- +features: + - | + Add support for the new Glance ``Locations`` APIs + + - Add client support for newly added API, + ``POST /v2/images/{image_id}/locations`` in Glance. + New add location operation is allowed for service to service + interaction, end users only when `http` store is enabled in + deployment and images which are in ``queued`` state only. + This api replaces the image-update (old location-add) mechanism + for consumers like cinder and nova to address `OSSN-0090`_ and + `OSSN-0065`_. This client change adds support of new shell command + ``add-location`` and new client method ``add_image_location``. + - Add support for newly added API, + ``GET /v2/images/{image_id}/locations`` in Glance to fetch the + locations associated to an image. This change adds new client method + ``get_image_locations`` since this new get locations api is meant for + service user only hence it is not exposed to the end user as a shell + command. + + .. _OSSN-0090: https://wiki.openstack.org/wiki/OSSN/OSSN-0090 + .. _OSSN-0065: https://wiki.openstack.org/wiki/OSSN/OSSN-0065 From 95df2d15f048107f2686d024c9cfdde9062c19ab Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Fri, 6 Sep 2024 13:29:13 +0000 Subject: [PATCH 143/167] Update master for stable/2024.2 Add file to the reno documentation build to show release notes for stable/2024.2. Use pbr instruction to increment the minor version number automatically so that master versions are higher than the versions on stable/2024.2. Sem-Ver: feature Change-Id: Ie82f74527486521f77fb52eb948b9d369c32b8bc --- releasenotes/source/2024.2.rst | 6 ++++++ releasenotes/source/index.rst | 1 + 2 files changed, 7 insertions(+) create mode 100644 releasenotes/source/2024.2.rst diff --git a/releasenotes/source/2024.2.rst b/releasenotes/source/2024.2.rst new file mode 100644 index 000000000..aaebcbc8c --- /dev/null +++ b/releasenotes/source/2024.2.rst @@ -0,0 +1,6 @@ +=========================== +2024.2 Series Release Notes +=========================== + +.. release-notes:: + :branch: stable/2024.2 diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index adff77cd5..99c63de4c 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ glanceclient Release Notes :maxdepth: 1 unreleased + 2024.2 2024.1 2023.2 2023.1 From 96d3b0a3bcabfa0df1d3d319aa5eabaa9001ec2c Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Thu, 24 Oct 2024 18:11:44 +0900 Subject: [PATCH 144/167] Remove Python 3.8 support Python 3.8 was removed from the tested runtimes for 2024.2[1] and has not been tested since then. Also add Python 3.12 which is part of the tested runtimes for 2025.1. Now unit tests job with Python 3.12 is voting. [1] https://governance.openstack.org/tc/reference/runtimes/2024.2.html Change-Id: If4e0eabd5f04a04c1cef0d38688631dfad2ee727 --- releasenotes/notes/remove-py38-e14e0516991682eb.yaml | 5 +++++ setup.cfg | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/remove-py38-e14e0516991682eb.yaml diff --git a/releasenotes/notes/remove-py38-e14e0516991682eb.yaml b/releasenotes/notes/remove-py38-e14e0516991682eb.yaml new file mode 100644 index 000000000..040316360 --- /dev/null +++ b/releasenotes/notes/remove-py38-e14e0516991682eb.yaml @@ -0,0 +1,5 @@ +--- +upgrade: + - | + Support for Python 3.8 has been removed. Now the minimum python version + supported is 3.9 . diff --git a/setup.cfg b/setup.cfg index ad1bc5eef..65eb86e37 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,7 +7,7 @@ license = Apache License, Version 2.0 author = OpenStack author_email = openstack-discuss@lists.openstack.org home_page = https://docs.openstack.org/python-glanceclient/latest/ -python_requires = >=3.8 +python_requires = >=3.9 classifier = Development Status :: 5 - Production/Stable Environment :: Console @@ -20,10 +20,10 @@ classifier = Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3 - Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 + Programming Language :: Python :: 3.12 [files] packages = From be962800d5a9b55618169ff7fdf2a8c9ea30afa5 Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Mon, 25 Nov 2024 00:39:59 +0900 Subject: [PATCH 145/167] Remove ceilometer service overrides devstack no longer installs ceilometer services unlesss ceilometer devstack plugin is enabled, so we can safely remove the options to disable ceilometer services. This allows us to remove references to removed services such as ceilometer-api . Change-Id: I615f699336584f86f9a46584e78cb3b54d10048f --- .zuul.yaml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index 65ac4cc62..4721bfb07 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -12,14 +12,6 @@ devstack_localrc: LIBS_FROM_GIT: python-glanceclient devstack_services: - # turn off ceilometer - ceilometer-acentral: false - ceilometer-acompute: false - ceilometer-alarm-evaluator: false - ceilometer-alarm-notifier: false - ceilometer-anotification: false - ceilometer-api: false - ceilometer-collector: false # turn on swift s-account: true s-container: true From 34ae3eadb4d5800accf87f01a6e6cd1e7ff1dafe Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Mon, 25 Nov 2024 10:44:22 +0000 Subject: [PATCH 146/167] reno: Update master for unmaintained/2023.1 Update the 2023.1 release notes configuration to build from unmaintained/2023.1. Change-Id: Iaed48c6145436e643dedbbecadd76bc50455638b --- releasenotes/source/2023.1.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/source/2023.1.rst b/releasenotes/source/2023.1.rst index d1238479b..2c9a36fae 100644 --- a/releasenotes/source/2023.1.rst +++ b/releasenotes/source/2023.1.rst @@ -3,4 +3,4 @@ =========================== .. release-notes:: - :branch: stable/2023.1 + :branch: unmaintained/2023.1 From b78fdc33be735fe5714ab206bffa67931b7b3d35 Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Mon, 3 Feb 2025 20:47:55 +0900 Subject: [PATCH 147/167] Replace oslo_utils.encodeutils.exception_to_unicode The function is being deprecated now because it is equivalent to str(ex) in Python 3. Depends-on: https://review.opendev.org/c/openstack/oslo.utils/+/938929 Change-Id: I7c9ca3730e1ab6ffdaf8cb78c002ac4e157fe2f1 --- glanceclient/shell.py | 2 +- glanceclient/v2/images.py | 4 ++-- glanceclient/v2/metadefs.py | 18 +++++++++--------- glanceclient/v2/tasks.py | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/glanceclient/shell.py b/glanceclient/shell.py index 4a505a5d7..d5c40d865 100644 --- a/glanceclient/shell.py +++ b/glanceclient/shell.py @@ -660,4 +660,4 @@ def main(): except Exception as e: if utils.debug_enabled(argv) is True: traceback.print_exc() - utils.exit(encodeutils.exception_to_unicode(e)) + utils.exit(str(e)) diff --git a/glanceclient/v2/images.py b/glanceclient/v2/images.py index 94c3d12c9..5a566a77b 100644 --- a/glanceclient/v2/images.py +++ b/glanceclient/v2/images.py @@ -408,7 +408,7 @@ def create(self, **kwargs): try: setattr(image, key, value) except warlock.InvalidOperation as e: - raise TypeError(encodeutils.exception_to_unicode(e)) + raise TypeError(str(e)) resp, body = self.http_client.post(url, headers=headers, data=image) # NOTE(esheffield): remove 'self' for now until we have an elegant @@ -443,7 +443,7 @@ def update(self, image_id, remove_props=None, **kwargs): try: setattr(image, key, value) except warlock.InvalidOperation as e: - raise TypeError(encodeutils.exception_to_unicode(e)) + raise TypeError(str(e)) if remove_props: cur_props = image.keys() diff --git a/glanceclient/v2/metadefs.py b/glanceclient/v2/metadefs.py index a98a9fec5..fdc089a03 100644 --- a/glanceclient/v2/metadefs.py +++ b/glanceclient/v2/metadefs.py @@ -46,7 +46,7 @@ def create(self, **kwargs): try: namespace = self.model(kwargs) except (warlock.InvalidOperation, ValueError) as e: - raise TypeError(encodeutils.exception_to_unicode(e)) + raise TypeError(str(e)) resp, body = self.http_client.post(url, data=namespace) body.pop('self', None) @@ -63,7 +63,7 @@ def update(self, namespace_name, **kwargs): try: setattr(namespace, key, value) except warlock.InvalidOperation as e: - raise TypeError(encodeutils.exception_to_unicode(e)) + raise TypeError(str(e)) # Remove read-only parameters. read_only = ['schema', 'updated_at', 'created_at'] @@ -212,7 +212,7 @@ def associate(self, namespace, **kwargs): try: res_type = self.model(kwargs) except (warlock.InvalidOperation, ValueError) as e: - raise TypeError(encodeutils.exception_to_unicode(e)) + raise TypeError(str(e)) url = '/v2/metadefs/namespaces/%(namespace)s/resource_types' % { 'namespace': namespace} @@ -272,7 +272,7 @@ def create(self, namespace, **kwargs): try: prop = self.model(kwargs) except (warlock.InvalidOperation, ValueError) as e: - raise TypeError(encodeutils.exception_to_unicode(e)) + raise TypeError(str(e)) url = '/v2/metadefs/namespaces/%(namespace)s/properties' % { 'namespace': namespace} @@ -292,7 +292,7 @@ def update(self, namespace, prop_name, **kwargs): try: setattr(prop, key, value) except warlock.InvalidOperation as e: - raise TypeError(encodeutils.exception_to_unicode(e)) + raise TypeError(str(e)) url = ('/v2/metadefs/namespaces/%(namespace)s/' 'properties/%(prop_name)s') % { @@ -374,7 +374,7 @@ def create(self, namespace, **kwargs): try: obj = self.model(kwargs) except (warlock.InvalidOperation, ValueError) as e: - raise TypeError(encodeutils.exception_to_unicode(e)) + raise TypeError(str(e)) url = '/v2/metadefs/namespaces/%(namespace)s/objects' % { 'namespace': namespace} @@ -395,7 +395,7 @@ def update(self, namespace, object_name, **kwargs): try: setattr(obj, key, value) except warlock.InvalidOperation as e: - raise TypeError(encodeutils.exception_to_unicode(e)) + raise TypeError(str(e)) # Remove read-only parameters. read_only = ['schema', 'updated_at', 'created_at'] @@ -499,7 +499,7 @@ def create_multiple(self, namespace, **kwargs): try: md_tag_list.append(self.model(name=tag_name)) except (warlock.InvalidOperation) as e: - raise TypeError(encodeutils.exception_to_unicode(e)) + raise TypeError(str(e)) tags = {'tags': md_tag_list} headers = {} @@ -526,7 +526,7 @@ def update(self, namespace, tag_name, **kwargs): try: setattr(tag, key, value) except warlock.InvalidOperation as e: - raise TypeError(encodeutils.exception_to_unicode(e)) + raise TypeError(str(e)) # Remove read-only parameters. read_only = ['updated_at', 'created_at'] diff --git a/glanceclient/v2/tasks.py b/glanceclient/v2/tasks.py index 6649f4b48..41ab347eb 100644 --- a/glanceclient/v2/tasks.py +++ b/glanceclient/v2/tasks.py @@ -116,7 +116,7 @@ def create(self, **kwargs): try: setattr(task, key, value) except warlock.InvalidOperation as e: - raise TypeError(encodeutils.exception_to_unicode(e)) + raise TypeError(str(e)) resp, body = self.http_client.post(url, data=task) # NOTE(flwang): remove 'self' for now until we have an elegant From d6407a996ab0b51edf360259f483ce0e49b475ab Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Fri, 7 Mar 2025 14:53:05 +0000 Subject: [PATCH 148/167] Update master for stable/2025.1 Add file to the reno documentation build to show release notes for stable/2025.1. Use pbr instruction to increment the minor version number automatically so that master versions are higher than the versions on stable/2025.1. Sem-Ver: feature Change-Id: I16aca3ba0ed68b49b2773cd25cc5ad0fe43351aa --- releasenotes/source/2025.1.rst | 6 ++++++ releasenotes/source/index.rst | 1 + 2 files changed, 7 insertions(+) create mode 100644 releasenotes/source/2025.1.rst diff --git a/releasenotes/source/2025.1.rst b/releasenotes/source/2025.1.rst new file mode 100644 index 000000000..3add0e53a --- /dev/null +++ b/releasenotes/source/2025.1.rst @@ -0,0 +1,6 @@ +=========================== +2025.1 Series Release Notes +=========================== + +.. release-notes:: + :branch: stable/2025.1 diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index 99c63de4c..997e4ce91 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ glanceclient Release Notes :maxdepth: 1 unreleased + 2025.1 2024.2 2024.1 2023.2 From 55edc64b98d13f26de20aee63802fd1ab6f688e4 Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Mon, 9 Jun 2025 18:06:39 +0900 Subject: [PATCH 149/167] Remove Python 3.9 support Python 3.9 is no longer part of the tested runtimes[1]. [1] https://governance.openstack.org/tc/reference/runtimes/2025.2.html Change-Id: I6f04f6aac6439aa16ccd077201fdea4d95d34da3 --- releasenotes/notes/remove-py39-63731125072287a3.yaml | 5 +++++ setup.cfg | 3 +-- 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/remove-py39-63731125072287a3.yaml diff --git a/releasenotes/notes/remove-py39-63731125072287a3.yaml b/releasenotes/notes/remove-py39-63731125072287a3.yaml new file mode 100644 index 000000000..4b631b0b6 --- /dev/null +++ b/releasenotes/notes/remove-py39-63731125072287a3.yaml @@ -0,0 +1,5 @@ +--- +upgrade: + - | + Support for Pyton 3.9 has been removed. Now Python 3.10 is the minimum + version supported. diff --git a/setup.cfg b/setup.cfg index 65eb86e37..3fab8bbea 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,7 +7,7 @@ license = Apache License, Version 2.0 author = OpenStack author_email = openstack-discuss@lists.openstack.org home_page = https://docs.openstack.org/python-glanceclient/latest/ -python_requires = >=3.9 +python_requires = >=3.10 classifier = Development Status :: 5 - Production/Stable Environment :: Console @@ -20,7 +20,6 @@ classifier = Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3 - Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 From 4c181956c04039566782e7aab77022cc925483c1 Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Tue, 1 Jul 2025 00:57:55 +0900 Subject: [PATCH 150/167] Replace os-client-config It was deprecated[1] after the code was merged into openstacksdk[2]. [1] https://review.opendev.org/c/openstack/os-client-config/+/549307 [2] https://review.opendev.org/c/openstack/openstacksdk/+/518128 Change-Id: I57c3489c84a06e788735aa2244907b8ef4875b8c --- glanceclient/tests/functional/base.py | 10 ++++++---- test-requirements.txt | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/glanceclient/tests/functional/base.py b/glanceclient/tests/functional/base.py index 578dc3953..f890f50fc 100644 --- a/glanceclient/tests/functional/base.py +++ b/glanceclient/tests/functional/base.py @@ -10,13 +10,15 @@ # License for the specific language governing permissions and limitations # under the License. -import glanceclient +import os + from keystoneauth1 import loading from keystoneauth1 import session -import os -import os_client_config +from openstack import config as occ from tempest.lib.cli import base +import glanceclient + def credentials(cloud='devstack-admin'): """Retrieves credentials to run functional tests @@ -31,7 +33,7 @@ def credentials(cloud='devstack-admin'): cloud as that is the current expected behavior. """ - return os_client_config.OpenStackConfig().get_one_cloud(cloud=cloud) + return occ.OpenStackConfig().get_one(cloud=cloud) class ClientTestBase(base.ClientTestBase): diff --git a/test-requirements.txt b/test-requirements.txt index 39b8dcd32..f648a2a1b 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,6 +1,6 @@ hacking>=6.1.0,<6.2.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 -os-client-config>=1.28.0 # Apache-2.0 +openstacksdk>=0.10.0 # Apache-2.0 stestr>=2.0.0 # Apache-2.0 testtools>=2.2.0 # MIT testscenarios>=0.4 # Apache-2.0/BSD From 90d15f65a63bacb233eb1d9d42345bd6e6e9e6d1 Mon Sep 17 00:00:00 2001 From: Abhishek Kekane Date: Wed, 14 May 2025 17:06:22 +0000 Subject: [PATCH 151/167] Add support for passing image size to Glance API Introduced a new command-line option --size for the image-create and image-create-via-import commands. Added the `x-openstack-image-size` header to transmit the image size from image-upload and image-stage commands to the Glance API. If --size is not specified but --file is provided, the script calculates the file size and includes it in the `x-openstack-image-size` header when invoking image-upload and image-stage commands. Change-Id: I7572f8a5d42a9968b940ed71eecbe3028e92877e Signed-off-by: Abhishek Kekane --- glanceclient/tests/unit/v2/base.py | 4 + glanceclient/tests/unit/v2/test_images.py | 71 +++++- glanceclient/tests/unit/v2/test_shell_v2.py | 261 +++++++++++++++++++- glanceclient/v2/images.py | 10 +- glanceclient/v2/shell.py | 43 +++- 5 files changed, 376 insertions(+), 13 deletions(-) diff --git a/glanceclient/tests/unit/v2/base.py b/glanceclient/tests/unit/v2/base.py index c595f41c7..95f037b59 100644 --- a/glanceclient/tests/unit/v2/base.py +++ b/glanceclient/tests/unit/v2/base.py @@ -87,6 +87,10 @@ def upload(self, *args, **kwargs): resp = self.controller.upload(*args, **kwargs) self._assertRequestId(resp) + def stage(self, *args, **kwargs): + resp = self.controller.stage(*args, **kwargs) + self._assertRequestId(resp) + def data(self, *args, **kwargs): body = self.controller.data(*args, **kwargs) self._assertRequestId(body) diff --git a/glanceclient/tests/unit/v2/test_images.py b/glanceclient/tests/unit/v2/test_images.py index 8a82774b8..67dfdd64b 100644 --- a/glanceclient/tests/unit/v2/test_images.py +++ b/glanceclient/tests/unit/v2/test_images.py @@ -195,6 +195,12 @@ '', ), }, + '/v2/images/606b0e88-7c5a-4d54-b5bb-046105d4de6f/stage': { + 'PUT': ( + {}, + '', + ), + }, '/v2/images/5cc4bebc-db27-11e1-a1eb-080027cbe205/file': { 'GET': ( {}, @@ -1010,6 +1016,16 @@ def test_create_image(self): self.assertEqual('3a4560a1-e585-443e-9b39-553b46ec92d1', image.id) self.assertEqual('image-1', image.name) + def test_create_image_w_size(self): + properties = { + 'name': 'image-1', + 'size': '4' + } + image = self.controller.create(**properties) + self.assertEqual('3a4560a1-e585-443e-9b39-553b46ec92d1', image.id) + self.assertEqual('image-1', image.name) + self.assertIsNone(image.get('size')) + def test_create_bad_additionalProperty_type(self): properties = { 'name': 'image-1', @@ -1054,12 +1070,63 @@ def test_data_upload(self): image_data)] self.assertEqual(expect, self.api.calls) - def test_data_upload_w_size(self): + def test_data_upload_with_invalid_size(self): + image_data = 'CCC' + image_id = '606b0e88-7c5a-4d54-b5bb-046105d4de6f' + self.assertRaises(TypeError, self.controller.upload, image_id, + image_data, image_size='invalid_size') + expect = [] + self.assertEqual(expect, self.api.calls) + + def test_data_upload_w_size_same_as_data(self): image_data = 'CCC' image_id = '606b0e88-7c5a-4d54-b5bb-046105d4de6f' self.controller.upload(image_id, image_data, image_size=3) expect = [('PUT', '/v2/images/%s/file' % image_id, - {'Content-Type': 'application/octet-stream'}, + {'Content-Type': 'application/octet-stream', + 'x-openstack-image-size': str(len(image_data))}, + image_data)] + self.assertEqual(expect, self.api.calls) + + def test_data_upload_w_size_diff_than_data(self): + image_data = 'CCCCCC' + image_size = '3' + image_id = '606b0e88-7c5a-4d54-b5bb-046105d4de6f' + self.controller.upload(image_id, image_data, + image_size=int(image_size)) + expect = [('PUT', '/v2/images/%s/file' % image_id, + {'Content-Type': 'application/octet-stream', + 'x-openstack-image-size': image_size}, + image_data)] + self.assertEqual(expect, self.api.calls) + + def test_data_stage_with_invalid_size(self): + image_data = 'CCC' + image_id = '606b0e88-7c5a-4d54-b5bb-046105d4de6f' + self.assertRaises(TypeError, self.controller.stage, image_id, + image_data, image_size='invalid_size') + expect = [] + self.assertEqual(expect, self.api.calls) + + def test_data_stage_w_size_same_as_data(self): + image_data = 'CCC' + image_id = '606b0e88-7c5a-4d54-b5bb-046105d4de6f' + self.controller.stage(image_id, image_data, image_size=3) + expect = [('PUT', '/v2/images/%s/stage' % image_id, + {'Content-Type': 'application/octet-stream', + 'x-openstack-image-size': str(len(image_data))}, + image_data)] + self.assertEqual(expect, self.api.calls) + + def test_data_stage_w_size_diff_than_data(self): + image_data = 'CCCCCC' + image_size = '3' + image_id = '606b0e88-7c5a-4d54-b5bb-046105d4de6f' + self.controller.stage(image_id, image_data, + image_size=int(image_size)) + expect = [('PUT', '/v2/images/%s/stage' % image_id, + {'Content-Type': 'application/octet-stream', + 'x-openstack-image-size': image_size}, image_data)] self.assertEqual(expect, self.api.calls) diff --git a/glanceclient/tests/unit/v2/test_shell_v2.py b/glanceclient/tests/unit/v2/test_shell_v2.py index e5a33b009..bdfb13962 100644 --- a/glanceclient/tests/unit/v2/test_shell_v2.py +++ b/glanceclient/tests/unit/v2/test_shell_v2.py @@ -679,6 +679,117 @@ def test_usage(self): {'quota': 'quota2', 'limit': 20, 'usage': 5}], ['Quota', 'Limit', 'Usage']) + def test_do_image_stage_size_match(self): + args = mock.Mock() + args.id = 'IMG-01' + args.file = 'testfile' + args.size = 1024 + args.progress = False + + with mock.patch('glanceclient.common.utils.get_data_file', + return_value='fileobj'), \ + mock.patch('glanceclient.common.utils.get_file_size', + return_value=1024), \ + mock.patch('glanceclient.v2.shell._validate_backend'), \ + mock.patch.object(self.gc.images, + 'stage') as mock_stage: + test_shell.do_image_stage(self.gc, args) + mock_stage.assert_called_once_with('IMG-01', 'fileobj', + 1024) + + def test_do_image_stage_size_mismatch(self): + args = mock.Mock() + args.id = 'IMG-01' + args.file = 'testfile' + args.size = 1024 + args.progress = False + + with mock.patch('glanceclient.common.utils.get_data_file', + return_value='fileobj'), \ + mock.patch('glanceclient.common.utils.get_file_size', + return_value=2048), \ + mock.patch('glanceclient.v2.shell._validate_backend'): + with self.assertRaisesRegex(ValueError, + "Size mismatch: provided size 1024 " + "does not match the size of the " + "image 2048"): + test_shell.do_image_stage(self.gc, args) + + def test_do_image_stage_no_size_in_args(self): + args = mock.Mock() + args.id = 'IMG-01' + args.file = 'testfile' + args.size = None + args.progress = False + + with mock.patch('glanceclient.common.utils.get_data_file', + return_value='fileobj'), \ + mock.patch('glanceclient.common.utils.get_file_size', + return_value=1024), \ + mock.patch('glanceclient.v2.shell._validate_backend'), \ + mock.patch.object(self.gc.images, + 'stage') as mock_upload: + test_shell.do_image_stage(self.gc, args) + mock_upload.assert_called_once_with('IMG-01', 'fileobj', + 1024) + + def test_do_image_upload_size_match(self): + args = mock.Mock() + args.id = 'IMG-01' + args.file = 'testfile' + args.size = 1024 + args.store = None + args.progress = False + + with mock.patch('glanceclient.common.utils.get_data_file', + return_value='fileobj'), \ + mock.patch('glanceclient.common.utils.get_file_size', + return_value=1024), \ + mock.patch('glanceclient.v2.shell._validate_backend'), \ + mock.patch.object(self.gc.images, + 'upload') as mock_upload: + test_shell.do_image_upload(self.gc, args) + mock_upload.assert_called_once_with('IMG-01', 'fileobj', + 1024, backend=None) + + def test_do_image_upload_size_mismatch(self): + args = mock.Mock() + args.id = 'IMG-01' + args.file = 'testfile' + args.size = 1024 + args.store = None + args.progress = False + + with mock.patch('glanceclient.common.utils.get_data_file', + return_value='fileobj'), \ + mock.patch('glanceclient.common.utils.get_file_size', + return_value=2048), \ + mock.patch('glanceclient.v2.shell._validate_backend'): + with self.assertRaisesRegex(ValueError, + "Size mismatch: provided size 1024 " + "does not match the size of the " + "image 2048"): + test_shell.do_image_upload(self.gc, args) + + def test_do_image_upload_no_size_in_args(self): + args = mock.Mock() + args.id = 'IMG-01' + args.file = 'testfile' + args.size = None + args.store = None + args.progress = False + + with mock.patch('glanceclient.common.utils.get_data_file', + return_value='fileobj'), \ + mock.patch('glanceclient.common.utils.get_file_size', + return_value=1024), \ + mock.patch('glanceclient.v2.shell._validate_backend'), \ + mock.patch.object(self.gc.images, + 'upload') as mock_upload: + test_shell.do_image_upload(self.gc, args) + mock_upload.assert_called_once_with('IMG-01', 'fileobj', + 1024, backend=None) + @mock.patch('sys.stdin', autospec=True) def test_do_image_create_no_user_props(self, mock_stdin): args = self._make_args({'name': 'IMG-01', 'disk_format': 'vhd', @@ -781,6 +892,77 @@ def test_do_image_create_with_multihash(self): except Exception: pass + def _do_image_create(self, temp_args, expect_size=None): + self.mock_get_data_file.return_value = io.StringIO() + with open(tempfile.mktemp(), 'w+') as f: + f.write('Some data here') + f.flush() + f.seek(0) + file_name = f.name + self.addCleanup(lambda: os.remove(f.name) if os.path.exists( + f.name) else None) + temp_args = temp_args.copy() + temp_args['file'] = file_name + args = self._make_args(temp_args) + with mock.patch.object(self.gc.images, 'create') as mocked_create, \ + mock.patch.object(self.gc.images, 'get') as mocked_get, \ + mock.patch.object(utils, 'get_file_size') as mock_size: + expected_size = len('Some data here') + mock_size.return_value = expected_size + ignore_fields = ['self', 'access', 'schema'] + expect_image = dict( + [(field, field) for field in ignore_fields]) + expect_image['id'] = 'pass' + expect_image['name'] = 'IMG-01' + expect_image['disk_format'] = 'vhd' + expect_image['container_format'] = 'bare' + expect_image['checksum'] = 'fake-checksum' + expect_image['os_hash_algo'] = 'fake-hash_algo' + expect_image['os_hash_value'] = 'fake-hash_value' + if expect_size is not None: + expect_image['size'] = expect_size + mocked_create.return_value = expect_image + mocked_get.return_value = expect_image + + test_shell.do_image_create(self.gc, args) + + temp_args.pop('file', None) + mocked_create.assert_called_once_with(**temp_args) + mocked_get.assert_called_once_with('pass') + expected_dict = { + 'id': 'pass', 'name': 'IMG-01', + 'disk_format': 'vhd', + 'container_format': 'bare', + 'checksum': 'fake-checksum', + 'os_hash_algo': 'fake-hash_algo', + 'os_hash_value': 'fake-hash_value', + } + if expect_size is not None: + expected_dict['size'] = expect_size + utils.print_dict.assert_called_once_with(expected_dict) + mock_size.assert_called() + + def test_do_image_create_without_size(self): + self._do_image_create( + temp_args={'name': 'IMG-01', 'disk_format': 'vhd', + 'container_format': 'bare', 'progress': False}, + expect_size=14) + + def test_do_image_create_with_size_exits(self): + args = self._make_args({'name': 'test-image', 'size': 1234}) + expected_msg = ("Setting 'size' during image creation is " + "not supported. Please use --size only when " + "uploading data.") + with mock.patch('glanceclient.common.utils.exit') as mock_exit: + mock_exit.side_effect = self._mock_utils_exit + try: + test_shell.do_image_create(self.gc, args) + self.fail("utils.exit should have been called") + except SystemExit: + pass + + mock_exit.assert_called_once_with(expected_msg) + @mock.patch('sys.stdin', autospec=True) def test_do_image_create_hidden_image(self, mock_stdin): args = self._make_args({'name': 'IMG-01', 'disk_format': 'vhd', @@ -1652,6 +1834,77 @@ def test_image_create_via_import_no_method_with_stdin( 'id': 'via-stdin', 'name': 'Mortimer', 'disk_format': 'raw', 'container_format': 'bare'}) + def _image_create_via_import_with_file_helper( + self, with_access=True): + """Helper for image create via import with file tests.""" + @mock.patch('glanceclient.common.utils.get_file_size') + @mock.patch('glanceclient.v2.shell.do_image_import') + @mock.patch('os.access') + @mock.patch('sys.stdin', autospec=True) + def _test_with_access(mock_stdin, mock_access, + mock_do_import, mock_size): + mock_stdin.isatty = lambda: True + self.mock_get_data_file.return_value = io.StringIO() + mock_access.return_value = with_access + mock_size.return_value = 14 + with open(tempfile.mktemp(), 'w+') as f: + f.write('Some data here') + f.flush() + f.seek(0) + file_name = f.name + self.addCleanup( + lambda: os.remove(file_name) if os.path.exists( + file_name) else None) + my_args = self.base_args.copy() + my_args.update({'file': file_name}) + args = self._make_args(my_args) + with mock.patch.object( + self.gc.images, 'create') as mocked_create, \ + mock.patch.object( + self.gc.images, 'get') as mocked_get, \ + mock.patch.object( + self.gc.images, 'get_import_info') as mocked_info: + ignore_fields = ['self', 'access', 'schema'] + expect_image = dict( + [(field, field) for field in ignore_fields]) + expect_image['id'] = 'fake-image-id' + expect_image['name'] = 'Mortimer' + expect_image['disk_format'] = 'raw' + expect_image['container_format'] = 'bare' + mocked_create.return_value = expect_image + mocked_get.return_value = expect_image + mocked_info.return_value = self.import_info_response + + test_shell.do_image_create_via_import(self.gc, args) + mocked_create.assert_called_once() + mock_do_import.assert_called_once() + mocked_get.assert_called_with(expect_image['id']) + mock_size.assert_called_once() + utils.print_dict.assert_called_with({ + 'id': expect_image['id'], 'name': 'Mortimer', + 'disk_format': 'raw', 'container_format': 'bare' + }) + + _test_with_access() + + def test_image_create_via_import_with_file(self): + self._image_create_via_import_with_file_helper() + + def test_do_image_create_via_import_with_size_exits(self): + args = self._make_args({'name': 'test-image', 'size': 1234}) + expected_msg = ("Setting 'size' during image creation is not " + "supported. Please use --size only when " + "uploading data.") + with mock.patch('glanceclient.common.utils.exit') as mock_exit: + mock_exit.side_effect = self._mock_utils_exit + try: + test_shell.do_image_create(self.gc, args) + self.fail("utils.exit should have been called") + except SystemExit: + pass + + mock_exit.assert_called_once_with(expected_msg) + @mock.patch('glanceclient.v2.shell.do_image_import') @mock.patch('glanceclient.v2.shell.do_image_stage') @mock.patch('os.access') @@ -2056,11 +2309,13 @@ def test_image_upload(self): {'id': 'IMG-01', 'file': 'test', 'size': 1024, 'progress': False}) with mock.patch.object(self.gc.images, 'upload') as mocked_upload: - utils.get_data_file = mock.Mock(return_value='testfile') + expected_data = '*' * 1024 + utils.get_data_file = mock.Mock(return_value=expected_data) + utils.get_file_size = mock.Mock(return_value=1024) mocked_upload.return_value = None test_shell.do_image_upload(self.gc, args) - mocked_upload.assert_called_once_with('IMG-01', 'testfile', 1024, - backend=None) + mocked_upload.assert_called_once_with('IMG-01', expected_data, + 1024, backend=None) @mock.patch('glanceclient.common.utils.exit') def test_image_upload_invalid_store(self, mock_utils_exit): diff --git a/glanceclient/v2/images.py b/glanceclient/v2/images.py index 94c3d12c9..7fe68c472 100644 --- a/glanceclient/v2/images.py +++ b/glanceclient/v2/images.py @@ -295,12 +295,17 @@ def upload(self, image_id, image_data, image_size=None, u_url=None, :param image_id: ID of the image to upload data for. :param image_data: File-like object supplying the data to upload. - :param image_size: Unused - present for backwards compatibility + :param image_size: If present pass it as header :param u_url: Upload url to upload the data to. :param backend: Backend store to upload image to. """ url = u_url or '/v2/images/%s/file' % image_id hdrs = {'Content-Type': 'application/octet-stream'} + if image_size is not None: + if not isinstance(image_size, int): + raise TypeError("image_size must be an integer, " + "got %s" % type(image_size).__name__) + hdrs.update({'x-openstack-image-size': '%i' % image_size}) if backend is not None: hdrs['x-image-meta-store'] = backend @@ -343,11 +348,12 @@ def stage(self, image_id, image_data, image_size=None): :param image_id: ID of the image to upload data for. :param image_data: File-like object supplying the data to upload. - :param image_size: Unused - present for backwards compatibility + :param image_size: If present pass it to upload call """ url = '/v2/images/%s/stage' % image_id resp, body = self.upload(image_id, image_data, + image_size=image_size, u_url=url) return body, resp diff --git a/glanceclient/v2/shell.py b/glanceclient/v2/shell.py index 0b6478761..e77bb3463 100644 --- a/glanceclient/v2/shell.py +++ b/glanceclient/v2/shell.py @@ -49,6 +49,17 @@ def get_image_schema(): return IMAGE_SCHEMA +def get_filesize(args, image_data): + filesize = getattr(args, 'size', None) or utils.get_file_size(image_data) + if getattr(args, 'size', None) and getattr(args, 'file', None): + actual_size = utils.get_file_size(image_data) + if actual_size != args.size: + raise ValueError("Size mismatch: provided size %s does not match " + "the size of the image %s" % ( + args.size, actual_size)) + return filesize + + @utils.schema_args(get_image_schema, omit=['locations', 'os_hidden']) # NOTE(rosmaita): to make this option more intuitive for end users, we # do not use the Glance image property name 'os_hidden' here. This means @@ -66,6 +77,11 @@ def get_image_schema(): help=_('Local file that contains disk image to be uploaded ' 'during creation. Alternatively, the image data can be ' 'passed to the client via stdin.')) +@utils.arg('--size', metavar='', type=int, + help=_('Size in bytes of image to be uploaded. Default is to get ' + 'size from provided data object but this is supported in ' + 'case where size cannot be inferred.'), + default=None) @utils.arg('--progress', action='store_true', default=False, help=_('Show upload progress bar.')) @utils.arg('--store', metavar='', @@ -90,6 +106,11 @@ def do_image_create(gc, args): backend = args.store file_name = fields.pop('file', None) + if 'size' in fields: + utils.exit( + "Setting 'size' during image creation is not supported. " + "Please use --size only when uploading data.") + using_stdin = hasattr(sys.stdin, 'isatty') and not sys.stdin.isatty() if args.store and not (file_name or using_stdin): utils.exit("--store option should only be provided with --file " @@ -107,7 +128,6 @@ def do_image_create(gc, args): if utils.get_data_file(args) is not None: backend = fields.get('store', None) args.id = image['id'] - args.size = None do_image_upload(gc, args) image = gc.images.get(args.id) finally: @@ -128,6 +148,11 @@ def do_image_create(gc, args): help=_('Local file that contains disk image to be uploaded ' 'during creation. Alternatively, the image data can be ' 'passed to the client via stdin.')) +@utils.arg('--size', metavar='', type=int, + help=_('Size in bytes of image to be uploaded. Default is to get ' + 'size from provided data object but this is supported in ' + 'case where size cannot be inferred.'), + default=None) @utils.arg('--progress', action='store_true', default=False, help=_('Show upload progress bar.')) @utils.arg('--import-method', metavar='', @@ -205,6 +230,11 @@ def do_image_create_via_import(gc, args): fields[key] = value file_name = fields.pop('file', None) + if 'size' in fields: + utils.exit( + "Setting 'size' during image creation is not supported. " + "Please use --size only when uploading data.") + using_stdin = hasattr(sys.stdin, 'isatty') and not sys.stdin.isatty() # special processing for backward compatibility with image-create @@ -315,7 +345,6 @@ def do_image_create_via_import(gc, args): args.id = image['id'] if args.import_method: if utils.get_data_file(args) is not None: - args.size = None do_image_stage(gc, args) args.from_create = True args.stores = stores @@ -699,13 +728,14 @@ def do_image_upload(gc, args): _validate_backend(backend, gc) image_data = utils.get_data_file(args) + filesize = get_filesize(args, image_data) + if args.progress: - filesize = utils.get_file_size(image_data) if filesize is not None: # NOTE(kragniz): do not show a progress bar if the size of the # input is unknown (most likely a piped input) image_data = progressbar.VerboseFileWrapper(image_data, filesize) - gc.images.upload(args.id, image_data, args.size, backend=backend) + gc.images.upload(args.id, image_data, filesize, backend=backend) @utils.arg('--file', metavar='', @@ -724,13 +754,14 @@ def do_image_upload(gc, args): def do_image_stage(gc, args): """Upload data for a specific image to staging.""" image_data = utils.get_data_file(args) + filesize = get_filesize(args, image_data) + if args.progress: - filesize = utils.get_file_size(image_data) if filesize is not None: # NOTE(kragniz): do not show a progress bar if the size of the # input is unknown (most likely a piped input) image_data = progressbar.VerboseFileWrapper(image_data, filesize) - gc.images.stage(args.id, image_data, args.size) + gc.images.stage(args.id, image_data, filesize) @utils.arg('--import-method', metavar='', default='glance-direct', From d46a9bff0071e13201892dba6f3e6f9d16487f6c Mon Sep 17 00:00:00 2001 From: Cyril Roelandt Date: Mon, 22 Jul 2024 15:13:03 +0200 Subject: [PATCH 152/167] Python 3.13: fix tests Following a recent change in Python[1], mock_open now invokes close() when used as a context manager. This causes some of our tests to fail. This commit rewrites part of these tests to be less dependant on internal Python details. [1] https://github.com/python/cpython/pull/26902 Change-Id: Iad9c0f0bf7f34d5cf209ec10863b28ddde281d7e Signed-off-by: Cyril Roelandt --- glanceclient/tests/unit/test_shell.py | 28 +++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/glanceclient/tests/unit/test_shell.py b/glanceclient/tests/unit/test_shell.py index 4a123ab1c..520c600d6 100644 --- a/glanceclient/tests/unit/test_shell.py +++ b/glanceclient/tests/unit/test_shell.py @@ -786,15 +786,15 @@ def test_cache_schemas_gets_when_forced(self, exists_mock): client = self.shell._get_versioned_client('2', args) self.shell._cache_schemas(args, client, home_dir=self.cache_dir) - self.assertEqual(12, open.mock_calls.__len__()) + self.assertEqual(len(self.cache_files), len(open.call_args_list)) self.assertEqual(mock.call(self.cache_files[0], 'w'), - open.mock_calls[0]) + open.call_args_list[0]) self.assertEqual(mock.call(self.cache_files[1], 'w'), - open.mock_calls[4]) - actual = json.loads(open.mock_calls[2][1][0]) - self.assertEqual(schema_odict, actual) - actual = json.loads(open.mock_calls[6][1][0]) - self.assertEqual(schema_odict, actual) + open.call_args_list[1]) + actual = json.loads(open().write.call_args_list[0][0][0]) + self.assertEqual(schema_odict, OrderedDict(actual)) + actual = json.loads(open().write.call_args_list[1][0][0]) + self.assertEqual(schema_odict, OrderedDict(actual)) @mock.patch('builtins.open', new=mock.mock_open(), create=True) @mock.patch('os.path.exists', side_effect=[True, False, False, False]) @@ -809,15 +809,15 @@ def test_cache_schemas_gets_when_not_exists(self, exists_mock): client = self.shell._get_versioned_client('2', args) self.shell._cache_schemas(args, client, home_dir=self.cache_dir) - self.assertEqual(12, open.mock_calls.__len__()) + self.assertEqual(len(self.cache_files), len(open.call_args_list)) self.assertEqual(mock.call(self.cache_files[0], 'w'), - open.mock_calls[0]) + open.call_args_list[0]) self.assertEqual(mock.call(self.cache_files[1], 'w'), - open.mock_calls[4]) - actual = json.loads(open.mock_calls[2][1][0]) - self.assertEqual(schema_odict, actual) - actual = json.loads(open.mock_calls[6][1][0]) - self.assertEqual(schema_odict, actual) + open.call_args_list[1]) + actual = json.loads(open().write.call_args_list[0][0][0]) + self.assertEqual(schema_odict, OrderedDict(actual)) + actual = json.loads(open().write.call_args_list[1][0][0]) + self.assertEqual(schema_odict, OrderedDict(actual)) @mock.patch('builtins.open', new=mock.mock_open(), create=True) @mock.patch('os.path.exists', return_value=True) From bcf6652e3a569ebe88709bf20e92786b1dcdd70e Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Fri, 5 Sep 2025 12:32:26 +0000 Subject: [PATCH 153/167] Update master for stable/2025.2 Add file to the reno documentation build to show release notes for stable/2025.2. Use pbr instruction to increment the minor version number automatically so that master versions are higher than the versions on stable/2025.2. Sem-Ver: feature Change-Id: I85a4eb3bc5b0edd1dfefa7e203823bcd3543ddc9 Signed-off-by: OpenStack Release Bot Generated-By: openstack/project-config:roles/copy-release-tools-scripts/files/release-tools/add_release_note_page.sh --- releasenotes/source/2025.2.rst | 6 ++++++ releasenotes/source/index.rst | 1 + 2 files changed, 7 insertions(+) create mode 100644 releasenotes/source/2025.2.rst diff --git a/releasenotes/source/2025.2.rst b/releasenotes/source/2025.2.rst new file mode 100644 index 000000000..4dae18d86 --- /dev/null +++ b/releasenotes/source/2025.2.rst @@ -0,0 +1,6 @@ +=========================== +2025.2 Series Release Notes +=========================== + +.. release-notes:: + :branch: stable/2025.2 diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index 997e4ce91..ffb3658e6 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ glanceclient Release Notes :maxdepth: 1 unreleased + 2025.2 2025.1 2024.2 2024.1 From afcb487ae0e1a38b9e9c22a189d360c80dc81f5f Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Fri, 31 Oct 2025 12:04:17 +0000 Subject: [PATCH 154/167] reno: Update master for unmaintained/2024.1 Update the 2024.1 release notes configuration to build from unmaintained/2024.1. Change-Id: I929af785b200cb5a6206c3a945b4c7a3ee44a9f0 Signed-off-by: OpenStack Release Bot Generated-By: openstack/project-config:roles/copy-release-tools-scripts/files/release-tools/change_reno_branch_to_unmaintained.sh --- releasenotes/source/2024.1.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/source/2024.1.rst b/releasenotes/source/2024.1.rst index 4977a4f1a..6896656be 100644 --- a/releasenotes/source/2024.1.rst +++ b/releasenotes/source/2024.1.rst @@ -3,4 +3,4 @@ =========================== .. release-notes:: - :branch: stable/2024.1 + :branch: unmaintained/2024.1 From b637afdd9ae6ca486818c2d4411d98206e5d2951 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Fri, 6 Mar 2026 13:23:09 +0000 Subject: [PATCH 155/167] Update master for stable/2026.1 Add file to the reno documentation build to show release notes for stable/2026.1. Use pbr instruction to increment the minor version number automatically so that master versions are higher than the versions on stable/2026.1. Sem-Ver: feature Change-Id: Ie6909fcafcf3ebaa27379c7ce5ab57cf53744d20 Signed-off-by: OpenStack Release Bot Generated-By: openstack/project-config:roles/copy-release-tools-scripts/files/release-tools/add_release_note_page.sh --- releasenotes/source/2026.1.rst | 6 ++++++ releasenotes/source/index.rst | 1 + 2 files changed, 7 insertions(+) create mode 100644 releasenotes/source/2026.1.rst diff --git a/releasenotes/source/2026.1.rst b/releasenotes/source/2026.1.rst new file mode 100644 index 000000000..3d2861580 --- /dev/null +++ b/releasenotes/source/2026.1.rst @@ -0,0 +1,6 @@ +=========================== +2026.1 Series Release Notes +=========================== + +.. release-notes:: + :branch: stable/2026.1 diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index ffb3658e6..9c362310c 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ glanceclient Release Notes :maxdepth: 1 unreleased + 2026.1 2025.2 2025.1 2024.2 From e02b6b449ca0a93197608d3adfabf4e3e646a762 Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Mon, 9 Mar 2026 00:48:53 +0900 Subject: [PATCH 156/167] Fix unit tests with urllib3 2.x urllib3 was recently bumped to 2.x[1] in global upper constraints. Adopt the unit tests to fix a few new errors. The key points are - It now strictly requires byte response - It ignores CN to verify SSL certificates and we should add SAN Also leave the script to generate test certificates and keys so that we can adjust these in the future more easily. [1] https://review.opendev.org/c/openstack/requirements/+/972462 Change-Id: I4ed7182ad38593554b0ac7cbdb63af85d984371d Signed-off-by: Takashi Kajinami --- glanceclient/tests/unit/test_http.py | 14 +-- glanceclient/tests/unit/test_ssl.py | 18 +-- glanceclient/tests/unit/var/ca.crt | 64 +++++------ glanceclient/tests/unit/var/certificate.crt | 66 +++++------ glanceclient/tests/unit/var/expired-cert.crt | 67 +++++------ glanceclient/tests/unit/var/privatekey.key | 104 +++++++++--------- .../tests/unit/var/wildcard-certificate.crt | 61 ---------- .../unit/var/wildcard-san-certificate.crt | 54 --------- tools/generate_test_certs.sh | 56 ++++++++++ 9 files changed, 226 insertions(+), 278 deletions(-) delete mode 100644 glanceclient/tests/unit/var/wildcard-certificate.crt delete mode 100644 glanceclient/tests/unit/var/wildcard-san-certificate.crt create mode 100755 tools/generate_test_certs.sh diff --git a/glanceclient/tests/unit/test_http.py b/glanceclient/tests/unit/test_http.py index 31d21f9dd..1e5403b50 100644 --- a/glanceclient/tests/unit/test_http.py +++ b/glanceclient/tests/unit/test_http.py @@ -318,7 +318,7 @@ def test__chunk_body_exact_size_chunk(self): def test_http_chunked_request(self): text = "Ok" - data = io.StringIO(text) + data = io.BytesIO(text.encode()) path = '/v1/images/' self.mock.post(self.endpoint + path, text=text) @@ -341,9 +341,9 @@ def test_http_json(self): self.assertEqual(data, json.loads(self.mock.last_request.body)) def test_http_chunked_response(self): - data = "TEST" + data = b"TEST" path = '/v1/images/' - self.mock.get(self.endpoint + path, body=io.StringIO(data), + self.mock.get(self.endpoint + path, body=io.BytesIO(data), headers={"Content-Type": "application/octet-stream"}) resp, body = self.client.get(path) @@ -353,10 +353,10 @@ def test_http_chunked_response(self): @original_only def test_log_http_response_with_non_ascii_char(self): try: - response = 'Ok' + response = b'Ok' headers = {"Content-Type": "text/plain", "test": "value1\xa5\xa6"} - fake = utils.FakeResponse(headers, io.StringIO(response)) + fake = utils.FakeResponse(headers, io.BytesIO(response)) self.client.log_http_response(fake) except UnicodeDecodeError as e: self.fail("Unexpected UnicodeDecodeError exception '%s'" % e) @@ -457,9 +457,9 @@ def test_log_curl_request_with_token_header(self, mock_log): def test_log_request_id_once(self): logger = self.useFixture(fixtures.FakeLogger(level=logging.DEBUG)) - data = "TEST" + data = b"TEST" path = '/v1/images/' - self.mock.get(self.endpoint + path, body=io.StringIO(data), + self.mock.get(self.endpoint + path, body=io.BytesIO(data), headers={"Content-Type": "application/octet-stream", 'x-openstack-request-id': "1234"}) diff --git a/glanceclient/tests/unit/test_ssl.py b/glanceclient/tests/unit/test_ssl.py index 8641f3dae..50b0e1258 100644 --- a/glanceclient/tests/unit/test_ssl.py +++ b/glanceclient/tests/unit/test_ssl.py @@ -77,7 +77,7 @@ def setUp(self): def test_v1_requests_cert_verification(self, __): """v1 regression test for bug 115260.""" port = self.port - url = 'https://0.0.0.0:%d' % port + url = 'https://127.0.0.1:%d' % port try: client = v1.Client(url, @@ -94,7 +94,7 @@ def test_v1_requests_cert_verification_no_compression(self, __): """v1 regression test for bug 115260.""" # Legacy test. Verify 'no compression' has no effect port = self.port - url = 'https://0.0.0.0:%d' % port + url = 'https://127.0.0.1:%d' % port try: client = v1.Client(url, @@ -110,7 +110,7 @@ def test_v1_requests_cert_verification_no_compression(self, __): def test_v2_requests_cert_verification(self, __): """v2 regression test for bug 115260.""" port = self.port - url = 'https://0.0.0.0:%d' % port + url = 'https://127.0.0.1:%d' % port try: gc = v2.Client(url, @@ -127,7 +127,7 @@ def test_v2_requests_cert_verification_no_compression(self, __): """v2 regression test for bug 115260.""" # Legacy test. Verify 'no compression' has no effect port = self.port - url = 'https://0.0.0.0:%d' % port + url = 'https://127.0.0.1:%d' % port try: gc = v2.Client(url, @@ -143,7 +143,7 @@ def test_v2_requests_cert_verification_no_compression(self, __): def test_v2_requests_valid_cert_verification(self, __): """Test absence of SSL key file.""" port = self.port - url = 'https://0.0.0.0:%d' % port + url = 'https://127.0.0.1:%d' % port cacert = os.path.join(TEST_VAR_DIR, 'ca.crt') try: @@ -160,7 +160,7 @@ def test_v2_requests_valid_cert_verification(self, __): def test_v2_requests_valid_cert_verification_no_compression(self, __): """Test VerifiedHTTPSConnection: absence of SSL key file.""" port = self.port - url = 'https://0.0.0.0:%d' % port + url = 'https://127.0.0.1:%d' % port cacert = os.path.join(TEST_VAR_DIR, 'ca.crt') try: @@ -177,7 +177,7 @@ def test_v2_requests_valid_cert_verification_no_compression(self, __): def test_v2_requests_valid_cert_no_key(self, __): """Test VerifiedHTTPSConnection: absence of SSL key file.""" port = self.port - url = 'https://0.0.0.0:%d' % port + url = 'https://127.0.0.1:%d' % port cert_file = os.path.join(TEST_VAR_DIR, 'certificate.crt') cacert = os.path.join(TEST_VAR_DIR, 'ca.crt') @@ -196,7 +196,7 @@ def test_v2_requests_valid_cert_no_key(self, __): def test_v2_requests_bad_cert(self, __): """Test VerifiedHTTPSConnection: absence of SSL key file.""" port = self.port - url = 'https://0.0.0.0:%d' % port + url = 'https://127.0.0.1:%d' % port cert_file = os.path.join(TEST_VAR_DIR, 'badcert.crt') cacert = os.path.join(TEST_VAR_DIR, 'ca.crt') @@ -219,7 +219,7 @@ def test_v2_requests_bad_cert(self, __): def test_v2_requests_bad_ca(self, __): """Test VerifiedHTTPSConnection: absence of SSL key file.""" port = self.port - url = 'https://0.0.0.0:%d' % port + url = 'https://127.0.0.1:%d' % port cacert = os.path.join(TEST_VAR_DIR, 'badca.crt') try: diff --git a/glanceclient/tests/unit/var/ca.crt b/glanceclient/tests/unit/var/ca.crt index cfdda7917..2fbbe1882 100644 --- a/glanceclient/tests/unit/var/ca.crt +++ b/glanceclient/tests/unit/var/ca.crt @@ -1,36 +1,38 @@ +# DO NOT EDIT. This file is generated by tools/generate_test_certs.sh -----BEGIN CERTIFICATE----- -MIIGVTCCBD2gAwIBAgIUEstxpjoCFDZo8K1Mmz7QIpYwSXIwDQYJKoZIhvcNAQEL +MIIGYzCCBEugAwIBAgIUVH2HK/6/Au6Gf7JYvFM+Rps4J94wDQYJKoZIhvcNAQEL BQAwgbgxCzAJBgNVBAYTAkFVMREwDwYDVQQIDAhTdGF0ZSBDQTELMAkGA1UEBwwC Q0ExGTAXBgNVBAoMEE9wZW5TdGFjayBDQSBPcmcxGjAYBgNVBAsMEU9wZW5TdGFj ayBUZXN0IENBMS0wKwYDVQQDDCRPcGVuc3RhY2sgVGVzdCBDZXJ0aWZpY2F0ZSBB -dXRob3JpdHkxIzAhBgkqhkiG9w0BCQEWFGFkbWluQGNhLmV4YW1wbGUuY29tMCAX -DTIwMDQwNzEyMjYwMVoYDzI5OTkwMTAxMTIyNjAxWjCBuDELMAkGA1UEBhMCQVUx -ETAPBgNVBAgMCFN0YXRlIENBMQswCQYDVQQHDAJDQTEZMBcGA1UECgwQT3BlblN0 -YWNrIENBIE9yZzEaMBgGA1UECwwRT3BlblN0YWNrIFRlc3QgQ0ExLTArBgNVBAMM -JE9wZW5zdGFjayBUZXN0IENlcnRpZmljYXRlIEF1dGhvcml0eTEjMCEGCSqGSIb3 -DQEJARYUYWRtaW5AY2EuZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4IC -DwAwggIKAoICAQD6KfGpVSJsmhTgdbdovaaa3Qe4PeP+Dg9Y7muSggVQVlqUp3YB -xUSo1RDoLyu1ci+KrNODr+kkD/Cbo8yJQCxqTzUpCw3tadFhUJOWvPaqdcYTA8R9 -p7Xjvw3Me7Q7xr4l0pCUIiz/kwxYxk+GCQyXzpXZm14zz+Qm8gz37eoW2jJfoyzA -dB9Tp609Id7C6VHFCWZ2Zsa4+Ua/q+Pn7vLNJ61C2H5sfus8dtcGDzViDWwnWyHw -spyR79rciV2yA3xeq09RvIx2SB1tc3S2Rxw7SmKeYcnkv6YplvhIG5QpErvp+URh -De2wbbxzjFzJqFQO0Yh8IgTBIvQI02++lA8ZX84UDaxmrT92m8GfqQvb96em/H1k -RKJTq0QqSC+BbGDeFxHkuOTJiOZm5Bnivpo0TAPwX6YqpadXARAFWw+fJiHCuFGr -6ltD7zgRnx6SR5WNRNWmTZQNx7wC2Bm0cJ2ec0Asn+bl93RVloaNtbFJhkaN555G -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 +VAPX6MRWpQ== -----END CERTIFICATE----- diff --git a/glanceclient/tests/unit/var/certificate.crt b/glanceclient/tests/unit/var/certificate.crt index 64b2291f4..d4952a858 100644 --- a/glanceclient/tests/unit/var/certificate.crt +++ b/glanceclient/tests/unit/var/certificate.crt @@ -1,34 +1,36 @@ +# DO NOT EDIT. This file is generated by tools/generate_test_certs.sh -----BEGIN CERTIFICATE----- -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 -9l4gwJ1ojNB2abF4LOEf5bU= +MIIGIjCCBAqgAwIBAgIUa8IbpYTnevi57LCXS/7RHHMAhEQwDQYJKoZIhvcNAQEL +BQAwgbgxCzAJBgNVBAYTAkFVMREwDwYDVQQIDAhTdGF0ZSBDQTELMAkGA1UEBwwC +Q0ExGTAXBgNVBAoMEE9wZW5TdGFjayBDQSBPcmcxGjAYBgNVBAsMEU9wZW5TdGFj +ayBUZXN0IENBMS0wKwYDVQQDDCRPcGVuc3RhY2sgVGVzdCBDZXJ0aWZpY2F0ZSBB +dXRob3JpdHkxIzAhBgkqhkiG9w0BCQEWFGFkbWluQGNhLmV4YW1wbGUuY29tMB4X +DTI2MDMwOTE1MjYyMloXDTM2MDMwNjE1MjYyMlowgYcxCzAJBgNVBAYTAkFVMREw +DwYDVQQIDAhTdGF0ZSBDQTEPMA0GA1UEBwwGU3RhdGUxMRswGQYDVQQKDBJPcGVu +U3RhY2sgVGVzdCBPcmcxHDAaBgNVBAsME09wZW5TdGFjayBUZXN0IFVuaXQxGTAX +BgNVBAMMEHRlc3QuZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw +ggIKAoICAQCcc5oW+VurGaz9KFHaO1DmQm2I7jUsYNsZK9+FnPJMxg836Zk3N1e9 +NDZiwQwXxgcU8yPEFdToqEJEedxi827iQW+yOvZKwCewhyTosqWblxhOUEHPmCRB +6gJlMzxUj3JKoBbZyoBp1bjpMq8262fNVCxmwR7lMW6Fcmv1hrrtZY/BFA8h2j1R +H8wIsSN+cQXyresXaP1hff67S/MKJWrQCcLL1MmBzD7ukbUaCaOmcCfhU10/2L99 +EtbZhc12DWxfWPdTs/wPwxPfWfuebv3T153B9sWSiIR2gClIFhm/moslbsDrlAmf +hcPxLz6cUpWtqyk2kOXeoNiOgPD+om98Q9SunokPFI9uLjSy/wkbXbW6/C14ay8c ++044foTs6u2ACnKne9FricVB3p8zLhn5vtVAAE0koH8/aVUWkOD13lNLxIyz4kc2 +4qwIUOjc/YLcYoCZw91A7TRFQuHgm0A1GtBDIUg393fGsXMR8jXz6YOcI4tYobDK +FXX3m8K79h3LgdKQismD/+hjZNOqgzYR7WH+6LMZw8ueXnBZzK0716iRC603dswU +mUF5Ajz9YRWdGHn+MRdk3L4/p3YZzLkpLf1itgMxoyYsdyPyYktr+n7cmf8mqCBi +5P+qhdGZqFh+1haZX/f8ugqN5Ev0LNUFQHBxGcPn4ascBXXW0N9gHwIDAQABo1Mw +UTAPBgNVHREECDAGhwR/AAABMB0GA1UdDgQWBBS7bt0vNE5i4KM+nLEo+ZgdhHwN +bTAfBgNVHSMEGDAWgBSdjBWCNzn38PrEmKGB2t7KLGKxHjANBgkqhkiG9w0BAQsF +AAOCAgEAKJu6m7MhTUZx7g9jXltZJ/oI+EWDPAKocp5Z38ipfSa1pkmYc3wIWU1h +FxBleFjbWFCHL6l3vHSjbm7ExfB9w+J9SA/7ZmztC2NiS7DimwPexlIAStESQhPp +wBSSQWD3b/N+JYDmLykyDkNcgq+eNSs9Fn50mqnsgm1Q+UiFX13l97MnlH5S83RB +KnsnR1PwL3a1XaiGPu8TlKabm40AGwBVVX2Y85jU/WMYTkZZJf/LtxNh6CLqcnQ2 +WPOyArKgXQVDhO9oeHfwK3K8FBzFtDHC+1TY962mC2yzqCv2eBbDeyk9hPhQQqUT +VXUo49lW+ZryTRXVlHZYVFuMxedjDr6AMPf1y8m228p7H2rIiB+FWYPIv7xJ08T0 +hGT4NMMv+yrk3ZgabCmtPAqCVcmRTiPmygFu7QsantwI8o19/BFlE7xgOKNvIc7J +ozNdBpcjXIYjtBi79dVSgqL3kzMJERUqu+uykLqruysGLMPcPtmAQxLOU/YuKray +0j0QIiKN4XcArTR55L3hrREJKGw3wCgvYSahVoj04timrsIUnUl+YymjKAwykMP0 +/wZxdtLFpYOueAYrMgbOQOVfVxA4tfFD7hQuWr8VIRahDyjhb+rsH533xllDkDLB +zHpRA3zDHjJXn8kP9DaNYo3tSBJQ9FzR6uQFexjqiTVwe4BBXi0= -----END CERTIFICATE----- diff --git a/glanceclient/tests/unit/var/expired-cert.crt b/glanceclient/tests/unit/var/expired-cert.crt index 227d42275..c99fb367f 100644 --- a/glanceclient/tests/unit/var/expired-cert.crt +++ b/glanceclient/tests/unit/var/expired-cert.crt @@ -1,35 +1,36 @@ +# DO NOT EDIT. This file is generated by tools/generate_test_certs.sh -----BEGIN CERTIFICATE----- -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 -l3+dFHnWayuCdqcFwIl3m8i8FupFihz9+A== +MIIGIjCCBAqgAwIBAgIUa8IbpYTnevi57LCXS/7RHHMAhEUwDQYJKoZIhvcNAQEL +BQAwgbgxCzAJBgNVBAYTAkFVMREwDwYDVQQIDAhTdGF0ZSBDQTELMAkGA1UEBwwC +Q0ExGTAXBgNVBAoMEE9wZW5TdGFjayBDQSBPcmcxGjAYBgNVBAsMEU9wZW5TdGFj +ayBUZXN0IENBMS0wKwYDVQQDDCRPcGVuc3RhY2sgVGVzdCBDZXJ0aWZpY2F0ZSBB +dXRob3JpdHkxIzAhBgkqhkiG9w0BCQEWFGFkbWluQGNhLmV4YW1wbGUuY29tMB4X +DTI2MDMwOTE1MjYyMloXDTI2MDMwOTE1MjYyMlowgYcxCzAJBgNVBAYTAkFVMREw +DwYDVQQIDAhTdGF0ZSBDQTEPMA0GA1UEBwwGU3RhdGUxMRswGQYDVQQKDBJPcGVu +U3RhY2sgVGVzdCBPcmcxHDAaBgNVBAsME09wZW5TdGFjayBUZXN0IFVuaXQxGTAX +BgNVBAMMEHRlc3QuZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw +ggIKAoICAQCcc5oW+VurGaz9KFHaO1DmQm2I7jUsYNsZK9+FnPJMxg836Zk3N1e9 +NDZiwQwXxgcU8yPEFdToqEJEedxi827iQW+yOvZKwCewhyTosqWblxhOUEHPmCRB +6gJlMzxUj3JKoBbZyoBp1bjpMq8262fNVCxmwR7lMW6Fcmv1hrrtZY/BFA8h2j1R +H8wIsSN+cQXyresXaP1hff67S/MKJWrQCcLL1MmBzD7ukbUaCaOmcCfhU10/2L99 +EtbZhc12DWxfWPdTs/wPwxPfWfuebv3T153B9sWSiIR2gClIFhm/moslbsDrlAmf +hcPxLz6cUpWtqyk2kOXeoNiOgPD+om98Q9SunokPFI9uLjSy/wkbXbW6/C14ay8c ++044foTs6u2ACnKne9FricVB3p8zLhn5vtVAAE0koH8/aVUWkOD13lNLxIyz4kc2 +4qwIUOjc/YLcYoCZw91A7TRFQuHgm0A1GtBDIUg393fGsXMR8jXz6YOcI4tYobDK +FXX3m8K79h3LgdKQismD/+hjZNOqgzYR7WH+6LMZw8ueXnBZzK0716iRC603dswU +mUF5Ajz9YRWdGHn+MRdk3L4/p3YZzLkpLf1itgMxoyYsdyPyYktr+n7cmf8mqCBi +5P+qhdGZqFh+1haZX/f8ugqN5Ev0LNUFQHBxGcPn4ascBXXW0N9gHwIDAQABo1Mw +UTAPBgNVHREECDAGhwR/AAABMB0GA1UdDgQWBBS7bt0vNE5i4KM+nLEo+ZgdhHwN +bTAfBgNVHSMEGDAWgBSdjBWCNzn38PrEmKGB2t7KLGKxHjANBgkqhkiG9w0BAQsF +AAOCAgEAMCwZe3uUThBuhcI01Xzr1J08YqHB81WeORqRRmhQoSQZVawuwgMqFKzv +GOEYGi1cYB1r+877C4vDjYtRQmtaLhVd09sgvNmTpdbPWfHooebSJcJzPhKt0Stb +5oG5AMaM0GrcDPTXz7b81vGJPwKu0lYg/cDXmXbt176i7DHovEFXjA8COfAluc56 +rStc3wFRJ2mYP2j5iEWa7VjXrJGHSDhJAglTg1tuQkmePbcShThzw8OFpUdCBH3H +s82Pr1aaH4NkgtG81sq+1EPHDOvBR8bjvuzxCDxIOkMMdDW7xjRwIdnT2uOE5kMj +WJ1Z+AXZIsGvu8Wx2j4nwutuKc71ejHXUir3vrDmbGs7hqUdibu652bycw7Nc7a6 +D2JNrOWzJH7IuIyvR+NmL4IPcG3je3exfwmfz7612VkFv8Ek4cPWGi00zHwkE/Xz +DMR/lFBc4R6Tpkj09JRWFGL2VDSzImcmaojIqQAapD40nDpZJ8TqOPwpaXeNcWIr +xpEnMq4pLI9TUWFteXEMh5ij3CB2U0TAXeeowQ5nX2WNrAjnDov+mrSG79eNOBxl +UblTCjfeytyciH4WQ2BDXkX06db2udeo7hSTABgnUYk26u7r2qPM4O1Zf/sWiDG4 +8oWEaI5553dfsp+sWW5UsC47N3YMUVCyrWX1/yX7Xc9RuuytF34= -----END CERTIFICATE----- diff --git a/glanceclient/tests/unit/var/privatekey.key b/glanceclient/tests/unit/var/privatekey.key index 6cd8725b4..d5178fcc0 100644 --- a/glanceclient/tests/unit/var/privatekey.key +++ b/glanceclient/tests/unit/var/privatekey.key @@ -1,51 +1,53 @@ ------BEGIN RSA PRIVATE KEY----- -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 ------END RSA PRIVATE KEY----- +# DO NOT EDIT. This file is generated by tools/generate_test_certs.sh +-----BEGIN PRIVATE KEY----- +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 +blfeGXB9HbKj2/tdUT6DnyixuyDmv4DU +-----END PRIVATE KEY----- diff --git a/glanceclient/tests/unit/var/wildcard-certificate.crt b/glanceclient/tests/unit/var/wildcard-certificate.crt deleted file mode 100644 index a5f0b6257..000000000 --- a/glanceclient/tests/unit/var/wildcard-certificate.crt +++ /dev/null @@ -1,61 +0,0 @@ -#Certificate: -# Data: -# Version: 1 (0x0) -# Serial Number: 13493453254446411258 (0xbb42603e589dedfa) -# Signature Algorithm: sha1WithRSAEncryption -# Issuer: C=US, ST=CA, L=State1, O=Openstack Test Org, OU=Openstack Test Unit, CN=*.pong.example.com/emailAddress=admin@example.com -# Validity -# Not Before: Aug 21 17:29:18 2013 GMT -# Not After : Jul 28 17:29:18 2113 GMT -# Subject: C=US, ST=CA, L=State1, O=Openstack Test Org, OU=Openstack Test Unit, CN=*.pong.example.com/emailAddress=admin@example.com -# Subject Public Key Info: -# Public Key Algorithm: rsaEncryption -# Public-Key: (4096 bit) -# Modulus: -# 00:d4:bb:3a:c4:a0:06:54:31:23:5d:b0:78:5a:be: -# 45:44:ae:a1:89:86:11:d8:ca:a8:33:b0:4f:f3:e1: -# 46:1e:85:a3:2a:9c:a4:e0:c2:14:34:4f:91:df:dc: -# . -# . -# . -# Exponent: 65537 (0x10001) -# Signature Algorithm: sha1WithRSAEncryption -# 9f:cc:08:5d:19:ee:54:31:a3:57:d7:3c:89:89:c0:69:41:dd: -# 46:f8:73:68:ec:46:b9:fa:f5:df:f6:d9:58:35:d8:53:94:88: -# bd:36:a6:23:9e:0c:0d:89:62:35:91:49:b6:14:f4:43:69:3c: -# . -# . -# . ------BEGIN CERTIFICATE----- -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 ------END CERTIFICATE----- diff --git a/glanceclient/tests/unit/var/wildcard-san-certificate.crt b/glanceclient/tests/unit/var/wildcard-san-certificate.crt deleted file mode 100644 index 6ce27bc6f..000000000 --- a/glanceclient/tests/unit/var/wildcard-san-certificate.crt +++ /dev/null @@ -1,54 +0,0 @@ -#Certificate: -# Data: -# Version: 3 (0x2) -# Serial Number: 11990626514780340979 (0xa66743493fdcc2f3) -# Signature Algorithm: sha1WithRSAEncryption -# Issuer: C=US, ST=CA, L=State1, O=Openstack Test Org, OU=Openstack Test Unit, CN=0.0.0.0 -# Validity -# Not Before: Dec 10 15:31:22 2013 GMT -# Not After : Nov 16 15:31:22 2113 GMT -# Subject: C=US, ST=CA, L=State1, O=Openstack Test Org, OU=Openstack Test Unit, CN=0.0.0.0 -# Subject Public Key Info: -# Public Key Algorithm: rsaEncryption -# Public-Key: (2048 bit) -# Modulus: -# 00:ca:6b:07:73:53:24:45:74:05:a5:2a:27:bd:3e: -# . -# . -# . -# Exponent: 65537 (0x10001) -# X509v3 extensions: -# X509v3 Key Usage: -# Key Encipherment, Data Encipherment -# X509v3 Extended Key Usage: -# TLS Web Server Authentication -# X509v3 Subject Alternative Name: -# DNS:foo.example.net, DNS:*.example.com -# Signature Algorithm: sha1WithRSAEncryption -# 7e:41:69:da:f4:3c:06:d6:83:c6:f2:db:df:37:f1:ac:fa:f5: -# . -# . -# . ------BEGIN CERTIFICATE----- -MIIDxDCCAqygAwIBAgIJAKZnQ0k/3MLzMA0GCSqGSIb3DQEBBQUAMHgxCzAJBgNV -BAYTAlVTMQswCQYDVQQIEwJDQTEPMA0GA1UEBxMGU3RhdGUxMRswGQYDVQQKExJP -cGVuc3RhY2sgVGVzdCBPcmcxHDAaBgNVBAsTE09wZW5zdGFjayBUZXN0IFVuaXQx -EDAOBgNVBAMTBzAuMC4wLjAwIBcNMTMxMjEwMTUzMTIyWhgPMjExMzExMTYxNTMx -MjJaMHgxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEPMA0GA1UEBxMGU3RhdGUx -MRswGQYDVQQKExJPcGVuc3RhY2sgVGVzdCBPcmcxHDAaBgNVBAsTE09wZW5zdGFj -ayBUZXN0IFVuaXQxEDAOBgNVBAMTBzAuMC4wLjAwggEiMA0GCSqGSIb3DQEBAQUA -A4IBDwAwggEKAoIBAQDKawdzUyRFdAWlKie9Pn10j7frffN+z1gEMluK2CtDEwv9 -kbD4uS/Kz4dujfTx03mdyNfiMVlOM+YJm/qeLLSdJyFyvZ9Y3WmJ+vT2RGlMMhLd -/wEnMRrTYLL39pwI6z+gyw+4D78Pyv/OXy02IA6WtVEefYSx1vmVngb3pL+iBzhO -8CZXNI6lqrFhh+Hr4iMkYMtY1vTnwezAL6p64E/ZAFNPYCEJlacESTLQ4VZYniHc -QTgnE1czlI1vxlIk1KDXAzUGeeopZecRih9qlTxtOpklqEciQEE+sHtPcvyvdRE9 -Bdyx5rNSALLIcXs0ViJE1RPlw3fjdBoDIOygqvX1AgMBAAGjTzBNMAsGA1UdDwQE -AwIEMDATBgNVHSUEDDAKBggrBgEFBQcDATApBgNVHREEIjAggg9mb28uZXhhbXBs -ZS5uZXSCDSouZXhhbXBsZS5jb20wDQYJKoZIhvcNAQEFBQADggEBAH5Badr0PAbW -g8by29838az69Raul5IkpZQ5V3O1NaNNWxvmF1q8zFFqqGK5ktXJAwGiwnYEBb30 -Zfrr+eFIEERzBthSJkWlP8NG+2ooMyg50femp+asAvW+KYYefJW8KaXTsznMsAFy -z1agcWVYVZ4H9PwunEYn/rM1krLEe4Cagsw5nmf8VqZg+hHtw930q8cRzgDsZdfA -jVK6dWdmzmLCUTL1GKCeNriDw1jIeFvNufC+Q3orH7xBx4VL+NV5ORWdNY/B8q1b -mFHdzbuZX6v39+2ww6aZqG2orfxUocc/5Ox6fXqenKPI3moeHS6Ktesq7sEQSJ6H -QZFsTuT/124= ------END CERTIFICATE----- diff --git a/tools/generate_test_certs.sh b/tools/generate_test_certs.sh new file mode 100755 index 000000000..08d3b4f06 --- /dev/null +++ b/tools/generate_test_certs.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# +# 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. +# +# The script to generate the key file sand certificates files used in unit +# tests. These files are saved in glanceclient/tests/unit/var . +# +pushd $(dirname -- "$0")/../glanceclient/tests/unit/var + +# Remove existing files +rm *.key *.csr *.crt *.srl + +openssl genrsa -out ca.key 4096 + +openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.crt \ + -subj "/C=AU/ST=State CA/L=CA/O=OpenStack CA Org/OU=OpenStack Test CA/CN=Openstack Test Certificate Authority/emailAddress=admin@ca.example.com/" \ + -addext "keyUsage=critical,digitalSignature,keyCertSign" + +openssl genrsa -out privatekey.key 4096 + +openssl req -new -key privatekey.key -out server.csr \ + -subj "/C=AU/ST=State CA/L=State1/O=OpenStack Test Org/OU=OpenStack Test Unit/CN=test.example.com/" \ + -addext "subjectAltName=IP:127.0.0.1" +openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \ + -out certificate.crt -days 3650 -sha256 -copy_extensions copyall + +openssl req -new -key privatekey.key -out expired-cert.csr \ + -subj "/C=AU/ST=State CA/L=State1/O=OpenStack Test Org/OU=OpenStack Test Unit/CN=test.example.com/" \ + -addext "subjectAltName=IP:127.0.0.1" +openssl x509 -req -in expired-cert.csr -CA ca.crt -CAkey ca.key -CAcreateserial \ + -out expired-cert.crt -days 0 -sha256 -copy_extensions copyall + +touch badcert.crt + +for f in badcert.crt ca.crt certificate.crt expired-cert.crt privatekey.key; do + if [ -f $f ]; then + sed -i '1i # DO NOT EDIT. This file is generated by tools/generate_test_certs.sh' $f + fi +done + +# Remove unused files +rm *.csr +rm ca.key +rm ca.srl + +popd From b7274aa4d22b5ab6e90fc1a9e18d5c6fff95f1e6 Mon Sep 17 00:00:00 2001 From: Abhishek Kekane Date: Mon, 24 Nov 2025 07:13:28 +0000 Subject: [PATCH 157/167] Support for downloading image from preferred stores Added query parameter for store suggestion while downloading the image to existing image-download command. Related: blueprint bp/download-from-specific-store Change-Id: I4a20d20f16cded83d391bc2ba1baa0e9cedfa20a Signed-off-by: Abhishek Kekane --- glanceclient/tests/unit/v2/test_images.py | 30 +++++++++ glanceclient/tests/unit/v2/test_shell_v2.py | 61 +++++++++++++++++-- glanceclient/v2/images.py | 12 +++- glanceclient/v2/shell.py | 12 +++- .../notes/download-from-suggested-stores.yaml | 19 ++++++ 5 files changed, 126 insertions(+), 8 deletions(-) create mode 100644 releasenotes/notes/download-from-suggested-stores.yaml diff --git a/glanceclient/tests/unit/v2/test_images.py b/glanceclient/tests/unit/v2/test_images.py index 67dfdd64b..33626fdbf 100644 --- a/glanceclient/tests/unit/v2/test_images.py +++ b/glanceclient/tests/unit/v2/test_images.py @@ -1323,6 +1323,36 @@ def test_download_forbidden(self): except exc.HTTPForbidden: pass + def test_data_with_prefer(self): + image_id = '606b0e88-7c5a-4d54-b5bb-046105d4de6f' + prefer = ['store1', 'store2'] + resp = utils.FakeResponse(headers={'content-length': '3'}, + status_code=200) + self.controller.controller.http_client.get = mock.Mock( + return_value=(resp, 'CCC')) + self.controller.data(image_id, do_checksum=False, + prefer=prefer) + # Check that the prefer parameter was passed as query parameters + calls = self.controller.controller.http_client.get.call_args_list + self.assertEqual(1, len(calls)) + expected_call = calls[0] + self.assertIn('prefer=store1%2Cstore2', expected_call[0][0]) + self.assertNotIn('stores', expected_call[0][0]) + + def test_data_without_stores(self): + image_id = '606b0e88-7c5a-4d54-b5bb-046105d4de6f' + resp = utils.FakeResponse(headers={'content-length': '3'}, + status_code=200) + self.controller.controller.http_client.get = mock.Mock( + return_value=(resp, 'CCC')) + self.controller.data(image_id, do_checksum=False) + # Check that no query parameters were added + calls = self.controller.controller.http_client.get.call_args_list + self.assertEqual(1, len(calls)) + expected_call = calls[0] + self.assertEqual('/v2/images/%s/file' % image_id, expected_call[0][0]) + self.assertNotIn('?', expected_call[0][0]) + def test_update_replace_prop(self): image_id = '3a4560a1-e585-443e-9b39-553b46ec92d1' params = {'name': 'pong'} diff --git a/glanceclient/tests/unit/v2/test_shell_v2.py b/glanceclient/tests/unit/v2/test_shell_v2.py index bdfb13962..1f088215a 100644 --- a/glanceclient/tests/unit/v2/test_shell_v2.py +++ b/glanceclient/tests/unit/v2/test_shell_v2.py @@ -2774,7 +2774,7 @@ def test_neg_image_import_stores_all_stores_not_specified( def test_image_download(self): args = self._make_args( {'id': 'IMG-01', 'file': 'test', 'progress': True, - 'allow_md5_fallback': False}) + 'allow_md5_fallback': False, 'prefer': None}) with mock.patch.object(self.gc.images, 'data') as mocked_data, \ mock.patch.object(utils, '_extract_request_id'): @@ -2783,7 +2783,8 @@ def test_image_download(self): test_shell.do_image_download(self.gc, args) mocked_data.assert_called_once_with('IMG-01', - allow_md5_fallback=False) + allow_md5_fallback=False, + prefer=None) # check that non-default value is being passed correctly args.allow_md5_fallback = True @@ -2794,7 +2795,8 @@ def test_image_download(self): test_shell.do_image_download(self.gc, args) mocked_data.assert_called_once_with('IMG-01', - allow_md5_fallback=True) + allow_md5_fallback=True, + prefer=None) @mock.patch.object(utils, 'exit') @mock.patch('sys.stdout', autospec=True) @@ -2802,7 +2804,7 @@ def test_image_download_no_file_arg(self, mocked_stdout, mocked_utils_exit): # Indicate that no file name was given as command line argument args = self._make_args({'id': '1234', 'file': None, 'progress': False, - 'allow_md5_fallback': False}) + 'allow_md5_fallback': False, 'prefer': None}) # Indicate that no file is specified for output redirection mocked_stdout.isatty = lambda: True test_shell.do_image_download(self.gc, args) @@ -2917,7 +2919,8 @@ def test_do_image_download_with_forbidden_id(self, mocked_print_err, mocked_stdout): args = self._make_args({'id': 'IMG-01', 'file': None, 'progress': False, - 'allow_md5_fallback': False}) + 'allow_md5_fallback': False, + 'prefer': None}) mocked_stdout.isatty = lambda: False with mock.patch.object(self.gc.images, 'data') as mocked_data: mocked_data.side_effect = exc.HTTPForbidden @@ -2935,7 +2938,8 @@ def test_do_image_download_with_forbidden_id(self, mocked_print_err, def test_do_image_download_with_500(self, mocked_print_err, mocked_stdout): args = self._make_args({'id': 'IMG-01', 'file': None, 'progress': False, - 'allow_md5_fallback': False}) + 'allow_md5_fallback': False, + 'prefer': None}) mocked_stdout.isatty = lambda: False with mock.patch.object(self.gc.images, 'data') as mocked_data: mocked_data.side_effect = exc.HTTPInternalServerError @@ -2948,6 +2952,51 @@ def test_do_image_download_with_500(self, mocked_print_err, mocked_stdout): self.assertEqual(1, mocked_data.call_count) self.assertEqual(1, mocked_print_err.call_count) + def test_image_download_with_prefer(self): + args = self._make_args( + {'id': 'IMG-01', 'file': 'test', 'progress': False, + 'allow_md5_fallback': False, 'prefer': 'store1,store2'}) + + with mock.patch.object(self.gc.images, 'data') as mocked_data, \ + mock.patch.object(utils, '_extract_request_id'): + mocked_data.return_value = utils.RequestIdProxy( + [c for c in 'abcdef']) + + test_shell.do_image_download(self.gc, args) + mocked_data.assert_called_once_with('IMG-01', + allow_md5_fallback=False, + prefer=['store1', 'store2']) + + def test_image_download_with_prefer_empty_string(self): + args = self._make_args( + {'id': 'IMG-01', 'file': 'test', 'progress': False, + 'allow_md5_fallback': False, 'prefer': ''}) + + with mock.patch.object(self.gc.images, 'data') as mocked_data, \ + mock.patch.object(utils, '_extract_request_id'): + mocked_data.return_value = utils.RequestIdProxy( + [c for c in 'abcdef']) + + test_shell.do_image_download(self.gc, args) + mocked_data.assert_called_once_with('IMG-01', + allow_md5_fallback=False, + prefer=None) + + def test_image_download_without_prefer(self): + args = self._make_args( + {'id': 'IMG-01', 'file': 'test', 'progress': False, + 'allow_md5_fallback': False, 'prefer': None}) + + with mock.patch.object(self.gc.images, 'data') as mocked_data, \ + mock.patch.object(utils, '_extract_request_id'): + mocked_data.return_value = utils.RequestIdProxy( + [c for c in 'abcdef']) + + test_shell.do_image_download(self.gc, args) + mocked_data.assert_called_once_with('IMG-01', + allow_md5_fallback=False, + prefer=None) + def test_do_member_list(self): args = self._make_args({'image_id': 'IMG-01'}) with mock.patch.object(self.gc.image_members, 'list') as mocked_list: diff --git a/glanceclient/v2/images.py b/glanceclient/v2/images.py index d9f30289c..3c658fad5 100644 --- a/glanceclient/v2/images.py +++ b/glanceclient/v2/images.py @@ -216,7 +216,8 @@ def get_associated_image_tasks(self, image_id): 'This operation is not supported by Glance.') @utils.add_req_id_to_object() - def data(self, image_id, do_checksum=True, allow_md5_fallback=False): + def data(self, image_id, do_checksum=True, allow_md5_fallback=False, + prefer=None): """Retrieve data of an image. When do_checksum is enabled, validation proceeds as follows: @@ -240,6 +241,8 @@ def data(self, image_id, do_checksum=True, allow_md5_fallback=False): :param allow_md5_fallback: Use the MD5 checksum for validation if the algorithm specified by the image's 'os_hash_algo' property is not available + :param prefer: List of store identifiers suggesting the ordering of + stores to try when downloading :returns: An iterable body or ``None`` """ if do_checksum: @@ -252,6 +255,13 @@ def data(self, image_id, do_checksum=True, allow_md5_fallback=False): meta_hash_algo = image_meta.get('os_hash_algo', None) url = '/v2/images/%s/file' % image_id + params = {} + if prefer: + params['prefer'] = ','.join(prefer) + + if params: + url = '%s?%s' % (url, urllib.parse.urlencode(params)) + resp, body = self.http_client.get(url) if resp.status_code == codes.no_content: return None, resp diff --git a/glanceclient/v2/shell.py b/glanceclient/v2/shell.py index e77bb3463..f3703cdeb 100644 --- a/glanceclient/v2/shell.py +++ b/glanceclient/v2/shell.py @@ -678,6 +678,11 @@ def do_stores_delete(gc, args): @utils.arg('id', metavar='', help=_('ID of image to download.')) @utils.arg('--progress', action='store_true', default=False, help=_('Show download progress bar.')) +@utils.arg('--prefer', metavar='', + help=_('Comma-separated list of store identifiers suggesting the ' + 'ordering of stores to try when downloading the image. ' + 'Store identifiers are site-specific and can be discovered ' + 'using the "stores-info" command.')) def do_image_download(gc, args): """Download a specific image.""" if sys.stdout.isatty() and (args.file is None): @@ -686,9 +691,14 @@ def do_image_download(gc, args): 'downloaded image or redirect output to another source.') utils.exit(msg) + prefer = None + if args.prefer: + prefer = [s for s in (x.strip() for x in args.prefer.split(',')) if s] + try: body = gc.images.data(args.id, - allow_md5_fallback=args.allow_md5_fallback) + allow_md5_fallback=args.allow_md5_fallback, + prefer=prefer) except (exc.HTTPForbidden, exc.HTTPException) as e: msg = "Unable to download image '%s'. (%s)" % (args.id, e) utils.exit(msg) diff --git a/releasenotes/notes/download-from-suggested-stores.yaml b/releasenotes/notes/download-from-suggested-stores.yaml new file mode 100644 index 000000000..46adb2d94 --- /dev/null +++ b/releasenotes/notes/download-from-suggested-stores.yaml @@ -0,0 +1,19 @@ +--- +features: + - | + The ``image-download`` command now supports the ``--prefer`` parameter + to suggest which stores to try when downloading images. The prefer + parameter accepts a comma-separated list of store identifiers. + + If the image is not found in any of the specified stores, Glance will + fall back to trying all available stores. + + Example usage: + + .. code-block:: bash + + glance image-download --prefer store1,store2 --file image.qcow2 + + Store identifiers are site-specific and can be discovered using the + ``stores-info`` command. + From 5989ae089eba572a4d11064da8676cc7f0459f9b Mon Sep 17 00:00:00 2001 From: Cyril Roelandt Date: Wed, 22 Apr 2026 01:28:24 +0200 Subject: [PATCH 158/167] Remove useless glanceclient/common/https.py No code from this file is used anywhere else in the client. This file should probably have been removed after merging the following commits: - 28c003dc1179ddb3124fd30c6f525dd341ae9213 - 618637a5bd545d8271d9349b08a8e4ab2841d086 Also remove all references to VerifiedHTTPSConnection from test_ssl.py. Conveniently, this also gets rid of all eventlet imports. Change-Id: I9d8b1020a296de60c196a82ad6e109b51f0a0761 Signed-off-by: Cyril Roelandt --- glanceclient/common/https.py | 250 ---------------------------- glanceclient/tests/unit/test_ssl.py | 8 +- 2 files changed, 4 insertions(+), 254 deletions(-) delete mode 100644 glanceclient/common/https.py diff --git a/glanceclient/common/https.py b/glanceclient/common/https.py deleted file mode 100644 index 94aeeb32d..000000000 --- a/glanceclient/common/https.py +++ /dev/null @@ -1,250 +0,0 @@ -# Copyright 2014 Red Hat, Inc -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import socket -import ssl -import struct - -import OpenSSL - - -try: - from eventlet import patcher - # Handle case where we are running in a monkey patched environment - if patcher.is_monkey_patched('socket'): - from eventlet.green.httplib import HTTPSConnection - from eventlet.green.OpenSSL.SSL import GreenConnection as Connection - else: - raise ImportError -except ImportError: - import http.client - from OpenSSL import SSL - HTTPSConnection = http.client.HTTPSConnection - Connection = SSL.Connection - - -from glanceclient import exc - - -def verify_callback(host=None): - """Provide wrapper for do_verify_callback. - - We use a partial around the 'real' verify_callback function - so that we can stash the host value without holding a - reference on the VerifiedHTTPSConnection. - """ - def wrapper(connection, x509, errnum, - depth, preverify_ok, host=host): - return do_verify_callback(connection, x509, errnum, - depth, preverify_ok, host=host) - return wrapper - - -def do_verify_callback(connection, x509, errnum, - depth, preverify_ok, host=None): - """Verify the server's SSL certificate. - - This is a standalone function rather than a method to avoid - issues around closing sockets if a reference is held on - a VerifiedHTTPSConnection by the callback function. - """ - if x509.has_expired(): - msg = "SSL Certificate expired on '%s'" % x509.get_notAfter() - raise exc.SSLCertificateError(msg) - - if depth == 0 and preverify_ok: - # We verify that the host matches against the last - # certificate in the chain - return host_matches_cert(host, x509) - else: - # Pass through OpenSSL's default result - return preverify_ok - - -def host_matches_cert(host, x509): - """Verify the certificate identifies the host. - - Verify that the x509 certificate we have received - from 'host' correctly identifies the server we are - connecting to, ie that the certificate's Common Name - or a Subject Alternative Name matches 'host'. - """ - def check_match(name): - # Directly match the name - if name == host: - return True - - # Support single wildcard matching - if name.startswith('*.') and host.find('.') > 0: - if name[2:] == host.split('.', 1)[1]: - return True - - common_name = x509.get_subject().commonName - - # First see if we can match the CN - if check_match(common_name): - return True - # Also try Subject Alternative Names for a match - san_list = None - for i in range(x509.get_extension_count()): - ext = x509.get_extension(i) - if ext.get_short_name() == b'subjectAltName': - san_list = str(ext) - for san in ''.join(san_list.split()).split(','): - if san.startswith('DNS:'): - if check_match(san.split(':', 1)[1]): - return True - - # Server certificate does not match host - msg = ('Host "%s" does not match x509 certificate contents: ' - 'CommonName "%s"' % (host, common_name)) - if san_list is not None: - msg = msg + ', subjectAltName "%s"' % san_list - raise exc.SSLCertificateError(msg) - - -def to_bytes(s): - if isinstance(s, str): - return bytes(s, 'latin-1') - else: - return s - - -class OpenSSLConnectionDelegator(object): - """An OpenSSL.SSL.Connection delegator. - - Supplies an additional 'makefile' method which httplib requires - and is not present in OpenSSL.SSL.Connection. - - Note: Since it is not possible to inherit from OpenSSL.SSL.Connection - a delegator must be used. - """ - def __init__(self, *args, **kwargs): - self.connection = Connection(*args, **kwargs) - - def __getattr__(self, name): - return getattr(self.connection, name) - - def makefile(self, *args, **kwargs): - return socket._fileobject(self.connection, *args, **kwargs) - - -class VerifiedHTTPSConnection(HTTPSConnection): - """Extended OpenSSL HTTPSConnection for enhanced SSL support. - - Note: Much of this functionality can eventually be replaced - with native Python 3.3 code. - """ - # Restrict the set of client supported cipher suites - CIPHERS = 'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:'\ - 'eCDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:'\ - 'RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS' - - def __init__(self, host, port=None, key_file=None, cert_file=None, - cacert=None, timeout=None, insecure=False, - ssl_compression=True): - # List of exceptions reported by Python3 instead of - # SSLConfigurationError - excp_lst = (TypeError, FileNotFoundError, ssl.SSLError) - try: - HTTPSConnection.__init__(self, host, port, - key_file=key_file, - cert_file=cert_file) - self.key_file = key_file - self.cert_file = cert_file - self.timeout = timeout - self.insecure = insecure - # NOTE(flaper87): `is_verified` is needed for - # requests' urllib3. If insecure is True then - # the request is not `verified`, hence `not insecure` - self.is_verified = not insecure - self.ssl_compression = ssl_compression - self.cacert = None if cacert is None else str(cacert) - self.set_context() - # ssl exceptions are reported in various form in Python 3 - # so to be compatible, we report the same kind as under - # Python2 - except excp_lst as e: - raise exc.SSLConfigurationError(str(e)) - - def set_context(self): - """Set up the OpenSSL context.""" - self.context = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD) - self.context.set_cipher_list(self.CIPHERS) - - if self.ssl_compression is False: - self.context.set_options(0x20000) # SSL_OP_NO_COMPRESSION - - if self.insecure is not True: - self.context.set_verify(OpenSSL.SSL.VERIFY_PEER, - verify_callback(host=self.host)) - else: - self.context.set_verify(OpenSSL.SSL.VERIFY_NONE, - lambda *args: True) - - if self.cert_file: - try: - self.context.use_certificate_file(self.cert_file) - except Exception as e: - msg = 'Unable to load cert from "%s" %s' % (self.cert_file, e) - raise exc.SSLConfigurationError(msg) - if self.key_file is None: - # We support having key and cert in same file - try: - self.context.use_privatekey_file(self.cert_file) - except Exception as e: - msg = ('No key file specified and unable to load key ' - 'from "%s" %s' % (self.cert_file, e)) - raise exc.SSLConfigurationError(msg) - - if self.key_file: - try: - self.context.use_privatekey_file(self.key_file) - except Exception as e: - msg = 'Unable to load key from "%s" %s' % (self.key_file, e) - raise exc.SSLConfigurationError(msg) - - if self.cacert: - try: - self.context.load_verify_locations(to_bytes(self.cacert)) - except Exception as e: - msg = 'Unable to load CA from "%s" %s' % (self.cacert, e) - raise exc.SSLConfigurationError(msg) - else: - self.context.set_default_verify_paths() - - def connect(self): - """Connect to an SSL port using the OpenSSL library. - - This method also applies per-connection parameters to the connection. - """ - result = socket.getaddrinfo(self.host, self.port, 0, - socket.SOCK_STREAM) - if result: - socket_family = result[0][0] - if socket_family == socket.AF_INET6: - sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) - else: - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - else: - # If due to some reason the address lookup fails - we still connect - # to IPv4 socket. This retains the older behavior. - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - if self.timeout is not None: - # '0' microseconds - sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO, - struct.pack('LL', self.timeout, 0)) - self.sock = OpenSSLConnectionDelegator(self.context, sock) - self.sock.connect((self.host, self.port)) diff --git a/glanceclient/tests/unit/test_ssl.py b/glanceclient/tests/unit/test_ssl.py index 50b0e1258..61ad93cb1 100644 --- a/glanceclient/tests/unit/test_ssl.py +++ b/glanceclient/tests/unit/test_ssl.py @@ -158,7 +158,7 @@ def test_v2_requests_valid_cert_verification(self, __): @mock.patch('sys.stderr') def test_v2_requests_valid_cert_verification_no_compression(self, __): - """Test VerifiedHTTPSConnection: absence of SSL key file.""" + """Test SSL certificate verification with valid CA certificate.""" port = self.port url = 'https://127.0.0.1:%d' % port cacert = os.path.join(TEST_VAR_DIR, 'ca.crt') @@ -175,7 +175,7 @@ def test_v2_requests_valid_cert_verification_no_compression(self, __): @mock.patch('sys.stderr') def test_v2_requests_valid_cert_no_key(self, __): - """Test VerifiedHTTPSConnection: absence of SSL key file.""" + """Test client cert authentication fails without private key.""" port = self.port url = 'https://127.0.0.1:%d' % port cert_file = os.path.join(TEST_VAR_DIR, 'certificate.crt') @@ -194,7 +194,7 @@ def test_v2_requests_valid_cert_no_key(self, __): @mock.patch('sys.stderr') def test_v2_requests_bad_cert(self, __): - """Test VerifiedHTTPSConnection: absence of SSL key file.""" + """Test client cert authentication fails with bad certificate.""" port = self.port url = 'https://127.0.0.1:%d' % port cert_file = os.path.join(TEST_VAR_DIR, 'badcert.crt') @@ -217,7 +217,7 @@ def test_v2_requests_bad_cert(self, __): @mock.patch('sys.stderr') def test_v2_requests_bad_ca(self, __): - """Test VerifiedHTTPSConnection: absence of SSL key file.""" + """Test SSL verification fails with invalid CA certificate path.""" port = self.port url = 'https://127.0.0.1:%d' % port cacert = os.path.join(TEST_VAR_DIR, 'badca.crt') From e298d0fd124ae92fae6a8aeaea855d70d9d23e74 Mon Sep 17 00:00:00 2001 From: Cyril Roelandt Date: Fri, 27 Jun 2025 02:50:43 +0200 Subject: [PATCH 159/167] Zuul: do not use USE_PYTHON3 Devstack has removed the USE_PYTHON3 variable[1][2] and now always uses Python 3. [1] https://review.opendev.org/c/openstack/devstack/+/920658 [2] Commit 5412dbfe7b797149f1f68100de8003b1876398fe Change-Id: Ifa9f21383f31bae9abe24a9b47a1a0f50523b554 Signed-off-by: Takashi Kajinami --- .zuul.yaml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index 4721bfb07..44c78bc75 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -59,13 +59,6 @@ vars: tox_envlist: py3 -- job: - name: glanceclient-dsvm-functional-py3 - parent: glanceclient-dsvm-functional - vars: - devstack_localrc: - USE_PYTHON3: true - - project: templates: - check-requirements @@ -98,6 +91,3 @@ branches: master - glanceclient-tox-py3-oslo-tips: branches: master - experimental: - jobs: - - glanceclient-dsvm-functional-py3 From 2cab61228cc9f67676bc5bae24edaf6294a077dc Mon Sep 17 00:00:00 2001 From: Abhishek Kekane Date: Wed, 13 May 2026 06:59:57 +0000 Subject: [PATCH 160/167] Add support for GET /v2/cache/nodes/{image_id} This adds the ``cache-nodes-list`` shell command. Related blueprint list-nodes-image-cached Change-Id: I9f9d1112ee76439339a486ce7c5ca8142fc9aa4c Signed-off-by: Abhishek Kekane --- glanceclient/tests/unit/v2/test_cache.py | 43 ++++++++ glanceclient/tests/unit/v2/test_shell_v2.py | 100 ++++++++++++++++++ glanceclient/v2/cache.py | 7 ++ glanceclient/v2/shell.py | 33 ++++++ .../notes/cache-nodes-list-command.yaml | 7 ++ 5 files changed, 190 insertions(+) create mode 100644 releasenotes/notes/cache-nodes-list-command.yaml diff --git a/glanceclient/tests/unit/v2/test_cache.py b/glanceclient/tests/unit/v2/test_cache.py index a5a908f09..8fb27ad29 100644 --- a/glanceclient/tests/unit/v2/test_cache.py +++ b/glanceclient/tests/unit/v2/test_cache.py @@ -64,6 +64,12 @@ '', ), }, + '/v2/cache/nodes/3a4560a1-e585-443e-9b39-553b46ec92d1': { + 'GET': ( + {}, + ['http://node1.example', 'http://node2.example'], + ), + }, } @@ -133,3 +139,40 @@ def test_cache_not_supported(self, mock_has_version): mock_has_version.return_value = False self.assertRaises(exc.HTTPNotImplemented, self.controller.list) + + @mock.patch.object(common_utils, 'has_version') + def test_list_cached_nodes(self, mock_has_version): + mock_has_version.return_value = True + image_id = '3a4560a1-e585-443e-9b39-553b46ec92d1' + nodes = self.controller.list_cached_nodes(image_id) + self.assertEqual(['http://node1.example', 'http://node2.example'], + list(nodes)) + expect = [('GET', '/v2/cache/nodes/%s' % image_id, {}, None)] + self.assertEqual(expect, self.api.calls) + + @mock.patch.object(common_utils, 'has_version') + def test_list_cached_nodes_empty(self, mock_has_version): + mock_has_version.return_value = True + image_id = 'df601a47-7251-4d20-84ae-07de335af424' + dummy_fixtures = { + '/v2/cache/nodes/%s' % image_id: { + 'GET': ( + {}, + [], + ), + } + } + dummy_api = utils.FakeAPI(dummy_fixtures) + dummy_controller = cache.Controller(dummy_api) + nodes = dummy_controller.list_cached_nodes(image_id) + self.assertEqual([], list(nodes)) + self.assertEqual( + [('GET', '/v2/cache/nodes/%s' % image_id, {}, None)], + dummy_api.calls) + + @mock.patch.object(common_utils, 'has_version') + def test_list_cached_nodes_not_supported(self, mock_has_version): + mock_has_version.return_value = False + self.assertRaises(exc.HTTPNotImplemented, + self.controller.list_cached_nodes, + '3a4560a1-e585-443e-9b39-553b46ec92d1') diff --git a/glanceclient/tests/unit/v2/test_shell_v2.py b/glanceclient/tests/unit/v2/test_shell_v2.py index 1f088215a..e00e0df12 100644 --- a/glanceclient/tests/unit/v2/test_shell_v2.py +++ b/glanceclient/tests/unit/v2/test_shell_v2.py @@ -3799,6 +3799,106 @@ def test_do_cache_list_endpoint_not_provided(self): 'Direct server endpoint needs to be provided. Do ' 'not use loadbalanced or catalog endpoints.') + def test_do_cache_nodes_list(self): + args = argparse.Namespace( + id='3a4560a1-e585-443e-9b39-553b46ec92d1') + expected_nodes = ['http://node1.example', 'http://node2.example'] + with mock.patch.object( + self.gc.cache, 'list_cached_nodes') as mocked_list_nodes: + mocked_list_nodes.return_value = expected_nodes + test_shell.do_cache_nodes_list(self.gc, args) + mocked_list_nodes.assert_called_once_with(args.id) + objs, fields = utils.print_list.call_args[0] + self.assertEqual(['Node Reference URL'], fields) + self.assertEqual(2, len(objs)) + self.assertEqual('http://node1.example', objs[0].node_reference_url) + self.assertEqual('http://node2.example', objs[1].node_reference_url) + + def test_do_cache_nodes_list_empty(self): + args = argparse.Namespace( + id='3a4560a1-e585-443e-9b39-553b46ec92d1') + with mock.patch.object( + self.gc.cache, 'list_cached_nodes') as mocked_list_nodes: + mocked_list_nodes.return_value = [] + test_shell.do_cache_nodes_list(self.gc, args) + objs, fields = utils.print_list.call_args[0] + self.assertEqual(['Node Reference URL'], fields) + self.assertEqual(0, len(objs)) + + def test_do_cache_nodes_list_unsupported(self): + args = argparse.Namespace( + id='3a4560a1-e585-443e-9b39-553b46ec92d1') + with mock.patch.object( + self.gc.cache, 'list_cached_nodes') as mocked_list_nodes: + mocked_list_nodes.side_effect = exc.HTTPNotImplemented + self.assertRaises(exc.HTTPNotImplemented, + test_shell.do_cache_nodes_list, + self.gc, args) + + def test_do_cache_nodes_list_forbidden(self): + image_id = '3a4560a1-e585-443e-9b39-553b46ec92d1' + args = argparse.Namespace(id=image_id) + with mock.patch.object( + self.gc.cache, 'list_cached_nodes') as mocked_list_nodes: + mocked_list_nodes.side_effect = exc.HTTPForbidden + with mock.patch( + 'glanceclient.common.utils.print_err') as mock_print_err: + test_shell.do_cache_nodes_list(self.gc, args) + mock_print_err.assert_called_once_with( + "You are not permitted to list cached nodes for image '%s'." + % image_id) + + def test_do_cache_nodes_list_conflict(self): + image_id = '3a4560a1-e585-443e-9b39-553b46ec92d1' + args = argparse.Namespace(id=image_id) + with mock.patch.object( + self.gc.cache, 'list_cached_nodes') as mocked_list_nodes: + mocked_list_nodes.side_effect = exc.HTTPConflict + with mock.patch( + 'glanceclient.common.utils.print_err') as mock_print_err: + test_shell.do_cache_nodes_list(self.gc, args) + mock_print_err.assert_called_once_with( + "'%s': Unable to list cached nodes for image '%s'." + % (exc.HTTPConflict(), image_id)) + + def test_do_cache_nodes_list_not_found(self): + image_id = '3a4560a1-e585-443e-9b39-553b46ec92d1' + args = argparse.Namespace(id=image_id) + with mock.patch.object( + self.gc.cache, 'list_cached_nodes') as mocked_list_nodes: + mocked_list_nodes.side_effect = exc.HTTPNotFound + with mock.patch( + 'glanceclient.common.utils.print_err') as mock_print_err: + test_shell.do_cache_nodes_list(self.gc, args) + mock_print_err.assert_called_once_with( + "'%s': Unable to list cached nodes for image '%s'." + % (exc.HTTPNotFound(), image_id)) + + def test_do_cache_nodes_list_http_exception(self): + image_id = '3a4560a1-e585-443e-9b39-553b46ec92d1' + args = argparse.Namespace(id=image_id) + with mock.patch.object( + self.gc.cache, 'list_cached_nodes') as mocked_list_nodes: + mocked_list_nodes.side_effect = exc.HTTPBadRequest + with mock.patch( + 'glanceclient.common.utils.print_err') as mock_print_err: + test_shell.do_cache_nodes_list(self.gc, args) + mock_print_err.assert_called_once_with( + "'%s': Unable to list cached nodes for image '%s'." + % (exc.HTTPBadRequest(), image_id)) + + def test_do_cache_nodes_list_endpoint_not_provided(self): + args = argparse.Namespace( + id='3a4560a1-e585-443e-9b39-553b46ec92d1') + self.gc.endpoint_provided = False + with mock.patch('glanceclient.common.utils.exit') as mock_exit: + mock_exit.side_effect = self._mock_utils_exit + with self.assertRaises(SystemExit): + test_shell.do_cache_nodes_list(self.gc, args) + mock_exit.assert_called_once_with( + 'Direct server endpoint needs to be provided. Do ' + 'not use loadbalanced or catalog endpoints.') + def _test_cache_queue(self, supported=True, forbidden=False,): args = argparse.Namespace(id=['image1']) with mock.patch.object(self.gc.cache, 'queue') as mocked_cache_queue: diff --git a/glanceclient/v2/cache.py b/glanceclient/v2/cache.py index 7631c4abc..252d8326c 100644 --- a/glanceclient/v2/cache.py +++ b/glanceclient/v2/cache.py @@ -60,3 +60,10 @@ def queue(self, image_id): url = '/v2/cache/%s' % image_id resp, body = self.http_client.put(url) return body, resp + + @utils.add_req_id_to_object() + def list_cached_nodes(self, image_id): + if self.is_supported('v2.14'): + url = '/v2/cache/nodes/%s' % image_id + resp, body = self.http_client.get(url) + return body, resp diff --git a/glanceclient/v2/shell.py b/glanceclient/v2/shell.py index f3703cdeb..60137e6e4 100644 --- a/glanceclient/v2/shell.py +++ b/glanceclient/v2/shell.py @@ -16,6 +16,7 @@ import json import os import sys +from types import SimpleNamespace from oslo_utils import strutils @@ -1651,6 +1652,38 @@ def do_cache_list(gc, args): utils.print_cached_images(cached_images) +@utils.arg('id', metavar='', + help=_('ID of the image.')) +def do_cache_nodes_list(gc, args): + """List node reference URLs where an image is cached.""" + if not gc.endpoint_provided: + utils.exit("Direct server endpoint needs to be provided. Do not use " + "loadbalanced or catalog endpoints.") + try: + nodes = gc.cache.list_cached_nodes(args.id) + except exc.HTTPForbidden: + msg = _("You are not permitted to list cached nodes for image '%s'.") + utils.print_err(msg % args.id) + return + except exc.HTTPConflict as e: + msg = _("'%s': Unable to list cached nodes for image '%s'.") + utils.print_err(msg % (e, args.id)) + return + except exc.HTTPNotFound as e: + msg = _("'%s': Unable to list cached nodes for image '%s'.") + utils.print_err(msg % (e, args.id)) + return + except exc.HTTPNotImplemented: + raise + except exc.HTTPException as e: + msg = _("'%s': Unable to list cached nodes for image '%s'.") + utils.print_err(msg % (e, args.id)) + return + + rows = [SimpleNamespace(node_reference_url=u) for u in nodes] + utils.print_list(rows, ['Node Reference URL']) + + @utils.arg('id', metavar='', nargs='+', help=_('ID of image(s) to queue for caching.')) def do_cache_queue(gc, args): diff --git a/releasenotes/notes/cache-nodes-list-command.yaml b/releasenotes/notes/cache-nodes-list-command.yaml new file mode 100644 index 000000000..bca787f15 --- /dev/null +++ b/releasenotes/notes/cache-nodes-list-command.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Add support for the Glance image-cache API ``GET /v2/cache/nodes/{image_id}`` + (list node reference URLs where an image is cached when centralized caching + is enabled). This adds the ``cache-nodes-list`` shell command and the + ``list_cached_nodes`` method on the v2 cache controller. From a96909dada5a8dea24045b1aafbbf17ff4e723fd Mon Sep 17 00:00:00 2001 From: Abhishek Kekane Date: Thu, 25 Jun 2026 06:13:41 +0000 Subject: [PATCH 161/167] Add support for cache clean and prune APIs Add client and CLI support for Glance Image API v2.18 cache maintenance endpoints. Related-Blueprint: clean-prune-cache-api Assisted-By: Cursor (claude-4.5-sonnet) for tests and releasenote Change-Id: Ia9753e11705010e3e37ab103c880a8d7c94fb3df Signed-off-by: Abhishek Kekane --- glanceclient/tests/unit/v2/test_cache.py | 43 +++++++++ glanceclient/tests/unit/v2/test_shell_v2.py | 88 +++++++++++++++++++ glanceclient/v2/cache.py | 16 +++- glanceclient/v2/shell.py | 35 ++++++++ .../notes/cache-clean-prune-api-support.yaml | 14 +++ 5 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/cache-clean-prune-api-support.yaml diff --git a/glanceclient/tests/unit/v2/test_cache.py b/glanceclient/tests/unit/v2/test_cache.py index 8fb27ad29..3f4f3b922 100644 --- a/glanceclient/tests/unit/v2/test_cache.py +++ b/glanceclient/tests/unit/v2/test_cache.py @@ -70,6 +70,21 @@ ['http://node1.example', 'http://node2.example'], ), }, + '/v2/cache/clean': { + 'POST': ( + {}, + '', + ), + }, + '/v2/cache/prune': { + 'POST': ( + {}, + { + 'total_files_pruned': 5, + 'total_bytes_pruned': 104857600, + }, + ), + }, } @@ -176,3 +191,31 @@ def test_list_cached_nodes_not_supported(self, mock_has_version): self.assertRaises(exc.HTTPNotImplemented, self.controller.list_cached_nodes, '3a4560a1-e585-443e-9b39-553b46ec92d1') + + @mock.patch.object(common_utils, 'has_version') + def test_cache_clean(self, mock_has_version): + mock_has_version.return_value = True + self.controller.clean() + expect = [('POST', '/v2/cache/clean', {}, None)] + self.assertEqual(expect, self.api.calls) + + @mock.patch.object(common_utils, 'has_version') + def test_cache_prune(self, mock_has_version): + mock_has_version.return_value = True + result = self.controller.prune() + expect = [('POST', '/v2/cache/prune', {}, None)] + self.assertEqual(expect, self.api.calls) + self.assertEqual(5, result['total_files_pruned']) + self.assertEqual(104857600, result['total_bytes_pruned']) + + @mock.patch.object(common_utils, 'has_version') + def test_cache_clean_not_supported(self, mock_has_version): + mock_has_version.return_value = False + self.assertRaises(exc.HTTPNotImplemented, + self.controller.clean) + + @mock.patch.object(common_utils, 'has_version') + def test_cache_prune_not_supported(self, mock_has_version): + mock_has_version.return_value = False + self.assertRaises(exc.HTTPNotImplemented, + self.controller.prune) diff --git a/glanceclient/tests/unit/v2/test_shell_v2.py b/glanceclient/tests/unit/v2/test_shell_v2.py index e00e0df12..0c55c14ba 100644 --- a/glanceclient/tests/unit/v2/test_shell_v2.py +++ b/glanceclient/tests/unit/v2/test_shell_v2.py @@ -4031,3 +4031,91 @@ def test_do_cache_clear_endpoint_not_provided(self): mock_exit.assert_called_once_with( 'Direct server endpoint needs to be provided. Do ' 'not use loadbalanced or catalog endpoints.') + + def _test_cache_clean(self, supported=True, forbidden=False): + args = self._make_args({}) + with mock.patch.object(self.gc.cache, 'clean') as mocked_cache_clean: + if supported: + mocked_cache_clean.return_value = None + else: + mocked_cache_clean.side_effect = exc.HTTPNotImplemented + if forbidden: + mocked_cache_clean.side_effect = exc.HTTPForbidden + + test_shell.do_cache_clean(self.gc, args) + if supported: + mocked_cache_clean.assert_called_once_with() + + def test_do_cache_clean(self): + self._test_cache_clean() + + def test_do_cache_clean_unsupported(self): + with mock.patch( + 'glanceclient.common.utils.print_err') as mock_print_err: + self._test_cache_clean(supported=False) + mock_print_err.assert_called_once_with( + "'HTTP HTTPNotImplemented': Unable to clean the image cache.") + + def test_do_cache_clean_forbidden(self): + with mock.patch( + 'glanceclient.common.utils.print_err') as mock_print_err: + self._test_cache_clean(forbidden=True) + mock_print_err.assert_called_once_with( + "You are not permitted to clean the image cache.") + + def test_do_cache_clean_endpoint_not_provided(self): + args = self._make_args({}) + self.gc.endpoint_provided = False + with mock.patch('glanceclient.common.utils.exit') as mock_exit: + test_shell.do_cache_clean(self.gc, args) + mock_exit.assert_called_once_with( + 'Direct server endpoint needs to be provided. Do ' + 'not use loadbalanced or catalog endpoints.') + + def _test_cache_prune(self, supported=True, forbidden=False): + args = self._make_args({}) + with mock.patch.object(self.gc.cache, 'prune') as mocked_cache_prune: + if supported: + mocked_cache_prune.return_value = { + 'total_files_pruned': 5, + 'total_bytes_pruned': 104857600, + } + else: + mocked_cache_prune.side_effect = exc.HTTPNotImplemented + if forbidden: + mocked_cache_prune.side_effect = exc.HTTPForbidden + + with mock.patch('builtins.print') as mock_print: + test_shell.do_cache_prune(self.gc, args) + if supported and not forbidden: + mocked_cache_prune.assert_called_once_with() + mock_print.assert_called_once_with( + 'Pruned 5 file(s), 104857600 byte(s).') + + def test_do_cache_prune(self): + self._test_cache_prune() + + def test_do_cache_prune_unsupported(self): + with mock.patch( + 'glanceclient.common.utils.print_err') as mock_print_err: + self._test_cache_prune(supported=False) + mock_print_err.assert_called_once_with( + "'HTTP HTTPNotImplemented': Unable to prune the image cache.") + + def test_do_cache_prune_forbidden(self): + with mock.patch( + 'glanceclient.common.utils.print_err') as mock_print_err: + self._test_cache_prune(forbidden=True) + mock_print_err.assert_called_once_with( + "You are not permitted to prune the image cache.") + + def test_do_cache_prune_endpoint_not_provided(self): + args = self._make_args({}) + self.gc.endpoint_provided = False + with mock.patch('glanceclient.common.utils.exit') as mock_exit: + mock_exit.side_effect = SystemExit + self.assertRaises(SystemExit, + test_shell.do_cache_prune, self.gc, args) + mock_exit.assert_called_once_with( + 'Direct server endpoint needs to be provided. Do not use ' + 'loadbalanced or catalog endpoints.') diff --git a/glanceclient/v2/cache.py b/glanceclient/v2/cache.py index 252d8326c..bff04a2af 100644 --- a/glanceclient/v2/cache.py +++ b/glanceclient/v2/cache.py @@ -28,7 +28,7 @@ def is_supported(self, version): return True else: raise exc.HTTPNotImplemented( - 'Glance does not support image caching API (v2.14)') + 'Glance does not support image caching API (%s)' % version) @utils.add_req_id_to_object() def list(self): @@ -67,3 +67,17 @@ def list_cached_nodes(self, image_id): url = '/v2/cache/nodes/%s' % image_id resp, body = self.http_client.get(url) return body, resp + + @utils.add_req_id_to_object() + def clean(self): + if self.is_supported('v2.18'): + url = '/v2/cache/clean' + resp, body = self.http_client.post(url) + return body, resp + + @utils.add_req_id_to_object() + def prune(self): + if self.is_supported('v2.18'): + url = '/v2/cache/prune' + resp, body = self.http_client.post(url) + return body, resp diff --git a/glanceclient/v2/shell.py b/glanceclient/v2/shell.py index 60137e6e4..41175c9dc 100644 --- a/glanceclient/v2/shell.py +++ b/glanceclient/v2/shell.py @@ -1643,6 +1643,41 @@ def do_cache_delete(gc, args): utils.print_err(msg) +def do_cache_clean(gc, args): + """Clean invalid and stalled cached images.""" + if not gc.endpoint_provided: + utils.exit("Direct server endpoint needs to be provided. Do not use " + "loadbalanced or catalog endpoints.") + try: + gc.cache.clean() + except exc.HTTPForbidden: + msg = _("You are not permitted to clean the image cache.") + utils.print_err(msg) + except exc.HTTPException as e: + msg = _("'%s': Unable to clean the image cache." % e) + utils.print_err(msg) + + +def do_cache_prune(gc, args): + """Prune cached images to reduce cache size.""" + if not gc.endpoint_provided: + utils.exit("Direct server endpoint needs to be provided. Do not use " + "loadbalanced or catalog endpoints.") + try: + result = gc.cache.prune() + if result: + print(_("Pruned %(files)d file(s), %(bytes)d byte(s).") % { + 'files': result.get('total_files_pruned', 0), + 'bytes': result.get('total_bytes_pruned', 0), + }) + except exc.HTTPForbidden: + msg = _("You are not permitted to prune the image cache.") + utils.print_err(msg) + except exc.HTTPException as e: + msg = _("'%s': Unable to prune the image cache." % e) + utils.print_err(msg) + + def do_cache_list(gc, args): """Get cache state.""" if not gc.endpoint_provided: diff --git a/releasenotes/notes/cache-clean-prune-api-support.yaml b/releasenotes/notes/cache-clean-prune-api-support.yaml new file mode 100644 index 000000000..4e4d7932d --- /dev/null +++ b/releasenotes/notes/cache-clean-prune-api-support.yaml @@ -0,0 +1,14 @@ +--- +features: + - | + Client support has been added for the Glance Image API v2.18 cache + maintenance endpoints. The following commands have been added to the + command line interface: + + * ``cache-clean`` - Clean invalid cache entries and stalled incomplete + images + * ``cache-prune`` - Prune cached images when the cache size exceeds the + maximum configured size + + These commands require a direct glance-api server endpoint and are only + available when image caching is enabled on the Glance service. From 7c6b4e8d5529b747f7d4b1fcfba62991a4e6ab42 Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Wed, 16 Apr 2025 22:12:35 +0900 Subject: [PATCH 162/167] Apply upper constraints to build documentation ... to avoid problems caused by the latest libraries. Change-Id: I85fbc375078155131877621a2fbfa8ffbc63d8c1 Signed-off-by: Takashi Kajinami --- tox.ini | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tox.ini b/tox.ini index d6d6916c2..16941f234 100644 --- a/tox.ini +++ b/tox.ini @@ -53,7 +53,12 @@ commands = [testenv:docs] basepython = python3 +# Nowadays, TOX_CONSTRAINTS_FILE should be used, but some older scripts might +# still be using UPPER_CONSTRAINTS_FILE, so we check both variables and use the +# first one that is defined. If none of them is defined, we fallback to the +# default value. deps = + -c{env:TOX_CONSTRAINTS_FILE:{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}} -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = From 7ba57fef1d07aa22a976f2e8daeec33c64b30fe2 Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Wed, 16 Apr 2025 22:14:33 +0900 Subject: [PATCH 163/167] Drop fallback to UPPER_CONSTRAINTS_FILE UPPER_CONSTRAINTS_FILE was replaced by TOX_CONSTRAINTS_FILE some time ago and is not at all used in CI now. Change-Id: I78a611dea101f69c017b45a8cb4a175164ddab62 Signed-off-by: Takashi Kajinami --- tox.ini | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/tox.ini b/tox.ini index 16941f234..854743f43 100644 --- a/tox.ini +++ b/tox.ini @@ -7,15 +7,10 @@ usedevelop = True setenv = OS_STDOUT_NOCAPTURE=False OS_STDERR_NOCAPTURE=False PYTHONDONTWRITEBYTECODE=1 - -# Nowadays, TOX_CONSTRAINTS_FILE should be used, but some older scripts might -# still be using UPPER_CONSTRAINTS_FILE, so we check both variables and use the -# first one that is defined. If none of them is defined, we fallback to the -# default value. deps = - -c{env:TOX_CONSTRAINTS_FILE:{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}} - -r{toxinidir}/requirements.txt - -r{toxinidir}/test-requirements.txt + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt commands = stestr run --slowest {posargs} [testenv:pep8] @@ -53,12 +48,8 @@ commands = [testenv:docs] basepython = python3 -# Nowadays, TOX_CONSTRAINTS_FILE should be used, but some older scripts might -# still be using UPPER_CONSTRAINTS_FILE, so we check both variables and use the -# first one that is defined. If none of them is defined, we fallback to the -# default value. deps = - -c{env:TOX_CONSTRAINTS_FILE:{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}} + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = @@ -67,12 +58,8 @@ commands = [testenv:releasenotes] basepython = python3 -# Nowadays, TOX_CONSTRAINTS_FILE should be used, but some older scripts might -# still be using UPPER_CONSTRAINTS_FILE, so we check both variables and use the -# first one that is defined. If none of them is defined, we fallback to the -# default value. deps = - -c{env:TOX_CONSTRAINTS_FILE:{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}} + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/doc/requirements.txt commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html From e7115aaf9273cbbf8561f4d754c7ededf2a1d9fa Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Tue, 17 Jun 2025 00:27:53 +0900 Subject: [PATCH 164/167] Replace deprecated datetime.utcfromtimestamp It was deprecated in Python 3.12 due to datetime.fromtimestamp[1]. [1] https://docs.python.org/3/library/datetime.html#datetime.datetime.utcfromtimestamp Change-Id: Idff8617b02e9b42ac77897dfe33ff73b2531b5b1 Signed-off-by: Takashi Kajinami --- glanceclient/common/utils.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/glanceclient/common/utils.py b/glanceclient/common/utils.py index 8d2b8006d..a5f1720d8 100644 --- a/glanceclient/common/utils.py +++ b/glanceclient/common/utils.py @@ -208,15 +208,18 @@ def print_cached_images(cached_images): if last_accessed == 0: last_accessed = "N/A" else: - last_accessed = datetime.datetime.utcfromtimestamp( - last_accessed).isoformat() - - cache_pt.add_row((image['image_id'], state, - last_accessed, - datetime.datetime.utcfromtimestamp( - image['last_modified']).isoformat(), - image['size'], - image['hits'])) + last_accessed = datetime.datetime.fromtimestamp( + last_accessed, tz=datetime.timezone.utc + ).replace(tzinfo=None).isoformat() + + cache_pt.add_row(( + image['image_id'], state, + last_accessed, + datetime.datetime.fromtimestamp( + image['last_modified'], tz=datetime.timezone.utc + ).replace(tzinfo=None).isoformat(), + image['size'], + image['hits'])) else: for image in cached_images[item]: cache_pt.add_row((image, From 7132ff8f77946e25a900ac1b7b9a62b62c95a2d3 Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Mon, 13 Oct 2025 03:01:50 +0900 Subject: [PATCH 165/167] Fix outdated default envlist Python 3.9 is no longer supported so can't be tested now. Change-Id: Ic0800e644f67998b1a86fb5cd3a1cca12079ecc3 Signed-off-by: Takashi Kajinami --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index d6d6916c2..f3b061503 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py39,pep8 +envlist = py3,pep8 minversion = 3.18.0 [testenv] From 50595eea02a2727b45f870f8fe8304c207c0d049 Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Sun, 19 Apr 2026 02:54:11 +0900 Subject: [PATCH 166/167] Add pyproject.toml to support pip 23.1 pip 23.1 removed the "setup.py install" fallback for projects that do not have pyproject.toml and now uses a pyproject.toml which is vendored in pip. To address that, this change adds the minimal pyproject.toml to enable pbr to be properly used to build editable wheels. This is required to support installing devstack on centos stream 9 and related distros with GLOBAL_VENV=True Without this change the wsgi scripts are not generated in editable mode. i.e. pip install -e /opt/stack/keystone See https://pip.pypa.io/en/stable/news/#v23-1 and https://github.com/pypa/pip/issues/8368 for more details on the removal of the fallback support. setuptools v64.0.0 is used to support editable installs via its PEP-660 implmentation https://github.com/pypa/setuptools/pull/3488 Co-Authored-By: Sean Mooney Change-Id: Id8e9d66ac026bc359287b997c5c49fbb857667d6 Signed-off-by: Takashi Kajinami --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..2a38d6bc7 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["pbr>=6.1.1"] +build-backend = "pbr.build" From bb9d499ee5690cda9e53196b78f66d180d17072b Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Wed, 22 Jul 2026 12:05:04 +0900 Subject: [PATCH 167/167] Remove Python 3.10 support Python 3.10 is no longer part of the tested runtimes. Change-Id: Ibe01332a495aef9ef5d04e76b61c606f815bc1b3 Signed-off-by: Takashi Kajinami --- releasenotes/notes/remove-py310-5ee8bf147676093b.yaml | 5 +++++ setup.cfg | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/remove-py310-5ee8bf147676093b.yaml diff --git a/releasenotes/notes/remove-py310-5ee8bf147676093b.yaml b/releasenotes/notes/remove-py310-5ee8bf147676093b.yaml new file mode 100644 index 000000000..c4f8b66de --- /dev/null +++ b/releasenotes/notes/remove-py310-5ee8bf147676093b.yaml @@ -0,0 +1,5 @@ +--- +upgrade: + - | + Support for Python 3.10 has been removed. Now the minimum python version + supported is 3.11. diff --git a/setup.cfg b/setup.cfg index 3fab8bbea..32110b525 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,7 +7,7 @@ license = Apache License, Version 2.0 author = OpenStack author_email = openstack-discuss@lists.openstack.org home_page = https://docs.openstack.org/python-glanceclient/latest/ -python_requires = >=3.10 +python_requires = >=3.11 classifier = Development Status :: 5 - Production/Stable Environment :: Console @@ -20,9 +20,9 @@ classifier = Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3 - Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 + Programming Language :: Python :: 3.13 [files] packages =