From 7f282a5ea46cb46a306d16cc352c3beddf7c129e Mon Sep 17 00:00:00 2001 From: Eric Harney Date: Fri, 18 Oct 2019 12:08:00 -0400 Subject: [PATCH 001/159] Hide cinder CLI errors on bash-completion On my environment, the cinderclient CLI reports: /usr/lib/python2.7/site-packages/requests/__init__.py:91: RequestsDependencyWarning: urllib3 (1.25.6) or chardet (2.2.1) doesn't match a supported version! When running any command. This is fairly irritating to have pop up in the context of cinder bash-completion calls, i.e., when you hit tab after "cinder" at the shell. Just hide errors there rather than pollute the screen with them. Change-Id: I40019dcc845015de8cfe8165656829cdaa446666 --- tools/cinder.bash_completion | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/cinder.bash_completion b/tools/cinder.bash_completion index 4dbc7795a..1d981089b 100644 --- a/tools/cinder.bash_completion +++ b/tools/cinder.bash_completion @@ -10,7 +10,7 @@ _cinder() prev="${COMP_WORDS[COMP_CWORD-1]}" if [ "x$_cinder_opts" == "x" ] ; then - cbc="`cinder bash-completion | sed -e "s/ *-h */ /" -e "s/ *-i */ /"`" + cbc="`cinder bash-completion 2>/dev/null | sed -e "s/ *-h */ /" -e "s/ *-i */ /"`" _cinder_opts="`echo "$cbc" | sed -e "s/--[a-z0-9_-]*//g" -e "s/ */ /g"`" _cinder_flags="`echo " $cbc" | sed -e "s/ [^-][^-][a-z0-9_-]*//g" -e "s/ */ /g"`" fi From e2436b310f294992857f22a6d52ede03e36b99b1 Mon Sep 17 00:00:00 2001 From: jacky06 Date: Wed, 6 Mar 2019 22:34:42 +0800 Subject: [PATCH 002/159] Update hacking version Use latest release 1.1.0 and compatible changes w.r.t pep8 Change-Id: I1ae708f0753249226ceb47610a1a4d0b558c1d0e --- cinderclient/tests/unit/v3/test_shell.py | 4 ++-- cinderclient/v3/shell.py | 1 + doc/source/user/shell.rst | 2 +- test-requirements.txt | 3 ++- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/cinderclient/tests/unit/v3/test_shell.py b/cinderclient/tests/unit/v3/test_shell.py index 8969cabf9..98d4dd1a6 100644 --- a/cinderclient/tests/unit/v3/test_shell.py +++ b/cinderclient/tests/unit/v3/test_shell.py @@ -1068,8 +1068,8 @@ def test_group_disable_replication(self): def test_group_failover_replication(self, attach_vol, backend): attach = '--allow-attached-volume ' if attach_vol else '' backend_id = ('--secondary-backend-id ' + backend) if backend else '' - cmd = ('--os-volume-api-version 3.38 group-failover-replication 1234 ' - + attach + backend_id) + cmd = ('--os-volume-api-version 3.38 ' + 'group-failover-replication 1234 ' + attach + backend_id) self.run_command(cmd) expected = {'failover_replication': {'allow_attached_volume': attach_vol, diff --git a/cinderclient/v3/shell.py b/cinderclient/v3/shell.py index b076804d4..51e3f76be 100644 --- a/cinderclient/v3/shell.py +++ b/cinderclient/v3/shell.py @@ -31,6 +31,7 @@ from cinderclient import utils from cinderclient.v2.shell import * # noqa +from cinderclient.v2.shell import CheckSizeArgForCreate FILTER_DEPRECATED = ("This option is deprecated and will be removed in " "newer release. Please use '--filters' option which " diff --git a/doc/source/user/shell.rst b/doc/source/user/shell.rst index a06fbff3e..7d478cf4f 100644 --- a/doc/source/user/shell.rst +++ b/doc/source/user/shell.rst @@ -1,5 +1,5 @@ The :program:`cinder` shell utility -========================================= +=================================== .. program:: cinder .. highlight:: bash diff --git a/test-requirements.txt b/test-requirements.txt index a62a30869..662a346b6 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -2,7 +2,8 @@ # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. # Hacking already pins down pep8, pyflakes and flake8 -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 ddt>=1.0.1 # MIT fixtures>=3.0.0 # Apache-2.0/BSD From a7e9a49a3f851097b323093048c62b2acbbf5090 Mon Sep 17 00:00:00 2001 From: Eric Harney Date: Tue, 12 Nov 2019 15:54:07 -0500 Subject: [PATCH 003/159] Add test for subcommands This tests that the expected subcommands register in the shell client, by looking at the output that would show up in "cinder help". The purpose of this is to help prevent us from accidentally deleting some commands when refactoring shell code. TODO: cover commands post-3.0 Change-Id: Ifcbc08ae9184fa33049b18f8ad7ef5d92003a7b8 --- cinderclient/shell.py | 1 + cinderclient/tests/unit/v3/test_shell.py | 118 +++++++++++++++++++++++ 2 files changed, 119 insertions(+) diff --git a/cinderclient/shell.py b/cinderclient/shell.py index d948332a9..57c9d8ccb 100644 --- a/cinderclient/shell.py +++ b/cinderclient/shell.py @@ -135,6 +135,7 @@ class OpenStackCinderShell(object): def __init__(self): self.ks_logger = None self.client_logger = None + self.extensions = [] def get_base_parser(self): parser = CinderClientArgumentParser( diff --git a/cinderclient/tests/unit/v3/test_shell.py b/cinderclient/tests/unit/v3/test_shell.py index ee35ddfd9..4dd6c38f0 100644 --- a/cinderclient/tests/unit/v3/test_shell.py +++ b/cinderclient/tests/unit/v3/test_shell.py @@ -1443,3 +1443,121 @@ def test_list_transfer_sorty_not_sorty(self): '--os-volume-api-version 3.59 transfer-list') url = ('/volume-transfers/detail') self.assert_called('GET', url) + + def test_subcommand_parser(self): + """Ensure that all the expected commands show up. + + This test ensures that refactoring code does not somehow result in + a command accidentally ceasing to exist. + + TODO: add a similar test for 3.59 or so + """ + p = self.shell.get_subcommand_parser(api_versions.APIVersion("3.0"), + input_args=['help'], do_help=True) + help_text = p.format_help() + + # These are v3.0 commands only + expected_commands = ('absolute-limits', + 'api-version', + 'availability-zone-list', + 'backup-create', + 'backup-delete', + 'backup-export', + 'backup-import', + 'backup-list', + 'backup-reset-state', + 'backup-restore', + 'backup-show', + 'cgsnapshot-create', + 'cgsnapshot-delete', + 'cgsnapshot-list', + 'cgsnapshot-show', + 'consisgroup-create', + 'consisgroup-create-from-src', + 'consisgroup-delete', + 'consisgroup-list', + 'consisgroup-show', + 'consisgroup-update', + 'create', + 'delete', + 'encryption-type-create', + 'encryption-type-delete', + 'encryption-type-list', + 'encryption-type-show', + 'encryption-type-update', + 'extend', + 'extra-specs-list', + 'failover-host', + 'force-delete', + 'freeze-host', + 'get-capabilities', + 'get-pools', + 'image-metadata', + 'image-metadata-show', + 'list', + 'manage', + 'metadata', + 'metadata-show', + 'metadata-update-all', + 'migrate', + 'qos-associate', + 'qos-create', + 'qos-delete', + 'qos-disassociate', + 'qos-disassociate-all', + 'qos-get-association', + 'qos-key', + 'qos-list', + 'qos-show', + 'quota-class-show', + 'quota-class-update', + 'quota-defaults', + 'quota-delete', + 'quota-show', + 'quota-update', + 'quota-usage', + 'rate-limits', + 'readonly-mode-update', + 'rename', + 'reset-state', + 'retype', + 'service-disable', + 'service-enable', + 'service-list', + 'set-bootable', + 'show', + 'snapshot-create', + 'snapshot-delete', + 'snapshot-list', + 'snapshot-manage', + 'snapshot-metadata', + 'snapshot-metadata-show', + 'snapshot-metadata-update-all', + 'snapshot-rename', + 'snapshot-reset-state', + 'snapshot-show', + 'snapshot-unmanage', + 'thaw-host', + 'transfer-accept', + 'transfer-create', + 'transfer-delete', + 'transfer-list', + 'transfer-show', + 'type-access-add', + 'type-access-list', + 'type-access-remove', + 'type-create', + 'type-default', + 'type-delete', + 'type-key', + 'type-list', + 'type-show', + 'type-update', + 'unmanage', + 'upload-to-image', + 'version-list', + 'bash-completion', + 'help',) + + for e in expected_commands: + self.assertIn(' ' + e, help_text) From ceddb3cfd033661f63d1245a45e0e197561be19c Mon Sep 17 00:00:00 2001 From: whoami-rajat Date: Thu, 5 Dec 2019 05:18:38 +0000 Subject: [PATCH 004/159] Fix: --poll inconsistency When we use `--poll` parameter with cinder create command, it waits for the volume to become available but doesn't update the data displayed to the user. Due to this, there are inconsistency between several parameters in the output after 'poll' and 'cinder show' command. Eg: cinder create 1 --image --poll shows 'bootable' flag as false whereas, cinder show shows 'bootable' as true Change-Id: I1502e88f1cd84d225b75c07313e4eb252cc2d645 Closes-Bug: #1855224 --- cinderclient/tests/unit/v3/test_shell.py | 1 - cinderclient/v3/shell.py | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cinderclient/tests/unit/v3/test_shell.py b/cinderclient/tests/unit/v3/test_shell.py index ee35ddfd9..f76a4847e 100644 --- a/cinderclient/tests/unit/v3/test_shell.py +++ b/cinderclient/tests/unit/v3/test_shell.py @@ -1165,7 +1165,6 @@ def test_create_with_poll(self, poll_method): volume = self.shell.cs.volumes.get('1234') info = dict() info.update(volume._info) - info.pop('links', None) self.assertEqual(1, poll_method.call_count) timeout_period = 3600 poll_method.assert_has_calls([mock.call(self.shell.cs.volumes.get, diff --git a/cinderclient/v3/shell.py b/cinderclient/v3/shell.py index fa1cc9c0f..fcb9e4a04 100644 --- a/cinderclient/v3/shell.py +++ b/cinderclient/v3/shell.py @@ -675,6 +675,8 @@ def do_create(cs, args): shell_utils._poll_for_status( cs.volumes.get, volume.id, info, 'creating', ['available'], timeout_period, cs.client.global_request_id, cs.messages) + volume = cs.volumes.get(volume.id) + info.update(volume._info) utils.print_dict(info) From 8f12b620a0002325b8f90fa8c2cfe267543443df Mon Sep 17 00:00:00 2001 From: Armstrong Liu Date: Fri, 13 Dec 2019 11:43:23 +0800 Subject: [PATCH 005/159] Update revert_to_snapshot params In revert_to_snapshot method, snapshot param must be a object and also has 'id' attribute, so before use the method, we have to use snapshot.get method to get snapshot information. But revert_to_snaoshot only use id of snapshot, we can use snapshot instead of snapshot.id. Change-Id: Ifbdbae3ee66d72f9d34cf4a8fdf2bde388b2b6f0 --- cinderclient/v3/volumes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cinderclient/v3/volumes.py b/cinderclient/v3/volumes.py index 7c161bc60..fac5effce 100644 --- a/cinderclient/v3/volumes.py +++ b/cinderclient/v3/volumes.py @@ -129,11 +129,11 @@ def revert_to_snapshot(self, volume, snapshot): """Revert a volume to a snapshot. The snapshot must be the most recent one known to cinder. - :param volume: volume object. - :param snapshot: snapshot object. + :param volume: volume object or volume id. + :param snapshot: snapshot object or snapshot id. """ return self._action('revert', volume, - info={'snapshot_id': base.getid(snapshot.id)}) + info={'snapshot_id': base.getid(snapshot)}) @api_versions.wraps('3.12') def summary(self, all_tenants): From ced267b1d5d92237f456a5e8907534ded5324d28 Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Sat, 21 Dec 2019 03:27:48 -0600 Subject: [PATCH 006/159] Raise hacking version to 2.0.0 We've kept hacking capped for a long time now. This raises the hacking package version to the latest release and fixes the issues that it found. Change-Id: I69e41a340c815090f25677607e971a8e75791f6d Signed-off-by: Sean McGinnis --- cinderclient/_i18n.py | 2 +- cinderclient/client.py | 6 +- cinderclient/tests/unit/test_shell.py | 16 ++--- cinderclient/tests/unit/v2/test_limits.py | 85 ++++++++++++----------- cinderclient/v3/shell.py | 10 +-- lower-constraints.txt | 2 +- test-requirements.txt | 2 +- tools/install_venv.py | 1 + tox.ini | 2 +- 9 files changed, 63 insertions(+), 63 deletions(-) diff --git a/cinderclient/_i18n.py b/cinderclient/_i18n.py index 96c924655..9a38e5568 100644 --- a/cinderclient/_i18n.py +++ b/cinderclient/_i18n.py @@ -37,7 +37,7 @@ def get_available_languages(): - return oslo_i18n.get_available_languages(DOMAIN) + return oslo_i18n.get_available_languages(DOMAIN) def enable_lazy(): diff --git a/cinderclient/client.py b/cinderclient/client.py index 57dc52316..fb84861f1 100644 --- a/cinderclient/client.py +++ b/cinderclient/client.py @@ -14,9 +14,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -""" -OpenStack Client interface. Handles the REST calls and responses. -""" +"""OpenStack Client interface. Handles the REST calls and responses.""" from __future__ import print_function @@ -429,7 +427,7 @@ def _cs_request(self, url, method, **kwargs): url = self.management_url + url resp, body = self.request(url, method, **kwargs) return resp, body - except exceptions.BadRequest as e: + except exceptions.BadRequest: if attempts > self.retries: raise except exceptions.Unauthorized: diff --git a/cinderclient/tests/unit/test_shell.py b/cinderclient/tests/unit/test_shell.py index 305a37ce6..c82c1b9db 100644 --- a/cinderclient/tests/unit/test_shell.py +++ b/cinderclient/tests/unit/test_shell.py @@ -118,10 +118,10 @@ def test_help_unknown_command(self): def test_help(self): # Some expected help output, including microversioned commands required = [ - '.*?^usage: ', - '.*?(?m)^\s+create\s+Creates a volume.', - '.*?(?m)^\s+summary\s+Get volumes summary.', - '.*?(?m)^Run "cinder help SUBCOMMAND" for help on a subcommand.', + r'.*?^usage: ', + r'.*?(?m)^\s+create\s+Creates a volume.', + r'.*?(?m)^\s+summary\s+Get volumes summary.', + r'.*?(?m)^Run "cinder help SUBCOMMAND" for help on a subcommand.', ] help_text = self.shell('help') for r in required: @@ -130,8 +130,8 @@ def test_help(self): def test_help_on_subcommand(self): required = [ - '.*?^usage: cinder list', - '.*?(?m)^Lists all volumes.', + r'.*?^usage: cinder list', + r'.*?(?m)^Lists all volumes.', ] help_text = self.shell('help list') for r in required: @@ -140,8 +140,8 @@ def test_help_on_subcommand(self): def test_help_on_subcommand_mv(self): required = [ - '.*?^usage: cinder summary', - '.*?(?m)^Get volumes summary.', + r'.*?^usage: cinder summary', + r'.*?(?m)^Get volumes summary.', ] help_text = self.shell('help summary') for r in required: diff --git a/cinderclient/tests/unit/v2/test_limits.py b/cinderclient/tests/unit/v2/test_limits.py index 1bc900f54..b1732e58b 100644 --- a/cinderclient/tests/unit/v2/test_limits.py +++ b/cinderclient/tests/unit/v2/test_limits.py @@ -33,60 +33,61 @@ def _get_default_RateLimit(verb="verb1", uri="uri1", regex="regex1", class TestLimits(utils.TestCase): def test_repr(self): - l = limits.Limits(None, {"foo": "bar"}, resp=REQUEST_ID) - self.assertEqual("", repr(l)) - self._assert_request_id(l) + limit = limits.Limits(None, {"foo": "bar"}, resp=REQUEST_ID) + self.assertEqual("", repr(limit)) + self._assert_request_id(limit) def test_absolute(self): - l = limits.Limits(None, - {"absolute": {"name1": "value1", "name2": "value2"}}, - resp=REQUEST_ID) + limit = limits.Limits( + None, + {"absolute": {"name1": "value1", "name2": "value2"}}, + resp=REQUEST_ID) l1 = limits.AbsoluteLimit("name1", "value1") l2 = limits.AbsoluteLimit("name2", "value2") - for item in l.absolute: + for item in limit.absolute: self.assertIn(item, [l1, l2]) - self._assert_request_id(l) + self._assert_request_id(limit) def test_rate(self): - l = limits.Limits(None, - { - "rate": [ - { - "uri": "uri1", - "regex": "regex1", - "limit": [ - { - "verb": "verb1", - "value": "value1", - "remaining": "remain1", - "unit": "unit1", - "next-available": "next1", - }, - ], - }, - { - "uri": "uri2", - "regex": "regex2", - "limit": [ - { - "verb": "verb2", - "value": "value2", - "remaining": "remain2", - "unit": "unit2", - "next-available": "next2", - }, - ], - }, - ], - }, - resp=REQUEST_ID) + limit = limits.Limits( + None, + { + "rate": [ + { + "uri": "uri1", + "regex": "regex1", + "limit": [ + { + "verb": "verb1", + "value": "value1", + "remaining": "remain1", + "unit": "unit1", + "next-available": "next1", + }, + ], + }, + { + "uri": "uri2", + "regex": "regex2", + "limit": [ + { + "verb": "verb2", + "value": "value2", + "remaining": "remain2", + "unit": "unit2", + "next-available": "next2", + }, + ], + }, ], + }, + resp=REQUEST_ID) l1 = limits.RateLimit("verb1", "uri1", "regex1", "value1", "remain1", "unit1", "next1") l2 = limits.RateLimit("verb2", "uri2", "regex2", "value2", "remain2", "unit2", "next2") - for item in l.rate: + for item in limit.rate: self.assertIn(item, [l1, l2]) - self._assert_request_id(l) + self._assert_request_id(limit) class TestRateLimit(utils.TestCase): diff --git a/cinderclient/v3/shell.py b/cinderclient/v3/shell.py index 85feca5c0..ddedcc03e 100644 --- a/cinderclient/v3/shell.py +++ b/cinderclient/v3/shell.py @@ -52,12 +52,12 @@ def __call__(self, parser, namespace, values, option_string): default=None, help='Show enabled filters for specified resource. Default=None.') def do_list_filters(cs, args): - """List enabled filters. + """List enabled filters. - Symbol '~' after filter key means it supports inexact filtering. - """ - filters = cs.resource_filters.list(resource=args.resource) - shell_utils.print_resource_filter_list(filters) + Symbol '~' after filter key means it supports inexact filtering. + """ + filters = cs.resource_filters.list(resource=args.resource) + shell_utils.print_resource_filter_list(filters) @utils.arg('--filters', diff --git a/lower-constraints.txt b/lower-constraints.txt index 73d8a64c1..9e44ad897 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -12,7 +12,7 @@ fasteners==0.7.0 fixtures==3.0.0 flake8==2.5.5 future==0.16.0 -hacking==0.12.0 +hacking==2.0.0 idna==2.6 iso8601==0.1.11 jsonschema==2.6.0 diff --git a/test-requirements.txt b/test-requirements.txt index 662a346b6..562d2cd6c 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -3,7 +3,7 @@ # process, which may cause wedges in the gate later. # Hacking already pins down pep8, pyflakes and flake8 -hacking>=1.1.0,<1.2.0 # Apache-2.0 +hacking>=2.0.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 ddt>=1.0.1 # MIT fixtures>=3.0.0 # Apache-2.0/BSD diff --git a/tools/install_venv.py b/tools/install_venv.py index f10293ba8..03fe5afa6 100644 --- a/tools/install_venv.py +++ b/tools/install_venv.py @@ -71,5 +71,6 @@ def main(argv): install.install_dependencies() print_help(project, venv, root) + if __name__ == '__main__': main(sys.argv) diff --git a/tox.ini b/tox.ini index bfc523f6d..f3e745ec9 100644 --- a/tox.ini +++ b/tox.ini @@ -94,7 +94,7 @@ passenv = OS_* [flake8] show-source = True -ignore = H404,H405,E122,E123,E128,E251 +ignore = H404,H405,E122,E123,E128,E251,W504 exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build [testenv:lower-constraints] From 658de38c20e028898fe0641de2e18686703f7b70 Mon Sep 17 00:00:00 2001 From: xuanyandong Date: Sat, 26 Oct 2019 14:31:46 +0800 Subject: [PATCH 007/159] Drop support for python 2 Also adds support for py3.6 and py3.7 check and gate jobs. Co-authored-by: xuanyandong Co-authored-by: Brian Rosmaita Closes-bug: #1853372 Change-Id: Ia978b692ade23ee6482957f41b17cb879c96fea7 --- .zuul.yaml | 39 ++++++++++++++++--- doc/requirements.txt | 3 +- playbooks/python-cinderclient-functional.yaml | 2 +- ...drop-python2-support-d3a1bedc75445edc.yaml | 7 ++++ setup.cfg | 2 - tox.ini | 31 +++++++++------ 6 files changed, 63 insertions(+), 21 deletions(-) create mode 100644 releasenotes/notes/drop-python2-support-d3a1bedc75445edc.yaml diff --git a/.zuul.yaml b/.zuul.yaml index 594163a97..351b922c7 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -1,5 +1,6 @@ - job: - name: python-cinderclient-functional + name: python-cinderclient-functional-base + abstract: true parent: devstack run: playbooks/python-cinderclient-functional.yaml post-run: playbooks/post.yaml @@ -12,19 +13,47 @@ USE_PYTHON3: true VOLUME_BACKING_FILE_SIZE: 16G +- job: + name: python-cinderclient-functional-py36 + parent: python-cinderclient-functional-base + vars: + python_version: 3.6 + tox_envlist: functional-py36 + +- job: + name: python-cinderclient-functional-py37 + parent: python-cinderclient-functional-base + # Just to be clear what's going on here: which python is used by + # tox is controlled by tox.ini. But, that python needs to + # actually be available on the node running the job in order for + # the job to succeed. At this point, we can assume that 3.6 will + # be available everywhere (this is guaranteed by openstack-infra). + # But 3.7 is still problematic (don't ask me why). So for this + # job that we want running in py3.7, we need to (a) specify a + # nodeset for which py3.7 is available, and (b) tell the job to + # make sure it's available (i.e., install it if necessary). + # (a) is handled by the 'nodeset' specification below. + # (b) is handled by the setting the 'python_version' variable + # below, although by itself that doesn't do anything: it also + # requires that the 'ensure-python' role is included in the + # job playbook. + nodeset: openstack-single-node-bionic + vars: + python_version: 3.7 + tox_envlist: functional-py37 + - project: templates: - check-requirements - - lib-forward-testing - 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: jobs: - - python-cinderclient-functional + - python-cinderclient-functional-py36 + - python-cinderclient-functional-py37 - openstack-tox-pylint: voting: false diff --git a/doc/requirements.txt b/doc/requirements.txt index bf81d8446..f0f258fc9 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -4,5 +4,4 @@ # These are needed for docs generation 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 diff --git a/playbooks/python-cinderclient-functional.yaml b/playbooks/python-cinderclient-functional.yaml index ea7d2db27..dec94d03c 100644 --- a/playbooks/python-cinderclient-functional.yaml +++ b/playbooks/python-cinderclient-functional.yaml @@ -1,5 +1,6 @@ - hosts: all roles: + - ensure-python - run-devstack # Run bindep and test-setup after devstack so that they won't interfere - role: bindep @@ -9,6 +10,5 @@ - get-os-environment - ensure-tox - role: tox - tox_envlist: functional tox_install_siblings: false environment: "{{ os_env_vars }}" diff --git a/releasenotes/notes/drop-python2-support-d3a1bedc75445edc.yaml b/releasenotes/notes/drop-python2-support-d3a1bedc75445edc.yaml new file mode 100644 index 000000000..23ee4d5ca --- /dev/null +++ b/releasenotes/notes/drop-python2-support-d3a1bedc75445edc.yaml @@ -0,0 +1,7 @@ +--- +upgrade: + - | + Python 2.7 support has been dropped. Beginning with release 6.0.0, + the minimum version of Python supported by python-cinderclient is + Python 3.6. The last version of python-cinderclient to support + Python 2.7 is the 5.x series from the Train release. diff --git a/setup.cfg b/setup.cfg index ed9e19e86..3c66ddb19 100644 --- a/setup.cfg +++ b/setup.cfg @@ -15,8 +15,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 bfc523f6d..7153e4748 100644 --- a/tox.ini +++ b/tox.ini @@ -1,10 +1,15 @@ [tox] distribute = False -envlist = py27,py37,pep8 -minversion = 2.0 +envlist = py36,py37,pep8 +minversion = 3.1.0 skipsdist = True +skip_missing_interpreters = true +# this allows tox to infer the base python from the environment name +# and override any basepython configured in this file +ignore_basepython_conflict=true [testenv] +basepython = python3 usedevelop = True install_command = pip install {opts} {packages} setenv = @@ -25,11 +30,9 @@ commands = find . -type f -name "*.pyc" -delete whitelist_externals = find [testenv:pep8] -basepython = python3 commands = flake8 [testenv:pylint] -basepython = python3 deps = -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt @@ -38,11 +41,9 @@ commands = bash tools/lintstack.sh whitelist_externals = bash [testenv:venv] -basepython = python3 commands = {posargs} [testenv:cover] -basepython = python3 setenv = {[testenv]setenv} PYTHON=coverage run --source cinderclient --parallel-mode @@ -53,7 +54,6 @@ commands = coverage xml -o cover/coverage.xml [testenv:docs] -basepython = python3 deps = -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt @@ -61,7 +61,6 @@ deps = commands = sphinx-build -W -b html doc/source doc/build/html [testenv:pdf-docs] -basepython = python3 deps = {[testenv:docs]deps} commands = @@ -73,7 +72,6 @@ whitelist_externals = cp [testenv:releasenotes] -basepython = python3 deps = -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt @@ -81,24 +79,35 @@ deps = commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [testenv:functional] -basepython = python3 commands = stestr run {posargs} setenv = {[testenv]setenv} OS_TEST_PATH = ./cinderclient/tests/functional OS_VOLUME_API_VERSION = 3 + # must define this here so it can be inherited by the -py3* environments + OS_CINDERCLIENT_EXEC_DIR = {envdir}/bin + # The OS_CACERT environment variable should be passed to the test # environments to specify a CA bundle file to use in verifying a # TLS (https) server certificate. passenv = OS_* +[testenv:functional-py36] +setenv = {[testenv:functional]setenv} +passenv = {[testenv:functional]passenv} +commands = {[testenv:functional]commands} + +[testenv:functional-py37] +setenv = {[testenv:functional]setenv} +passenv = {[testenv:functional]passenv} +commands = {[testenv:functional]commands} + [flake8] show-source = True ignore = H404,H405,E122,E123,E128,E251 exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build [testenv:lower-constraints] -basepython = python3 deps = -c{toxinidir}/lower-constraints.txt -r{toxinidir}/test-requirements.txt From 8d0d0521c62aafc5cb364dd8033635fd8f88f826 Mon Sep 17 00:00:00 2001 From: Rajat Dhasmana Date: Thu, 16 Jan 2020 17:43:32 +0000 Subject: [PATCH 008/159] Add filters support for volume transfer Currently ``id`` and ``volume_id`` filters are working correctly for transfer-list command. support for filtering by ``name`` is handled in patch provided in Depends-On. Since filtering by all parameters is supported by API, we can happily add the filters option on the client for volume transfers. Also adds functional test for transfers. Related-Bug: #1860100 Depends-On: https://review.opendev.org/#/c/703658/ Change-Id: I2fd3a6a7b9add65a9a21388df44efb6747065a74 --- cinderclient/tests/functional/base.py | 4 ++- cinderclient/tests/functional/test_cli.py | 33 +++++++++++++++++++ cinderclient/tests/unit/v3/test_shell.py | 32 ++++++++++++++++++ cinderclient/v3/shell.py | 11 +++++++ ...ort-filters-transfer-a1e7b728c7895a45.yaml | 6 ++++ 5 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/support-filters-transfer-a1e7b728c7895a45.yaml diff --git a/cinderclient/tests/functional/base.py b/cinderclient/tests/functional/base.py index 349bd1e79..3d2e2f8f9 100644 --- a/cinderclient/tests/functional/base.py +++ b/cinderclient/tests/functional/base.py @@ -156,7 +156,9 @@ def object_create(self, object_name, params): output = self.cinder(cmd, params=params) object = self._get_property_from_output(output) self.addCleanup(self.object_delete, object_name, object['id']) - self.wait_for_object_status(object_name, object['id'], 'available') + if object_name in ('volume', 'snapshot', 'backup'): + self.wait_for_object_status( + object_name, object['id'], 'available') return object def object_delete(self, object_name, object_id): diff --git a/cinderclient/tests/functional/test_cli.py b/cinderclient/tests/functional/test_cli.py index 0490df9d1..5f4a64a79 100644 --- a/cinderclient/tests/functional/test_cli.py +++ b/cinderclient/tests/functional/test_cli.py @@ -102,3 +102,36 @@ def test_backup_create_and_delete(self): self.check_object_deleted('volume', volume['id']) self.object_delete('backup', backup['id']) self.check_object_deleted('backup', backup['id']) + + +class VolumeTransferTests(base.ClientTestBase): + """Check of base cinder volume transfers command""" + + TRANSFER_PROPERTY = ('created_at', 'volume_id', 'id', 'auth_key', 'name') + TRANSFER_SHOW_PROPERTY = ('created_at', 'volume_id', 'id', 'name') + + def test_transfer_create_delete(self): + """Create and delete a volume transfer""" + volume = self.object_create('volume', params='1') + transfer = self.object_create('transfer', params=volume['id']) + self.assert_object_details(self.TRANSFER_PROPERTY, transfer.keys()) + self.object_delete('transfer', transfer['id']) + self.check_object_deleted('transfer', transfer['id']) + self.object_delete('volume', volume['id']) + self.check_object_deleted('volume', volume['id']) + + def test_transfer_show_delete_by_name(self): + """Show volume transfer by name""" + volume = self.object_create('volume', params='1') + self.object_create( + 'transfer', + params=('%s --name TEST_TRANSFER_SHOW' % volume['id'])) + output = self.cinder('transfer-show', params='TEST_TRANSFER_SHOW') + transfer = self._get_property_from_output(output) + self.assertEqual('TEST_TRANSFER_SHOW', transfer['name']) + self.assert_object_details(self.TRANSFER_SHOW_PROPERTY, + transfer.keys()) + self.object_delete('transfer', 'TEST_TRANSFER_SHOW') + self.check_object_deleted('transfer', 'TEST_TRANSFER_SHOW') + self.object_delete('volume', volume['id']) + self.check_object_deleted('volume', volume['id']) diff --git a/cinderclient/tests/unit/v3/test_shell.py b/cinderclient/tests/unit/v3/test_shell.py index 74e702ed6..06c3d443a 100644 --- a/cinderclient/tests/unit/v3/test_shell.py +++ b/cinderclient/tests/unit/v3/test_shell.py @@ -1560,3 +1560,35 @@ def test_subcommand_parser(self): for e in expected_commands: self.assertIn(' ' + e, help_text) + + @ddt.data( + # testcases for list transfers + {'command': + 'transfer-list --filters volume_id=456', + 'expected': + '/os-volume-transfer/detail?volume_id=456'}, + {'command': + 'transfer-list --filters id=123', + 'expected': + '/os-volume-transfer/detail?id=123'}, + {'command': + 'transfer-list --filters name=abc', + 'expected': + '/os-volume-transfer/detail?name=abc'}, + {'command': + 'transfer-list --filters name=abc --filters volume_id=456', + 'expected': + '/os-volume-transfer/detail?name=abc&volume_id=456'}, + {'command': + 'transfer-list --filters id=123 --filters volume_id=456', + 'expected': + '/os-volume-transfer/detail?id=123&volume_id=456'}, + {'command': + 'transfer-list --filters id=123 --filters name=abc', + 'expected': + '/os-volume-transfer/detail?id=123&name=abc'}, + ) + @ddt.unpack + def test_transfer_list_with_filters(self, command, expected): + self.run_command('--os-volume-api-version 3.52 %s' % command) + self.assert_called('GET', expected) diff --git a/cinderclient/v3/shell.py b/cinderclient/v3/shell.py index 9067b064c..8c95b76d6 100644 --- a/cinderclient/v3/shell.py +++ b/cinderclient/v3/shell.py @@ -2569,12 +2569,22 @@ def do_transfer_create(cs, args): default=None, help='Sort keys and directions in the form of [:].', start_version='3.59') +@utils.arg('--filters', + action=AppendFilters, + type=six.text_type, + nargs='*', + start_version='3.52', + metavar='', + default=None, + help="Filter key and value pairs.") def do_transfer_list(cs, args): """Lists all transfers.""" all_tenants = int(os.environ.get("ALL_TENANTS", args.all_tenants)) search_opts = { 'all_tenants': all_tenants, } + if AppendFilters.filters: + search_opts.update(shell_utils.extract_filters(AppendFilters.filters)) sort = getattr(args, 'sort', None) if sort: @@ -2587,3 +2597,4 @@ def do_transfer_list(cs, args): transfers = cs.transfers.list(search_opts=search_opts, sort=sort) columns = ['ID', 'Volume ID', 'Name'] utils.print_list(transfers, columns) + AppendFilters.filters = [] diff --git a/releasenotes/notes/support-filters-transfer-a1e7b728c7895a45.yaml b/releasenotes/notes/support-filters-transfer-a1e7b728c7895a45.yaml new file mode 100644 index 000000000..49308c98b --- /dev/null +++ b/releasenotes/notes/support-filters-transfer-a1e7b728c7895a45.yaml @@ -0,0 +1,6 @@ +--- +features: + - New command option ``--filters`` is added to ``transfer-list`` + command to support filtering. + The ``transfer-list`` command can be used with filters when + communicating with the Block Storage API version 3.52 and higher. \ No newline at end of file From 9191d76450c0d80400a08e730a8f54f826e23022 Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Tue, 3 Mar 2020 08:42:23 -0500 Subject: [PATCH 009/159] Ussuri contrib docs community goal This patch standardizes the CONTRIBUTING.rst file and adds doc/source/contributor/contributing.rst Change-Id: I892e49f96573b77b46bd8847a5d2ac8254e8e5e1 --- CONTRIBUTING.rst | 23 +++++++++++++---------- doc/source/contributor/contributing.rst | 14 ++++++++++++++ doc/source/index.rst | 1 + 3 files changed, 28 insertions(+), 10 deletions(-) create mode 100644 doc/source/contributor/contributing.rst diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 80ea3b533..5d5f77290 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -1,16 +1,19 @@ -If you would like to contribute to the development of OpenStack, -you must follow the steps in this page: +The source repository for this project can be found at: - https://docs.openstack.org/infra/manual/developers.html + https://opendev.org/openstack/python-cinderclient -Once those steps have been completed, changes to OpenStack -should be submitted for review via the Gerrit tool, following -the workflow documented at: +Pull requests submitted through GitHub are not monitored. - https://docs.openstack.org/infra/manual/developers.html#development-workflow +To start contributing to OpenStack, follow the steps in the contribution guide +to set up and use Gerrit: -Pull requests submitted through GitHub will be ignored. + https://docs.openstack.org/contributors/code-and-documentation/quick-start.html -Bugs should be filed on Launchpad, not in GitHub's issue tracker: +Bugs should be filed on Launchpad: - https://bugs.launchpad.net/python-cinderclient + https://bugs.launchpad.net/python-cinderclient + +For more specific information about contributing to this repository, see the +cinderclient contributor guide: + + https://docs.openstack.org/python-cinderclient/latest/contributor/contributing.html diff --git a/doc/source/contributor/contributing.rst b/doc/source/contributor/contributing.rst new file mode 100644 index 000000000..b43385a74 --- /dev/null +++ b/doc/source/contributor/contributing.rst @@ -0,0 +1,14 @@ +============================ +So You Want to Contribute... +============================ + +For general information on contributing to OpenStack, please check out the +`contributor guide `_ to get started. +It covers all the basics that are common to all OpenStack projects: the +accounts you need, the basics of interacting with our Gerrit review system, how +we communicate as a community, etc. + +The python-cinderclient is maintained by the OpenStack Cinder project. +To understand our development process and how you can contribute to it, please +look at the Cinder project's general contributor's page: +http://docs.openstack.org/cinder/latest/contributor/contributing.html diff --git a/doc/source/index.rst b/doc/source/index.rst index 085957415..557bddc9f 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -57,6 +57,7 @@ Developer Guides .. toctree:: :maxdepth: 2 + contributor/contributing contributor/functional_tests contributor/unit_tests From 5fe5c63b510ad6741b98581eb7faa82f1354e549 Mon Sep 17 00:00:00 2001 From: Eric Harney Date: Fri, 6 Mar 2020 11:41:21 -0500 Subject: [PATCH 010/159] Fix doc bug filing link This needs to go to "python-cinderclient" to work instead of "cinderclient". Add a "doc" tag to these bugs as well. Change-Id: I45ae03e9c071dd9b159b1c0183b53db4dd837453 --- doc/source/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 6642cbf09..1e0fa4a6f 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -91,8 +91,8 @@ # -- Options for openstackdocstheme ------------------------------------------- repository_name = 'openstack/python-cinderclient' -bug_project = 'cinderclient' -bug_tag = '' +bug_project = 'python-cinderclient' +bug_tag = 'doc' # -- Options for LaTeX output ------------------------------------------------- From 38a44e7ebeead6c086374d80f25d135618380faa Mon Sep 17 00:00:00 2001 From: Ian Wienand Date: Tue, 24 Mar 2020 15:24:59 +1100 Subject: [PATCH 011/159] Pass os_endpoint to keystone session The os_endpoint should be passed to the keystone session as the endpoint_override argument. This is particularly imprtant for talking to Rackspace, who seem to have an odd situation where the endpoint is V2 compatible [1], but the API is still at /v1/ [2] (i think?). To use the RAX API you need to find your account number, then something like: OS_USERNAME=xyz OS_PASSWORD=abc OS_AUTH_URL=https://identity.api.rackspacecloud.com/v2.0/ OS_VOLUME_API_VERSION=2 CINDER_ENDPOINT=https://dfw.blockstorage.api.rackspacecloud.com/v1/ cinder volume list Should work Honestly I'm not 100% what's up with the unit test. I think endpoint override was not being processed previously, and now it is so it drops the "admin"? Story: #2007459 Task: #39138 [1] https://developer.rackspace.com/docs/cloud-block-storage/v1/general-api-info/cbsv1-methods-vs-cinderv2-methods/ [2] https://developer.rackspace.com/docs/cloud-block-storage/v1/general-api-info/service-access/ Change-Id: I6b9a1f088c84676ddf9894cf9524d3239f3cf3a9 --- cinderclient/client.py | 2 ++ cinderclient/tests/unit/test_shell.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cinderclient/client.py b/cinderclient/client.py index fb84861f1..068d05952 100644 --- a/cinderclient/client.py +++ b/cinderclient/client.py @@ -701,6 +701,8 @@ def _construct_http_client(username=None, password=None, project_id=None, if session: kwargs.setdefault('user_agent', 'python-cinderclient') kwargs.setdefault('interface', endpoint_type) + kwargs.setdefault('endpoint_override', bypass_url) + return SessionClient(session=session, auth=auth, service_type=service_type, diff --git a/cinderclient/tests/unit/test_shell.py b/cinderclient/tests/unit/test_shell.py index c82c1b9db..430f4fb1f 100644 --- a/cinderclient/tests/unit/test_shell.py +++ b/cinderclient/tests/unit/test_shell.py @@ -240,7 +240,7 @@ def test_password_prompted(self, mock_getpass, mock_stdin, mock_discover, def test_noauth_plugin(self, mocker): os_auth_url = "http://example.com/v2" mocker.register_uri('GET', - "%s/admin/volumes/detail" + "%s/volumes/detail" % os_auth_url, text='{"volumes": []}') _shell = shell.OpenStackCinderShell() args = ['--os-endpoint', os_auth_url, From 9c5a850f5aad8def78434f4ec4c7ca2f9aa8d25d Mon Sep 17 00:00:00 2001 From: Ian Wienand Date: Tue, 24 Mar 2020 15:47:31 +1100 Subject: [PATCH 012/159] Remove --bypass-url documentation This was removed with I3b951cc4eb3adff23f3d2cbe674971816261ef56; cleanup old references. Change-Id: I71e6da99dff04d86b9dd67a754764b1e742d366a --- README.rst | 8 ++++---- cinderclient/client.py | 5 ++--- doc/source/cli/details.rst | 9 ++------- doc/source/user/no_auth.rst | 4 ++-- 4 files changed, 10 insertions(+), 16 deletions(-) diff --git a/README.rst b/README.rst index ac616d705..63d67c7eb 100644 --- a/README.rst +++ b/README.rst @@ -90,7 +90,7 @@ You'll find complete documentation on the shell by running [--os-endpoint-type ] [--endpoint-type ] [--os-volume-api-version ] - [--bypass-url ] [--retries ] + [--retries ] [--profile HMAC_KEY] [--os-auth-strategy ] [--os-username ] [--os-password ] [--os-tenant-name ] @@ -254,6 +254,9 @@ You'll find complete documentation on the shell by running --volume-service-name Volume service name. Default=env[CINDER_VOLUME_SERVICE_NAME]. + --os-endpoint + Use this API endpoint instead of the Service Catalog. + Default=env[CINDER_ENDPOINT] --os-endpoint-type Endpoint type, which is publicURL or internalURL. Default=env[OS_ENDPOINT_TYPE] or nova @@ -264,9 +267,6 @@ You'll find complete documentation on the shell by running Block Storage API version. Accepts X, X.Y (where X is major and Y is minor part).Default=env[OS_VOLUME_API_VERSION]. - --bypass-url - Use this API endpoint instead of the Service Catalog. - Defaults to env[CINDERCLIENT_BYPASS_URL]. --retries Number of retries. --profile HMAC_KEY HMAC key to use for encrypting context data for performance profiling of operation. This key needs to diff --git a/cinderclient/client.py b/cinderclient/client.py index 068d05952..6783dc8c6 100644 --- a/cinderclient/client.py +++ b/cinderclient/client.py @@ -485,9 +485,8 @@ def get_volume_api_version_from_endpoint(self): version = get_volume_api_from_url(self.management_url) except exceptions.UnsupportedVersion as e: if self.management_url == self.bypass_url: - msg = (_("Invalid url was specified in --os-endpoint or " - "environment variable CINDERCLIENT_BYPASS_URL.\n" - "%s") % six.text_type(e)) + msg = (_("Invalid url was specified in --os-endpoint %s") + % six.text_type(e)) else: msg = (_("Service catalog returned invalid url.\n" "%s") % six.text_type(e)) diff --git a/doc/source/cli/details.rst b/doc/source/cli/details.rst index 9ced4c66d..e182fa5b6 100644 --- a/doc/source/cli/details.rst +++ b/doc/source/cli/details.rst @@ -43,7 +43,7 @@ cinder usage [--os-endpoint-type ] [--endpoint-type ] [--os-volume-api-version ] - [--bypass-url ] [--os-endpoint ] + [--os-endpoint ] [--retries ] [--profile HMAC_KEY] [--os-auth-strategy ] [--os-username ] [--os-password ] @@ -877,14 +877,9 @@ cinder optional arguments major and Y is minor part).Default= ``env[OS_VOLUME_API_VERSION]``. -``--bypass-url `` - **DEPRECATED!** Use os_endpoint. Use this API endpoint - instead of the Service Catalog. Defaults to - ``env[CINDERCLIENT_BYPASS_URL]``. - ``--os-endpoint `` Use this API endpoint instead of the Service Catalog. - Defaults to ``env[CINDER_ENDPOINT]``. + Default=``env[CINDER_ENDPOINT]`` ``--retries `` Number of retries. diff --git a/doc/source/user/no_auth.rst b/doc/source/user/no_auth.rst index 71a65e9e9..597b69abc 100644 --- a/doc/source/user/no_auth.rst +++ b/doc/source/user/no_auth.rst @@ -16,7 +16,7 @@ Using cinderclient To use the cinderclient you'll need to set the following env variables:: OS_AUTH_TYPE=noauth - CINDERCLIENT_BYPASS_URL=http://:8776/v3 + CINDER_ENDPOINT=http://:8776/v3 OS_PROJECT_ID=foo OS_VOLUME_API_VERSION=3.10 @@ -27,6 +27,6 @@ point, it's noauth. Each of these options can also be specified on the cmd line:: cinder --os-auth-type=noauth \ - --bypass-url=http://:8776/v3 \ + --os-endpoint=http://:8776/v3 \ --os-project-id=admin \ --os-volume-api-version=3.10 list From d41d7155c01ef2a33b0b18bf55e5b3ec284462f4 Mon Sep 17 00:00:00 2001 From: Ian Wienand Date: Tue, 24 Mar 2020 15:52:04 +1100 Subject: [PATCH 013/159] Replace bypass_url with os_endpoint The --bypass-url argument was removed with I3b951cc4eb3adff23f3d2cbe674971816261ef56 so this name does not make sense now. Replace with os_endpoint. Change-Id: Ifa889cc2e885e9c621c8494995b2020195b696ca --- cinderclient/client.py | 23 ++++++++++++----------- cinderclient/shell.py | 2 +- cinderclient/tests/unit/test_client.py | 4 ++-- cinderclient/tests/unit/test_http.py | 4 ++-- cinderclient/v2/client.py | 4 ++-- cinderclient/v3/client.py | 4 ++-- 6 files changed, 21 insertions(+), 20 deletions(-) diff --git a/cinderclient/client.py b/cinderclient/client.py index 6783dc8c6..6193e9590 100644 --- a/cinderclient/client.py +++ b/cinderclient/client.py @@ -277,7 +277,7 @@ def __init__(self, user, password, projectid, auth_url=None, proxy_tenant_id=None, proxy_token=None, region_name=None, endpoint_type='publicURL', service_type=None, service_name=None, volume_service_name=None, - bypass_url=None, retries=None, + os_endpoint=None, retries=None, http_log_debug=False, cacert=None, auth_system='keystone', auth_plugin=None, api_version=None, logger=None, user_domain_name='Default', @@ -304,11 +304,12 @@ def __init__(self, user, password, projectid, auth_url=None, self.service_type = service_type self.service_name = service_name self.volume_service_name = volume_service_name - self.bypass_url = bypass_url.rstrip('/') if bypass_url else bypass_url + self.os_endpoint = os_endpoint.rstrip('/') \ + if os_endpoint else os_endpoint self.retries = int(retries or 0) self.http_log_debug = http_log_debug - self.management_url = self.bypass_url or None + self.management_url = self.os_endpoint or None self.auth_token = None self.proxy_token = proxy_token self.proxy_tenant_id = proxy_tenant_id @@ -484,7 +485,7 @@ def get_volume_api_version_from_endpoint(self): try: version = get_volume_api_from_url(self.management_url) except exceptions.UnsupportedVersion as e: - if self.management_url == self.bypass_url: + if self.management_url == self.os_endpoint: msg = (_("Invalid url was specified in --os-endpoint %s") % six.text_type(e)) else: @@ -588,8 +589,8 @@ def authenticate(self): # existing token? If so, our actual endpoints may # be different than that of the admin token. if self.proxy_token: - if self.bypass_url: - self.set_management_url(self.bypass_url) + if self.os_endpoint: + self.set_management_url(self.os_endpoint) else: self._fetch_endpoints_from_auth(admin_url) # Since keystone no longer returns the user token @@ -608,8 +609,8 @@ def authenticate(self): auth_url = auth_url + '/v2.0' self._v2_or_v3_auth(auth_url) - if self.bypass_url: - self.set_management_url(self.bypass_url) + if self.os_endpoint: + self.set_management_url(self.os_endpoint) elif not self.management_url: raise exceptions.Unauthorized('Cinder Client') @@ -689,7 +690,7 @@ def _construct_http_client(username=None, password=None, project_id=None, region_name=None, endpoint_type='publicURL', service_type='volume', service_name=None, volume_service_name=None, - bypass_url=None, retries=None, + os_endpoint=None, retries=None, http_log_debug=False, auth_system='keystone', auth_plugin=None, cacert=None, tenant_id=None, @@ -700,7 +701,7 @@ def _construct_http_client(username=None, password=None, project_id=None, if session: kwargs.setdefault('user_agent', 'python-cinderclient') kwargs.setdefault('interface', endpoint_type) - kwargs.setdefault('endpoint_override', bypass_url) + kwargs.setdefault('endpoint_override', os_endpoint) return SessionClient(session=session, auth=auth, @@ -728,7 +729,7 @@ def _construct_http_client(username=None, password=None, project_id=None, service_type=service_type, service_name=service_name, volume_service_name=volume_service_name, - bypass_url=bypass_url, + os_endpoint=os_endpoint, retries=retries, http_log_debug=http_log_debug, cacert=cacert, diff --git a/cinderclient/shell.py b/cinderclient/shell.py index 57c9d8ccb..acbccdad5 100644 --- a/cinderclient/shell.py +++ b/cinderclient/shell.py @@ -718,7 +718,7 @@ def main(self, argv): service_type=service_type, service_name=service_name, volume_service_name=volume_service_name, - bypass_url=os_endpoint, + os_endpoint=os_endpoint, retries=options.retries, http_log_debug=args.debug, insecure=insecure, diff --git a/cinderclient/tests/unit/test_client.py b/cinderclient/tests/unit/test_client.py index 655636210..874cdccba 100644 --- a/cinderclient/tests/unit/test_client.py +++ b/cinderclient/tests/unit/test_client.py @@ -49,10 +49,10 @@ def test_construct_http_client_endpoint_url( os_endpoint = 'http://example.com/' httpclient_mock.return_value = None cinderclient.client._construct_http_client( - bypass_url=os_endpoint) + os_endpoint=os_endpoint) self.assertTrue(httpclient_mock.called) self.assertEqual(os_endpoint, - httpclient_mock.call_args[1].get('bypass_url')) + httpclient_mock.call_args[1].get('os_endpoint')) session_mock.assert_not_called() def test_log_req(self): diff --git a/cinderclient/tests/unit/test_http.py b/cinderclient/tests/unit/test_http.py index 5e49948b8..73ce6ae98 100644 --- a/cinderclient/tests/unit/test_http.py +++ b/cinderclient/tests/unit/test_http.py @@ -110,7 +110,7 @@ def get_authed_client(retries=0, **kwargs): def get_authed_endpoint_url(retries=0): cl = client.HTTPClient("username", "password", "project_id", "auth_test", - bypass_url="volume/v100/", retries=retries) + os_endpoint="volume/v100/", retries=retries) cl.auth_token = "token" return cl @@ -333,7 +333,7 @@ def test_post_call(): def test_os_endpoint_url(self): cl = get_authed_endpoint_url() - self.assertEqual("volume/v100", cl.bypass_url) + self.assertEqual("volume/v100", cl.os_endpoint) self.assertEqual("volume/v100", cl.management_url) def test_auth_failure(self): diff --git a/cinderclient/v2/client.py b/cinderclient/v2/client.py index a0ad1b883..0086a4d2a 100644 --- a/cinderclient/v2/client.py +++ b/cinderclient/v2/client.py @@ -55,7 +55,7 @@ def __init__(self, username=None, api_key=None, project_id=None, proxy_tenant_id=None, proxy_token=None, region_name=None, endpoint_type='publicURL', extensions=None, service_type='volumev2', service_name=None, - volume_service_name=None, bypass_url=None, retries=0, + volume_service_name=None, os_endpoint=None, retries=0, http_log_debug=False, cacert=None, auth_system='keystone', auth_plugin=None, session=None, api_version=None, logger=None, **kwargs): @@ -114,7 +114,7 @@ def __init__(self, username=None, api_key=None, project_id=None, service_type=service_type, service_name=service_name, volume_service_name=volume_service_name, - bypass_url=bypass_url, + os_endpoint=os_endpoint, retries=retries, http_log_debug=http_log_debug, cacert=cacert, diff --git a/cinderclient/v3/client.py b/cinderclient/v3/client.py index c1e8877a3..5703826ea 100644 --- a/cinderclient/v3/client.py +++ b/cinderclient/v3/client.py @@ -61,7 +61,7 @@ def __init__(self, username=None, api_key=None, project_id=None, proxy_tenant_id=None, proxy_token=None, region_name=None, endpoint_type='publicURL', extensions=None, service_type='volumev3', service_name=None, - volume_service_name=None, bypass_url=None, retries=0, + volume_service_name=None, os_endpoint=None, retries=0, http_log_debug=False, cacert=None, auth_system='keystone', auth_plugin=None, session=None, api_version=None, logger=None, **kwargs): @@ -125,7 +125,7 @@ def __init__(self, username=None, api_key=None, project_id=None, service_type=service_type, service_name=service_name, volume_service_name=volume_service_name, - bypass_url=bypass_url, + os_endpoint=os_endpoint, retries=retries, http_log_debug=http_log_debug, cacert=cacert, From aa85c7b3105fc45ec9e345b4036ab0073fb9f673 Mon Sep 17 00:00:00 2001 From: Ian Wienand Date: Tue, 24 Mar 2020 18:20:55 +1100 Subject: [PATCH 014/159] Remove autogen warning This isn't autogenerated ... it would be good if it was, but it isn't. Change-Id: Iaf8b2375051e2dbd8cf6fd653fac4cdc60b4e7ea --- doc/source/cli/details.rst | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/doc/source/cli/details.rst b/doc/source/cli/details.rst index e182fa5b6..e4ff9be61 100644 --- a/doc/source/cli/details.rst +++ b/doc/source/cli/details.rst @@ -1,21 +1,3 @@ -.. ################################################### -.. ## WARNING ###################################### -.. ############## WARNING ########################## -.. ########################## WARNING ############## -.. ###################################### WARNING ## -.. ################################################### -.. ################################################### -.. ## -.. This file is tool-generated. Do not edit manually. -.. https://docs.openstack.org/contributor-guide/ -.. doc-tools/cli-reference.html -.. ## -.. ## WARNING ###################################### -.. ############## WARNING ########################## -.. ########################## WARNING ############## -.. ###################################### WARNING ## -.. ################################################### - ================================================== Block Storage service (cinder) command-line client ================================================== From c0edaade9762b04f8f64aa3b87e5d554c22b26f3 Mon Sep 17 00:00:00 2001 From: Andreas Jaeger Date: Sat, 4 Apr 2020 11:24:20 +0200 Subject: [PATCH 015/159] Cleanup py27 support Make a few cleanups: - Remove python 2.7 stanza from setup.py - Add requires on python >= 3.6 to setup.cfg so that pypi and pip know about the requirement - Remove old sections from setup.cfg: Wheel is not needed for python 3 only repo Change-Id: I92d21453d610ba7f90ae9b150e6a245ce0e709e6 --- setup.cfg | 12 +----------- setup.py | 9 --------- 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/setup.cfg b/setup.cfg index 3c66ddb19..0c179b1bf 100644 --- a/setup.cfg +++ b/setup.cfg @@ -6,6 +6,7 @@ description-file = author = OpenStack author-email = openstack-discuss@lists.openstack.org home-page = https://docs.openstack.org/python-cinderclient/latest/ +python-requires = >=3.6 classifier = Development Status :: 5 - Production/Stable Environment :: Console @@ -19,11 +20,6 @@ classifier = Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 - -[global] -setup-hooks = - pbr.hooks.setup_hook - [files] packages = cinderclient @@ -34,9 +30,3 @@ console_scripts = keystoneauth1.plugin = noauth = cinderclient.contrib.noauth:CinderNoAuthLoader - -[upload_sphinx] -upload-dir = doc/build/html - -[wheel] -universal = 1 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 26a55de681c64410d0c22e4e579100f318dcc79e Mon Sep 17 00:00:00 2001 From: wanghao Date: Mon, 2 Mar 2020 16:53:23 +0800 Subject: [PATCH 016/159] Add support for Block Storage API mv 3.60 Change I1f43c37c2266e43146637beadc027ccf6dec017e adds time-comparison filtering to the volume list calls (summary and detail) in the Block Storage API microversion 3.60. The current cinderclient filter support will pass these filters correctly, so the only change needed on the client side is to bump the MAX_VERSION so that the client can make calls to mv 3.60. Co-authored-by: Brian Rosmaita Change-Id: Ib4b7cbc7e527c0524336e139e127f19accfb7568 Partially-Implements: bp support-to-query-cinder-resources-filter-by-time-comparison-operators --- cinderclient/api_versions.py | 2 +- cinderclient/tests/unit/v3/test_shell.py | 18 +++++ doc/source/cli/details.rst | 76 ++++++++++++++----- .../support-bs-mv-3.60-a65f1919b5068d17.yaml | 13 ++++ 4 files changed, 91 insertions(+), 18 deletions(-) create mode 100644 releasenotes/notes/support-bs-mv-3.60-a65f1919b5068d17.yaml diff --git a/cinderclient/api_versions.py b/cinderclient/api_versions.py index 54d2fceca..2d2219953 100644 --- a/cinderclient/api_versions.py +++ b/cinderclient/api_versions.py @@ -29,7 +29,7 @@ # key is a deprecated version and value is an alternative version. DEPRECATED_VERSIONS = {"2": "3"} DEPRECATED_VERSION = "2.0" -MAX_VERSION = "3.59" +MAX_VERSION = "3.60" MIN_VERSION = "3.0" _SUBSTITUTIONS = {} diff --git a/cinderclient/tests/unit/v3/test_shell.py b/cinderclient/tests/unit/v3/test_shell.py index 06c3d443a..6a2523802 100644 --- a/cinderclient/tests/unit/v3/test_shell.py +++ b/cinderclient/tests/unit/v3/test_shell.py @@ -149,6 +149,24 @@ def test_list_filters(self, resource, query_url): u'list --filters name=abc --filters size=1', 'expected': '/volumes/detail?name=abc&size=1'}, + {'command': + u'list --filters created_at=lt:2020-01-15T00:00:00', + 'expected': + '/volumes/detail?created_at=lt%3A2020-01-15T00%3A00%3A00'}, + {'command': + u'list --filters updated_at=gte:2020-02-01T00:00:00,' + u'lt:2020-03-01T00:00:00', + 'expected': + '/volumes/detail?updated_at=gte%3A2020-02-01T00%3A00%3A00%2C' + 'lt%3A2020-03-01T00%3A00%3A00'}, + {'command': + u'list --filters updated_at=gte:2020-02-01T00:00:00,' + u'lt:2020-03-01T00:00:00 --filters created_at=' + u'lt:2020-01-15T00:00:00', + 'expected': + '/volumes/detail?created_at=lt%3A2020-01-15T00%3A00%3A00' + '&updated_at=gte%3A2020-02-01T00%3A00%3A00%2C' + 'lt%3A2020-03-01T00%3A00%3A00'}, # testcases for list group {'command': 'group-list --filters name=456', diff --git a/doc/source/cli/details.rst b/doc/source/cli/details.rst index 9ced4c66d..80076d5d5 100644 --- a/doc/source/cli/details.rst +++ b/doc/source/cli/details.rst @@ -2749,24 +2749,66 @@ Lists all volumes. ``--tenant []`` Display information from single tenant (Admin only). +.. _cinder-list-filters-usage: + ``--filters [ [ ...]]`` - Filter - key - and - value - pairs. - Please - use - 'cinder - list-filters' - to - check - enabled - filters - from - server, - Default=None. (Supported by API version 3.33 and - later) + Filter key and value pairs. + Please use the ``cinder list-filters`` command to check enabled filters + from server. + Default=None. + (Supported by API version 3.33 and later) + + **Time Comparison Filters** + + Beginning with API version 3.60, you can apply time comparison filtering + to the ``created_at`` and ``updated at`` fields. Time must be + expressed in ISO 8601 format: CCYY-MM-DDThh:mm:ss±hh:mm. The + ±hh:mm value, if included, returns the time zone as an offset from + UTC. + + To use time comparison filtering, use the standard ``key=value`` syntax + for the ``--filters`` option. The allowable keys are: + + * ``created_at`` + * ``updated_at`` + + The value is a *time comparison statement*, which is specified as follows: + a comparison operator, followed immediately by a colon (``:``), followed + immediately by a time expressed in ISO 8601 format. You can filter by a + *time range* by appending a comma (``,``) followed a second time + comparison statement. + + Six *comparison operators* are supported: + + * ``gt`` (greater than) - return results more recent than the specified + time + * ``gte`` (greater than or equal) - return any results matching the + specified time and also any more recent results + * ``eq`` (equal) - return any results matching the specified time + exactly + * ``neq`` (not equal) - return any results that do not match the + specified time + * ``lt`` (less than) - return results older than the specified time + * ``lte`` (less than or equal) - return any results matching the + specified time and also any older results + + **Examples** + + To filter the response to volumes created before 15 January 2020: + + .. code-block:: console + + cinder list --filters created_at=lt:2020-01-15T00:00:00 + + To filter the response to those volumes updated in February 2020: + + .. code-block:: console + + cinder list --filters updated_at=gte:2020-02-01T00:00:00,lt:2020-03-01T00:00:00 + + See the `Block Storage API v3 Reference + `_ for + more information. .. _cinder_list-extensions: diff --git a/releasenotes/notes/support-bs-mv-3.60-a65f1919b5068d17.yaml b/releasenotes/notes/support-bs-mv-3.60-a65f1919b5068d17.yaml new file mode 100644 index 000000000..3813767c3 --- /dev/null +++ b/releasenotes/notes/support-bs-mv-3.60-a65f1919b5068d17.yaml @@ -0,0 +1,13 @@ +--- +features: + - | + When communicating with the Block Storage API version 3.60 and higher, + you can apply time comparison filtering to the volume list command + on the ``created_at`` or ``updated_at`` fields. Time must be + expressed in ISO 8601 format: CCYY-MM-DDThh:mm:ss±hh:mm. The + ±hh:mm value, if included, returns the time zone as an offset from + UTC. + + See the `Block Storage service (cinder) command-line client + `_ + documentation for usage details. From 4e1427a41870f15748155b1ad7339be4fd920e4c Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Tue, 7 Apr 2020 17:25:02 -0400 Subject: [PATCH 017/159] Add release note for Ussuri cinderclient release. The official Ussuri release will be 7.0.0. Change-Id: I4f41291768d06faf0d76f8d8a1c420b765f29b6b --- .../ussuri-release-f0ebfc54cdac6680.yaml | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 releasenotes/notes/ussuri-release-f0ebfc54cdac6680.yaml diff --git a/releasenotes/notes/ussuri-release-f0ebfc54cdac6680.yaml b/releasenotes/notes/ussuri-release-f0ebfc54cdac6680.yaml new file mode 100644 index 000000000..f1ac0b554 --- /dev/null +++ b/releasenotes/notes/ussuri-release-f0ebfc54cdac6680.yaml @@ -0,0 +1,28 @@ +--- +prelude: | + The Ussuri release of the python-cinderclient supports Block Storage + API version 2 and Block Storage API version 3 through microversion + 3.60. (The maximum microversion of the Block Storage API in the + Ussuri release is 3.60.) + + In addition to the features and bugfixes described below, this release + includes some documentation updates. + + Note that this release corresponds to a major bump in the version + number. See the "Upgrade Notes" section of this document for details. + + Please keep in mind that the minimum version of Python supported by + this release is Python 3.6. +upgrade: + - | + The ``--bypass-url`` command line argument, having been deprecated in + version 2.10, was removed in version 4.0.0. It was replaced by the + command line argument ``--os-endpoint`` for consistency with other + OpenStack clients. In this release, the initializer functions for + client objects no longer recognize ``bypass_url`` as a parameter name. + Instead, use ``os_endpoint``. This keeps the cinderclient consistent + both internally and with respect to other OpenStack clients. +fixes: + - | + Fixed an issue where the ``os_endpoint`` was not being passed to the + keystone session as the ``endpoint_override`` argument. From 7f5868299e2a63b4ab5fdf41bd8eec298073248d Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Wed, 8 Apr 2020 19:40:14 +0000 Subject: [PATCH 018/159] 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: Ibe088b927b963f1fe6b3e654ad1a5f03618c332b 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 01536cace..a4a59f99f 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ :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 d17035a375f17d572261cd631f8b9d5ec8ca337a Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Fri, 10 Apr 2020 13:27:06 -0500 Subject: [PATCH 019/159] 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: I8e798da1c5cc2b36d00ab861a77e836bd55a9900 Signed-off-by: Sean McGinnis --- .zuul.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.zuul.yaml b/.zuul.yaml index 351b922c7..1f51c0481 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -48,7 +48,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 dfb04f1c4ad11e39dfb73db9403ee6e1b87129da Mon Sep 17 00:00:00 2001 From: Dmitry Tantsur Date: Fri, 17 Apr 2020 15:39:39 +0200 Subject: [PATCH 020/159] Remove Babel from requirements It's not a runtime dependency (and even oslo.i18n has dropped it). The translation infrastructure installs Babel explicitly. See this mailing list thread for a full reasoning: http://lists.openstack.org/pipermail/openstack-discuss/2020-April/014227.html Change-Id: I2898616ec6302bb578ddc265d6c6dc02d8415eac --- lower-constraints.txt | 1 - requirements.txt | 1 - 2 files changed, 2 deletions(-) diff --git a/lower-constraints.txt b/lower-constraints.txt index 9e44ad897..740ac5a1a 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -1,5 +1,4 @@ asn1crypto==0.23.0 -Babel==2.3.4 cffi==1.7.0 cliff==2.8.0 cmd2==0.8.0 diff --git a/requirements.txt b/requirements.txt index efa6cf381..fef5e1e44 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,6 @@ pbr!=2.1.0,>=2.0.0 # Apache-2.0 PrettyTable<0.8,>=0.7.1 # BSD keystoneauth1>=3.4.0 # Apache-2.0 simplejson>=3.5.1 # MIT -Babel!=2.4.0,>=2.3.4 # BSD six>=1.10.0 # MIT oslo.i18n>=3.15.3 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 From 71d939677bc22cc19a3f9b6d30809ded9764f745 Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Fri, 24 Apr 2020 08:23:15 -0500 Subject: [PATCH 021/159] 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: I92118f2a4a6c6db3f614addd324c76411c3b7f24 Signed-off-by: Sean McGinnis --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 0c179b1bf..dabff754e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,6 +19,7 @@ classifier = Programming Language :: Python :: 3 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 [files] packages = From 8b5d67930c12e72d3f869bfdc627aff9000f7a9f Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Fri, 24 Apr 2020 10:25:54 -0500 Subject: [PATCH 022/159] 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: Ie26a8d5a79c3ba8348419f031af0cef0f0e5c6c7 Signed-off-by: Sean McGinnis --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 8062bcdea..5aeea014b 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] distribute = False -envlist = py36,py37,pep8 +envlist = py36,py38,pep8 minversion = 3.1.0 skipsdist = True skip_missing_interpreters = true From 66b7d53da98ec152f8e665b85dcaccc597c053cc Mon Sep 17 00:00:00 2001 From: Ghanshyam Mann Date: Thu, 14 May 2020 15:24:36 -0500 Subject: [PATCH 023/159] 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. Also removing the hacking and other related dep from lower-constraints file as theose are blacklisted requirements and does not need to be present there. - http://lists.openstack.org/pipermail/openstack-discuss/2020-May/014828.html Change-Id: If5f2f970a353189f3eaeea00322848cb8346245c --- lower-constraints.txt | 4 ---- test-requirements.txt | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/lower-constraints.txt b/lower-constraints.txt index 740ac5a1a..6b864c1a0 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -9,9 +9,7 @@ debtcollector==1.2.0 extras==1.0.0 fasteners==0.7.0 fixtures==3.0.0 -flake8==2.5.5 future==0.16.0 -hacking==2.0.0 idna==2.6 iso8601==0.1.11 jsonschema==2.6.0 @@ -33,11 +31,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 pyinotify==0.9.6 pyparsing==2.1.0 pyperclip==1.5.27 diff --git a/test-requirements.txt b/test-requirements.txt index 562d2cd6c..a9ed4bc52 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -3,7 +3,7 @@ # process, which may cause wedges in the gate later. # Hacking already pins down pep8, pyflakes and flake8 -hacking>=2.0.0 # Apache-2.0 +hacking>=3.0.1,<3.1.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 ddt>=1.0.1 # MIT fixtures>=3.0.0 # Apache-2.0/BSD From 7c8217511ee5e59ee126c1ca59a764ef20a750bb Mon Sep 17 00:00:00 2001 From: fuzihao Date: Wed, 20 May 2020 09:52:11 +0800 Subject: [PATCH 024/159] 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: I7d7bebc5b29b4aa251bba784aeca98e23d3e9c23 --- 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 1e0fa4a6f..01420042b 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -67,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 -------------------------------------------------- diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py index cff4496af..457fd30a3 100644 --- a/releasenotes/source/conf.py +++ b/releasenotes/source/conf.py @@ -92,7 +92,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 67d992b07f8de99bb867a3be7d66aae4781fd014 Mon Sep 17 00:00:00 2001 From: zhangboye Date: Fri, 22 May 2020 15:15:15 +0800 Subject: [PATCH 025/159] Add py38 package metadata Change-Id: I3a0df4c9886465238bb62c1c24b3cafb9bc5aeb1 --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 0c179b1bf..dabff754e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,6 +19,7 @@ classifier = Programming Language :: Python :: 3 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 [files] packages = From 1a81faf86a7cc644c761dcf43ed19e6c2db0033d Mon Sep 17 00:00:00 2001 From: Andreas Jaeger Date: Fri, 15 May 2020 20:12:15 +0200 Subject: [PATCH 026/159] 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. openstackdocstheme renames some variables, so follow the renames. A couple of variables are also not needed anymore, remove them. Set openstackdocs_auto_name to use project as name. Set openstackdocs_pdf_link to link to PDF file. Note that the link to the published document only works on docs.openstack.org where the PDF file is placed in the top-level html directory. The site-preview places the PDF in a pdf directory. 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: I2cc022495b162bec1424ec69611acb879900c005 --- doc/requirements.txt | 6 +++--- doc/source/conf.py | 11 +++++------ lower-constraints.txt | 2 +- releasenotes/source/conf.py | 14 +++++--------- test-requirements.txt | 2 +- 5 files changed, 15 insertions(+), 20 deletions(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index f0f258fc9..dad08373a 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -2,6 +2,6 @@ # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. # These are needed for docs generation -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 diff --git a/doc/source/conf.py b/doc/source/conf.py index 1e0fa4a6f..282a881ce 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -14,7 +14,6 @@ import os import sys -import openstackdocstheme sys.setrecursionlimit(4000) @@ -67,7 +66,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 -------------------------------------------------- @@ -90,10 +89,10 @@ # -- Options for openstackdocstheme ------------------------------------------- -repository_name = 'openstack/python-cinderclient' -bug_project = 'python-cinderclient' -bug_tag = 'doc' - +openstackdocs_repo_name = 'openstack/python-cinderclient' +openstackdocs_bug_project = 'python-cinderclient' +openstackdocs_bug_tag = 'doc' +openstackdocs_pdf_link = True # -- Options for LaTeX output ------------------------------------------------- diff --git a/lower-constraints.txt b/lower-constraints.txt index 740ac5a1a..69a231fef 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -46,7 +46,7 @@ python-mimeparse==1.6.0 python-subunit==1.0.0 pytz==2013.6 PyYAML==3.12 -reno==2.5.0 +reno==3.1.0 requests-mock==1.2.0 requests==2.14.2 rfc3986==0.3.1 diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py index cff4496af..58ed2686b 100644 --- a/releasenotes/source/conf.py +++ b/releasenotes/source/conf.py @@ -56,6 +56,7 @@ # General information about the project. project = u'Cinder Client Release Notes' +openstackdocs_auto_name = False copyright = u'2015, Cinder Developers' # Release notes are version independent, no need to set version and release @@ -92,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 = [] @@ -141,11 +142,6 @@ # directly to the root of the documentation. # html_extra_path = [] -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -# html_last_updated_fmt = '%b %d, %Y' -html_last_updated_fmt = '%Y-%m-%d %H:%M' - # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True @@ -273,6 +269,6 @@ locale_dirs = ['locale/'] # -- Options for openstackdocstheme ------------------------------------------- -repository_name = 'openstack/python-cinderclient' -bug_project = 'cinderclient' -bug_tag = '' +openstackdocs_repo_name = 'openstack/python-cinderclient' +openstackdocs_bug_project = 'cinderclient' +openstackdocs_bug_tag = '' diff --git a/test-requirements.txt b/test-requirements.txt index 562d2cd6c..210ad76df 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -8,7 +8,7 @@ coverage!=4.4,>=4.0 # Apache-2.0 ddt>=1.0.1 # MIT fixtures>=3.0.0 # Apache-2.0/BSD mock>=2.0.0 # BSD -reno>=2.5.0 # Apache-2.0 +reno>=3.1.0 # Apache-2.0 requests-mock>=1.2.0 # Apache-2.0 tempest>=17.1.0 # Apache-2.0 testtools>=2.2.0 # MIT From b649d7f4f4903d766c42834429010c7d0952ab4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Beraud?= Date: Tue, 2 Jun 2020 20:46:48 +0200 Subject: [PATCH 027/159] 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: Id785793c36b3a6819a7522525252c3fef15ebe2b --- cinderclient/client.py | 2 -- cinderclient/shell.py | 2 -- cinderclient/shell_utils.py | 2 -- cinderclient/tests/unit/fakes.py | 2 -- cinderclient/utils.py | 2 -- cinderclient/v2/shell.py | 2 -- cinderclient/v3/shell.py | 2 -- tools/install_venv_common.py | 2 -- 8 files changed, 16 deletions(-) diff --git a/cinderclient/client.py b/cinderclient/client.py index 6193e9590..d1e5bbac4 100644 --- a/cinderclient/client.py +++ b/cinderclient/client.py @@ -16,8 +16,6 @@ # under the License. """OpenStack Client interface. Handles the REST calls and responses.""" -from __future__ import print_function - import glob import hashlib import imp diff --git a/cinderclient/shell.py b/cinderclient/shell.py index acbccdad5..814b71ae9 100644 --- a/cinderclient/shell.py +++ b/cinderclient/shell.py @@ -17,8 +17,6 @@ Command-line interface to the OpenStack Cinder API. """ -from __future__ import print_function - import argparse import collections import getpass diff --git a/cinderclient/shell_utils.py b/cinderclient/shell_utils.py index 411dd17c0..b5db281ec 100644 --- a/cinderclient/shell_utils.py +++ b/cinderclient/shell_utils.py @@ -12,8 +12,6 @@ # License for the specific language governing permissions and limitations # under the License. -from __future__ import print_function - import sys import time diff --git a/cinderclient/tests/unit/fakes.py b/cinderclient/tests/unit/fakes.py index 61d19048d..018a75d69 100644 --- a/cinderclient/tests/unit/fakes.py +++ b/cinderclient/tests/unit/fakes.py @@ -19,8 +19,6 @@ places where actual behavior differs from the spec. """ -from __future__ import print_function - def assert_has_keys(dict, required=None, optional=None): required = required or [] diff --git a/cinderclient/utils.py b/cinderclient/utils.py index 28c458dc0..45d193988 100644 --- a/cinderclient/utils.py +++ b/cinderclient/utils.py @@ -12,8 +12,6 @@ # 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 __future__ import print_function import collections import os diff --git a/cinderclient/v2/shell.py b/cinderclient/v2/shell.py index b83175e8a..d41e014fb 100644 --- a/cinderclient/v2/shell.py +++ b/cinderclient/v2/shell.py @@ -14,8 +14,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import print_function - import argparse import collections import copy diff --git a/cinderclient/v3/shell.py b/cinderclient/v3/shell.py index 8c95b76d6..70f004164 100644 --- a/cinderclient/v3/shell.py +++ b/cinderclient/v3/shell.py @@ -14,8 +14,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import print_function - import argparse import collections import os diff --git a/tools/install_venv_common.py b/tools/install_venv_common.py index 3b7ac1065..0322c1845 100644 --- a/tools/install_venv_common.py +++ b/tools/install_venv_common.py @@ -22,8 +22,6 @@ Synced in from openstack-common """ -from __future__ import print_function - import optparse import os import subprocess From 2d3bcebf7fc0f053072a93f61d866b2d35a818a6 Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Tue, 2 Jun 2020 17:35:53 -0500 Subject: [PATCH 028/159] Clean up some old v1 API references This removes some code that was still pointing to the V1 API. No need to add release note or additional announcements since this was all removed long ago, and if anyone would have gone down this code path it would have just blown up trying to import the v1 code that is no longer there. Change-Id: I7d239d3fe3d879e4c391e83cae1e394cf1f5e6eb Signed-off-by: Sean McGinnis --- cinderclient/shell.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/cinderclient/shell.py b/cinderclient/shell.py index acbccdad5..e11478bd5 100644 --- a/cinderclient/shell.py +++ b/cinderclient/shell.py @@ -48,7 +48,6 @@ DEFAULT_MAJOR_OS_VOLUME_API_VERSION = "3" DEFAULT_CINDER_ENDPOINT_TYPE = 'publicURL' -V1_SHELL = 'cinderclient.v1.shell' V2_SHELL = 'cinderclient.v2.shell' V3_SHELL = 'cinderclient.v3.shell' HINT_HELP_MSG = (" [hint: use '--os-volume-api-version' flag to show help " @@ -354,12 +353,10 @@ def get_subcommand_parser(self, version, do_help=False, input_args=None): self.subcommands = {} subparsers = parser.add_subparsers(metavar='') - if version.ver_major == 2: - actions_module = importutils.import_module(V2_SHELL) - elif version.ver_major == 3: + if version.ver_major == 3: actions_module = importutils.import_module(V3_SHELL) else: - actions_module = importutils.import_module(V1_SHELL) + actions_module = importutils.import_module(V2_SHELL) self._find_actions(subparsers, actions_module, version, do_help, input_args) From 1021aee31f5c0df9e02f71f9827797dacd568e24 Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Wed, 3 Jun 2020 21:19:59 -0500 Subject: [PATCH 029/159] Add directive to document CLI Our CLI docs are very out of date. These used to be generated by the docs team with some tooling they had. Since the docs moved in-repo, that tooling has gone away, and for the most part no one has done any updates to the CLI docs. This adds a sphinx directive that will generate these docs every time the docs are built. This way, whenever someone makes a CLI change, they do not need to have to know to also edit a documentation file to match their change. Any code changes will automatically be picked up and reflected in the docs. Change-Id: I4406872ab6e9335e338b710e492171580df74fa5 Signed-off-by: Sean McGinnis --- cinderclient/v2/contrib/list_extensions.py | 4 +- cinderclient/v3/shell.py | 2 + doc/ext/__init__.py | 0 doc/ext/cli.py | 187 + doc/source/cli/details.rst | 4470 +------------------- doc/source/conf.py | 6 +- 6 files changed, 193 insertions(+), 4476 deletions(-) create mode 100644 doc/ext/__init__.py create mode 100644 doc/ext/cli.py diff --git a/cinderclient/v2/contrib/list_extensions.py b/cinderclient/v2/contrib/list_extensions.py index cd25b7656..937d34b53 100644 --- a/cinderclient/v2/contrib/list_extensions.py +++ b/cinderclient/v2/contrib/list_extensions.py @@ -38,9 +38,7 @@ def show_all(self): def do_list_extensions(client, _args): - """ - Lists all available os-api extensions. - """ + """Lists all available os-api extensions.""" extensions = client.list_extensions.show_all() fields = ["Name", "Summary", "Alias", "Updated"] utils.print_list(extensions, fields) diff --git a/cinderclient/v3/shell.py b/cinderclient/v3/shell.py index 8c95b76d6..d2ff83822 100644 --- a/cinderclient/v3/shell.py +++ b/cinderclient/v3/shell.py @@ -2410,6 +2410,7 @@ def do_version_list(cs, args): default='', help='Prefix for the log. ie: "cinder.volume.drivers.".') def do_service_set_log(cs, args): + """Sets the service log level.""" cs.services.set_log_levels(args.level, args.binary, args.server, args.prefix) @@ -2427,6 +2428,7 @@ def do_service_set_log(cs, args): default='', help='Prefix for the log. ie: "sqlalchemy.".') def do_service_get_log(cs, args): + """Gets the service log level.""" log_levels = cs.services.get_log_levels(args.binary, args.server, args.prefix) columns = ('Binary', 'Host', 'Prefix', 'Level') diff --git a/doc/ext/__init__.py b/doc/ext/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/doc/ext/cli.py b/doc/ext/cli.py new file mode 100644 index 000000000..bf24faab0 --- /dev/null +++ b/doc/ext/cli.py @@ -0,0 +1,187 @@ +# 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. + +"""Sphinx extension to generate CLI documentation.""" + +from docutils import nodes +from docutils.parsers import rst +from docutils.parsers.rst import directives +from docutils import statemachine as sm +from sphinx.util import logging +from sphinx.util import nested_parse_with_titles + +from cinderclient import api_versions +from cinderclient import shell + +LOG = logging.getLogger(__name__) + + +class CLIDocsDirective(rst.Directive): + """Directive to generate CLI details into docs output.""" + + def _get_usage_lines(self, usage, append_value=None): + """Breaks usage output into separate lines.""" + results = [] + lines = usage.split('\n') + + indent = 0 + if '[' in lines[0]: + indent = lines[0].index('[') + + for line in lines: + if line.strip(): + results.append(line) + + if append_value: + results.append(' {}{}'.format(' ' * indent, append_value)) + + return results + + def _format_description_lines(self, description): + """Formats option description into formatted lines.""" + desc = description.split('\n') + return [line.strip() for line in desc if line.strip() != ''] + + def run(self): + """Load and document the current config options.""" + + cindershell = shell.OpenStackCinderShell() + parser = cindershell.get_base_parser() + + api_version = api_versions.APIVersion(api_versions.MAX_VERSION) + LOG.info('Generating CLI docs %s', api_version) + + cindershell.get_subcommand_parser(api_version, False, []) + + result = sm.ViewList() + source = '<{}>'.format(__name__) + + result.append('.. _cinder_command_usage:', source) + result.append('', source) + result.append('cinder usage', source) + result.append('------------', source) + result.append('', source) + result.append('.. code-block:: console', source) + result.append('', source) + result.append('', source) + usage = self._get_usage_lines( + parser.format_usage(), ' ...') + for line in usage: + result.append(' {}'.format(line), source) + result.append('', source) + + result.append('.. _cinder_command_options:', source) + result.append('', source) + result.append('Optional Arguments', source) + result.append('~~~~~~~~~~~~~~~~~~', source) + result.append('', source) + + # This accesses a private variable from argparse. That's a little + # risky, but since this is just for the docs and not "production" code, + # and since this variable hasn't changed in years, it's a calculated + # risk to make this documentation generation easier. But if something + # suddenly breaks, check here first. + actions = sorted(parser._actions, key=lambda x: x.option_strings[0]) + for action in actions: + if action.help == '==SUPPRESS==': + continue + opts = ', '.join(action.option_strings) + result.append('``{}``'.format(opts), source) + result.append(' {}'.format(action.help), source) + result.append('', source) + + result.append('', source) + result.append('.. _cinder_commands:', source) + result.append('', source) + result.append('Commands', source) + result.append('~~~~~~~~', source) + result.append('', source) + + for cmd in cindershell.subcommands: + if 'completion' in cmd: + continue + result.append('``{}``'.format(cmd), source) + subcmd = cindershell.subcommands[cmd] + description = self._format_description_lines(subcmd.description) + result.append(' {}'.format(description[0]), source) + result.append('', source) + + result.append('', source) + result.append('.. _cinder_command_details:', source) + result.append('', source) + result.append('Command Details', source) + result.append('---------------', source) + result.append('', source) + + for cmd in cindershell.subcommands: + if 'completion' in cmd: + continue + subcmd = cindershell.subcommands[cmd] + result.append('.. _cinder{}:'.format(cmd), source) + result.append('', source) + result.append(subcmd.prog, source) + result.append('~' * len(subcmd.prog), source) + result.append('', source) + result.append('.. code-block:: console', source) + result.append('', source) + usage = self._get_usage_lines(subcmd.format_usage()) + for line in usage: + result.append(' {}'.format(line), source) + result.append('', source) + description = self._format_description_lines(subcmd.description) + result.append(description[0], source) + result.append('', source) + + if len(subcmd._actions) == 0: + continue + + positional = [] + optional = [] + for action in subcmd._actions: + if len(action.option_strings): + if (action.option_strings[0] != '-h' and + action.help != '==SUPPRESS=='): + optional.append(action) + else: + positional.append(action) + + if positional: + result.append('**Positional arguments:**', source) + result.append('', source) + for action in positional: + result.append('``{}``'.format(action.metavar), source) + result.append(' {}'.format(action.help), source) + result.append('', source) + + if optional: + result.append('**Optional arguments:**', source) + result.append('', source) + for action in optional: + result.append('``{} {}``'.format( + ', '.join(action.option_strings), action.metavar), + source) + result.append(' {}'.format(action.help), source) + result.append('', source) + + node = nodes.section() + node.document = self.state.document + nested_parse_with_titles(self.state, result, node) + return node.children + + +def setup(app): + app.add_directive('cli-docs', CLIDocsDirective) + return { + 'version': '1.0', + 'parallel_read_safe': True, + 'parallel_write_safe': True, + } diff --git a/doc/source/cli/details.rst b/doc/source/cli/details.rst index ffa800ccc..4311ef005 100644 --- a/doc/source/cli/details.rst +++ b/doc/source/cli/details.rst @@ -11,4472 +11,4 @@ For help on a specific :command:`cinder` command, enter: $ cinder help COMMAND -.. _cinder_command_usage: - -cinder usage -~~~~~~~~~~~~ - -.. code-block:: console - - usage: cinder [--version] [-d] [--os-auth-system ] - [--os-auth-type ] [--service-type ] - [--service-name ] - [--volume-service-name ] - [--os-endpoint-type ] - [--endpoint-type ] - [--os-volume-api-version ] - [--os-endpoint ] - [--retries ] [--profile HMAC_KEY] - [--os-auth-strategy ] - [--os-username ] [--os-password ] - [--os-tenant-name ] - [--os-tenant-id ] [--os-auth-url ] - [--os-user-id ] - [--os-user-domain-id ] - [--os-user-domain-name ] - [--os-project-id ] - [--os-project-name ] - [--os-project-domain-id ] - [--os-project-domain-name ] - [--os-region-name ] [--os-token ] - [--os-url ] [--insecure] [--os-cacert ] - [--os-cert ] [--os-key ] [--timeout ] - ... - -**Subcommands:** - -``absolute-limits`` - Lists absolute limits for a user. - -``api-version`` - Display the server API version information. (Supported - by - API - versions - 3.0 - - - 3.latest) - [hint: - use - '--os-volume-api-version' - flag - to - show - help - message - for - proper version] - -``attachment-create`` - Create an attachment for a cinder volume. (Supported - by - API - versions - 3.27 - - - 3.latest) - [hint: - use - '--os-volume-api-version' - flag - to - show - help - message - for - proper version] - -``attachment-delete`` - Delete an attachment for a cinder volume. (Supported - by - API - versions - 3.27 - - - 3.latest) - [hint: - use - '--os-volume-api-version' - flag - to - show - help - message - for - proper version] - -``attachment-list`` - Lists all attachments. (Supported by API versions 3.27 - - 3.latest) [hint: use '--os-volume-api-version' flag - to show help message for proper version] - -``attachment-show`` - Show detailed information for attachment. (Supported - by - API - versions - 3.27 - - - 3.latest) - [hint: - use - '--os-volume-api-version' - flag - to - show - help - message - for - proper version] - -``attachment-update`` - Update an attachment for a cinder volume. (Supported - by - API - versions - 3.27 - - - 3.latest) - [hint: - use - '--os-volume-api-version' - flag - to - show - help - message - for - proper version] - -``availability-zone-list`` - Lists all availability zones. - -``backup-create`` - Creates a volume backup. - -``backup-delete`` - Removes one or more backups. - -``backup-export`` - Export backup metadata record. - -``backup-import`` - Import backup metadata record. - -``backup-list`` - Lists all backups. - -``backup-reset-state`` - Explicitly updates the backup state. - -``backup-restore`` - Restores a backup. - -``backup-show`` - Shows backup details. - -``backup-update`` - Renames - a - backup. - (Supported - by - API - versions - 3.9 - -3.latest) - [hint: - use - '--os-volume-api-version' - flag - to - show help message for proper version] - -``cgsnapshot-create`` - Creates a cgsnapshot. - -``cgsnapshot-delete`` - Removes one or more cgsnapshots. - -``cgsnapshot-list`` - Lists all cgsnapshots. - -``cgsnapshot-show`` - Shows cgsnapshot details. - -``cluster-disable`` - Disables clustered services. (Supported by API - versions - 3.7 - - - 3.latest) - [hint: - use - '--os-volume-api-version' - flag - to - show - help - message - for - proper - version] - -``cluster-enable`` - Enables clustered services. (Supported by API versions - 3.7 - 3.latest) [hint: use '--os-volume-api-version' - flag to show help message for proper version] - -``cluster-list`` - Lists clustered services with optional filtering. - (Supported by API versions 3.7 - 3.latest) [hint: use - '--os-volume-api-version' flag to show help message - for proper version] - -``cluster-show`` - Show detailed information on a clustered service. - (Supported by API versions 3.7 - 3.latest) [hint: use - '--os-volume-api-version' flag to show help message - for proper version] - -``consisgroup-create`` - Creates a consistency group. - -``consisgroup-create-from-src`` - Creates a consistency group from a cgsnapshot or a - source CG. - -``consisgroup-delete`` - Removes one or more consistency groups. - -``consisgroup-list`` - Lists all consistency groups. - -``consisgroup-show`` - Shows details of a consistency group. - -``consisgroup-update`` - Updates a consistency group. - -``create`` - Creates a volume. - -``credentials`` - Shows user credentials returned from auth. - -``delete`` - Removes one or more volumes. - -``encryption-type-create`` - Creates encryption type for a volume type. Admin only. - -``encryption-type-delete`` - Deletes encryption type for a volume type. Admin only. - -``encryption-type-list`` - Shows encryption type details for volume types. Admin - only. - -``encryption-type-show`` - Shows encryption type details for a volume type. Admin - only. - -``encryption-type-update`` - Update encryption type information for a volume type - (Admin Only). - -``endpoints`` - Discovers endpoints registered by authentication - service. - -``extend`` - Attempts to extend size of an existing volume. - -``extra-specs-list`` - Lists current volume types and extra specs. - -``failover-host`` - Failover a replicating cinder-volume host. - -``force-delete`` - Attempts force-delete of volume, regardless of state. - -``freeze-host`` - Freeze and disable the specified cinder-volume host. - -``get-capabilities`` - Show backend volume stats and properties. Admin only. - -``get-pools`` - Show pool information for backends. Admin only. - -``group-create`` - Creates - a - group. - (Supported - by - API - versions - 3.13 - -3.latest) - [hint: - use - '--os-volume-api-version' - flag - to - show help message for proper version] - -``group-create-from-src`` - Creates a group from a group snapshot or a source - group. (Supported by API versions 3.14 - 3.latest) - [hint: use '--os-volume-api-version' flag to show help - message for proper version] - -``group-delete`` - Removes one or more groups. (Supported by API versions - 3.13 - 3.latest) [hint: use '--os-volume-api-version' - flag to show help message for proper version] - -``group-list`` - Lists - all - groups. - (Supported - by - API - versions - 3.13 - -3.latest) - [hint: - use - '--os-volume-api-version' - flag - to - show help message for proper version] - -``group-show`` - Shows details of a group. (Supported by API versions - 3.13 - 3.latest) [hint: use '--os-volume-api-version' - flag to show help message for proper version] - -``group-snapshot-create`` - Creates a group snapshot. (Supported by API versions - 3.14 - 3.latest) [hint: use '--os-volume-api-version' - flag to show help message for proper version] - -``group-snapshot-delete`` - Removes one or more group snapshots. (Supported by API - versions - 3.14 - - - 3.latest) - [hint: - use - '--os-volume-api-version' - flag - to - show - help - message - for - proper - version] - -``group-snapshot-list`` - Lists all group snapshots. (Supported by API versions - 3.14 - 3.latest) [hint: use '--os-volume-api-version' - flag to show help message for proper version] - -``group-snapshot-show`` - Shows group snapshot details. (Supported by API - versions - 3.14 - - - 3.latest) - [hint: - use - '--os-volume-api-version' - flag - to - show - help - message - for - proper - version] - -``group-specs-list`` - Lists current group types and specs. (Supported by API - versions - 3.11 - - - 3.latest) - [hint: - use - '--os-volume-api-version' - flag - to - show - help - message - for - proper - version] - -``group-type-create`` - Creates a group type. (Supported by API versions 3.11 - - 3.latest) [hint: use '--os-volume-api-version' flag - to show help message for proper version] - -``group-type-default`` - List the default group type. (Supported by API - versions - 3.11 - - - 3.latest) - [hint: - use - '--os-volume-api-version' - flag - to - show - help - message - for - proper - version] - -``group-type-delete`` - Deletes group type or types. (Supported by API - versions - 3.11 - - - 3.latest) - [hint: - use - '--os-volume-api-version' - flag - to - show - help - message - for - proper - version] - -``group-type-key`` - Sets or unsets group_spec for a group type. (Supported - by - API - versions - 3.11 - - - 3.latest) - [hint: - use - '--os-volume-api-version' - flag - to - show - help - message - for - proper version] - -``group-type-list`` - Lists available 'group types'. (Admin only will see - private - types) - (Supported - by - API - versions - 3.11 - -3.latest) - [hint: - use - '--os-volume-api-version' - flag - to - show help message for proper version] - -``group-type-show`` - Show group type details. (Supported by API versions - 3.11 - 3.latest) [hint: use '--os-volume-api-version' - flag to show help message for proper version] - -``group-type-update`` - Updates group type name, description, and/or - is_public. (Supported by API versions 3.11 - 3.latest) - [hint: use '--os-volume-api-version' flag to show help - message for proper version] - -``group-update`` - Updates - a - group. - (Supported - by - API - versions - 3.13 - -3.latest) - [hint: - use - '--os-volume-api-version' - flag - to - show help message for proper version] - -``image-metadata`` - Sets or deletes volume image metadata. - -``image-metadata-show`` - Shows volume image metadata. - -``list`` - Lists all volumes. - -``list-filters`` - (Supported by API versions 3.33 - 3.latest) [hint: use - '--os-volume-api-version' flag to show help message - for proper version] - -``manage`` - Manage an existing volume. - -``manageable-list`` - Lists all manageable volumes. (Supported by API - versions - 3.8 - - - 3.latest) - [hint: - use - '--os-volume-api-version' - flag - to - show - help - message - for - proper - version] - -``message-delete`` - Removes one or more messages. (Supported by API - versions - 3.3 - - - 3.latest) - [hint: - use - '--os-volume-api-version' - flag - to - show - help - message - for - proper - version] - -``message-list`` - Lists - all - messages. - (Supported - by - API - versions - 3.3 - -3.latest) - [hint: - use - '--os-volume-api-version' - flag - to - show help message for proper version] - -``message-show`` - Shows message details. (Supported by API versions 3.3 - - 3.latest) [hint: use '--os-volume-api-version' flag - to show help message for proper version] - -``metadata`` - Sets or deletes volume metadata. - -``metadata-show`` - Shows volume metadata. - -``metadata-update-all`` - Updates volume metadata. - -``migrate`` - Migrates volume to a new host. - -``qos-associate`` - Associates qos specs with specified volume type. - -``qos-create`` - Creates a qos specs. - -``qos-delete`` - Deletes a specified qos specs. - -``qos-disassociate`` - Disassociates qos specs from specified volume type. - -``qos-disassociate-all`` - Disassociates qos specs from all its associations. - -``qos-get-association`` - Lists all associations for specified qos specs. - -``qos-key`` - Sets or unsets specifications for a qos spec. - -``qos-list`` - Lists qos specs. - -``qos-show`` - Shows qos specs details. - -``quota-class-show`` - Lists quotas for a quota class. - -``quota-class-update`` - Updates quotas for a quota class. - -``quota-defaults`` - Lists default quotas for a tenant. - -``quota-delete`` - Delete the quotas for a tenant. - -``quota-show`` - Lists quotas for a tenant. - -``quota-update`` - Updates quotas for a tenant. - -``quota-usage`` - Lists quota usage for a tenant. - -``rate-limits`` - Lists rate limits for a user. - -``readonly-mode-update`` - Updates volume read-only access-mode flag. - -``rename`` - Renames a volume. - -``reset-state`` - Explicitly updates the entity state in the Cinder - database. - -``retype`` - Changes the volume type for a volume. - -``service-disable`` - Disables the service. - -``service-enable`` - Enables the service. - -``service-list`` - Lists all services. Filter by host and service binary. - (Supported by API versions 3.0 - 3.latest) [hint: use - '--os-volume-api-version' flag to show help message - for proper version] - -``set-bootable`` - Update bootable status of a volume. - -``show`` - Shows volume details. - -``snapshot-create`` - Creates a snapshot. - -``snapshot-delete`` - Removes one or more snapshots. - -``snapshot-list`` - Lists all snapshots. - -``snapshot-manage`` - Manage an existing snapshot. - -``snapshot-manageable-list`` - Lists all manageable snapshots. (Supported by API - versions - 3.8 - - - 3.latest) - [hint: - use - '--os-volume-api-version' - flag - to - show - help - message - for - proper - version] - -``snapshot-metadata`` - Sets or deletes snapshot metadata. - -``snapshot-metadata-show`` - Shows snapshot metadata. - -``snapshot-metadata-update-all`` - Updates snapshot metadata. - -``snapshot-rename`` - Renames a snapshot. - -``snapshot-reset-state`` - Explicitly updates the snapshot state. - -``snapshot-show`` - Shows snapshot details. - -``snapshot-unmanage`` - Stop managing a snapshot. - -``thaw-host`` - Thaw and enable the specified cinder-volume host. - -``transfer-accept`` - Accepts a volume transfer. - -``transfer-create`` - Creates a volume transfer. - -``transfer-delete`` - Undoes a transfer. - -``transfer-list`` - Lists all transfers. - -``transfer-show`` - Shows transfer details. - -``type-access-add`` - Adds volume type access for the given project. - -``type-access-list`` - Print access information about the given volume type. - -``type-access-remove`` - Removes volume type access for the given project. - -``type-create`` - Creates a volume type. - -``type-default`` - List the default volume type. - -``type-delete`` - Deletes volume type or types. - -``type-key`` - Sets or unsets extra_spec for a volume type. - -``type-list`` - Lists available 'volume types'. - -``type-show`` - Show volume type details. - -``type-update`` - Updates volume type name, description, and/or - is_public. - -``unmanage`` - Stop managing a volume. - -``upload-to-image`` - Uploads volume to Image Service as an image. - -``version-list`` - List all API versions. (Supported by API versions 3.0 - - 3.latest) [hint: use '--os-volume-api-version' flag - to show help message for proper version] - -``bash-completion`` - Prints arguments for bash_completion. - -``help`` - Shows help about this program or one of its - subcommands. - -``list-extensions`` - -.. _cinder_command_options: - -cinder optional arguments -~~~~~~~~~~~~~~~~~~~~~~~~~ - -``--version`` - show program's version number and exit - -``-d, --debug`` - Shows debugging output. - -``--os-auth-system `` - **DEPRECATED!** Use --os-auth-type. Defaults to - ``env[OS_AUTH_SYSTEM]``. - -``--os-auth-type `` - Defaults to ``env[OS_AUTH_TYPE]``. - -``--service-type `` - Service type. For most actions, default is volume. - -``--service-name `` - Service name. Default= ``env[CINDER_SERVICE_NAME]``. - -``--volume-service-name `` - Volume service name. - Default= ``env[CINDER_VOLUME_SERVICE_NAME]``. - -``--os-endpoint-type `` - Endpoint type, which is publicURL or internalURL. - Default= ``env[OS_ENDPOINT_TYPE]`` or nova - ``env[CINDER_ENDPOINT_TYPE]`` or publicURL. - -``--endpoint-type `` - **DEPRECATED!** Use --os-endpoint-type. - -``--os-volume-api-version `` - Block Storage API version. Accepts X, X.Y (where X is - major and Y is minor - part).Default= ``env[OS_VOLUME_API_VERSION]``. - -``--os-endpoint `` - Use this API endpoint instead of the Service Catalog. - Default=``env[CINDER_ENDPOINT]`` - -``--retries `` - Number of retries. - -``--profile HMAC_KEY`` - HMAC key to use for encrypting context data for - performance profiling of operation. This key needs to - match the one configured on the cinder api server. - Without key the profiling will not be triggered even - if osprofiler is enabled on server side. - -``--os-auth-strategy `` - Authentication strategy (Env: OS_AUTH_STRATEGY, - default keystone). For now, any other value will - disable the authentication. - -``--os-username `` - OpenStack user name. Default= ``env[OS_USERNAME]``. - -``--os-password `` - Password for OpenStack user. Default= ``env[OS_PASSWORD]``. - -``--os-tenant-name `` - Tenant name. Default= ``env[OS_TENANT_NAME]``. - -``--os-tenant-id `` - ID for the tenant. Default= ``env[OS_TENANT_ID]``. - -``--os-auth-url `` - URL for the authentication service. - Default= ``env[OS_AUTH_URL]``. - -``--os-user-id `` - Authentication user ID (Env: OS_USER_ID). - -``--os-user-domain-id `` - OpenStack user domain ID. Defaults to - ``env[OS_USER_DOMAIN_ID]``. - -``--os-user-domain-name `` - OpenStack user domain name. Defaults to - ``env[OS_USER_DOMAIN_NAME]``. - -``--os-project-id `` - Another way to specify tenant ID. This option is - mutually exclusive with --os-tenant-id. Defaults to - ``env[OS_PROJECT_ID]``. - -``--os-project-name `` - Another way to specify tenant name. This option is - mutually exclusive with --os-tenant-name. Defaults to - ``env[OS_PROJECT_NAME]``. - -``--os-project-domain-id `` - Defaults to ``env[OS_PROJECT_DOMAIN_ID]``. - -``--os-project-domain-name `` - Defaults to ``env[OS_PROJECT_DOMAIN_NAME]``. - -``--os-region-name `` - Region name. Default= ``env[OS_REGION_NAME]``. - -``--os-token `` - Defaults to ``env[OS_TOKEN]``. - -``--os-url `` - Defaults to ``env[OS_URL]``. - -.. _cinder_absolute-limits: - -cinder absolute-limits ----------------------- - -.. code-block:: console - - usage: cinder absolute-limits [] - -Lists absolute limits for a user. - -**Positional arguments:** - -```` - Display information for a single tenant (Admin only). - -.. _cinder_api-version: - -cinder api-version ------------------- - -.. code-block:: console - - usage: cinder api-version - -Display the server API version information. - -.. _cinder_attachment-create: - -cinder attachment-create ------------------------- - -.. code-block:: console - - usage: cinder attachment-create [--connect ] - [--initiator ] [--ip ] - [--host ] [--platform ] - [--ostype ] [--multipath ] - [--mountpoint ] - - -Create an attachment for a cinder volume. - -**Positional arguments:** - -```` - Name or ID of volume or volumes to attach. - -```` - ID of server attaching to. - -**Optional arguments:** - -``--connect `` - Make an active connection using provided connector - info (True or False). - -``--initiator `` - iqn of the initiator attaching to. Default=None. - -``--ip `` - ip of the system attaching to. Default=None. - -``--host `` - Name of the host attaching to. Default=None. - -``--platform `` - Platform type. Default=x86_64. - -``--ostype `` - OS type. Default=linux2. - -``--multipath `` - Use multipath. Default=False. - -``--mountpoint `` - Mountpoint volume will be attached at. Default=None. - -.. _cinder_attachment-delete: - -cinder attachment-delete ------------------------- - -.. code-block:: console - - usage: cinder attachment-delete [ ...] - -Delete an attachment for a cinder volume. - -**Positional arguments:** - -```` - ID of attachment or attachments to delete. - -.. _cinder_attachment-list: - -cinder attachment-list ----------------------- - -.. code-block:: console - - usage: cinder attachment-list [--all-tenants [<0|1>]] - [--volume-id ] [--status ] - [--marker ] [--limit ] - [--sort [:]] - [--tenant []] - [--filters [ [ ...]]] - -Lists all attachments. - -**Optional arguments:** - -``--all-tenants [<0|1>]`` - Shows details for all tenants. Admin only. - -``--volume-id `` - Filters results by a volume ID. Default=None. This - option is deprecated and will be removed in newer - release. Please use '--filters' option which is - introduced since 3.33 instead. - -``--status `` - Filters results by a status. Default=None. This option - is deprecated and will be removed in newer release. - Please use '--filters' option which is introduced - since 3.33 instead. - -``--marker `` - Begin returning attachments that appear later in - attachment list than that represented by this id. - Default=None. - -``--limit `` - Maximum number of attachments to return. Default=None. - -``--sort [:]`` - Comma-separated list of sort keys and directions in - the form of [:]. Valid keys: id, - status, size, availability_zone, name, bootable, - created_at, reference. Default=None. - -``--tenant []`` - Display information from single tenant (Admin only). - -``--filters [ [ ...]]`` - Filter - key - and - value - pairs. - Please - use - 'cinder - list-filters' - to - check - enabled - filters - from - server, - Default=None. (Supported by API version 3.33 and - later) - -.. _cinder_attachment-show: - -cinder attachment-show ----------------------- - -.. code-block:: console - - usage: cinder attachment-show - -Show detailed information for attachment. - -**Positional arguments:** - -```` - ID of attachment. - -.. _cinder_attachment-update: - -cinder attachment-update ------------------------- - -.. code-block:: console - - usage: cinder attachment-update [--initiator ] [--ip ] - [--host ] [--platform ] - [--ostype ] [--multipath ] - [--mountpoint ] - - -Update an attachment for a cinder volume. This call is designed to be more of -an attachment completion than anything else. It expects the value of a -connector object to notify the driver that the volume is going to be connected -and where it's being connected to. - -**Positional arguments:** - -```` - ID of attachment. - -**Optional arguments:** - -``--initiator `` - iqn of the initiator attaching to. Default=None. - -``--ip `` - ip of the system attaching to. Default=None. - -``--host `` - Name of the host attaching to. Default=None. - -``--platform `` - Platform type. Default=x86_64. - -``--ostype `` - OS type. Default=linux2. - -``--multipath `` - Use multipath. Default=False. - -``--mountpoint `` - Mountpoint volume will be attached at. Default=None. - -.. _cinder_availability-zone-list: - -cinder availability-zone-list ------------------------------ - -.. code-block:: console - - usage: cinder availability-zone-list - -Lists all availability zones. - -.. _cinder_backup-create: - -cinder backup-create --------------------- - -.. code-block:: console - - usage: cinder backup-create [--container ] [--name ] - [--description ] [--incremental] - [--force] [--snapshot-id ] - - -Creates a volume backup. - -**Positional arguments:** - -```` - Name or ID of volume to backup. - -**Optional arguments:** - -``--container `` - Backup container name. Default=None. - -``--name `` - Backup name. Default=None. - -``--description `` - Backup description. Default=None. - -``--incremental`` - Incremental backup. Default=False. - -``--force`` - Allows or disallows backup of a volume when the volume - is attached to an instance. If set to True, backs up - the - volume - whether - its - status - is - "available" - or - "in-use". - The - backup - of - an - "in-use" - volume - means - your - data - is crash consistent. Default=False. - -``--snapshot-id `` - ID of snapshot to backup. Default=None. - -.. _cinder_backup-delete: - -cinder backup-delete --------------------- - -.. code-block:: console - - usage: cinder backup-delete [--force] [ ...] - -Removes one or more backups. - -**Positional arguments:** - -```` - Name or ID of backup(s) to delete. - -**Optional arguments:** - -``--force`` - Allows deleting backup of a volume when its status is other than - "available" or "error". Default=False. - -.. _cinder_backup-export: - -cinder backup-export --------------------- - -.. code-block:: console - - usage: cinder backup-export - -Export backup metadata record. - -**Positional arguments:** - -```` - ID of the backup to export. - -.. _cinder_backup-import: - -cinder backup-import --------------------- - -.. code-block:: console - - usage: cinder backup-import - -Import backup metadata record. - -**Positional arguments:** - -```` - Backup service to use for importing the backup. - -```` - Backup URL for importing the backup metadata. - -.. _cinder_backup-list: - -cinder backup-list ------------------- - -.. code-block:: console - - usage: cinder backup-list [--all-tenants []] [--name ] - [--status ] [--volume-id ] - [--marker ] [--limit ] - [--sort [:]] - [--filters [ [ ...]]] - -Lists all backups. - -**Optional arguments:** - -``--all-tenants []`` - Shows details for all tenants. Admin only. - -``--name `` - Filters results by a name. Default=None. This option - is deprecated and will be removed in newer release. - Please use '--filters' option which is introduced - since 3.33 instead. - -``--status `` - Filters results by a status. Default=None. This option - is deprecated and will be removed in newer release. - Please use '--filters' option which is introduced - since 3.33 instead. - -``--volume-id `` - Filters results by a volume ID. Default=None. This - option is deprecated and will be removed in newer - release. Please use '--filters' option which is - introduced since 3.33 instead. - -``--marker `` - Begin returning backups that appear later in the - backup list than that represented by this id. - Default=None. - -``--limit `` - Maximum number of backups to return. Default=None. - -``--sort [:]`` - Comma-separated list of sort keys and directions in - the form of [:]. Valid keys: id, - status, size, availability_zone, name, bootable, - created_at, reference. Default=None. - -``--filters [ [ ...]]`` - Filter - key - and - value - pairs. - Please - use - 'cinder - list-filters' - to - check - enabled - filters - from - server, - Default=None. (Supported by API version 3.33 and - later) - -.. _cinder_backup-reset-state: - -cinder backup-reset-state -------------------------- - -.. code-block:: console - - usage: cinder backup-reset-state [--state ] [ ...] - -Explicitly updates the backup state. - -**Positional arguments:** - -```` - Name or ID of the backup to modify. - -**Optional arguments:** - -``--state `` - The state to assign to the backup. Valid values are - "available", "error". Default=available. - -.. _cinder_backup-restore: - -cinder backup-restore ---------------------- - -.. code-block:: console - - usage: cinder backup-restore [--volume ] [--name ] - -Restores a backup. - -**Positional arguments:** - -```` - Name or ID of backup to restore. - -**Optional arguments:** - -``--volume `` - Name or ID of existing volume to which to restore. This - is mutually exclusive with --name and takes priority. - Default=None. - -``--name `` - Use the name for new volume creation to restore. This is - mutually exclusive with --volume (or the deprecated - --volume-id) and --volume (or --volume-id) takes - priority. Default=None. - -.. _cinder_backup-show: - -cinder backup-show ------------------- - -.. code-block:: console - - usage: cinder backup-show - -Shows backup details. - -**Positional arguments:** - -```` - Name or ID of backup. - -.. _cinder_backup-update: - -cinder backup-update --------------------- - -.. code-block:: console - - usage: cinder backup-update [--name []] [--description ] - - -Renames a backup. - -**Positional arguments:** - -```` - Name or ID of backup to rename. - -**Optional arguments:** - -``--name []`` - New name for backup. - -``--description `` - Backup description. Default=None. - -.. _cinder_cgsnapshot-create: - -cinder cgsnapshot-create ------------------------- - -.. code-block:: console - - usage: cinder cgsnapshot-create [--name ] [--description ] - - -Creates a cgsnapshot. - -**Positional arguments:** - -```` - Name or ID of a consistency group. - -**Optional arguments:** - -``--name `` - Cgsnapshot name. Default=None. - -``--description `` - Cgsnapshot description. Default=None. - -.. _cinder_cgsnapshot-delete: - -cinder cgsnapshot-delete ------------------------- - -.. code-block:: console - - usage: cinder cgsnapshot-delete [ ...] - -Removes one or more cgsnapshots. - -**Positional arguments:** - -```` - Name or ID of one or more cgsnapshots to be deleted. - -.. _cinder_cgsnapshot-list: - -cinder cgsnapshot-list ----------------------- - -.. code-block:: console - - usage: cinder cgsnapshot-list [--all-tenants [<0|1>]] [--status ] - [--consistencygroup-id ] - -Lists all cgsnapshots. - -**Optional arguments:** - -``--all-tenants [<0|1>]`` - Shows details for all tenants. Admin only. - -``--status `` - Filters results by a status. Default=None. - -``--consistencygroup-id `` - Filters results by a consistency group ID. - Default=None. - -.. _cinder_cgsnapshot-show: - -cinder cgsnapshot-show ----------------------- - -.. code-block:: console - - usage: cinder cgsnapshot-show - -Shows cgsnapshot details. - -**Positional arguments:** - -```` - Name or ID of cgsnapshot. - -.. _cinder_cluster-disable: - -cinder cluster-disable ----------------------- - -.. code-block:: console - - usage: cinder cluster-disable [--reason ] [] - -Disables clustered services. - -**Positional arguments:** - -```` - Binary to filter by. Default: cinder-volume. - -```` - Name of the clustered services to update. - -**Optional arguments:** - -``--reason `` - Reason for disabling clustered service. - -.. _cinder_cluster-enable: - -cinder cluster-enable ---------------------- - -.. code-block:: console - - usage: cinder cluster-enable [] - -Enables clustered services. - -**Positional arguments:** - -```` - Binary to filter by. Default: cinder-volume. - -```` - Name of the clustered services to update. - -.. _cinder_cluster-list: - -cinder cluster-list -------------------- - -.. code-block:: console - - usage: cinder cluster-list [--name ] [--binary ] - [--is-up ] - [--disabled ] - [--num-hosts ] - [--num-down-hosts ] [--detailed] - -Lists clustered services with optional filtering. - -**Optional arguments:** - -``--name `` - Filter by cluster name, without backend will list all - clustered services from the same cluster. - Default=None. - -``--binary `` - Cluster binary. Default=None. - -``--is-up `` - Filter by up/dow status. Default=None. - -``--disabled `` - Filter by disabled status. Default=None. - -``--num-hosts `` - Filter by number of hosts in the cluster. - -``--num-down-hosts `` - Filter by number of hosts that are down. - -``--detailed`` - Get detailed clustered service information - (Default=False). - -.. _cinder_cluster-show: - -cinder cluster-show -------------------- - -.. code-block:: console - - usage: cinder cluster-show [] - -Show detailed information on a clustered service. - -**Positional arguments:** - -```` - Binary to filter by. Default: cinder-volume. - -```` - Name of the clustered service to show. - -.. _cinder_consisgroup-create: - -cinder consisgroup-create -------------------------- - -.. code-block:: console - - usage: cinder consisgroup-create [--name ] [--description ] - [--availability-zone ] - - -Creates a consistency group. - -**Positional arguments:** - -```` - Volume types. - -**Optional arguments:** - -``--name `` - Name of a consistency group. - -``--description `` - Description of a consistency group. Default=None. - -``--availability-zone `` - Availability zone for volume. Default=None. - -.. _cinder_consisgroup-create-from-src: - -cinder consisgroup-create-from-src ----------------------------------- - -.. code-block:: console - - usage: cinder consisgroup-create-from-src [--cgsnapshot ] - [--source-cg ] - [--name ] - [--description ] - -Creates a consistency group from a cgsnapshot or a source CG. - -**Optional arguments:** - -``--cgsnapshot `` - Name or ID of a cgsnapshot. Default=None. - -``--source-cg `` - Name or ID of a source CG. Default=None. - -``--name `` - Name of a consistency group. Default=None. - -``--description `` - Description of a consistency group. Default=None. - -.. _cinder_consisgroup-delete: - -cinder consisgroup-delete -------------------------- - -.. code-block:: console - - usage: cinder consisgroup-delete [--force] - [ ...] - -Removes one or more consistency groups. - -**Positional arguments:** - -```` - Name or ID of one or more consistency groups to be - deleted. - -**Optional arguments:** - -``--force`` - Allows or disallows consistency groups to be deleted. If - the consistency group is empty, it can be deleted - without the force flag. If the consistency group is not - empty, the force flag is required for it to be deleted. - -.. _cinder_consisgroup-list: - -cinder consisgroup-list ------------------------ - -.. code-block:: console - - usage: cinder consisgroup-list [--all-tenants [<0|1>]] - -Lists all consistency groups. - -**Optional arguments:** - -``--all-tenants [<0|1>]`` - Shows details for all tenants. Admin only. - -.. _cinder_consisgroup-show: - -cinder consisgroup-show ------------------------ - -.. code-block:: console - - usage: cinder consisgroup-show - -Shows details of a consistency group. - -**Positional arguments:** - -```` - Name or ID of a consistency group. - -.. _cinder_consisgroup-update: - -cinder consisgroup-update -------------------------- - -.. code-block:: console - - usage: cinder consisgroup-update [--name ] [--description ] - [--add-volumes ] - [--remove-volumes ] - - -Updates a consistency group. - -**Positional arguments:** - -```` - Name or ID of a consistency group. - -**Optional arguments:** - -``--name `` - New name for consistency group. Default=None. - -``--description `` - New description for consistency group. Default=None. - -``--add-volumes `` - UUID of one or more volumes to be added to the - consistency group, separated by commas. Default=None. - -``--remove-volumes `` - UUID of one or more volumes to be removed from the - consistency group, separated by commas. Default=None. - -.. _cinder_create: - -cinder create -------------- - -.. code-block:: console - - usage: cinder create [--consisgroup-id ] - [--group-id ] [--snapshot-id ] - [--source-volid ] - [--source-replica ] - [--image-id ] [--image ] [--name ] - [--description ] - [--volume-type ] - [--availability-zone ] - [--metadata [ [ ...]]] - [--hint ] [--allow-multiattach] - [] - -Creates a volume. - -**Positional arguments:** - -```` - Size of volume, in GiBs. (Required unless snapshot-id - /source-volid is specified). - -**Optional arguments:** - -``--consisgroup-id `` - ID of a consistency group where the new volume belongs - to. Default=None. - -``--group-id `` - ID of a group where the new volume belongs to. - Default=None. (Supported by API version 3.13 and - later) - -``--snapshot-id `` - Creates volume from snapshot ID. Default=None. - -``--source-volid `` - Creates volume from volume ID. Default=None. - -``--source-replica `` - Creates volume from replicated volume ID. - Default=None. - -``--image-id `` - Creates volume from image ID. Default=None. - -``--image `` - Creates a volume from image (ID or name). - Default=None. - -``--name `` - Volume name. Default=None. - -``--description `` - Volume description. Default=None. - -``--volume-type `` - Volume type. Default=None. - -``--availability-zone `` - Availability zone for volume. Default=None. - -``--metadata [ [ ...]]`` - Metadata key and value pairs. Default=None. - -``--hint `` - Scheduler hint, like in nova. - -``--allow-multiattach`` - Allow volume to be attached more than once. - Default=False - -.. _cinder_credentials: - -cinder credentials ------------------- - -.. code-block:: console - - usage: cinder credentials - -Shows user credentials returned from auth. - -.. _cinder_delete: - -cinder delete -------------- - -.. code-block:: console - - usage: cinder delete [--cascade] [ ...] - -Removes one or more volumes. - -**Positional arguments:** - -```` - Name or ID of volume or volumes to delete. - -**Optional arguments:** - -``--cascade`` - Remove any snapshots along with volume. Default=False. - -.. _cinder_encryption-type-create: - -cinder encryption-type-create ------------------------------ - -.. code-block:: console - - usage: cinder encryption-type-create [--cipher ] - [--key-size ] - [--control-location ] - - -Creates encryption type for a volume type. Admin only. - -**Positional arguments:** - -```` - Name or ID of volume type. - -```` - The class that provides encryption support. For - example, LuksEncryptor. - -**Optional arguments:** - -``--cipher `` - The - encryption - algorithm - or - mode. - For - example, - aes-xts-plain64. - Default=None. - -``--key-size `` - Size of encryption key, in bits. For example, 128 or - 256. Default=None. - -``--control-location `` - Notional service where encryption is performed. Valid - values are "front-end" or "back-end." For example, - front-end=Nova. Default is "front-end." - -.. _cinder_encryption-type-delete: - -cinder encryption-type-delete ------------------------------ - -.. code-block:: console - - usage: cinder encryption-type-delete - -Deletes encryption type for a volume type. Admin only. - -**Positional arguments:** - -```` - Name or ID of volume type. - -.. _cinder_encryption-type-list: - -cinder encryption-type-list ---------------------------- - -.. code-block:: console - - usage: cinder encryption-type-list - -Shows encryption type details for volume types. Admin only. - -.. _cinder_encryption-type-show: - -cinder encryption-type-show ---------------------------- - -.. code-block:: console - - usage: cinder encryption-type-show - -Shows encryption type details for a volume type. Admin only. - -**Positional arguments:** - -```` - Name or ID of volume type. - -.. _cinder_encryption-type-update: - -cinder encryption-type-update ------------------------------ - -.. code-block:: console - - usage: cinder encryption-type-update [--provider ] - [--cipher []] - [--key-size []] - [--control-location ] - - -Update encryption type information for a volume type (Admin Only). - -**Positional arguments:** - -```` - Name or ID of the volume type - -**Optional arguments:** - -``--provider `` - Class providing encryption support (e.g. - LuksEncryptor) - -``--cipher []`` - Encryption - algorithm/mode - to - use - (e.g., - aes-xts-plain64). - Provide - parameter - without - value - to - set - to - provider default. - -``--key-size []`` - Size of the encryption key, in bits (e.g., 128, 256). - Provide parameter without value to set to provider - default. - -``--control-location `` - Notional service where encryption is performed (e.g., - front-end=Nova). Values: 'front-end', 'back-end' - -.. _cinder_endpoints: - -cinder endpoints ----------------- - -.. code-block:: console - - usage: cinder endpoints - -Discovers endpoints registered by authentication service. - -.. _cinder_extend: - -cinder extend -------------- - -.. code-block:: console - - usage: cinder extend - -Attempts to extend size of an existing volume. - -**Positional arguments:** - -```` - Name or ID of volume to extend. - -```` - New size of volume, in GiBs. - -.. _cinder_extra-specs-list: - -cinder extra-specs-list ------------------------ - -.. code-block:: console - - usage: cinder extra-specs-list - -Lists current volume types and extra specs. - -.. _cinder_failover-host: - -cinder failover-host --------------------- - -.. code-block:: console - - usage: cinder failover-host [--backend_id ] - -Failover a replicating cinder-volume host. - -**Positional arguments:** - -```` - Host name. - -**Optional arguments:** - -``--backend_id `` - ID of backend to failover to (Default=None) - -.. _cinder_force-delete: - -cinder force-delete -------------------- - -.. code-block:: console - - usage: cinder force-delete [ ...] - -Attempts force-delete of volume, regardless of state. - -**Positional arguments:** - -```` - Name or ID of volume or volumes to delete. - -.. _cinder_freeze-host: - -cinder freeze-host ------------------- - -.. code-block:: console - - usage: cinder freeze-host - -Freeze and disable the specified cinder-volume host. - -**Positional arguments:** - -```` - Host name. - -.. _cinder_get-capabilities: - -cinder get-capabilities ------------------------ - -.. code-block:: console - - usage: cinder get-capabilities - -Show backend volume stats and properties. Admin only. - -**Positional arguments:** - -```` - Cinder host to show backend volume stats and properties; takes the - form: host@backend-name - -.. _cinder_get-pools: - -cinder get-pools ----------------- - -.. code-block:: console - - usage: cinder get-pools [--detail] [--filters [ [ ...]]] - -Show pool information for backends. Admin only. - -**Optional arguments:** - -``--detail`` - Show detailed information about pools. - -``--filters [ [ ...]]`` - Filter - key - and - value - pairs. - Please - use - 'cinder - list-filters' - to - check - enabled - filters - from - server, - Default=None. (Supported by API version 3.33 and - later) - -.. _cinder_group-create: - -cinder group-create -------------------- - -.. code-block:: console - - usage: cinder group-create [--name ] [--description ] - [--availability-zone ] - - -Creates a group. - -**Positional arguments:** - -```` - Group type. - -```` - Comma-separated list of volume types. - -**Optional arguments:** - -``--name `` - Name of a group. - -``--description `` - Description of a group. Default=None. - -``--availability-zone `` - Availability zone for group. Default=None. - -.. _cinder_group-create-from-src: - -cinder group-create-from-src ----------------------------- - -.. code-block:: console - - usage: cinder group-create-from-src [--group-snapshot ] - [--source-group ] - [--name ] - [--description ] - -Creates a group from a group snapshot or a source group. - -**Optional arguments:** - -``--group-snapshot `` - Name or ID of a group snapshot. Default=None. - -``--source-group `` - Name or ID of a source group. Default=None. - -``--name `` - Name of a group. Default=None. - -``--description `` - Description of a group. Default=None. - -.. _cinder_group-delete: - -cinder group-delete -------------------- - -.. code-block:: console - - usage: cinder group-delete [--delete-volumes] [ ...] - -Removes one or more groups. - -**Positional arguments:** - -```` - Name or ID of one or more groups to be deleted. - -**Optional arguments:** - -``--delete-volumes`` - Allows or disallows groups to be deleted if they are not - empty. If the group is empty, it can be deleted without - the delete-volumes flag. If the group is not empty, the - delete-volumes flag is required for it to be deleted. If - True, all volumes in the group will also be deleted. - -.. _cinder_group-list: - -cinder group-list ------------------ - -.. code-block:: console - - usage: cinder group-list [--all-tenants [<0|1>]] - [--filters [ [ ...]]] - -Lists all groups. - -**Optional arguments:** - -``--all-tenants [<0|1>]`` - Shows details for all tenants. Admin only. - -``--filters [ [ ...]]`` - Filter - key - and - value - pairs. - Please - use - 'cinder - list-filters' - to - check - enabled - filters - from - server, - Default=None. (Supported by API version 3.33 and - later) - -.. _cinder_group-show: - -cinder group-show ------------------ - -.. code-block:: console - - usage: cinder group-show - -Shows details of a group. - -**Positional arguments:** - -```` - Name or ID of a group. - -.. _cinder_group-snapshot-create: - -cinder group-snapshot-create ----------------------------- - -.. code-block:: console - - usage: cinder group-snapshot-create [--name ] - [--description ] - - -Creates a group snapshot. - -**Positional arguments:** - -```` - Name or ID of a group. - -**Optional arguments:** - -``--name `` - Group snapshot name. Default=None. - -``--description `` - Group snapshot description. Default=None. - -.. _cinder_group-snapshot-delete: - -cinder group-snapshot-delete ----------------------------- - -.. code-block:: console - - usage: cinder group-snapshot-delete [ ...] - -Removes one or more group snapshots. - -**Positional arguments:** - -```` - Name or ID of one or more group snapshots to be deleted. - -.. _cinder_group-snapshot-list: - -cinder group-snapshot-list --------------------------- - -.. code-block:: console - - usage: cinder group-snapshot-list [--all-tenants [<0|1>]] [--status ] - [--group-id ] - [--filters [ [ ...]]] - -Lists all group snapshots. - -**Optional arguments:** - -``--all-tenants [<0|1>]`` - Shows details for all tenants. Admin only. - -``--status `` - Filters results by a status. Default=None. This option - is deprecated and will be removed in newer release. - Please use '--filters' option which is introduced - since 3.33 instead. - -``--group-id `` - Filters results by a group ID. Default=None. This - option is deprecated and will be removed in newer - release. Please use '--filters' option which is - introduced since 3.33 instead. - -``--filters [ [ ...]]`` - Filter - key - and - value - pairs. - Please - use - 'cinder - list-filters' - to - check - enabled - filters - from - server, - Default=None. (Supported by API version 3.33 and - later) - -.. _cinder_group-snapshot-show: - -cinder group-snapshot-show --------------------------- - -.. code-block:: console - - usage: cinder group-snapshot-show - -Shows group snapshot details. - -**Positional arguments:** - -```` - Name or ID of group snapshot. - -.. _cinder_group-specs-list: - -cinder group-specs-list ------------------------ - -.. code-block:: console - - usage: cinder group-specs-list - -Lists current group types and specs. - -.. _cinder_group-type-create: - -cinder group-type-create ------------------------- - -.. code-block:: console - - usage: cinder group-type-create [--description ] - [--is-public ] - - -Creates a group type. - -**Positional arguments:** - -```` - Name of new group type. - -**Optional arguments:** - -``--description `` - Description of new group type. - -``--is-public `` - Make type accessible to the public (default true). - -.. _cinder_group-type-default: - -cinder group-type-default -------------------------- - -.. code-block:: console - - usage: cinder group-type-default - -List the default group type. - -.. _cinder_group-type-delete: - -cinder group-type-delete ------------------------- - -.. code-block:: console - - usage: cinder group-type-delete [ ...] - -Deletes group type or types. - -**Positional arguments:** - -```` - Name or ID of group type or types to delete. - -.. _cinder_group-type-key: - -cinder group-type-key ---------------------- - -.. code-block:: console - - usage: cinder group-type-key [ ...] - -Sets or unsets group_spec for a group type. - -**Positional arguments:** - -```` - Name or ID of group type. - -```` - The action. Valid values are "set" or "unset." - -```` - The group specs key and value pair to set or unset. For unset, - specify only the key. - -.. _cinder_group-type-list: - -cinder group-type-list ----------------------- - -.. code-block:: console - - usage: cinder group-type-list - -Lists available 'group types'. (Admin only will see private types) - -.. _cinder_group-type-show: - -cinder group-type-show ----------------------- - -.. code-block:: console - - usage: cinder group-type-show - -Show group type details. - -**Positional arguments:** - -```` - Name or ID of the group type. - -.. _cinder_group-type-update: - -cinder group-type-update ------------------------- - -.. code-block:: console - - usage: cinder group-type-update [--name ] [--description ] - [--is-public ] - - -Updates group type name, description, and/or is_public. - -**Positional arguments:** - -```` - ID of the group type. - -**Optional arguments:** - -``--name `` - Name of the group type. - -``--description `` - Description of the group type. - -``--is-public `` - Make type accessible to the public or not. - -.. _cinder_group-update: - -cinder group-update -------------------- - -.. code-block:: console - - usage: cinder group-update [--name ] [--description ] - [--add-volumes ] - [--remove-volumes ] - - -Updates a group. - -**Positional arguments:** - -```` - Name or ID of a group. - -**Optional arguments:** - -``--name `` - New name for group. Default=None. - -``--description `` - New description for group. Default=None. - -``--add-volumes `` - UUID of one or more volumes to be added to the group, - separated by commas. Default=None. - -``--remove-volumes `` - UUID of one or more volumes to be removed from the - group, separated by commas. Default=None. - -.. _cinder_image-metadata: - -cinder image-metadata ---------------------- - -.. code-block:: console - - usage: cinder image-metadata [ ...] - -Sets or deletes volume image metadata. - -**Positional arguments:** - -```` - Name or ID of volume for which to update metadata. - -```` - The action. Valid values are 'set' or 'unset.' - -```` - Metadata key and value pair to set or unset. For unset, specify - only the key. - -.. _cinder_image-metadata-show: - -cinder image-metadata-show --------------------------- - -.. code-block:: console - - usage: cinder image-metadata-show - -Shows volume image metadata. - -**Positional arguments:** - -```` - ID of volume. - -.. _cinder_list: - -cinder list ------------ - -.. code-block:: console - - usage: cinder list [--group_id ] [--all-tenants [<0|1>]] - [--name ] [--status ] - [--bootable []] - [--migration_status ] - [--metadata [ [ ...]]] - [--image_metadata [ [ ...]]] - [--marker ] [--limit ] [--fields ] - [--sort [:]] [--tenant []] - [--filters [ [ ...]]] - -Lists all volumes. - -**Optional arguments:** - -``--group_id `` - Filters results by a group_id. Default=None.This - option is deprecated and will be removed in newer - release. Please use '--filters' option which is - introduced since 3.33 instead. (Supported by API - version 3.10 and later) - -``--all-tenants [<0|1>]`` - Shows details for all tenants. Admin only. - -``--name `` - Filters results by a name. Default=None. This option - is deprecated and will be removed in newer release. - Please use '--filters' option which is introduced - since 3.33 instead. - -``--status `` - Filters results by a status. Default=None. This option - is deprecated and will be removed in newer release. - Please use '--filters' option which is introduced - since 3.33 instead. - -``--bootable []`` - Filters results by bootable status. Default=None. This - option is deprecated and will be removed in newer - release. Please use '--filters' option which is - introduced since 3.33 instead. - -``--migration_status `` - Filters results by a migration status. Default=None. - Admin only. This option is deprecated and will be - removed in newer release. Please use '--filters' - option which is introduced since 3.33 instead. - -``--metadata [ [ ...]]`` - Filters results by a metadata key and value pair. - Default=None. This option is deprecated and will be - removed in newer release. Please use '--filters' - option which is introduced since 3.33 instead. - -``--image_metadata [ [ ...]]`` - Filters results by a image metadata key and value - pair. Require volume api version >=3.4. - Default=None.This option is deprecated and will be - removed in newer release. Please use '--filters' - option which is introduced since 3.33 instead. - (Supported by API version 3.4 and later) - -``--marker `` - Begin returning volumes that appear later in the - volume list than that represented by this volume id. - Default=None. - -``--limit `` - Maximum number of volumes to return. Default=None. - -``--fields `` - Comma-separated list of fields to display. Use the - show command to see which fields are available. - Unavailable/non-existent fields will be ignored. - Default=None. - -``--sort [:]`` - Comma-separated list of sort keys and directions in - the form of [:]. Valid keys: id, - status, size, availability_zone, name, bootable, - created_at, reference. Default=None. - -``--tenant []`` - Display information from single tenant (Admin only). - -.. _cinder-list-filters-usage: - -``--filters [ [ ...]]`` - Filter key and value pairs. - Please use the ``cinder list-filters`` command to check enabled filters - from server. - Default=None. - (Supported by API version 3.33 and later) - - **Time Comparison Filters** - - Beginning with API version 3.60, you can apply time comparison filtering - to the ``created_at`` and ``updated at`` fields. Time must be - expressed in ISO 8601 format: CCYY-MM-DDThh:mm:ss±hh:mm. The - ±hh:mm value, if included, returns the time zone as an offset from - UTC. - - To use time comparison filtering, use the standard ``key=value`` syntax - for the ``--filters`` option. The allowable keys are: - - * ``created_at`` - * ``updated_at`` - - The value is a *time comparison statement*, which is specified as follows: - a comparison operator, followed immediately by a colon (``:``), followed - immediately by a time expressed in ISO 8601 format. You can filter by a - *time range* by appending a comma (``,``) followed a second time - comparison statement. - - Six *comparison operators* are supported: - - * ``gt`` (greater than) - return results more recent than the specified - time - * ``gte`` (greater than or equal) - return any results matching the - specified time and also any more recent results - * ``eq`` (equal) - return any results matching the specified time - exactly - * ``neq`` (not equal) - return any results that do not match the - specified time - * ``lt`` (less than) - return results older than the specified time - * ``lte`` (less than or equal) - return any results matching the - specified time and also any older results - - **Examples** - - To filter the response to volumes created before 15 January 2020: - - .. code-block:: console - - cinder list --filters created_at=lt:2020-01-15T00:00:00 - - To filter the response to those volumes updated in February 2020: - - .. code-block:: console - - cinder list --filters updated_at=gte:2020-02-01T00:00:00,lt:2020-03-01T00:00:00 - - See the `Block Storage API v3 Reference - `_ for - more information. - -.. _cinder_list-extensions: - -cinder list-extensions ----------------------- - -.. code-block:: console - - usage: cinder list-extensions - - -.. _cinder_list-filters: - -cinder list-filters -------------------- - -.. code-block:: console - - usage: cinder list-filters [--resource ] - - -**Optional arguments:** - -``--resource `` - Show enabled filters for specified resource. - Default=None. - -.. _cinder_manage: - -cinder manage -------------- - -.. code-block:: console - - usage: cinder manage [--id-type ] [--name ] - [--description ] - [--volume-type ] - [--availability-zone ] - [--metadata [ [ ...]]] [--bootable] - - -Manage an existing volume. - -**Positional arguments:** - -```` - Cinder host on which the existing volume resides; - takes the form: host@backend-name#pool - -```` - Name or other Identifier for existing volume - -**Optional arguments:** - -``--id-type `` - Type of backend device identifier provided, typically - source-name or source-id (Default=source-name) - -``--name `` - Volume name (Default=None) - -``--description `` - Volume description (Default=None) - -``--volume-type `` - Volume type (Default=None) - -``--availability-zone `` - Availability zone for volume (Default=None) - -``--metadata [ [ ...]]`` - Metadata key=value pairs (Default=None) - -``--bootable`` - Specifies that the newly created volume should be - marked as bootable - -.. _cinder_manageable-list: - -cinder manageable-list ----------------------- - -.. code-block:: console - - usage: cinder manageable-list [--detailed ] [--marker ] - [--limit ] [--offset ] - [--sort [:]] - - -Lists all manageable volumes. - -**Positional arguments:** - -```` - Cinder host on which to list manageable volumes; takes - the form: host@backend-name#pool - -**Optional arguments:** - -``--detailed `` - Returned detailed information (default true). - -``--marker `` - Begin returning volumes that appear later in the - volume list than that represented by this reference. - This reference should be json like. Default=None. - -``--limit `` - Maximum number of volumes to return. Default=None. - -``--offset `` - Number of volumes to skip after marker. Default=None. - -``--sort [:]`` - Comma-separated list of sort keys and directions in - the form of [:]. Valid keys: size, - reference. Default=None. - -.. _cinder_message-delete: - -cinder message-delete ---------------------- - -.. code-block:: console - - usage: cinder message-delete [ ...] - -Removes one or more messages. - -**Positional arguments:** - -```` - ID of one or more message to be deleted. - -.. _cinder_message-list: - -cinder message-list -------------------- - -.. code-block:: console - - usage: cinder message-list [--marker ] [--limit ] - [--sort [:]] - [--resource_uuid ] - [--resource_type ] [--event_id ] - [--request_id ] [--level ] - [--filters [ [ ...]]] - -Lists all messages. - -**Optional arguments:** - -``--marker `` - Begin returning message that appear later in the - message list than that represented by this id. - Default=None. (Supported by API version 3.5 and later) - -``--limit `` - Maximum number of messages to return. Default=None. - (Supported by API version 3.5 and later) - -``--sort [:]`` - Comma-separated list of sort keys and directions in - the form of [:]. Valid keys: id, - status, size, availability_zone, name, bootable, - created_at, reference. Default=None. (Supported by API - version 3.5 and later) - -``--resource_uuid `` - Filters results by a resource uuid. Default=None. This - option is deprecated and will be removed in newer - release. Please use '--filters' option which is - introduced since 3.33 instead. - -``--resource_type `` - Filters results by a resource type. Default=None. This - option is deprecated and will be removed in newer - release. Please use '--filters' option which is - introduced since 3.33 instead. - -``--event_id `` - Filters results by event id. Default=None. This option - is deprecated and will be removed in newer release. - Please use '--filters' option which is introduced - since 3.33 instead. - -``--request_id `` - Filters results by request id. Default=None. This - option is deprecated and will be removed in newer - release. Please use '--filters' option which is - introduced since 3.33 instead. - -``--level `` - Filters results by the message level. Default=None. - This option is deprecated and will be removed in newer - release. Please use '--filters' option which is - introduced since 3.33 instead. - -``--filters [ [ ...]]`` - Filter - key - and - value - pairs. - Please - use - 'cinder - list-filters' - to - check - enabled - filters - from - server, - Default=None. (Supported by API version 3.33 and - later) - -.. _cinder_message-show: - -cinder message-show -------------------- - -.. code-block:: console - - usage: cinder message-show - -Shows message details. - -**Positional arguments:** - -```` - ID of message. - -.. _cinder_metadata: - -cinder metadata ---------------- - -.. code-block:: console - - usage: cinder metadata [ ...] - -Sets or deletes volume metadata. - -**Positional arguments:** - -```` - Name or ID of volume for which to update metadata. - -```` - The action. Valid values are "set" or "unset." - -```` - Metadata key and value pair to set or unset. For unset, specify - only the key(s): (Supported by API version 3.15 and - later) - -.. _cinder_metadata-show: - -cinder metadata-show --------------------- - -.. code-block:: console - - usage: cinder metadata-show - -Shows volume metadata. - -**Positional arguments:** - -```` - ID of volume. - -.. _cinder_metadata-update-all: - -cinder metadata-update-all --------------------------- - -.. code-block:: console - - usage: cinder metadata-update-all [ ...] - -Updates volume metadata. - -**Positional arguments:** - -```` - ID of volume for which to update metadata. - -```` - Metadata key and value pair or pairs to update. - -.. _cinder_migrate: - -cinder migrate --------------- - -.. code-block:: console - - usage: cinder migrate [--force-host-copy []] - [--lock-volume []] - - -Migrates volume to a new host. - -**Positional arguments:** - -```` - ID of volume to migrate. - -```` - Destination host. Takes the form: host@backend-name#pool - -**Optional arguments:** - -``--force-host-copy []`` - Enables - or - disables - generic - host-based - force-migration, - which - bypasses - driver - optimizations. - Default=False. - -``--lock-volume []`` - Enables or disables the termination of volume - migration caused by other commands. This option - applies to the available volume. True means it locks - the volume state and does not allow the migration to - be aborted. The volume status will be in maintenance - during the migration. False means it allows the volume - migration to be aborted. The volume status is still in - the original status. Default=False. - -.. _cinder_qos-associate: - -cinder qos-associate --------------------- - -.. code-block:: console - - usage: cinder qos-associate - -Associates qos specs with specified volume type. - -**Positional arguments:** - -```` - ID of QoS specifications. - -```` - ID of volume type with which to associate QoS - specifications. - -.. _cinder_qos-create: - -cinder qos-create ------------------ - -.. code-block:: console - - usage: cinder qos-create [ ...] - -Creates a qos specs. - -**Positional arguments:** - -```` - Name of new QoS specifications. - -```` - QoS specifications. - -.. _cinder_qos-delete: - -cinder qos-delete ------------------ - -.. code-block:: console - - usage: cinder qos-delete [--force []] - -Deletes a specified qos specs. - -**Positional arguments:** - -```` - ID of QoS specifications to delete. - -**Optional arguments:** - -``--force []`` - Enables or disables deletion of in-use QoS - specifications. Default=False. - -.. _cinder_qos-disassociate: - -cinder qos-disassociate ------------------------ - -.. code-block:: console - - usage: cinder qos-disassociate - -Disassociates qos specs from specified volume type. - -**Positional arguments:** - -```` - ID of QoS specifications. - -```` - ID of volume type with which to associate QoS - specifications. - -.. _cinder_qos-disassociate-all: - -cinder qos-disassociate-all ---------------------------- - -.. code-block:: console - - usage: cinder qos-disassociate-all - -Disassociates qos specs from all its associations. - -**Positional arguments:** - -```` - ID of QoS specifications on which to operate. - -.. _cinder_qos-get-association: - -cinder qos-get-association --------------------------- - -.. code-block:: console - - usage: cinder qos-get-association - -Lists all associations for specified qos specs. - -**Positional arguments:** - -```` - ID of QoS specifications. - -.. _cinder_qos-key: - -cinder qos-key --------------- - -.. code-block:: console - - usage: cinder qos-key key=value [key=value ...] - -Sets or unsets specifications for a qos spec. - -**Positional arguments:** - -```` - ID of QoS specifications. - -```` - The action. Valid values are "set" or "unset." - -``key=value`` - Metadata key and value pair to set or unset. For unset, specify - only the key. - -.. _cinder_qos-list: - -cinder qos-list ---------------- - -.. code-block:: console - - usage: cinder qos-list - -Lists qos specs. - -.. _cinder_qos-show: - -cinder qos-show ---------------- - -.. code-block:: console - - usage: cinder qos-show - -Shows qos specs details. - -**Positional arguments:** - -```` - ID of QoS specifications to show. - -.. _cinder_quota-class-show: - -cinder quota-class-show ------------------------ - -.. code-block:: console - - usage: cinder quota-class-show - -Lists quotas for a quota class. - -**Positional arguments:** - -```` - Name of quota class for which to list quotas. - -.. _cinder_quota-class-update: - -cinder quota-class-update -------------------------- - -.. code-block:: console - - usage: cinder quota-class-update [--volumes ] - [--snapshots ] - [--gigabytes ] - [--volume-type ] - - -Updates quotas for a quota class. - -**Positional arguments:** - -```` - Name of quota class for which to set quotas. - -**Optional arguments:** - -``--volumes `` - The new "volumes" quota value. Default=None. - -``--snapshots `` - The new "snapshots" quota value. Default=None. - -``--gigabytes `` - The new "gigabytes" quota value. Default=None. - -``--volume-type `` - Volume type. Default=None. - -.. _cinder_quota-defaults: - -cinder quota-defaults ---------------------- - -.. code-block:: console - - usage: cinder quota-defaults - -Lists default quotas for a tenant. - -**Positional arguments:** - -```` - ID of tenant for which to list quota defaults. - -.. _cinder_quota-delete: - -cinder quota-delete -------------------- - -.. code-block:: console - - usage: cinder quota-delete - -Delete the quotas for a tenant. - -**Positional arguments:** - -```` - UUID of tenant to delete the quotas for. - -.. _cinder_quota-show: - -cinder quota-show ------------------ - -.. code-block:: console - - usage: cinder quota-show - -Lists quotas for a tenant. - -**Positional arguments:** - -```` - ID of tenant for which to list quotas. - -.. _cinder_quota-update: - -cinder quota-update -------------------- - -.. code-block:: console - - usage: cinder quota-update [--volumes ] [--snapshots ] - [--gigabytes ] [--backups ] - [--backup-gigabytes ] - [--consistencygroups ] - [--groups ] - [--volume-type ] - [--per-volume-gigabytes ] - - -Updates quotas for a tenant. - -**Positional arguments:** - -```` - ID of tenant for which to set quotas. - -**Optional arguments:** - -``--volumes `` - The new "volumes" quota value. Default=None. - -``--snapshots `` - The new "snapshots" quota value. Default=None. - -``--gigabytes `` - The new "gigabytes" quota value. Default=None. - -``--backups `` - The new "backups" quota value. Default=None. - -``--backup-gigabytes `` - The new "backup_gigabytes" quota value. Default=None. - -``--consistencygroups `` - The new "consistencygroups" quota value. Default=None. - -``--groups `` - The new "groups" quota value. Default=None. (Supported - by API version 3.13 and later) - -``--volume-type `` - Volume type. Default=None. - -``--per-volume-gigabytes `` - Set max volume size limit. Default=None. - -.. _cinder_quota-usage: - -cinder quota-usage ------------------- - -.. code-block:: console - - usage: cinder quota-usage - -Lists quota usage for a tenant. - -**Positional arguments:** - -```` - ID of tenant for which to list quota usage. - -.. _cinder_rate-limits: - -cinder rate-limits ------------------- - -.. code-block:: console - - usage: cinder rate-limits [] - -Lists rate limits for a user. - -**Positional arguments:** - -```` - Display information for a single tenant (Admin only). - -.. _cinder_readonly-mode-update: - -cinder readonly-mode-update ---------------------------- - -.. code-block:: console - - usage: cinder readonly-mode-update - -Updates volume read-only access-mode flag. - -**Positional arguments:** - -```` - ID of volume to update. - -```` - Enables or disables update of volume to read-only - access mode. - -.. _cinder_rename: - -cinder rename -------------- - -.. code-block:: console - - usage: cinder rename [--description ] [] - -Renames a volume. - -**Positional arguments:** - -```` - Name or ID of volume to rename. - -```` - New name for volume. - -**Optional arguments:** - -``--description `` - Volume description. Default=None. - -.. _cinder_replication-promote: - -cinder replication-promote --------------------------- - -.. code-block:: console - - usage: cinder replication-promote - -Promote a secondary volume to primary for a relationship. - -**Positional arguments:** - -```` - Name or ID of the volume to promote. The volume should have the - replica volume created with source-replica argument. - -.. _cinder_replication-reenable: - -cinder replication-reenable ---------------------------- - -.. code-block:: console - - usage: cinder replication-reenable - -Sync the secondary volume with primary for a relationship. - -**Positional arguments:** - -```` - Name - or - ID - of - the - volume - to - reenable - replication. - The - replication-status - of - the - volume - should - be - inactive. - -.. _cinder_reset-state: - -cinder reset-state ------------------- - -.. code-block:: console - - usage: cinder reset-state [--type ] [--state ] - [--attach-status ] - [--reset-migration-status] - [ ...] - -Explicitly updates the entity state in the Cinder database. Being a database -change only, this has no impact on the true state of the entity and may not -match the actual state. This can render a entity unusable in the case of -changing to the 'available' state. - -**Positional arguments:** - -```` - Name or ID of entity to update. - -**Optional arguments:** - -``--type `` - Type of entity to update. Available resources are: - 'volume', 'snapshot', 'backup', 'group' (since 3.20) - and 'group-snapshot' (since 3.19), Default=volume. - -``--state `` - The state to assign to the entity. NOTE: This command - simply changes the state of the entity in the database - with no regard to actual status, exercise caution when - using. Default=None, that means the state is - unchanged. - -``--attach-status `` - This is only used for a volume entity. The attach - status to assign to the volume in the database, with - no regard to the actual status. Valid values are - "attached" and "detached". Default=None, that means - the status is unchanged. - -``--reset-migration-status`` - This is only used for a volume entity. Clears the - migration status of the volume in the DataBase that - indicates the volume is source or destination of - volume migration, with no regard to the actual status. - -.. _cinder_retype: - -cinder retype -------------- - -.. code-block:: console - - usage: cinder retype [--migration-policy ] - - -Changes the volume type for a volume. - -**Positional arguments:** - -```` - Name or ID of volume for which to modify type. - -```` - New volume type. - -**Optional arguments:** - -``--migration-policy `` - Migration policy during retype of volume. - -.. _cinder_service-disable: - -cinder service-disable ----------------------- - -.. code-block:: console - - usage: cinder service-disable [--reason ] - -Disables the service. - -**Positional arguments:** - -```` - Host name. - -```` - Service binary. - -**Optional arguments:** - -``--reason `` - Reason for disabling service. - -.. _cinder_service-enable: - -cinder service-enable ---------------------- - -.. code-block:: console - - usage: cinder service-enable - -Enables the service. - -**Positional arguments:** - -```` - Host name. - -```` - Service binary. - -.. _cinder_service-list: - -cinder service-list -------------------- - -.. code-block:: console - - usage: cinder service-list [--host ] [--binary ] - [--withreplication []] - -Lists all services. Filter by host and service binary. - -**Optional arguments:** - -``--host `` - Host name. Default=None. - -``--binary `` - Service binary. Default=None. - -``--withreplication []`` - Enables or disables display of Replication info for - c-vol services. Default=False. (Supported by API - version 3.7 and later) - -.. _cinder_set-bootable: - -cinder set-bootable -------------------- - -.. code-block:: console - - usage: cinder set-bootable - -Update bootable status of a volume. - -**Positional arguments:** - -```` - ID of the volume to update. - -```` - Flag to indicate whether volume is bootable. - -.. _cinder_show: - -cinder show ------------ - -.. code-block:: console - - usage: cinder show - -Shows volume details. - -**Positional arguments:** - -```` - Name or ID of volume. - -.. _cinder_snapshot-create: - -cinder snapshot-create ----------------------- - -.. code-block:: console - - usage: cinder snapshot-create [--force []] [--name ] - [--description ] - [--metadata [ [ ...]]] - - -Creates a snapshot. - -**Positional arguments:** - -```` - Name or ID of volume to snapshot. - -**Optional arguments:** - -``--force []`` - Allows or disallows snapshot of a volume when the - volume is attached to an instance. If set to True, - ignores the current status of the volume when - attempting to snapshot it rather than forcing it to be - available. Default=False. - -``--name `` - Snapshot name. Default=None. - -``--description `` - Snapshot description. Default=None. - -``--metadata [ [ ...]]`` - Snapshot metadata key and value pairs. Default=None. - -.. _cinder_snapshot-delete: - -cinder snapshot-delete ----------------------- - -.. code-block:: console - - usage: cinder snapshot-delete [--force] [ ...] - -Removes one or more snapshots. - -**Positional arguments:** - -```` - Name or ID of the snapshot(s) to delete. - -**Optional arguments:** - -``--force`` - Allows deleting snapshot of a volume when its status is other - than "available" or "error". Default=False. - -.. _cinder_snapshot-list: - -cinder snapshot-list --------------------- - -.. code-block:: console - - usage: cinder snapshot-list [--all-tenants [<0|1>]] [--name ] - [--status ] [--volume-id ] - [--marker ] [--limit ] - [--sort [:]] [--tenant []] - [--metadata [ [ ...]]] - [--filters [ [ ...]]] - -Lists all snapshots. - -**Optional arguments:** - -``--all-tenants [<0|1>]`` - Shows details for all tenants. Admin only. - -``--name `` - Filters results by a name. Default=None. This option - is deprecated and will be removed in newer release. - Please use '--filters' option which is introduced - since 3.33 instead. - -``--status `` - Filters results by a status. Default=None. This option - is deprecated and will be removed in newer release. - Please use '--filters' option which is introduced - since 3.33 instead. - -``--volume-id `` - Filters results by a volume ID. Default=None. This - option is deprecated and will be removed in newer - release. Please use '--filters' option which is - introduced since 3.33 instead. - -``--marker `` - Begin returning snapshots that appear later in the - snapshot list than that represented by this id. - Default=None. - -``--limit `` - Maximum number of snapshots to return. Default=None. - -``--sort [:]`` - Comma-separated list of sort keys and directions in - the form of [:]. Valid keys: id, - status, size, availability_zone, name, bootable, - created_at, reference. Default=None. - -``--tenant []`` - Display information from single tenant (Admin only). - -``--metadata [ [ ...]]`` - Filters results by a metadata key and value pair. - Require volume api version >=3.22. Default=None. This - option is deprecated and will be removed in newer - release. Please use '--filters' option which is - introduced since 3.33 instead. (Supported by API - version 3.22 and later) - -``--filters [ [ ...]]`` - Filter - key - and - value - pairs. - Please - use - 'cinder - list-filters' - to - check - enabled - filters - from - server, - Default=None. (Supported by API version 3.33 and - later) - -.. _cinder_snapshot-manage: - -cinder snapshot-manage ----------------------- - -.. code-block:: console - - usage: cinder snapshot-manage [--id-type ] [--name ] - [--description ] - [--metadata [ [ ...]]] - - -Manage an existing snapshot. - -**Positional arguments:** - -```` - Cinder volume already exists in volume backend - -```` - Name or other Identifier for existing snapshot - -**Optional arguments:** - -``--id-type `` - Type of backend device identifier provided, typically - source-name or source-id (Default=source-name) - -``--name `` - Snapshot name (Default=None) - -``--description `` - Snapshot description (Default=None) - -``--metadata [ [ ...]]`` - Metadata key=value pairs (Default=None) - -.. _cinder_snapshot-manageable-list: - -cinder snapshot-manageable-list -------------------------------- - -.. code-block:: console - - usage: cinder snapshot-manageable-list [--detailed ] - [--marker ] [--limit ] - [--offset ] - [--sort [:]] - - -Lists all manageable snapshots. - -**Positional arguments:** - -```` - Cinder host on which to list manageable snapshots; - takes the form: host@backend-name#pool - -**Optional arguments:** - -``--detailed `` - Returned detailed information (default true). - -``--marker `` - Begin returning volumes that appear later in the - volume list than that represented by this reference. - This reference should be json like. Default=None. - -``--limit `` - Maximum number of volumes to return. Default=None. - -``--offset `` - Number of volumes to skip after marker. Default=None. - -``--sort [:]`` - Comma-separated list of sort keys and directions in - the form of [:]. Valid keys: size, - reference. Default=None. - -.. _cinder_snapshot-metadata: - -cinder snapshot-metadata ------------------------- - -.. code-block:: console - - usage: cinder snapshot-metadata - [ ...] - -Sets or deletes snapshot metadata. - -**Positional arguments:** - -```` - ID of snapshot for which to update metadata. - -```` - The action. Valid values are "set" or "unset." - -```` - Metadata key and value pair to set or unset. For unset, specify - only the key. - -.. _cinder_snapshot-metadata-show: - -cinder snapshot-metadata-show ------------------------------ - -.. code-block:: console - - usage: cinder snapshot-metadata-show - -Shows snapshot metadata. - -**Positional arguments:** - -```` - ID of snapshot. - -.. _cinder_snapshot-metadata-update-all: - -cinder snapshot-metadata-update-all ------------------------------------ - -.. code-block:: console - - usage: cinder snapshot-metadata-update-all - [ ...] - -Updates snapshot metadata. - -**Positional arguments:** - -```` - ID of snapshot for which to update metadata. - -```` - Metadata key and value pair to update. - -.. _cinder_snapshot-rename: - -cinder snapshot-rename ----------------------- - -.. code-block:: console - - usage: cinder snapshot-rename [--description ] - [] - -Renames a snapshot. - -**Positional arguments:** - -```` - Name or ID of snapshot. - -```` - New name for snapshot. - -**Optional arguments:** - -``--description `` - Snapshot description. Default=None. - -.. _cinder_snapshot-reset-state: - -cinder snapshot-reset-state ---------------------------- - -.. code-block:: console - - usage: cinder snapshot-reset-state [--state ] - [ ...] - -Explicitly updates the snapshot state. - -**Positional arguments:** - -```` - Name or ID of snapshot to modify. - -**Optional arguments:** - -``--state `` - The state to assign to the snapshot. Valid values are - "available", "error", "creating", "deleting", and - "error_deleting". NOTE: This command simply changes the - state of the Snapshot in the DataBase with no regard to - actual status, exercise caution when using. - Default=available. - -.. _cinder_snapshot-show: - -cinder snapshot-show --------------------- - -.. code-block:: console - - usage: cinder snapshot-show - -Shows snapshot details. - -**Positional arguments:** - -```` - Name or ID of snapshot. - -.. _cinder_snapshot-unmanage: - -cinder snapshot-unmanage ------------------------- - -.. code-block:: console - - usage: cinder snapshot-unmanage - -Stop managing a snapshot. - -**Positional arguments:** - -```` - Name or ID of the snapshot to unmanage. - -.. _cinder_thaw-host: - -cinder thaw-host ----------------- - -.. code-block:: console - - usage: cinder thaw-host - -Thaw and enable the specified cinder-volume host. - -**Positional arguments:** - -```` - Host name. - -.. _cinder_transfer-accept: - -cinder transfer-accept ----------------------- - -.. code-block:: console - - usage: cinder transfer-accept - -Accepts a volume transfer. - -**Positional arguments:** - -```` - ID of transfer to accept. - -```` - Authentication key of transfer to accept. - -.. _cinder_transfer-create: - -cinder transfer-create ----------------------- - -.. code-block:: console - - usage: cinder transfer-create [--name ] - -Creates a volume transfer. - -**Positional arguments:** - -```` - Name or ID of volume to transfer. - -**Optional arguments:** - -``--name `` - Transfer name. Default=None. - -.. _cinder_transfer-delete: - -cinder transfer-delete ----------------------- - -.. code-block:: console - - usage: cinder transfer-delete - -Undoes a transfer. - -**Positional arguments:** - -```` - Name or ID of transfer to delete. - -.. _cinder_transfer-list: - -cinder transfer-list --------------------- - -.. code-block:: console - - usage: cinder transfer-list [--all-tenants [<0|1>]] - -Lists all transfers. - -**Optional arguments:** - -``--all-tenants [<0|1>]`` - Shows details for all tenants. Admin only. - -.. _cinder_transfer-show: - -cinder transfer-show --------------------- - -.. code-block:: console - - usage: cinder transfer-show - -Shows transfer details. - -**Positional arguments:** - -```` - Name or ID of transfer to accept. - -.. _cinder_type-access-add: - -cinder type-access-add ----------------------- - -.. code-block:: console - - usage: cinder type-access-add --volume-type --project-id - - -Adds volume type access for the given project. - -**Optional arguments:** - -``--volume-type `` - Volume type name or ID to add access for the given - project. - -``--project-id `` - Project ID to add volume type access for. - -.. _cinder_type-access-list: - -cinder type-access-list ------------------------ - -.. code-block:: console - - usage: cinder type-access-list --volume-type - -Print access information about the given volume type. - -**Optional arguments:** - -``--volume-type `` - Filter results by volume type name or ID. - -.. _cinder_type-access-remove: - -cinder type-access-remove -------------------------- - -.. code-block:: console - - usage: cinder type-access-remove --volume-type --project-id - - -Removes volume type access for the given project. - -**Optional arguments:** - -``--volume-type `` - Volume type name or ID to remove access for the given - project. - -``--project-id `` - Project ID to remove volume type access for. - -.. _cinder_type-create: - -cinder type-create ------------------- - -.. code-block:: console - - usage: cinder type-create [--description ] - [--is-public ] - - -Creates a volume type. - -**Positional arguments:** - -```` - Name of new volume type. - -**Optional arguments:** - -``--description `` - Description of new volume type. - -``--is-public `` - Make type accessible to the public (default true). - -.. _cinder_type-default: - -cinder type-default -------------------- - -.. code-block:: console - - usage: cinder type-default - -List the default volume type. - -.. _cinder_type-delete: - -cinder type-delete ------------------- - -.. code-block:: console - - usage: cinder type-delete [ ...] - -Deletes volume type or types. - -**Positional arguments:** - -```` - Name or ID of volume type or types to delete. - -.. _cinder_type-key: - -cinder type-key ---------------- - -.. code-block:: console - - usage: cinder type-key [ ...] - -Sets or unsets extra_spec for a volume type. - -**Positional arguments:** - -```` - Name or ID of volume type. - -```` - The action. Valid values are "set" or "unset." - -```` - The extra specs key and value pair to set or unset. For unset, - specify only the key. - -.. _cinder_type-list: - -cinder type-list ----------------- - -.. code-block:: console - - usage: cinder type-list [--filters [ ...]] - -Lists available 'volume types'. (Only admin and tenant users will see private -types) - -**Optional arguments:** - -``--filters [ [ ...]]`` - Filter key and value pairs. Please use 'cinder list-filters' - to check enabled filters from server, Default=None. - (Supported by API version 3.52 and later) - -.. _cinder_type-show: - -cinder type-show ----------------- - -.. code-block:: console - - usage: cinder type-show - -Show volume type details. - -**Positional arguments:** - -```` - Name or ID of the volume type. - -.. _cinder_type-update: - -cinder type-update ------------------- - -.. code-block:: console - - usage: cinder type-update [--name ] [--description ] - [--is-public ] - - -Updates volume type name, description, and/or is_public. - -**Positional arguments:** - -```` - ID of the volume type. - -**Optional arguments:** - -``--name `` - Name of the volume type. - -``--description `` - Description of the volume type. - -``--is-public `` - Make type accessible to the public or not. - -.. _cinder_unmanage: - -cinder unmanage ---------------- - -.. code-block:: console - - usage: cinder unmanage - -Stop managing a volume. - -**Positional arguments:** - -```` - Name or ID of the volume to unmanage. - -.. _cinder_upload-to-image: - -cinder upload-to-image ----------------------- - -.. code-block:: console - - usage: cinder upload-to-image [--force []] - [--container-format ] - [--disk-format ] - [--visibility ] - [--protected ] - - -Uploads volume to Image Service as an image. - -**Positional arguments:** - -```` - Name or ID of volume to snapshot. - -```` - The new image name. - -**Optional arguments:** - -``--force []`` - Enables or disables upload of a volume that is - attached to an instance. Default=False. This option - may not be supported by your cloud. - -``--container-format `` - Container format type. Default is bare. - -``--disk-format `` - Disk format type. Default is raw. - -``--visibility `` - Set image visibility to either public or private. - Default=private. (Supported by API version 3.1 and - later) - -``--protected `` - Prevents image from being deleted. Default=False. - (Supported by API version 3.1 and later) - -.. _cinder_version-list: - -cinder version-list -------------------- - -.. code-block:: console - - usage: cinder version-list - -List all API versions. - +.. cli-docs:: diff --git a/doc/source/conf.py b/doc/source/conf.py index 282a881ce..2868e84b9 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -21,10 +21,7 @@ # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.append(os.path.abspath('.')) - -BASE_DIR = os.path.dirname(os.path.abspath(__file__)) -ROOT = os.path.abspath(os.path.join(BASE_DIR, "..", "..")) -sys.path.insert(0, ROOT) +sys.path.insert(0, os.path.join(os.path.abspath('..'), 'ext')) # -- General configuration ---------------------------------------------------- @@ -35,6 +32,7 @@ 'sphinx.ext.autodoc', 'openstackdocstheme', 'reno.sphinxext', + 'cli', ] # Add any paths that contain templates here, relative to this directory. From 4e24fd614be8e7aae82224f0add044e3d62c8b51 Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Sat, 18 Apr 2020 11:57:42 -0500 Subject: [PATCH 030/159] 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: Ia41326a601dfd72750bd81c3ebee9ec5884ad91b Signed-off-by: Sean McGinnis --- cinderclient/tests/unit/test_api_versions.py | 6 +++--- cinderclient/tests/unit/test_base.py | 14 ++++++-------- cinderclient/tests/unit/test_client.py | 5 ++--- cinderclient/tests/unit/test_exceptions.py | 3 ++- cinderclient/tests/unit/test_http.py | 5 +++-- cinderclient/tests/unit/test_shell.py | 2 +- cinderclient/tests/unit/test_utils.py | 7 +++---- cinderclient/tests/unit/utils.py | 2 +- cinderclient/tests/unit/v2/test_auth.py | 5 ++--- cinderclient/tests/unit/v2/test_limits.py | 3 ++- cinderclient/tests/unit/v2/test_shell.py | 10 +++++----- cinderclient/tests/unit/v3/test_shell.py | 3 ++- lower-constraints.txt | 1 - test-requirements.txt | 1 - 14 files changed, 32 insertions(+), 35 deletions(-) diff --git a/cinderclient/tests/unit/test_api_versions.py b/cinderclient/tests/unit/test_api_versions.py index 02e445092..0f8690006 100644 --- a/cinderclient/tests/unit/test_api_versions.py +++ b/cinderclient/tests/unit/test_api_versions.py @@ -13,17 +13,17 @@ # License for the specific language governing permissions and limitations # under the License. +from unittest import mock + import ddt -import mock import six from cinderclient import api_versions from cinderclient import client as base_client from cinderclient import exceptions -from cinderclient.v3 import client - from cinderclient.tests.unit import test_utils from cinderclient.tests.unit import utils +from cinderclient.v3 import client @ddt.ddt diff --git a/cinderclient/tests/unit/test_base.py b/cinderclient/tests/unit/test_base.py index 63c569b7a..1b545497d 100644 --- a/cinderclient/tests/unit/test_base.py +++ b/cinderclient/tests/unit/test_base.py @@ -12,30 +12,28 @@ # See the License for the specific language governing permissions and # limitations under the License. -import mock -from requests import Response +from unittest import mock + +import requests import six from cinderclient import api_versions from cinderclient.apiclient import base as common_base from cinderclient import base from cinderclient import exceptions -from cinderclient.v3 import client -from cinderclient.v3 import volumes - from cinderclient.tests.unit import test_utils from cinderclient.tests.unit import utils from cinderclient.tests.unit.v2 import fakes - +from cinderclient.v3 import client +from cinderclient.v3 import volumes cs = fakes.FakeClient() - REQUEST_ID = 'req-test-request-id' def create_response_obj_with_header(): - resp = Response() + resp = requests.Response() resp.headers['x-openstack-request-id'] = REQUEST_ID resp.headers['Etag'] = 'd5103bf7b26ff0310200d110da3ed186' resp.status_code = 200 diff --git a/cinderclient/tests/unit/test_client.py b/cinderclient/tests/unit/test_client.py index 874cdccba..6770f2439 100644 --- a/cinderclient/tests/unit/test_client.py +++ b/cinderclient/tests/unit/test_client.py @@ -13,22 +13,21 @@ import json import logging +from unittest import mock import ddt import fixtures from keystoneauth1 import adapter from keystoneauth1 import exceptions as keystone_exception -import mock from oslo_serialization import jsonutils import six from cinderclient import api_versions import cinderclient.client from cinderclient import exceptions -import cinderclient.v2.client - from cinderclient.tests.unit import utils from cinderclient.tests.unit.v3 import fakes +import cinderclient.v2.client @ddt.ddt diff --git a/cinderclient/tests/unit/test_exceptions.py b/cinderclient/tests/unit/test_exceptions.py index 2504f6e90..0ecda02da 100644 --- a/cinderclient/tests/unit/test_exceptions.py +++ b/cinderclient/tests/unit/test_exceptions.py @@ -15,7 +15,8 @@ """Tests the cinderclient.exceptions module.""" import datetime -import mock +from unittest import mock + import requests from cinderclient import exceptions diff --git a/cinderclient/tests/unit/test_http.py b/cinderclient/tests/unit/test_http.py index 73ce6ae98..534a216f9 100644 --- a/cinderclient/tests/unit/test_http.py +++ b/cinderclient/tests/unit/test_http.py @@ -12,10 +12,11 @@ # limitations under the License. import json -import mock -import requests +from unittest import mock import uuid +import requests + from cinderclient import client from cinderclient import exceptions from cinderclient.tests.unit import utils diff --git a/cinderclient/tests/unit/test_shell.py b/cinderclient/tests/unit/test_shell.py index 430f4fb1f..ac7404cbf 100644 --- a/cinderclient/tests/unit/test_shell.py +++ b/cinderclient/tests/unit/test_shell.py @@ -15,6 +15,7 @@ import re import sys import unittest +from unittest import mock import ddt import fixtures @@ -22,7 +23,6 @@ from keystoneauth1.exceptions import DiscoveryFailure from keystoneauth1.identity.generic.password import Password as ks_password from keystoneauth1 import session -import mock import requests_mock from six import moves from testtools import matchers diff --git a/cinderclient/tests/unit/test_utils.py b/cinderclient/tests/unit/test_utils.py index 0cb12a699..8b3b7a6e1 100644 --- a/cinderclient/tests/unit/test_utils.py +++ b/cinderclient/tests/unit/test_utils.py @@ -12,10 +12,10 @@ # limitations under the License. import collections -import ddt import sys +from unittest import mock -import mock +import ddt import six from six import moves @@ -24,10 +24,9 @@ from cinderclient import base from cinderclient import exceptions from cinderclient import shell_utils -from cinderclient import utils - from cinderclient.tests.unit import utils as test_utils from cinderclient.tests.unit.v2 import fakes +from cinderclient import utils UUID = '8e8ec658-c7b0-4243-bdf8-6f7f2952c0d0' diff --git a/cinderclient/tests/unit/utils.py b/cinderclient/tests/unit/utils.py index 124b71498..8f4e9acf7 100644 --- a/cinderclient/tests/unit/utils.py +++ b/cinderclient/tests/unit/utils.py @@ -13,9 +13,9 @@ import json import os +from unittest import mock import fixtures -import mock import requests from requests_mock.contrib import fixture as requests_mock_fixture import six diff --git a/cinderclient/tests/unit/v2/test_auth.py b/cinderclient/tests/unit/v2/test_auth.py index 50c72a301..5d7a4bc36 100644 --- a/cinderclient/tests/unit/v2/test_auth.py +++ b/cinderclient/tests/unit/v2/test_auth.py @@ -15,14 +15,13 @@ # under the License. import json +from unittest import mock -import mock import requests from cinderclient import exceptions -from cinderclient.v2 import client - from cinderclient.tests.unit import utils +from cinderclient.v2 import client class AuthenticateAgainstKeystoneTests(utils.TestCase): diff --git a/cinderclient/tests/unit/v2/test_limits.py b/cinderclient/tests/unit/v2/test_limits.py index b1732e58b..34ed1d2f4 100644 --- a/cinderclient/tests/unit/v2/test_limits.py +++ b/cinderclient/tests/unit/v2/test_limits.py @@ -13,8 +13,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +from unittest import mock + import ddt -import mock from cinderclient.tests.unit import utils from cinderclient.v2 import limits diff --git a/cinderclient/tests/unit/v2/test_shell.py b/cinderclient/tests/unit/v2/test_shell.py index a61075359..f6f6355db 100644 --- a/cinderclient/tests/unit/v2/test_shell.py +++ b/cinderclient/tests/unit/v2/test_shell.py @@ -13,22 +13,22 @@ # License for the specific language governing permissions and limitations # under the License. +from unittest import mock + import ddt import fixtures -import mock from requests_mock.contrib import fixture as requests_mock_fixture from six.moves.urllib import parse from cinderclient import client from cinderclient import exceptions from cinderclient import shell -from cinderclient.v2 import shell as test_shell -from cinderclient.v2 import volume_backups -from cinderclient.v2 import volumes - from cinderclient.tests.unit.fixture_data import keystone_client from cinderclient.tests.unit import utils from cinderclient.tests.unit.v2 import fakes +from cinderclient.v2 import shell as test_shell +from cinderclient.v2 import volume_backups +from cinderclient.v2 import volumes @ddt.ddt diff --git a/cinderclient/tests/unit/v3/test_shell.py b/cinderclient/tests/unit/v3/test_shell.py index 6a2523802..8338a3462 100644 --- a/cinderclient/tests/unit/v3/test_shell.py +++ b/cinderclient/tests/unit/v3/test_shell.py @@ -38,9 +38,10 @@ # 'volume_id': '1234'}) # return original(manager, name_or_id, **kwargs) +from unittest import mock + import ddt import fixtures -import mock from requests_mock.contrib import fixture as requests_mock_fixture import six from six.moves.urllib import parse diff --git a/lower-constraints.txt b/lower-constraints.txt index 7e8989a41..11e6cd7c2 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -16,7 +16,6 @@ jsonschema==2.6.0 keystoneauth1==3.4.0 linecache2==1.0.0 mccabe==0.2.1 -mock==2.0.0 monotonic==0.6 msgpack-python==0.4.0 netaddr==0.7.18 diff --git a/test-requirements.txt b/test-requirements.txt index 7aa409635..fdc3d2596 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -7,7 +7,6 @@ hacking>=3.0.1,<3.1.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 ddt>=1.0.1 # MIT fixtures>=3.0.0 # Apache-2.0/BSD -mock>=2.0.0 # BSD reno>=3.1.0 # Apache-2.0 requests-mock>=1.2.0 # Apache-2.0 tempest>=17.1.0 # Apache-2.0 From d6530c48d9c860aeb604c4efddb3ddc58cc24c2c Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Thu, 14 May 2020 16:15:15 -0500 Subject: [PATCH 031/159] Add doc linting to pep8 target This adds doc8 to the pep8 job to lint the docs. Also fixes a few issues it highlighted. Change-Id: Id0f4b9bee1f6a0103ec581b20037a9b74201aaca Signed-off-by: Sean McGinnis --- HACKING.rst | 25 ++++++++++++--------- doc/source/contributor/functional_tests.rst | 10 +++++---- doc/source/index.rst | 8 ++++--- lower-constraints.txt | 1 + test-requirements.txt | 1 + tox.ini | 8 ++++++- 6 files changed, 34 insertions(+), 19 deletions(-) diff --git a/HACKING.rst b/HACKING.rst index fed361147..82683e7b0 100644 --- a/HACKING.rst +++ b/HACKING.rst @@ -10,7 +10,8 @@ Cinder Client Specific Commandments General ------- -- Use 'raise' instead of 'raise e' to preserve original traceback or exception being reraised:: +- Use 'raise' instead of 'raise e' to preserve original traceback or exception + being reraised:: except Exception as e: ... @@ -22,26 +23,28 @@ General Release Notes ------------- -- Any patch that makes a change significant to the end consumer or deployer of an - OpenStack environment should include a release note (new features, upgrade impacts, - deprecated functionality, significant bug fixes, etc.) +- Any patch that makes a change significant to the end consumer or deployer of + an OpenStack environment should include a release note (new features, upgrade + impacts, deprecated functionality, significant bug fixes, etc.) -- Cinder Client uses Reno for release notes management. See the `Reno Documentation`_ - for more details on its usage. +- Cinder Client uses Reno for release notes management. See the `Reno + Documentation`_ for more details on its usage. .. _Reno Documentation: https://docs.openstack.org/reno/latest/ -- As a quick example, when adding a new shell command for Awesome Storage Feature, one - could perform the following steps to include a release note for the new feature: +- As a quick example, when adding a new shell command for Awesome Storage + Feature, one could perform the following steps to include a release note for + the new feature:: $ tox -e venv -- reno new add-awesome-command $ vi releasenotes/notes/add-awesome-command-bb8bb8bb8bb8bb81.yaml - Remove the extra template text from the release note and update the details so it - looks something like: + Remove the extra template text from the release note and update the details + so it looks something like:: --- features: - Added shell command `cinder be-awesome` for Awesome Storage Feature. -- Include the generated release notes file when submitting your patch for review. +- Include the generated release notes file when submitting your patch for + review. diff --git a/doc/source/contributor/functional_tests.rst b/doc/source/contributor/functional_tests.rst index 9eea42df2..1494f475a 100644 --- a/doc/source/contributor/functional_tests.rst +++ b/doc/source/contributor/functional_tests.rst @@ -40,7 +40,9 @@ For more information on these options and how to run tests, please see the Gotchas ------- -The cinderclient.tests.functional.test_cli.CinderBackupTests.test_backup_create_and_delete -test will fail in Devstack without c-bak service running, which requires Swift. -Make sure Swift is enabled when you stack.sh by putting this in local.conf : -enable_service s-proxy s-object s-container s-account +The cinderclient.tests.functional.test_cli.CinderBackupTests.test_backup_create +and_delete test will fail in Devstack without c-bak service running, which +requires Swift. Make sure Swift is enabled when you stack.sh by putting this in +local.conf:: + + enable_service s-proxy s-object s-container s-account diff --git a/doc/source/index.rst b/doc/source/index.rst index 557bddc9f..2bd01af79 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -221,6 +221,11 @@ The following are kept for historical purposes. * Use Adapter from keystoneclient * Add support for Replication feature * Add pagination for Volume List +* Note Connection refused --> Connection error commit: + c9e7818f3f90ce761ad8ccd09181c705880a4266 +* Note Mask Passwords in log output commit: + 80582f2b860b2dadef7ae07bdbd8395bf03848b1 + .. _1325773: http://bugs.launchpad.net/python-cinderclient/+bug/1325773 .. _1333257: http://bugs.launchpad.net/python-cinderclient/+bug/1333257 @@ -234,9 +239,6 @@ The following are kept for historical purposes. .. _1130572: http://bugs.launchpad.net/python-cinderclient/+bug/1130572 .. _1156994: http://bugs.launchpad.net/python-cinderclient/+bug/1156994 -** Note Connection refused --> Connection error commit: c9e7818f3f90ce761ad8ccd09181c705880a4266 -** Note Mask Passwords in log output commit: 80582f2b860b2dadef7ae07bdbd8395bf03848b1 - 1.0.9 ------ diff --git a/lower-constraints.txt b/lower-constraints.txt index 11e6cd7c2..470edc19f 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -6,6 +6,7 @@ coverage==4.0 cryptography==2.1 ddt==1.0.1 debtcollector==1.2.0 +doc8==0.6.0 extras==1.0.0 fasteners==0.7.0 fixtures==3.0.0 diff --git a/test-requirements.txt b/test-requirements.txt index fdc3d2596..c8e64f2fe 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -13,3 +13,4 @@ tempest>=17.1.0 # Apache-2.0 testtools>=2.2.0 # MIT stestr>=1.0.0 # Apache-2.0 oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0 +doc8>=0.6.0 # Apache-2.0 diff --git a/tox.ini b/tox.ini index 5aeea014b..40f881c6c 100644 --- a/tox.ini +++ b/tox.ini @@ -30,7 +30,9 @@ commands = find . -type f -name "*.pyc" -delete whitelist_externals = find [testenv:pep8] -commands = flake8 +commands = + flake8 + doc8 [testenv:pylint] deps = @@ -107,6 +109,10 @@ show-source = True ignore = H404,H405,E122,E123,E128,E251,W504 exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build +[doc8] +ignore-path=.tox,*.egg-info,doc/src/api,doc/source/drivers.rst,doc/build,.eggs/*/EGG-INFO/*.txt,doc/so urce/configuration/tables,./*.txt,releasenotes/build,doc/source/cli/details.rst +extension=.txt,.rst,.inc + [testenv:lower-constraints] deps = -c{toxinidir}/lower-constraints.txt From 718474a092192ea0034a8107b6947a10636a4a0a Mon Sep 17 00:00:00 2001 From: Luigi Toscano Date: Tue, 16 Jun 2020 11:04:08 +0200 Subject: [PATCH 032/159] Fix typo: dow -> down Closes-Bug: #1883674 Change-Id: I4f4468b23d04ecf74fb6347bfb518377127b563d --- cinderclient/v3/shell.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cinderclient/v3/shell.py b/cinderclient/v3/shell.py index d2ff83822..b3f69b08e 100644 --- a/cinderclient/v3/shell.py +++ b/cinderclient/v3/shell.py @@ -1079,7 +1079,7 @@ def do_backup_update(cs, args): help='Cluster binary. Default=None.') @utils.arg('--is-up', metavar='', default=None, choices=('True', 'true', 'False', 'false'), - help='Filter by up/dow status. Default=None.') + help='Filter by up/down status. Default=None.') @utils.arg('--disabled', metavar='', default=None, choices=('True', 'true', 'False', 'false'), help='Filter by disabled status. Default=None.') From 37f6a3079434186a534864b8a3e919232f56ff27 Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Sun, 5 Jul 2020 15:51:53 -0400 Subject: [PATCH 033/159] use stevedore to load util plugins Importing pkg_resources has a side-effect of scanning all of the installed python modules looking for entrypoints to build an in-memory cache. Stevedore will be adding an on-disk cache to speed that process up, which should provide significant performance benefits for client applications such as python-openstackclient. This change introduces stevedore to replace pkg_resources. Change-Id: I66decf6d5a4f79ddaa6617737e9334a56dbbbad4 Signed-off-by: Doug Hellmann --- cinderclient/utils.py | 17 +++++++++++------ requirements.txt | 1 + 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/cinderclient/utils.py b/cinderclient/utils.py index 28c458dc0..681a2d410 100644 --- a/cinderclient/utils.py +++ b/cinderclient/utils.py @@ -17,13 +17,13 @@ import collections import os -import pkg_resources import sys import uuid import prettytable import six from six.moves.urllib import parse +import stevedore from cinderclient import exceptions from oslo_utils import encodeutils @@ -332,11 +332,16 @@ def safe_issubclass(*args): def _load_entry_point(ep_name, name=None): """Try to load the entry point ep_name that matches name.""" - for ep in pkg_resources.iter_entry_points(ep_name, name=name): - try: - return ep.load() - except (ImportError, pkg_resources.UnknownExtra, AttributeError): - continue + mgr = stevedore.NamedExtensionManager( + namespace=ep_name, + names=[name], + # Ignore errors on load + on_load_failure_callback=lambda mgr, entry_point, error: None, + ) + try: + return mgr[name].plugin + except KeyError: + pass def get_function_name(func): diff --git a/requirements.txt b/requirements.txt index fef5e1e44..f6567f49a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,3 +9,4 @@ six>=1.10.0 # MIT oslo.i18n>=3.15.3 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 requests!=2.20.0,>=2.14.2 # Apache-2.0 +stevedore>=1.20.0 # Apache-2.0 From 8ecbbcd7a19bb4784bef6dfa98b7b8c980580843 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 9 Jul 2020 11:21:44 +0100 Subject: [PATCH 034/159] trivial: Drop references to os-testr os-testr is dead. Long live stestr. Change-Id: Ie37f00e2f5ef2230adcff0d662e2d0b214b8c74c Signed-off-by: Stephen Finucane --- doc/source/contributor/functional_tests.rst | 2 +- doc/source/contributor/unit_tests.rst | 2 +- lower-constraints.txt | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/source/contributor/functional_tests.rst b/doc/source/contributor/functional_tests.rst index 9eea42df2..5f60a48cc 100644 --- a/doc/source/contributor/functional_tests.rst +++ b/doc/source/contributor/functional_tests.rst @@ -35,7 +35,7 @@ Or all tests in the test_readonly_clitest_readonly_cli.py file:: tox -e functional -- -n cinderclient.tests.functional.test_readonly_cli For more information on these options and how to run tests, please see the -`ostestr documentation `_. +`stestr documentation `_. Gotchas ------- diff --git a/doc/source/contributor/unit_tests.rst b/doc/source/contributor/unit_tests.rst index 07247aa36..c86891f51 100644 --- a/doc/source/contributor/unit_tests.rst +++ b/doc/source/contributor/unit_tests.rst @@ -36,7 +36,7 @@ Or all tests in the test_volumes.py file:: tox -epy27 -- -n cinderclient.tests.unit.v2.test_volumes For more information on these options and how to run tests, please see the -`ostestr documentation `_. +`stestr documentation `_. Gotchas ------- diff --git a/lower-constraints.txt b/lower-constraints.txt index 11e6cd7c2..db75ae4ac 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -20,7 +20,6 @@ monotonic==0.6 msgpack-python==0.4.0 netaddr==0.7.18 netifaces==0.10.4 -os-testr==1.0.0 oslo.concurrency==3.25.0 oslo.config==5.2.0 oslo.context==2.19.2 From 1dc592a6fd6693288f479ad770b0ccdac0b48607 Mon Sep 17 00:00:00 2001 From: Eric Harney Date: Thu, 16 Jul 2020 10:21:28 -0400 Subject: [PATCH 035/159] Bump hacking to 3.1.0 pycodestyle does not know about "importutils.try_import" calls, so we have to either a) put them inside a "try" block or b) add "# noqa: E402" to each import after the try_import call Change-Id: Ia545bb689cfdfb57962d74e3957dfb372fd3782b --- cinderclient/client.py | 5 ++++- cinderclient/shell.py | 5 ++++- cinderclient/tests/unit/v2/test_limits.py | 4 ++-- cinderclient/utils.py | 2 +- test-requirements.txt | 2 +- 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/cinderclient/client.py b/cinderclient/client.py index 6193e9590..485acd93d 100644 --- a/cinderclient/client.py +++ b/cinderclient/client.py @@ -35,7 +35,10 @@ from oslo_utils import encodeutils from oslo_utils import importutils from oslo_utils import strutils -osprofiler_web = importutils.try_import("osprofiler.web") # noqa +try: + osprofiler_web = importutils.try_import("osprofiler.web") +except Exception: + pass import requests from six.moves import urllib import six.moves.urllib.parse as urlparse diff --git a/cinderclient/shell.py b/cinderclient/shell.py index acbccdad5..7fddb5a08 100644 --- a/cinderclient/shell.py +++ b/cinderclient/shell.py @@ -33,7 +33,10 @@ from keystoneauth1 import session from oslo_utils import encodeutils from oslo_utils import importutils -osprofiler_profiler = importutils.try_import("osprofiler.profiler") # noqa +try: + osprofiler_profiler = importutils.try_import("osprofiler.profiler") +except Exception: + pass import requests import six import six.moves.urllib.parse as urlparse diff --git a/cinderclient/tests/unit/v2/test_limits.py b/cinderclient/tests/unit/v2/test_limits.py index 34ed1d2f4..d8fbdfe59 100644 --- a/cinderclient/tests/unit/v2/test_limits.py +++ b/cinderclient/tests/unit/v2/test_limits.py @@ -177,5 +177,5 @@ def test_get(self, tenant_id): api.client.get.assert_called_once_with('/limits%s' % query_str) self.assertIsInstance(lim, limits.Limits) - for l in lim.absolute: - self.assertEqual(l1, l) + for limit in lim.absolute: + self.assertEqual(l1, limit) diff --git a/cinderclient/utils.py b/cinderclient/utils.py index 28c458dc0..04236a309 100644 --- a/cinderclient/utils.py +++ b/cinderclient/utils.py @@ -199,7 +199,7 @@ def unicode_key_value_to_string(src): _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 [unicode_key_value_to_string(item) for item in src] return _encode(src) diff --git a/test-requirements.txt b/test-requirements.txt index fdc3d2596..065e1275b 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -3,7 +3,7 @@ # process, which may cause wedges in the gate later. # Hacking already pins down pep8, pyflakes and flake8 -hacking>=3.0.1,<3.1.0 # Apache-2.0 +hacking>=3.1.0,<3.2.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 ddt>=1.0.1 # MIT fixtures>=3.0.0 # Apache-2.0/BSD From a9e9b762fd71eec44a5d8f72c50daa2238b2c3b2 Mon Sep 17 00:00:00 2001 From: Ivan Kolodyazhny Date: Fri, 24 Jul 2020 23:31:52 +0300 Subject: [PATCH 036/159] Add support for Cinder API mv3.61 Microversion 3.61 adds cluster_name attribute to volume details output for admin users. Change-Id: I13f85c8ddd4cb238a245c263151123fd271a9927 Depends-On: https://review.opendev.org/742991 --- cinderclient/api_versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cinderclient/api_versions.py b/cinderclient/api_versions.py index 2d2219953..dbd9d1eca 100644 --- a/cinderclient/api_versions.py +++ b/cinderclient/api_versions.py @@ -29,7 +29,7 @@ # key is a deprecated version and value is an alternative version. DEPRECATED_VERSIONS = {"2": "3"} DEPRECATED_VERSION = "2.0" -MAX_VERSION = "3.60" +MAX_VERSION = "3.61" MIN_VERSION = "3.0" _SUBSTITUTIONS = {} From f85896af2b044abc2c966f874414b38aae5b0d06 Mon Sep 17 00:00:00 2001 From: Ghanshyam Mann Date: Sun, 26 Jul 2020 17:37:51 -0500 Subject: [PATCH 037/159] [goal] Migrate python-cinderclient jobs to 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. - pep8 error - Set bionic nodeset for py36 and py37 job. [1] https://governance.openstack.org/tc/goals/selected/victoria/migrate-ci-cd-jobs-to-ubuntu-fo$ Story: #2007865 Task: #40179 Change-Id: Ibab96807a7747738282732fe0069b9bc197da0ee --- .zuul.yaml | 1 + cinderclient/client.py | 10 ++++++---- cinderclient/shell.py | 9 +++++---- cinderclient/tests/unit/fake_actions_module.py | 2 +- cinderclient/tests/unit/test_utils.py | 2 +- cinderclient/v3/messages.py | 2 +- cinderclient/v3/volume_backups.py | 4 ++-- cinderclient/v3/volumes.py | 6 +++--- lower-constraints.txt | 6 +++--- 9 files changed, 23 insertions(+), 19 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index 1f51c0481..68aef3297 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -16,6 +16,7 @@ - job: name: python-cinderclient-functional-py36 parent: python-cinderclient-functional-base + nodeset: openstack-single-node-bionic vars: python_version: 3.6 tox_envlist: functional-py36 diff --git a/cinderclient/client.py b/cinderclient/client.py index b013c1eb8..fdf1f363e 100644 --- a/cinderclient/client.py +++ b/cinderclient/client.py @@ -33,10 +33,6 @@ from oslo_utils import encodeutils from oslo_utils import importutils from oslo_utils import strutils -try: - osprofiler_web = importutils.try_import("osprofiler.web") -except Exception: - pass import requests from six.moves import urllib import six.moves.urllib.parse as urlparse @@ -56,6 +52,12 @@ except ImportError: import simplejson as json +try: + osprofiler_web = importutils.try_import("osprofiler.web") +except Exception: + pass + + _VALID_VERSIONS = ['v2', 'v3'] V3_SERVICE_TYPE = 'volumev3' V2_SERVICE_TYPE = 'volumev2' diff --git a/cinderclient/shell.py b/cinderclient/shell.py index 09a55b811..de71bcfde 100644 --- a/cinderclient/shell.py +++ b/cinderclient/shell.py @@ -31,10 +31,6 @@ from keystoneauth1 import session from oslo_utils import encodeutils from oslo_utils import importutils -try: - osprofiler_profiler = importutils.try_import("osprofiler.profiler") -except Exception: - pass import requests import six import six.moves.urllib.parse as urlparse @@ -46,6 +42,11 @@ from cinderclient import exceptions as exc from cinderclient import utils +try: + osprofiler_profiler = importutils.try_import("osprofiler.profiler") +except Exception: + pass + DEFAULT_MAJOR_OS_VOLUME_API_VERSION = "3" DEFAULT_CINDER_ENDPOINT_TYPE = 'publicURL' diff --git a/cinderclient/tests/unit/fake_actions_module.py b/cinderclient/tests/unit/fake_actions_module.py index 5c023066d..07e7d29b4 100644 --- a/cinderclient/tests/unit/fake_actions_module.py +++ b/cinderclient/tests/unit/fake_actions_module.py @@ -27,7 +27,7 @@ def do_fake_action(): @api_versions.wraps("3.2", "3.3") # noqa: F811 -def do_fake_action(): +def do_fake_action(): # noqa return "fake_action 3.2 to 3.3" diff --git a/cinderclient/tests/unit/test_utils.py b/cinderclient/tests/unit/test_utils.py index 8b3b7a6e1..1243b0a62 100644 --- a/cinderclient/tests/unit/test_utils.py +++ b/cinderclient/tests/unit/test_utils.py @@ -72,7 +72,7 @@ def return_api_version(self): return '3.1' @api_versions.wraps('3.2') # noqa: F811 - def return_api_version(self): + def return_api_version(self): # noqa return '3.2' diff --git a/cinderclient/v3/messages.py b/cinderclient/v3/messages.py index 3dc538881..2c620c206 100644 --- a/cinderclient/v3/messages.py +++ b/cinderclient/v3/messages.py @@ -52,7 +52,7 @@ def list(self, **kwargs): return self._list(url, resource_type) @api_versions.wraps('3.5') # noqa: F811 - def list(self, search_opts=None, marker=None, limit=None, sort=None): + def list(self, search_opts=None, marker=None, limit=None, sort=None): # noqa """Lists all messages. :param search_opts: Search options to filter out volumes. diff --git a/cinderclient/v3/volume_backups.py b/cinderclient/v3/volume_backups.py index b07ecfb4c..7dd85603b 100644 --- a/cinderclient/v3/volume_backups.py +++ b/cinderclient/v3/volume_backups.py @@ -61,7 +61,7 @@ def create(self, volume_id, container=None, incremental, force, snapshot_id) @api_versions.wraps("3.43") # noqa: F811 - def create(self, volume_id, container=None, + def create(self, volume_id, container=None, # noqa name=None, description=None, incremental=False, force=False, snapshot_id=None, @@ -85,7 +85,7 @@ def create(self, volume_id, container=None, incremental, force, snapshot_id, metadata) @api_versions.wraps("3.51") # noqa: F811 - def create(self, volume_id, container=None, name=None, description=None, + def create(self, volume_id, container=None, name=None, description=None, # noqa incremental=False, force=False, snapshot_id=None, metadata=None, availability_zone=None): return self._create_backup(volume_id, container, name, description, diff --git a/cinderclient/v3/volumes.py b/cinderclient/v3/volumes.py index fac5effce..974bcfcfe 100644 --- a/cinderclient/v3/volumes.py +++ b/cinderclient/v3/volumes.py @@ -160,7 +160,7 @@ def delete_metadata(self, volume, keys): return common_base.ListWithMeta([], response_list) @api_versions.wraps("3.15") # noqa: F811 - def delete_metadata(self, volume, keys): + def delete_metadata(self, volume, keys): # noqa """Delete specified keys from volumes metadata. :param volume: The :class:`Volume`. @@ -191,7 +191,7 @@ def upload_to_image(self, volume, force, image_name, container_format, 'disk_format': disk_format}) @api_versions.wraps("3.1") # noqa: F811 - def upload_to_image(self, volume, force, image_name, container_format, + def upload_to_image(self, volume, force, image_name, container_format, # noqa disk_format, visibility, protected): """Upload volume to image service as image. :param volume: The :class:`Volume` to upload. @@ -265,7 +265,7 @@ def get_pools(self, detail): return self._get('/scheduler-stats/get_pools%s' % query_string, None) @api_versions.wraps("3.33") # noqa: F811 - def get_pools(self, detail, search_opts): + def get_pools(self, detail, search_opts): # noqa """Show pool information for backends.""" # pylint: disable=function-redefined options = {'detail': detail} diff --git a/lower-constraints.txt b/lower-constraints.txt index db75ae4ac..bd2915f75 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -1,9 +1,9 @@ asn1crypto==0.23.0 -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.0.1 debtcollector==1.2.0 extras==1.0.0 @@ -39,7 +39,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 reno==3.1.0 requests-mock==1.2.0 requests==2.14.2 From 3228111af3e21561b578688ab406a0c1effaf397 Mon Sep 17 00:00:00 2001 From: Luigi Toscano Date: Tue, 18 Aug 2020 17:03:18 +0200 Subject: [PATCH 038/159] zuul functional job: drop the custom playbooks The base devstack-tox-functional* jobs now set the required environment (OS_* vars set by devstack) before calling tox. Depends-On: https://review.opendev.org/746235 Change-Id: If58a6088f92c4be5edd53f0f5cd9a093d4cbc5f1 --- .zuul.yaml | 5 ++--- playbooks/post.yaml | 6 ------ playbooks/python-cinderclient-functional.yaml | 14 -------------- roles/get-os-environment/defaults/main.yaml | 2 -- roles/get-os-environment/tasks/main.yaml | 12 ------------ 5 files changed, 2 insertions(+), 37 deletions(-) delete mode 100644 playbooks/post.yaml delete mode 100644 playbooks/python-cinderclient-functional.yaml delete mode 100644 roles/get-os-environment/defaults/main.yaml delete mode 100644 roles/get-os-environment/tasks/main.yaml diff --git a/.zuul.yaml b/.zuul.yaml index 1f51c0481..e2accb180 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -1,14 +1,13 @@ - job: name: python-cinderclient-functional-base abstract: true - parent: devstack - run: playbooks/python-cinderclient-functional.yaml - post-run: playbooks/post.yaml + parent: devstack-tox-functional timeout: 4500 required-projects: - openstack/cinder - openstack/python-cinderclient vars: + openrc_enable_export: true devstack_localrc: USE_PYTHON3: true VOLUME_BACKING_FILE_SIZE: 16G diff --git a/playbooks/post.yaml b/playbooks/post.yaml deleted file mode 100644 index 9860e2a96..000000000 --- a/playbooks/post.yaml +++ /dev/null @@ -1,6 +0,0 @@ -- hosts: all - vars: - tox_envlist: functional - roles: - - fetch-tox-output - - fetch-subunit-output diff --git a/playbooks/python-cinderclient-functional.yaml b/playbooks/python-cinderclient-functional.yaml deleted file mode 100644 index dec94d03c..000000000 --- a/playbooks/python-cinderclient-functional.yaml +++ /dev/null @@ -1,14 +0,0 @@ -- hosts: all - roles: - - ensure-python - - run-devstack - # Run bindep and test-setup after devstack so that they won't interfere - - role: bindep - bindep_profile: test - bindep_dir: "{{ zuul_work_dir }}" - - test-setup - - get-os-environment - - ensure-tox - - role: tox - tox_install_siblings: false - environment: "{{ os_env_vars }}" diff --git a/roles/get-os-environment/defaults/main.yaml b/roles/get-os-environment/defaults/main.yaml deleted file mode 100644 index 91190b325..000000000 --- a/roles/get-os-environment/defaults/main.yaml +++ /dev/null @@ -1,2 +0,0 @@ ---- -openrc_file: "{{ devstack_base_dir|default('/opt/stack') }}/devstack/openrc" diff --git a/roles/get-os-environment/tasks/main.yaml b/roles/get-os-environment/tasks/main.yaml deleted file mode 100644 index b3f457bbb..000000000 --- a/roles/get-os-environment/tasks/main.yaml +++ /dev/null @@ -1,12 +0,0 @@ -- name: Extract the OS_ environment variables - shell: - cmd: | - source {{ openrc_file }} admin admin &>/dev/null - env | awk -F= 'BEGIN {print "---" } /^OS_/ { print " "$1": \""$2"\""} ' - args: - executable: "/bin/bash" - register: env_os - -- name: Save the OS_ environment variables as a fact - set_fact: - os_env_vars: "{{ env_os.stdout|from_yaml }}" From aebb6011e67955d5e54b8596eb0a3301877b9345 Mon Sep 17 00:00:00 2001 From: zhoulinhui Date: Sun, 30 Aug 2020 22:05:41 +0800 Subject: [PATCH 039/159] Use importlib to take place of imp module The imp module is deprecated[1] since version 3.4, use importlib to instead [1]: https://docs.python.org/3/library/imp.html Change-Id: Ie250592bc183e8db1758b6cfa4681a45f4c489ab --- cinderclient/client.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/cinderclient/client.py b/cinderclient/client.py index b013c1eb8..24af7de73 100644 --- a/cinderclient/client.py +++ b/cinderclient/client.py @@ -18,7 +18,7 @@ import glob import hashlib -import imp +import importlib.util import itertools import logging import os @@ -791,6 +791,15 @@ def _discover_via_python_path(): yield name, module +def load_module(name, path): + module_spec = importlib.util.spec_from_file_location( + name, path + ) + module = importlib.util.module_from_spec(module_spec) + module_spec.loader.exec_module(module) + return module + + def _discover_via_contrib_path(version): module_path = os.path.dirname(os.path.abspath(__file__)) version_str = "v%s" % version.replace('.', '_') @@ -803,7 +812,7 @@ def _discover_via_contrib_path(version): if name == "__init__": continue - module = imp.load_source(name, ext_path) + module = load_module(name, ext_path) yield name, module From 9dc1d61d3276d32b455340559ed574de27e29040 Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Thu, 3 Sep 2020 11:53:27 -0400 Subject: [PATCH 040/159] Remove excess whitespace in ignore-path Some extra whitespace is present in the ignore-path in the [doc8] testenv, so remove it. Change-Id: Ibd1b5f8259ec174b2bbb233b270f0e09daa67bc2 --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 40f881c6c..dab603dc0 100644 --- a/tox.ini +++ b/tox.ini @@ -110,7 +110,7 @@ ignore = H404,H405,E122,E123,E128,E251,W504 exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build [doc8] -ignore-path=.tox,*.egg-info,doc/src/api,doc/source/drivers.rst,doc/build,.eggs/*/EGG-INFO/*.txt,doc/so urce/configuration/tables,./*.txt,releasenotes/build,doc/source/cli/details.rst +ignore-path=.tox,*.egg-info,doc/src/api,doc/source/drivers.rst,doc/build,.eggs/*/EGG-INFO/*.txt,doc/source/configuration/tables,./*.txt,releasenotes/build,doc/source/cli/details.rst extension=.txt,.rst,.inc [testenv:lower-constraints] From 7397f709579427a96919cf5036dcc821941a6f20 Mon Sep 17 00:00:00 2001 From: tushargite96 Date: Tue, 15 Sep 2020 21:50:14 +0530 Subject: [PATCH 041/159] Python API in python-cinderclient "myvol.delete" should be "myvol.delete()" Change-Id: Iadddfed8deec1f0dee2b71effb3b4de2689d6ada Closes-Bug: #1866374 --- doc/source/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/index.rst b/doc/source/index.rst index 2bd01af79..eeb706c7d 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -14,7 +14,7 @@ can use the API like so:: ce06d0a8-5c1b-4e2c-81d2-39eca6bbfb70 >>> cinder.volumes.list() [] - >>>myvol.delete + >>> myvol.delete() Alternatively, you can create a client instance using the keystoneauth session API:: From 7ee7d376a19cebbf7d8bc6d273f7e7daba552526 Mon Sep 17 00:00:00 2001 From: whoami-rajat Date: Fri, 3 Jul 2020 12:31:48 +0000 Subject: [PATCH 042/159] Add commands for default type overrides This patch adds command for set,get and delete default volume types for projects. This patch adds 3 commands : 1) Set Set a default volume type for a project cinder --os-volume-api-version 3.62 default-type-set 2) Get Get the default volume type for a project cinder --os-volume-api-version 3.62 default-type-list --project-id Get all default types cinder --os-volume-api-version 3.62 default-type-list 3) Unset Unset default volume type for a project cinder --os-volume-api-version 3.62 default-type-unset Implements: Blueprint multiple-default-volume-types Change-Id: Id2fb00c218edbb98df3193577dba6a897c6e73f6 --- cinderclient/api_versions.py | 2 +- cinderclient/base.py | 20 ++++++ cinderclient/client.py | 6 ++ cinderclient/tests/unit/v2/fakes.py | 50 ++++++++++++++ .../tests/unit/v3/test_default_types.py | 46 +++++++++++++ cinderclient/tests/unit/v3/test_shell.py | 30 +++++++++ cinderclient/v3/client.py | 2 + cinderclient/v3/default_types.py | 65 +++++++++++++++++++ cinderclient/v3/shell.py | 51 +++++++++++++++ ...roject-default-types-727156d1db10a24d.yaml | 6 ++ 10 files changed, 277 insertions(+), 1 deletion(-) create mode 100644 cinderclient/tests/unit/v3/test_default_types.py create mode 100644 cinderclient/v3/default_types.py create mode 100644 releasenotes/notes/project-default-types-727156d1db10a24d.yaml diff --git a/cinderclient/api_versions.py b/cinderclient/api_versions.py index dbd9d1eca..ff4cc9310 100644 --- a/cinderclient/api_versions.py +++ b/cinderclient/api_versions.py @@ -29,7 +29,7 @@ # key is a deprecated version and value is an alternative version. DEPRECATED_VERSIONS = {"2": "3"} DEPRECATED_VERSION = "2.0" -MAX_VERSION = "3.61" +MAX_VERSION = "3.62" MIN_VERSION = "3.0" _SUBSTITUTIONS = {} diff --git a/cinderclient/base.py b/cinderclient/base.py index a317ad432..40ce381be 100644 --- a/cinderclient/base.py +++ b/cinderclient/base.py @@ -331,6 +331,26 @@ def _get_with_base_url(self, url, response_key=None): else: return self.resource_class(self, body, loaded=True) + def _get_all_with_base_url(self, url, response_key=None): + resp, body = self.api.client.get_with_base_url(url) + if response_key: + if isinstance(body[response_key], list): + return [self.resource_class(self, res, loaded=True) + for res in body[response_key] if res] + return self.resource_class(self, body[response_key], + loaded=True) + return self.resource_class(self, body, loaded=True) + + def _create_update_with_base_url(self, url, body, response_key=None): + resp, body = self.api.client.create_update_with_base_url( + url, body=body) + if response_key: + return self.resource_class(self, body[response_key], loaded=True) + return self.resource_class(self, body, loaded=True) + + def _delete_with_base_url(self, url, response_key=None): + self.api.client.delete_with_base_url(url) + class ManagerWithFind(six.with_metaclass(abc.ABCMeta, Manager)): """ diff --git a/cinderclient/client.py b/cinderclient/client.py index fdf1f363e..eb8c0de23 100644 --- a/cinderclient/client.py +++ b/cinderclient/client.py @@ -269,6 +269,12 @@ def _cs_request_base_url(self, url, method, **kwargs): def get_with_base_url(self, url, **kwargs): return self._cs_request_base_url(url, 'GET', **kwargs) + def create_update_with_base_url(self, url, **kwargs): + return self._cs_request_base_url(url, 'PUT', **kwargs) + + def delete_with_base_url(self, url, **kwargs): + return self._cs_request_base_url(url, 'DELETE', **kwargs) + class HTTPClient(object): diff --git a/cinderclient/tests/unit/v2/fakes.py b/cinderclient/tests/unit/v2/fakes.py index 0cf1faa9e..0dd269e1d 100644 --- a/cinderclient/tests/unit/v2/fakes.py +++ b/cinderclient/tests/unit/v2/fakes.py @@ -309,6 +309,30 @@ def _stub_server_versions(): ] +def stub_default_type(): + return { + 'default_type': { + 'project_id': '629632e7-99d2-4c40-9ae3-106fa3b1c9b7', + 'volume_type_id': '4c298f16-e339-4c80-b934-6cbfcb7525a0' + } + } + + +def stub_default_types(): + return { + 'default_types': [ + { + 'project_id': '629632e7-99d2-4c40-9ae3-106fa3b1c9b7', + 'volume_type_id': '4c298f16-e339-4c80-b934-6cbfcb7525a0' + }, + { + 'project_id': 'a0c01994-1245-416e-8fc9-1aca86329bfd', + 'volume_type_id': 'ff094b46-f82a-4a74-9d9e-d3d08116ad93' + } + ] + } + + class FakeClient(fakes.FakeClient, client.Client): def __init__(self, api_version=None, *args, **kwargs): @@ -1055,9 +1079,35 @@ def post_os_volume_transfer_5678_accept(self, **kw): {'transfer': _stub_transfer(transfer1, base_uri, tenant_id)}) def get_with_base_url(self, url, **kw): + if 'default-types' in url: + return self._cs_request(url, 'GET', **kw) server_versions = _stub_server_versions() return (200, {'versions': server_versions}) + def create_update_with_base_url(self, url, **kwargs): + return self._cs_request(url, 'PUT', **kwargs) + + def put_v3_default_types_629632e7_99d2_4c40_9ae3_106fa3b1c9b7( + self, **kwargs): + default_type = stub_default_type() + return (200, {}, default_type) + + def get_v3_default_types_629632e7_99d2_4c40_9ae3_106fa3b1c9b7( + self, **kw): + default_types = stub_default_type() + return (200, {}, default_types) + + def get_v3_default_types(self, **kw): + default_types = stub_default_types() + return (200, {}, default_types) + + def delete_with_base_url(self, url, **kwargs): + return self._cs_request(url, 'DELETE', **kwargs) + + def delete_v3_default_types_629632e7_99d2_4c40_9ae3_106fa3b1c9b7( + self, **kwargs): + return (204, {}, {}) + # # Services # diff --git a/cinderclient/tests/unit/v3/test_default_types.py b/cinderclient/tests/unit/v3/test_default_types.py new file mode 100644 index 000000000..621aeb804 --- /dev/null +++ b/cinderclient/tests/unit/v3/test_default_types.py @@ -0,0 +1,46 @@ +# 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 cinderclient import api_versions +from cinderclient.tests.unit import utils +from cinderclient.tests.unit.v3 import fakes + +defaults = fakes.FakeClient(api_versions.APIVersion('3.62')) + + +class VolumeTypeDefaultTest(utils.TestCase): + + def test_set(self): + defaults.default_types.create('4c298f16-e339-4c80-b934-6cbfcb7525a0', + '629632e7-99d2-4c40-9ae3-106fa3b1c9b7') + defaults.assert_called( + 'PUT', 'v3/default-types/629632e7-99d2-4c40-9ae3-106fa3b1c9b7', + body={'default_type': + {'volume_type': '4c298f16-e339-4c80-b934-6cbfcb7525a0'}} + ) + + def test_get(self): + defaults.default_types.list('629632e7-99d2-4c40-9ae3-106fa3b1c9b7') + defaults.assert_called( + 'GET', 'v3/default-types/629632e7-99d2-4c40-9ae3-106fa3b1c9b7') + + def test_get_all(self): + defaults.default_types.list() + defaults.assert_called( + 'GET', 'v3/default-types') + + def test_unset(self): + defaults.default_types.delete('629632e7-99d2-4c40-9ae3-106fa3b1c9b7') + defaults.assert_called( + 'DELETE', 'v3/default-types/629632e7-99d2-4c40-9ae3-106fa3b1c9b7') diff --git a/cinderclient/tests/unit/v3/test_shell.py b/cinderclient/tests/unit/v3/test_shell.py index 8338a3462..0332ae3d1 100644 --- a/cinderclient/tests/unit/v3/test_shell.py +++ b/cinderclient/tests/unit/v3/test_shell.py @@ -1611,3 +1611,33 @@ def test_subcommand_parser(self): def test_transfer_list_with_filters(self, command, expected): self.run_command('--os-volume-api-version 3.52 %s' % command) self.assert_called('GET', expected) + + def test_default_type_set(self): + self.run_command('--os-volume-api-version 3.62 default-type-set ' + '4c298f16-e339-4c80-b934-6cbfcb7525a0 ' + '629632e7-99d2-4c40-9ae3-106fa3b1c9b7') + body = { + 'default_type': + { + 'volume_type': '4c298f16-e339-4c80-b934-6cbfcb7525a0' + } + } + self.assert_called( + 'PUT', 'v3/default-types/629632e7-99d2-4c40-9ae3-106fa3b1c9b7', + body=body) + + def test_default_type_list_project(self): + self.run_command('--os-volume-api-version 3.62 default-type-list ' + '--project-id 629632e7-99d2-4c40-9ae3-106fa3b1c9b7') + self.assert_called( + 'GET', 'v3/default-types/629632e7-99d2-4c40-9ae3-106fa3b1c9b7') + + def test_default_type_list(self): + self.run_command('--os-volume-api-version 3.62 default-type-list') + self.assert_called('GET', 'v3/default-types') + + def test_default_type_delete(self): + self.run_command('--os-volume-api-version 3.62 default-type-unset ' + '629632e7-99d2-4c40-9ae3-106fa3b1c9b7') + self.assert_called( + 'DELETE', 'v3/default-types/629632e7-99d2-4c40-9ae3-106fa3b1c9b7') diff --git a/cinderclient/v3/client.py b/cinderclient/v3/client.py index 5703826ea..770d9d605 100644 --- a/cinderclient/v3/client.py +++ b/cinderclient/v3/client.py @@ -21,6 +21,7 @@ from cinderclient.v3 import cgsnapshots from cinderclient.v3 import clusters from cinderclient.v3 import consistencygroups +from cinderclient.v3 import default_types from cinderclient.v3 import group_snapshots from cinderclient.v3 import group_types from cinderclient.v3 import groups @@ -80,6 +81,7 @@ def __init__(self, username=None, api_key=None, project_id=None, volume_type_access.VolumeTypeAccessManager(self) self.volume_encryption_types = \ volume_encryption_types.VolumeEncryptionTypeManager(self) + self.default_types = default_types.DefaultVolumeTypeManager(self) self.qos_specs = qos_specs.QoSSpecsManager(self) self.quota_classes = quota_classes.QuotaClassSetManager(self) self.quotas = quotas.QuotaSetManager(self) diff --git a/cinderclient/v3/default_types.py b/cinderclient/v3/default_types.py new file mode 100644 index 000000000..58e04ccb3 --- /dev/null +++ b/cinderclient/v3/default_types.py @@ -0,0 +1,65 @@ +# 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. + + +"""Default Volume Type interface.""" + +from cinderclient import base + + +class DefaultVolumeType(base.Resource): + """Default volume types for projects.""" + def __repr__(self): + return "" % self.project_id + + +class DefaultVolumeTypeManager(base.ManagerWithFind): + """Manage :class:`DefaultVolumeType` resources.""" + resource_class = DefaultVolumeType + + def create(self, volume_type, project_id): + """Creates a default volume type for a project + + :param volume_type: Name or ID of the volume type + :param project_id: Project to set default type for + """ + + body = { + "default_type": { + "volume_type": volume_type + } + } + + return self._create_update_with_base_url( + 'v3/default-types/%s' % project_id, body, + response_key='default_type') + + def list(self, project_id=None): + """List the default types.""" + + url = 'v3/default-types' + response_key = "default_types" + + if project_id: + url += '/' + project_id + response_key = "default_type" + + return self._get_all_with_base_url(url, response_key) + + def delete(self, project_id): + """Removes the default volume type for a project + + :param project_id: The ID of the project to unset default for. + """ + + return self._delete_with_base_url('v3/default-types/%s' % project_id) diff --git a/cinderclient/v3/shell.py b/cinderclient/v3/shell.py index 1ccf02e56..eaded7eab 100644 --- a/cinderclient/v3/shell.py +++ b/cinderclient/v3/shell.py @@ -2598,3 +2598,54 @@ def do_transfer_list(cs, args): columns = ['ID', 'Volume ID', 'Name'] utils.print_list(transfers, columns) AppendFilters.filters = [] + + +@api_versions.wraps('3.62') +@utils.arg('volume_type', + metavar='', + help='Name or ID of the volume type.') +@utils.arg('project', + metavar='', + help='ID of project for which to set default type.') +def do_default_type_set(cs, args): + """Sets a default volume type for a project.""" + volume_type = args.volume_type + project = args.project + + default_type = cs.default_types.create(volume_type, project) + utils.print_dict(default_type._info) + + +@api_versions.wraps('3.62') +@utils.arg('--project-id', + metavar='', + default=None, + help='ID of project for which to show the default type.') +def do_default_type_list(cs, args): + """Lists all default volume types.""" + + project_id = args.project_id + default_types = cs.default_types.list(project_id) + columns = ['Volume Type ID', 'Project ID'] + if project_id: + utils.print_dict(default_types._info) + else: + utils.print_list(default_types, columns) + + +@api_versions.wraps('3.62') +@utils.arg('project_id', + metavar='', + nargs='+', + help='ID of project for which to unset default type.') +def do_default_type_unset(cs, args): + """Unset default volume types.""" + + for project_id in args.project_id: + try: + cs.default_types.delete(project_id) + print("Default volume type for project %s has been unset " + "successfully." % (project_id)) + except Exception as e: + print("Unset for default volume type for project %s failed: %s" + % (project_id, e)) diff --git a/releasenotes/notes/project-default-types-727156d1db10a24d.yaml b/releasenotes/notes/project-default-types-727156d1db10a24d.yaml new file mode 100644 index 000000000..c4385a595 --- /dev/null +++ b/releasenotes/notes/project-default-types-727156d1db10a24d.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Added support to set, get, and unset the default volume type for + projects with Block Storage API version 3.62 and higher. + From 5026a8add385b894d699f42a4e749b02dc11b297 Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Mon, 14 Sep 2020 08:30:20 -0400 Subject: [PATCH 043/159] Add note for Victoria release Change-Id: If948b069a52f3a4fc9d2464b86652fc523e8af53 --- .../notes/victoria-release-0d9c2b43845c3d9e.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 releasenotes/notes/victoria-release-0d9c2b43845c3d9e.yaml diff --git a/releasenotes/notes/victoria-release-0d9c2b43845c3d9e.yaml b/releasenotes/notes/victoria-release-0d9c2b43845c3d9e.yaml new file mode 100644 index 000000000..485513f76 --- /dev/null +++ b/releasenotes/notes/victoria-release-0d9c2b43845c3d9e.yaml @@ -0,0 +1,11 @@ +--- +prelude: | + The Victoria release of the python-cinderclient supports Block Storage + API version 2 and Block Storage API version 3 through microversion + 3.62. (The maximum microversion of the Block Storage API in the + Victoria release is 3.62.) +features: + - | + Added support to display the ``cluster_name`` attribute in volume + detail output for admin users with Block Storage API version 3.61 + and higher. From 3600121929ca4c494469700bddc28be746f50ef8 Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Wed, 16 Sep 2020 07:36:20 -0400 Subject: [PATCH 044/159] Add functional-py38 job The openstack-python3-victoria-jobs template is running unit tests for py36 and py38. We should do the same for our functional test jobs. So replace the functional-py37 job with a functional-py38 job. Change-Id: Icb338611169975be04bb27b86b5b3de0a37a6f5f --- .zuul.yaml | 24 +++++------------------- tox.ini | 5 +++++ 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index a38d6f16c..fc4686ce5 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -21,26 +21,12 @@ tox_envlist: functional-py36 - job: - name: python-cinderclient-functional-py37 + name: python-cinderclient-functional-py38 parent: python-cinderclient-functional-base - # Just to be clear what's going on here: which python is used by - # tox is controlled by tox.ini. But, that python needs to - # actually be available on the node running the job in order for - # the job to succeed. At this point, we can assume that 3.6 will - # be available everywhere (this is guaranteed by openstack-infra). - # But 3.7 is still problematic (don't ask me why). So for this - # job that we want running in py3.7, we need to (a) specify a - # nodeset for which py3.7 is available, and (b) tell the job to - # make sure it's available (i.e., install it if necessary). - # (a) is handled by the 'nodeset' specification below. - # (b) is handled by the setting the 'python_version' variable - # below, although by itself that doesn't do anything: it also - # requires that the 'ensure-python' role is included in the - # job playbook. - nodeset: openstack-single-node-bionic + nodeset: openstack-single-node-focal vars: - python_version: 3.7 - tox_envlist: functional-py37 + python_version: 3.8 + tox_envlist: functional-py38 - project: templates: @@ -54,6 +40,6 @@ check: jobs: - python-cinderclient-functional-py36 - - python-cinderclient-functional-py37 + - python-cinderclient-functional-py38 - openstack-tox-pylint: voting: false diff --git a/tox.ini b/tox.ini index dab603dc0..bd8a2c629 100644 --- a/tox.ini +++ b/tox.ini @@ -104,6 +104,11 @@ setenv = {[testenv:functional]setenv} passenv = {[testenv:functional]passenv} commands = {[testenv:functional]commands} +[testenv:functional-py38] +setenv = {[testenv:functional]setenv} +passenv = {[testenv:functional]passenv} +commands = {[testenv:functional]commands} + [flake8] show-source = True ignore = H404,H405,E122,E123,E128,E251,W504 From 0b3e99731a635890a114fd08ecf0f19cf06811e2 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Wed, 16 Sep 2020 18:35:18 +0000 Subject: [PATCH 045/159] 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: Ia36030fb2858ddf4c6672173cde510dd8f8b7a53 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 a4a59f99f..ca77d930b 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ :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 5558ba7fdcbe8eeb60fbcb3f20b4893f62d045d6 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Wed, 16 Sep 2020 18:35:23 +0000 Subject: [PATCH 046/159] 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: I4aed8f56609dea21a8170bd5f5d242cfac045dde --- .zuul.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.zuul.yaml b/.zuul.yaml index fc4686ce5..7e92f758b 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -34,7 +34,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 da9728b15b94e30f814b9420a769adbf64e63e33 Mon Sep 17 00:00:00 2001 From: tushargite96 Date: Tue, 15 Sep 2020 22:04:04 +0530 Subject: [PATCH 047/159] doc: Update Py37 instead of py27 Examples in this section should use a Python 3 environment and not py27. Change-Id: If082b92e089af980e411b4b4c1319e462862a55f Closes-Bug: #1866375 --- doc/source/contributor/unit_tests.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/source/contributor/unit_tests.rst b/doc/source/contributor/unit_tests.rst index c86891f51..387ecb00b 100644 --- a/doc/source/contributor/unit_tests.rst +++ b/doc/source/contributor/unit_tests.rst @@ -27,13 +27,13 @@ options and what the test run does by default. Running a subset of tests using tox ----------------------------------- One common activity is to just run a single test, you can do this with tox -simply by specifying to just run py27 or py35 tests against a single test:: +simply by specifying to just run py3 tests against a single test:: - tox -epy27 -- -n cinderclient.tests.unit.v2.test_volumes.VolumesTest.test_attach + tox -e py3 -- -n cinderclient.tests.unit.v2.test_volumes.VolumesTest.test_attach Or all tests in the test_volumes.py file:: - tox -epy27 -- -n cinderclient.tests.unit.v2.test_volumes + tox -e py3 -- -n cinderclient.tests.unit.v2.test_volumes For more information on these options and how to run tests, please see the `stestr documentation `_. From d92f15a09e59942bb60b7ef3a81c1e21dc16b578 Mon Sep 17 00:00:00 2001 From: Eduardo Santos Date: Fri, 23 Oct 2020 14:17:23 +0000 Subject: [PATCH 048/159] Fix undesirable raw Python error Using the cinderclient 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 `cinder --os-volume-api-version 3.40`. Added a default value to `func` and an empty value to `command` as placeholders so that a help message is shown instead of the Python error. Change-Id: Idb51e8635b97f0da2976f3268d5e19100ec77203 Closes-Bug: #1867061 --- cinderclient/shell.py | 3 +++ cinderclient/tests/unit/test_shell.py | 12 ++++++++++++ ...867061-fix-py-raw-error-msg-ff3c6da0b01d5d6c.yaml | 7 +++++++ 3 files changed, 22 insertions(+) create mode 100644 releasenotes/notes/bug-1867061-fix-py-raw-error-msg-ff3c6da0b01d5d6c.yaml diff --git a/cinderclient/shell.py b/cinderclient/shell.py index cfd4e82e8..ff8c4c4c8 100644 --- a/cinderclient/shell.py +++ b/cinderclient/shell.py @@ -225,6 +225,9 @@ def get_base_parser(self): default=0, help=_('Number of retries.')) + parser.set_defaults(func=self.do_help) + parser.set_defaults(command='') + if osprofiler_profiler: parser.add_argument('--profile', metavar='HMAC_KEY', diff --git a/cinderclient/tests/unit/test_shell.py b/cinderclient/tests/unit/test_shell.py index ac7404cbf..c8a6375d8 100644 --- a/cinderclient/tests/unit/test_shell.py +++ b/cinderclient/tests/unit/test_shell.py @@ -148,6 +148,18 @@ def test_help_on_subcommand_mv(self): self.assertThat(help_text, matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE)) + def test_help_arg_no_subcommand(self): + required = [ + r'.*?^usage: ', + r'.*?(?m)^\s+create\s+Creates a volume.', + r'.*?(?m)^\s+summary\s+Get volumes summary.', + r'.*?(?m)^Run "cinder help SUBCOMMAND" for help on a subcommand.', + ] + help_text = self.shell('--os-volume-api-version 3.40') + for r in required: + self.assertThat(help_text, + matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE)) + @ddt.data('backup-create --help', '--help backup-create') def test_dash_dash_help_on_subcommand(self, cmd): required = ['.*?^Creates a volume backup.'] diff --git a/releasenotes/notes/bug-1867061-fix-py-raw-error-msg-ff3c6da0b01d5d6c.yaml b/releasenotes/notes/bug-1867061-fix-py-raw-error-msg-ff3c6da0b01d5d6c.yaml new file mode 100644 index 000000000..91d026bf1 --- /dev/null +++ b/releasenotes/notes/bug-1867061-fix-py-raw-error-msg-ff3c6da0b01d5d6c.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + `Bug #1867061 `_: + Fixed raw Python error message when using ``cinder`` without + a subcommand while passing an optional argument, such as + ``--os-volume-api-version``. \ No newline at end of file From 14bc1360e05a8eeabe463426d9578131fce75c6f Mon Sep 17 00:00:00 2001 From: "zhu.boxiang" Date: Thu, 17 Dec 2020 11:03:11 +0800 Subject: [PATCH 049/159] Fix list resources when use limit and with-count When we use with-count query param, we will get the count of resources. While the _list function return two different result, one is just list, another is tuple with list and int. So we must to handle the items which return from _list function. Closes-bug: #1908474 Change-Id: If8e31a637cf098cca60d8a10e9835ef66a3e66bc --- cinderclient/base.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cinderclient/base.py b/cinderclient/base.py index 40ce381be..60bd41040 100644 --- a/cinderclient/base.py +++ b/cinderclient/base.py @@ -125,6 +125,12 @@ def _list(self, url, response_key, obj_class=None, body=None, # till there is no more items. items = self._list(next, response_key, obj_class, None, limit, items) + # If we use '--with-count' to get the resource count, + # the _list function will return the tuple result with + # (resources, count). + # So here, we must check the items' type then to do return. + if isinstance(items, tuple): + items = items[0] if "count" in body: return common_base.ListWithMeta(items, resp), body['count'] else: From 1abc1b5d404c523a696f7186bc4c4b6fc7407cad Mon Sep 17 00:00:00 2001 From: Alan Bishop Date: Thu, 10 Dec 2020 12:41:40 -0800 Subject: [PATCH 050/159] Update requirements and lower-constraints Sync the versions with cinder's, which were recently updated by I42af21b1c4247d04d479f1fc1ecd6f9baac0cfc9. Also increased the minimum version of tempest to the most recent release. Also added indirect dependencies to test-requirements.txt in order to limit the number of versions considered by the resolver. Change-Id: I7b4bc7b392b2192e0c832c4f0148546a5920b9e2 --- lower-constraints.txt | 96 +++++++++++++++++++++++-------------------- requirements.txt | 16 ++++---- test-requirements.txt | 23 +++++++---- 3 files changed, 74 insertions(+), 61 deletions(-) diff --git a/lower-constraints.txt b/lower-constraints.txt index 17477981c..d0c0ef7e2 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -1,58 +1,64 @@ asn1crypto==0.23.0 -cffi==1.14.0 -cliff==2.8.0 -cmd2==0.8.0 -coverage==4.0 -cryptography==2.7 -ddt==1.0.1 -debtcollector==1.2.0 -doc8==0.6.0 +attrs==20.3.0 +certifi==2020.6.20 +cffi==1.14.2 +chardet==3.0.4 +cliff==3.4.0 +cmd2==1.3.8 +colorama==0.4.4 +coverage==5.2.1 +cryptography==3.1 +ddt==1.4.1 +debtcollector==2.2.0 +doc8==0.8.1 +docutils==0.15.2 +dulwich==0.20.6 extras==1.0.0 -fasteners==0.7.0 +fasteners==0.14.1 fixtures==3.0.0 -future==0.16.0 -idna==2.6 -iso8601==0.1.11 -jsonschema==2.6.0 -keystoneauth1==3.4.0 +future==0.18.2 +idna==2.10 +iso8601==0.1.12 +jsonschema==3.2.0 +keystoneauth1==4.2.1 linecache2==1.0.0 -mccabe==0.2.1 +mccabe==0.6.0 monotonic==0.6 msgpack-python==0.4.0 -netaddr==0.7.18 -netifaces==0.10.4 -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 +netaddr==0.8.0 +netifaces==0.10.9 +oslo.concurrency==4.3.0 +oslo.config==8.3.2 +oslo.context==3.1.1 +oslo.i18n==5.0.1 +oslo.log==4.4.0 +oslo.serialization==4.0.1 +oslo.utils==4.7.0 +paramiko==2.7.2 +pbr==5.5.0 +prettytable==0.7.2 +pyasn1==0.4.8 +pycparser==2.20 pyinotify==0.9.6 -pyparsing==2.1.0 -pyperclip==1.5.27 -python-dateutil==2.5.3 +pyparsing==2.4.7 +pyperclip==1.8.0 +python-dateutil==2.8.1 python-mimeparse==1.6.0 -python-subunit==1.0.0 -pytz==2013.6 -PyYAML==3.13 -reno==3.1.0 +python-subunit==1.4.0 +pytz==2020.1 +PyYAML==5.3.1 +reno==3.2.0 requests-mock==1.2.0 -requests==2.14.2 -rfc3986==0.3.1 +requests==2.23.0 +rfc3986==1.4.0 simplejson==3.5.1 -six==1.10.0 -stestr==1.0.0 -stevedore==1.20.0 -tempest==17.1.0 +six==1.15.0 +stestr==3.0.1 +stevedore==3.2.2 +tempest==26.0.0 testrepository==0.0.18 -testtools==2.2.0 +testtools==2.4.0 traceback2==1.4.0 unittest2==1.1.0 -urllib3==1.21.1 -wrapt==1.7.0 +urllib3==1.25.10 +wrapt==1.12.1 diff --git a/requirements.txt b/requirements.txt index f6567f49a..051474451 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,12 @@ # 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.8,>=0.7.1 # BSD -keystoneauth1>=3.4.0 # Apache-2.0 +pbr>=5.5.0 # Apache-2.0 +PrettyTable<0.8,>=0.7.2 # BSD +keystoneauth1>=4.2.1 # Apache-2.0 simplejson>=3.5.1 # MIT -six>=1.10.0 # MIT -oslo.i18n>=3.15.3 # Apache-2.0 -oslo.utils>=3.33.0 # Apache-2.0 -requests!=2.20.0,>=2.14.2 # Apache-2.0 -stevedore>=1.20.0 # Apache-2.0 +six>=1.15.0 # MIT +oslo.i18n>=5.0.1 # Apache-2.0 +oslo.utils>=4.7.0 # Apache-2.0 +requests>=2.23.0 # Apache-2.0 +stevedore>=3.2.2 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index 774f99a4d..29ab6b0a8 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -4,13 +4,20 @@ # Hacking already pins down pep8, pyflakes and flake8 hacking>=3.1.0,<3.2.0 # Apache-2.0 -coverage!=4.4,>=4.0 # Apache-2.0 -ddt>=1.0.1 # MIT +docutils>=0.15.2 +coverage>=5.2.1 # Apache-2.0 +ddt>=1.4.1 # MIT fixtures>=3.0.0 # Apache-2.0/BSD -reno>=3.1.0 # Apache-2.0 +reno>=3.2.0 # Apache-2.0 requests-mock>=1.2.0 # Apache-2.0 -tempest>=17.1.0 # Apache-2.0 -testtools>=2.2.0 # MIT -stestr>=1.0.0 # Apache-2.0 -oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0 -doc8>=0.6.0 # Apache-2.0 +tempest>=26.0.0 # Apache-2.0 +testtools>=2.4.0 # MIT +stestr>=3.0.1 # Apache-2.0 +oslo.serialization>=4.0.1 # Apache-2.0 +doc8>=0.8.1 # Apache-2.0 +# +# These are here to enable the resolver to work faster. +# They are not directly used by python-cinderclient. +debtcollector>=2.2.0 +dulwich>=0.20.6 +mccabe>=0.6.0 From 7e3566ed04c8f664f6e1df0614499989f6b3560a Mon Sep 17 00:00:00 2001 From: Alan Bishop Date: Mon, 11 Jan 2021 13:05:11 -0800 Subject: [PATCH 051/159] Support backup-restore to a specific volume type or AZ Enhance the 'backup-restore' shell command to support restoring a backup to a newly created volume of a specific volume type and/or in a different AZ. New '--volume-type' and '--availability-zone' arguments leverage the existing cinder API's ability to create a volume from a backup, which was added in microversion v3.47. The shell code is a new v3 implementation, and it drops support for the v2 command's deprecated '--volume-id' argument. Change-Id: Ic6645d3b973f8487903c5f57e936ba3b4b3bf005 --- cinderclient/tests/unit/v3/test_shell.py | 157 ++++++++++++++++++ cinderclient/v3/shell.py | 68 ++++++++ ...estore-shell-command-0cf55df6ca4b4c55.yaml | 7 + 3 files changed, 232 insertions(+) create mode 100644 releasenotes/notes/enhance-backup-restore-shell-command-0cf55df6ca4b4c55.yaml diff --git a/cinderclient/tests/unit/v3/test_shell.py b/cinderclient/tests/unit/v3/test_shell.py index 0332ae3d1..314259dca 100644 --- a/cinderclient/tests/unit/v3/test_shell.py +++ b/cinderclient/tests/unit/v3/test_shell.py @@ -1641,3 +1641,160 @@ def test_default_type_delete(self): '629632e7-99d2-4c40-9ae3-106fa3b1c9b7') self.assert_called( 'DELETE', 'v3/default-types/629632e7-99d2-4c40-9ae3-106fa3b1c9b7') + + def test_restore(self): + self.run_command('backup-restore 1234') + self.assert_called('POST', '/backups/1234/restore') + + def test_restore_with_name(self): + self.run_command('backup-restore 1234 --name restore_vol') + expected = {'restore': {'volume_id': None, 'name': 'restore_vol'}} + self.assert_called('POST', '/backups/1234/restore', + body=expected) + + def test_restore_with_name_error(self): + self.assertRaises(exceptions.CommandError, self.run_command, + 'backup-restore 1234 --volume fake_vol --name ' + 'restore_vol') + + def test_restore_with_az(self): + self.run_command('--os-volume-api-version 3.47 backup-restore 1234 ' + '--name restore_vol --availability-zone restore_az') + expected = {'volume': {'size': 10, + 'name': 'restore_vol', + 'availability_zone': 'restore_az', + 'backup_id': '1234', + 'metadata': {}, + 'imageRef': None, + 'source_volid': None, + 'consistencygroup_id': None, + 'snapshot_id': None, + 'volume_type': None, + 'description': None}} + self.assert_called('POST', '/volumes', body=expected) + + def test_restore_with_az_microversion_error(self): + self.assertRaises(exceptions.UnsupportedAttribute, self.run_command, + '--os-volume-api-version 3.46 backup-restore 1234 ' + '--name restore_vol --availability-zone restore_az') + + def test_restore_with_volume_type(self): + self.run_command('--os-volume-api-version 3.47 backup-restore 1234 ' + '--name restore_vol --volume-type restore_type') + expected = {'volume': {'size': 10, + 'name': 'restore_vol', + 'volume_type': 'restore_type', + 'backup_id': '1234', + 'metadata': {}, + 'imageRef': None, + 'source_volid': None, + 'consistencygroup_id': None, + 'snapshot_id': None, + 'availability_zone': None, + 'description': None}} + self.assert_called('POST', '/volumes', body=expected) + + def test_restore_with_volume_type_microversion_error(self): + self.assertRaises(exceptions.UnsupportedAttribute, self.run_command, + '--os-volume-api-version 3.46 backup-restore 1234 ' + '--name restore_vol --volume-type restore_type') + + def test_restore_with_volume_type_and_az_no_name(self): + self.run_command('--os-volume-api-version 3.47 backup-restore 1234 ' + '--volume-type restore_type ' + '--availability-zone restore_az') + expected = {'volume': {'size': 10, + 'name': 'restore_backup_1234', + 'volume_type': 'restore_type', + 'availability_zone': 'restore_az', + 'backup_id': '1234', + 'metadata': {}, + 'imageRef': None, + 'source_volid': None, + 'consistencygroup_id': None, + 'snapshot_id': None, + 'description': None}} + self.assert_called('POST', '/volumes', body=expected) + + @ddt.data( + { + 'volume': '1234', + 'name': None, + 'volume_type': None, + 'availability_zone': None, + }, { + 'volume': '1234', + 'name': 'ignored', + 'volume_type': None, + 'availability_zone': None, + }, { + 'volume': None, + 'name': 'sample-volume', + 'volume_type': 'sample-type', + 'availability_zone': None, + }, { + 'volume': None, + 'name': 'sample-volume', + 'volume_type': None, + 'availability_zone': 'az1', + }, { + 'volume': None, + 'name': 'sample-volume', + 'volume_type': None, + 'availability_zone': 'different-az', + }, { + 'volume': None, + 'name': None, + 'volume_type': None, + 'availability_zone': 'different-az', + }, + ) + @ddt.unpack + @mock.patch('cinderclient.utils.print_dict') + @mock.patch('cinderclient.tests.unit.v2.fakes._stub_restore') + def test_do_backup_restore(self, + mock_stub_restore, + mock_print_dict, + volume, + name, + volume_type, + availability_zone): + + # Restore from the fake '1234' backup. + cmd = '--os-volume-api-version 3.47 backup-restore 1234' + + if volume: + cmd += ' --volume %s' % volume + if name: + cmd += ' --name %s' % name + if volume_type: + cmd += ' --volume-type %s' % volume_type + if availability_zone: + cmd += ' --availability-zone %s' % availability_zone + + if name or volume: + volume_name = 'sample-volume' + else: + volume_name = 'restore_backup_1234' + + mock_stub_restore.return_value = {'volume_id': '1234', + 'volume_name': volume_name} + + self.run_command(cmd) + + # Check whether mock_stub_restore was called in order to determine + # whether the restore command invoked the backup-restore API. If + # mock_stub_restore was not called then this indicates the command + # invoked the volume-create API to restore the backup to a new volume + # of a specific volume type, or in a different AZ (the fake '1234' + # backup is in az1). + if volume_type or availability_zone == 'different-az': + mock_stub_restore.assert_not_called() + else: + mock_stub_restore.assert_called_once() + + mock_print_dict.assert_called_once_with({ + 'backup_id': '1234', + 'volume_id': '1234', + 'volume_name': volume_name, + }) diff --git a/cinderclient/v3/shell.py b/cinderclient/v3/shell.py index eaded7eab..306535208 100644 --- a/cinderclient/v3/shell.py +++ b/cinderclient/v3/shell.py @@ -218,6 +218,74 @@ def do_backup_list(cs, args): AppendFilters.filters = [] +@utils.arg('backup', metavar='', + help='Name or ID of backup to restore.') +@utils.arg('--volume', metavar='', + default=None, + help='Name or ID of existing volume to which to restore. ' + 'This is mutually exclusive with --name and takes priority. ' + 'Default=None.') +@utils.arg('--name', metavar='', + default=None, + help='Use the name for new volume creation to restore. ' + 'This is mutually exclusive with --volume and --volume ' + 'takes priority. ' + 'Default=None.') +@utils.arg('--volume-type', + metavar='', + default=None, + start_version='3.47', + help='Volume type for the new volume creation to restore. This ' + 'option is not valid when used with the "volume" option. ' + 'Default=None.') +@utils.arg('--availability-zone', metavar='', + default=None, + start_version='3.47', + help='AZ for the new volume creation to restore. By default it ' + 'will be the same as backup AZ. This option is not valid when ' + 'used with the "volume" option. Default=None.') +def do_backup_restore(cs, args): + """Restores a backup.""" + if args.volume: + volume_id = utils.find_volume(cs, args.volume).id + if args.name: + args.name = None + print('Mutually exclusive options are specified simultaneously: ' + '"volume" and "name". The volume option takes priority.') + else: + volume_id = None + + volume_type = getattr(args, 'volume_type', None) + az = getattr(args, 'availability_zone', None) + if (volume_type or az) and args.volume: + msg = ('The "volume-type" and "availability-zone" options are not ' + 'valid when used with the "volume" option.') + raise exceptions.ClientException(code=1, message=msg) + + backup = shell_utils.find_backup(cs, args.backup) + info = {"backup_id": backup.id} + + if volume_type or (az and az != backup.availability_zone): + # Implement restoring a backup to a newly created volume of a + # specific volume type or in a different AZ by using the + # volume-create API. The default volume name matches the pattern + # cinder uses (see I23730834058d88e30be62624ada3b24cdaeaa6f3). + volume_name = args.name or 'restore_backup_%s' % backup.id + volume = cs.volumes.create(size=backup.size, + name=volume_name, + volume_type=volume_type, + availability_zone=az, + backup_id=backup.id) + info['volume_id'] = volume._info['id'] + info['volume_name'] = volume_name + else: + restore = cs.restores.restore(backup.id, volume_id, args.name) + info.update(restore._info) + info.pop('links', None) + + utils.print_dict(info) + + @utils.arg('--detail', action='store_true', help='Show detailed information about pools.') diff --git a/releasenotes/notes/enhance-backup-restore-shell-command-0cf55df6ca4b4c55.yaml b/releasenotes/notes/enhance-backup-restore-shell-command-0cf55df6ca4b4c55.yaml new file mode 100644 index 000000000..ee5d85202 --- /dev/null +++ b/releasenotes/notes/enhance-backup-restore-shell-command-0cf55df6ca4b4c55.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Enhance the ``backup-restore`` shell command to support restoring to a new + volume created with a specific volume type and/or in a different AZ. New + ``--volume-type`` and ``--availability-zone`` arguments are compatible with + cinder API microversion v3.47 onward. From 070307d2b4ec181301aea248e06a5f549930c0e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Weing=C3=A4rtner?= Date: Wed, 27 Jan 2021 16:01:26 -0300 Subject: [PATCH 052/159] Add MV 3.63 to the max supported version During the implementation on [1], it was requested to extend the Cinder client to support the newly created microversion (MV). Therefore, this patch is doing exactly that. We do not need to do any other work forward bcause the list volumes will display whatever we have in the response. I mean, the default is ``['ID', 'Status', 'Name', 'Size', 'Volume Type', 'Bootable', 'Attached to']``, but one can customize these fields using ``--fields`` [1] https://review.opendev.org/c/openstack/cinder/+/666886 Closes-Bug: https://bugs.launchpad.net/python-cinderclient/+bug/1913474 Depends-on: https://review.opendev.org/c/openstack/cinder/+/666886 Change-Id: I0815570a50e9b38fe18733c727acd52a406bfc1e --- cinderclient/api_versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cinderclient/api_versions.py b/cinderclient/api_versions.py index ff4cc9310..55dbfec6a 100644 --- a/cinderclient/api_versions.py +++ b/cinderclient/api_versions.py @@ -29,7 +29,7 @@ # key is a deprecated version and value is an alternative version. DEPRECATED_VERSIONS = {"2": "3"} DEPRECATED_VERSION = "2.0" -MAX_VERSION = "3.62" +MAX_VERSION = "3.63" MIN_VERSION = "3.0" _SUBSTITUTIONS = {} From 2c43d65238063eeaf54e9ae4b0e1c8463c9cbe0e Mon Sep 17 00:00:00 2001 From: tushargite96 Date: Wed, 3 Feb 2021 21:59:28 +0530 Subject: [PATCH 053/159] Stop configuring install_command in tox. 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: I7a703e0ee91b73b7ab736712756caaba6981f8e4 --- tox.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/tox.ini b/tox.ini index bd8a2c629..fcf35415e 100644 --- a/tox.ini +++ b/tox.ini @@ -11,7 +11,6 @@ ignore_basepython_conflict=true [testenv] basepython = python3 usedevelop = True -install_command = pip install {opts} {packages} setenv = VIRTUAL_ENV={envdir} OS_TEST_PATH=./cinderclient/tests/unit From f1236e09404c52b5eb3d9ba528be91d49487e08a Mon Sep 17 00:00:00 2001 From: Wander Way Date: Thu, 18 Feb 2021 14:27:47 +0800 Subject: [PATCH 054/159] Uncap PrettyTable This is now maintained as a Jazzband project [1]. [1] https://github.com/jazzband/prettytable Change-Id: I71ace4c2857b9e12e9023aadaf72bbe97c2fda1c --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 051474451..f5445f44f 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>=5.5.0 # Apache-2.0 -PrettyTable<0.8,>=0.7.2 # BSD +PrettyTable>=0.7.2 # BSD keystoneauth1>=4.2.1 # Apache-2.0 simplejson>=3.5.1 # MIT six>=1.15.0 # MIT From 2ab0c6ee79fb3c30748de4e5e89686fc56699af7 Mon Sep 17 00:00:00 2001 From: "wu.shiming" Date: Fri, 11 Sep 2020 15:38:04 +0800 Subject: [PATCH 055/159] Remove install unnecessary packages The docs requirements migrated to doc/requirements.txt we need not install things from requirements.txt. Change-Id: I94c2ba1ddfc3a54edc2f69b8d7017e09b02e54a5 --- tox.ini | 2 -- 1 file changed, 2 deletions(-) diff --git a/tox.ini b/tox.ini index bd8a2c629..a8b560de8 100644 --- a/tox.ini +++ b/tox.ini @@ -58,7 +58,6 @@ commands = [testenv:docs] deps = -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} - -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = sphinx-build -W -b html doc/source doc/build/html @@ -76,7 +75,6 @@ whitelist_externals = [testenv:releasenotes] deps = -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} - -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html From 755dabdc92ebd848e6ba786f8a89f2f3dbdcc3db Mon Sep 17 00:00:00 2001 From: Luigi Toscano Date: Fri, 19 Feb 2021 17:30:14 +0100 Subject: [PATCH 056/159] Bump pylint to 2.6.0 The current version does not work anymore with the current testing stack. Due to the bump, the wrapper needed a few changes (maybe it could be dropped at some point? Version 2.3.0 (used by cinder right now) was considered too, but it requires a specific version of isort: https://github.com/PyCQA/isort/issues/1273 Change-Id: I07fa32e7f49bde041d101a2d09860d0bc27acda0 --- tools/lintstack.py | 2 +- tox.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/lintstack.py b/tools/lintstack.py index 1ae34d73d..269777606 100755 --- a/tools/lintstack.py +++ b/tools/lintstack.py @@ -153,7 +153,7 @@ def run_pylint(): args = [ "--msg-template='{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}'", "-E", "cinderclient"] - lint.Run(args, reporter=reporter, exit=False) + lint.Run(args, reporter=reporter, do_exit=False) val = buff.getvalue() buff.close() return val diff --git a/tox.ini b/tox.ini index bd8a2c629..2151067a4 100644 --- a/tox.ini +++ b/tox.ini @@ -38,7 +38,7 @@ commands = deps = -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt - pylint==1.9.1 + pylint==2.6.0 commands = bash tools/lintstack.sh whitelist_externals = bash From 8ea4f1379dab8ffb6b9a00f5af9aca1e43e2a1e9 Mon Sep 17 00:00:00 2001 From: tushargite96 Date: Thu, 17 Dec 2020 17:31:41 +0530 Subject: [PATCH 057/159] Doc: Functional Tests in python-cinderclient ostestr command is deprecated. Closes-bug: #1866376 Change-Id: I24398d481002f30754368ac601ff3ac304f3fb82 --- doc/source/contributor/functional_tests.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/contributor/functional_tests.rst b/doc/source/contributor/functional_tests.rst index d6b2db210..1e3f239c4 100644 --- a/doc/source/contributor/functional_tests.rst +++ b/doc/source/contributor/functional_tests.rst @@ -5,13 +5,13 @@ Functional Tests Cinderclient contains a suite of functional tests, in the cinderclient/ tests/functional directory. -These are currently non-voting, meaning that Jenkins will not reject a +These are currently non-voting, meaning that zuul will not reject a patched based on failure of the functional tests. It is highly recommended, however, that these tests are investigated in the case of a failure. Running the tests ----------------- -Run the tests using tox, which calls ostestr via the tox.ini file. To run all +Run the tests using tox, via the tox.ini file. To run all tests simply run:: tox -e functional From 2611cd118df8d72d8b9ecee8f94dfd5fc6cb9dd6 Mon Sep 17 00:00:00 2001 From: tushargite96 Date: Fri, 19 Feb 2021 14:09:22 +0530 Subject: [PATCH 058/159] Move cinderclient to new hacking 4.0.0 Change-Id: Id3e61949550e70e2c4235406d258bbd9c66e67cf --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 29ab6b0a8..c66025919 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -3,7 +3,7 @@ # process, which may cause wedges in the gate later. # Hacking already pins down pep8, pyflakes and flake8 -hacking>=3.1.0,<3.2.0 # Apache-2.0 +hacking>=4.0.0,<4.1.0 # Apache-2.0 docutils>=0.15.2 coverage>=5.2.1 # Apache-2.0 ddt>=1.4.1 # MIT From 55059f6a890505cbe87803174c09a87528ade909 Mon Sep 17 00:00:00 2001 From: tushargite96 Date: Wed, 20 Jan 2021 13:39:36 +0530 Subject: [PATCH 059/159] Changed minversion in tox to 3.18.0 The patch bumps min version of tox to 3.18.0 in order to replace tox's whitelist_externals by allowlist_externals option: https://github.com/tox-dev/tox/blob/master/docs/changelog.rst#v3180-2020-07-23 Change-Id: Ia637e459b9401ab3f07fdf6e88868080ce59e245 --- tox.ini | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tox.ini b/tox.ini index bd8a2c629..e69d989c5 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ [tox] distribute = False envlist = py36,py38,pep8 -minversion = 3.1.0 +minversion = 3.18.0 skipsdist = True skip_missing_interpreters = true # this allows tox to infer the base python from the environment name @@ -27,7 +27,7 @@ deps = commands = find . -type f -name "*.pyc" -delete stestr run {posargs} stestr slowest -whitelist_externals = find +allowlist_externals = find [testenv:pep8] commands = @@ -40,7 +40,7 @@ deps = -r{toxinidir}/requirements.txt pylint==1.9.1 commands = bash tools/lintstack.sh -whitelist_externals = bash +allowlist_externals = bash [testenv:venv] commands = {posargs} @@ -69,7 +69,7 @@ commands = {[testenv:docs]commands} sphinx-build -W -b latex doc/source doc/build/pdf make -C doc/build/pdf -whitelist_externals = +allowlist_externals = make cp From cea1f674ae1ce545c0cac209b423ac9d626f0c68 Mon Sep 17 00:00:00 2001 From: haixin Date: Wed, 30 Sep 2020 10:40:31 +0800 Subject: [PATCH 060/159] Remove all usage of six library Replace six with Python 3 style code. Change-Id: I4b97e040f3e790ac114dcd43c68e6b67b1079adf --- cinderclient/apiclient/base.py | 5 +-- cinderclient/base.py | 8 ++-- cinderclient/client.py | 13 +++---- cinderclient/shell.py | 7 ++-- cinderclient/shell_utils.py | 4 +- cinderclient/tests/functional/base.py | 6 +-- .../functional/test_snapshot_create_cli.py | 4 +- .../functional/test_volume_create_cli.py | 9 ++--- .../functional/test_volume_extend_cli.py | 12 ++---- cinderclient/tests/unit/test_api_versions.py | 3 +- cinderclient/tests/unit/test_base.py | 15 -------- cinderclient/tests/unit/test_client.py | 7 ++-- cinderclient/tests/unit/test_shell.py | 4 +- cinderclient/tests/unit/test_utils.py | 15 +------- cinderclient/tests/unit/utils.py | 5 +-- cinderclient/tests/unit/v2/fakes.py | 2 +- .../tests/unit/v2/test_availability_zone.py | 24 ++++++------ cinderclient/tests/unit/v2/test_shell.py | 2 +- cinderclient/tests/unit/v3/test_messages.py | 2 +- cinderclient/tests/unit/v3/test_shell.py | 11 ++---- cinderclient/tests/unit/v3/test_volumes.py | 3 +- cinderclient/utils.py | 37 +++---------------- cinderclient/v2/shell.py | 6 +-- cinderclient/v3/group_types.py | 2 +- cinderclient/v3/groups.py | 2 +- cinderclient/v3/shell.py | 27 +++++++------- cinderclient/v3/volume_types.py | 2 +- lower-constraints.txt | 1 - requirements.txt | 1 - tools/colorizer.py | 3 +- tools/install_venv.py | 3 +- tools/lintstack.py | 2 +- 32 files changed, 83 insertions(+), 164 deletions(-) diff --git a/cinderclient/apiclient/base.py b/cinderclient/apiclient/base.py index f1febe30d..c0bcd24a5 100644 --- a/cinderclient/apiclient/base.py +++ b/cinderclient/apiclient/base.py @@ -27,7 +27,7 @@ import copy from requests import Response -import six + from cinderclient.apiclient import exceptions from cinderclient import utils @@ -199,8 +199,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 diff --git a/cinderclient/base.py b/cinderclient/base.py index 40ce381be..f86a3a8b0 100644 --- a/cinderclient/base.py +++ b/cinderclient/base.py @@ -23,8 +23,6 @@ import hashlib import os -import six - from cinderclient.apiclient import base as common_base from cinderclient import exceptions from cinderclient import utils @@ -153,7 +151,7 @@ def _build_list_url(self, resource_type, detailed=True, search_opts=None, if offset: query_params['offset'] = offset - query_params = utils.unicode_key_value_to_string(query_params) + query_params = query_params # Transform the dict to a sequence of two-element tuples in fixed # order, then the encoded string will be consistent in Python 2&3. @@ -188,7 +186,7 @@ def _format_sort_param(self, sort, resource_type=None): if not sort: return None - if isinstance(sort, six.string_types): + if isinstance(sort, str): # Convert the string into a list for consistent validation sort = [s for s in sort.split(',') if s] @@ -352,7 +350,7 @@ def _delete_with_base_url(self, url, response_key=None): self.api.client.delete_with_base_url(url) -class ManagerWithFind(six.with_metaclass(abc.ABCMeta, Manager)): +class ManagerWithFind(Manager, metaclass=abc.ABCMeta): """ Like a `Manager`, but with additional `find()`/`findall()` methods. """ diff --git a/cinderclient/client.py b/cinderclient/client.py index ffc491fa7..2d7b80d14 100644 --- a/cinderclient/client.py +++ b/cinderclient/client.py @@ -24,7 +24,8 @@ import os import pkgutil import re -import six +import urllib +from urllib import parse as urlparse from keystoneauth1 import access from keystoneauth1 import adapter @@ -34,8 +35,6 @@ from oslo_utils import importutils from oslo_utils import strutils import requests -from six.moves import urllib -import six.moves.urllib.parse as urlparse from cinderclient._i18n import _ from cinderclient import api_versions @@ -131,7 +130,7 @@ def get_server_version(url, insecure=False, cacert=None): current_version = '2.0' except exceptions.ClientException as e: logger.warning("Error in server version query:%s\n" - "Returning APIVersion 2.0", six.text_type(e.message)) + "Returning APIVersion 2.0", str(e.message)) return (api_versions.APIVersion(min_version), api_versions.APIVersion(current_version)) @@ -239,7 +238,7 @@ def get_volume_api_version_from_endpoint(self): version = get_volume_api_from_url(self.get_endpoint()) except exceptions.UnsupportedVersion as e: msg = (_("Service catalog returned invalid url.\n" - "%s") % six.text_type(e)) + "%s") % str(e)) raise exceptions.UnsupportedVersion(msg) return version @@ -496,10 +495,10 @@ def get_volume_api_version_from_endpoint(self): except exceptions.UnsupportedVersion as e: if self.management_url == self.os_endpoint: msg = (_("Invalid url was specified in --os-endpoint %s") - % six.text_type(e)) + % str(e)) else: msg = (_("Service catalog returned invalid url.\n" - "%s") % six.text_type(e)) + "%s") % str(e)) raise exceptions.UnsupportedVersion(msg) diff --git a/cinderclient/shell.py b/cinderclient/shell.py index ff8c4c4c8..9e9cfb1f1 100644 --- a/cinderclient/shell.py +++ b/cinderclient/shell.py @@ -32,8 +32,7 @@ from oslo_utils import encodeutils from oslo_utils import importutils import requests -import six -import six.moves.urllib.parse as urlparse +from urllib import parse as urlparse import cinderclient from cinderclient._i18n import _ @@ -395,7 +394,7 @@ def _build_versioned_help_message(self, start_version, end_version): else: msg = (_(" (Supported until API version %(end)s)") % {"end": end_version.get_string()}) - return six.text_type(msg) + return str(msg) def _find_actions(self, subparsers, actions_module, version, do_help, input_args): @@ -1026,7 +1025,7 @@ def main(): sys.exit(130) except Exception as e: logger.debug(e, exc_info=1) - print("ERROR: %s" % six.text_type(e), file=sys.stderr) + print("ERROR: %s" % str(e), file=sys.stderr) sys.exit(1) diff --git a/cinderclient/shell_utils.py b/cinderclient/shell_utils.py index b5db281ec..cd8f1628f 100644 --- a/cinderclient/shell_utils.py +++ b/cinderclient/shell_utils.py @@ -201,7 +201,7 @@ def print_resource_filter_list(filters): def quota_show(quotas): - quotas_info_dict = utils.unicode_key_value_to_string(quotas._info) + quotas_info_dict = quotas._info quota_dict = {} for resource in quotas_info_dict.keys(): good_name = False @@ -216,7 +216,7 @@ def quota_show(quotas): def quota_usage_show(quotas): quota_list = [] - quotas_info_dict = utils.unicode_key_value_to_string(quotas._info) + quotas_info_dict = quotas._info for resource in quotas_info_dict.keys(): good_name = False for name in _quota_resources: diff --git a/cinderclient/tests/functional/base.py b/cinderclient/tests/functional/base.py index 3d2e2f8f9..2f03475a6 100644 --- a/cinderclient/tests/functional/base.py +++ b/cinderclient/tests/functional/base.py @@ -10,10 +10,10 @@ # License for the specific language governing permissions and limitations # under the License. +import configparser import os import time -import six from tempest.lib.cli import base from tempest.lib.cli import output_parser from tempest.lib import exceptions @@ -38,7 +38,7 @@ def credentials(): os.environ.get('OS_PROJECT_NAME')) auth_url = os.environ.get('OS_AUTH_URL') - config = six.moves.configparser.RawConfigParser() + config = configparser.RawConfigParser() if config.read(_CREDS_FILE): username = username or config.get('admin', 'user') password = password or config.get('admin', 'pass') @@ -101,7 +101,7 @@ def _get_property_from_output(self, output): obj = {} items = self.parser.listing(output) for item in items: - obj[item['Property']] = six.text_type(item['Value']) + obj[item['Property']] = str(item['Value']) return obj def object_cmd(self, object_name, cmd): diff --git a/cinderclient/tests/functional/test_snapshot_create_cli.py b/cinderclient/tests/functional/test_snapshot_create_cli.py index 7ef5dfda5..4c0bd1204 100644 --- a/cinderclient/tests/functional/test_snapshot_create_cli.py +++ b/cinderclient/tests/functional/test_snapshot_create_cli.py @@ -10,7 +10,7 @@ # 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 six + from cinderclient.tests.functional import base @@ -47,7 +47,7 @@ def test_snapshot_create_metadata(self): 'snapshot', params='--metadata test_metadata=test_date {0}'.format( self.volume['id'])) - self.assertEqual(six.text_type({u'test_metadata': u'test_date'}), + self.assertEqual(str({'test_metadata': 'test_date'}), snapshot['metadata']) self.object_delete('snapshot', snapshot['id']) self.check_object_deleted('snapshot', snapshot['id']) diff --git a/cinderclient/tests/functional/test_volume_create_cli.py b/cinderclient/tests/functional/test_volume_create_cli.py index deae383a0..9c9fc0d47 100644 --- a/cinderclient/tests/functional/test_volume_create_cli.py +++ b/cinderclient/tests/functional/test_volume_create_cli.py @@ -11,7 +11,6 @@ # under the License. import ddt -import six from tempest.lib import exceptions from cinderclient.tests.functional import base @@ -32,9 +31,9 @@ class CinderVolumeNegativeTests(base.ClientTestBase): ) @ddt.unpack def test_volume_create_with_incorrect_size(self, value, ex_text): - - six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text, - self.object_create, 'volume', params=value) + self.assertRaisesRegex(exceptions.CommandFailed, + ex_text, self.object_create, + 'volume', params=value) class CinderVolumeTests(base.ClientTestBase): @@ -96,5 +95,5 @@ def test_volume_create_metadata(self): """ volume = self.object_create( 'volume', params='--metadata test_metadata=test_date 1') - self.assertEqual(six.text_type({u'test_metadata': u'test_date'}), + self.assertEqual(str({'test_metadata': 'test_date'}), volume['metadata']) diff --git a/cinderclient/tests/functional/test_volume_extend_cli.py b/cinderclient/tests/functional/test_volume_extend_cli.py index 8dd557a2b..6a5c99cbf 100644 --- a/cinderclient/tests/functional/test_volume_extend_cli.py +++ b/cinderclient/tests/functional/test_volume_extend_cli.py @@ -11,8 +11,6 @@ # under the License. import ddt -import six - from tempest.lib import exceptions from cinderclient.tests.functional import base @@ -39,9 +37,8 @@ def setUp(self): ) @ddt.unpack def test_volume_extend_with_incorrect_size(self, value, ex_text): - - six.assertRaisesRegex( - self, exceptions.CommandFailed, ex_text, self.cinder, 'extend', + self.assertRaisesRegex( + exceptions.CommandFailed, ex_text, self.cinder, 'extend', params='{0} {1}'.format(self.volume['id'], value)) @ddt.data( @@ -52,7 +49,6 @@ def test_volume_extend_with_incorrect_size(self, value, ex_text): ) @ddt.unpack def test_volume_extend_with_incorrect_volume_id(self, value, ex_text): - - six.assertRaisesRegex( - self, exceptions.CommandFailed, ex_text, self.cinder, 'extend', + self.assertRaisesRegex( + exceptions.CommandFailed, ex_text, self.cinder, 'extend', params='{0} 2'.format(value)) diff --git a/cinderclient/tests/unit/test_api_versions.py b/cinderclient/tests/unit/test_api_versions.py index 0f8690006..d8aad761a 100644 --- a/cinderclient/tests/unit/test_api_versions.py +++ b/cinderclient/tests/unit/test_api_versions.py @@ -16,7 +16,6 @@ from unittest import mock import ddt -import six from cinderclient import api_versions from cinderclient import client as base_client @@ -273,4 +272,4 @@ def test_get_highest_version_bad_client(self): v2_client = base_client.Client('2.0') ex = self.assertRaises(exceptions.UnsupportedVersion, api_versions.get_highest_version, v2_client) - self.assertIn('Invalid client version 2.0 to get', six.text_type(ex)) + self.assertIn('Invalid client version 2.0 to get', str(ex)) diff --git a/cinderclient/tests/unit/test_base.py b/cinderclient/tests/unit/test_base.py index 1b545497d..6ec2ca6f4 100644 --- a/cinderclient/tests/unit/test_base.py +++ b/cinderclient/tests/unit/test_base.py @@ -15,7 +15,6 @@ from unittest import mock import requests -import six from cinderclient import api_versions from cinderclient.apiclient import base as common_base @@ -111,20 +110,6 @@ def test_api_version(self): r1 = base.Resource(manager, {'id': 1}) self.assertEqual(version, r1.api_version) - @mock.patch('cinderclient.utils.unicode_key_value_to_string', - side_effect=lambda x: x) - def test_build_list_url_failed(self, fake_encode): - # NOTE(mdovgal): This test is reasonable only for py27 version, - # due to issue with parse.urlencode method only in py27 - if six.PY2: - arguments = dict(resource_type = 'volumes', - search_opts = {'all_tenants': 1, - 'name': u'ффф'}) - manager = base.Manager(None) - self.assertRaises(UnicodeEncodeError, - manager._build_list_url, - **arguments) - def test__list_no_link(self): api = mock.Mock() api.client.get.return_value = (mock.sentinel.resp, diff --git a/cinderclient/tests/unit/test_client.py b/cinderclient/tests/unit/test_client.py index 6770f2439..d92e1f20d 100644 --- a/cinderclient/tests/unit/test_client.py +++ b/cinderclient/tests/unit/test_client.py @@ -20,7 +20,6 @@ from keystoneauth1 import adapter from keystoneauth1 import exceptions as keystone_exception from oslo_serialization import jsonutils -import six from cinderclient import api_versions import cinderclient.client @@ -136,7 +135,7 @@ def test_sessionclient_request_method( request_id = "req-f551871a-4950-4225-9b2c-29a14c8f075e" mock_response = utils.TestResponse({ "status_code": 202, - "text": six.b(json.dumps(resp)), + "text": json.dumps(resp).encode("latin-1"), "headers": {"x-openstack-request-id": request_id}, }) @@ -176,7 +175,7 @@ def test_sessionclient_request_method_raises_badrequest( mock_response = utils.TestResponse({ "status_code": 400, - "text": six.b(json.dumps(resp)), + "text": json.dumps(resp).encode("latin-1"), }) # 'request' method of Adaptor will return 400 response @@ -202,7 +201,7 @@ def test_sessionclient_request_method_raises_overlimit( mock_response = utils.TestResponse({ "status_code": 413, - "text": six.b(json.dumps(resp)), + "text": json.dumps(resp).encode("latin-1"), }) # 'request' method of Adaptor will return 413 response diff --git a/cinderclient/tests/unit/test_shell.py b/cinderclient/tests/unit/test_shell.py index c8a6375d8..8c5df116b 100644 --- a/cinderclient/tests/unit/test_shell.py +++ b/cinderclient/tests/unit/test_shell.py @@ -12,6 +12,7 @@ # limitations under the License. import argparse +import io import re import sys import unittest @@ -24,7 +25,6 @@ from keystoneauth1.identity.generic.password import Password as ks_password from keystoneauth1 import session import requests_mock -from six import moves from testtools import matchers import cinderclient @@ -64,7 +64,7 @@ def setUp(self): def shell(self, argstr): orig = sys.stdout try: - sys.stdout = moves.StringIO() + sys.stdout = io.StringIO() _shell = shell.OpenStackCinderShell() _shell.main(argstr.split()) except SystemExit: diff --git a/cinderclient/tests/unit/test_utils.py b/cinderclient/tests/unit/test_utils.py index 1243b0a62..a9636db83 100644 --- a/cinderclient/tests/unit/test_utils.py +++ b/cinderclient/tests/unit/test_utils.py @@ -12,12 +12,11 @@ # limitations under the License. import collections +import io import sys from unittest import mock import ddt -import six -from six import moves from cinderclient import api_versions from cinderclient.apiclient import base as common_base @@ -151,7 +150,7 @@ class CaptureStdout(object): """Context manager for capturing stdout from statements in its block.""" def __enter__(self): self.real_stdout = sys.stdout - self.stringio = moves.StringIO() + self.stringio = io.StringIO() sys.stdout = self.stringio return self @@ -309,16 +308,6 @@ def test_print_list_with_return(self): +---+-----+ """, cso.read()) - 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)) - class PrintDictTestCase(test_utils.TestCase): diff --git a/cinderclient/tests/unit/utils.py b/cinderclient/tests/unit/utils.py index 8f4e9acf7..680062e3d 100644 --- a/cinderclient/tests/unit/utils.py +++ b/cinderclient/tests/unit/utils.py @@ -18,7 +18,6 @@ import fixtures import requests from requests_mock.contrib import fixture as requests_mock_fixture -import six import testtools @@ -92,9 +91,9 @@ def assert_called(self, method, path, body=None): if body: req_data = self.requests.last_request.body - if isinstance(req_data, six.binary_type): + if isinstance(req_data, bytes): req_data = req_data.decode('utf-8') - if not isinstance(body, six.string_types): + if not isinstance(body, str): # json load if the input body to match against is not a string req_data = json.loads(req_data) self.assertEqual(body, req_data) diff --git a/cinderclient/tests/unit/v2/fakes.py b/cinderclient/tests/unit/v2/fakes.py index 0dd269e1d..99a87d018 100644 --- a/cinderclient/tests/unit/v2/fakes.py +++ b/cinderclient/tests/unit/v2/fakes.py @@ -14,7 +14,7 @@ from datetime import datetime -import six.moves.urllib.parse as urlparse +from urllib import parse as urlparse from cinderclient import client as base_client from cinderclient.tests.unit import fakes diff --git a/cinderclient/tests/unit/v2/test_availability_zone.py b/cinderclient/tests/unit/v2/test_availability_zone.py index 8c2e1ebd8..e9b5d020e 100644 --- a/cinderclient/tests/unit/v2/test_availability_zone.py +++ b/cinderclient/tests/unit/v2/test_availability_zone.py @@ -14,8 +14,6 @@ # License for the specific language governing permissions and limitations # under the License. -import six - from cinderclient.v2 import availability_zones from cinderclient.v2 import shell @@ -44,8 +42,8 @@ def test_list_availability_zone(self): self.assertEqual(2, len(zones)) - l0 = [six.u('zone-1'), six.u('available')] - l1 = [six.u('zone-2'), six.u('not available')] + l0 = ['zone-1', 'available'] + l1 = ['zone-2', 'not available'] z0 = shell.treeizeAvailabilityZone(zones[0]) z1 = shell.treeizeAvailabilityZone(zones[1]) @@ -66,15 +64,15 @@ def test_detail_availability_zone(self): self.assertEqual(3, len(zones)) - l0 = [six.u('zone-1'), six.u('available')] - l1 = [six.u('|- fake_host-1'), six.u('')] - l2 = [six.u('| |- cinder-volume'), - six.u('enabled :-) 2012-12-26 14:45:25')] - l3 = [six.u('internal'), six.u('available')] - l4 = [six.u('|- fake_host-1'), six.u('')] - l5 = [six.u('| |- cinder-sched'), - six.u('enabled :-) 2012-12-26 14:45:24')] - l6 = [six.u('zone-2'), six.u('not available')] + l0 = ['zone-1', 'available'] + l1 = ['|- fake_host-1', ''] + l2 = ['| |- cinder-volume', + 'enabled :-) 2012-12-26 14:45:25'] + l3 = ['internal', 'available'] + l4 = ['|- fake_host-1', ''] + l5 = ['| |- cinder-sched', + 'enabled :-) 2012-12-26 14:45:24'] + l6 = ['zone-2', 'not available'] z0 = shell.treeizeAvailabilityZone(zones[0]) z1 = shell.treeizeAvailabilityZone(zones[1]) diff --git a/cinderclient/tests/unit/v2/test_shell.py b/cinderclient/tests/unit/v2/test_shell.py index f6f6355db..f54846e95 100644 --- a/cinderclient/tests/unit/v2/test_shell.py +++ b/cinderclient/tests/unit/v2/test_shell.py @@ -18,7 +18,7 @@ import ddt import fixtures from requests_mock.contrib import fixture as requests_mock_fixture -from six.moves.urllib import parse +from urllib import parse from cinderclient import client from cinderclient import exceptions diff --git a/cinderclient/tests/unit/v3/test_messages.py b/cinderclient/tests/unit/v3/test_messages.py index 262a7777b..9f22996ce 100644 --- a/cinderclient/tests/unit/v3/test_messages.py +++ b/cinderclient/tests/unit/v3/test_messages.py @@ -9,9 +9,9 @@ # 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 urllib import parse import ddt -from six.moves.urllib import parse from cinderclient import api_versions from cinderclient.tests.unit import utils diff --git a/cinderclient/tests/unit/v3/test_shell.py b/cinderclient/tests/unit/v3/test_shell.py index 0332ae3d1..054f332c5 100644 --- a/cinderclient/tests/unit/v3/test_shell.py +++ b/cinderclient/tests/unit/v3/test_shell.py @@ -39,12 +39,11 @@ # return original(manager, name_or_id, **kwargs) from unittest import mock +from urllib import parse import ddt import fixtures from requests_mock.contrib import fixture as requests_mock_fixture -import six -from six.moves.urllib import parse import cinderclient from cinderclient import api_versions @@ -322,7 +321,7 @@ def test_type_list_with_filters(self): self.assert_call_contained( parse.urlencode( {'extra_specs': - {six.text_type('key'): six.text_type('value')}})) + {'key': 'value'}})) self.assert_call_contained(parse.urlencode({'is_public': None})) def test_type_list_public(self): @@ -1150,11 +1149,7 @@ def test_service_set_log_missing_required(self, error_mock, self.run_command, '--os-volume-api-version 3.32 ' 'service-set-log') set_levels_mock.assert_not_called() - # Different error message from argparse library in Python 2 and 3 - if six.PY3: - msg = 'the following arguments are required: ' - else: - msg = 'too few arguments' + msg = 'the following arguments are required: ' error_mock.assert_called_once_with(msg) @ddt.data('debug', 'DEBUG', 'info', 'INFO', 'warning', 'WARNING', 'error', diff --git a/cinderclient/tests/unit/v3/test_volumes.py b/cinderclient/tests/unit/v3/test_volumes.py index 79206f62f..e5b3f2183 100644 --- a/cinderclient/tests/unit/v3/test_volumes.py +++ b/cinderclient/tests/unit/v3/test_volumes.py @@ -14,6 +14,7 @@ # 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 urllib import parse import ddt @@ -24,8 +25,6 @@ from cinderclient.v3 import volume_snapshots from cinderclient.v3 import volumes -from six.moves.urllib import parse - @ddt.ddt class VolumesTest(utils.TestCase): diff --git a/cinderclient/utils.py b/cinderclient/utils.py index 3b0096736..8919fc616 100644 --- a/cinderclient/utils.py +++ b/cinderclient/utils.py @@ -12,15 +12,14 @@ # 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 collections +import collections import os import sys +from urllib import parse import uuid import prettytable -import six -from six.moves.urllib import parse import stevedore from cinderclient import exceptions @@ -156,7 +155,7 @@ def print_list(objs, fields, exclude_unavailable=False, formatters=None, data = getattr(o, field_name, '') if data is None: data = '-' - if isinstance(data, six.string_types) and "\r" in data: + if isinstance(data, str) and "\r" in data: data = data.replace("\r", " ") row.append(data) rows.append(row) @@ -172,7 +171,6 @@ def print_list(objs, fields, exclude_unavailable=False, formatters=None, for part in row: count = count + 1 if isinstance(part, dict): - part = unicode_key_value_to_string(part) row[count - 1] = part pt.add_row(row) @@ -183,24 +181,6 @@ def print_list(objs, fields, exclude_unavailable=False, formatters=None, _print(pt, order_by) -def _encode(src): - """remove extra 'u' in PY2.""" - if six.PY2 and isinstance(src, six.text_type): - return src.encode('utf-8') - 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(item) for item in src] - return _encode(src) - - def build_query_param(params, sort=False): """parse list to url query parameters""" @@ -249,10 +229,9 @@ def print_dict(d, property="Property", formatters=None): r = list(r) if r[0] in formatters: - r[1] = unicode_key_value_to_string(r[1]) if isinstance(r[1], dict): r[1] = _pretty_format_dict(r[1]) - if isinstance(r[1], six.string_types) and "\r" in r[1]: + if isinstance(r[1], str) and "\r" in r[1]: r[1] = r[1].replace("\r", " ") pt.add_row(r) _print(pt, property) @@ -343,10 +322,4 @@ def _load_entry_point(ep_name, name=None): def get_function_name(func): - if six.PY2: - if hasattr(func, "im_class"): - return "%s.%s" % (func.im_class, func.__name__) - else: - return "%s.%s" % (func.__module__, func.__name__) - else: - return "%s.%s" % (func.__module__, func.__qualname__) + return "%s.%s" % (func.__module__, func.__qualname__) diff --git a/cinderclient/v2/shell.py b/cinderclient/v2/shell.py index d41e014fb..e3f868200 100644 --- a/cinderclient/v2/shell.py +++ b/cinderclient/v2/shell.py @@ -20,7 +20,6 @@ import os from oslo_utils import strutils -import six from cinderclient import base from cinderclient import exceptions @@ -295,7 +294,7 @@ def do_create(cs, args): # NOTE(vish): multiple copies of same hint will # result in a list of values if key in hints: - if isinstance(hints[key], six.string_types): + if isinstance(hints[key], str): hints[key] = [hints[key]] hints[key] += [value] else: @@ -1113,8 +1112,7 @@ def do_migrate(cs, args): args.lock_volume) print("Request to migrate volume %s has been accepted." % (volume.id)) except Exception as e: - print("Migration for volume %s failed: %s." % (volume.id, - six.text_type(e))) + print("Migration for volume %s failed: %s." % (volume.id, e)) @utils.arg('volume', metavar='', diff --git a/cinderclient/v3/group_types.py b/cinderclient/v3/group_types.py index 74ea9b740..696f02be1 100644 --- a/cinderclient/v3/group_types.py +++ b/cinderclient/v3/group_types.py @@ -16,7 +16,7 @@ """Group Type interface.""" -from six.moves.urllib import parse +from urllib import parse from cinderclient import api_versions from cinderclient import base diff --git a/cinderclient/v3/groups.py b/cinderclient/v3/groups.py index e42f7509f..310006c94 100644 --- a/cinderclient/v3/groups.py +++ b/cinderclient/v3/groups.py @@ -137,7 +137,7 @@ def get(self, group_id, **kwargs): :param group_id: The ID of the group to get. :rtype: :class:`Group` """ - query_params = utils.unicode_key_value_to_string(kwargs) + query_params = kwargs query_string = utils.build_query_param(query_params, sort=True) return self._get("/groups/%s" % group_id + query_string, diff --git a/cinderclient/v3/shell.py b/cinderclient/v3/shell.py index eaded7eab..fd78c57dd 100644 --- a/cinderclient/v3/shell.py +++ b/cinderclient/v3/shell.py @@ -19,7 +19,6 @@ import os from oslo_utils import strutils -import six import cinderclient from cinderclient import api_versions @@ -60,7 +59,7 @@ def do_list_filters(cs, args): @utils.arg('--filters', action=AppendFilters, - type=six.text_type, + type=str, nargs='*', start_version='3.52', metavar='', @@ -142,7 +141,7 @@ def do_type_list(cs, args): 'Default=None.') % ', '.join(base.SORT_KEY_VALUES))) @utils.arg('--filters', action=AppendFilters, - type=six.text_type, + type=str, nargs='*', start_version='3.33', metavar='', @@ -223,7 +222,7 @@ def do_backup_list(cs, args): help='Show detailed information about pools.') @utils.arg('--filters', action=AppendFilters, - type=six.text_type, + type=str, nargs='*', start_version='3.33', metavar='', @@ -351,7 +350,7 @@ def do_get_pools(cs, args): help='Display information from single tenant (Admin only).') @utils.arg('--filters', action=AppendFilters, - type=six.text_type, + type=str, nargs='*', start_version='3.33', metavar='', @@ -629,7 +628,7 @@ def do_create(cs, args): # NOTE(vish): multiple copies of same hint will # result in a list of values if key in hints: - if isinstance(hints[key], six.string_types): + if isinstance(hints[key], str): hints[key] = [hints[key]] hints[key] += [value] else: @@ -748,7 +747,7 @@ def do_summary(cs, args): @api_versions.wraps('3.11') @utils.arg('--filters', action=AppendFilters, - type=six.text_type, + type=str, nargs='*', start_version='3.52', metavar='', @@ -1031,7 +1030,7 @@ def do_migrate(cs, args): print("Request to migrate volume %s has been accepted." % (volume.id)) except Exception as e: print("Migration for volume %s failed: %s." % (volume.id, - six.text_type(e))) + str(e))) @api_versions.wraps('3.9') @@ -1338,7 +1337,7 @@ def do_manageable_list(cs, args): help='Shows details for all tenants. Admin only.') @utils.arg('--filters', action=AppendFilters, - type=six.text_type, + type=str, nargs='*', start_version='3.33', metavar='', @@ -1650,7 +1649,7 @@ def do_group_list_replication_targets(cs, args): "%s" % FILTER_DEPRECATED) @utils.arg('--filters', action=AppendFilters, - type=six.text_type, + type=str, nargs='*', start_version='3.33', metavar='', @@ -1902,7 +1901,7 @@ def do_revert_to_snapshot(cs, args): "%s" % FILTER_DEPRECATED) @utils.arg('--filters', action=AppendFilters, - type=six.text_type, + type=str, nargs='*', start_version='3.33', metavar='', @@ -2042,7 +2041,7 @@ def do_message_delete(cs, args): "%s" % FILTER_DEPRECATED) @utils.arg('--filters', action=AppendFilters, - type=six.text_type, + type=str, nargs='*', start_version='3.33', metavar='', @@ -2171,7 +2170,7 @@ def do_snapshot_list(cs, args): help='Display information from single tenant (Admin only).') @utils.arg('--filters', action=AppendFilters, - type=six.text_type, + type=str, nargs='*', start_version='3.33', metavar='', @@ -2571,7 +2570,7 @@ def do_transfer_create(cs, args): start_version='3.59') @utils.arg('--filters', action=AppendFilters, - type=six.text_type, + type=str, nargs='*', start_version='3.52', metavar='', diff --git a/cinderclient/v3/volume_types.py b/cinderclient/v3/volume_types.py index c55a4a4a3..e82fbb3ac 100644 --- a/cinderclient/v3/volume_types.py +++ b/cinderclient/v3/volume_types.py @@ -15,7 +15,7 @@ """Volume Type interface.""" -from six.moves.urllib import parse +from urllib import parse from cinderclient.apiclient import base as common_base from cinderclient import base diff --git a/lower-constraints.txt b/lower-constraints.txt index d0c0ef7e2..947303a6d 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -52,7 +52,6 @@ requests-mock==1.2.0 requests==2.23.0 rfc3986==1.4.0 simplejson==3.5.1 -six==1.15.0 stestr==3.0.1 stevedore==3.2.2 tempest==26.0.0 diff --git a/requirements.txt b/requirements.txt index 051474451..16cf67dc4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,6 @@ pbr>=5.5.0 # Apache-2.0 PrettyTable<0.8,>=0.7.2 # BSD keystoneauth1>=4.2.1 # Apache-2.0 simplejson>=3.5.1 # MIT -six>=1.15.0 # MIT oslo.i18n>=5.0.1 # Apache-2.0 oslo.utils>=4.7.0 # Apache-2.0 requests>=2.23.0 # Apache-2.0 diff --git a/tools/colorizer.py b/tools/colorizer.py index 7824b8c78..2a667b41f 100755 --- a/tools/colorizer.py +++ b/tools/colorizer.py @@ -46,7 +46,6 @@ import sys import unittest -import six import testtools @@ -277,7 +276,7 @@ def done(self): self.stopTestRun() def stopTestRun(self): - for cls in list(six.iterkeys(self.results)): + for cls in list(self.results.keys()): self.writeTestCase(cls) self.stream.writeln() self.writeSlowTests() diff --git a/tools/install_venv.py b/tools/install_venv.py index 03fe5afa6..4ff48a228 100644 --- a/tools/install_venv.py +++ b/tools/install_venv.py @@ -18,11 +18,10 @@ # License for the specific language governing permissions and limitations # under the License. +import configparser import os import sys -from six.moves import configparser - import install_venv_common as install_venv diff --git a/tools/lintstack.py b/tools/lintstack.py index 1ae34d73d..1f7923a20 100755 --- a/tools/lintstack.py +++ b/tools/lintstack.py @@ -16,13 +16,13 @@ """pylint error checking.""" +from io import StringIO import json import re import sys from pylint import lint from pylint.reporters import text -from six.moves import cStringIO as StringIO ignore_codes = [ # Note(maoy): E1103 is error code related to partial type inference From 23e7f0cb3434f32f56ac82678b6814ba95fa0855 Mon Sep 17 00:00:00 2001 From: Eric Harney Date: Mon, 8 Mar 2021 09:02:30 -0500 Subject: [PATCH 061/159] Remove more python2 compat code Remove code still adjusting for Python 2 behavior based on version checks. Change-Id: I29576a824278611d80991dce2501f14f1b262c76 --- cinderclient/shell.py | 7 +------ cinderclient/utils.py | 10 +--------- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/cinderclient/shell.py b/cinderclient/shell.py index 9e9cfb1f1..f0825d5f2 100644 --- a/cinderclient/shell.py +++ b/cinderclient/shell.py @@ -29,7 +29,6 @@ from keystoneauth1.identity import v3 as v3_auth from keystoneauth1 import loading from keystoneauth1 import session -from oslo_utils import encodeutils from oslo_utils import importutils import requests from urllib import parse as urlparse @@ -1015,11 +1014,7 @@ def start_section(self, heading): def main(): try: - if sys.version_info >= (3, 0): - OpenStackCinderShell().main(sys.argv[1:]) - else: - OpenStackCinderShell().main([encodeutils.safe_decode(item) - for item in sys.argv[1:]]) + OpenStackCinderShell().main(sys.argv[1:]) except KeyboardInterrupt: print("... terminating cinder client", file=sys.stderr) sys.exit(130) diff --git a/cinderclient/utils.py b/cinderclient/utils.py index 8919fc616..99acc03ef 100644 --- a/cinderclient/utils.py +++ b/cinderclient/utils.py @@ -15,7 +15,6 @@ import collections import os -import sys from urllib import parse import uuid @@ -23,7 +22,6 @@ import stevedore from cinderclient import exceptions -from oslo_utils import encodeutils def arg(*args, **kwargs): @@ -109,10 +107,7 @@ def isunauthenticated(f): def _print(pt, order): - if sys.version_info >= (3, 0): - print(pt.get_string(sortby=order)) - else: - print(encodeutils.safe_encode(pt.get_string(sortby=order))) + print(pt.get_string(sortby=order)) def print_list(objs, fields, exclude_unavailable=False, formatters=None, @@ -258,9 +253,6 @@ def find_resource(manager, name_or_id, **kwargs): except (ValueError, exceptions.NotFound): pass - if sys.version_info <= (3, 0): - name_or_id = encodeutils.safe_decode(name_or_id) - try: try: resource = getattr(manager, 'resource_class', None) From 9c2e8df94839412b4ccf13cc538c61af7c16ca2f Mon Sep 17 00:00:00 2001 From: sri harsha mekala Date: Wed, 17 Feb 2021 21:03:53 -0800 Subject: [PATCH 062/159] Support passing client certificates for server version requests Using the cinderclient to fetch server versions will fail with error `OpenSSL.SSL.Error: [sslv3 alert handshake failure]` when the server requires client certificates to be passed with these requests. Added the optional parameter `cert` to both get_server_version get_highest_client_server_version and methods so that users can have the option to pass client certificates while fetching server versions. Also support passing mTLS certificate/key to HTTPClient Closes-Bug: #1915996 Change-Id: I57c665dd9d4b8c32e5f10994d891d1e0f5315548 Signed-off-by: sri harsha mekala --- cinderclient/client.py | 21 +++++++++----- cinderclient/tests/unit/test_client.py | 28 +++++++++++++++++-- cinderclient/tests/unit/utils.py | 1 + cinderclient/v2/client.py | 7 +++-- cinderclient/v3/client.py | 7 +++-- .../notes/bug-1915996-3aaa5e2548eb7c93.yaml | 7 +++++ 6 files changed, 56 insertions(+), 15 deletions(-) create mode 100644 releasenotes/notes/bug-1915996-3aaa5e2548eb7c93.yaml diff --git a/cinderclient/client.py b/cinderclient/client.py index 2d7b80d14..c47334341 100644 --- a/cinderclient/client.py +++ b/cinderclient/client.py @@ -70,7 +70,7 @@ discover.add_catalog_discover_hack(svc, re.compile(r'/v[12]/\w+/?$'), '/') -def get_server_version(url, insecure=False, cacert=None): +def get_server_version(url, insecure=False, cacert=None, cert=None): """Queries the server via the naked endpoint and gets version info. :param url: url of the cinder endpoint @@ -78,6 +78,10 @@ def get_server_version(url, insecure=False, cacert=None): (https) requests :param cacert: Specify a CA bundle file to use in verifying a TLS (https) server certificate + :param cert: A client certificate to pass to requests. These are of the + same form as requests expects. Either a single filename + containing both the certificate and key or a tuple containing + the path to the certificate then a path to the key. (optional) :returns: APIVersion object for min and max version supported by the server """ @@ -115,7 +119,7 @@ def get_server_version(url, insecure=False, cacert=None): verify_cert = cacert else: verify_cert = True - response = requests.get(version_url, verify=verify_cert) + response = requests.get(version_url, verify=verify_cert, cert=cert) data = json.loads(response.text) versions = data['versions'] for version in versions: @@ -135,9 +139,10 @@ def get_server_version(url, insecure=False, cacert=None): api_versions.APIVersion(current_version)) -def get_highest_client_server_version(url, insecure=False, cacert=None): +def get_highest_client_server_version(url, insecure=False, + cacert=None, cert=None): """Returns highest supported version by client and server as a string.""" - min_server, max_server = get_server_version(url, insecure, cacert) + min_server, max_server = get_server_version(url, insecure, cacert, cert) max_client = api_versions.APIVersion(api_versions.MAX_VERSION) return min(max_server, max_client).get_string() @@ -286,7 +291,7 @@ def __init__(self, user, password, projectid, auth_url=None, endpoint_type='publicURL', service_type=None, service_name=None, volume_service_name=None, os_endpoint=None, retries=None, - http_log_debug=False, cacert=None, + http_log_debug=False, cacert=None, cert=None, auth_system='keystone', auth_plugin=None, api_version=None, logger=None, user_domain_name='Default', project_domain_name='Default', global_request_id=None): @@ -324,7 +329,7 @@ def __init__(self, user, password, projectid, auth_url=None, self.timeout = timeout self.user_domain_name = user_domain_name self.project_domain_name = project_domain_name - + self.cert = cert if insecure: self.verify_cert = False else: @@ -405,6 +410,7 @@ def request(self, url, method, **kwargs): method, url, verify=self.verify_cert, + cert=self.cert, **kwargs) self.http_log_resp(resp) @@ -701,7 +707,7 @@ def _construct_http_client(username=None, password=None, project_id=None, os_endpoint=None, retries=None, http_log_debug=False, auth_system='keystone', auth_plugin=None, - cacert=None, tenant_id=None, + cacert=None, cert=None, tenant_id=None, session=None, auth=None, api_version=None, **kwargs): @@ -741,6 +747,7 @@ def _construct_http_client(username=None, password=None, project_id=None, retries=retries, http_log_debug=http_log_debug, cacert=cacert, + cert=cert, auth_system=auth_system, auth_plugin=auth_plugin, logger=logger, diff --git a/cinderclient/tests/unit/test_client.py b/cinderclient/tests/unit/test_client.py index d92e1f20d..fa19492e7 100644 --- a/cinderclient/tests/unit/test_client.py +++ b/cinderclient/tests/unit/test_client.py @@ -365,7 +365,9 @@ def test_get_server_version_insecure(self, mock_request): cinderclient.client.get_server_version(url, True) - mock_request.assert_called_once_with(expected_url, verify=False) + mock_request.assert_called_once_with(expected_url, + verify=False, + cert=None) @mock.patch('cinderclient.client.requests.get') def test_get_server_version_cacert(self, mock_request): @@ -383,7 +385,29 @@ def test_get_server_version_cacert(self, mock_request): cacert = '/path/to/cert' cinderclient.client.get_server_version(url, cacert=cacert) - mock_request.assert_called_once_with(expected_url, verify=cacert) + mock_request.assert_called_once_with(expected_url, + verify=cacert, + cert=None) + + @mock.patch('cinderclient.client.requests.get') + def test_get_server_version_cert(self, mock_request): + mock_response = utils.TestResponse({ + "status_code": 200, + "text": json.dumps(fakes.fake_request_get_no_v3()) + }) + + mock_request.return_value = mock_response + + url = ( + "https://192.168.122.127:8776/v3/e5526285ebd741b1819393f772f11fc3") + expected_url = "https://192.168.122.127:8776/" + + client_cert = '/path/to/cert' + cinderclient.client.get_server_version(url, cert=client_cert) + + mock_request.assert_called_once_with(expected_url, + verify=True, + cert=client_cert) @mock.patch('cinderclient.client.requests.get') @ddt.data('3.12', '3.40') diff --git a/cinderclient/tests/unit/utils.py b/cinderclient/tests/unit/utils.py index 680062e3d..2bf242fad 100644 --- a/cinderclient/tests/unit/utils.py +++ b/cinderclient/tests/unit/utils.py @@ -27,6 +27,7 @@ class TestCase(testtools.TestCase): TEST_REQUEST_BASE = { 'verify': True, + 'cert': None } def setUp(self): diff --git a/cinderclient/v2/client.py b/cinderclient/v2/client.py index 0086a4d2a..a111c51a5 100644 --- a/cinderclient/v2/client.py +++ b/cinderclient/v2/client.py @@ -56,9 +56,9 @@ def __init__(self, username=None, api_key=None, project_id=None, endpoint_type='publicURL', extensions=None, service_type='volumev2', service_name=None, volume_service_name=None, os_endpoint=None, retries=0, - http_log_debug=False, cacert=None, auth_system='keystone', - auth_plugin=None, session=None, api_version=None, - logger=None, **kwargs): + http_log_debug=False, cacert=None, cert=None, + auth_system='keystone', auth_plugin=None, session=None, + api_version=None, logger=None, **kwargs): # FIXME(comstud): Rename the api_key argument above when we # know it's not being used as keyword argument password = api_key @@ -118,6 +118,7 @@ def __init__(self, username=None, api_key=None, project_id=None, retries=retries, http_log_debug=http_log_debug, cacert=cacert, + cert=cert, auth_system=auth_system, auth_plugin=auth_plugin, session=session, diff --git a/cinderclient/v3/client.py b/cinderclient/v3/client.py index 770d9d605..8ecaf0069 100644 --- a/cinderclient/v3/client.py +++ b/cinderclient/v3/client.py @@ -63,9 +63,9 @@ def __init__(self, username=None, api_key=None, project_id=None, endpoint_type='publicURL', extensions=None, service_type='volumev3', service_name=None, volume_service_name=None, os_endpoint=None, retries=0, - http_log_debug=False, cacert=None, auth_system='keystone', - auth_plugin=None, session=None, api_version=None, - logger=None, **kwargs): + http_log_debug=False, cacert=None, cert=None, + auth_system='keystone', auth_plugin=None, session=None, + api_version=None, logger=None, **kwargs): # FIXME(comstud): Rename the api_key argument above when we # know it's not being used as keyword argument password = api_key @@ -131,6 +131,7 @@ def __init__(self, username=None, api_key=None, project_id=None, retries=retries, http_log_debug=http_log_debug, cacert=cacert, + cert=cert, auth_system=auth_system, auth_plugin=auth_plugin, session=session, diff --git a/releasenotes/notes/bug-1915996-3aaa5e2548eb7c93.yaml b/releasenotes/notes/bug-1915996-3aaa5e2548eb7c93.yaml new file mode 100644 index 000000000..89f51d87b --- /dev/null +++ b/releasenotes/notes/bug-1915996-3aaa5e2548eb7c93.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + `Bug #1915996 `_: + Passing client certificates for mTLS connections was not supported + and now has been fixed. + From da7a49f679d6310692be81a61cb7d6f29f86f7c1 Mon Sep 17 00:00:00 2001 From: Alan Bishop Date: Mon, 18 Jan 2021 07:33:28 -0800 Subject: [PATCH 063/159] Bump API max version to 3.64 Bump MAX_VERSION to 3.64 to support including the encryption_key_id attribute in volume and backup details. Implements: blueprint include-encryption-key-id-in-details Depends-On: I16f54e6722cdbcbad4af1eb0d30264b0039412fd Change-Id: I6e1f3ff62d4b7b9b8299f7bd73071c3c7856f6df --- cinderclient/api_versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cinderclient/api_versions.py b/cinderclient/api_versions.py index 55dbfec6a..d7a470d6f 100644 --- a/cinderclient/api_versions.py +++ b/cinderclient/api_versions.py @@ -29,7 +29,7 @@ # key is a deprecated version and value is an alternative version. DEPRECATED_VERSIONS = {"2": "3"} DEPRECATED_VERSION = "2.0" -MAX_VERSION = "3.63" +MAX_VERSION = "3.64" MIN_VERSION = "3.0" _SUBSTITUTIONS = {} From 0439a44707788f9400e5aba716001f4e4bb55d81 Mon Sep 17 00:00:00 2001 From: Lee Yarwood Date: Fri, 18 Dec 2020 11:14:51 +0000 Subject: [PATCH 064/159] client: Stop logging request-id twice in DEBUG This is already logged by http_log_resp in DEBUG along with the other response headers so drop the duplicate logging in _log_request_id. Change-Id: I3c5ad2b480d80611ecb10449068d836c4bbe7bdb --- cinderclient/client.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/cinderclient/client.py b/cinderclient/client.py index 2d7b80d14..2fed6fbd9 100644 --- a/cinderclient/client.py +++ b/cinderclient/client.py @@ -155,16 +155,6 @@ def get_volume_api_from_url(url): raise exceptions.UnsupportedVersion(msg) -def _log_request_id(logger, resp, service_name): - request_id = resp.headers.get('x-openstack-request-id') - if request_id: - logger.debug('%(method)s call to %(service_type)s for %(url)s ' - 'used request id %(response_request_id)s', - {'method': resp.request.method, - 'service_type': service_name, - 'url': resp.url, 'response_request_id': request_id}) - - class SessionClient(adapter.LegacyJsonAdapter): def __init__(self, *args, **kwargs): @@ -378,10 +368,6 @@ def http_log_resp(self, resp): resp.headers, strutils.mask_password(resp.text)) - # if service name is None then use service_type for logging - service = self.service_name or self.service_type - _log_request_id(self._logger, resp, service) - def request(self, url, method, **kwargs): kwargs.setdefault('headers', kwargs.get('headers', {})) kwargs['headers']['User-Agent'] = self.USER_AGENT From 4ac8efc93ec8cc454ddbca05ee4520f787674c68 Mon Sep 17 00:00:00 2001 From: likui Date: Mon, 2 Nov 2020 15:55:58 +0800 Subject: [PATCH 065/159] Use TOX_CONSTRAINTS_FILE UPPER_CONSTRAINTS_FILE is old name and deprecated -https://zuul-ci.org/docs/zuul-jobs/python-roles.html#rolevar-tox.tox_constraints_file This allows to use lower-constraints file as more readable way instead of UPPER_CONSTRAINTS_FILE=. [1] https://review.opendev.org/#/c/722814/ Change-Id: I26f4c8fcaaaf0eca5e5d6fcb6fd443b56ea41f35 --- tox.ini | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tox.ini b/tox.ini index a12307095..71ba18b7e 100644 --- a/tox.ini +++ b/tox.ini @@ -20,7 +20,7 @@ setenv = passenv = *_proxy *_PROXY deps = - -c{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}/test-requirements.txt commands = find . -type f -name "*.pyc" -delete @@ -35,7 +35,7 @@ commands = [testenv:pylint] deps = - -c{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 pylint==2.6.0 commands = bash tools/lintstack.sh @@ -56,7 +56,7 @@ commands = [testenv:docs] deps = - -c{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 -W -b html doc/source doc/build/html @@ -73,7 +73,7 @@ allowlist_externals = [testenv:releasenotes] deps = - -c{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 82f0ceb724e93ccf22140762c65da88c9c2f4bb4 Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Tue, 2 Jun 2020 16:58:53 -0500 Subject: [PATCH 066/159] Add flake8-import-order extension This adds the import order extension to match what we have in the cinder repo. This is a linting extension that will check that imports are in the correct order and the correct grouping so they automatically get flagged, and it won't be whether reviewers notice and decide to do anything or not. Cinder change was Ic13ba238a4a45c6219f4de131cfe0366219d722f for a little more wordy reasoning. Also includes updates for noqa tags. Newer version of the linters appear to want these on the function definition line, not on the decorator line. Change-Id: Ibf3f3afbf3bb6ec6613b35f91d4a353c6a391f41 Signed-off-by: Sean McGinnis --- cinderclient/apiclient/base.py | 8 +++----- cinderclient/shell.py | 2 +- cinderclient/tests/unit/fake_actions_module.py | 4 ++-- cinderclient/tests/unit/test_utils.py | 4 ++-- .../tests/unit/v2/contrib/test_list_extensions.py | 4 +--- cinderclient/tests/unit/v2/fakes.py | 1 - cinderclient/tests/unit/v2/test_capabilities.py | 3 +-- cinderclient/tests/unit/v2/test_pools.py | 3 +-- cinderclient/tests/unit/v2/test_shell.py | 2 +- cinderclient/tests/unit/v2/test_type_access.py | 3 +-- cinderclient/tests/unit/v2/test_types.py | 3 +-- cinderclient/tests/unit/v3/fakes.py | 3 +-- cinderclient/tests/unit/v3/test_clusters.py | 4 ++-- cinderclient/tests/unit/v3/test_group_types.py | 3 +-- cinderclient/tests/unit/v3/test_services.py | 3 +-- cinderclient/tests/unit/v3/test_shell.py | 7 +++---- cinderclient/v3/messages.py | 5 +++-- cinderclient/v3/volume_backups.py | 12 ++++++------ cinderclient/v3/volumes.py | 14 +++++++------- test-requirements.txt | 2 ++ tools/colorizer.py | 2 +- tox.ini | 4 +++- 22 files changed, 44 insertions(+), 52 deletions(-) diff --git a/cinderclient/apiclient/base.py b/cinderclient/apiclient/base.py index c0bcd24a5..8caa0bc1b 100644 --- a/cinderclient/apiclient/base.py +++ b/cinderclient/apiclient/base.py @@ -16,9 +16,7 @@ # License for the specific language governing permissions and limitations # under the License. -""" -Base utilities to build API operation managers and objects on top of. -""" +"""Base utilities to build API operation managers and objects on top of.""" # E1102: %s is not callable # pylint: disable=E1102 @@ -26,13 +24,13 @@ import abc import copy +from oslo_utils import encodeutils +from oslo_utils import strutils from requests import Response from cinderclient.apiclient import exceptions from cinderclient import utils -from oslo_utils import encodeutils -from oslo_utils import strutils def getid(obj): diff --git a/cinderclient/shell.py b/cinderclient/shell.py index f0825d5f2..0c3fa4535 100644 --- a/cinderclient/shell.py +++ b/cinderclient/shell.py @@ -22,6 +22,7 @@ import getpass import logging import sys +from urllib import parse as urlparse from keystoneauth1 import discover from keystoneauth1 import exceptions @@ -31,7 +32,6 @@ from keystoneauth1 import session from oslo_utils import importutils import requests -from urllib import parse as urlparse import cinderclient from cinderclient._i18n import _ diff --git a/cinderclient/tests/unit/fake_actions_module.py b/cinderclient/tests/unit/fake_actions_module.py index 07e7d29b4..a2c4bf79c 100644 --- a/cinderclient/tests/unit/fake_actions_module.py +++ b/cinderclient/tests/unit/fake_actions_module.py @@ -26,8 +26,8 @@ def do_fake_action(): return "fake_action 3.0 to 3.1" -@api_versions.wraps("3.2", "3.3") # noqa: F811 -def do_fake_action(): # noqa +@api_versions.wraps("3.2", "3.3") +def do_fake_action(): # noqa: F811 return "fake_action 3.2 to 3.3" diff --git a/cinderclient/tests/unit/test_utils.py b/cinderclient/tests/unit/test_utils.py index a9636db83..1fb9433b4 100644 --- a/cinderclient/tests/unit/test_utils.py +++ b/cinderclient/tests/unit/test_utils.py @@ -70,8 +70,8 @@ class FakeManagerWithApi(base.Manager): def return_api_version(self): return '3.1' - @api_versions.wraps('3.2') # noqa: F811 - def return_api_version(self): # noqa + @api_versions.wraps('3.2') + def return_api_version(self): # noqa: F811 return '3.2' diff --git a/cinderclient/tests/unit/v2/contrib/test_list_extensions.py b/cinderclient/tests/unit/v2/contrib/test_list_extensions.py index 313b6ef58..4b6100f7a 100644 --- a/cinderclient/tests/unit/v2/contrib/test_list_extensions.py +++ b/cinderclient/tests/unit/v2/contrib/test_list_extensions.py @@ -15,11 +15,9 @@ # under the License. from cinderclient import extension -from cinderclient.v2.contrib import list_extensions - from cinderclient.tests.unit import utils from cinderclient.tests.unit.v2 import fakes - +from cinderclient.v2.contrib import list_extensions extensions = [ extension.Extension(list_extensions.__name__.split(".")[-1], diff --git a/cinderclient/tests/unit/v2/fakes.py b/cinderclient/tests/unit/v2/fakes.py index 99a87d018..4c09e9dcb 100644 --- a/cinderclient/tests/unit/v2/fakes.py +++ b/cinderclient/tests/unit/v2/fakes.py @@ -13,7 +13,6 @@ # limitations under the License. from datetime import datetime - from urllib import parse as urlparse from cinderclient import client as base_client diff --git a/cinderclient/tests/unit/v2/test_capabilities.py b/cinderclient/tests/unit/v2/test_capabilities.py index 01a132d95..98d8d71fc 100644 --- a/cinderclient/tests/unit/v2/test_capabilities.py +++ b/cinderclient/tests/unit/v2/test_capabilities.py @@ -13,10 +13,9 @@ # License for the specific language governing permissions and limitations # under the License. -from cinderclient.v2.capabilities import Capabilities - from cinderclient.tests.unit import utils from cinderclient.tests.unit.v2 import fakes +from cinderclient.v2.capabilities import Capabilities cs = fakes.FakeClient() diff --git a/cinderclient/tests/unit/v2/test_pools.py b/cinderclient/tests/unit/v2/test_pools.py index 543e31674..e909871ae 100644 --- a/cinderclient/tests/unit/v2/test_pools.py +++ b/cinderclient/tests/unit/v2/test_pools.py @@ -13,10 +13,9 @@ # License for the specific language governing permissions and limitations # under the License. -from cinderclient.v2.pools import Pool - from cinderclient.tests.unit import utils from cinderclient.tests.unit.v2 import fakes +from cinderclient.v2.pools import Pool cs = fakes.FakeClient() diff --git a/cinderclient/tests/unit/v2/test_shell.py b/cinderclient/tests/unit/v2/test_shell.py index f54846e95..78ecf7409 100644 --- a/cinderclient/tests/unit/v2/test_shell.py +++ b/cinderclient/tests/unit/v2/test_shell.py @@ -14,11 +14,11 @@ # under the License. from unittest import mock +from urllib import parse import ddt import fixtures from requests_mock.contrib import fixture as requests_mock_fixture -from urllib import parse from cinderclient import client from cinderclient import exceptions diff --git a/cinderclient/tests/unit/v2/test_type_access.py b/cinderclient/tests/unit/v2/test_type_access.py index 35a4480a7..d904c1d3e 100644 --- a/cinderclient/tests/unit/v2/test_type_access.py +++ b/cinderclient/tests/unit/v2/test_type_access.py @@ -14,10 +14,9 @@ # License for the specific language governing permissions and limitations # under the License. -from cinderclient.v2 import volume_type_access - from cinderclient.tests.unit import utils from cinderclient.tests.unit.v2 import fakes +from cinderclient.v2 import volume_type_access cs = fakes.FakeClient() diff --git a/cinderclient/tests/unit/v2/test_types.py b/cinderclient/tests/unit/v2/test_types.py index 9ba13a9c0..cf13723f1 100644 --- a/cinderclient/tests/unit/v2/test_types.py +++ b/cinderclient/tests/unit/v2/test_types.py @@ -14,10 +14,9 @@ # License for the specific language governing permissions and limitations # under the License. -from cinderclient.v2 import volume_types - from cinderclient.tests.unit import utils from cinderclient.tests.unit.v2 import fakes +from cinderclient.v2 import volume_types cs = fakes.FakeClient() diff --git a/cinderclient/tests/unit/v3/fakes.py b/cinderclient/tests/unit/v3/fakes.py index 3fb6a36b0..7647fb39f 100644 --- a/cinderclient/tests/unit/v3/fakes.py +++ b/cinderclient/tests/unit/v3/fakes.py @@ -14,10 +14,9 @@ from datetime import datetime -from cinderclient.v3 import client - from cinderclient.tests.unit import fakes from cinderclient.tests.unit.v2 import fakes as fake_v2 +from cinderclient.v3 import client fake_attachment = {'attachment': { diff --git a/cinderclient/tests/unit/v3/test_clusters.py b/cinderclient/tests/unit/v3/test_clusters.py index c2045b6e1..21b560d5f 100644 --- a/cinderclient/tests/unit/v3/test_clusters.py +++ b/cinderclient/tests/unit/v3/test_clusters.py @@ -13,12 +13,12 @@ # License for the specific language governing permissions and limitations # under the License. +import ddt + from cinderclient import api_versions from cinderclient import exceptions as exc from cinderclient.tests.unit import utils from cinderclient.tests.unit.v3 import fakes -import ddt - cs = fakes.FakeClient(api_version=api_versions.APIVersion('3.7')) diff --git a/cinderclient/tests/unit/v3/test_group_types.py b/cinderclient/tests/unit/v3/test_group_types.py index 5833c3fc2..2263d0e8a 100644 --- a/cinderclient/tests/unit/v3/test_group_types.py +++ b/cinderclient/tests/unit/v3/test_group_types.py @@ -16,10 +16,9 @@ from cinderclient import api_versions from cinderclient import exceptions as exc -from cinderclient.v3 import group_types - from cinderclient.tests.unit import utils from cinderclient.tests.unit.v3 import fakes +from cinderclient.v3 import group_types cs = fakes.FakeClient(api_version=api_versions.APIVersion('3.11')) pre_cs = fakes.FakeClient(api_version=api_versions.APIVersion('3.10')) diff --git a/cinderclient/tests/unit/v3/test_services.py b/cinderclient/tests/unit/v3/test_services.py index 0715cd378..8af368283 100644 --- a/cinderclient/tests/unit/v3/test_services.py +++ b/cinderclient/tests/unit/v3/test_services.py @@ -14,10 +14,9 @@ # under the License. from cinderclient import api_versions -from cinderclient.v3 import services - from cinderclient.tests.unit import utils from cinderclient.tests.unit.v3 import fakes +from cinderclient.v3 import services class ServicesTest(utils.TestCase): diff --git a/cinderclient/tests/unit/v3/test_shell.py b/cinderclient/tests/unit/v3/test_shell.py index 82e3943da..756f51201 100644 --- a/cinderclient/tests/unit/v3/test_shell.py +++ b/cinderclient/tests/unit/v3/test_shell.py @@ -51,15 +51,14 @@ from cinderclient import client from cinderclient import exceptions from cinderclient import shell +from cinderclient.tests.unit.fixture_data import keystone_client +from cinderclient.tests.unit import utils +from cinderclient.tests.unit.v3 import fakes from cinderclient import utils as cinderclient_utils from cinderclient.v3 import attachments from cinderclient.v3 import volume_snapshots from cinderclient.v3 import volumes -from cinderclient.tests.unit.fixture_data import keystone_client -from cinderclient.tests.unit import utils -from cinderclient.tests.unit.v3 import fakes - @ddt.ddt @mock.patch.object(client, 'Client', fakes.FakeClient) diff --git a/cinderclient/v3/messages.py b/cinderclient/v3/messages.py index 2c620c206..93aeefa71 100644 --- a/cinderclient/v3/messages.py +++ b/cinderclient/v3/messages.py @@ -51,8 +51,9 @@ def list(self, **kwargs): url = self._build_list_url(resource_type, detailed=False) return self._list(url, resource_type) - @api_versions.wraps('3.5') # noqa: F811 - def list(self, search_opts=None, marker=None, limit=None, sort=None): # noqa + @api_versions.wraps('3.5') + def list(self, search_opts=None, marker=None, limit=None, # noqa: F811 + sort=None): """Lists all messages. :param search_opts: Search options to filter out volumes. diff --git a/cinderclient/v3/volume_backups.py b/cinderclient/v3/volume_backups.py index 7dd85603b..22d25a338 100644 --- a/cinderclient/v3/volume_backups.py +++ b/cinderclient/v3/volume_backups.py @@ -60,8 +60,8 @@ def create(self, volume_id, container=None, return self._create_backup(volume_id, container, name, description, incremental, force, snapshot_id) - @api_versions.wraps("3.43") # noqa: F811 - def create(self, volume_id, container=None, # noqa + @api_versions.wraps("3.43") + def create(self, volume_id, container=None, # noqa: F811 name=None, description=None, incremental=False, force=False, snapshot_id=None, @@ -84,10 +84,10 @@ def create(self, volume_id, container=None, # noqa return self._create_backup(volume_id, container, name, description, incremental, force, snapshot_id, metadata) - @api_versions.wraps("3.51") # noqa: F811 - def create(self, volume_id, container=None, name=None, description=None, # noqa - incremental=False, force=False, snapshot_id=None, metadata=None, - availability_zone=None): + @api_versions.wraps("3.51") + def create(self, volume_id, container=None, name=None, # noqa: F811 + description=None, incremental=False, force=False, + snapshot_id=None, metadata=None, availability_zone=None): return self._create_backup(volume_id, container, name, description, incremental, force, snapshot_id, metadata, availability_zone) diff --git a/cinderclient/v3/volumes.py b/cinderclient/v3/volumes.py index 974bcfcfe..8ea388008 100644 --- a/cinderclient/v3/volumes.py +++ b/cinderclient/v3/volumes.py @@ -159,8 +159,8 @@ def delete_metadata(self, volume, keys): return common_base.ListWithMeta([], response_list) - @api_versions.wraps("3.15") # noqa: F811 - def delete_metadata(self, volume, keys): # noqa + @api_versions.wraps("3.15") + def delete_metadata(self, volume, keys): # noqa: F811 """Delete specified keys from volumes metadata. :param volume: The :class:`Volume`. @@ -190,9 +190,9 @@ def upload_to_image(self, volume, force, image_name, container_format, 'container_format': container_format, 'disk_format': disk_format}) - @api_versions.wraps("3.1") # noqa: F811 - def upload_to_image(self, volume, force, image_name, container_format, # noqa - disk_format, visibility, protected): + @api_versions.wraps("3.1") + def upload_to_image(self, volume, force, image_name, # noqa: F811 + container_format, disk_format, visibility, protected): """Upload volume to image service as image. :param volume: The :class:`Volume` to upload. """ @@ -264,8 +264,8 @@ def get_pools(self, detail): return self._get('/scheduler-stats/get_pools%s' % query_string, None) - @api_versions.wraps("3.33") # noqa: F811 - def get_pools(self, detail, search_opts): # noqa + @api_versions.wraps("3.33") + def get_pools(self, detail, search_opts): # noqa: F811 """Show pool information for backends.""" # pylint: disable=function-redefined options = {'detail': detail} diff --git a/test-requirements.txt b/test-requirements.txt index c66025919..9e9500238 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -4,6 +4,8 @@ # Hacking already pins down pep8, pyflakes and flake8 hacking>=4.0.0,<4.1.0 # Apache-2.0 +flake8-import-order # LGPLv3 + docutils>=0.15.2 coverage>=5.2.1 # Apache-2.0 ddt>=1.4.1 # MIT diff --git a/tools/colorizer.py b/tools/colorizer.py index 2a667b41f..cf89535b5 100755 --- a/tools/colorizer.py +++ b/tools/colorizer.py @@ -42,10 +42,10 @@ """Display a subunit stream through a colorized unittest test runner.""" import heapq -import subunit import sys import unittest +import subunit import testtools diff --git a/tox.ini b/tox.ini index a12307095..d66d486ee 100644 --- a/tox.ini +++ b/tox.ini @@ -109,7 +109,9 @@ commands = {[testenv:functional]commands} [flake8] show-source = True ignore = H404,H405,E122,E123,E128,E251,W504 -exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build +exclude = .venv,.git,.tox,dist,doc,*lib/python*,*egg,build +application-import-names = cinderclient +import-order-style = pep8 [doc8] ignore-path=.tox,*.egg-info,doc/src/api,doc/source/drivers.rst,doc/build,.eggs/*/EGG-INFO/*.txt,doc/source/configuration/tables,./*.txt,releasenotes/build,doc/source/cli/details.rst From d9213138b1df8ead680823157848b8f5056b9c63 Mon Sep 17 00:00:00 2001 From: tushargite96 Date: Tue, 16 Feb 2021 19:55:30 +0530 Subject: [PATCH 067/159] Dropping explicit unicode literal In python 3, all strings are considered as unicode string. This patch drops the explicit unicode literal (u'...') or (u"..") appearances from the unicode strings. Change-Id: I9902966892a1dc4f85d449dfe580fb128647487b --- cinderclient/tests/unit/v2/fakes.py | 14 +++++++------- doc/source/conf.py | 8 ++++---- releasenotes/source/conf.py | 16 ++++++++-------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/cinderclient/tests/unit/v2/fakes.py b/cinderclient/tests/unit/v2/fakes.py index 99a87d018..ddd20c5ee 100644 --- a/cinderclient/tests/unit/v2/fakes.py +++ b/cinderclient/tests/unit/v2/fakes.py @@ -28,9 +28,9 @@ def _stub_volume(*args, **kwargs): volume = { "migration_status": None, - "attachments": [{u'server_id': u'1234', - u'id': u'3f88836f-adde-4296-9f6b-2c59a0bcda9a', - u'attachment_id': u'5678'}], + "attachments": [{'server_id': '1234', + 'id': '3f88836f-adde-4296-9f6b-2c59a0bcda9a', + 'attachment_id': '5678'}], "links": [ { "href": "http://localhost/v2/fake/volumes/1234", @@ -741,7 +741,7 @@ def get_types_1(self, **kw): return (200, {}, {'volume_type': {'id': 1, 'name': 'test-type-1', 'description': 'test_type-1-desc', - 'extra_specs': {u'key': u'value'}}}) + 'extra_specs': {'key': 'value'}}}) def get_types_2(self, **kw): return (200, {}, {'volume_type': {'id': 2, @@ -1317,9 +1317,9 @@ def get_capabilities_host(self, **kw): 'storage_protocol': 'iSCSI', 'properties': { 'compression': { - u'title': u'Compression', - u'description': u'Enables compression.', - u'type': u'boolean'}, + 'title': 'Compression', + 'description': 'Enables compression.', + 'type': 'boolean'}, } } ) diff --git a/doc/source/conf.py b/doc/source/conf.py index 2868e84b9..0c5ce55f9 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -81,8 +81,8 @@ # -- Options for manual page output ------------------------------------------ man_pages = [ - ('cli/details', 'cinder', u'Client for OpenStack Block Storage API', - [u'OpenStack Contributors'], 1), + ('cli/details', 'cinder', 'Client for OpenStack Block Storage API', + ['OpenStack Contributors'], 1), ] # -- Options for openstackdocstheme ------------------------------------------- @@ -104,8 +104,8 @@ # (source start file, target name, title, author, documentclass # [howto/manual]). latex_documents = [ - ('index', 'doc-python-cinderclient.tex', u'Cinder Client Documentation', - u'Cinder Contributors', 'manual'), + ('index', 'doc-python-cinderclient.tex', 'Cinder Client Documentation', + 'Cinder Contributors', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py index 58ed2686b..a0a7d091c 100644 --- a/releasenotes/source/conf.py +++ b/releasenotes/source/conf.py @@ -55,9 +55,9 @@ master_doc = 'index' # General information about the project. -project = u'Cinder Client Release Notes' +project = 'Cinder Client Release Notes' openstackdocs_auto_name = False -copyright = u'2015, Cinder Developers' +copyright = '2015, Cinder Developers' # Release notes are version independent, no need to set version and release release = '' @@ -201,8 +201,8 @@ # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'CinderClientReleaseNotes.tex', - u'Cinder Client Release Notes Documentation', - u'Cinder Developers', 'manual'), + 'Cinder Client Release Notes Documentation', + 'Cinder Developers', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -232,8 +232,8 @@ # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'cinderclientreleasenotes', - u'Cinder Client Release Notes Documentation', - [u'Cinder Developers'], 1) + 'Cinder Client Release Notes Documentation', + ['Cinder Developers'], 1) ] # If true, show URL addresses after external links. @@ -247,8 +247,8 @@ # dir menu entry, description, category) texinfo_documents = [ ('index', 'CinderClientReleaseNotes', - u'Cinder Client Release Notes Documentation', - u'Cinder Developers', 'CinderClientReleaseNotes', + 'Cinder Client Release Notes Documentation', + 'Cinder Developers', 'CinderClientReleaseNotes', 'Block Storage Service client.', 'Miscellaneous'), ] From a933a69e8757c651f5ee84eccb2d119c9d174f8f Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Wed, 10 Mar 2021 17:21:46 -0500 Subject: [PATCH 068/159] Update requirements for wallaby release Updates requirements to what we're using now and revises the lower-constraints file to contain only direct dependencies. Change-Id: I1a3cc999b94dc6b6482b1ca44218b42ee2638a8c --- doc/requirements.txt | 2 +- lower-constraints.txt | 62 +++++++------------------------------------ requirements.txt | 8 +++--- test-requirements.txt | 16 +++-------- 4 files changed, 18 insertions(+), 70 deletions(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index dad08373a..2eec3c6c4 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -3,5 +3,5 @@ # process, which may cause wedges in the gate later. # These are needed for docs generation openstackdocstheme>=2.2.1 # Apache-2.0 -reno>=3.1.0 # Apache-2.0 +reno>=3.2.0 # Apache-2.0 sphinx>=2.0.0,!=2.1.0 # BSD diff --git a/lower-constraints.txt b/lower-constraints.txt index 947303a6d..5533a7380 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -1,63 +1,19 @@ -asn1crypto==0.23.0 -attrs==20.3.0 -certifi==2020.6.20 -cffi==1.14.2 -chardet==3.0.4 -cliff==3.4.0 -cmd2==1.3.8 -colorama==0.4.4 -coverage==5.2.1 -cryptography==3.1 +coverage==5.5 ddt==1.4.1 -debtcollector==2.2.0 doc8==0.8.1 -docutils==0.15.2 -dulwich==0.20.6 -extras==1.0.0 -fasteners==0.14.1 +docutils==0.16 fixtures==3.0.0 -future==0.18.2 -idna==2.10 -iso8601==0.1.12 -jsonschema==3.2.0 -keystoneauth1==4.2.1 -linecache2==1.0.0 -mccabe==0.6.0 -monotonic==0.6 -msgpack-python==0.4.0 -netaddr==0.8.0 -netifaces==0.10.9 -oslo.concurrency==4.3.0 -oslo.config==8.3.2 -oslo.context==3.1.1 +keystoneauth1==4.3.1 oslo.i18n==5.0.1 -oslo.log==4.4.0 -oslo.serialization==4.0.1 -oslo.utils==4.7.0 -paramiko==2.7.2 +oslo.serialization==4.1.0 +oslo.utils==4.8.0 pbr==5.5.0 -prettytable==0.7.2 -pyasn1==0.4.8 -pycparser==2.20 -pyinotify==0.9.6 -pyparsing==2.4.7 -pyperclip==1.8.0 -python-dateutil==2.8.1 -python-mimeparse==1.6.0 -python-subunit==1.4.0 -pytz==2020.1 -PyYAML==5.3.1 +PrettyTable==0.7.2 reno==3.2.0 +requests==2.25.1 requests-mock==1.2.0 -requests==2.23.0 -rfc3986==1.4.0 simplejson==3.5.1 -stestr==3.0.1 -stevedore==3.2.2 +stestr==3.1.0 +stevedore==3.3.0 tempest==26.0.0 -testrepository==0.0.18 testtools==2.4.0 -traceback2==1.4.0 -unittest2==1.1.0 -urllib3==1.25.10 -wrapt==1.12.1 diff --git a/requirements.txt b/requirements.txt index 1a5d13d23..6f8e90b3f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,9 +3,9 @@ # process, which may cause wedges in the gate later. pbr>=5.5.0 # Apache-2.0 PrettyTable>=0.7.2 # BSD -keystoneauth1>=4.2.1 # Apache-2.0 +keystoneauth1>=4.3.1 # Apache-2.0 simplejson>=3.5.1 # MIT oslo.i18n>=5.0.1 # Apache-2.0 -oslo.utils>=4.7.0 # Apache-2.0 -requests>=2.23.0 # Apache-2.0 -stevedore>=3.2.2 # Apache-2.0 +oslo.utils>=4.8.0 # Apache-2.0 +requests>=2.25.1 # Apache-2.0 +stevedore>=3.3.0 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index 9e9500238..e0d7c93c1 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -5,21 +5,13 @@ hacking>=4.0.0,<4.1.0 # Apache-2.0 flake8-import-order # LGPLv3 - -docutils>=0.15.2 -coverage>=5.2.1 # Apache-2.0 +docutils>=0.16 +coverage>=5.5 # Apache-2.0 ddt>=1.4.1 # MIT fixtures>=3.0.0 # Apache-2.0/BSD -reno>=3.2.0 # Apache-2.0 requests-mock>=1.2.0 # Apache-2.0 tempest>=26.0.0 # Apache-2.0 testtools>=2.4.0 # MIT -stestr>=3.0.1 # Apache-2.0 -oslo.serialization>=4.0.1 # Apache-2.0 +stestr>=3.1.0 # Apache-2.0 +oslo.serialization>=4.1.0 # Apache-2.0 doc8>=0.8.1 # Apache-2.0 -# -# These are here to enable the resolver to work faster. -# They are not directly used by python-cinderclient. -debtcollector>=2.2.0 -dulwich>=0.20.6 -mccabe>=0.6.0 From 900630d8b997d498684f2eff0d2c9c06024deb5e Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Thu, 11 Mar 2021 11:17:32 -0500 Subject: [PATCH 069/159] Add note for Wallaby release Change-Id: Ia958840739cab51236391e4d8d6d1569604d9a3e --- .../notes/wallaby-release-2535df50cc307fea.yaml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 releasenotes/notes/wallaby-release-2535df50cc307fea.yaml diff --git a/releasenotes/notes/wallaby-release-2535df50cc307fea.yaml b/releasenotes/notes/wallaby-release-2535df50cc307fea.yaml new file mode 100644 index 000000000..4943b873e --- /dev/null +++ b/releasenotes/notes/wallaby-release-2535df50cc307fea.yaml @@ -0,0 +1,16 @@ +--- +prelude: | + The Wallaby release of the python-cinderclient supports Block Storage + API version 2 and Block Storage API version 3 through microversion + 3.64. (The maximum microversion of the Block Storage API in the + Wallaby release is 3.64.) +features: + - | + Added support to display the ``volume_type_id`` attribute in volume + detail output when used with Block Storage API microversion 3.63 and + higher. + - | + Added support to display the ``encryption_key_id`` attribute in + volume detail and backup detail output when used with Block Storage + API microversion 3.64 and higher. + From a0e36218f20e95778f449edb9edccbead4a6eed9 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Mon, 15 Mar 2021 08:41:38 +0000 Subject: [PATCH 070/159] 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: I7a9c0cd51c45f05c75d3fa5be137aad7e62a016c --- 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 ca77d930b..8acb39c47 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ :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 202c1a321f32f45b5c8013ae05cf57ed5dc339f6 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Mon, 15 Mar 2021 08:41:41 +0000 Subject: [PATCH 071/159] 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: I966cfcc1f1aa528385a57f2012bf2540e3d3edc4 --- .zuul.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.zuul.yaml b/.zuul.yaml index 7e92f758b..52c35ae0d 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -34,7 +34,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 8d8d4575b9376d3f817121d8b409e646570b8073 Mon Sep 17 00:00:00 2001 From: YuehuiLei Date: Wed, 28 Apr 2021 11:12:56 +0800 Subject: [PATCH 072/159] 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: I77d52471fea255982ad677e9d4bc318f24a9ace7 --- setup.cfg | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/setup.cfg b/setup.cfg index dabff754e..b54ff289e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,12 +1,12 @@ [metadata] name = python-cinderclient summary = OpenStack Block Storage API Client Library -description-file = +description_file = README.rst author = OpenStack -author-email = openstack-discuss@lists.openstack.org -home-page = https://docs.openstack.org/python-cinderclient/latest/ -python-requires = >=3.6 +author_email = openstack-discuss@lists.openstack.org +home_page = https://docs.openstack.org/python-cinderclient/latest/ +python_requires = >=3.6 classifier = Development Status :: 5 - Production/Stable Environment :: Console From f54b873ca3f9900e17b42f3600a20a36abe2b1a7 Mon Sep 17 00:00:00 2001 From: Ghanshyam Mann Date: Thu, 29 Apr 2021 17:07:04 -0500 Subject: [PATCH 073/159] Run functional job on Ubuntu Focal Devstack is planning to remove the Ubuntu Bionic support. - https://review.opendev.org/c/openstack/devstack/+/788754 Before that we need to switch Bionic job to focal. devstack-tox-functional define the latest nodeset which is single node focal currently so removing nodeset setting from python-cinderclient functional jobs. Change-Id: Ibbbfb20c86bb2ea0d3d74f6a6a1bc913874f67ad --- .zuul.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index 7e92f758b..1074955eb 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -15,7 +15,6 @@ - job: name: python-cinderclient-functional-py36 parent: python-cinderclient-functional-base - nodeset: openstack-single-node-bionic vars: python_version: 3.6 tox_envlist: functional-py36 @@ -23,7 +22,6 @@ - job: name: python-cinderclient-functional-py38 parent: python-cinderclient-functional-base - nodeset: openstack-single-node-focal vars: python_version: 3.8 tox_envlist: functional-py38 From b891c9980f316bd603a9f1429eebad41adf43825 Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Wed, 30 Jun 2021 16:24:06 -0400 Subject: [PATCH 074/159] Remove skip_missing_interpreters This prevents a job reporting 'success' when the appropriate python interpreter cannot be found, when actually it didn't run at all. Also change the default envlist to use generic 'py3' instead of a specific version which might not be present. Also change zuul config so the python-cinderclient-functional-py36 job runs on centos-8-stream nodes, where py36 should be available. And change bindep.txt to specify the correct package name for centos-8. Jeremy Stanley has given a more thorough explanation of why this is a good change: http://lists.openstack.org/pipermail/openstack-discuss/2020-May/014810.html This isn't a theoretical issue. If you look at recent python-cinderclient-functional-py36 job results (for example, [0]), you'll see that Zuul reported 'success', but on a closer look, you'll see that no tests were run. [0] https://zuul.opendev.org/t/openstack/build/1bfc80638086405f8b29905cdd6f71be/log/job-output.txt#25470 Change-Id: I2e2aa24e1592b66b287c84eda97b5079c40a36ec --- .zuul.yaml | 2 ++ bindep.txt | 4 ++-- tox.ini | 3 +-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index 6d8dd0748..ad42b7cdc 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -15,6 +15,8 @@ - job: name: python-cinderclient-functional-py36 parent: python-cinderclient-functional-base + # need to specify a platform that has python 3.6 available + nodeset: devstack-single-node-centos-8-stream vars: python_version: 3.6 tox_envlist: functional-py36 diff --git a/bindep.txt b/bindep.txt index 812bcbacf..2dbd41a1b 100644 --- a/bindep.txt +++ b/bindep.txt @@ -7,7 +7,7 @@ libffi-devel [platform:rpm] libssl-dev [platform:ubuntu-xenial] locales [platform:debian] python-dev [platform:dpkg] -python-devel [platform:rpm] +python-devel [platform:rpm !platform:centos-8] python3-all-dev [platform:ubuntu !platform:ubuntu-precise] python3-dev [platform:dpkg] -python3-devel [platform:fedora] +python3-devel [platform:rpm] diff --git a/tox.ini b/tox.ini index f4740148f..bc78f2496 100644 --- a/tox.ini +++ b/tox.ini @@ -1,9 +1,8 @@ [tox] distribute = False -envlist = py36,py38,pep8 +envlist = py3,pep8 minversion = 3.18.0 skipsdist = True -skip_missing_interpreters = true # this allows tox to infer the base python from the environment name # and override any basepython configured in this file ignore_basepython_conflict=true From 14bb5434db5c4e5de6cb46add098f6559871155d Mon Sep 17 00:00:00 2001 From: zhangboye Date: Sun, 2 May 2021 09:28:16 +0800 Subject: [PATCH 075/159] Dropping lower constraints testing We facing errors related to the new pip resolver, this topic was discussed on the ML and QA team proposed to to test lower-constraints [1]. I propose to drop this test because the complexity and recurring pain needed to maintain that now exceeds the benefits provided by this mechanismes. [1] http://lists.openstack.org/pipermail/openstack-discuss/2020-December/019390.html Change-Id: I324cd145da8469f505ae2c135ee5035ee7008ca1 --- .zuul.yaml | 1 - lower-constraints.txt | 19 ------------------- tox.ini | 5 ----- 3 files changed, 25 deletions(-) delete mode 100644 lower-constraints.txt diff --git a/.zuul.yaml b/.zuul.yaml index ad42b7cdc..418dc1fa2 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -33,7 +33,6 @@ - check-requirements - lib-forward-testing-python3 - openstack-cover-jobs - - openstack-lower-constraints-jobs - openstack-python3-xena-jobs - publish-openstack-docs-pti - release-notes-jobs-python3 diff --git a/lower-constraints.txt b/lower-constraints.txt deleted file mode 100644 index 5533a7380..000000000 --- a/lower-constraints.txt +++ /dev/null @@ -1,19 +0,0 @@ -coverage==5.5 -ddt==1.4.1 -doc8==0.8.1 -docutils==0.16 -fixtures==3.0.0 -keystoneauth1==4.3.1 -oslo.i18n==5.0.1 -oslo.serialization==4.1.0 -oslo.utils==4.8.0 -pbr==5.5.0 -PrettyTable==0.7.2 -reno==3.2.0 -requests==2.25.1 -requests-mock==1.2.0 -simplejson==3.5.1 -stestr==3.1.0 -stevedore==3.3.0 -tempest==26.0.0 -testtools==2.4.0 diff --git a/tox.ini b/tox.ini index bc78f2496..935de967a 100644 --- a/tox.ini +++ b/tox.ini @@ -116,8 +116,3 @@ import-order-style = pep8 ignore-path=.tox,*.egg-info,doc/src/api,doc/source/drivers.rst,doc/build,.eggs/*/EGG-INFO/*.txt,doc/source/configuration/tables,./*.txt,releasenotes/build,doc/source/cli/details.rst extension=.txt,.rst,.inc -[testenv:lower-constraints] -deps = - -c{toxinidir}/lower-constraints.txt - -r{toxinidir}/test-requirements.txt - -r{toxinidir}/requirements.txt From 3502a5591a654ae57741c6738994ffa9d8457696 Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Mon, 17 May 2021 18:34:11 -0400 Subject: [PATCH 076/159] Remove v2 support from the shell Also removes the v2 support from the generic client and restores a skipped test. Additionally, the cinderclient.tests.v2.test_availablity_zone module depends on the v2.shell class, so move that module to v3, update the v3 AvailablityZone class, and make appropriate adjustments to the tests and test fixtures. Change-Id: I7a3cca15f5944141d510a75af6684221c297963b --- cinderclient/api_versions.py | 48 +- cinderclient/client.py | 44 +- cinderclient/shell.py | 41 +- .../tests/unit/fixture_data/client.py | 16 + .../unit/fixture_data/keystone_client.py | 4 +- cinderclient/tests/unit/test_api_versions.py | 39 +- cinderclient/tests/unit/test_client.py | 33 +- cinderclient/tests/unit/test_shell.py | 24 +- cinderclient/tests/unit/v2/test_shell.py | 1358 ----------------- .../unit/{v2 => v3}/test_availability_zone.py | 6 +- cinderclient/v3/availability_zones.py | 25 +- cinderclient/v3/shell.py | 4 +- .../{v2/shell.py => v3/shell_base.py} | 0 13 files changed, 188 insertions(+), 1454 deletions(-) delete mode 100644 cinderclient/tests/unit/v2/test_shell.py rename cinderclient/tests/unit/{v2 => v3}/test_availability_zone.py (96%) rename cinderclient/{v2/shell.py => v3/shell_base.py} (100%) diff --git a/cinderclient/api_versions.py b/cinderclient/api_versions.py index d7a470d6f..47a923a82 100644 --- a/cinderclient/api_versions.py +++ b/cinderclient/api_versions.py @@ -13,8 +13,6 @@ import functools import logging -import os -import pkgutil import re from oslo_utils import strutils @@ -26,9 +24,8 @@ LOG = logging.getLogger(__name__) -# key is a deprecated version and value is an alternative version. -DEPRECATED_VERSIONS = {"2": "3"} -DEPRECATED_VERSION = "2.0" +# key is unsupported version, value is appropriate supported alternative +REPLACEMENT_VERSIONS = {"1": "3", "2": "3"} MAX_VERSION = "3.64" MIN_VERSION = "3.0" @@ -190,14 +187,12 @@ def __repr__(self): def get_available_major_versions(): - # NOTE(andreykurilin): available clients version should not be - # hardcoded, so let's discover them. - matcher = re.compile(r"v[0-9]*$") - submodules = pkgutil.iter_modules([os.path.dirname(__file__)]) - available_versions = [name[1:] for loader, name, ispkg in submodules - if matcher.search(name)] - - return available_versions + # NOTE: the discovery code previously here assumed that if a v2 + # module exists, it must contain a client. This will be False + # during the transition period when the v2 client is removed but + # we are still using other classes in that module. Right now there's + # only one client version available, so we simply hard-code it. + return ['3'] def check_major_version(api_version): @@ -224,11 +219,11 @@ def check_major_version(api_version): def get_api_version(version_string): """Returns checked APIVersion object""" version_string = str(version_string) - if version_string in DEPRECATED_VERSIONS: - LOG.warning("Version %(deprecated_version)s is deprecated, use " - "alternative version %(alternative)s instead.", - {"deprecated_version": version_string, - "alternative": DEPRECATED_VERSIONS[version_string]}) + if version_string in REPLACEMENT_VERSIONS: + LOG.warning("Version %(old)s is not supported, use " + "supported version %(now)s instead.", + {"old": version_string, + "now": REPLACEMENT_VERSIONS[version_string]}) if strutils.is_int_like(version_string): version_string = "%s.0" % version_string @@ -248,11 +243,20 @@ def _get_server_version_range(client): client.version) if not versions: - return APIVersion(), APIVersion() + msg = _("Server does not support microversions. You cannot use this " + "version of the cinderclient with the requested server. " + "Try using a cinderclient version less than 8.0.0.") + raise exceptions.UnsupportedVersion(msg) + for version in versions: if '3.' in version.version: return APIVersion(version.min_version), APIVersion(version.version) + # if we're still here, there's nothing we understand in the versions + msg = _("You cannot use this version of the cinderclient with the " + "requested server.") + raise exceptions.UnsupportedVersion(msg) + def get_highest_version(client): """Queries the server version info and returns highest supported @@ -278,12 +282,6 @@ def discover_version(client, requested_version): server_start_version, server_end_version = _get_server_version_range( client) - if not server_start_version and not server_end_version: - msg = ("Server does not support microversions. Changing server " - "version to %(min_version)s.") - LOG.debug(msg, {"min_version": DEPRECATED_VERSION}) - return APIVersion(DEPRECATED_VERSION) - _validate_server_version(server_start_version, server_end_version) # get the highest version the server can handle relative to the diff --git a/cinderclient/client.py b/cinderclient/client.py index c47334341..559e6aa43 100644 --- a/cinderclient/client.py +++ b/cinderclient/client.py @@ -57,16 +57,14 @@ pass -_VALID_VERSIONS = ['v2', 'v3'] +_VALID_VERSIONS = ['v3'] V3_SERVICE_TYPE = 'volumev3' -V2_SERVICE_TYPE = 'volumev2' -SERVICE_TYPES = {'2': V2_SERVICE_TYPE, - '3': V3_SERVICE_TYPE} +SERVICE_TYPES = {'3': V3_SERVICE_TYPE} REQ_ID_HEADER = 'X-OpenStack-Request-ID' # tell keystoneclient that we can ignore the /v1|v2/{project_id} component of # the service catalog when doing discovery lookups -for svc in ('volume', 'volumev2', 'volumev3'): +for svc in ('volume', 'volumev3'): discover.add_catalog_discover_hack(svc, re.compile(r'/v[12]/\w+/?$'), '/') @@ -85,6 +83,8 @@ def get_server_version(url, insecure=False, cacert=None, cert=None): :returns: APIVersion object for min and max version supported by the server """ + # NOTE: we (the client) don't support v2 anymore, but this function + # is checking the server version min_version = "2.0" current_version = "2.0" @@ -128,22 +128,37 @@ def get_server_version(url, insecure=False, cacert=None, cert=None): current_version = version['version'] break else: - # Set the values, but don't break out the loop here in case v3 - # comes later - min_version = '2.0' - current_version = '2.0' + # keep looking in case this cloud is running v2 and + # we haven't seen v3 yet + continue except exceptions.ClientException as e: + # NOTE: logging the warning but returning the lowest server API version + # supported in this OpenStack release is the legacy behavior, so that's + # what we do here + min_version = '3.0' + current_version = '3.0' logger.warning("Error in server version query:%s\n" - "Returning APIVersion 2.0", str(e.message)) + "Returning APIVersion 3.0", str(e.message)) return (api_versions.APIVersion(min_version), api_versions.APIVersion(current_version)) def get_highest_client_server_version(url, insecure=False, cacert=None, cert=None): - """Returns highest supported version by client and server as a string.""" + """Returns highest supported version by client and server as a string. + + :raises: UnsupportedVersion if the maximum supported by the server + is less than the minimum supported by the client + """ min_server, max_server = get_server_version(url, insecure, cacert, cert) max_client = api_versions.APIVersion(api_versions.MAX_VERSION) + min_client = api_versions.APIVersion(api_versions.MIN_VERSION) + if max_server < min_client: + msg = _("The maximum version supported by the server (%(srv)s) does " + "not meet the minimum version supported by this client " + "(%(cli)s)") % {"srv": str(max_server), + "cli": api_versions.MIN_VERSION} + raise exceptions.UnsupportedVersion(msg) return min(max_server, max_client).get_string() @@ -769,7 +784,6 @@ def _get_client_class_and_version(version): def get_client_class(version): version_map = { - '2': 'cinderclient.v2.client.Client', '3': 'cinderclient.v3.client.Client', } try: @@ -797,10 +811,6 @@ def discover_extensions(version): def _discover_via_python_path(): for (module_loader, name, ispkg) in pkgutil.iter_modules(): if name.endswith('cinderclient_ext'): - if not hasattr(module_loader, 'load_module'): - # Python 2.6 compat: actually get an ImpImporter obj - module_loader = module_loader.find_module(name) - module = module_loader.load_module(name) yield name, module @@ -845,7 +855,7 @@ def Client(version, *args, **kwargs): Here ``VERSION`` can be a string or ``cinderclient.api_versions.APIVersion`` obj. If you prefer string value, - you can use ``2`` (deprecated now) or ``3.X`` (where X is a microversion). + you can use ``3`` or ``3.X`` (where X is a microversion). Alternatively, you can create a client instance using the keystoneclient diff --git a/cinderclient/shell.py b/cinderclient/shell.py index 0c3fa4535..dc9190ab4 100644 --- a/cinderclient/shell.py +++ b/cinderclient/shell.py @@ -48,7 +48,6 @@ DEFAULT_MAJOR_OS_VOLUME_API_VERSION = "3" DEFAULT_CINDER_ENDPOINT_TYPE = 'publicURL' -V2_SHELL = 'cinderclient.v2.shell' V3_SHELL = 'cinderclient.v3.shell' HINT_HELP_MSG = (" [hint: use '--os-volume-api-version' flag to show help " "message for proper version]") @@ -202,7 +201,8 @@ def get_base_parser(self): default=None), help=_('Block Storage API version. ' 'Accepts X, X.Y (where X is major and Y is minor ' - 'part).' + 'part). NOTE: this client accepts only \'3\' for ' + 'the major version. ' 'Default=env[OS_VOLUME_API_VERSION].')) parser.add_argument('--os_volume_api_version', help=argparse.SUPPRESS) @@ -356,10 +356,7 @@ def get_subcommand_parser(self, version, do_help=False, input_args=None): self.subcommands = {} subparsers = parser.add_subparsers(metavar='') - if version.ver_major == 3: - actions_module = importutils.import_module(V3_SHELL) - else: - actions_module = importutils.import_module(V2_SHELL) + actions_module = importutils.import_module(V3_SHELL) self._find_actions(subparsers, actions_module, version, do_help, input_args) @@ -740,6 +737,10 @@ def main(self, argv): except exc.AuthorizationFailure: raise exc.CommandError("Unable to authorize user.") + # FIXME: this section figuring out the api version could use + # analysis and refactoring. See + # https://review.opendev.org/c/openstack/python-cinderclient/+/766882/ + # for some ideas. endpoint_api_version = None # Try to get the API version from the endpoint URL. If that fails fall # back to trying to use what the user specified via @@ -750,18 +751,26 @@ def main(self, argv): self.cs.get_volume_api_version_from_endpoint() except exc.UnsupportedVersion: endpoint_api_version = options.os_volume_api_version - if api_version_input: + # FIXME: api_version_input is initialized as True at the beginning + # of this function and never modified + if api_version_input and endpoint_api_version: logger.warning("Cannot determine the API version from " "the endpoint URL. Falling back to the " "user-specified version: %s", endpoint_api_version) - else: + elif endpoint_api_version: logger.warning("Cannot determine the API version from the " "endpoint URL or user input. Falling back " "to the default API version: %s", endpoint_api_version) + else: + msg = _("Cannot determine API version. Please specify by " + "using --os-volume-api-version option.") + raise exc.UnsupportedVersion(msg) API_MAX_VERSION = api_versions.APIVersion(api_versions.MAX_VERSION) + # FIXME: the endpoint_api_version[0] can ONLY be '3' now, so the + # above line should probably be ripped out and this condition removed if endpoint_api_version[0] == '3': disc_client = client.Client(API_MAX_VERSION, os_username, @@ -807,14 +816,9 @@ def _discover_client(self, os_auth_url, client_args): - if (os_api_version.get_major_version() in - api_versions.DEPRECATED_VERSIONS): - discovered_version = api_versions.DEPRECATED_VERSION - os_service_type = 'volume' - else: - discovered_version = api_versions.discover_version( - current_client, - os_api_version) + discovered_version = api_versions.discover_version( + current_client, + os_api_version) if not os_endpoint_type: os_endpoint_type = DEFAULT_CINDER_ENDPOINT_TYPE @@ -841,6 +845,11 @@ def _discover_client(self, return current_client, discovered_version def _discover_service_type(self, discovered_version): + # FIXME: this function is either no longer needed or could use a + # refactoring. The official service type is 'block-storage', + # which isn't even present here. (Devstack creates 2 service + # types which it maps to v3: 'block-storage' and 'volumev3'. + # The default 'catalog_type' in tempest is 'volumev3'.) SERVICE_TYPES = {'1': 'volume', '2': 'volumev2', '3': 'volumev3'} major_version = discovered_version.get_major_version() service_type = SERVICE_TYPES[major_version] diff --git a/cinderclient/tests/unit/fixture_data/client.py b/cinderclient/tests/unit/fixture_data/client.py index 4a30f70b3..2beeb906d 100644 --- a/cinderclient/tests/unit/fixture_data/client.py +++ b/cinderclient/tests/unit/fixture_data/client.py @@ -14,6 +14,7 @@ from cinderclient.tests.unit.fixture_data import base from cinderclient.v2 import client as v2client +from cinderclient.v3 import client as v3client class Base(base.Fixture): @@ -46,3 +47,18 @@ def new_client(self): api_key='xx', project_id='xx', auth_url=self.identity_url) + + +class V3(Base): + + def __init__(self, *args, **kwargs): + super(V3, self).__init__(*args, **kwargs) + + svc = self.token.add_service('volumev3') + svc.add_endpoint(self.volume_url) + + def new_client(self): + return v3client.Client(username='xx', + api_key='xx', + project_id='xx', + auth_url=self.identity_url) diff --git a/cinderclient/tests/unit/fixture_data/keystone_client.py b/cinderclient/tests/unit/fixture_data/keystone_client.py index 061235b8c..81767c556 100644 --- a/cinderclient/tests/unit/fixture_data/keystone_client.py +++ b/cinderclient/tests/unit/fixture_data/keystone_client.py @@ -153,7 +153,7 @@ def generate_v2_project_scoped_token(**kwargs): ], 'endpoints_links': [], 'name': None, - 'type': 'volumev2' + 'type': 'volumev3' } # Add multiple Cinder endpoints @@ -163,7 +163,7 @@ def generate_v2_project_scoped_token(**kwargs): name = "cinder%i" % count # Assign the service name and a unique endpoint endpoint_copy['endpoints'][0]['publicURL'] = \ - 'http://%s.api.com/v2' % name + 'http://%s.api.com/v3' % name endpoint_copy['name'] = name o['access']['serviceCatalog'].append(endpoint_copy) diff --git a/cinderclient/tests/unit/test_api_versions.py b/cinderclient/tests/unit/test_api_versions.py index d8aad761a..f56336ccc 100644 --- a/cinderclient/tests/unit/test_api_versions.py +++ b/cinderclient/tests/unit/test_api_versions.py @@ -18,7 +18,6 @@ import ddt from cinderclient import api_versions -from cinderclient import client as base_client from cinderclient import exceptions from cinderclient.tests.unit import test_utils from cinderclient.tests.unit import utils @@ -212,6 +211,14 @@ def _mock_returned_server_version(self, server_version, self.fake_client.services.server_api_version.return_value = val @ddt.data( + # what the data mean: + # items 1, 2: client min, max + # items 3, 4: server min, max + # item 5: user's requested API version + # item 6: should this raise an exception? + # item 7: version that should be returned when no exception + # item 8: what client.services.server_api_version should return + # when called by _get_server_version_range in discover_version ("3.1", "3.3", "3.4", "3.7", "3.3", True), # Server too new ("3.9", "3.10", "3.0", "3.3", "3.10", True), # Server too old ("3.3", "3.9", "3.7", "3.17", "3.9", False), # Requested < server @@ -222,9 +229,8 @@ def _mock_returned_server_version(self, server_version, # downgraded because of both: ("3.5", "3.7", "3.0", "3.8", "3.9", False, "3.7"), ("3.5", "3.5", "3.0", "3.5", "3.5", False), # Server & client same - ("3.5", "3.5", "3.0", "3.5", "3.5", False, "2.0", []), # Pre-micro + ("3.5", "3.5", None, None, "3.5", True, None, []), # Pre-micro ("3.1", "3.11", "3.4", "3.7", "3.7", False), # Requested in range - ("3.1", "3.11", None, None, "3.7", False), # Server w/o support ("3.5", "3.5", "3.0", "3.5", "1.0", True) # Requested too old ) @ddt.unpack @@ -240,21 +246,23 @@ def test_microversion(self, client_min, client_max, server_min, server_max, api_versions.MIN_VERSION = client_min if exp_range: - self.assertRaisesRegex(exceptions.UnsupportedVersion, - ".*range is '%s' to '%s'.*" % - (server_min, server_max), - api_versions.discover_version, - self.fake_client, - api_versions.APIVersion(requested_version)) + exc = self.assertRaises(exceptions.UnsupportedVersion, + api_versions.discover_version, + self.fake_client, + api_versions.APIVersion(requested_version)) + if ret_val is not None: + self.assertIn("Server does not support microversions", + str(exc)) + else: + self.assertIn("range is '%s' to '%s'" % + (server_min, server_max), str(exc)) else: discovered_version = api_versions.discover_version( self.fake_client, api_versions.APIVersion(requested_version)) version = requested_version - if server_min is None and server_max is None: - version = api_versions.DEPRECATED_VERSION - elif end_version is not None: + if end_version is not None: version = end_version self.assertEqual(version, discovered_version.get_string()) @@ -266,10 +274,3 @@ def test_get_highest_version(self): highest_version = api_versions.get_highest_version(self.fake_client) self.assertEqual("3.14", highest_version.get_string()) self.assertTrue(self.fake_client.services.server_api_version.called) - - def test_get_highest_version_bad_client(self): - """Tests that we gracefully handle the wrong version of client.""" - v2_client = base_client.Client('2.0') - ex = self.assertRaises(exceptions.UnsupportedVersion, - api_versions.get_highest_version, v2_client) - self.assertIn('Invalid client version 2.0 to get', str(ex)) diff --git a/cinderclient/tests/unit/test_client.py b/cinderclient/tests/unit/test_client.py index fa19492e7..1501d6f32 100644 --- a/cinderclient/tests/unit/test_client.py +++ b/cinderclient/tests/unit/test_client.py @@ -33,8 +33,9 @@ class ClientTest(utils.TestCase): def test_get_client_class_v2(self): - output = cinderclient.client.get_client_class('2') - self.assertEqual(cinderclient.v2.client.Client, output) + self.assertRaises(cinderclient.exceptions.UnsupportedVersion, + cinderclient.client.get_client_class, + '2') def test_get_client_class_unknown(self): self.assertRaises(cinderclient.exceptions.UnsupportedVersion, @@ -81,10 +82,14 @@ def test_log_req(self): def test_versions(self): v2_url = 'http://fakeurl/v2/tenants' + v3_url = 'http://fakeurl/v3/tenants' unknown_url = 'http://fakeurl/v9/tenants' - self.assertEqual('2', - cinderclient.client.get_volume_api_from_url(v2_url)) + self.assertRaises(cinderclient.exceptions.UnsupportedVersion, + cinderclient.client.get_volume_api_from_url, + v2_url) + self.assertEqual('3', + cinderclient.client.get_volume_api_from_url(v3_url)) self.assertRaises(cinderclient.exceptions.UnsupportedVersion, cinderclient.client.get_volume_api_from_url, unknown_url) @@ -318,6 +323,7 @@ class GetAPIVersionTestCase(utils.TestCase): @mock.patch('cinderclient.client.requests.get') def test_get_server_version_v2(self, mock_request): + # Why are we testing this? Because we can! mock_response = utils.TestResponse({ "status_code": 200, @@ -329,6 +335,7 @@ def test_get_server_version_v2(self, mock_request): url = "http://192.168.122.127:8776/v2/e5526285ebd741b1819393f772f11fc3" min_version, max_version = cinderclient.client.get_server_version(url) + self.assertEqual(api_versions.APIVersion('2.0'), min_version) self.assertEqual(api_versions.APIVersion('2.0'), max_version) @@ -427,3 +434,21 @@ def test_get_highest_client_server_version(self, version, mock_request): cinderclient.client.get_highest_client_server_version(url)) expected = version if version == '3.12' else '3.16' self.assertEqual(expected, highest) + + @mock.patch('cinderclient.client.requests.get') + def test_get_highest_client_server_version_negative(self, + mock_request): + + mock_response = utils.TestResponse({ + "status_code": 200, + "text": json.dumps(fakes.fake_request_get_no_v3()) + }) + + mock_request.return_value = mock_response + + url = "http://192.168.122.127:8776/v3/e5526285ebd741b1819393f772f11fc3" + + self.assertRaises(exceptions.UnsupportedVersion, + cinderclient.client. + get_highest_client_server_version, + url) diff --git a/cinderclient/tests/unit/test_shell.py b/cinderclient/tests/unit/test_shell.py index 8c5df116b..682d50920 100644 --- a/cinderclient/tests/unit/test_shell.py +++ b/cinderclient/tests/unit/test_shell.py @@ -13,9 +13,9 @@ import argparse import io +import json import re import sys -import unittest from unittest import mock import ddt @@ -35,6 +35,7 @@ from cinderclient.tests.unit import fake_actions_module from cinderclient.tests.unit.fixture_data import keystone_client from cinderclient.tests.unit import utils +from cinderclient.tests.unit.v3 import fakes @ddt.ddt @@ -205,8 +206,13 @@ def list_volumes_on_service(self, count, mocker): os_auth_url = "http://multiple.service.names/v2.0" mocker.register_uri('POST', os_auth_url + "/tokens", text=keystone_client.keystone_request_callback) + # microversion support requires us to make a versions request + # to the endpoint to see exactly what is supported by the server mocker.register_uri('GET', - "http://cinder%i.api.com/v2/volumes/detail" + "http://cinder%i.api.com/" + % count, text=json.dumps(fakes.fake_request_get())) + mocker.register_uri('GET', + "http://cinder%i.api.com/v3/volumes/detail" % count, text='{"volumes": []}') self.make_env(include={'OS_AUTH_URL': os_auth_url, 'CINDER_SERVICE_NAME': 'cinder%i' % count}) @@ -219,7 +225,6 @@ def test_duplicate_filters(self): _shell.main, ['list', '--name', 'abc', '--filters', 'name=xyz']) - @unittest.skip("Skip cuz I broke it") def test_cinder_service_name(self): # Failing with 'No mock address' means we are not # choosing the correct endpoint @@ -248,14 +253,19 @@ def test_password_prompted(self, mock_getpass, mock_stdin, mock_discover, tenant_name=self.FAKE_ENV['OS_PROJECT_NAME'], username=self.FAKE_ENV['OS_USERNAME']) + @mock.patch('cinderclient.api_versions.discover_version', + return_value=api_versions.APIVersion("3.0")) @requests_mock.Mocker() - def test_noauth_plugin(self, mocker): - os_auth_url = "http://example.com/v2" + def test_noauth_plugin(self, mock_disco, mocker): + # just to prove i'm not crazy about the mock parameter ordering + self.assertTrue(requests_mock.mocker.Mocker, type(mocker)) + + os_volume_url = "http://example.com/volumes/v3" mocker.register_uri('GET', "%s/volumes/detail" - % os_auth_url, text='{"volumes": []}') + % os_volume_url, text='{"volumes": []}') _shell = shell.OpenStackCinderShell() - args = ['--os-endpoint', os_auth_url, + args = ['--os-endpoint', os_volume_url, '--os-auth-type', 'noauth', '--os-user-id', 'admin', '--os-project-id', 'admin', 'list'] _shell.main(args) diff --git a/cinderclient/tests/unit/v2/test_shell.py b/cinderclient/tests/unit/v2/test_shell.py deleted file mode 100644 index 78ecf7409..000000000 --- a/cinderclient/tests/unit/v2/test_shell.py +++ /dev/null @@ -1,1358 +0,0 @@ -# Copyright (c) 2013 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 unittest import mock -from urllib import parse - -import ddt -import fixtures -from requests_mock.contrib import fixture as requests_mock_fixture - -from cinderclient import client -from cinderclient import exceptions -from cinderclient import shell -from cinderclient.tests.unit.fixture_data import keystone_client -from cinderclient.tests.unit import utils -from cinderclient.tests.unit.v2 import fakes -from cinderclient.v2 import shell as test_shell -from cinderclient.v2 import volume_backups -from cinderclient.v2 import volumes - - -@ddt.ddt -@mock.patch.object(client, 'Client', fakes.FakeClient) -class ShellTest(utils.TestCase): - - FAKE_ENV = { - 'CINDER_USERNAME': 'username', - 'CINDER_PASSWORD': 'password', - 'CINDER_PROJECT_ID': 'project_id', - 'OS_VOLUME_API_VERSION': '2', - 'CINDER_URL': keystone_client.BASE_URL, - } - - # Patch os.environ to avoid required auth info. - def setUp(self): - """Run before each test.""" - super(ShellTest, self).setUp() - for var in self.FAKE_ENV: - self.useFixture(fixtures.EnvironmentVariable(var, - self.FAKE_ENV[var])) - - self.mock_completion() - - self.shell = shell.OpenStackCinderShell() - - self.requests = self.useFixture(requests_mock_fixture.Fixture()) - self.requests.register_uri( - 'GET', keystone_client.BASE_URL, - text=keystone_client.keystone_request_callback) - - self.cs = mock.Mock() - - def _make_args(self, args): - class Args(object): - def __init__(self, entries): - self.__dict__.update(entries) - - return Args(args) - - def run_command(self, cmd): - self.shell.main(cmd.split()) - - def assert_called(self, method, url, body=None, - partial_body=None, **kwargs): - return self.shell.cs.assert_called(method, url, body, - partial_body, **kwargs) - - def test_list(self): - self.run_command('list') - # NOTE(jdg): we default to detail currently - self.assert_called('GET', '/volumes/detail') - - def test_list_filter_tenant_with_all_tenants(self): - self.run_command('list --all-tenants=1 --tenant 123') - self.assert_called('GET', - '/volumes/detail?all_tenants=1&project_id=123') - - def test_list_filter_tenant_without_all_tenants(self): - self.run_command('list --tenant 123') - self.assert_called('GET', - '/volumes/detail?all_tenants=1&project_id=123') - - def test_metadata_args_with_limiter(self): - self.run_command('create --metadata key1="--test1" 1') - self.assert_called('GET', '/volumes/1234') - expected = {'volume': {'imageRef': None, - 'size': 1, - 'availability_zone': None, - 'source_volid': None, - 'consistencygroup_id': None, - 'name': None, - 'snapshot_id': None, - 'metadata': {'key1': '"--test1"'}, - 'volume_type': None, - 'description': None, - }} - self.assert_called_anytime('POST', '/volumes', expected) - - def test_metadata_args_limiter_display_name(self): - self.run_command('create --metadata key1="--t1" --name="t" 1') - self.assert_called('GET', '/volumes/1234') - expected = {'volume': {'imageRef': None, - 'size': 1, - 'availability_zone': None, - 'source_volid': None, - 'consistencygroup_id': None, - 'name': '"t"', - 'snapshot_id': None, - 'metadata': {'key1': '"--t1"'}, - 'volume_type': None, - 'description': None, - }} - self.assert_called_anytime('POST', '/volumes', expected) - - def test_delimit_metadata_args(self): - self.run_command('create --metadata key1="test1" key2="test2" 1') - expected = {'volume': {'imageRef': None, - 'size': 1, - 'availability_zone': None, - 'source_volid': None, - 'consistencygroup_id': None, - 'name': None, - 'snapshot_id': None, - 'metadata': {'key1': '"test1"', - 'key2': '"test2"'}, - 'volume_type': None, - 'description': None, - }} - self.assert_called_anytime('POST', '/volumes', expected) - - def test_delimit_metadata_args_display_name(self): - self.run_command('create --metadata key1="t1" --name="t" 1') - self.assert_called('GET', '/volumes/1234') - expected = {'volume': {'imageRef': None, - 'size': 1, - 'availability_zone': None, - 'source_volid': None, - 'consistencygroup_id': None, - 'name': '"t"', - 'snapshot_id': None, - 'metadata': {'key1': '"t1"'}, - 'volume_type': None, - 'description': None, - }} - self.assert_called_anytime('POST', '/volumes', expected) - - def test_list_filter_status(self): - self.run_command('list --status=available') - self.assert_called('GET', '/volumes/detail?status=available') - - def test_list_filter_bootable_true(self): - self.run_command('list --bootable=true') - self.assert_called('GET', '/volumes/detail?bootable=true') - - def test_list_filter_bootable_false(self): - self.run_command('list --bootable=false') - self.assert_called('GET', '/volumes/detail?bootable=false') - - def test_list_filter_name(self): - self.run_command('list --name=1234') - self.assert_called('GET', '/volumes/detail?name=1234') - - def test_list_all_tenants(self): - self.run_command('list --all-tenants=1') - self.assert_called('GET', '/volumes/detail?all_tenants=1') - - def test_list_marker(self): - self.run_command('list --marker=1234') - self.assert_called('GET', '/volumes/detail?marker=1234') - - def test_list_limit(self): - self.run_command('list --limit=10') - self.assert_called('GET', '/volumes/detail?limit=10') - - @mock.patch("cinderclient.utils.print_list") - def test_list_field(self, mock_print): - self.run_command('list --field Status,Name,Size,Bootable') - self.assert_called('GET', '/volumes/detail') - key_list = ['ID', 'Status', 'Name', 'Size', 'Bootable'] - mock_print.assert_called_once_with(mock.ANY, key_list, - exclude_unavailable=True, sortby_index=0) - - @mock.patch("cinderclient.utils.print_list") - def test_list_field_with_all_tenants(self, mock_print): - self.run_command('list --field Status,Name,Size,Bootable ' - '--all-tenants 1') - self.assert_called('GET', '/volumes/detail?all_tenants=1') - key_list = ['ID', 'Status', 'Name', 'Size', 'Bootable'] - mock_print.assert_called_once_with(mock.ANY, key_list, - exclude_unavailable=True, sortby_index=0) - - @mock.patch("cinderclient.utils.print_list") - def test_list_duplicate_fields(self, mock_print): - self.run_command('list --field Status,id,Size,status') - self.assert_called('GET', '/volumes/detail') - key_list = ['ID', 'Status', 'Size'] - mock_print.assert_called_once_with(mock.ANY, key_list, - exclude_unavailable=True, sortby_index=0) - - @mock.patch("cinderclient.utils.print_list") - def test_list_field_with_tenant(self, mock_print): - self.run_command('list --field Status,Name,Size,Bootable ' - '--tenant 123') - self.assert_called('GET', - '/volumes/detail?all_tenants=1&project_id=123') - key_list = ['ID', 'Status', 'Name', 'Size', 'Bootable'] - mock_print.assert_called_once_with(mock.ANY, key_list, - exclude_unavailable=True, sortby_index=0) - - def test_list_sort_name(self): - # Client 'name' key is mapped to 'display_name' - self.run_command('list --sort=name') - self.assert_called('GET', '/volumes/detail?sort=display_name') - - def test_list_sort_single_key_only(self): - self.run_command('list --sort=id') - self.assert_called('GET', '/volumes/detail?sort=id') - - def test_list_sort_single_key_trailing_colon(self): - self.run_command('list --sort=id:') - self.assert_called('GET', '/volumes/detail?sort=id') - - def test_list_sort_single_key_and_dir(self): - self.run_command('list --sort=id:asc') - url = '/volumes/detail?%s' % parse.urlencode([('sort', 'id:asc')]) - self.assert_called('GET', url) - - def test_list_sort_multiple_keys_only(self): - self.run_command('list --sort=id,status,size') - url = ('/volumes/detail?%s' % - parse.urlencode([('sort', 'id,status,size')])) - self.assert_called('GET', url) - - def test_list_sort_multiple_keys_and_dirs(self): - self.run_command('list --sort=id:asc,status,size:desc') - url = ('/volumes/detail?%s' % - parse.urlencode([('sort', 'id:asc,status,size:desc')])) - self.assert_called('GET', url) - - def test_list_reorder_with_sort(self): - # sortby_index is None if there is sort information - for cmd in ['list --sort=name', - 'list --sort=name:asc']: - with mock.patch('cinderclient.utils.print_list') as mock_print: - self.run_command(cmd) - mock_print.assert_called_once_with( - mock.ANY, mock.ANY, exclude_unavailable=True, - sortby_index=None) - - def test_list_reorder_without_sort(self): - # sortby_index is 0 without sort information - for cmd in ['list', 'list --all-tenants']: - with mock.patch('cinderclient.utils.print_list') as mock_print: - self.run_command(cmd) - mock_print.assert_called_once_with( - mock.ANY, mock.ANY, exclude_unavailable=True, - sortby_index=0) - - def test_list_availability_zone(self): - self.run_command('availability-zone-list') - self.assert_called('GET', '/os-availability-zone') - - def test_create_volume_from_snapshot(self): - expected = {'volume': {'size': None}} - - expected['volume']['snapshot_id'] = '1234' - self.run_command('create --snapshot-id=1234') - self.assert_called_anytime('POST', '/volumes', partial_body=expected) - self.assert_called('GET', '/volumes/1234') - - expected['volume']['size'] = 2 - self.run_command('create --snapshot-id=1234 2') - self.assert_called_anytime('POST', '/volumes', partial_body=expected) - self.assert_called('GET', '/volumes/1234') - - def test_create_volume_from_volume(self): - expected = {'volume': {'size': None}} - - expected['volume']['source_volid'] = '1234' - self.run_command('create --source-volid=1234') - self.assert_called_anytime('POST', '/volumes', partial_body=expected) - self.assert_called('GET', '/volumes/1234') - - expected['volume']['size'] = 2 - self.run_command('create --source-volid=1234 2') - self.assert_called_anytime('POST', '/volumes', partial_body=expected) - self.assert_called('GET', '/volumes/1234') - - def test_create_volume_from_image(self): - expected = {'volume': {'size': 1, - 'imageRef': '1234'}} - self.run_command('create --image=1234 1') - self.assert_called_anytime('POST', '/volumes', partial_body=expected) - self.assert_called('GET', '/volumes/1234') - - def test_upload_to_image(self): - expected = {'os-volume_upload_image': {'force': False, - 'container_format': 'bare', - 'disk_format': 'raw', - 'image_name': 'test-image'}} - self.run_command('upload-to-image 1234 test-image') - self.assert_called_anytime('GET', '/volumes/1234') - self.assert_called_anytime('POST', '/volumes/1234/action', - body=expected) - - def test_upload_to_image_force(self): - expected = {'os-volume_upload_image': {'force': 'True', - 'container_format': 'bare', - 'disk_format': 'raw', - 'image_name': 'test-image'}} - self.run_command('upload-to-image --force=True 1234 test-image') - self.assert_called_anytime('GET', '/volumes/1234') - self.assert_called_anytime('POST', '/volumes/1234/action', - body=expected) - - def test_create_size_required_if_not_snapshot_or_clone(self): - self.assertRaises(SystemExit, self.run_command, 'create') - - def test_create_size_zero_if_not_snapshot_or_clone(self): - expected = {'volume': {'size': 0}} - self.run_command('create 0') - self.assert_called_anytime('POST', '/volumes', partial_body=expected) - self.assert_called('GET', '/volumes/1234') - - def test_show(self): - self.run_command('show 1234') - self.assert_called('GET', '/volumes/1234') - - def test_delete(self): - self.run_command('delete 1234') - self.assert_called('DELETE', '/volumes/1234') - - def test_delete_by_name(self): - self.run_command('delete sample-volume') - self.assert_called_anytime('GET', '/volumes/detail?all_tenants=1&' - 'name=sample-volume') - self.assert_called('DELETE', '/volumes/1234') - - def test_delete_multiple(self): - self.run_command('delete 1234 5678') - self.assert_called_anytime('DELETE', '/volumes/1234') - self.assert_called('DELETE', '/volumes/5678') - - def test_delete_with_cascade_true(self): - self.run_command('delete 1234 --cascade') - self.assert_called('DELETE', '/volumes/1234?cascade=True') - self.run_command('delete --cascade 1234') - self.assert_called('DELETE', '/volumes/1234?cascade=True') - - def test_delete_with_cascade_with_invalid_value(self): - self.assertRaises(SystemExit, self.run_command, - 'delete 1234 --cascade 1234') - - def test_backup(self): - self.run_command('backup-create 1234') - self.assert_called('POST', '/backups') - - def test_backup_incremental(self): - self.run_command('backup-create 1234 --incremental') - self.assert_called('POST', '/backups') - - def test_backup_force(self): - self.run_command('backup-create 1234 --force') - self.assert_called('POST', '/backups') - - def test_backup_snapshot(self): - self.run_command('backup-create 1234 --snapshot-id 4321') - self.assert_called('POST', '/backups') - - def test_multiple_backup_delete(self): - self.run_command('backup-delete 1234 5678') - self.assert_called_anytime('DELETE', '/backups/1234') - self.assert_called('DELETE', '/backups/5678') - - def test_restore(self): - self.run_command('backup-restore 1234') - self.assert_called('POST', '/backups/1234/restore') - - def test_restore_with_name(self): - self.run_command('backup-restore 1234 --name restore_vol') - expected = {'restore': {'volume_id': None, 'name': 'restore_vol'}} - self.assert_called('POST', '/backups/1234/restore', - body=expected) - - def test_restore_with_name_error(self): - self.assertRaises(exceptions.CommandError, self.run_command, - 'backup-restore 1234 --volume fake_vol --name ' - 'restore_vol') - - @ddt.data('backup_name', '1234') - @mock.patch('cinderclient.shell_utils.find_backup') - @mock.patch('cinderclient.utils.print_dict') - @mock.patch('cinderclient.utils.find_volume') - def test_do_backup_restore_with_name(self, - value, - mock_find_volume, - mock_print_dict, - mock_find_backup): - backup_id = '1234' - volume_id = '5678' - name = None - input = { - 'backup': value, - 'volume': volume_id, - 'name': None - } - - args = self._make_args(input) - with mock.patch.object(self.cs.restores, - 'restore') as mocked_restore: - mock_find_volume.return_value = volumes.Volume(self, - {'id': volume_id}, - loaded=True) - mock_find_backup.return_value = volume_backups.VolumeBackup( - self, - {'id': backup_id}, - loaded=True) - test_shell.do_backup_restore(self.cs, args) - mock_find_backup.assert_called_once_with( - self.cs, - value) - mocked_restore.assert_called_once_with( - backup_id, - volume_id, - name) - self.assertTrue(mock_print_dict.called) - - def test_record_export(self): - self.run_command('backup-export 1234') - self.assert_called('GET', '/backups/1234/export_record') - - def test_record_import(self): - self.run_command('backup-import fake.driver URL_STRING') - expected = {'backup-record': {'backup_service': 'fake.driver', - 'backup_url': 'URL_STRING'}} - self.assert_called('POST', '/backups/import_record', expected) - - def test_snapshot_list_filter_volume_id(self): - self.run_command('snapshot-list --volume-id=1234') - self.assert_called('GET', '/snapshots/detail?volume_id=1234') - - def test_snapshot_list_filter_status_and_volume_id(self): - self.run_command('snapshot-list --status=available --volume-id=1234') - self.assert_called('GET', '/snapshots/detail?' - 'status=available&volume_id=1234') - - def test_snapshot_list_filter_name(self): - self.run_command('snapshot-list --name abc') - self.assert_called('GET', '/snapshots/detail?name=abc') - - @mock.patch("cinderclient.utils.print_list") - def test_snapshot_list_sort(self, mock_print_list): - self.run_command('snapshot-list --sort id') - self.assert_called('GET', '/snapshots/detail?sort=id') - columns = ['ID', 'Volume ID', 'Status', 'Name', 'Size'] - mock_print_list.assert_called_once_with(mock.ANY, columns, - sortby_index=None) - - def test_snapshot_list_filter_tenant_with_all_tenants(self): - self.run_command('snapshot-list --all-tenants=1 --tenant 123') - self.assert_called('GET', - '/snapshots/detail?all_tenants=1&project_id=123') - - def test_snapshot_list_filter_tenant_without_all_tenants(self): - self.run_command('snapshot-list --tenant 123') - self.assert_called('GET', - '/snapshots/detail?all_tenants=1&project_id=123') - - def test_rename(self): - # basic rename with positional arguments - self.run_command('rename 1234 new-name') - expected = {'volume': {'name': 'new-name'}} - self.assert_called('PUT', '/volumes/1234', body=expected) - # change description only - self.run_command('rename 1234 --description=new-description') - expected = {'volume': {'description': 'new-description'}} - self.assert_called('PUT', '/volumes/1234', body=expected) - # rename and change description - self.run_command('rename 1234 new-name ' - '--description=new-description') - expected = {'volume': { - 'name': 'new-name', - 'description': 'new-description', - }} - self.assert_called('PUT', '/volumes/1234', body=expected) - - # Call rename with no arguments - self.assertRaises(SystemExit, self.run_command, 'rename') - - def test_rename_invalid_args(self): - """Ensure that error generated does not reference an HTTP code.""" - - self.assertRaisesRegex(exceptions.ClientException, - '(?!HTTP)', - self.run_command, - 'rename volume-1234-abcd') - - def test_rename_snapshot(self): - # basic rename with positional arguments - self.run_command('snapshot-rename 1234 new-name') - expected = {'snapshot': {'name': 'new-name'}} - self.assert_called('PUT', '/snapshots/1234', body=expected) - # change description only - self.run_command('snapshot-rename 1234 ' - '--description=new-description') - expected = {'snapshot': {'description': 'new-description'}} - self.assert_called('PUT', '/snapshots/1234', body=expected) - # snapshot-rename and change description - self.run_command('snapshot-rename 1234 new-name ' - '--description=new-description') - expected = {'snapshot': { - 'name': 'new-name', - 'description': 'new-description', - }} - self.assert_called('PUT', '/snapshots/1234', body=expected) - - # Call snapshot-rename with no arguments - self.assertRaises(SystemExit, self.run_command, 'snapshot-rename') - - def test_rename_snapshot_invalid_args(self): - self.assertRaises(exceptions.ClientException, - self.run_command, - 'snapshot-rename snapshot-1234') - - def test_set_metadata_set(self): - self.run_command('metadata 1234 set key1=val1 key2=val2') - self.assert_called('POST', '/volumes/1234/metadata', - {'metadata': {'key1': 'val1', 'key2': 'val2'}}) - - def test_set_metadata_delete_dict(self): - self.run_command('metadata 1234 unset key1=val1 key2=val2') - self.assert_called('DELETE', '/volumes/1234/metadata/key1') - self.assert_called('DELETE', '/volumes/1234/metadata/key2', pos=-2) - - def test_set_metadata_delete_keys(self): - self.run_command('metadata 1234 unset key1 key2') - self.assert_called('DELETE', '/volumes/1234/metadata/key1') - self.assert_called('DELETE', '/volumes/1234/metadata/key2', pos=-2) - - def test_reset_state(self): - self.run_command('reset-state 1234') - expected = {'os-reset_status': {'status': 'available'}} - self.assert_called('POST', '/volumes/1234/action', body=expected) - - def test_reset_state_attach(self): - self.run_command('reset-state --state in-use 1234') - expected = {'os-reset_status': {'status': 'in-use'}} - self.assert_called('POST', '/volumes/1234/action', body=expected) - - def test_reset_state_with_flag(self): - self.run_command('reset-state --state error 1234') - expected = {'os-reset_status': {'status': 'error'}} - self.assert_called('POST', '/volumes/1234/action', body=expected) - - def test_reset_state_with_attach_status(self): - self.run_command('reset-state --attach-status detached 1234') - expected = {'os-reset_status': {'attach_status': 'detached'}} - self.assert_called('POST', '/volumes/1234/action', body=expected) - - def test_reset_state_with_attach_status_with_flag(self): - self.run_command('reset-state --state in-use ' - '--attach-status attached 1234') - expected = {'os-reset_status': {'status': 'in-use', - 'attach_status': 'attached'}} - self.assert_called('POST', '/volumes/1234/action', body=expected) - - def test_reset_state_with_reset_migration_status(self): - self.run_command('reset-state --reset-migration-status 1234') - expected = {'os-reset_status': {'migration_status': 'none'}} - self.assert_called('POST', '/volumes/1234/action', body=expected) - - def test_reset_state_multiple(self): - self.run_command('reset-state 1234 5678 --state error') - expected = {'os-reset_status': {'status': 'error'}} - self.assert_called_anytime('POST', '/volumes/1234/action', - body=expected) - self.assert_called_anytime('POST', '/volumes/5678/action', - body=expected) - - def test_reset_state_two_with_one_nonexistent(self): - cmd = 'reset-state 1234 123456789' - self.assertRaises(exceptions.CommandError, self.run_command, cmd) - expected = {'os-reset_status': {'status': 'available'}} - self.assert_called_anytime('POST', '/volumes/1234/action', - body=expected) - - def test_reset_state_one_with_one_nonexistent(self): - cmd = 'reset-state 123456789' - self.assertRaises(exceptions.CommandError, self.run_command, cmd) - - def test_snapshot_reset_state(self): - self.run_command('snapshot-reset-state 1234') - expected = {'os-reset_status': {'status': 'available'}} - self.assert_called('POST', '/snapshots/1234/action', body=expected) - - def test_snapshot_reset_state_with_flag(self): - self.run_command('snapshot-reset-state --state error 1234') - expected = {'os-reset_status': {'status': 'error'}} - self.assert_called('POST', '/snapshots/1234/action', body=expected) - - def test_snapshot_reset_state_multiple(self): - self.run_command('snapshot-reset-state 1234 5678') - expected = {'os-reset_status': {'status': 'available'}} - self.assert_called_anytime('POST', '/snapshots/1234/action', - body=expected) - self.assert_called_anytime('POST', '/snapshots/5678/action', - body=expected) - - def test_backup_reset_state(self): - self.run_command('backup-reset-state 1234') - expected = {'os-reset_status': {'status': 'available'}} - self.assert_called('POST', '/backups/1234/action', body=expected) - - def test_backup_reset_state_with_flag(self): - self.run_command('backup-reset-state --state error 1234') - expected = {'os-reset_status': {'status': 'error'}} - self.assert_called('POST', '/backups/1234/action', body=expected) - - def test_backup_reset_state_multiple(self): - self.run_command('backup-reset-state 1234 5678') - expected = {'os-reset_status': {'status': 'available'}} - self.assert_called_anytime('POST', '/backups/1234/action', - body=expected) - self.assert_called_anytime('POST', '/backups/5678/action', - body=expected) - - def test_type_list(self): - self.run_command('type-list') - self.assert_called_anytime('GET', '/types?is_public=None') - - def test_type_show(self): - self.run_command('type-show 1') - self.assert_called('GET', '/types/1') - - def test_type_create(self): - self.run_command('type-create test-type-1') - self.assert_called('POST', '/types') - - def test_type_create_public(self): - expected = {'volume_type': {'name': 'test-type-1', - 'description': 'test_type-1-desc', - 'os-volume-type-access:is_public': True}} - self.run_command('type-create test-type-1 ' - '--description=test_type-1-desc ' - '--is-public=True') - self.assert_called('POST', '/types', body=expected) - - def test_type_create_private(self): - expected = {'volume_type': {'name': 'test-type-3', - 'description': 'test_type-3-desc', - 'os-volume-type-access:is_public': False}} - self.run_command('type-create test-type-3 ' - '--description=test_type-3-desc ' - '--is-public=False') - self.assert_called('POST', '/types', body=expected) - - def test_type_create_with_invalid_bool(self): - self.assertRaises(ValueError, - self.run_command, - ('type-create test-type-3 ' - '--description=test_type-3-desc ' - '--is-public=invalid_bool')) - - def test_type_update(self): - expected = {'volume_type': {'name': 'test-type-1', - 'description': 'test_type-1-desc', - 'is_public': False}} - self.run_command('type-update --name test-type-1 ' - '--description=test_type-1-desc ' - '--is-public=False 1') - self.assert_called('PUT', '/types/1', body=expected) - - def test_type_update_with_invalid_bool(self): - self.assertRaises(ValueError, - self.run_command, - 'type-update --name test-type-1 ' - '--description=test_type-1-desc ' - '--is-public=invalid_bool 1') - - def test_type_update_without_args(self): - self.assertRaises(exceptions.CommandError, self.run_command, - 'type-update 1') - - def test_type_access_list(self): - self.run_command('type-access-list --volume-type 3') - self.assert_called('GET', '/types/3/os-volume-type-access') - - def test_type_access_add_project(self): - expected = {'addProjectAccess': {'project': '101'}} - self.run_command('type-access-add --volume-type 3 --project-id 101') - self.assert_called_anytime('GET', '/types/3') - self.assert_called('POST', '/types/3/action', - body=expected) - - def test_type_access_add_project_by_name(self): - expected = {'addProjectAccess': {'project': '101'}} - with mock.patch('cinderclient.utils.find_resource') as mock_find: - mock_find.return_value = '3' - self.run_command('type-access-add --volume-type type_name \ - --project-id 101') - mock_find.assert_called_once_with(mock.ANY, 'type_name') - self.assert_called('POST', '/types/3/action', - body=expected) - - def test_type_access_remove_project(self): - expected = {'removeProjectAccess': {'project': '101'}} - self.run_command('type-access-remove ' - '--volume-type 3 --project-id 101') - self.assert_called_anytime('GET', '/types/3') - self.assert_called('POST', '/types/3/action', - body=expected) - - def test_type_delete(self): - self.run_command('type-delete 1') - self.assert_called('DELETE', '/types/1') - - def test_type_delete_multiple(self): - self.run_command('type-delete 1 3') - self.assert_called_anytime('DELETE', '/types/1') - self.assert_called('DELETE', '/types/3') - - def test_type_delete_by_name(self): - self.run_command('type-delete test-type-1') - self.assert_called_anytime('GET', '/types?is_public=None') - self.assert_called('DELETE', '/types/1') - - def test_encryption_type_list(self): - """ - Test encryption-type-list shell command. - - Verify a series of GET requests are made: - - one to get the volume type list information - - one per volume type to retrieve the encryption type information - """ - self.run_command('encryption-type-list') - self.assert_called_anytime('GET', '/types?is_public=None') - self.assert_called_anytime('GET', '/types/1/encryption') - self.assert_called_anytime('GET', '/types/2/encryption') - - def test_encryption_type_show(self): - """ - Test encryption-type-show shell command. - - Verify two GET requests are made per command invocation: - - one to get the volume type information - - one to get the encryption type information - """ - self.run_command('encryption-type-show 1') - self.assert_called('GET', '/types/1/encryption') - self.assert_called_anytime('GET', '/types/1') - - def test_encryption_type_create(self): - """ - Test encryption-type-create shell command. - - Verify GET and POST requests are made per command invocation: - - one GET request to retrieve the relevant volume type information - - one POST request to create the new encryption type - """ - - expected = {'encryption': {'cipher': None, 'key_size': None, - 'provider': 'TestProvider', - 'control_location': 'front-end'}} - self.run_command('encryption-type-create 2 TestProvider') - self.assert_called('POST', '/types/2/encryption', body=expected) - self.assert_called_anytime('GET', '/types/2') - - @ddt.data('--key-size 512 --control-location front-end', - '--key_size 512 --control_location front-end') # old style - def test_encryption_type_create_with_args(self, arg): - expected = {'encryption': {'cipher': None, - 'key_size': 512, - 'provider': 'TestProvider', - 'control_location': 'front-end'}} - self.run_command('encryption-type-create 2 TestProvider ' + arg) - self.assert_called('POST', '/types/2/encryption', body=expected) - self.assert_called_anytime('GET', '/types/2') - - def test_encryption_type_update(self): - """ - Test encryption-type-update shell command. - - Verify two GETs/one PUT requests are made per command invocation: - - one GET request to retrieve the relevant volume type information - - one GET request to retrieve the relevant encryption type information - - one PUT request to update the encryption type information - Verify that the PUT request correctly parses encryption-type-update - parameters from sys.argv - """ - parameters = {'--provider': 'EncryptionProvider', '--cipher': 'des', - '--key-size': 1024, '--control-location': 'back-end'} - - # Construct the argument string for the update call and the - # expected encryption-type body that should be produced by it - args = ' '.join(['%s %s' % (k, v) for k, v in parameters.items()]) - expected = {'encryption': {'provider': 'EncryptionProvider', - 'cipher': 'des', - 'key_size': 1024, - 'control_location': 'back-end'}} - - self.run_command('encryption-type-update 1 %s' % args) - self.assert_called('GET', '/types/1/encryption') - self.assert_called_anytime('GET', '/types/1') - self.assert_called_anytime('PUT', '/types/1/encryption/provider', - body=expected) - - def test_encryption_type_update_no_attributes(self): - """ - Test encryption-type-update shell command. - - Verify two GETs/one PUT requests are made per command invocation: - - one GET request to retrieve the relevant volume type information - - one GET request to retrieve the relevant encryption type information - - one PUT request to update the encryption type information - """ - expected = {'encryption': {}} - self.run_command('encryption-type-update 1') - self.assert_called('GET', '/types/1/encryption') - self.assert_called_anytime('GET', '/types/1') - self.assert_called_anytime('PUT', '/types/1/encryption/provider', - body=expected) - - def test_encryption_type_update_default_attributes(self): - """ - Test encryption-type-update shell command. - - Verify two GETs/one PUT requests are made per command invocation: - - one GET request to retrieve the relevant volume type information - - one GET request to retrieve the relevant encryption type information - - one PUT request to update the encryption type information - Verify that the encryption-type body produced contains default None - values for all specified parameters. - """ - parameters = ['--cipher', '--key-size'] - - # Construct the argument string for the update call and the - # expected encryption-type body that should be produced by it - args = ' '.join(['%s' % (p) for p in parameters]) - expected_pairs = [(k.strip('-').replace('-', '_'), None) for k in - parameters] - expected = {'encryption': dict(expected_pairs)} - - self.run_command('encryption-type-update 1 %s' % args) - self.assert_called('GET', '/types/1/encryption') - self.assert_called_anytime('GET', '/types/1') - self.assert_called_anytime('PUT', '/types/1/encryption/provider', - body=expected) - - def test_encryption_type_delete(self): - """ - Test encryption-type-delete shell command. - - Verify one GET/one DELETE requests are made per command invocation: - - one GET request to retrieve the relevant volume type information - - one DELETE request to delete the encryption type information - """ - self.run_command('encryption-type-delete 1') - self.assert_called('DELETE', '/types/1/encryption/provider') - self.assert_called_anytime('GET', '/types/1') - - def test_migrate_volume(self): - self.run_command('migrate 1234 fakehost --force-host-copy=True ' - '--lock-volume=True') - expected = {'os-migrate_volume': {'force_host_copy': 'True', - 'lock_volume': 'True', - 'host': 'fakehost'}} - self.assert_called('POST', '/volumes/1234/action', body=expected) - - def test_migrate_volume_bool_force(self): - self.run_command('migrate 1234 fakehost --force-host-copy ' - '--lock-volume') - expected = {'os-migrate_volume': {'force_host_copy': True, - 'lock_volume': True, - 'host': 'fakehost'}} - self.assert_called('POST', '/volumes/1234/action', body=expected) - - def test_migrate_volume_bool_force_false(self): - # Set both --force-host-copy and --lock-volume to False. - self.run_command('migrate 1234 fakehost --force-host-copy=False ' - '--lock-volume=False') - expected = {'os-migrate_volume': {'force_host_copy': 'False', - 'lock_volume': 'False', - 'host': 'fakehost'}} - self.assert_called('POST', '/volumes/1234/action', body=expected) - - # Do not set the values to --force-host-copy and --lock-volume. - self.run_command('migrate 1234 fakehost') - expected = {'os-migrate_volume': {'force_host_copy': False, - 'lock_volume': False, - 'host': 'fakehost'}} - self.assert_called('POST', '/volumes/1234/action', - body=expected) - - def test_snapshot_metadata_set(self): - self.run_command('snapshot-metadata 1234 set key1=val1 key2=val2') - self.assert_called('POST', '/snapshots/1234/metadata', - {'metadata': {'key1': 'val1', 'key2': 'val2'}}) - - def test_snapshot_metadata_unset_dict(self): - self.run_command('snapshot-metadata 1234 unset key1=val1 key2=val2') - self.assert_called_anytime('DELETE', '/snapshots/1234/metadata/key1') - self.assert_called_anytime('DELETE', '/snapshots/1234/metadata/key2') - - def test_snapshot_metadata_unset_keys(self): - self.run_command('snapshot-metadata 1234 unset key1 key2') - self.assert_called_anytime('DELETE', '/snapshots/1234/metadata/key1') - self.assert_called_anytime('DELETE', '/snapshots/1234/metadata/key2') - - def test_volume_metadata_update_all(self): - self.run_command('metadata-update-all 1234 key1=val1 key2=val2') - self.assert_called('PUT', '/volumes/1234/metadata', - {'metadata': {'key1': 'val1', 'key2': 'val2'}}) - - def test_snapshot_metadata_update_all(self): - self.run_command('snapshot-metadata-update-all\ - 1234 key1=val1 key2=val2') - self.assert_called('PUT', '/snapshots/1234/metadata', - {'metadata': {'key1': 'val1', 'key2': 'val2'}}) - - def test_readonly_mode_update(self): - self.run_command('readonly-mode-update 1234 True') - expected = {'os-update_readonly_flag': {'readonly': True}} - self.assert_called('POST', '/volumes/1234/action', body=expected) - - self.run_command('readonly-mode-update 1234 False') - expected = {'os-update_readonly_flag': {'readonly': False}} - self.assert_called('POST', '/volumes/1234/action', body=expected) - - def test_service_disable(self): - self.run_command('service-disable host cinder-volume') - self.assert_called('PUT', '/os-services/disable', - {"binary": "cinder-volume", "host": "host"}) - - def test_services_disable_with_reason(self): - cmd = 'service-disable host cinder-volume --reason no_reason' - self.run_command(cmd) - body = {'host': 'host', 'binary': 'cinder-volume', - 'disabled_reason': 'no_reason'} - self.assert_called('PUT', '/os-services/disable-log-reason', body) - - def test_service_enable(self): - self.run_command('service-enable host cinder-volume') - self.assert_called('PUT', '/os-services/enable', - {"binary": "cinder-volume", "host": "host"}) - - def test_retype_with_policy(self): - self.run_command('retype 1234 foo --migration-policy=on-demand') - expected = {'os-retype': {'new_type': 'foo', - 'migration_policy': 'on-demand'}} - self.assert_called('POST', '/volumes/1234/action', body=expected) - - def test_retype_default_policy(self): - self.run_command('retype 1234 foo') - expected = {'os-retype': {'new_type': 'foo', - 'migration_policy': 'never'}} - self.assert_called('POST', '/volumes/1234/action', body=expected) - - def test_snapshot_delete(self): - """Tests delete snapshot without force parameter""" - self.run_command('snapshot-delete 1234') - self.assert_called('DELETE', '/snapshots/1234') - - def test_snapshot_delete_multiple(self): - """Tests delete multiple snapshots without force parameter""" - self.run_command('snapshot-delete 5678 1234') - self.assert_called_anytime('DELETE', '/snapshots/5678') - self.assert_called('DELETE', '/snapshots/1234') - - def test_force_snapshot_delete(self): - """Tests delete snapshot with default force parameter value(True)""" - self.run_command('snapshot-delete 1234 --force') - expected_body = {'os-force_delete': None} - self.assert_called('POST', - '/snapshots/1234/action', - expected_body) - - def test_force_snapshot_delete_multiple(self): - """ - Tests delete multiple snapshots with force parameter - - Snapshot delete with force parameter allows deleting snapshot of a - volume when its status is other than "available" or "error". - """ - self.run_command('snapshot-delete 5678 1234 --force') - expected_body = {'os-force_delete': None} - self.assert_called_anytime('POST', - '/snapshots/5678/action', - expected_body) - self.assert_called_anytime('POST', - '/snapshots/1234/action', - expected_body) - - def test_quota_delete(self): - self.run_command('quota-delete 1234') - self.assert_called('DELETE', '/os-quota-sets/1234') - - def test_volume_manage(self): - self.run_command('manage host1 some_fake_name ' - '--name foo --description bar ' - '--volume-type baz --availability-zone az ' - '--metadata k1=v1 k2=v2') - expected = {'volume': {'host': 'host1', - 'ref': {'source-name': 'some_fake_name'}, - 'name': 'foo', - 'description': 'bar', - 'volume_type': 'baz', - 'availability_zone': 'az', - 'metadata': {'k1': 'v1', 'k2': 'v2'}, - 'bootable': False}} - self.assert_called_anytime('POST', '/os-volume-manage', body=expected) - - def test_volume_manage_bootable(self): - """ - Tests the --bootable option - - If this flag is specified, then the resulting POST should contain - bootable: True. - """ - self.run_command('manage host1 some_fake_name ' - '--name foo --description bar --bootable ' - '--volume-type baz --availability-zone az ' - '--metadata k1=v1 k2=v2') - expected = {'volume': {'host': 'host1', - 'ref': {'source-name': 'some_fake_name'}, - 'name': 'foo', - 'description': 'bar', - 'volume_type': 'baz', - 'availability_zone': 'az', - 'metadata': {'k1': 'v1', 'k2': 'v2'}, - 'bootable': True}} - self.assert_called_anytime('POST', '/os-volume-manage', body=expected) - - def test_volume_manage_source_name(self): - """ - Tests the --source-name option. - - Checks that the --source-name option correctly updates the - ref structure that is passed in the HTTP POST - """ - self.run_command('manage host1 VolName ' - '--name foo --description bar ' - '--volume-type baz --availability-zone az ' - '--metadata k1=v1 k2=v2') - expected = {'volume': {'host': 'host1', - 'ref': {'source-name': 'VolName'}, - 'name': 'foo', - 'description': 'bar', - 'volume_type': 'baz', - 'availability_zone': 'az', - 'metadata': {'k1': 'v1', 'k2': 'v2'}, - 'bootable': False}} - self.assert_called_anytime('POST', '/os-volume-manage', body=expected) - - def test_volume_manage_source_id(self): - """ - Tests the --source-id option. - - Checks that the --source-id option correctly updates the - ref structure that is passed in the HTTP POST - """ - self.run_command('manage host1 1234 ' - '--id-type source-id ' - '--name foo --description bar ' - '--volume-type baz --availability-zone az ' - '--metadata k1=v1 k2=v2') - expected = {'volume': {'host': 'host1', - 'ref': {'source-id': '1234'}, - 'name': 'foo', - 'description': 'bar', - 'volume_type': 'baz', - 'availability_zone': 'az', - 'metadata': {'k1': 'v1', 'k2': 'v2'}, - 'bootable': False}} - self.assert_called_anytime('POST', '/os-volume-manage', body=expected) - - def test_volume_manageable_list(self): - self.run_command('manageable-list fakehost') - self.assert_called('GET', '/os-volume-manage/detail?host=fakehost') - - def test_volume_manageable_list_details(self): - self.run_command('manageable-list fakehost --detailed True') - self.assert_called('GET', '/os-volume-manage/detail?host=fakehost') - - def test_volume_manageable_list_no_details(self): - self.run_command('manageable-list fakehost --detailed False') - self.assert_called('GET', '/os-volume-manage?host=fakehost') - - def test_volume_unmanage(self): - self.run_command('unmanage 1234') - self.assert_called('POST', '/volumes/1234/action', - body={'os-unmanage': None}) - - def test_create_snapshot_from_volume_with_metadata(self): - """ - Tests create snapshot with --metadata parameter. - - Checks metadata params are set during create snapshot - when metadata is passed - """ - expected = {'snapshot': {'volume_id': 1234, - 'metadata': {'k1': 'v1', - 'k2': 'v2'}}} - self.run_command('snapshot-create 1234 --metadata k1=v1 k2=v2 ' - '--force=True') - self.assert_called_anytime('POST', '/snapshots', partial_body=expected) - - def test_create_snapshot_from_volume_with_metadata_bool_force(self): - """ - Tests create snapshot with --metadata parameter. - - Checks metadata params are set during create snapshot - when metadata is passed - """ - expected = {'snapshot': {'volume_id': 1234, - 'metadata': {'k1': 'v1', - 'k2': 'v2'}}} - self.run_command('snapshot-create 1234 --metadata k1=v1 k2=v2 --force') - self.assert_called_anytime('POST', '/snapshots', partial_body=expected) - - def test_get_pools(self): - self.run_command('get-pools') - self.assert_called('GET', '/scheduler-stats/get_pools') - - def test_get_pools_detail(self): - self.run_command('get-pools --detail') - self.assert_called('GET', '/scheduler-stats/get_pools?detail=True') - - def test_list_transfer(self): - self.run_command('transfer-list') - self.assert_called('GET', '/os-volume-transfer/detail?all_tenants=0') - - def test_list_transfer_all_tenants(self): - self.run_command('transfer-list --all-tenants=1') - self.assert_called('GET', '/os-volume-transfer/detail?all_tenants=1') - - def test_consistencygroup_update(self): - self.run_command('consisgroup-update ' - '--name cg2 --description desc2 ' - '--add-volumes uuid1,uuid2 ' - '--remove-volumes uuid3,uuid4 ' - '1234') - expected = {'consistencygroup': {'name': 'cg2', - 'description': 'desc2', - 'add_volumes': 'uuid1,uuid2', - 'remove_volumes': 'uuid3,uuid4'}} - self.assert_called('PUT', '/consistencygroups/1234', - body=expected) - - def test_consistencygroup_update_invalid_args(self): - self.assertRaises(exceptions.ClientException, - self.run_command, - 'consisgroup-update 1234') - - def test_consistencygroup_create_from_src_snap(self): - self.run_command('consisgroup-create-from-src ' - '--name cg ' - '--cgsnapshot 1234') - expected = { - 'consistencygroup-from-src': { - 'name': 'cg', - 'cgsnapshot_id': '1234', - 'description': None, - 'user_id': None, - 'project_id': None, - 'status': 'creating', - 'source_cgid': None - } - } - self.assert_called('POST', '/consistencygroups/create_from_src', - expected) - - def test_consistencygroup_create_from_src_cg(self): - self.run_command('consisgroup-create-from-src ' - '--name cg ' - '--source-cg 1234') - expected = { - 'consistencygroup-from-src': { - 'name': 'cg', - 'cgsnapshot_id': None, - 'description': None, - 'user_id': None, - 'project_id': None, - 'status': 'creating', - 'source_cgid': '1234' - } - } - self.assert_called('POST', '/consistencygroups/create_from_src', - expected) - - def test_consistencygroup_create_from_src_fail_no_snap_cg(self): - self.assertRaises(exceptions.ClientException, - self.run_command, - 'consisgroup-create-from-src ' - '--name cg') - - def test_consistencygroup_create_from_src_fail_both_snap_cg(self): - self.assertRaises(exceptions.ClientException, - self.run_command, - 'consisgroup-create-from-src ' - '--name cg ' - '--cgsnapshot 1234 ' - '--source-cg 5678') - - def test_set_image_metadata(self): - self.run_command('image-metadata 1234 set key1=val1') - expected = {"os-set_image_metadata": {"metadata": {"key1": "val1"}}} - self.assert_called('POST', '/volumes/1234/action', - body=expected) - - def test_unset_image_metadata(self): - self.run_command('image-metadata 1234 unset key1') - expected = {"os-unset_image_metadata": {"key": "key1"}} - self.assert_called('POST', '/volumes/1234/action', - body=expected) - - def _get_params_from_stack(self, pos=-1): - method, url = self.shell.cs.client.callstack[pos][0:2] - path, query = parse.splitquery(url) - params = parse.parse_qs(query) - return path, params - - def test_backup_list_all_tenants(self): - self.run_command('backup-list --all-tenants=1 --name=bc ' - '--status=available --volume-id=1234') - expected = { - 'all_tenants': ['1'], - 'name': ['bc'], - 'status': ['available'], - 'volume_id': ['1234'], - } - - path, params = self._get_params_from_stack() - - self.assertEqual('/backups/detail', path) - self.assertEqual(4, len(params)) - - for k in params.keys(): - self.assertEqual(expected[k], params[k]) - - def test_backup_list_volume_id(self): - self.run_command('backup-list --volume-id=1234') - self.assert_called('GET', '/backups/detail?volume_id=1234') - - def test_backup_list(self): - self.run_command('backup-list') - self.assert_called('GET', '/backups/detail') - - @mock.patch("cinderclient.utils.print_list") - def test_backup_list_sort(self, mock_print_list): - self.run_command('backup-list --sort id') - self.assert_called('GET', '/backups/detail?sort=id') - columns = ['ID', 'Volume ID', 'Status', 'Name', 'Size', 'Object Count', - 'Container'] - mock_print_list.assert_called_once_with(mock.ANY, columns, - sortby_index=None) - - def test_backup_list_data_timestamp(self): - self.run_command('backup-list --sort data_timestamp') - self.assert_called('GET', '/backups/detail?sort=data_timestamp') - - def test_get_capabilities(self): - self.run_command('get-capabilities host') - self.assert_called('GET', '/capabilities/host') - - def test_image_metadata_show(self): - # since the request is not actually sent to cinder API but is - # calling the method in :class:`v2.fakes.FakeHTTPClient` instead. - # Thus, ignore any exception which is false negative compare - # with real API call. - try: - self.run_command('image-metadata-show 1234') - except Exception: - pass - expected = {"os-show_image_metadata": None} - self.assert_called('POST', '/volumes/1234/action', body=expected) - - def test_snapshot_manage(self): - self.run_command('snapshot-manage 1234 some_fake_name ' - '--name foo --description bar ' - '--metadata k1=v1 k2=v2') - expected = {'snapshot': {'volume_id': 1234, - 'ref': {'source-name': 'some_fake_name'}, - 'name': 'foo', - 'description': 'bar', - 'metadata': {'k1': 'v1', 'k2': 'v2'} - }} - self.assert_called_anytime('POST', '/os-snapshot-manage', - body=expected) - - def test_snapshot_manageable_list(self): - self.run_command('snapshot-manageable-list fakehost') - self.assert_called('GET', '/os-snapshot-manage/detail?host=fakehost') - - def test_snapshot_manageable_list_details(self): - self.run_command('snapshot-manageable-list fakehost --detailed True') - self.assert_called('GET', '/os-snapshot-manage/detail?host=fakehost') - - def test_snapshot_manageable_list_no_details(self): - self.run_command('snapshot-manageable-list fakehost --detailed False') - self.assert_called('GET', '/os-snapshot-manage?host=fakehost') - - def test_snapshot_unmanage(self): - self.run_command('snapshot-unmanage 1234') - self.assert_called('POST', '/snapshots/1234/action', - body={'os-unmanage': None}) - - def test_extra_specs_list(self): - self.run_command('extra-specs-list') - self.assert_called('GET', '/types?is_public=None') - - def test_quota_class_show(self): - self.run_command('quota-class-show test') - self.assert_called('GET', '/os-quota-class-sets/test') - - def test_quota_class_update(self): - expected = {'quota_class_set': {'volumes': 2, - 'snapshots': 2, - 'gigabytes': 1, - 'backups': 1, - 'backup_gigabytes': 1, - 'per_volume_gigabytes': 1}} - self.run_command('quota-class-update test ' - '--volumes 2 ' - '--snapshots 2 ' - '--gigabytes 1 ' - '--backups 1 ' - '--backup-gigabytes 1 ' - '--per-volume-gigabytes 1') - self.assert_called('PUT', '/os-quota-class-sets/test', body=expected) - - def test_translate_attachments(self): - attachment_id = 'aaaa' - server_id = 'bbbb' - obj_id = 'cccc' - info = { - 'attachments': [{ - 'attachment_id': attachment_id, - 'id': obj_id, - 'server_id': server_id}] - } - - new_info = test_shell._translate_attachments(info) - - self.assertEqual(attachment_id, new_info['attachment_ids'][0]) - self.assertEqual(server_id, new_info['attached_servers'][0]) - self.assertNotIn('id', new_info) diff --git a/cinderclient/tests/unit/v2/test_availability_zone.py b/cinderclient/tests/unit/v3/test_availability_zone.py similarity index 96% rename from cinderclient/tests/unit/v2/test_availability_zone.py rename to cinderclient/tests/unit/v3/test_availability_zone.py index e9b5d020e..ebacf83ae 100644 --- a/cinderclient/tests/unit/v2/test_availability_zone.py +++ b/cinderclient/tests/unit/v3/test_availability_zone.py @@ -14,8 +14,8 @@ # License for the specific language governing permissions and limitations # under the License. -from cinderclient.v2 import availability_zones -from cinderclient.v2 import shell +from cinderclient.v3 import availability_zones +from cinderclient.v3 import shell from cinderclient.tests.unit.fixture_data import availability_zones as azfixture # noqa from cinderclient.tests.unit.fixture_data import client @@ -24,7 +24,7 @@ class AvailabilityZoneTest(utils.FixturedTestCase): - client_fixture_class = client.V2 + client_fixture_class = client.V3 data_fixture_class = azfixture.Fixture def _assertZone(self, zone, name, status): diff --git a/cinderclient/v3/availability_zones.py b/cinderclient/v3/availability_zones.py index 3b99540c6..db6b8da26 100644 --- a/cinderclient/v3/availability_zones.py +++ b/cinderclient/v3/availability_zones.py @@ -16,4 +16,27 @@ """Availability Zone interface (v3 extension)""" -from cinderclient.v2.availability_zones import * # noqa +from cinderclient import base + + +class AvailabilityZone(base.Resource): + NAME_ATTR = 'display_name' + + def __repr__(self): + return "" % self.zoneName + + +class AvailabilityZoneManager(base.ManagerWithFind): + """Manage :class:`AvailabilityZone` resources.""" + resource_class = AvailabilityZone + + def list(self, detailed=False): + """Lists all availability zones. + + :rtype: list of :class:`AvailabilityZone` + """ + if detailed is True: + return self._list("/os-availability-zone/detail", + "availabilityZoneInfo") + else: + return self._list("/os-availability-zone", "availabilityZoneInfo") diff --git a/cinderclient/v3/shell.py b/cinderclient/v3/shell.py index 6d7d1eb3f..afbe369c7 100644 --- a/cinderclient/v3/shell.py +++ b/cinderclient/v3/shell.py @@ -27,8 +27,8 @@ from cinderclient import shell_utils from cinderclient import utils -from cinderclient.v2.shell import * # noqa -from cinderclient.v2.shell import CheckSizeArgForCreate +from cinderclient.v3.shell_base import * # noqa +from cinderclient.v3.shell_base import CheckSizeArgForCreate FILTER_DEPRECATED = ("This option is deprecated and will be removed in " "newer release. Please use '--filters' option which " diff --git a/cinderclient/v2/shell.py b/cinderclient/v3/shell_base.py similarity index 100% rename from cinderclient/v2/shell.py rename to cinderclient/v3/shell_base.py From d714249ca9e95297f5e53c9dcc0af9ba58d0ab6d Mon Sep 17 00:00:00 2001 From: Rajat Dhasmana Date: Mon, 29 Mar 2021 06:31:31 -0400 Subject: [PATCH 077/159] Make instance_uuid optional in attachment create Cinder and cinderclient assumes an attachment create request will always contain instance_uuid. This is not true when glance calls cinder for attachment in glance cinder configuration. This patch (along with the cinder patch) make the instance_uuid optional and allow glance to do attachments without passing instance_uuid. Change-Id: Ifbaca4aa87d890bc5130069638d42665b914b378 --- cinderclient/tests/unit/v3/fakes.py | 14 ++++++- .../tests/unit/v3/test_attachments.py | 11 +++++ cinderclient/tests/unit/v3/test_shell.py | 40 +++++++++++++++++++ cinderclient/v3/attachments.py | 5 ++- cinderclient/v3/shell.py | 2 + ...e-optional-server-id-9299d9da2b62b263.yaml | 10 +++++ 6 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/attachment-create-optional-server-id-9299d9da2b62b263.yaml diff --git a/cinderclient/tests/unit/v3/fakes.py b/cinderclient/tests/unit/v3/fakes.py index 7647fb39f..ffdfeb75e 100644 --- a/cinderclient/tests/unit/v3/fakes.py +++ b/cinderclient/tests/unit/v3/fakes.py @@ -29,6 +29,16 @@ 'instance': 'e84fda45-4de4-4ce4-8f39-fc9d3b0aa05e', 'volume_id': '557ad76c-ce54-40a3-9e91-c40d21665cc3', }} +fake_attachment_without_instance_id = {'attachment': { + 'status': 'reserved', + 'detached_at': '', + 'connection_info': {}, + 'attached_at': '', + 'attach_mode': None, + 'id': 'a232e9ae', + 'instance': None, + 'volume_id': '557ad76c-ce54-40a3-9e91-c40d21665cc3', }} + fake_attachment_list = {'attachments': [ {'instance': 'instance_1', 'name': 'attachment-1', @@ -297,7 +307,9 @@ def put_backups_1234(self, **kw): # def post_attachments(self, **kw): - return (200, {}, fake_attachment) + if kw['body']['attachment'].get('instance_uuid'): + return (200, {}, fake_attachment) + return (200, {}, fake_attachment_without_instance_id) def get_attachments(self, **kw): return (200, {}, fake_attachment_list) diff --git a/cinderclient/tests/unit/v3/test_attachments.py b/cinderclient/tests/unit/v3/test_attachments.py index 1802334ec..acf064639 100644 --- a/cinderclient/tests/unit/v3/test_attachments.py +++ b/cinderclient/tests/unit/v3/test_attachments.py @@ -31,6 +31,17 @@ def test_create_attachment(self): cs.assert_called('POST', '/attachments') self.assertEqual(fakes.fake_attachment['attachment'], att) + def test_create_attachment_without_instance_uuid(self): + cs = fakes.FakeClient(api_versions.APIVersion('3.27')) + att = cs.attachments.create( + 'e84fda45-4de4-4ce4-8f39-fc9d3b0aa05e', + {}, + None, + 'null') + cs.assert_called('POST', '/attachments') + self.assertEqual( + fakes.fake_attachment_without_instance_id['attachment'], att) + def test_complete_attachment(self): cs = fakes.FakeClient(api_versions.APIVersion('3.44')) att = cs.attachments.complete('a232e9ae') diff --git a/cinderclient/tests/unit/v3/test_shell.py b/cinderclient/tests/unit/v3/test_shell.py index 756f51201..d0f1bfbe6 100644 --- a/cinderclient/tests/unit/v3/test_shell.py +++ b/cinderclient/tests/unit/v3/test_shell.py @@ -388,6 +388,25 @@ def test_list_availability_zone(self): {'cmd': 'abc 1233', 'body': {'instance_uuid': '1233', 'connector': {}, + 'volume_uuid': '1234'}}, + {'cmd': '1234', + 'body': {'connector': {}, + 'volume_uuid': '1234'}}, + {'cmd': '1234 ' + '--connect True ' + '--ip 10.23.12.23 --host server01 ' + '--platform x86_xx ' + '--ostype 123 ' + '--multipath true ' + '--mountpoint /123 ' + '--initiator aabbccdd', + 'body': {'connector': {'ip': '10.23.12.23', + 'host': 'server01', + 'os_type': '123', + 'multipath': 'true', + 'mountpoint': '/123', + 'initiator': 'aabbccdd', + 'platform': 'x86_xx'}, 'volume_uuid': '1234'}}) @mock.patch('cinderclient.utils.find_resource') @ddt.unpack @@ -429,6 +448,27 @@ def test_attachment_create(self, mock_find_volume, cmd, body): 'body': {'instance_uuid': '1233', 'connector': {}, 'volume_uuid': '1234', + 'mode': 'ro'}}, + {'cmd': '1234', + 'body': {'connector': {}, + 'volume_uuid': '1234', + 'mode': 'ro'}}, + {'cmd': '1234 ' + '--connect True ' + '--ip 10.23.12.23 --host server01 ' + '--platform x86_xx ' + '--ostype 123 ' + '--multipath true ' + '--mountpoint /123 ' + '--initiator aabbccdd', + 'body': {'connector': {'ip': '10.23.12.23', + 'host': 'server01', + 'os_type': '123', + 'multipath': 'true', + 'mountpoint': '/123', + 'initiator': 'aabbccdd', + 'platform': 'x86_xx'}, + 'volume_uuid': '1234', 'mode': 'ro'}}) @mock.patch('cinderclient.utils.find_resource') @ddt.unpack diff --git a/cinderclient/v3/attachments.py b/cinderclient/v3/attachments.py index e1e929003..506796270 100644 --- a/cinderclient/v3/attachments.py +++ b/cinderclient/v3/attachments.py @@ -27,11 +27,12 @@ class VolumeAttachmentManager(base.ManagerWithFind): resource_class = VolumeAttachment @api_versions.wraps('3.27') - def create(self, volume_id, connector, instance_id, mode='null'): + def create(self, volume_id, connector, instance_id=None, mode='null'): """Create a attachment for specified volume.""" body = {'attachment': {'volume_uuid': volume_id, - 'instance_uuid': instance_id, 'connector': connector}} + if instance_id: + body['attachment']['instance_uuid'] = instance_id if self.api_version >= api_versions.APIVersion("3.54"): if mode and mode != 'null': body['attachment']['mode'] = mode diff --git a/cinderclient/v3/shell.py b/cinderclient/v3/shell.py index 6d7d1eb3f..9865d38cf 100644 --- a/cinderclient/v3/shell.py +++ b/cinderclient/v3/shell.py @@ -2297,6 +2297,8 @@ def do_attachment_show(cs, args): help='Name or ID of volume or volumes to attach.') @utils.arg('server_id', metavar='', + nargs='?', + default=None, help='ID of server attaching to.') @utils.arg('--connect', metavar='', diff --git a/releasenotes/notes/attachment-create-optional-server-id-9299d9da2b62b263.yaml b/releasenotes/notes/attachment-create-optional-server-id-9299d9da2b62b263.yaml new file mode 100644 index 000000000..11935820b --- /dev/null +++ b/releasenotes/notes/attachment-create-optional-server-id-9299d9da2b62b263.yaml @@ -0,0 +1,10 @@ +--- +fixes: + - | + When attaching to a host, we don't need a server id + so it shouldn't be mandatory to be supplied with + attachment-create operation. + The server_id parameter is made optional so we can + create attachments without passing it. + The backward compatibility is maintained so we can pass + it like how we currently do if required. \ No newline at end of file From cb5235250cc10957b4392764b1dc3a6757784da5 Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Mon, 24 May 2021 14:55:34 -0400 Subject: [PATCH 078/159] Remove v2 classes Remove all cinderclient.v2 classes, mostly incorporating them into their v3 counterparts and updating the tests and test fixtures. Depends-on: https://review.opendev.org/c/openstack/horizon/+/800814 Change-Id: I335db5c1799edb2273bf8bfc9e1bc9de404a4ba5 --- .../tests/unit/fixture_data/client.py | 16 -- cinderclient/tests/unit/test_base.py | 4 +- cinderclient/tests/unit/test_client.py | 1 - cinderclient/tests/unit/test_utils.py | 4 +- .../tests/unit/v2/contrib/__init__.py | 0 cinderclient/tests/unit/v2/test_quotas.py | 83 ---------- .../tests/unit/v2/test_volume_transfers.py | 58 ------- .../tests/unit/{v2 => v3/contrib}/__init__.py | 0 .../contrib/test_list_extensions.py | 4 +- cinderclient/tests/unit/v3/fakes.py | 38 +++-- .../unit/{v2/fakes.py => v3/fakes_base.py} | 14 -- .../tests/unit/{v2 => v3}/test_auth.py | 2 +- .../unit/{v2 => v3}/test_capabilities.py | 7 +- .../tests/unit/{v2 => v3}/test_cgsnapshots.py | 5 +- .../unit/{v2 => v3}/test_consistencygroups.py | 5 +- .../tests/unit/{v2 => v3}/test_limits.py | 2 +- .../tests/unit/{v2 => v3}/test_pools.py | 8 +- .../tests/unit/{v2 => v3}/test_qos.py | 5 +- .../unit/{v2 => v3}/test_quota_classes.py | 5 +- cinderclient/tests/unit/v3/test_quotas.py | 63 ++++++- .../test_services_base.py} | 8 +- cinderclient/tests/unit/v3/test_shell.py | 2 +- .../unit/{v2 => v3}/test_snapshot_actions.py | 2 +- .../tests/unit/{v2 => v3}/test_type_access.py | 4 +- .../tests/unit/{v2 => v3}/test_types.py | 4 +- .../tests/unit/v3/test_volume_backups.py | 21 +++ .../test_volume_backups_30.py} | 21 +-- .../test_volume_encryption_types.py | 4 +- cinderclient/tests/unit/v3/test_volumes.py | 19 +++ .../test_volumes_base.py} | 35 ++-- cinderclient/v2/__init__.py | 17 -- cinderclient/v2/availability_zones.py | 41 ----- cinderclient/v2/capabilities.py | 38 ----- cinderclient/v2/cgsnapshots.py | 112 ------------- cinderclient/v2/client.py | 141 ---------------- cinderclient/v2/consistencygroups.py | 149 ----------------- cinderclient/v2/contrib/__init__.py | 0 cinderclient/v2/contrib/list_extensions.py | 44 ----- cinderclient/v2/limits.py | 99 ----------- cinderclient/v2/pools.py | 60 ------- cinderclient/v2/qos_specs.py | 155 ------------------ cinderclient/v2/quota_classes.py | 47 ------ cinderclient/v2/quotas.py | 56 ------- cinderclient/v2/services.py | 80 --------- cinderclient/v2/volume_backups.py | 137 ---------------- cinderclient/v2/volume_backups_restore.py | 44 ----- cinderclient/v2/volume_encryption_types.py | 104 ------------ cinderclient/v2/volume_snapshots.py | 39 ----- cinderclient/v2/volume_transfers.py | 88 ---------- cinderclient/v2/volume_type_access.py | 53 ------ cinderclient/v2/volume_types.py | 153 ----------------- cinderclient/v3/capabilities.py | 22 ++- cinderclient/v3/cgsnapshots.py | 96 ++++++++++- cinderclient/v3/consistencygroups.py | 133 ++++++++++++++- cinderclient/v3/contrib/list_extensions.py | 30 +++- cinderclient/v3/limits.py | 84 +++++++++- cinderclient/v3/pools.py | 44 ++++- cinderclient/v3/qos_specs.py | 136 ++++++++++++++- cinderclient/v3/quota_classes.py | 33 +++- cinderclient/v3/quotas.py | 31 +++- cinderclient/v3/services.py | 63 ++++++- cinderclient/v3/shell_base.py | 2 +- cinderclient/v3/volume_backups.py | 91 +++++++++- cinderclient/v3/volume_backups_restore.py | 25 ++- cinderclient/v3/volume_encryption_types.py | 86 +++++++++- cinderclient/v3/volume_snapshots.py | 12 +- cinderclient/v3/volume_transfers.py | 17 +- cinderclient/v3/volume_type_access.py | 38 ++++- cinderclient/v3/volumes.py | 20 ++- .../{v2/volumes.py => v3/volumes_base.py} | 88 +--------- doc/source/contributor/unit_tests.rst | 4 +- .../drop-v2-support-e578ca21c7c6b532.yaml | 5 + 72 files changed, 1122 insertions(+), 2039 deletions(-) delete mode 100644 cinderclient/tests/unit/v2/contrib/__init__.py delete mode 100644 cinderclient/tests/unit/v2/test_quotas.py delete mode 100644 cinderclient/tests/unit/v2/test_volume_transfers.py rename cinderclient/tests/unit/{v2 => v3/contrib}/__init__.py (100%) rename cinderclient/tests/unit/{v2 => v3}/contrib/test_list_extensions.py (92%) rename cinderclient/tests/unit/{v2/fakes.py => v3/fakes_base.py} (98%) rename cinderclient/tests/unit/{v2 => v3}/test_auth.py (99%) rename cinderclient/tests/unit/{v2 => v3}/test_capabilities.py (90%) rename cinderclient/tests/unit/{v2 => v3}/test_cgsnapshots.py (96%) rename cinderclient/tests/unit/{v2 => v3}/test_consistencygroups.py (98%) rename cinderclient/tests/unit/{v2 => v3}/test_limits.py (99%) rename cinderclient/tests/unit/{v2 => v3}/test_pools.py (90%) rename cinderclient/tests/unit/{v2 => v3}/test_qos.py (96%) rename cinderclient/tests/unit/{v2 => v3}/test_quota_classes.py (95%) rename cinderclient/tests/unit/{v2/test_services.py => v3/test_services_base.py} (94%) rename cinderclient/tests/unit/{v2 => v3}/test_snapshot_actions.py (98%) rename cinderclient/tests/unit/{v2 => v3}/test_type_access.py (94%) rename cinderclient/tests/unit/{v2 => v3}/test_types.py (98%) rename cinderclient/tests/unit/{v2/test_volume_backups.py => v3/test_volume_backups_30.py} (86%) rename cinderclient/tests/unit/{v2 => v3}/test_volume_encryption_types.py (98%) rename cinderclient/tests/unit/{v2/test_volumes.py => v3/test_volumes_base.py} (93%) delete mode 100644 cinderclient/v2/__init__.py delete mode 100644 cinderclient/v2/availability_zones.py delete mode 100644 cinderclient/v2/capabilities.py delete mode 100644 cinderclient/v2/cgsnapshots.py delete mode 100644 cinderclient/v2/client.py delete mode 100644 cinderclient/v2/consistencygroups.py delete mode 100644 cinderclient/v2/contrib/__init__.py delete mode 100644 cinderclient/v2/contrib/list_extensions.py delete mode 100644 cinderclient/v2/limits.py delete mode 100644 cinderclient/v2/pools.py delete mode 100644 cinderclient/v2/qos_specs.py delete mode 100644 cinderclient/v2/quota_classes.py delete mode 100644 cinderclient/v2/quotas.py delete mode 100644 cinderclient/v2/services.py delete mode 100644 cinderclient/v2/volume_backups.py delete mode 100644 cinderclient/v2/volume_backups_restore.py delete mode 100644 cinderclient/v2/volume_encryption_types.py delete mode 100644 cinderclient/v2/volume_snapshots.py delete mode 100644 cinderclient/v2/volume_transfers.py delete mode 100644 cinderclient/v2/volume_type_access.py delete mode 100644 cinderclient/v2/volume_types.py rename cinderclient/{v2/volumes.py => v3/volumes_base.py} (82%) create mode 100644 releasenotes/notes/drop-v2-support-e578ca21c7c6b532.yaml diff --git a/cinderclient/tests/unit/fixture_data/client.py b/cinderclient/tests/unit/fixture_data/client.py index 2beeb906d..9fe975666 100644 --- a/cinderclient/tests/unit/fixture_data/client.py +++ b/cinderclient/tests/unit/fixture_data/client.py @@ -13,7 +13,6 @@ from keystoneauth1 import fixture from cinderclient.tests.unit.fixture_data import base -from cinderclient.v2 import client as v2client from cinderclient.v3 import client as v3client @@ -34,21 +33,6 @@ def setUp(self): headers=self.json_headers) -class V2(Base): - - def __init__(self, *args, **kwargs): - super(V2, self).__init__(*args, **kwargs) - - svc = self.token.add_service('volumev2') - svc.add_endpoint(self.volume_url) - - def new_client(self): - return v2client.Client(username='xx', - api_key='xx', - project_id='xx', - auth_url=self.identity_url) - - class V3(Base): def __init__(self, *args, **kwargs): diff --git a/cinderclient/tests/unit/test_base.py b/cinderclient/tests/unit/test_base.py index 6ec2ca6f4..36503e152 100644 --- a/cinderclient/tests/unit/test_base.py +++ b/cinderclient/tests/unit/test_base.py @@ -22,13 +22,13 @@ from cinderclient import exceptions from cinderclient.tests.unit import test_utils from cinderclient.tests.unit import utils -from cinderclient.tests.unit.v2 import fakes +from cinderclient.tests.unit.v3 import fakes from cinderclient.v3 import client from cinderclient.v3 import volumes cs = fakes.FakeClient() -REQUEST_ID = 'req-test-request-id' +REQUEST_ID = test_utils.REQUEST_ID def create_response_obj_with_header(): diff --git a/cinderclient/tests/unit/test_client.py b/cinderclient/tests/unit/test_client.py index 1501d6f32..b7cd3c63a 100644 --- a/cinderclient/tests/unit/test_client.py +++ b/cinderclient/tests/unit/test_client.py @@ -26,7 +26,6 @@ from cinderclient import exceptions from cinderclient.tests.unit import utils from cinderclient.tests.unit.v3 import fakes -import cinderclient.v2.client @ddt.ddt diff --git a/cinderclient/tests/unit/test_utils.py b/cinderclient/tests/unit/test_utils.py index 1fb9433b4..cce4498b4 100644 --- a/cinderclient/tests/unit/test_utils.py +++ b/cinderclient/tests/unit/test_utils.py @@ -24,9 +24,9 @@ from cinderclient import exceptions from cinderclient import shell_utils from cinderclient.tests.unit import utils as test_utils -from cinderclient.tests.unit.v2 import fakes from cinderclient import utils +REQUEST_ID = 'req-test-request-id' UUID = '8e8ec658-c7b0-4243-bdf8-6f7f2952c0d0' @@ -61,7 +61,7 @@ def get(self, resource_id, **kwargs): raise exceptions.NotFound(resource_id) def list(self, search_opts, **kwargs): - return common_base.ListWithMeta(self.resources, fakes.REQUEST_ID) + return common_base.ListWithMeta(self.resources, REQUEST_ID) class FakeManagerWithApi(base.Manager): diff --git a/cinderclient/tests/unit/v2/contrib/__init__.py b/cinderclient/tests/unit/v2/contrib/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/cinderclient/tests/unit/v2/test_quotas.py b/cinderclient/tests/unit/v2/test_quotas.py deleted file mode 100644 index bb29e4d8e..000000000 --- a/cinderclient/tests/unit/v2/test_quotas.py +++ /dev/null @@ -1,83 +0,0 @@ -# Copyright (c) 2013 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 cinderclient.tests.unit import utils -from cinderclient.tests.unit.v2 import fakes - - -cs = fakes.FakeClient() - - -class QuotaSetsTest(utils.TestCase): - - def test_tenant_quotas_get(self): - tenant_id = 'test' - quota = cs.quotas.get(tenant_id) - cs.assert_called('GET', '/os-quota-sets/%s?usage=False' % tenant_id) - self._assert_request_id(quota) - - def test_tenant_quotas_defaults(self): - tenant_id = 'test' - quota = cs.quotas.defaults(tenant_id) - cs.assert_called('GET', '/os-quota-sets/%s/defaults' % tenant_id) - self._assert_request_id(quota) - - def test_update_quota(self): - q = cs.quotas.get('test') - q.update(volumes=2) - q.update(snapshots=2) - q.update(gigabytes=2000) - q.update(backups=2) - q.update(backup_gigabytes=2000) - q.update(per_volume_gigabytes=100) - cs.assert_called('PUT', '/os-quota-sets/test') - self._assert_request_id(q) - - def test_refresh_quota(self): - q = cs.quotas.get('test') - q2 = cs.quotas.get('test') - self.assertEqual(q.volumes, q2.volumes) - self.assertEqual(q.snapshots, q2.snapshots) - self.assertEqual(q.gigabytes, q2.gigabytes) - self.assertEqual(q.backups, q2.backups) - self.assertEqual(q.backup_gigabytes, q2.backup_gigabytes) - self.assertEqual(q.per_volume_gigabytes, q2.per_volume_gigabytes) - q2.volumes = 0 - self.assertNotEqual(q.volumes, q2.volumes) - q2.snapshots = 0 - self.assertNotEqual(q.snapshots, q2.snapshots) - q2.gigabytes = 0 - self.assertNotEqual(q.gigabytes, q2.gigabytes) - q2.backups = 0 - self.assertNotEqual(q.backups, q2.backups) - q2.backup_gigabytes = 0 - self.assertNotEqual(q.backup_gigabytes, q2.backup_gigabytes) - q2.per_volume_gigabytes = 0 - self.assertNotEqual(q.per_volume_gigabytes, q2.per_volume_gigabytes) - q2.get() - self.assertEqual(q.volumes, q2.volumes) - self.assertEqual(q.snapshots, q2.snapshots) - self.assertEqual(q.gigabytes, q2.gigabytes) - self.assertEqual(q.backups, q2.backups) - self.assertEqual(q.backup_gigabytes, q2.backup_gigabytes) - self.assertEqual(q.per_volume_gigabytes, q2.per_volume_gigabytes) - self._assert_request_id(q) - self._assert_request_id(q2) - - def test_delete_quota(self): - tenant_id = 'test' - quota = cs.quotas.delete(tenant_id) - cs.assert_called('DELETE', '/os-quota-sets/test') - self._assert_request_id(quota) diff --git a/cinderclient/tests/unit/v2/test_volume_transfers.py b/cinderclient/tests/unit/v2/test_volume_transfers.py deleted file mode 100644 index a265cf534..000000000 --- a/cinderclient/tests/unit/v2/test_volume_transfers.py +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright (C) 2013 Hewlett-Packard Development Company, L.P. -# 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 cinderclient.tests.unit import utils -from cinderclient.tests.unit.v2 import fakes - - -cs = fakes.FakeClient() - - -class VolumeTransfersTest(utils.TestCase): - - def test_create(self): - vol = cs.transfers.create('1234') - cs.assert_called('POST', '/os-volume-transfer') - self._assert_request_id(vol) - - def test_get(self): - transfer_id = '5678' - vol = cs.transfers.get(transfer_id) - cs.assert_called('GET', '/os-volume-transfer/%s' % transfer_id) - self._assert_request_id(vol) - - def test_list(self): - lst = cs.transfers.list() - cs.assert_called('GET', '/os-volume-transfer/detail') - self._assert_request_id(lst) - - def test_delete(self): - b = cs.transfers.list()[0] - vol = b.delete() - cs.assert_called('DELETE', '/os-volume-transfer/5678') - self._assert_request_id(vol) - vol = cs.transfers.delete('5678') - self._assert_request_id(vol) - cs.assert_called('DELETE', '/os-volume-transfer/5678') - vol = cs.transfers.delete(b) - cs.assert_called('DELETE', '/os-volume-transfer/5678') - self._assert_request_id(vol) - - def test_accept(self): - transfer_id = '5678' - auth_key = '12345' - vol = cs.transfers.accept(transfer_id, auth_key) - cs.assert_called('POST', '/os-volume-transfer/%s/accept' % transfer_id) - self._assert_request_id(vol) diff --git a/cinderclient/tests/unit/v2/__init__.py b/cinderclient/tests/unit/v3/contrib/__init__.py similarity index 100% rename from cinderclient/tests/unit/v2/__init__.py rename to cinderclient/tests/unit/v3/contrib/__init__.py diff --git a/cinderclient/tests/unit/v2/contrib/test_list_extensions.py b/cinderclient/tests/unit/v3/contrib/test_list_extensions.py similarity index 92% rename from cinderclient/tests/unit/v2/contrib/test_list_extensions.py rename to cinderclient/tests/unit/v3/contrib/test_list_extensions.py index 4b6100f7a..1d7eb357d 100644 --- a/cinderclient/tests/unit/v2/contrib/test_list_extensions.py +++ b/cinderclient/tests/unit/v3/contrib/test_list_extensions.py @@ -16,8 +16,8 @@ from cinderclient import extension from cinderclient.tests.unit import utils -from cinderclient.tests.unit.v2 import fakes -from cinderclient.v2.contrib import list_extensions +from cinderclient.tests.unit.v3 import fakes +from cinderclient.v3.contrib import list_extensions extensions = [ extension.Extension(list_extensions.__name__.split(".")[-1], diff --git a/cinderclient/tests/unit/v3/fakes.py b/cinderclient/tests/unit/v3/fakes.py index 7647fb39f..7429af255 100644 --- a/cinderclient/tests/unit/v3/fakes.py +++ b/cinderclient/tests/unit/v3/fakes.py @@ -15,7 +15,7 @@ from datetime import datetime from cinderclient.tests.unit import fakes -from cinderclient.tests.unit.v2 import fakes as fake_v2 +from cinderclient.tests.unit.v3 import fakes_base from cinderclient.v3 import client @@ -133,7 +133,7 @@ def get_volume_api_version_from_endpoint(self): return self.client.get_volume_api_version_from_endpoint() -class FakeHTTPClient(fake_v2.FakeHTTPClient): +class FakeHTTPClient(fakes_base.FakeHTTPClient): def __init__(self, **kwargs): super(FakeHTTPClient, self).__init__() @@ -194,6 +194,19 @@ def get_os_services(self, **kw): del svc['backend_state'] return (200, {}, {'services': services}) + def put_os_services_enable(self, body, **kw): + return (200, {}, {'host': body['host'], 'binary': body['binary'], + 'status': 'enabled'}) + + def put_os_services_disable(self, body, **kw): + return (200, {}, {'host': body['host'], 'binary': body['binary'], + 'status': 'disabled'}) + + def put_os_services_disable_log_reason(self, body, **kw): + return (200, {}, {'host': body['host'], 'binary': body['binary'], + 'status': 'disabled', + 'disabled_reason': body['disabled_reason']}) + # # Clusters # @@ -285,7 +298,7 @@ def put_clusters_disable(self, body): # Backups # def put_backups_1234(self, **kw): - backup = fake_v2._stub_backup( + backup = fakes_base._stub_backup( id='1234', base_uri='http://localhost:8776', tenant_id='0fa851f6668144cf9cd8c8419c1646c1') @@ -640,10 +653,10 @@ def get_volume_transfers_detail(self, **kw): transfer2 = 'f625ec3e-13dd-4498-a22a-50afd534cc41' return (200, {}, {'transfers': [ - fake_v2._stub_transfer_full(transfer1, base_uri, - tenant_id), - fake_v2._stub_transfer_full(transfer2, base_uri, - tenant_id)]}) + fakes_base._stub_transfer_full(transfer1, base_uri, + tenant_id), + fakes_base._stub_transfer_full(transfer2, base_uri, + tenant_id)]}) def get_volume_transfers_5678(self, **kw): base_uri = 'http://localhost:8776' @@ -651,7 +664,8 @@ def get_volume_transfers_5678(self, **kw): transfer1 = '5678' return (200, {}, {'transfer': - fake_v2._stub_transfer_full(transfer1, base_uri, tenant_id)}) + fakes_base._stub_transfer_full(transfer1, base_uri, + tenant_id)}) def delete_volume_transfers_5678(self, **kw): return (202, {}, None) @@ -661,16 +675,16 @@ def post_volume_transfers(self, **kw): tenant_id = '0fa851f6668144cf9cd8c8419c1646c1' transfer1 = '5678' return (202, {}, - {'transfer': fake_v2._stub_transfer(transfer1, base_uri, - tenant_id)}) + {'transfer': fakes_base._stub_transfer(transfer1, base_uri, + tenant_id)}) def post_volume_transfers_5678_accept(self, **kw): base_uri = 'http://localhost:8776' tenant_id = '0fa851f6668144cf9cd8c8419c1646c1' transfer1 = '5678' return (200, {}, - {'transfer': fake_v2._stub_transfer(transfer1, base_uri, - tenant_id)}) + {'transfer': fakes_base._stub_transfer(transfer1, base_uri, + tenant_id)}) def fake_request_get(): diff --git a/cinderclient/tests/unit/v2/fakes.py b/cinderclient/tests/unit/v3/fakes_base.py similarity index 98% rename from cinderclient/tests/unit/v2/fakes.py rename to cinderclient/tests/unit/v3/fakes_base.py index 700bf1e2f..ec75ff079 100644 --- a/cinderclient/tests/unit/v2/fakes.py +++ b/cinderclient/tests/unit/v3/fakes_base.py @@ -18,7 +18,6 @@ from cinderclient import client as base_client from cinderclient.tests.unit import fakes import cinderclient.tests.unit.utils as utils -from cinderclient.v2 import client REQUEST_ID = 'req-test-request-id' @@ -332,19 +331,6 @@ def stub_default_types(): } -class FakeClient(fakes.FakeClient, client.Client): - - def __init__(self, api_version=None, *args, **kwargs): - client.Client.__init__(self, 'username', 'password', - 'project_id', 'auth_url', - extensions=kwargs.get('extensions')) - self.api_version = api_version - self.client = FakeHTTPClient(**kwargs) - - def get_volume_api_version_from_endpoint(self): - return self.client.get_volume_api_version_from_endpoint() - - class FakeHTTPClient(base_client.HTTPClient): def __init__(self, version_header=None, **kwargs): diff --git a/cinderclient/tests/unit/v2/test_auth.py b/cinderclient/tests/unit/v3/test_auth.py similarity index 99% rename from cinderclient/tests/unit/v2/test_auth.py rename to cinderclient/tests/unit/v3/test_auth.py index 5d7a4bc36..3e5e70890 100644 --- a/cinderclient/tests/unit/v2/test_auth.py +++ b/cinderclient/tests/unit/v3/test_auth.py @@ -21,7 +21,7 @@ from cinderclient import exceptions from cinderclient.tests.unit import utils -from cinderclient.v2 import client +from cinderclient.v3 import client class AuthenticateAgainstKeystoneTests(utils.TestCase): diff --git a/cinderclient/tests/unit/v2/test_capabilities.py b/cinderclient/tests/unit/v3/test_capabilities.py similarity index 90% rename from cinderclient/tests/unit/v2/test_capabilities.py rename to cinderclient/tests/unit/v3/test_capabilities.py index 98d8d71fc..9f8c4c66f 100644 --- a/cinderclient/tests/unit/v2/test_capabilities.py +++ b/cinderclient/tests/unit/v3/test_capabilities.py @@ -13,11 +13,12 @@ # License for the specific language governing permissions and limitations # under the License. +from cinderclient import api_versions from cinderclient.tests.unit import utils -from cinderclient.tests.unit.v2 import fakes -from cinderclient.v2.capabilities import Capabilities +from cinderclient.tests.unit.v3 import fakes +from cinderclient.v3.capabilities import Capabilities -cs = fakes.FakeClient() +cs = fakes.FakeClient(api_versions.APIVersion('3.0')) FAKE_CAPABILITY = { 'namespace': 'OS::Storage::Capabilities::fake', diff --git a/cinderclient/tests/unit/v2/test_cgsnapshots.py b/cinderclient/tests/unit/v3/test_cgsnapshots.py similarity index 96% rename from cinderclient/tests/unit/v2/test_cgsnapshots.py rename to cinderclient/tests/unit/v3/test_cgsnapshots.py index 3ebef9e4f..5f0cec76c 100644 --- a/cinderclient/tests/unit/v2/test_cgsnapshots.py +++ b/cinderclient/tests/unit/v3/test_cgsnapshots.py @@ -14,11 +14,12 @@ # License for the specific language governing permissions and limitations # under the License. +from cinderclient import api_versions from cinderclient.tests.unit import utils -from cinderclient.tests.unit.v2 import fakes +from cinderclient.tests.unit.v3 import fakes -cs = fakes.FakeClient() +cs = fakes.FakeClient(api_versions.APIVersion('3.0')) class cgsnapshotsTest(utils.TestCase): diff --git a/cinderclient/tests/unit/v2/test_consistencygroups.py b/cinderclient/tests/unit/v3/test_consistencygroups.py similarity index 98% rename from cinderclient/tests/unit/v2/test_consistencygroups.py rename to cinderclient/tests/unit/v3/test_consistencygroups.py index a695fe9d9..d265aabd3 100644 --- a/cinderclient/tests/unit/v2/test_consistencygroups.py +++ b/cinderclient/tests/unit/v3/test_consistencygroups.py @@ -14,10 +14,11 @@ # License for the specific language governing permissions and limitations # under the License. +from cinderclient import api_versions from cinderclient.tests.unit import utils -from cinderclient.tests.unit.v2 import fakes +from cinderclient.tests.unit.v3 import fakes -cs = fakes.FakeClient() +cs = fakes.FakeClient(api_versions.APIVersion('3.0')) class ConsistencygroupsTest(utils.TestCase): diff --git a/cinderclient/tests/unit/v2/test_limits.py b/cinderclient/tests/unit/v3/test_limits.py similarity index 99% rename from cinderclient/tests/unit/v2/test_limits.py rename to cinderclient/tests/unit/v3/test_limits.py index d8fbdfe59..a42f770fe 100644 --- a/cinderclient/tests/unit/v2/test_limits.py +++ b/cinderclient/tests/unit/v3/test_limits.py @@ -18,7 +18,7 @@ import ddt from cinderclient.tests.unit import utils -from cinderclient.v2 import limits +from cinderclient.v3 import limits REQUEST_ID = 'req-test-request-id' diff --git a/cinderclient/tests/unit/v2/test_pools.py b/cinderclient/tests/unit/v3/test_pools.py similarity index 90% rename from cinderclient/tests/unit/v2/test_pools.py rename to cinderclient/tests/unit/v3/test_pools.py index e909871ae..6af90578b 100644 --- a/cinderclient/tests/unit/v2/test_pools.py +++ b/cinderclient/tests/unit/v3/test_pools.py @@ -13,11 +13,13 @@ # License for the specific language governing permissions and limitations # under the License. +from cinderclient import api_versions from cinderclient.tests.unit import utils -from cinderclient.tests.unit.v2 import fakes -from cinderclient.v2.pools import Pool +from cinderclient.tests.unit.v3 import fakes +from cinderclient.v3.pools import Pool -cs = fakes.FakeClient() + +cs = fakes.FakeClient(api_versions.APIVersion('3.0')) class PoolsTest(utils.TestCase): diff --git a/cinderclient/tests/unit/v2/test_qos.py b/cinderclient/tests/unit/v3/test_qos.py similarity index 96% rename from cinderclient/tests/unit/v2/test_qos.py rename to cinderclient/tests/unit/v3/test_qos.py index 809a71947..f6133900a 100644 --- a/cinderclient/tests/unit/v2/test_qos.py +++ b/cinderclient/tests/unit/v3/test_qos.py @@ -13,11 +13,12 @@ # License for the specific language governing permissions and limitations # under the License. +from cinderclient import api_versions from cinderclient.tests.unit import utils -from cinderclient.tests.unit.v2 import fakes +from cinderclient.tests.unit.v3 import fakes -cs = fakes.FakeClient() +cs = fakes.FakeClient(api_versions.APIVersion('3.0')) class QoSSpecsTest(utils.TestCase): diff --git a/cinderclient/tests/unit/v2/test_quota_classes.py b/cinderclient/tests/unit/v3/test_quota_classes.py similarity index 95% rename from cinderclient/tests/unit/v2/test_quota_classes.py rename to cinderclient/tests/unit/v3/test_quota_classes.py index 4182fdf3a..29f4d0c23 100644 --- a/cinderclient/tests/unit/v2/test_quota_classes.py +++ b/cinderclient/tests/unit/v3/test_quota_classes.py @@ -13,11 +13,12 @@ # License for the specific language governing permissions and limitations # under the License. +from cinderclient import api_versions from cinderclient.tests.unit import utils -from cinderclient.tests.unit.v2 import fakes +from cinderclient.tests.unit.v3 import fakes -cs = fakes.FakeClient() +cs = fakes.FakeClient(api_versions.APIVersion('3.0')) class QuotaClassSetsTest(utils.TestCase): diff --git a/cinderclient/tests/unit/v3/test_quotas.py b/cinderclient/tests/unit/v3/test_quotas.py index fbabb47f7..e67c47764 100644 --- a/cinderclient/tests/unit/v3/test_quotas.py +++ b/cinderclient/tests/unit/v3/test_quotas.py @@ -13,17 +13,78 @@ # License for the specific language governing permissions and limitations # under the License. +from cinderclient import api_versions from cinderclient.tests.unit import utils from cinderclient.tests.unit.v3 import fakes -cs = fakes.FakeClient() +cs = fakes.FakeClient(api_versions.APIVersion('3.0')) class QuotaSetsTest(utils.TestCase): + def test_tenant_quotas_get(self): + tenant_id = 'test' + quota = cs.quotas.get(tenant_id) + cs.assert_called('GET', '/os-quota-sets/%s?usage=False' % tenant_id) + self._assert_request_id(quota) + + def test_tenant_quotas_defaults(self): + tenant_id = 'test' + quota = cs.quotas.defaults(tenant_id) + cs.assert_called('GET', '/os-quota-sets/%s/defaults' % tenant_id) + self._assert_request_id(quota) + + def test_update_quota(self): + q = cs.quotas.get('test') + q.update(volumes=2) + q.update(snapshots=2) + q.update(gigabytes=2000) + q.update(backups=2) + q.update(backup_gigabytes=2000) + q.update(per_volume_gigabytes=100) + cs.assert_called('PUT', '/os-quota-sets/test') + self._assert_request_id(q) + def test_update_quota_with_skip_(self): q = cs.quotas.get('test') q.update(skip_validation=False) cs.assert_called('PUT', '/os-quota-sets/test?skip_validation=False') self._assert_request_id(q) + + def test_refresh_quota(self): + q = cs.quotas.get('test') + q2 = cs.quotas.get('test') + self.assertEqual(q.volumes, q2.volumes) + self.assertEqual(q.snapshots, q2.snapshots) + self.assertEqual(q.gigabytes, q2.gigabytes) + self.assertEqual(q.backups, q2.backups) + self.assertEqual(q.backup_gigabytes, q2.backup_gigabytes) + self.assertEqual(q.per_volume_gigabytes, q2.per_volume_gigabytes) + q2.volumes = 0 + self.assertNotEqual(q.volumes, q2.volumes) + q2.snapshots = 0 + self.assertNotEqual(q.snapshots, q2.snapshots) + q2.gigabytes = 0 + self.assertNotEqual(q.gigabytes, q2.gigabytes) + q2.backups = 0 + self.assertNotEqual(q.backups, q2.backups) + q2.backup_gigabytes = 0 + self.assertNotEqual(q.backup_gigabytes, q2.backup_gigabytes) + q2.per_volume_gigabytes = 0 + self.assertNotEqual(q.per_volume_gigabytes, q2.per_volume_gigabytes) + q2.get() + self.assertEqual(q.volumes, q2.volumes) + self.assertEqual(q.snapshots, q2.snapshots) + self.assertEqual(q.gigabytes, q2.gigabytes) + self.assertEqual(q.backups, q2.backups) + self.assertEqual(q.backup_gigabytes, q2.backup_gigabytes) + self.assertEqual(q.per_volume_gigabytes, q2.per_volume_gigabytes) + self._assert_request_id(q) + self._assert_request_id(q2) + + def test_delete_quota(self): + tenant_id = 'test' + quota = cs.quotas.delete(tenant_id) + cs.assert_called('DELETE', '/os-quota-sets/test') + self._assert_request_id(quota) diff --git a/cinderclient/tests/unit/v2/test_services.py b/cinderclient/tests/unit/v3/test_services_base.py similarity index 94% rename from cinderclient/tests/unit/v2/test_services.py rename to cinderclient/tests/unit/v3/test_services_base.py index d355d7fb1..711a5361d 100644 --- a/cinderclient/tests/unit/v2/test_services.py +++ b/cinderclient/tests/unit/v3/test_services_base.py @@ -13,15 +13,17 @@ # License for the specific language governing permissions and limitations # under the License. +from cinderclient import api_versions from cinderclient.tests.unit import utils -from cinderclient.tests.unit.v2 import fakes -from cinderclient.v2 import services +from cinderclient.tests.unit.v3 import fakes +from cinderclient.v3 import services -cs = fakes.FakeClient() +cs = fakes.FakeClient(api_version=api_versions.APIVersion('3.0')) class ServicesTest(utils.TestCase): + """Tests for v3.0 behavior""" def test_list_services(self): svs = cs.services.list() diff --git a/cinderclient/tests/unit/v3/test_shell.py b/cinderclient/tests/unit/v3/test_shell.py index 756f51201..a5b76fe96 100644 --- a/cinderclient/tests/unit/v3/test_shell.py +++ b/cinderclient/tests/unit/v3/test_shell.py @@ -1745,7 +1745,7 @@ def test_restore_with_volume_type_and_az_no_name(self): ) @ddt.unpack @mock.patch('cinderclient.utils.print_dict') - @mock.patch('cinderclient.tests.unit.v2.fakes._stub_restore') + @mock.patch('cinderclient.tests.unit.v3.fakes_base._stub_restore') def test_do_backup_restore(self, mock_stub_restore, mock_print_dict, diff --git a/cinderclient/tests/unit/v2/test_snapshot_actions.py b/cinderclient/tests/unit/v3/test_snapshot_actions.py similarity index 98% rename from cinderclient/tests/unit/v2/test_snapshot_actions.py rename to cinderclient/tests/unit/v3/test_snapshot_actions.py index 61fd0d06d..8d2b23a0d 100644 --- a/cinderclient/tests/unit/v2/test_snapshot_actions.py +++ b/cinderclient/tests/unit/v3/test_snapshot_actions.py @@ -20,7 +20,7 @@ class SnapshotActionsTest(utils.FixturedTestCase): - client_fixture_class = client.V2 + client_fixture_class = client.V3 data_fixture_class = snapshots.Fixture def test_update_snapshot_status(self): diff --git a/cinderclient/tests/unit/v2/test_type_access.py b/cinderclient/tests/unit/v3/test_type_access.py similarity index 94% rename from cinderclient/tests/unit/v2/test_type_access.py rename to cinderclient/tests/unit/v3/test_type_access.py index d904c1d3e..5b2dbb3e4 100644 --- a/cinderclient/tests/unit/v2/test_type_access.py +++ b/cinderclient/tests/unit/v3/test_type_access.py @@ -15,8 +15,8 @@ # under the License. from cinderclient.tests.unit import utils -from cinderclient.tests.unit.v2 import fakes -from cinderclient.v2 import volume_type_access +from cinderclient.tests.unit.v3 import fakes +from cinderclient.v3 import volume_type_access cs = fakes.FakeClient() diff --git a/cinderclient/tests/unit/v2/test_types.py b/cinderclient/tests/unit/v3/test_types.py similarity index 98% rename from cinderclient/tests/unit/v2/test_types.py rename to cinderclient/tests/unit/v3/test_types.py index cf13723f1..fdbd32317 100644 --- a/cinderclient/tests/unit/v2/test_types.py +++ b/cinderclient/tests/unit/v3/test_types.py @@ -15,8 +15,8 @@ # under the License. from cinderclient.tests.unit import utils -from cinderclient.tests.unit.v2 import fakes -from cinderclient.v2 import volume_types +from cinderclient.tests.unit.v3 import fakes +from cinderclient.v3 import volume_types cs = fakes.FakeClient() diff --git a/cinderclient/tests/unit/v3/test_volume_backups.py b/cinderclient/tests/unit/v3/test_volume_backups.py index faa0185c2..3be6328fb 100644 --- a/cinderclient/tests/unit/v3/test_volume_backups.py +++ b/cinderclient/tests/unit/v3/test_volume_backups.py @@ -17,6 +17,7 @@ from cinderclient import exceptions as exc from cinderclient.tests.unit import utils from cinderclient.tests.unit.v3 import fakes +from cinderclient.v3 import volume_backups_restore class VolumesTest(utils.TestCase): @@ -35,3 +36,23 @@ def test_pre_version(self): b = cs.backups.get('1234') self.assertRaises(exc.VersionNotFoundForAPIMethod, b.update, name='new-name') + + def test_restore(self): + cs = fakes.FakeClient(api_version=api_versions.APIVersion('3.0')) + backup_id = '76a17945-3c6f-435c-975b-b5685db10b62' + info = cs.restores.restore(backup_id) + cs.assert_called('POST', '/backups/%s/restore' % backup_id) + self.assertIsInstance(info, + volume_backups_restore.VolumeBackupsRestore) + self._assert_request_id(info) + + def test_restore_with_name(self): + cs = fakes.FakeClient(api_version=api_versions.APIVersion('3.0')) + backup_id = '76a17945-3c6f-435c-975b-b5685db10b62' + name = 'restore_vol' + info = cs.restores.restore(backup_id, name=name) + expected_body = {'restore': {'volume_id': None, 'name': name}} + cs.assert_called('POST', '/backups/%s/restore' % backup_id, + body=expected_body) + self.assertIsInstance(info, + volume_backups_restore.VolumeBackupsRestore) diff --git a/cinderclient/tests/unit/v2/test_volume_backups.py b/cinderclient/tests/unit/v3/test_volume_backups_30.py similarity index 86% rename from cinderclient/tests/unit/v2/test_volume_backups.py rename to cinderclient/tests/unit/v3/test_volume_backups_30.py index 700c44086..daf517c9f 100644 --- a/cinderclient/tests/unit/v2/test_volume_backups.py +++ b/cinderclient/tests/unit/v3/test_volume_backups_30.py @@ -14,8 +14,7 @@ # under the License. from cinderclient.tests.unit import utils -from cinderclient.tests.unit.v2 import fakes -from cinderclient.v2 import volume_backups_restore +from cinderclient.tests.unit.v3 import fakes cs = fakes.FakeClient() @@ -118,24 +117,6 @@ def test_force_delete_with_false_force_param_vaule(self): '/backups/76a17945-3c6f-435c-975b-b5685db10b62') self._assert_request_id(del_back) - def test_restore(self): - backup_id = '76a17945-3c6f-435c-975b-b5685db10b62' - info = cs.restores.restore(backup_id) - cs.assert_called('POST', '/backups/%s/restore' % backup_id) - self.assertIsInstance(info, - volume_backups_restore.VolumeBackupsRestore) - self._assert_request_id(info) - - def test_restore_with_name(self): - backup_id = '76a17945-3c6f-435c-975b-b5685db10b62' - name = 'restore_vol' - info = cs.restores.restore(backup_id, name=name) - expected_body = {'restore': {'volume_id': None, 'name': name}} - cs.assert_called('POST', '/backups/%s/restore' % backup_id, - body=expected_body) - self.assertIsInstance(info, - volume_backups_restore.VolumeBackupsRestore) - def test_reset_state(self): b = cs.backups.list()[0] api = '/backups/76a17945-3c6f-435c-975b-b5685db10b62/action' diff --git a/cinderclient/tests/unit/v2/test_volume_encryption_types.py b/cinderclient/tests/unit/v3/test_volume_encryption_types.py similarity index 98% rename from cinderclient/tests/unit/v2/test_volume_encryption_types.py rename to cinderclient/tests/unit/v3/test_volume_encryption_types.py index 1bbf537a1..9e38d5165 100644 --- a/cinderclient/tests/unit/v2/test_volume_encryption_types.py +++ b/cinderclient/tests/unit/v3/test_volume_encryption_types.py @@ -14,8 +14,8 @@ # under the License. from cinderclient.tests.unit import utils -from cinderclient.tests.unit.v2 import fakes -from cinderclient.v2.volume_encryption_types import VolumeEncryptionType +from cinderclient.tests.unit.v3 import fakes +from cinderclient.v3.volume_encryption_types import VolumeEncryptionType cs = fakes.FakeClient() diff --git a/cinderclient/tests/unit/v3/test_volumes.py b/cinderclient/tests/unit/v3/test_volumes.py index e5b3f2183..c733a0917 100644 --- a/cinderclient/tests/unit/v3/test_volumes.py +++ b/cinderclient/tests/unit/v3/test_volumes.py @@ -87,6 +87,25 @@ def test_create_volume(self): cs.assert_called('POST', '/volumes', body=expected) self._assert_request_id(vol) + def test_create_volume_with_hint(self): + cs = fakes.FakeClient(api_versions.APIVersion('3.0')) + vol = cs.volumes.create(1, scheduler_hints='uuid') + expected = {'volume': {'description': None, + 'availability_zone': None, + 'source_volid': None, + 'snapshot_id': None, + 'size': 1, + 'name': None, + 'imageRef': None, + 'volume_type': None, + 'metadata': {}, + 'consistencygroup_id': None, + 'backup_id': None, + }, + 'OS-SCH-HNT:scheduler_hints': 'uuid'} + cs.assert_called('POST', '/volumes', body=expected) + self._assert_request_id(vol) + @ddt.data((False, '/volumes/summary'), (True, '/volumes/summary?all_tenants=True')) def test_volume_summary(self, all_tenants_input): diff --git a/cinderclient/tests/unit/v2/test_volumes.py b/cinderclient/tests/unit/v3/test_volumes_base.py similarity index 93% rename from cinderclient/tests/unit/v2/test_volumes.py rename to cinderclient/tests/unit/v3/test_volumes_base.py index e4f9e323e..cca808aac 100644 --- a/cinderclient/tests/unit/v2/test_volumes.py +++ b/cinderclient/tests/unit/v3/test_volumes_base.py @@ -15,14 +15,16 @@ # License for the specific language governing permissions and limitations # under the License. +from cinderclient import api_versions from cinderclient.tests.unit import utils -from cinderclient.tests.unit.v2 import fakes -from cinderclient.v2.volumes import Volume +from cinderclient.tests.unit.v3 import fakes +from cinderclient.v3.volumes import Volume -cs = fakes.FakeClient() +cs = fakes.FakeClient(api_version=api_versions.APIVersion('3.0')) class VolumesTest(utils.TestCase): + """Block Storage API v3.0""" def test_list_volumes_with_marker_limit(self): lst = cs.volumes.list(marker=1234, limit=2) @@ -58,6 +60,11 @@ def test__list(self): self._assert_request_id(volumes) cs.client.osapi_max_limit = 1000 + def test_create_volume(self): + vol = cs.volumes.create(1) + cs.assert_called('POST', '/volumes') + self._assert_request_id(vol) + def test_delete_volume(self): v = cs.volumes.list()[0] del_v = v.delete() @@ -70,28 +77,6 @@ def test_delete_volume(self): cs.assert_called('DELETE', '/volumes/1234') self._assert_request_id(del_v) - def test_create_volume(self): - vol = cs.volumes.create(1) - cs.assert_called('POST', '/volumes') - self._assert_request_id(vol) - - def test_create_volume_with_hint(self): - vol = cs.volumes.create(1, scheduler_hints='uuid') - expected = {'volume': {'description': None, - 'availability_zone': None, - 'source_volid': None, - 'snapshot_id': None, - 'size': 1, - 'name': None, - 'imageRef': None, - 'volume_type': None, - 'metadata': {}, - 'consistencygroup_id': None, - }, - 'OS-SCH-HNT:scheduler_hints': 'uuid'} - cs.assert_called('POST', '/volumes', body=expected) - self._assert_request_id(vol) - def test_attach(self): v = cs.volumes.get('1234') self._assert_request_id(v) diff --git a/cinderclient/v2/__init__.py b/cinderclient/v2/__init__.py deleted file mode 100644 index 325460e38..000000000 --- a/cinderclient/v2/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright (c) 2013 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 cinderclient.v2.client import Client # noqa diff --git a/cinderclient/v2/availability_zones.py b/cinderclient/v2/availability_zones.py deleted file mode 100644 index 6503c12ae..000000000 --- a/cinderclient/v2/availability_zones.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright 2011-2013 OpenStack Foundation -# Copyright 2013 IBM Corp. -# 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. -"""Availability Zone interface (v2 extension)""" - -from cinderclient import base - - -class AvailabilityZone(base.Resource): - NAME_ATTR = 'display_name' - - def __repr__(self): - return "" % self.zoneName - - -class AvailabilityZoneManager(base.ManagerWithFind): - """Manage :class:`AvailabilityZone` resources.""" - resource_class = AvailabilityZone - - def list(self, detailed=False): - """Lists all availability zones. - - :rtype: list of :class:`AvailabilityZone` - """ - if detailed is True: - return self._list("/os-availability-zone/detail", - "availabilityZoneInfo") - else: - return self._list("/os-availability-zone", "availabilityZoneInfo") diff --git a/cinderclient/v2/capabilities.py b/cinderclient/v2/capabilities.py deleted file mode 100644 index 2045f02be..000000000 --- a/cinderclient/v2/capabilities.py +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright (c) 2015 Hitachi Data Systems, 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. - -"""Capabilities interface (v2 extension)""" - -from cinderclient import base - - -class Capabilities(base.Resource): - NAME_ATTR = 'name' - - def __repr__(self): - return "" % self._info.get('namespace') - - -class CapabilitiesManager(base.Manager): - """Manage :class:`Capabilities` resources.""" - resource_class = Capabilities - - def get(self, host): - """Show backend volume stats and properties. - - :param host: Specified backend to obtain volume stats and properties. - :rtype: :class:`Capabilities` - """ - return self._get('/capabilities/%s' % host, None) diff --git a/cinderclient/v2/cgsnapshots.py b/cinderclient/v2/cgsnapshots.py deleted file mode 100644 index 04444e2b3..000000000 --- a/cinderclient/v2/cgsnapshots.py +++ /dev/null @@ -1,112 +0,0 @@ -# Copyright (C) 2012 - 2014 EMC Corporation. -# 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. - -"""cgsnapshot interface (v2 extension).""" - -from cinderclient.apiclient import base as common_base -from cinderclient import base -from cinderclient import utils - - -class Cgsnapshot(base.Resource): - """A cgsnapshot is snapshot of a consistency group.""" - def __repr__(self): - return "" % self.id - - def delete(self): - """Delete this cgsnapshot.""" - return self.manager.delete(self) - - def update(self, **kwargs): - """Update the name or description for this cgsnapshot.""" - return self.manager.update(self, **kwargs) - - -class CgsnapshotManager(base.ManagerWithFind): - """Manage :class:`Cgsnapshot` resources.""" - resource_class = Cgsnapshot - - def create(self, consistencygroup_id, name=None, description=None, - user_id=None, - project_id=None): - """Creates a cgsnapshot. - - :param consistencygroup: Name or uuid of a consistency group - :param name: Name of the cgsnapshot - :param description: Description of the cgsnapshot - :param user_id: User id derived from context - :param project_id: Project id derived from context - :rtype: :class:`Cgsnapshot` - """ - - body = {'cgsnapshot': {'consistencygroup_id': consistencygroup_id, - 'name': name, - 'description': description, - 'user_id': user_id, - 'project_id': project_id, - 'status': "creating", - }} - - return self._create('/cgsnapshots', body, 'cgsnapshot') - - def get(self, cgsnapshot_id): - """Get a cgsnapshot. - - :param cgsnapshot_id: The ID of the cgsnapshot to get. - :rtype: :class:`Cgsnapshot` - """ - return self._get("/cgsnapshots/%s" % cgsnapshot_id, "cgsnapshot") - - def list(self, detailed=True, search_opts=None): - """Lists all cgsnapshots. - - :rtype: list of :class:`Cgsnapshot` - """ - query_string = utils.build_query_param(search_opts) - - detail = "" - if detailed: - detail = "/detail" - - return self._list("/cgsnapshots%s%s" % (detail, query_string), - "cgsnapshots") - - def delete(self, cgsnapshot): - """Delete a cgsnapshot. - - :param cgsnapshot: The :class:`Cgsnapshot` to delete. - """ - return self._delete("/cgsnapshots/%s" % base.getid(cgsnapshot)) - - def update(self, cgsnapshot, **kwargs): - """Update the name or description for a cgsnapshot. - - :param cgsnapshot: The :class:`Cgsnapshot` to update. - """ - if not kwargs: - return - - body = {"cgsnapshot": kwargs} - - return self._update("/cgsnapshots/%s" % base.getid(cgsnapshot), body) - - def _action(self, action, cgsnapshot, info=None, **kwargs): - """Perform a cgsnapshot "action." - """ - body = {action: info} - self.run_hooks('modify_body_for_action', body, **kwargs) - url = '/cgsnapshots/%s/action' % base.getid(cgsnapshot) - resp, body = self.api.client.post(url, body=body) - return common_base.TupleWithMeta((resp, body), resp) diff --git a/cinderclient/v2/client.py b/cinderclient/v2/client.py deleted file mode 100644 index a111c51a5..000000000 --- a/cinderclient/v2/client.py +++ /dev/null @@ -1,141 +0,0 @@ -# Copyright (c) 2013 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. - -import logging - -from cinderclient import api_versions -from cinderclient import client -from cinderclient.v2 import availability_zones -from cinderclient.v2 import capabilities -from cinderclient.v2 import cgsnapshots -from cinderclient.v2 import consistencygroups -from cinderclient.v2 import limits -from cinderclient.v2 import pools -from cinderclient.v2 import qos_specs -from cinderclient.v2 import quota_classes -from cinderclient.v2 import quotas -from cinderclient.v2 import services -from cinderclient.v2 import volume_backups -from cinderclient.v2 import volume_backups_restore -from cinderclient.v2 import volume_encryption_types -from cinderclient.v2 import volume_snapshots -from cinderclient.v2 import volume_transfers -from cinderclient.v2 import volume_type_access -from cinderclient.v2 import volume_types -from cinderclient.v2 import volumes - - -class Client(object): - """Top-level object to access the OpenStack Volume API. - - Create an instance with your creds:: - - >>> client = Client(USERNAME, PASSWORD, PROJECT_ID, AUTH_URL) - - Then call methods on its managers:: - - >>> client.volumes.list() - ... - """ - - def __init__(self, username=None, api_key=None, project_id=None, - auth_url='', insecure=False, timeout=None, tenant_id=None, - proxy_tenant_id=None, proxy_token=None, region_name=None, - endpoint_type='publicURL', extensions=None, - service_type='volumev2', service_name=None, - volume_service_name=None, os_endpoint=None, retries=0, - http_log_debug=False, cacert=None, cert=None, - auth_system='keystone', auth_plugin=None, session=None, - api_version=None, logger=None, **kwargs): - # FIXME(comstud): Rename the api_key argument above when we - # know it's not being used as keyword argument - password = api_key - self.version = '2.0' - self.limits = limits.LimitsManager(self) - - # extensions - self.volumes = volumes.VolumeManager(self) - self.volume_snapshots = volume_snapshots.SnapshotManager(self) - self.volume_types = volume_types.VolumeTypeManager(self) - self.volume_type_access = \ - volume_type_access.VolumeTypeAccessManager(self) - self.volume_encryption_types = \ - volume_encryption_types.VolumeEncryptionTypeManager(self) - self.qos_specs = qos_specs.QoSSpecsManager(self) - self.quota_classes = quota_classes.QuotaClassSetManager(self) - self.quotas = quotas.QuotaSetManager(self) - self.backups = volume_backups.VolumeBackupManager(self) - self.restores = volume_backups_restore.VolumeBackupRestoreManager(self) - self.transfers = volume_transfers.VolumeTransferManager(self) - self.services = services.ServiceManager(self) - self.consistencygroups = consistencygroups.\ - ConsistencygroupManager(self) - self.cgsnapshots = cgsnapshots.CgsnapshotManager(self) - self.availability_zones = \ - availability_zones.AvailabilityZoneManager(self) - self.pools = pools.PoolManager(self) - self.capabilities = capabilities.CapabilitiesManager(self) - self.api_version = api_version or api_versions.APIVersion(self.version) - - # Add in any extensions... - if extensions: - for extension in extensions: - if extension.manager_class: - setattr(self, extension.name, - extension.manager_class(self)) - - if not logger: - logger = logging.getLogger(__name__) - - self.client = client._construct_http_client( - username=username, - password=password, - project_id=project_id, - auth_url=auth_url, - insecure=insecure, - timeout=timeout, - tenant_id=tenant_id, - proxy_tenant_id=tenant_id, - proxy_token=proxy_token, - region_name=region_name, - endpoint_type=endpoint_type, - service_type=service_type, - service_name=service_name, - volume_service_name=volume_service_name, - os_endpoint=os_endpoint, - retries=retries, - http_log_debug=http_log_debug, - cacert=cacert, - cert=cert, - auth_system=auth_system, - auth_plugin=auth_plugin, - session=session, - api_version=self.api_version, - logger=logger, - **kwargs) - - def authenticate(self): - """Authenticate against the server. - - Normally this is called automatically when you first access the API, - but you can call this method to force authentication right now. - - Returns on success; raises :exc:`exceptions.Unauthorized` if the - credentials are wrong. - """ - self.client.authenticate() - - def get_volume_api_version_from_endpoint(self): - return self.client.get_volume_api_version_from_endpoint() diff --git a/cinderclient/v2/consistencygroups.py b/cinderclient/v2/consistencygroups.py deleted file mode 100644 index d5e5bbf77..000000000 --- a/cinderclient/v2/consistencygroups.py +++ /dev/null @@ -1,149 +0,0 @@ -# Copyright (C) 2012 - 2014 EMC Corporation. -# 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. - -"""Consistencygroup interface (v2 extension).""" - -from cinderclient.apiclient import base as common_base -from cinderclient import base -from cinderclient import utils - - -class Consistencygroup(base.Resource): - """A Consistencygroup of volumes.""" - def __repr__(self): - return "" % self.id - - def delete(self, force='False'): - """Delete this consistency group.""" - return self.manager.delete(self, force) - - def update(self, **kwargs): - """Update the name or description for this consistency group.""" - return self.manager.update(self, **kwargs) - - -class ConsistencygroupManager(base.ManagerWithFind): - """Manage :class:`Consistencygroup` resources.""" - resource_class = Consistencygroup - - def create(self, volume_types, name=None, - description=None, user_id=None, - project_id=None, availability_zone=None): - """Creates a consistency group. - - :param name: Name of the ConsistencyGroup - :param description: Description of the ConsistencyGroup - :param volume_types: Types of volume - :param user_id: User id derived from context - :param project_id: Project id derived from context - :param availability_zone: Availability Zone to use - :rtype: :class:`Consistencygroup` - """ - - body = {'consistencygroup': {'name': name, - 'description': description, - 'volume_types': volume_types, - 'user_id': user_id, - 'project_id': project_id, - 'availability_zone': availability_zone, - 'status': "creating", - }} - - return self._create('/consistencygroups', body, 'consistencygroup') - - def create_from_src(self, cgsnapshot_id, source_cgid, name=None, - description=None, user_id=None, - project_id=None): - """Creates a consistency group from a cgsnapshot or a source CG. - - :param cgsnapshot_id: UUID of a CGSnapshot - :param source_cgid: UUID of a source CG - :param name: Name of the ConsistencyGroup - :param description: Description of the ConsistencyGroup - :param user_id: User id derived from context - :param project_id: Project id derived from context - :rtype: A dictionary containing Consistencygroup metadata - """ - body = {'consistencygroup-from-src': {'name': name, - 'description': description, - 'cgsnapshot_id': cgsnapshot_id, - 'source_cgid': source_cgid, - 'user_id': user_id, - 'project_id': project_id, - 'status': "creating", - }} - - self.run_hooks('modify_body_for_update', body, - 'consistencygroup-from-src') - resp, body = self.api.client.post( - "/consistencygroups/create_from_src", body=body) - return common_base.DictWithMeta(body['consistencygroup'], resp) - - def get(self, group_id): - """Get a consistency group. - - :param group_id: The ID of the consistency group to get. - :rtype: :class:`Consistencygroup` - """ - return self._get("/consistencygroups/%s" % group_id, - "consistencygroup") - - def list(self, detailed=True, search_opts=None): - """Lists all consistency groups. - - :rtype: list of :class:`Consistencygroup` - """ - - query_string = utils.build_query_param(search_opts) - - detail = "" - if detailed: - detail = "/detail" - - return self._list("/consistencygroups%s%s" % (detail, query_string), - "consistencygroups") - - def delete(self, consistencygroup, force=False): - """Delete a consistency group. - - :param Consistencygroup: The :class:`Consistencygroup` to delete. - """ - body = {'consistencygroup': {'force': force}} - self.run_hooks('modify_body_for_action', body, 'consistencygroup') - url = '/consistencygroups/%s/delete' % base.getid(consistencygroup) - resp, body = self.api.client.post(url, body=body) - return common_base.TupleWithMeta((resp, body), resp) - - def update(self, consistencygroup, **kwargs): - """Update the name or description for a consistency group. - - :param Consistencygroup: The :class:`Consistencygroup` to update. - """ - if not kwargs: - return - - body = {"consistencygroup": kwargs} - - return self._update("/consistencygroups/%s" % - base.getid(consistencygroup), body) - - def _action(self, action, consistencygroup, info=None, **kwargs): - """Perform a consistency group "action." - """ - body = {action: info} - self.run_hooks('modify_body_for_action', body, **kwargs) - url = '/consistencygroups/%s/action' % base.getid(consistencygroup) - resp, body = self.api.client.post(url, body=body) - return common_base.TupleWithMeta((resp, body), resp) diff --git a/cinderclient/v2/contrib/__init__.py b/cinderclient/v2/contrib/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/cinderclient/v2/contrib/list_extensions.py b/cinderclient/v2/contrib/list_extensions.py deleted file mode 100644 index 937d34b53..000000000 --- a/cinderclient/v2/contrib/list_extensions.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright (c) 2013 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 cinderclient import base -from cinderclient import utils - - -class ListExtResource(base.Resource): - @property - def summary(self): - descr = self.description.strip() - if not descr: - return '??' - lines = descr.split("\n") - if len(lines) == 1: - return lines[0] - else: - return lines[0] + "..." - - -class ListExtManager(base.Manager): - resource_class = ListExtResource - - def show_all(self): - return self._list("/extensions", 'extensions') - - -def do_list_extensions(client, _args): - """Lists all available os-api extensions.""" - extensions = client.list_extensions.show_all() - fields = ["Name", "Summary", "Alias", "Updated"] - utils.print_list(extensions, fields) diff --git a/cinderclient/v2/limits.py b/cinderclient/v2/limits.py deleted file mode 100644 index dd1666da0..000000000 --- a/cinderclient/v2/limits.py +++ /dev/null @@ -1,99 +0,0 @@ -# Copyright 2013 OpenStack Foundation -# -# 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. -"""Limits interface (v2 extension)""" - -from cinderclient import base -from cinderclient import utils - - -class Limits(base.Resource): - """A collection of RateLimit and AbsoluteLimit objects.""" - - def __repr__(self): - return "" - - @property - def absolute(self): - for (name, value) in list(self._info['absolute'].items()): - yield AbsoluteLimit(name, value) - - @property - def rate(self): - for group in self._info['rate']: - uri = group['uri'] - regex = group['regex'] - for rate in group['limit']: - yield RateLimit(rate['verb'], uri, regex, rate['value'], - rate['remaining'], rate['unit'], - rate['next-available']) - - -class RateLimit(object): - """Data model that represents a flattened view of a single rate limit.""" - - def __init__(self, verb, uri, regex, value, remain, - unit, next_available): - self.verb = verb - self.uri = uri - self.regex = regex - self.value = value - self.remain = remain - self.unit = unit - self.next_available = next_available - - def __eq__(self, other): - return self.uri == other.uri \ - and self.regex == other.regex \ - and self.value == other.value \ - and self.verb == other.verb \ - and self.remain == other.remain \ - and self.unit == other.unit \ - and self.next_available == other.next_available - - def __repr__(self): - return "" % (self.verb, self.uri) - - -class AbsoluteLimit(object): - """Data model that represents a single absolute limit.""" - - def __init__(self, name, value): - self.name = name - self.value = value - - def __eq__(self, other): - return self.value == other.value and self.name == other.name - - def __repr__(self): - return "" % (self.name) - - -class LimitsManager(base.Manager): - """Manager object used to interact with limits resource.""" - - resource_class = Limits - - def get(self, tenant_id=None): - """Get a specific extension. - - :rtype: :class:`Limits` - """ - opts = {} - if tenant_id: - opts['tenant_id'] = tenant_id - - query_string = utils.build_query_param(opts) - - return self._get("/limits%s" % query_string, "limits") diff --git a/cinderclient/v2/pools.py b/cinderclient/v2/pools.py deleted file mode 100644 index 2a55e77bc..000000000 --- a/cinderclient/v2/pools.py +++ /dev/null @@ -1,60 +0,0 @@ -# Copyright (C) 2015 Hewlett-Packard Development Company, L.P. -# 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. - -"""Pools interface (v2 extension)""" - -from cinderclient import base - - -class Pool(base.Resource): - NAME_ATTR = 'name' - - def __repr__(self): - return "" % self.name - - -class PoolManager(base.Manager): - """Manage :class:`Pool` resources.""" - resource_class = Pool - - def list(self, detailed=False): - """Lists all - - :rtype: list of :class:`Pool` - """ - if detailed is True: - pools = self._list("/scheduler-stats/get_pools?detail=True", - "pools") - # Other than the name, all of the pool data is buried below in - # a 'capabilities' dictionary. In order to be consistent with the - # get-pools command line, these elements are moved up a level to - # be attributes of the pool itself. - for pool in pools: - if hasattr(pool, 'capabilities'): - for k, v in pool.capabilities.items(): - setattr(pool, k, v) - - # Remove the capabilities dictionary since all of its - # elements have been copied up to the containing pool - del pool.capabilities - return pools - else: - pools = self._list("/scheduler-stats/get_pools", "pools") - - # avoid cluttering the basic pool list with capabilities dict - for pool in pools: - if hasattr(pool, 'capabilities'): - del pool.capabilities - return pools diff --git a/cinderclient/v2/qos_specs.py b/cinderclient/v2/qos_specs.py deleted file mode 100644 index 147132a8a..000000000 --- a/cinderclient/v2/qos_specs.py +++ /dev/null @@ -1,155 +0,0 @@ -# Copyright (c) 2013 eBay Inc. -# Copyright (c) OpenStack Foundation -# -# 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. - -""" -QoS Specs interface. -""" - -from cinderclient.apiclient import base as common_base -from cinderclient import base - - -class QoSSpecs(base.Resource): - """QoS specs entity represents quality-of-service parameters/requirements. - - A QoS specs is a set of parameters or requirements for quality-of-service - purpose, which can be associated with volume types (for now). In future, - QoS specs may be extended to be associated other entities, such as single - volume. - """ - def __repr__(self): - return "" % self.name - - def delete(self): - return self.manager.delete(self) - - -class QoSSpecsManager(base.ManagerWithFind): - """ - Manage :class:`QoSSpecs` resources. - """ - resource_class = QoSSpecs - - def list(self, search_opts=None): - """Get a list of all qos specs. - - :rtype: list of :class:`QoSSpecs`. - """ - return self._list("/qos-specs", "qos_specs") - - def get(self, qos_specs): - """Get a specific qos specs. - - :param qos_specs: The ID of the :class:`QoSSpecs` to get. - :rtype: :class:`QoSSpecs` - """ - return self._get("/qos-specs/%s" % base.getid(qos_specs), "qos_specs") - - def delete(self, qos_specs, force=False): - """Delete a specific qos specs. - - :param qos_specs: The ID of the :class:`QoSSpecs` to be removed. - :param force: Flag that indicates whether to delete target qos specs - if it was in-use. - """ - return self._delete("/qos-specs/%s?force=%s" % - (base.getid(qos_specs), force)) - - def create(self, name, specs): - """Create a qos specs. - - :param name: Descriptive name of the qos specs, must be unique - :param specs: A dict of key/value pairs to be set - :rtype: :class:`QoSSpecs` - """ - - body = { - "qos_specs": { - "name": name, - } - } - - body["qos_specs"].update(specs) - return self._create("/qos-specs", body, "qos_specs") - - def set_keys(self, qos_specs, specs): - """Add/Update keys in qos specs. - - :param qos_specs: The ID of qos specs - :param specs: A dict of key/value pairs to be set - :rtype: :class:`QoSSpecs` - """ - - body = { - "qos_specs": {} - } - - body["qos_specs"].update(specs) - return self._update("/qos-specs/%s" % qos_specs, body) - - def unset_keys(self, qos_specs, specs): - """Remove keys from a qos specs. - - :param qos_specs: The ID of qos specs - :param specs: A list of key to be unset - :rtype: :class:`QoSSpecs` - """ - - body = {'keys': specs} - - return self._update("/qos-specs/%s/delete_keys" % qos_specs, - body) - - def get_associations(self, qos_specs): - """Get associated entities of a qos specs. - - :param qos_specs: The id of the :class: `QoSSpecs` - :return: a list of entities that associated with specific qos specs. - """ - return self._list("/qos-specs/%s/associations" % base.getid(qos_specs), - "qos_associations") - - def associate(self, qos_specs, vol_type_id): - """Associate a volume type with specific qos specs. - - :param qos_specs: The qos specs to be associated with - :param vol_type_id: The volume type id to be associated with - """ - resp, body = self.api.client.get( - "/qos-specs/%s/associate?vol_type_id=%s" % - (base.getid(qos_specs), vol_type_id)) - return common_base.TupleWithMeta((resp, body), resp) - - def disassociate(self, qos_specs, vol_type_id): - """Disassociate qos specs from volume type. - - :param qos_specs: The qos specs to be associated with - :param vol_type_id: The volume type id to be associated with - """ - resp, body = self.api.client.get( - "/qos-specs/%s/disassociate?vol_type_id=%s" % - (base.getid(qos_specs), vol_type_id)) - return common_base.TupleWithMeta((resp, body), resp) - - def disassociate_all(self, qos_specs): - """Disassociate all entities from specific qos specs. - - :param qos_specs: The qos specs to be associated with - """ - resp, body = self.api.client.get( - "/qos-specs/%s/disassociate_all" % - base.getid(qos_specs)) - return common_base.TupleWithMeta((resp, body), resp) diff --git a/cinderclient/v2/quota_classes.py b/cinderclient/v2/quota_classes.py deleted file mode 100644 index 1958fa133..000000000 --- a/cinderclient/v2/quota_classes.py +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright (c) 2013 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 cinderclient import base - - -class QuotaClassSet(base.Resource): - - @property - def id(self): - """Needed by base.Resource to self-refresh and be indexed.""" - return self.class_name - - def update(self, *args, **kwargs): - return self.manager.update(self.class_name, *args, **kwargs) - - -class QuotaClassSetManager(base.Manager): - resource_class = QuotaClassSet - - def get(self, class_name): - return self._get("/os-quota-class-sets/%s" % (class_name), - "quota_class_set") - - def update(self, class_name, **updates): - quota_class_set = {} - - for update in updates: - quota_class_set[update] = updates[update] - - result = self._update('/os-quota-class-sets/%s' % (class_name), - {'quota_class_set': quota_class_set}) - return self.resource_class(self, - result['quota_class_set'], loaded=True, - resp=result.request_ids) diff --git a/cinderclient/v2/quotas.py b/cinderclient/v2/quotas.py deleted file mode 100644 index bebf32a39..000000000 --- a/cinderclient/v2/quotas.py +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright (c) 2013 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 cinderclient import base - - -class QuotaSet(base.Resource): - - @property - def id(self): - """Needed by base.Resource to self-refresh and be indexed.""" - return self.tenant_id - - def update(self, *args, **kwargs): - return self.manager.update(self.tenant_id, *args, **kwargs) - - -class QuotaSetManager(base.Manager): - resource_class = QuotaSet - - def get(self, tenant_id, usage=False): - if hasattr(tenant_id, 'tenant_id'): - tenant_id = tenant_id.tenant_id - return self._get("/os-quota-sets/%s?usage=%s" % (tenant_id, usage), - "quota_set") - - def update(self, tenant_id, **updates): - body = {'quota_set': {'tenant_id': tenant_id}} - - for update in updates: - body['quota_set'][update] = updates[update] - - result = self._update('/os-quota-sets/%s' % (tenant_id), body) - return self.resource_class(self, result['quota_set'], loaded=True, - resp=result.request_ids) - - def defaults(self, tenant_id): - return self._get('/os-quota-sets/%s/defaults' % tenant_id, - 'quota_set') - - def delete(self, tenant_id): - if hasattr(tenant_id, 'tenant_id'): - tenant_id = tenant_id.tenant_id - return self._delete("/os-quota-sets/%s" % tenant_id) diff --git a/cinderclient/v2/services.py b/cinderclient/v2/services.py deleted file mode 100644 index 68ea1bca9..000000000 --- a/cinderclient/v2/services.py +++ /dev/null @@ -1,80 +0,0 @@ -# Copyright (c) 2013 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. - -""" -service interface -""" - -from cinderclient import base - - -class Service(base.Resource): - - def __repr__(self): - return "" % (self.binary, self.host) - - -class ServiceManager(base.ManagerWithFind): - resource_class = Service - - def list(self, host=None, binary=None): - """ - Describes service list for host. - - :param host: destination host name. - :param binary: service binary. - """ - url = "/os-services" - filters = [] - if host: - filters.append("host=%s" % host) - if binary: - filters.append("binary=%s" % binary) - if filters: - url = "%s?%s" % (url, "&".join(filters)) - return self._list(url, "services") - - def enable(self, host, binary): - """Enable the service specified by hostname and binary.""" - body = {"host": host, "binary": binary} - result = self._update("/os-services/enable", body) - return self.resource_class(self, result, resp=result.request_ids) - - def disable(self, host, binary): - """Disable the service specified by hostname and binary.""" - body = {"host": host, "binary": binary} - result = self._update("/os-services/disable", body) - return self.resource_class(self, result, resp=result.request_ids) - - def disable_log_reason(self, host, binary, reason): - """Disable the service with reason.""" - body = {"host": host, "binary": binary, "disabled_reason": reason} - result = self._update("/os-services/disable-log-reason", body) - return self.resource_class(self, result, resp=result.request_ids) - - def freeze_host(self, host): - """Freeze the service specified by hostname.""" - body = {"host": host} - return self._update("/os-services/freeze", body) - - def thaw_host(self, host): - """Thaw the service specified by hostname.""" - body = {"host": host} - return self._update("/os-services/thaw", body) - - def failover_host(self, host, backend_id): - """Failover a replicated backend by hostname.""" - body = {"host": host, "backend_id": backend_id} - return self._update("/os-services/failover_host", body) diff --git a/cinderclient/v2/volume_backups.py b/cinderclient/v2/volume_backups.py deleted file mode 100644 index bcf3e01fd..000000000 --- a/cinderclient/v2/volume_backups.py +++ /dev/null @@ -1,137 +0,0 @@ -# Copyright (C) 2013 Hewlett-Packard Development Company, L.P. -# 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. - -""" -Volume Backups interface (v2 extension). -""" - -from cinderclient.apiclient import base as common_base -from cinderclient import base - - -class VolumeBackup(base.Resource): - """A volume backup is a block level backup of a volume.""" - - def __repr__(self): - return "" % self.id - - def delete(self, force=False): - """Delete this volume backup.""" - return self.manager.delete(self, force) - - def reset_state(self, state): - return self.manager.reset_state(self, state) - - def update(self, **kwargs): - """Update the name or description for this backup.""" - return self.manager.update(self, **kwargs) - - -class VolumeBackupManager(base.ManagerWithFind): - """Manage :class:`VolumeBackup` resources.""" - resource_class = VolumeBackup - - def create(self, volume_id, container=None, - name=None, description=None, - incremental=False, force=False, - snapshot_id=None): - """Creates a volume backup. - - :param volume_id: The ID of the volume to backup. - :param container: The name of the backup service container. - :param name: The name of the backup. - :param description: The description of the backup. - :param incremental: Incremental backup. - :param force: If True, allows an in-use volume to be backed up. - :param snapshot_id: The ID of the snapshot to backup. This should - be a snapshot of the src volume, when specified, - the new backup will be based on the snapshot. - :rtype: :class:`VolumeBackup` - """ - body = {'backup': {'volume_id': volume_id, - 'container': container, - 'name': name, - 'description': description, - 'incremental': incremental, - 'force': force, - 'snapshot_id': snapshot_id, }} - return self._create('/backups', body, 'backup') - - def get(self, backup_id): - """Show volume backup details. - - :param backup_id: The ID of the backup to display. - :rtype: :class:`VolumeBackup` - """ - return self._get("/backups/%s" % backup_id, "backup") - - def list(self, detailed=True, search_opts=None, marker=None, limit=None, - sort=None): - """Get a list of all volume backups. - - :rtype: list of :class:`VolumeBackup` - """ - resource_type = "backups" - url = self._build_list_url(resource_type, detailed=detailed, - search_opts=search_opts, marker=marker, - limit=limit, sort=sort) - return self._list(url, resource_type, limit=limit) - - def delete(self, backup, force=False): - """Delete a volume backup. - - :param backup: The :class:`VolumeBackup` to delete. - :param force: Allow delete in state other than error or available. - """ - if force: - return self._action('os-force_delete', backup) - else: - return self._delete("/backups/%s" % base.getid(backup)) - - def reset_state(self, backup, state): - """Update the specified volume backup with the provided state.""" - return self._action('os-reset_status', backup, - {'status': state} if state else {}) - - def _action(self, action, backup, info=None, **kwargs): - """Perform a volume backup action.""" - body = {action: info} - self.run_hooks('modify_body_for_action', body, **kwargs) - url = '/backups/%s/action' % base.getid(backup) - resp, body = self.api.client.post(url, body=body) - return common_base.TupleWithMeta((resp, body), resp) - - def export_record(self, backup_id): - """Export volume backup metadata record. - - :param backup_id: The ID of the backup to export. - :rtype: A dictionary containing 'backup_url' and 'backup_service'. - """ - resp, body = \ - self.api.client.get("/backups/%s/export_record" % backup_id) - return common_base.DictWithMeta(body['backup-record'], resp) - - def import_record(self, backup_service, backup_url): - """Import volume backup metadata record. - - :param backup_service: Backup service to use for importing the backup - :param backup_url: Backup URL for importing the backup metadata - :rtype: A dictionary containing volume backup metadata. - """ - body = {'backup-record': {'backup_service': backup_service, - 'backup_url': backup_url}} - self.run_hooks('modify_body_for_update', body, 'backup-record') - resp, body = self.api.client.post("/backups/import_record", body=body) - return common_base.DictWithMeta(body['backup'], resp) diff --git a/cinderclient/v2/volume_backups_restore.py b/cinderclient/v2/volume_backups_restore.py deleted file mode 100644 index a76cb090e..000000000 --- a/cinderclient/v2/volume_backups_restore.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright (C) 2013 Hewlett-Packard Development Company, L.P. -# 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. - -"""Volume Backups Restore interface (v2 extension). - -This is part of the Volume Backups interface. -""" - -from cinderclient import base - - -class VolumeBackupsRestore(base.Resource): - """A Volume Backups Restore represents a restore operation.""" - def __repr__(self): - return "" % self.volume_id - - -class VolumeBackupRestoreManager(base.Manager): - """Manage :class:`VolumeBackupsRestore` resources.""" - resource_class = VolumeBackupsRestore - - def restore(self, backup_id, volume_id=None, name=None): - """Restore a backup to a volume. - - :param backup_id: The ID of the backup to restore. - :param volume_id: The ID of the volume to restore the backup to. - :param name : The name for new volume creation to restore. - :rtype: :class:`Restore` - """ - body = {'restore': {'volume_id': volume_id, 'name': name}} - return self._create("/backups/%s/restore" % backup_id, - body, "restore") diff --git a/cinderclient/v2/volume_encryption_types.py b/cinderclient/v2/volume_encryption_types.py deleted file mode 100644 index 9edacf978..000000000 --- a/cinderclient/v2/volume_encryption_types.py +++ /dev/null @@ -1,104 +0,0 @@ -# Copyright (c) 2013 The Johns Hopkins University/Applied Physics Laboratory -# 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. - -""" -Volume Encryption Type interface -""" - -from cinderclient.apiclient import base as common_base -from cinderclient import base - - -class VolumeEncryptionType(base.Resource): - """ - A Volume Encryption Type is a collection of settings used to conduct - encryption for a specific volume type. - """ - def __repr__(self): - return "" % self.encryption_id - - -class VolumeEncryptionTypeManager(base.ManagerWithFind): - """ - Manage :class: `VolumeEncryptionType` resources. - """ - resource_class = VolumeEncryptionType - - def list(self, search_opts=None): - """ - List all volume encryption types. - - :param search_opts: Search options to filter out volume - encryption types - :return: a list of :class: VolumeEncryptionType instances - """ - # Since the encryption type is a volume type extension, we cannot get - # all encryption types without going through all volume types. - volume_types = self.api.volume_types.list() - encryption_types = [] - list_of_resp = [] - for volume_type in volume_types: - encryption_type = self._get("/types/%s/encryption" - % base.getid(volume_type)) - if hasattr(encryption_type, 'volume_type_id'): - encryption_types.append(encryption_type) - - list_of_resp.extend(encryption_type.request_ids) - - return common_base.ListWithMeta(encryption_types, list_of_resp) - - def get(self, volume_type): - """ - Get the volume encryption type for the specified volume type. - - :param volume_type: the volume type to query - :return: an instance of :class: VolumeEncryptionType - """ - return self._get("/types/%s/encryption" % base.getid(volume_type)) - - def create(self, volume_type, specs): - """ - Creates encryption type for a volume type. Default: admin only. - - :param volume_type: the volume type on which to add an encryption type - :param specs: the encryption type specifications to add - :return: an instance of :class: VolumeEncryptionType - """ - body = {'encryption': specs} - return self._create("/types/%s/encryption" % base.getid(volume_type), - body, "encryption") - - def update(self, volume_type, specs): - """ - Update the encryption type information for the specified volume type. - - :param volume_type: the volume type whose encryption type information - must be updated - :param specs: the encryption type specifications to update - :return: an instance of :class: VolumeEncryptionType - """ - body = {'encryption': specs} - return self._update("/types/%s/encryption/provider" % - base.getid(volume_type), body) - - def delete(self, volume_type): - """ - Delete the encryption type information for the specified volume type. - - :param volume_type: the volume type whose encryption type information - must be deleted - """ - return self._delete("/types/%s/encryption/provider" % - base.getid(volume_type)) diff --git a/cinderclient/v2/volume_snapshots.py b/cinderclient/v2/volume_snapshots.py deleted file mode 100644 index a89c135f4..000000000 --- a/cinderclient/v2/volume_snapshots.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright (c) 2013 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. - -"""Volume snapshot interface (v2 extension).""" - -from cinderclient import api_versions -from cinderclient.v3 import volume_snapshots - - -class Snapshot(volume_snapshots.Snapshot): - def list_manageable(self, host, detailed=True, marker=None, limit=None, - offset=None, sort=None): - return self.manager.list_manageable(host, detailed=detailed, - marker=marker, limit=limit, - offset=offset, sort=sort) - - -class SnapshotManager(volume_snapshots.SnapshotManager): - resource_class = Snapshot - - @api_versions.wraps("2.0") - def list_manageable(self, host, detailed=True, marker=None, limit=None, - offset=None, sort=None): - url = self._build_list_url("os-snapshot-manage", detailed=detailed, - search_opts={'host': host}, marker=marker, - limit=limit, offset=offset, sort=sort) - return self._list(url, "manageable-snapshots") diff --git a/cinderclient/v2/volume_transfers.py b/cinderclient/v2/volume_transfers.py deleted file mode 100644 index 00508c0d0..000000000 --- a/cinderclient/v2/volume_transfers.py +++ /dev/null @@ -1,88 +0,0 @@ -# Copyright (C) 2013 Hewlett-Packard Development Company, L.P. -# 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. - -""" -Volume transfer interface (v2 extension). -""" - -from cinderclient import base -from cinderclient import utils - - -class VolumeTransfer(base.Resource): - """Transfer a volume from one tenant to another""" - - def __repr__(self): - return "" % self.id - - def delete(self): - """Delete this volume transfer.""" - return self.manager.delete(self) - - -class VolumeTransferManager(base.ManagerWithFind): - """Manage :class:`VolumeTransfer` resources.""" - resource_class = VolumeTransfer - - def create(self, volume_id, name=None): - """Creates a volume transfer. - - :param volume_id: The ID of the volume to transfer. - :param name: The name of the transfer. - :rtype: :class:`VolumeTransfer` - """ - body = {'transfer': {'volume_id': volume_id, - 'name': name}} - return self._create('/os-volume-transfer', body, 'transfer') - - def accept(self, transfer_id, auth_key): - """Accept a volume transfer. - - :param transfer_id: The ID of the transfer to accept. - :param auth_key: The auth_key of the transfer. - :rtype: :class:`VolumeTransfer` - """ - body = {'accept': {'auth_key': auth_key}} - return self._create('/os-volume-transfer/%s/accept' % transfer_id, - body, 'transfer') - - def get(self, transfer_id): - """Show details of a volume transfer. - - :param transfer_id: The ID of the volume transfer to display. - :rtype: :class:`VolumeTransfer` - """ - return self._get("/os-volume-transfer/%s" % transfer_id, "transfer") - - def list(self, detailed=True, search_opts=None): - """Get a list of all volume transfer. - - :rtype: list of :class:`VolumeTransfer` - """ - query_string = utils.build_query_param(search_opts) - - detail = "" - if detailed: - detail = "/detail" - - return self._list("/os-volume-transfer%s%s" % (detail, query_string), - "transfers") - - def delete(self, transfer_id): - """Delete a volume transfer. - - :param transfer_id: The :class:`VolumeTransfer` to delete. - """ - return self._delete("/os-volume-transfer/%s" % base.getid(transfer_id)) diff --git a/cinderclient/v2/volume_type_access.py b/cinderclient/v2/volume_type_access.py deleted file mode 100644 index bdd2e7028..000000000 --- a/cinderclient/v2/volume_type_access.py +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright 2014 OpenStack Foundation -# -# 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. - -"""Volume type access interface.""" - -from cinderclient.apiclient import base as common_base -from cinderclient import base - - -class VolumeTypeAccess(base.Resource): - def __repr__(self): - return "" % self.project_id - - -class VolumeTypeAccessManager(base.ManagerWithFind): - """ - Manage :class:`VolumeTypeAccess` resources. - """ - resource_class = VolumeTypeAccess - - def list(self, volume_type): - return self._list( - '/types/%s/os-volume-type-access' % base.getid(volume_type), - 'volume_type_access') - - def add_project_access(self, volume_type, project): - """Add a project to the given volume type access list.""" - info = {'project': project} - return self._action('addProjectAccess', volume_type, info) - - def remove_project_access(self, volume_type, project): - """Remove a project from the given volume type access list.""" - info = {'project': project} - return self._action('removeProjectAccess', volume_type, info) - - def _action(self, action, volume_type, info, **kwargs): - """Perform a volume type action.""" - body = {action: info} - self.run_hooks('modify_body_for_action', body, **kwargs) - url = '/types/%s/action' % base.getid(volume_type) - resp, body = self.api.client.post(url, body=body) - return common_base.TupleWithMeta((resp, body), resp) diff --git a/cinderclient/v2/volume_types.py b/cinderclient/v2/volume_types.py deleted file mode 100644 index 546d776d2..000000000 --- a/cinderclient/v2/volume_types.py +++ /dev/null @@ -1,153 +0,0 @@ -# Copyright (c) 2013 OpenStack Foundation -# -# 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. - -"""Volume Type interface.""" - -from cinderclient.apiclient import base as common_base -from cinderclient import base - - -class VolumeType(base.Resource): - """A Volume Type is the type of volume to be created.""" - def __repr__(self): - return "" % self.name - - @property - def is_public(self): - """ - Provide a user-friendly accessor to os-volume-type-access:is_public - """ - return self._info.get("os-volume-type-access:is_public", - self._info.get("is_public", 'N/A')) - - def get_keys(self): - """Get extra specs from a volume type. - - :param vol_type: The :class:`VolumeType` to get extra specs from - """ - _resp, body = self.manager.api.client.get( - "/types/%s/extra_specs" % - base.getid(self)) - return body["extra_specs"] - - def set_keys(self, metadata): - """Set extra specs on a volume type. - - :param type : The :class:`VolumeType` to set extra spec on - :param metadata: A dict of key/value pairs to be set - """ - body = {'extra_specs': metadata} - return self.manager._create( - "/types/%s/extra_specs" % base.getid(self), - body, - "extra_specs", - return_raw=True) - - def unset_keys(self, keys): - """Unset extra specs on a volue type. - - :param type_id: The :class:`VolumeType` to unset extra spec on - :param keys: A list of keys to be unset - """ - - # NOTE(jdg): This wasn't actually doing all of the keys before - # the return in the loop resulted in only ONE key being unset, - # since on success the return was ListWithMeta class, we'll only - # interrupt the loop and if an exception is raised. - response_list = [] - for k in keys: - resp, body = self.manager._delete( - "/types/%s/extra_specs/%s" % ( - base.getid(self), k)) - response_list.append(resp) - - return common_base.ListWithMeta([], response_list) - - -class VolumeTypeManager(base.ManagerWithFind): - """Manage :class:`VolumeType` resources.""" - resource_class = VolumeType - - def list(self, search_opts=None, is_public=None): - """Lists all volume types. - - :rtype: list of :class:`VolumeType`. - """ - query_string = '' - if not is_public: - query_string = '?is_public=%s' % is_public - return self._list("/types%s" % (query_string), "volume_types") - - def get(self, volume_type): - """Get a specific volume type. - - :param volume_type: The ID of the :class:`VolumeType` to get. - :rtype: :class:`VolumeType` - """ - return self._get("/types/%s" % base.getid(volume_type), "volume_type") - - def default(self): - """Get the default volume type. - - :rtype: :class:`VolumeType` - """ - return self._get("/types/default", "volume_type") - - def delete(self, volume_type): - """Deletes a specific volume_type. - - :param volume_type: The name or ID of the :class:`VolumeType` to get. - """ - return self._delete("/types/%s" % base.getid(volume_type)) - - def create(self, name, description=None, is_public=True): - """Creates a volume type. - - :param name: Descriptive name of the volume type - :param description: Description of the volume type - :param is_public: Volume type visibility - :rtype: :class:`VolumeType` - """ - - body = { - "volume_type": { - "name": name, - "description": description, - "os-volume-type-access:is_public": is_public, - } - } - - return self._create("/types", body, "volume_type") - - def update(self, volume_type, name=None, description=None, is_public=None): - """Update the name and/or description for a volume type. - - :param volume_type: The ID of the :class:`VolumeType` to update. - :param name: Descriptive name of the volume type. - :param description: Description of the volume type. - :rtype: :class:`VolumeType` - """ - - body = { - "volume_type": { - "name": name, - "description": description - } - } - if is_public is not None: - body["volume_type"]["is_public"] = is_public - - return self._update("/types/%s" % base.getid(volume_type), - body, response_key="volume_type") diff --git a/cinderclient/v3/capabilities.py b/cinderclient/v3/capabilities.py index 76a3b4e0e..c837a4009 100644 --- a/cinderclient/v3/capabilities.py +++ b/cinderclient/v3/capabilities.py @@ -16,4 +16,24 @@ """Capabilities interface (v3 extension)""" -from cinderclient.v2.capabilities import * # noqa +from cinderclient import base + + +class Capabilities(base.Resource): + NAME_ATTR = 'name' + + def __repr__(self): + return "" % self._info.get('namespace') + + +class CapabilitiesManager(base.Manager): + """Manage :class:`Capabilities` resources.""" + resource_class = Capabilities + + def get(self, host): + """Show backend volume stats and properties. + + :param host: Specified backend to obtain volume stats and properties. + :rtype: :class:`Capabilities` + """ + return self._get('/capabilities/%s' % host, None) diff --git a/cinderclient/v3/cgsnapshots.py b/cinderclient/v3/cgsnapshots.py index 0ed0cdcf8..1f5abc6e3 100644 --- a/cinderclient/v3/cgsnapshots.py +++ b/cinderclient/v3/cgsnapshots.py @@ -15,4 +15,98 @@ """cgsnapshot interface (v3 extension).""" -from cinderclient.v2.cgsnapshots import * # noqa +from cinderclient.apiclient import base as common_base +from cinderclient import base +from cinderclient import utils + + +class Cgsnapshot(base.Resource): + """A cgsnapshot is snapshot of a consistency group.""" + def __repr__(self): + return "" % self.id + + def delete(self): + """Delete this cgsnapshot.""" + return self.manager.delete(self) + + def update(self, **kwargs): + """Update the name or description for this cgsnapshot.""" + return self.manager.update(self, **kwargs) + + +class CgsnapshotManager(base.ManagerWithFind): + """Manage :class:`Cgsnapshot` resources.""" + resource_class = Cgsnapshot + + def create(self, consistencygroup_id, name=None, description=None, + user_id=None, + project_id=None): + """Creates a cgsnapshot. + + :param consistencygroup: Name or uuid of a consistency group + :param name: Name of the cgsnapshot + :param description: Description of the cgsnapshot + :param user_id: User id derived from context + :param project_id: Project id derived from context + :rtype: :class:`Cgsnapshot` + """ + + body = {'cgsnapshot': {'consistencygroup_id': consistencygroup_id, + 'name': name, + 'description': description, + 'user_id': user_id, + 'project_id': project_id, + 'status': "creating", + }} + + return self._create('/cgsnapshots', body, 'cgsnapshot') + + def get(self, cgsnapshot_id): + """Get a cgsnapshot. + + :param cgsnapshot_id: The ID of the cgsnapshot to get. + :rtype: :class:`Cgsnapshot` + """ + return self._get("/cgsnapshots/%s" % cgsnapshot_id, "cgsnapshot") + + def list(self, detailed=True, search_opts=None): + """Lists all cgsnapshots. + + :rtype: list of :class:`Cgsnapshot` + """ + query_string = utils.build_query_param(search_opts) + + detail = "" + if detailed: + detail = "/detail" + + return self._list("/cgsnapshots%s%s" % (detail, query_string), + "cgsnapshots") + + def delete(self, cgsnapshot): + """Delete a cgsnapshot. + + :param cgsnapshot: The :class:`Cgsnapshot` to delete. + """ + return self._delete("/cgsnapshots/%s" % base.getid(cgsnapshot)) + + def update(self, cgsnapshot, **kwargs): + """Update the name or description for a cgsnapshot. + + :param cgsnapshot: The :class:`Cgsnapshot` to update. + """ + if not kwargs: + return + + body = {"cgsnapshot": kwargs} + + return self._update("/cgsnapshots/%s" % base.getid(cgsnapshot), body) + + def _action(self, action, cgsnapshot, info=None, **kwargs): + """Perform a cgsnapshot "action." + """ + body = {action: info} + self.run_hooks('modify_body_for_action', body, **kwargs) + url = '/cgsnapshots/%s/action' % base.getid(cgsnapshot) + resp, body = self.api.client.post(url, body=body) + return common_base.TupleWithMeta((resp, body), resp) diff --git a/cinderclient/v3/consistencygroups.py b/cinderclient/v3/consistencygroups.py index a29fd8332..13bc2eefb 100644 --- a/cinderclient/v3/consistencygroups.py +++ b/cinderclient/v3/consistencygroups.py @@ -15,4 +15,135 @@ """Consistencygroup interface (v3 extension).""" -from cinderclient.v2.consistencygroups import * # noqa +from cinderclient.apiclient import base as common_base +from cinderclient import base +from cinderclient import utils + + +class Consistencygroup(base.Resource): + """A Consistencygroup of volumes.""" + def __repr__(self): + return "" % self.id + + def delete(self, force='False'): + """Delete this consistency group.""" + return self.manager.delete(self, force) + + def update(self, **kwargs): + """Update the name or description for this consistency group.""" + return self.manager.update(self, **kwargs) + + +class ConsistencygroupManager(base.ManagerWithFind): + """Manage :class:`Consistencygroup` resources.""" + resource_class = Consistencygroup + + def create(self, volume_types, name=None, + description=None, user_id=None, + project_id=None, availability_zone=None): + """Creates a consistency group. + + :param name: Name of the ConsistencyGroup + :param description: Description of the ConsistencyGroup + :param volume_types: Types of volume + :param user_id: User id derived from context + :param project_id: Project id derived from context + :param availability_zone: Availability Zone to use + :rtype: :class:`Consistencygroup` + """ + + body = {'consistencygroup': {'name': name, + 'description': description, + 'volume_types': volume_types, + 'user_id': user_id, + 'project_id': project_id, + 'availability_zone': availability_zone, + 'status': "creating", + }} + + return self._create('/consistencygroups', body, 'consistencygroup') + + def create_from_src(self, cgsnapshot_id, source_cgid, name=None, + description=None, user_id=None, + project_id=None): + """Creates a consistency group from a cgsnapshot or a source CG. + + :param cgsnapshot_id: UUID of a CGSnapshot + :param source_cgid: UUID of a source CG + :param name: Name of the ConsistencyGroup + :param description: Description of the ConsistencyGroup + :param user_id: User id derived from context + :param project_id: Project id derived from context + :rtype: A dictionary containing Consistencygroup metadata + """ + body = {'consistencygroup-from-src': {'name': name, + 'description': description, + 'cgsnapshot_id': cgsnapshot_id, + 'source_cgid': source_cgid, + 'user_id': user_id, + 'project_id': project_id, + 'status': "creating", + }} + + self.run_hooks('modify_body_for_update', body, + 'consistencygroup-from-src') + resp, body = self.api.client.post( + "/consistencygroups/create_from_src", body=body) + return common_base.DictWithMeta(body['consistencygroup'], resp) + + def get(self, group_id): + """Get a consistency group. + + :param group_id: The ID of the consistency group to get. + :rtype: :class:`Consistencygroup` + """ + return self._get("/consistencygroups/%s" % group_id, + "consistencygroup") + + def list(self, detailed=True, search_opts=None): + """Lists all consistency groups. + + :rtype: list of :class:`Consistencygroup` + """ + + query_string = utils.build_query_param(search_opts) + + detail = "" + if detailed: + detail = "/detail" + + return self._list("/consistencygroups%s%s" % (detail, query_string), + "consistencygroups") + + def delete(self, consistencygroup, force=False): + """Delete a consistency group. + + :param Consistencygroup: The :class:`Consistencygroup` to delete. + """ + body = {'consistencygroup': {'force': force}} + self.run_hooks('modify_body_for_action', body, 'consistencygroup') + url = '/consistencygroups/%s/delete' % base.getid(consistencygroup) + resp, body = self.api.client.post(url, body=body) + return common_base.TupleWithMeta((resp, body), resp) + + def update(self, consistencygroup, **kwargs): + """Update the name or description for a consistency group. + + :param Consistencygroup: The :class:`Consistencygroup` to update. + """ + if not kwargs: + return + + body = {"consistencygroup": kwargs} + + return self._update("/consistencygroups/%s" % + base.getid(consistencygroup), body) + + def _action(self, action, consistencygroup, info=None, **kwargs): + """Perform a consistency group "action." + """ + body = {action: info} + self.run_hooks('modify_body_for_action', body, **kwargs) + url = '/consistencygroups/%s/action' % base.getid(consistencygroup) + resp, body = self.api.client.post(url, body=body) + return common_base.TupleWithMeta((resp, body), resp) diff --git a/cinderclient/v3/contrib/list_extensions.py b/cinderclient/v3/contrib/list_extensions.py index 0b5e84584..937d34b53 100644 --- a/cinderclient/v3/contrib/list_extensions.py +++ b/cinderclient/v3/contrib/list_extensions.py @@ -13,4 +13,32 @@ # License for the specific language governing permissions and limitations # under the License. -from cinderclient.v2.contrib.list_extensions import * # noqa +from cinderclient import base +from cinderclient import utils + + +class ListExtResource(base.Resource): + @property + def summary(self): + descr = self.description.strip() + if not descr: + return '??' + lines = descr.split("\n") + if len(lines) == 1: + return lines[0] + else: + return lines[0] + "..." + + +class ListExtManager(base.Manager): + resource_class = ListExtResource + + def show_all(self): + return self._list("/extensions", 'extensions') + + +def do_list_extensions(client, _args): + """Lists all available os-api extensions.""" + extensions = client.list_extensions.show_all() + fields = ["Name", "Summary", "Alias", "Updated"] + utils.print_list(extensions, fields) diff --git a/cinderclient/v3/limits.py b/cinderclient/v3/limits.py index 29d1dca45..69f053f11 100644 --- a/cinderclient/v3/limits.py +++ b/cinderclient/v3/limits.py @@ -13,4 +13,86 @@ # See the License for the specific language governing permissions and # limitations under the License. -from cinderclient.v2.limits import * # noqa +from cinderclient import base +from cinderclient import utils + + +class Limits(base.Resource): + """A collection of RateLimit and AbsoluteLimit objects.""" + + def __repr__(self): + return "" + + @property + def absolute(self): + for (name, value) in list(self._info['absolute'].items()): + yield AbsoluteLimit(name, value) + + @property + def rate(self): + for group in self._info['rate']: + uri = group['uri'] + regex = group['regex'] + for rate in group['limit']: + yield RateLimit(rate['verb'], uri, regex, rate['value'], + rate['remaining'], rate['unit'], + rate['next-available']) + + +class RateLimit(object): + """Data model that represents a flattened view of a single rate limit.""" + + def __init__(self, verb, uri, regex, value, remain, + unit, next_available): + self.verb = verb + self.uri = uri + self.regex = regex + self.value = value + self.remain = remain + self.unit = unit + self.next_available = next_available + + def __eq__(self, other): + return self.uri == other.uri \ + and self.regex == other.regex \ + and self.value == other.value \ + and self.verb == other.verb \ + and self.remain == other.remain \ + and self.unit == other.unit \ + and self.next_available == other.next_available + + def __repr__(self): + return "" % (self.verb, self.uri) + + +class AbsoluteLimit(object): + """Data model that represents a single absolute limit.""" + + def __init__(self, name, value): + self.name = name + self.value = value + + def __eq__(self, other): + return self.value == other.value and self.name == other.name + + def __repr__(self): + return "" % (self.name) + + +class LimitsManager(base.Manager): + """Manager object used to interact with limits resource.""" + + resource_class = Limits + + def get(self, tenant_id=None): + """Get a specific extension. + + :rtype: :class:`Limits` + """ + opts = {} + if tenant_id: + opts['tenant_id'] = tenant_id + + query_string = utils.build_query_param(opts) + + return self._get("/limits%s" % query_string, "limits") diff --git a/cinderclient/v3/pools.py b/cinderclient/v3/pools.py index f4ab422aa..5303f8422 100644 --- a/cinderclient/v3/pools.py +++ b/cinderclient/v3/pools.py @@ -15,4 +15,46 @@ """Pools interface (v3 extension)""" -from cinderclient.v2.pools import * # noqa +from cinderclient import base + + +class Pool(base.Resource): + NAME_ATTR = 'name' + + def __repr__(self): + return "" % self.name + + +class PoolManager(base.Manager): + """Manage :class:`Pool` resources.""" + resource_class = Pool + + def list(self, detailed=False): + """Lists all + + :rtype: list of :class:`Pool` + """ + if detailed is True: + pools = self._list("/scheduler-stats/get_pools?detail=True", + "pools") + # Other than the name, all of the pool data is buried below in + # a 'capabilities' dictionary. In order to be consistent with the + # get-pools command line, these elements are moved up a level to + # be attributes of the pool itself. + for pool in pools: + if hasattr(pool, 'capabilities'): + for k, v in pool.capabilities.items(): + setattr(pool, k, v) + + # Remove the capabilities dictionary since all of its + # elements have been copied up to the containing pool + del pool.capabilities + return pools + else: + pools = self._list("/scheduler-stats/get_pools", "pools") + + # avoid cluttering the basic pool list with capabilities dict + for pool in pools: + if hasattr(pool, 'capabilities'): + del pool.capabilities + return pools diff --git a/cinderclient/v3/qos_specs.py b/cinderclient/v3/qos_specs.py index f70b56949..972316482 100644 --- a/cinderclient/v3/qos_specs.py +++ b/cinderclient/v3/qos_specs.py @@ -19,4 +19,138 @@ QoS Specs interface. """ -from cinderclient.v2.qos_specs import * # noqa +from cinderclient.apiclient import base as common_base +from cinderclient import base + + +class QoSSpecs(base.Resource): + """QoS specs entity represents quality-of-service parameters/requirements. + + A QoS specs is a set of parameters or requirements for quality-of-service + purpose, which can be associated with volume types (for now). In future, + QoS specs may be extended to be associated other entities, such as single + volume. + """ + def __repr__(self): + return "" % self.name + + def delete(self): + return self.manager.delete(self) + + +class QoSSpecsManager(base.ManagerWithFind): + """ + Manage :class:`QoSSpecs` resources. + """ + resource_class = QoSSpecs + + def list(self, search_opts=None): + """Get a list of all qos specs. + + :rtype: list of :class:`QoSSpecs`. + """ + return self._list("/qos-specs", "qos_specs") + + def get(self, qos_specs): + """Get a specific qos specs. + + :param qos_specs: The ID of the :class:`QoSSpecs` to get. + :rtype: :class:`QoSSpecs` + """ + return self._get("/qos-specs/%s" % base.getid(qos_specs), "qos_specs") + + def delete(self, qos_specs, force=False): + """Delete a specific qos specs. + + :param qos_specs: The ID of the :class:`QoSSpecs` to be removed. + :param force: Flag that indicates whether to delete target qos specs + if it was in-use. + """ + return self._delete("/qos-specs/%s?force=%s" % + (base.getid(qos_specs), force)) + + def create(self, name, specs): + """Create a qos specs. + + :param name: Descriptive name of the qos specs, must be unique + :param specs: A dict of key/value pairs to be set + :rtype: :class:`QoSSpecs` + """ + + body = { + "qos_specs": { + "name": name, + } + } + + body["qos_specs"].update(specs) + return self._create("/qos-specs", body, "qos_specs") + + def set_keys(self, qos_specs, specs): + """Add/Update keys in qos specs. + + :param qos_specs: The ID of qos specs + :param specs: A dict of key/value pairs to be set + :rtype: :class:`QoSSpecs` + """ + + body = { + "qos_specs": {} + } + + body["qos_specs"].update(specs) + return self._update("/qos-specs/%s" % qos_specs, body) + + def unset_keys(self, qos_specs, specs): + """Remove keys from a qos specs. + + :param qos_specs: The ID of qos specs + :param specs: A list of key to be unset + :rtype: :class:`QoSSpecs` + """ + + body = {'keys': specs} + + return self._update("/qos-specs/%s/delete_keys" % qos_specs, + body) + + def get_associations(self, qos_specs): + """Get associated entities of a qos specs. + + :param qos_specs: The id of the :class: `QoSSpecs` + :return: a list of entities that associated with specific qos specs. + """ + return self._list("/qos-specs/%s/associations" % base.getid(qos_specs), + "qos_associations") + + def associate(self, qos_specs, vol_type_id): + """Associate a volume type with specific qos specs. + + :param qos_specs: The qos specs to be associated with + :param vol_type_id: The volume type id to be associated with + """ + resp, body = self.api.client.get( + "/qos-specs/%s/associate?vol_type_id=%s" % + (base.getid(qos_specs), vol_type_id)) + return common_base.TupleWithMeta((resp, body), resp) + + def disassociate(self, qos_specs, vol_type_id): + """Disassociate qos specs from volume type. + + :param qos_specs: The qos specs to be associated with + :param vol_type_id: The volume type id to be associated with + """ + resp, body = self.api.client.get( + "/qos-specs/%s/disassociate?vol_type_id=%s" % + (base.getid(qos_specs), vol_type_id)) + return common_base.TupleWithMeta((resp, body), resp) + + def disassociate_all(self, qos_specs): + """Disassociate all entities from specific qos specs. + + :param qos_specs: The qos specs to be associated with + """ + resp, body = self.api.client.get( + "/qos-specs/%s/disassociate_all" % + base.getid(qos_specs)) + return common_base.TupleWithMeta((resp, body), resp) diff --git a/cinderclient/v3/quota_classes.py b/cinderclient/v3/quota_classes.py index 693621237..1958fa133 100644 --- a/cinderclient/v3/quota_classes.py +++ b/cinderclient/v3/quota_classes.py @@ -13,4 +13,35 @@ # License for the specific language governing permissions and limitations # under the License. -from cinderclient.v2.quota_classes import * # noqa +from cinderclient import base + + +class QuotaClassSet(base.Resource): + + @property + def id(self): + """Needed by base.Resource to self-refresh and be indexed.""" + return self.class_name + + def update(self, *args, **kwargs): + return self.manager.update(self.class_name, *args, **kwargs) + + +class QuotaClassSetManager(base.Manager): + resource_class = QuotaClassSet + + def get(self, class_name): + return self._get("/os-quota-class-sets/%s" % (class_name), + "quota_class_set") + + def update(self, class_name, **updates): + quota_class_set = {} + + for update in updates: + quota_class_set[update] = updates[update] + + result = self._update('/os-quota-class-sets/%s' % (class_name), + {'quota_class_set': quota_class_set}) + return self.resource_class(self, + result['quota_class_set'], loaded=True, + resp=result.request_ids) diff --git a/cinderclient/v3/quotas.py b/cinderclient/v3/quotas.py index 63dfba835..1295f6064 100644 --- a/cinderclient/v3/quotas.py +++ b/cinderclient/v3/quotas.py @@ -13,10 +13,28 @@ # License for the specific language governing permissions and limitations # under the License. -from cinderclient.v2 import quotas +from cinderclient import base -class QuotaSetManager(quotas.QuotaSetManager): +class QuotaSet(base.Resource): + + @property + def id(self): + """Needed by base.Resource to self-refresh and be indexed.""" + return self.tenant_id + + def update(self, *args, **kwargs): + return self.manager.update(self.tenant_id, *args, **kwargs) + + +class QuotaSetManager(base.Manager): + resource_class = QuotaSet + + def get(self, tenant_id, usage=False): + if hasattr(tenant_id, 'tenant_id'): + tenant_id = tenant_id.tenant_id + return self._get("/os-quota-sets/%s?usage=%s" % (tenant_id, usage), + "quota_set") def update(self, tenant_id, **updates): skip_validation = updates.pop('skip_validation', True) @@ -32,3 +50,12 @@ def update(self, tenant_id, **updates): result = self._update(request_url, body) return self.resource_class(self, result['quota_set'], loaded=True, resp=result.request_ids) + + def defaults(self, tenant_id): + return self._get('/os-quota-sets/%s/defaults' % tenant_id, + 'quota_set') + + def delete(self, tenant_id): + if hasattr(tenant_id, 'tenant_id'): + tenant_id = tenant_id.tenant_id + return self._delete("/os-quota-sets/%s" % tenant_id) diff --git a/cinderclient/v3/services.py b/cinderclient/v3/services.py index 55d3ee476..b48691f8e 100644 --- a/cinderclient/v3/services.py +++ b/cinderclient/v3/services.py @@ -19,9 +19,12 @@ from cinderclient import api_versions from cinderclient import base -from cinderclient.v2 import services -Service = services.Service + +class Service(base.Resource): + + def __repr__(self): + return "" % (self.binary, self.host) class LogLevel(base.Resource): @@ -30,7 +33,61 @@ def __repr__(self): self.binary, self.host, self.prefix, self.level) -class ServiceManager(services.ServiceManager): +class ServiceManagerBase(base.ManagerWithFind): + resource_class = Service + + def list(self, host=None, binary=None): + """ + Describes service list for host. + + :param host: destination host name. + :param binary: service binary. + """ + url = "/os-services" + filters = [] + if host: + filters.append("host=%s" % host) + if binary: + filters.append("binary=%s" % binary) + if filters: + url = "%s?%s" % (url, "&".join(filters)) + return self._list(url, "services") + + def enable(self, host, binary): + """Enable the service specified by hostname and binary.""" + body = {"host": host, "binary": binary} + result = self._update("/os-services/enable", body) + return self.resource_class(self, result, resp=result.request_ids) + + def disable(self, host, binary): + """Disable the service specified by hostname and binary.""" + body = {"host": host, "binary": binary} + result = self._update("/os-services/disable", body) + return self.resource_class(self, result, resp=result.request_ids) + + def disable_log_reason(self, host, binary, reason): + """Disable the service with reason.""" + body = {"host": host, "binary": binary, "disabled_reason": reason} + result = self._update("/os-services/disable-log-reason", body) + return self.resource_class(self, result, resp=result.request_ids) + + def freeze_host(self, host): + """Freeze the service specified by hostname.""" + body = {"host": host} + return self._update("/os-services/freeze", body) + + def thaw_host(self, host): + """Thaw the service specified by hostname.""" + body = {"host": host} + return self._update("/os-services/thaw", body) + + def failover_host(self, host, backend_id): + """Failover a replicated backend by hostname.""" + body = {"host": host, "backend_id": backend_id} + return self._update("/os-services/failover_host", body) + + +class ServiceManager(ServiceManagerBase): @api_versions.wraps("3.0") def server_api_version(self): """Returns the API Version supported by the server. diff --git a/cinderclient/v3/shell_base.py b/cinderclient/v3/shell_base.py index e3f868200..4dc6da9a4 100644 --- a/cinderclient/v3/shell_base.py +++ b/cinderclient/v3/shell_base.py @@ -25,7 +25,7 @@ from cinderclient import exceptions from cinderclient import shell_utils from cinderclient import utils -from cinderclient.v2 import availability_zones +from cinderclient.v3 import availability_zones def _translate_attachments(info): diff --git a/cinderclient/v3/volume_backups.py b/cinderclient/v3/volume_backups.py index 22d25a338..61069c8e5 100644 --- a/cinderclient/v3/volume_backups.py +++ b/cinderclient/v3/volume_backups.py @@ -18,14 +18,32 @@ """ from cinderclient import api_versions +from cinderclient.apiclient import base as common_base from cinderclient import base -from cinderclient.v2 import volume_backups -VolumeBackup = volume_backups.VolumeBackup +class VolumeBackup(base.Resource): + """A volume backup is a block level backup of a volume.""" + def __repr__(self): + return "" % self.id + + def delete(self, force=False): + """Delete this volume backup.""" + return self.manager.delete(self, force) + + def reset_state(self, state): + return self.manager.reset_state(self, state) + + def update(self, **kwargs): + """Update the name or description for this backup.""" + return self.manager.update(self, **kwargs) + + +class VolumeBackupManager(base.ManagerWithFind): + """Manage :class:`VolumeBackup` resources.""" + resource_class = VolumeBackup -class VolumeBackupManager(volume_backups.VolumeBackupManager): @api_versions.wraps("3.9") def update(self, backup, **kwargs): """Update the name or description for a backup. @@ -124,3 +142,70 @@ def _create_backup(self, volume_id, container=None, name=None, if availability_zone: body['backup']['availability_zone'] = availability_zone return self._create('/backups', body, 'backup') + + def get(self, backup_id): + """Show volume backup details. + + :param backup_id: The ID of the backup to display. + :rtype: :class:`VolumeBackup` + """ + return self._get("/backups/%s" % backup_id, "backup") + + def list(self, detailed=True, search_opts=None, marker=None, limit=None, + sort=None): + """Get a list of all volume backups. + + :rtype: list of :class:`VolumeBackup` + """ + resource_type = "backups" + url = self._build_list_url(resource_type, detailed=detailed, + search_opts=search_opts, marker=marker, + limit=limit, sort=sort) + return self._list(url, resource_type, limit=limit) + + def delete(self, backup, force=False): + """Delete a volume backup. + + :param backup: The :class:`VolumeBackup` to delete. + :param force: Allow delete in state other than error or available. + """ + if force: + return self._action('os-force_delete', backup) + else: + return self._delete("/backups/%s" % base.getid(backup)) + + def reset_state(self, backup, state): + """Update the specified volume backup with the provided state.""" + return self._action('os-reset_status', backup, + {'status': state} if state else {}) + + def _action(self, action, backup, info=None, **kwargs): + """Perform a volume backup action.""" + body = {action: info} + self.run_hooks('modify_body_for_action', body, **kwargs) + url = '/backups/%s/action' % base.getid(backup) + resp, body = self.api.client.post(url, body=body) + return common_base.TupleWithMeta((resp, body), resp) + + def export_record(self, backup_id): + """Export volume backup metadata record. + + :param backup_id: The ID of the backup to export. + :rtype: A dictionary containing 'backup_url' and 'backup_service'. + """ + resp, body = \ + self.api.client.get("/backups/%s/export_record" % backup_id) + return common_base.DictWithMeta(body['backup-record'], resp) + + def import_record(self, backup_service, backup_url): + """Import volume backup metadata record. + + :param backup_service: Backup service to use for importing the backup + :param backup_url: Backup URL for importing the backup metadata + :rtype: A dictionary containing volume backup metadata. + """ + body = {'backup-record': {'backup_service': backup_service, + 'backup_url': backup_url}} + self.run_hooks('modify_body_for_update', body, 'backup-record') + resp, body = self.api.client.post("/backups/import_record", body=body) + return common_base.DictWithMeta(body['backup'], resp) diff --git a/cinderclient/v3/volume_backups_restore.py b/cinderclient/v3/volume_backups_restore.py index 7e38c00a3..8a35ed162 100644 --- a/cinderclient/v3/volume_backups_restore.py +++ b/cinderclient/v3/volume_backups_restore.py @@ -18,4 +18,27 @@ This is part of the Volume Backups interface. """ -from cinderclient.v2.volume_backups_restore import * # noqa +from cinderclient import base + + +class VolumeBackupsRestore(base.Resource): + """A Volume Backups Restore represents a restore operation.""" + def __repr__(self): + return "" % self.volume_id + + +class VolumeBackupRestoreManager(base.Manager): + """Manage :class:`VolumeBackupsRestore` resources.""" + resource_class = VolumeBackupsRestore + + def restore(self, backup_id, volume_id=None, name=None): + """Restore a backup to a volume. + + :param backup_id: The ID of the backup to restore. + :param volume_id: The ID of the volume to restore the backup to. + :param name : The name for new volume creation to restore. + :rtype: :class:`Restore` + """ + body = {'restore': {'volume_id': volume_id, 'name': name}} + return self._create("/backups/%s/restore" % backup_id, + body, "restore") diff --git a/cinderclient/v3/volume_encryption_types.py b/cinderclient/v3/volume_encryption_types.py index 507709726..531e4d229 100644 --- a/cinderclient/v3/volume_encryption_types.py +++ b/cinderclient/v3/volume_encryption_types.py @@ -18,4 +18,88 @@ Volume Encryption Type interface """ -from cinderclient.v2.volume_encryption_types import * # noqa +from cinderclient.apiclient import base as common_base +from cinderclient import base + + +class VolumeEncryptionType(base.Resource): + """ + A Volume Encryption Type is a collection of settings used to conduct + encryption for a specific volume type. + """ + def __repr__(self): + return "" % self.encryption_id + + +class VolumeEncryptionTypeManager(base.ManagerWithFind): + """ + Manage :class: `VolumeEncryptionType` resources. + """ + resource_class = VolumeEncryptionType + + def list(self, search_opts=None): + """ + List all volume encryption types. + + :param search_opts: Search options to filter out volume + encryption types + :return: a list of :class: VolumeEncryptionType instances + """ + # Since the encryption type is a volume type extension, we cannot get + # all encryption types without going through all volume types. + volume_types = self.api.volume_types.list() + encryption_types = [] + list_of_resp = [] + for volume_type in volume_types: + encryption_type = self._get("/types/%s/encryption" + % base.getid(volume_type)) + if hasattr(encryption_type, 'volume_type_id'): + encryption_types.append(encryption_type) + + list_of_resp.extend(encryption_type.request_ids) + + return common_base.ListWithMeta(encryption_types, list_of_resp) + + def get(self, volume_type): + """ + Get the volume encryption type for the specified volume type. + + :param volume_type: the volume type to query + :return: an instance of :class: VolumeEncryptionType + """ + return self._get("/types/%s/encryption" % base.getid(volume_type)) + + def create(self, volume_type, specs): + """ + Creates encryption type for a volume type. Default: admin only. + + :param volume_type: the volume type on which to add an encryption type + :param specs: the encryption type specifications to add + :return: an instance of :class: VolumeEncryptionType + """ + body = {'encryption': specs} + return self._create("/types/%s/encryption" % base.getid(volume_type), + body, "encryption") + + def update(self, volume_type, specs): + """ + Update the encryption type information for the specified volume type. + + :param volume_type: the volume type whose encryption type information + must be updated + :param specs: the encryption type specifications to update + :return: an instance of :class: VolumeEncryptionType + """ + body = {'encryption': specs} + return self._update("/types/%s/encryption/provider" % + base.getid(volume_type), body) + + def delete(self, volume_type): + """ + Delete the encryption type information for the specified volume type. + + :param volume_type: the volume type whose encryption type information + must be deleted + """ + return self._delete("/types/%s/encryption/provider" % + base.getid(volume_type)) diff --git a/cinderclient/v3/volume_snapshots.py b/cinderclient/v3/volume_snapshots.py index 3691d2fb8..ce7f4e07a 100644 --- a/cinderclient/v3/volume_snapshots.py +++ b/cinderclient/v3/volume_snapshots.py @@ -213,9 +213,17 @@ def manage(self, volume_id, ref, name=None, description=None, } return self._create('/os-snapshot-manage', body, 'snapshot') + @api_versions.wraps("3.0") + def list_manageable(self, host, detailed=True, marker=None, + limit=None, offset=None, sort=None): + url = self._build_list_url("os-snapshot-manage", detailed=detailed, + search_opts={'host': host}, marker=marker, + limit=limit, offset=offset, sort=sort) + return self._list(url, "manageable-snapshots") + @api_versions.wraps('3.8') - def list_manageable(self, host, detailed=True, marker=None, limit=None, - offset=None, sort=None, cluster=None): + def list_manageable(self, host, detailed=True, marker=None, # noqa: F811 + limit=None, offset=None, sort=None, cluster=None): search_opts = {'cluster': cluster} if cluster else {'host': host} url = self._build_list_url("manageable_snapshots", detailed=detailed, search_opts=search_opts, marker=marker, diff --git a/cinderclient/v3/volume_transfers.py b/cinderclient/v3/volume_transfers.py index f40c5199d..bcf0e0cc0 100644 --- a/cinderclient/v3/volume_transfers.py +++ b/cinderclient/v3/volume_transfers.py @@ -16,10 +16,23 @@ """Volume transfer interface (v3 extension).""" from cinderclient import base -from cinderclient.v2 import volume_transfers -class VolumeTransferManager(volume_transfers.VolumeTransferManager): +class VolumeTransfer(base.Resource): + """Transfer a volume from one tenant to another""" + + def __repr__(self): + return "" % self.id + + def delete(self): + """Delete this volume transfer.""" + return self.manager.delete(self) + + +class VolumeTransferManager(base.ManagerWithFind): + """Manage :class:`VolumeTransfer` resources.""" + resource_class = VolumeTransfer + def create(self, volume_id, name=None, no_snapshots=False): """Creates a volume transfer. diff --git a/cinderclient/v3/volume_type_access.py b/cinderclient/v3/volume_type_access.py index d9fe2eff8..bdd2e7028 100644 --- a/cinderclient/v3/volume_type_access.py +++ b/cinderclient/v3/volume_type_access.py @@ -14,4 +14,40 @@ """Volume type access interface.""" -from cinderclient.v2.volume_type_access import * # noqa +from cinderclient.apiclient import base as common_base +from cinderclient import base + + +class VolumeTypeAccess(base.Resource): + def __repr__(self): + return "" % self.project_id + + +class VolumeTypeAccessManager(base.ManagerWithFind): + """ + Manage :class:`VolumeTypeAccess` resources. + """ + resource_class = VolumeTypeAccess + + def list(self, volume_type): + return self._list( + '/types/%s/os-volume-type-access' % base.getid(volume_type), + 'volume_type_access') + + def add_project_access(self, volume_type, project): + """Add a project to the given volume type access list.""" + info = {'project': project} + return self._action('addProjectAccess', volume_type, info) + + def remove_project_access(self, volume_type, project): + """Remove a project from the given volume type access list.""" + info = {'project': project} + return self._action('removeProjectAccess', volume_type, info) + + def _action(self, action, volume_type, info, **kwargs): + """Perform a volume type action.""" + body = {action: info} + self.run_hooks('modify_body_for_action', body, **kwargs) + url = '/types/%s/action' % base.getid(volume_type) + resp, body = self.api.client.post(url, body=body) + return common_base.TupleWithMeta((resp, body), resp) diff --git a/cinderclient/v3/volumes.py b/cinderclient/v3/volumes.py index 8ea388008..42527f7e7 100644 --- a/cinderclient/v3/volumes.py +++ b/cinderclient/v3/volumes.py @@ -18,10 +18,10 @@ from cinderclient import api_versions from cinderclient.apiclient import base as common_base from cinderclient import base -from cinderclient.v2 import volumes +from cinderclient.v3 import volumes_base -class Volume(volumes.Volume): +class Volume(volumes_base.Volume): def upload_to_image(self, force, image_name, container_format, disk_format, visibility=None, @@ -68,7 +68,7 @@ def manage(self, host, ref, name=None, description=None, cluster=cluster) -class VolumeManager(volumes.VolumeManager): +class VolumeManager(volumes_base.VolumeManager): resource_class = Volume def create(self, size, consistencygroup_id=None, @@ -246,16 +246,24 @@ def manage(self, host, ref, name=None, description=None, body['volume']['cluster'] = cluster return self._create('/os-volume-manage', body, 'volume') + @api_versions.wraps('3.0') + def list_manageable(self, host, detailed=True, marker=None, + limit=None, offset=None, sort=None): + url = self._build_list_url("os-volume-manage", detailed=detailed, + search_opts={'host': host}, marker=marker, + limit=limit, offset=offset, sort=sort) + return self._list(url, "manageable-volumes") + @api_versions.wraps('3.8') - def list_manageable(self, host, detailed=True, marker=None, limit=None, - offset=None, sort=None, cluster=None): + def list_manageable(self, host, detailed=True, marker=None, # noqa: F811 + limit=None, offset=None, sort=None, cluster=None): search_opts = {'cluster': cluster} if cluster else {'host': host} url = self._build_list_url("manageable_volumes", detailed=detailed, search_opts=search_opts, marker=marker, limit=limit, offset=offset, sort=sort) return self._list(url, "manageable-volumes") - @api_versions.wraps("2.0", "3.32") + @api_versions.wraps("3.0", "3.32") def get_pools(self, detail): """Show pool information for backends.""" query_string = "" diff --git a/cinderclient/v2/volumes.py b/cinderclient/v3/volumes_base.py similarity index 82% rename from cinderclient/v2/volumes.py rename to cinderclient/v3/volumes_base.py index 4c380fbd0..3b00b59d7 100644 --- a/cinderclient/v2/volumes.py +++ b/cinderclient/v3/volumes_base.py @@ -13,7 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. -"""Volume interface (v2 extension).""" +"""Base Volume interface.""" from cinderclient.apiclient import base as common_base from cinderclient import base @@ -130,24 +130,6 @@ def show_image_metadata(self, volume): """ return self.manager.show_image_metadata(self) - def upload_to_image(self, force, image_name, container_format, - disk_format, visibility=None, - protected=None): - """Upload a volume to image service as an image. - - :param force: Boolean to enables or disables upload of a volume that - is attached to an instance. - :param image_name: The new image name. - :param container_format: Container format type. - :param disk_format: Disk format type. - :param visibility: The accessibility of image (allowed for - 3.1-latest). - :param protected: Boolean to decide whether prevents image from being - deleted (allowed for 3.1-latest). - """ - return self.manager.upload_to_image(self, force, image_name, - container_format, disk_format) - def force_delete(self): """Delete the specified volume ignoring its current state. @@ -175,11 +157,6 @@ def extend(self, volume, new_size): """ return self.manager.extend(self, new_size) - def migrate_volume(self, host, force_host_copy, lock_volume): - """Migrate the volume to a new host.""" - return self.manager.migrate_volume(self, host, force_host_copy, - lock_volume) - def retype(self, volume_type, policy): """Change a volume's type.""" return self.manager.retype(self, volume_type, policy) @@ -197,16 +174,6 @@ def update_readonly_flag(self, volume, read_only): """ return self.manager.update_readonly_flag(self, read_only) - def manage(self, host, ref, name=None, description=None, - volume_type=None, availability_zone=None, metadata=None, - bootable=False): - """Manage an existing volume.""" - return self.manager.manage(host=host, ref=ref, name=name, - description=description, - volume_type=volume_type, - availability_zone=availability_zone, - metadata=metadata, bootable=bootable) - def list_manageable(self, host, detailed=True, marker=None, limit=None, offset=None, sort=None): return self.manager.list_manageable(host, detailed=detailed, @@ -226,52 +193,6 @@ class VolumeManager(base.ManagerWithFind): """Manage :class:`Volume` resources.""" resource_class = Volume - def create(self, size, consistencygroup_id=None, - snapshot_id=None, - source_volid=None, name=None, description=None, - volume_type=None, user_id=None, - project_id=None, availability_zone=None, - metadata=None, imageRef=None, scheduler_hints=None): - """Create a volume. - - :param size: Size of volume in GB - :param consistencygroup_id: ID of the consistencygroup - :param snapshot_id: ID of the snapshot - :param name: Name of the volume - :param description: Description of the volume - :param volume_type: Type of volume - :param user_id: User id derived from context (IGNORED) - :param project_id: Project id derived from context (IGNORED) - :param availability_zone: Availability Zone to use - :param metadata: Optional metadata to set on volume creation - :param imageRef: reference to an image stored in glance - :param source_volid: ID of source volume to clone from - :param scheduler_hints: (optional extension) arbitrary key-value pairs - specified by the client to help boot an instance - :rtype: :class:`Volume` - """ - if metadata is None: - volume_metadata = {} - else: - volume_metadata = metadata - - body = {'volume': {'size': size, - 'consistencygroup_id': consistencygroup_id, - 'snapshot_id': snapshot_id, - 'name': name, - 'description': description, - 'volume_type': volume_type, - 'availability_zone': availability_zone, - 'metadata': volume_metadata, - 'imageRef': imageRef, - 'source_volid': source_volid, - }} - - if scheduler_hints: - body['OS-SCH-HNT:scheduler_hints'] = scheduler_hints - - return self._create('/volumes', body, 'volume') - def get(self, volume_id): """Get a volume. @@ -603,13 +524,6 @@ def manage(self, host, ref, name=None, description=None, }} return self._create('/os-volume-manage', body, 'volume') - def list_manageable(self, host, detailed=True, marker=None, limit=None, - offset=None, sort=None): - url = self._build_list_url("os-volume-manage", detailed=detailed, - search_opts={'host': host}, marker=marker, - limit=limit, offset=offset, sort=sort) - return self._list(url, "manageable-volumes") - def unmanage(self, volume): """Unmanage a volume.""" return self._action('os-unmanage', volume, None) diff --git a/doc/source/contributor/unit_tests.rst b/doc/source/contributor/unit_tests.rst index 387ecb00b..248e1beb5 100644 --- a/doc/source/contributor/unit_tests.rst +++ b/doc/source/contributor/unit_tests.rst @@ -29,11 +29,11 @@ Running a subset of tests using tox One common activity is to just run a single test, you can do this with tox simply by specifying to just run py3 tests against a single test:: - tox -e py3 -- -n cinderclient.tests.unit.v2.test_volumes.VolumesTest.test_attach + tox -e py3 -- -n cinderclient.tests.unit.v3.test_volumes.VolumesTest.test_create_volume Or all tests in the test_volumes.py file:: - tox -e py3 -- -n cinderclient.tests.unit.v2.test_volumes + tox -e py3 -- -n cinderclient.tests.unit.v3.test_volumes For more information on these options and how to run tests, please see the `stestr documentation `_. diff --git a/releasenotes/notes/drop-v2-support-e578ca21c7c6b532.yaml b/releasenotes/notes/drop-v2-support-e578ca21c7c6b532.yaml new file mode 100644 index 000000000..8360a601f --- /dev/null +++ b/releasenotes/notes/drop-v2-support-e578ca21c7c6b532.yaml @@ -0,0 +1,5 @@ +--- +upgrade: + - | + This release drops support of the Block Storage API v2. The last version + of the python-cinderclient supporting that API is the 7.x series. From d04ded6a6ff4cc9ba8377a1b025493e939047b2c Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Thu, 29 Jul 2021 17:44:23 -0400 Subject: [PATCH 079/159] Unset tempest.lib timeout in functional tests The test_cli.CinderBackupTests.test_backup_create_and_delete test is hitting timeout errors in the python-cinderclient-functional-py36 zuul job. This is happening because it's inheriting the OS_TEST_TIMEOUT value of 60 from the base testenv, and that value is being used by the tempest.lib class we inherit from as a timeout for each test. This is a problem for test_backup_create_and_delete because it creates a volume, waits for available, creates a backup, waits for available, deletes the volume, waits for deletion, deletes the backup, waits for deletion. Our functional tests have their own timeout handling, so turn off the tempest.lib timeout and use ours. An alternative to turning it off is to set it at a value that respects our timeout for our longest test, which would be: - time-to-available: 120 sec (x2) - time-to-deleted: 60 sec (x2) that is, 360 sec. Change-Id: I33399b4c094af2cc059da6e332f4c0a91e6ab57e --- tox.ini | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tox.ini b/tox.ini index bc78f2496..c76b8a620 100644 --- a/tox.ini +++ b/tox.ini @@ -84,6 +84,11 @@ setenv = OS_VOLUME_API_VERSION = 3 # must define this here so it can be inherited by the -py3* environments OS_CINDERCLIENT_EXEC_DIR = {envdir}/bin + # Our functional tests contain their own timeout handling, so + # turn off the timeout handling provided by the + # tempest.lib.base.BaseTestCase that our ClientTestBase class + # inherits from. + OS_TEST_TIMEOUT=0 # The OS_CACERT environment variable should be passed to the test # environments to specify a CA bundle file to use in verifying a From 24b262c5c1f18303850efd1b5ef90a54a43b8ae8 Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Wed, 4 Aug 2021 18:32:11 -0400 Subject: [PATCH 080/159] Increase default quotas for zuul jobs We're seeing occasional failures in the functional test jobs due to exceeding volumes quota, so increase these from the default value of 10 to something more reasonable like 25. Change-Id: Id1ab94849bfb35256dd0705b888708f3fa1106c4 --- .zuul.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.zuul.yaml b/.zuul.yaml index 418dc1fa2..fa33c4e5b 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -11,6 +11,11 @@ devstack_localrc: USE_PYTHON3: true VOLUME_BACKING_FILE_SIZE: 16G + devstack_local_conf: + post-config: + $CINDER_CONF: + DEFAULT: + quota_volumes: 25 - job: name: python-cinderclient-functional-py36 From e474eeba3d0343d615ab7a625bb7d5ee3ca2dd61 Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Tue, 10 Aug 2021 12:35:15 -0400 Subject: [PATCH 081/159] Add functional jobs to the gate Voting check jobs are supposed to also be gate jobs. Change-Id: Ibdf4c30606552fa0c2660a24c97354f814e5be24 --- .zuul.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.zuul.yaml b/.zuul.yaml index fa33c4e5b..28853f469 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -16,6 +16,11 @@ $CINDER_CONF: DEFAULT: quota_volumes: 25 + irrelevant-files: + - ^.*\.rst$ + - ^doc/.*$ + - ^releasenotes/.*$ + - ^cinderclient/tests/unit/.*$ - job: name: python-cinderclient-functional-py36 @@ -47,3 +52,7 @@ - python-cinderclient-functional-py38 - openstack-tox-pylint: voting: false + gate: + jobs: + - python-cinderclient-functional-py36 + - python-cinderclient-functional-py38 From f94908008e7432678f89c58e7e61773e71c2acf5 Mon Sep 17 00:00:00 2001 From: Gorka Eguileor Date: Wed, 21 Apr 2021 16:01:11 +0200 Subject: [PATCH 082/159] Add consumes quota field support Cinder microversion v3.65 adds consumes_quota key to volume and snapshots as a way to differentiate between use generated resources and temporary ones. This patch adds support for this microversion and presents the consumes_quota field when the server sends it (which only happens when we request this microversion). Change-Id: I524490aa988fa4d654bfa8050d89cf99ce50bb4b Depends-On: I655a47fc75ddc11caf1defe984d9a66a9ad5a2e7 Implements: blueprint temp-resources --- cinderclient/api_versions.py | 2 +- cinderclient/tests/unit/v3/test_shell.py | 10 ++++++---- cinderclient/v3/shell.py | 21 ++++++++++----------- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/cinderclient/api_versions.py b/cinderclient/api_versions.py index 47a923a82..78bb8a0af 100644 --- a/cinderclient/api_versions.py +++ b/cinderclient/api_versions.py @@ -26,7 +26,7 @@ # key is unsupported version, value is appropriate supported alternative REPLACEMENT_VERSIONS = {"1": "3", "2": "3"} -MAX_VERSION = "3.64" +MAX_VERSION = "3.65" MIN_VERSION = "3.0" _SUBSTITUTIONS = {} diff --git a/cinderclient/tests/unit/v3/test_shell.py b/cinderclient/tests/unit/v3/test_shell.py index e7a166027..9a6852fa6 100644 --- a/cinderclient/tests/unit/v3/test_shell.py +++ b/cinderclient/tests/unit/v3/test_shell.py @@ -1318,12 +1318,14 @@ def test_backup_with_az(self): self.assert_called('POST', '/backups', body=expected) @mock.patch("cinderclient.utils.print_list") - def test_snapshot_list_with_userid(self, mock_print_list): - """Ensure 3.41 provides User ID header.""" - self.run_command('--os-volume-api-version 3.41 snapshot-list') + def test_snapshot_list(self, mock_print_list): + """Ensure we always present all existing fields when listing snaps.""" + self.run_command('--os-volume-api-version 3.65 snapshot-list') self.assert_called('GET', '/snapshots/detail') - columns = ['ID', 'Volume ID', 'Status', 'Name', 'Size', 'User ID'] + columns = ['ID', 'Volume ID', 'Status', 'Name', 'Size', + 'Consumes Quota', 'User ID'] mock_print_list.assert_called_once_with(mock.ANY, columns, + exclude_unavailable=True, sortby_index=0) @mock.patch('cinderclient.v3.volumes.Volume.migrate_volume') diff --git a/cinderclient/v3/shell.py b/cinderclient/v3/shell.py index 7ffba81a1..195d859f6 100644 --- a/cinderclient/v3/shell.py +++ b/cinderclient/v3/shell.py @@ -507,8 +507,8 @@ def do_list(cs, args): [x.title().strip() for x in field_titles]) if k != 'Id'] key_list.extend(unique_titles) else: - key_list = ['ID', 'Status', 'Name', 'Size', 'Volume Type', - 'Bootable', 'Attached to'] + key_list = ['ID', 'Status', 'Name', 'Size', 'Consumes Quota', + 'Volume Type', 'Bootable', 'Attached to'] # If all_tenants is specified, print # Tenant ID as well. if search_opts['all_tenants']: @@ -2173,15 +2173,14 @@ def do_snapshot_list(cs, args): shell_utils.translate_volume_snapshot_keys(snapshots) sortby_index = None if args.sort else 0 - if cs.api_version >= api_versions.APIVersion("3.41"): - utils.print_list(snapshots, - ['ID', 'Volume ID', 'Status', - 'Name', 'Size', 'User ID'], - sortby_index=sortby_index) - else: - utils.print_list(snapshots, - ['ID', 'Volume ID', 'Status', 'Name', 'Size'], - sortby_index=sortby_index) + # It's the server's responsibility to return the appropriate fields for the + # requested microversion, we present all known fields and skip those that + # are missing. + utils.print_list(snapshots, + ['ID', 'Volume ID', 'Status', 'Name', 'Size', + 'Consumes Quota', 'User ID'], + exclude_unavailable=True, + sortby_index=sortby_index) if show_count: print("Snapshot in total: %s" % total_count) From 5bf0a66377d26c91382b6c274b66199b2ef0b1ee Mon Sep 17 00:00:00 2001 From: dengzhaosen Date: Tue, 27 Apr 2021 10:15:13 +0800 Subject: [PATCH 083/159] Remove the unused tool scripts We support Python 3.6 as a minimum now, making these checks no-ops. These file is unused in current project and remove them. Change-Id: Ie5cbd9653375deeb523190d9c499f0c89c035d2e --- tools/install_venv.py | 75 --------------- tools/install_venv_common.py | 173 ----------------------------------- tools/with_venv.sh | 4 - 3 files changed, 252 deletions(-) delete mode 100644 tools/install_venv.py delete mode 100644 tools/install_venv_common.py delete mode 100755 tools/with_venv.sh diff --git a/tools/install_venv.py b/tools/install_venv.py deleted file mode 100644 index 4ff48a228..000000000 --- a/tools/install_venv.py +++ /dev/null @@ -1,75 +0,0 @@ -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# Copyright 2010 OpenStack Foundation -# Copyright 2013 IBM Corp. -# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. -# -# 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 configparser -import os -import sys - -import install_venv_common as install_venv - - -def print_help(project, venv, root): - help = """ - %(project)s development environment setup is complete. - - %(project)s development uses virtualenv to track and manage Python - dependencies while in development and testing. - - To activate the %(project)s virtualenv for the extent of your current - shell session you can run: - - $ . %(venv)s/bin/activate - - Or, if you prefer, you can run commands in the virtualenv on a case by - case basis by running: - - $ %(root)s/tools/with_venv.sh - """ - print(help % dict(project=project, venv=venv, root=root)) - - -def main(argv): - root = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) - - if os.environ.get('tools_path'): - root = os.environ['tools_path'] - venv = os.path.join(root, '.venv') - if os.environ.get('venv'): - venv = os.environ['venv'] - - pip_requires = os.path.join(root, 'requirements.txt') - test_requires = os.path.join(root, 'test-requirements.txt') - py_version = "python%s.%s" % (sys.version_info[0], sys.version_info[1]) - setup_cfg = configparser.ConfigParser() - setup_cfg.read('setup.cfg') - project = setup_cfg.get('metadata', 'name') - - install = install_venv.InstallVenv( - root, venv, pip_requires, test_requires, py_version, project) - options = install.parse_args(argv) - install.check_python_version() - install.check_dependencies() - install.create_virtualenv(no_site_packages=options.no_site_packages) - install.install_dependencies() - print_help(project, venv, root) - - -if __name__ == '__main__': - main(sys.argv) diff --git a/tools/install_venv_common.py b/tools/install_venv_common.py deleted file mode 100644 index 0322c1845..000000000 --- a/tools/install_venv_common.py +++ /dev/null @@ -1,173 +0,0 @@ -# Copyright 2013 OpenStack Foundation -# Copyright 2013 IBM Corp. -# -# 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. - -"""Provides methods needed by installation script for OpenStack development -virtual environments. - -Since this script is used to bootstrap a virtualenv from the system's Python -environment, it should be kept strictly compatible with Python 2.6. - -Synced in from openstack-common -""" - -import optparse -import os -import subprocess -import sys - - -class InstallVenv(object): - - def __init__(self, root, venv, requirements, - test_requirements, py_version, - project): - self.root = root - self.venv = venv - self.requirements = requirements - self.test_requirements = test_requirements - self.py_version = py_version - self.project = project - - def die(self, message, *args): - print(message % args, file=sys.stderr) - sys.exit(1) - - def check_python_version(self): - if sys.version_info < (2, 6): - self.die("Need Python Version >= 2.6") - - def run_command_with_code(self, cmd, redirect_output=True, - check_exit_code=True): - """Runs a command in an out-of-process shell. - - Returns the output of that command. Working directory is self.root. - """ - if redirect_output: - stdout = subprocess.PIPE - else: - stdout = None - - proc = subprocess.Popen(cmd, cwd=self.root, stdout=stdout) - output = proc.communicate()[0] - if check_exit_code and proc.returncode != 0: - self.die('Command "%s" failed.\n%s', ' '.join(cmd), output) - return (output, proc.returncode) - - def run_command(self, cmd, redirect_output=True, check_exit_code=True): - return self.run_command_with_code(cmd, redirect_output, - check_exit_code)[0] - - def get_distro(self): - if (os.path.exists('/etc/fedora-release') or - os.path.exists('/etc/redhat-release')): - return Fedora( - self.root, self.venv, self.requirements, - self.test_requirements, self.py_version, self.project) - else: - return Distro( - self.root, self.venv, self.requirements, - self.test_requirements, self.py_version, self.project) - - def check_dependencies(self): - self.get_distro().install_virtualenv() - - def create_virtualenv(self, no_site_packages=True): - """Creates the virtual environment and installs PIP. - - Creates the virtual environment and installs PIP only into the - virtual environment. - """ - if not os.path.isdir(self.venv): - print('Creating venv...', end=' ') - if no_site_packages: - self.run_command(['virtualenv', '-q', '--no-site-packages', - self.venv]) - else: - self.run_command(['virtualenv', '-q', self.venv]) - print('done.') - else: - print("venv already exists...") - pass - - def pip_install(self, *args): - self.run_command(['tools/with_venv.sh', - 'pip', 'install', '--upgrade'] + list(args), - redirect_output=False) - - def install_dependencies(self): - print('Installing dependencies with pip (this can take a while)...') - - # First things first, make sure our venv has the latest pip and - # setuptools and pbr - self.pip_install('pip>=1.4') - self.pip_install('setuptools') - self.pip_install('pbr') - - self.pip_install('-r', self.requirements, '-r', self.test_requirements) - - def parse_args(self, argv): - """Parses command-line arguments.""" - parser = optparse.OptionParser() - parser.add_option('-n', '--no-site-packages', - action='store_true', - help="Do not inherit packages from global Python " - "install") - return parser.parse_args(argv[1:])[0] - - def post_process(self, **kwargs): - pass - - -class Distro(InstallVenv): - - def check_cmd(self, cmd): - return bool(self.run_command(['which', cmd], - check_exit_code=False).strip()) - - def install_virtualenv(self): - if self.check_cmd('virtualenv'): - return - - if self.check_cmd('easy_install'): - print('Installing virtualenv via easy_install...', end=' ') - if self.run_command(['easy_install', 'virtualenv']): - print('Succeeded') - return - else: - print('Failed') - - self.die('ERROR: virtualenv not found.\n\n%s development' - ' requires virtualenv, please install it using your' - ' favorite package management tool' % self.project) - - -class Fedora(Distro): - """This covers all Fedora-based distributions. - - Includes: Fedora, RHEL, CentOS, Scientific Linux - """ - - def check_pkg(self, pkg): - return self.run_command_with_code(['rpm', '-q', pkg], - check_exit_code=False)[1] == 0 - - def install_virtualenv(self): - if self.check_cmd('virtualenv'): - return - - if not self.check_pkg('python-virtualenv'): - self.die("Please install 'python-virtualenv'.") - - super(Fedora, self).install_virtualenv() diff --git a/tools/with_venv.sh b/tools/with_venv.sh deleted file mode 100755 index c8d2940fc..000000000 --- a/tools/with_venv.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -TOOLS=`dirname $0` -VENV=$TOOLS/../.venv -source $VENV/bin/activate && $@ From e16b0256ec53ebe82d88a8eab8bd9d0fb3c7b4a6 Mon Sep 17 00:00:00 2001 From: dengzhaosen Date: Thu, 6 May 2021 09:46:52 +0800 Subject: [PATCH 084/159] update some scripts remove the the colorizer.py which hasn't been used since https://review.opendev.org/c/openstack/python-cinderclient/+/502120 Change-Id: I9bed2ec947705cd53daa04678093a80581dc3282 --- tools/colorizer.py | 334 --------------------------------------------- tools/lintstack.py | 2 +- 2 files changed, 1 insertion(+), 335 deletions(-) delete mode 100755 tools/colorizer.py diff --git a/tools/colorizer.py b/tools/colorizer.py deleted file mode 100755 index cf89535b5..000000000 --- a/tools/colorizer.py +++ /dev/null @@ -1,334 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 2013, Nebula, Inc. -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# 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. -# -# Colorizer Code is borrowed from Twisted: -# Copyright (c) 2001-2010 Twisted Matrix Laboratories. -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -"""Display a subunit stream through a colorized unittest test runner.""" - -import heapq -import sys -import unittest - -import subunit -import testtools - - -class _AnsiColorizer(object): - """ - A colorizer is an object that loosely wraps around a stream, allowing - callers to write text to the stream in a particular color. - - Colorizer classes must implement C{supported()} and C{write(text, color)}. - """ - _colors = dict(black=30, red=31, green=32, yellow=33, - blue=34, magenta=35, cyan=36, white=37) - - def __init__(self, stream): - self.stream = stream - - def supported(cls, stream=sys.stdout): - """ - A class method that returns True if the current platform supports - coloring terminal output using this method. Returns False otherwise. - """ - if not stream.isatty(): - return False # auto color only on TTYs - try: - import curses - except ImportError: - return False - else: - try: - try: - return curses.tigetnum("colors") > 2 - except curses.error: - curses.setupterm() - return curses.tigetnum("colors") > 2 - except Exception: - # guess false in case of error - return False - supported = classmethod(supported) - - def write(self, text, color): - """ - Write the given text to the stream in the given color. - - @param text: Text to be written to the stream. - - @param color: A string label for a color. e.g. 'red', 'white'. - """ - color = self._colors[color] - self.stream.write('\x1b[%s;1m%s\x1b[0m' % (color, text)) - - -class _Win32Colorizer(object): - """ - See _AnsiColorizer docstring. - """ - def __init__(self, stream): - import win32console - red, green, blue, bold = (win32console.FOREGROUND_RED, - win32console.FOREGROUND_GREEN, - win32console.FOREGROUND_BLUE, - win32console.FOREGROUND_INTENSITY) - self.stream = stream - self.screenBuffer = win32console.GetStdHandle( - win32console.STD_OUT_HANDLE) - self._colors = { - 'normal': red | green | blue, - 'red': red | bold, - 'green': green | bold, - 'blue': blue | bold, - 'yellow': red | green | bold, - 'magenta': red | blue | bold, - 'cyan': green | blue | bold, - 'white': red | green | blue | bold - } - - def supported(cls, stream=sys.stdout): - try: - import win32console - screenBuffer = win32console.GetStdHandle( - win32console.STD_OUT_HANDLE) - except ImportError: - return False - import pywintypes - try: - screenBuffer.SetConsoleTextAttribute( - win32console.FOREGROUND_RED | - win32console.FOREGROUND_GREEN | - win32console.FOREGROUND_BLUE) - except pywintypes.error: - return False - else: - return True - supported = classmethod(supported) - - def write(self, text, color): - color = self._colors[color] - self.screenBuffer.SetConsoleTextAttribute(color) - self.stream.write(text) - self.screenBuffer.SetConsoleTextAttribute(self._colors['normal']) - - -class _NullColorizer(object): - """ - See _AnsiColorizer docstring. - """ - def __init__(self, stream): - self.stream = stream - - def supported(cls, stream=sys.stdout): - return True - supported = classmethod(supported) - - def write(self, text, color): - self.stream.write(text) - - -def get_elapsed_time_color(elapsed_time): - if elapsed_time > 1.0: - return 'red' - elif elapsed_time > 0.25: - return 'yellow' - else: - return 'green' - - -class NovaTestResult(testtools.TestResult): - def __init__(self, stream, descriptions, verbosity): - super(NovaTestResult, self).__init__() - self.stream = stream - self.showAll = verbosity > 1 - self.num_slow_tests = 10 - self.slow_tests = [] # this is a fixed-sized heap - self.colorizer = None - # NOTE(vish): reset stdout for the terminal check - stdout = sys.stdout - sys.stdout = sys.__stdout__ - for colorizer in [_Win32Colorizer, _AnsiColorizer, _NullColorizer]: - if colorizer.supported(): - self.colorizer = colorizer(self.stream) - break - sys.stdout = stdout - self.start_time = None - self.last_time = {} - self.results = {} - self.last_written = None - - def _writeElapsedTime(self, elapsed): - color = get_elapsed_time_color(elapsed) - self.colorizer.write(" %.2f" % elapsed, color) - - def _addResult(self, test, *args): - try: - name = test.id() - except AttributeError: - name = 'Unknown.unknown' - test_class, test_name = name.rsplit('.', 1) - - elapsed = (self._now() - self.start_time).total_seconds() - item = (elapsed, test_class, test_name) - if len(self.slow_tests) >= self.num_slow_tests: - heapq.heappushpop(self.slow_tests, item) - else: - heapq.heappush(self.slow_tests, item) - - self.results.setdefault(test_class, []) - self.results[test_class].append((test_name, elapsed) + args) - self.last_time[test_class] = self._now() - self.writeTests() - - def _writeResult(self, test_name, elapsed, long_result, color, - short_result, success): - if self.showAll: - self.stream.write(' %s' % str(test_name).ljust(66)) - self.colorizer.write(long_result, color) - if success: - self._writeElapsedTime(elapsed) - self.stream.writeln() - else: - self.colorizer.write(short_result, color) - - def addSuccess(self, test): - super(NovaTestResult, self).addSuccess(test) - self._addResult(test, 'OK', 'green', '.', True) - - def addFailure(self, test, err): - if test.id() == 'process-returncode': - return - super(NovaTestResult, self).addFailure(test, err) - self._addResult(test, 'FAIL', 'red', 'F', False) - - def addError(self, test, err): - super(NovaTestResult, self).addFailure(test, err) - self._addResult(test, 'ERROR', 'red', 'E', False) - - def addSkip(self, test, reason=None, details=None): - super(NovaTestResult, self).addSkip(test, reason, details) - self._addResult(test, 'SKIP', 'blue', 'S', True) - - def startTest(self, test): - self.start_time = self._now() - super(NovaTestResult, self).startTest(test) - - def writeTestCase(self, cls): - if not self.results.get(cls): - return - if cls != self.last_written: - self.colorizer.write(cls, 'white') - self.stream.writeln() - for result in self.results[cls]: - self._writeResult(*result) - del self.results[cls] - self.stream.flush() - self.last_written = cls - - def writeTests(self): - time = self.last_time.get(self.last_written, self._now()) - if not self.last_written or (self._now() - time).total_seconds() > 2.0: - diff = 3.0 - while diff > 2.0: - classes = list(self.results) - oldest = min(classes, key=lambda x: self.last_time[x]) - diff = (self._now() - self.last_time[oldest]).total_seconds() - self.writeTestCase(oldest) - else: - self.writeTestCase(self.last_written) - - def done(self): - self.stopTestRun() - - def stopTestRun(self): - for cls in list(self.results.keys()): - self.writeTestCase(cls) - self.stream.writeln() - self.writeSlowTests() - - def writeSlowTests(self): - # Pare out 'fast' tests - slow_tests = [item for item in self.slow_tests - if get_elapsed_time_color(item[0]) != 'green'] - if slow_tests: - slow_total_time = sum(item[0] for item in slow_tests) - slow = ("Slowest %i tests took %.2f secs:" - % (len(slow_tests), slow_total_time)) - self.colorizer.write(slow, 'yellow') - self.stream.writeln() - last_cls = None - # sort by name - for elapsed, cls, name in sorted(slow_tests, - key=lambda x: x[1] + x[2]): - if cls != last_cls: - self.colorizer.write(cls, 'white') - self.stream.writeln() - last_cls = cls - self.stream.write(' %s' % str(name).ljust(68)) - self._writeElapsedTime(elapsed) - self.stream.writeln() - - def printErrors(self): - if self.showAll: - self.stream.writeln() - self.printErrorList('ERROR', self.errors) - self.printErrorList('FAIL', self.failures) - - def printErrorList(self, flavor, errors): - for test, err in errors: - self.colorizer.write("=" * 70, 'red') - self.stream.writeln() - self.colorizer.write(flavor, 'red') - self.stream.writeln(": %s" % test.id()) - self.colorizer.write("-" * 70, 'red') - self.stream.writeln() - self.stream.writeln("%s" % err) - - -test = subunit.ProtocolTestCase(sys.stdin, passthrough=None) - -if sys.version_info[0:2] <= (2, 6): - runner = unittest.TextTestRunner(verbosity=2) -else: - runner = unittest.TextTestRunner(verbosity=2, resultclass=NovaTestResult) - -if runner.run(test).wasSuccessful(): - exit_code = 0 -else: - exit_code = 1 -sys.exit(exit_code) diff --git a/tools/lintstack.py b/tools/lintstack.py index e0cfc28b2..e6516c7e3 100755 --- a/tools/lintstack.py +++ b/tools/lintstack.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # Copyright (c) 2013, AT&T Labs, Yun Mao # All Rights Reserved. # From fca890713aef37ed446071acf4cbe92ee09d05a8 Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Thu, 2 Sep 2021 09:49:00 -0400 Subject: [PATCH 085/159] Add W503 to flake8 ignores Cinder ignores both W503 and W504, and it's really annoying that cinderclient is not consistent with cinder about this. Change-Id: Iab7ff2bfcb61fd5d8a7ee25e245cebe7a50c46b1 --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index a9d74cef1..d158bf462 100644 --- a/tox.ini +++ b/tox.ini @@ -112,7 +112,7 @@ commands = {[testenv:functional]commands} [flake8] show-source = True -ignore = H404,H405,E122,E123,E128,E251,W504 +ignore = H404,H405,E122,E123,E128,E251,W503,W504 exclude = .venv,.git,.tox,dist,doc,*lib/python*,*egg,build application-import-names = cinderclient import-order-style = pep8 From c3c15f6cb2f50ec5bb2ae561d8fc61f27fae80d2 Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Tue, 31 Aug 2021 19:10:00 -0400 Subject: [PATCH 086/159] Support Block Storage API mv 3.66 Block Storage API mv 3.66 enables snapshots of in-use volumes without requiring a 'force' flag. For backward compatibility, the API silently accepts force=true, even though the 'force' flag is considered invalid for that call. That behavior is replicated in the client, where --force with a true value is silently accepted. The --force option is not advertised in the shell and an option value that doesn't evaluate to true raises an UnsupportedAttribute error. Similar behavior from the v3 Snapshot class, except it raises a ValueError under similar circumstances. Change-Id: I7408d0e3a5ed7f4cbcaf65cf3434ad60aaed511d --- cinderclient/api_versions.py | 2 +- cinderclient/tests/unit/v3/test_shell.py | 61 ++++++++ cinderclient/v3/shell.py | 138 ++++++++++++++++++ cinderclient/v3/shell_base.py | 56 ------- cinderclient/v3/volume_snapshots.py | 45 ++++++ .../support-bs-mv-3.66-5214deb20d164056.yaml | 9 ++ 6 files changed, 254 insertions(+), 57 deletions(-) create mode 100644 releasenotes/notes/support-bs-mv-3.66-5214deb20d164056.yaml diff --git a/cinderclient/api_versions.py b/cinderclient/api_versions.py index 78bb8a0af..48e00b741 100644 --- a/cinderclient/api_versions.py +++ b/cinderclient/api_versions.py @@ -26,7 +26,7 @@ # key is unsupported version, value is appropriate supported alternative REPLACEMENT_VERSIONS = {"1": "3", "2": "3"} -MAX_VERSION = "3.65" +MAX_VERSION = "3.66" MIN_VERSION = "3.0" _SUBSTITUTIONS = {} diff --git a/cinderclient/tests/unit/v3/test_shell.py b/cinderclient/tests/unit/v3/test_shell.py index 9a6852fa6..8f005253c 100644 --- a/cinderclient/tests/unit/v3/test_shell.py +++ b/cinderclient/tests/unit/v3/test_shell.py @@ -900,6 +900,67 @@ def test_volume_manageable_list_cluster(self): 'manageable-list --cluster dest') self.assert_called('GET', '/manageable_volumes/detail?cluster=dest') + @ddt.data(True, False, 'Nonboolean') + @mock.patch('cinderclient.utils.find_resource') + def test_snapshot_create_pre_3_66(self, force_value, mock_find_vol): + mock_find_vol.return_value = volumes.Volume( + self, {'id': '123456'}, loaded=True) + snap_body_3_65 = { + 'snapshot': { + 'volume_id': '123456', + 'force': f'{force_value}', + 'name': None, + 'description': None, + 'metadata': {} + } + } + self.run_command('--os-volume-api-version 3.65 ' + f'snapshot-create --force {force_value} 123456') + self.assert_called_anytime('POST', '/snapshots', body=snap_body_3_65) + + SNAP_BODY_3_66 = { + 'snapshot': { + 'volume_id': '123456', + 'name': None, + 'description': None, + 'metadata': {} + } + } + + @ddt.data(True, 'true', 'on', '1') + @mock.patch('cinderclient.utils.find_resource') + def test_snapshot_create_3_66_with_force_true(self, f_val, mock_find_vol): + mock_find_vol.return_value = volumes.Volume( + self, {'id': '123456'}, loaded=True) + mock_find_vol.return_value = volumes.Volume(self, + {'id': '123456'}, + loaded=True) + self.run_command('--os-volume-api-version 3.66 ' + f'snapshot-create --force {f_val} 123456') + self.assert_called_anytime('POST', '/snapshots', + body=self.SNAP_BODY_3_66) + + @ddt.data(False, 'false', 'no', '0', 'whatever') + @mock.patch('cinderclient.utils.find_resource') + def test_snapshot_create_3_66_with_force_not_true( + self, f_val, mock_find_vol): + mock_find_vol.return_value = volumes.Volume( + self, {'id': '123456'}, loaded=True) + uae = self.assertRaises(exceptions.UnsupportedAttribute, + self.run_command, + '--os-volume-api-version 3.66 ' + f'snapshot-create --force {f_val} 123456') + self.assertIn('not allowed after microversion 3.65', str(uae)) + + @mock.patch('cinderclient.utils.find_resource') + def test_snapshot_create_3_66(self, mock_find_vol): + mock_find_vol.return_value = volumes.Volume( + self, {'id': '123456'}, loaded=True) + self.run_command('--os-volume-api-version 3.66 ' + 'snapshot-create 123456') + self.assert_called_anytime('POST', '/snapshots', + body=self.SNAP_BODY_3_66) + def test_snapshot_manageable_list(self): self.run_command('--os-volume-api-version 3.8 ' 'snapshot-manageable-list fakehost') diff --git a/cinderclient/v3/shell.py b/cinderclient/v3/shell.py index 195d859f6..18f94d316 100644 --- a/cinderclient/v3/shell.py +++ b/cinderclient/v3/shell.py @@ -2193,6 +2193,144 @@ def do_snapshot_list(cs, args): AppendFilters.filters = [] +@api_versions.wraps("3.0", "3.65") +@utils.arg('volume', + metavar='', + help='Name or ID of volume to snapshot.') +@utils.arg('--force', + metavar='', + const=True, + nargs='?', + default=False, + end_version='3.65', + help='Allows or disallows snapshot of ' + 'a volume when the volume is attached to an instance. ' + 'If set to True, ignores the current status of the ' + 'volume when attempting to snapshot it rather ' + 'than forcing it to be available. From microversion 3.66, ' + 'all snapshots are "forced" and this option is invalid. ' + 'Default=False.') +@utils.arg('--force', + metavar='', + nargs='?', + default=None, + start_version='3.66', + help=argparse.SUPPRESS) +@utils.arg('--name', + metavar='', + default=None, + help='Snapshot name. Default=None.') +@utils.arg('--display-name', + help=argparse.SUPPRESS) +@utils.arg('--display_name', + help=argparse.SUPPRESS) +@utils.arg('--description', + metavar='', + default=None, + help='Snapshot description. Default=None.') +@utils.arg('--display-description', + help=argparse.SUPPRESS) +@utils.arg('--display_description', + help=argparse.SUPPRESS) +@utils.arg('--metadata', + nargs='*', + metavar='', + default=None, + help='Snapshot metadata key and value pairs. Default=None.') +def do_snapshot_create(cs, args): + """Creates a snapshot.""" + if args.display_name is not None: + args.name = args.display_name + + if args.display_description is not None: + args.description = args.display_description + + snapshot_metadata = None + if args.metadata is not None: + snapshot_metadata = shell_utils.extract_metadata(args) + + volume = utils.find_volume(cs, args.volume) + snapshot = cs.volume_snapshots.create(volume.id, + args.force, + args.name, + args.description, + metadata=snapshot_metadata) + shell_utils.print_volume_snapshot(snapshot) + + +@api_versions.wraps("3.66") +@utils.arg('volume', + metavar='', + help='Name or ID of volume to snapshot.') +@utils.arg('--force', + nargs='?', + help=argparse.SUPPRESS) +@utils.arg('--name', + metavar='', + default=None, + help='Snapshot name. Default=None.') +@utils.arg('--display-name', + help=argparse.SUPPRESS) +@utils.arg('--display_name', + help=argparse.SUPPRESS) +@utils.arg('--description', + metavar='', + default=None, + help='Snapshot description. Default=None.') +@utils.arg('--display-description', + help=argparse.SUPPRESS) +@utils.arg('--display_description', + help=argparse.SUPPRESS) +@utils.arg('--metadata', + nargs='*', + metavar='', + default=None, + help='Snapshot metadata key and value pairs. Default=None.') +def do_snapshot_create(cs, args): # noqa: F811 + """Creates a snapshot.""" + + # TODO(rosmaita): we really need to look into removing this v1 + # compatibility code and the v1 options entirely. Note that if you + # include the --name and also --display_name, the latter will be used. + # Not sure that's desirable, but it is consistent with all the other + # functions in this file, so we'll do it here too. + if args.display_name is not None: + args.name = args.display_name + if args.display_description is not None: + args.description = args.display_description + + snapshot_metadata = None + if args.metadata is not None: + snapshot_metadata = shell_utils.extract_metadata(args) + + force = getattr(args, 'force', None) + + volume = utils.find_volume(cs, args.volume) + + # this is a little weird, but for consistency with the API we + # will silently ignore the --force option when it's passed with + # a value that evaluates to True; otherwise, we report that the + # --force option is illegal for this call + try: + snapshot = cs.volume_snapshots.create(volume.id, + force=force, + name=args.name, + description=args.description, + metadata=snapshot_metadata) + except ValueError as ve: + # make sure it's the exception we expect + em = cinderclient.v3.volume_snapshots.MV_3_66_FORCE_FLAG_ERROR + if em == str(ve): + raise exceptions.UnsupportedAttribute( + 'force', + start_version=None, + end_version=api_versions.APIVersion('3.65')) + else: + raise + + shell_utils.print_volume_snapshot(snapshot) + + @api_versions.wraps('3.27') @utils.arg('--all-tenants', dest='all_tenants', diff --git a/cinderclient/v3/shell_base.py b/cinderclient/v3/shell_base.py index 4dc6da9a4..0fc1b5d7e 100644 --- a/cinderclient/v3/shell_base.py +++ b/cinderclient/v3/shell_base.py @@ -595,62 +595,6 @@ def do_snapshot_show(cs, args): shell_utils.print_volume_snapshot(snapshot) -@utils.arg('volume', - metavar='', - help='Name or ID of volume to snapshot.') -@utils.arg('--force', - metavar='', - const=True, - nargs='?', - default=False, - help='Allows or disallows snapshot of ' - 'a volume when the volume is attached to an instance. ' - 'If set to True, ignores the current status of the ' - 'volume when attempting to snapshot it rather ' - 'than forcing it to be available. ' - 'Default=False.') -@utils.arg('--name', - metavar='', - default=None, - help='Snapshot name. Default=None.') -@utils.arg('--display-name', - help=argparse.SUPPRESS) -@utils.arg('--display_name', - help=argparse.SUPPRESS) -@utils.arg('--description', - metavar='', - default=None, - help='Snapshot description. Default=None.') -@utils.arg('--display-description', - help=argparse.SUPPRESS) -@utils.arg('--display_description', - help=argparse.SUPPRESS) -@utils.arg('--metadata', - nargs='*', - metavar='', - default=None, - help='Snapshot metadata key and value pairs. Default=None.') -def do_snapshot_create(cs, args): - """Creates a snapshot.""" - if args.display_name is not None: - args.name = args.display_name - - if args.display_description is not None: - args.description = args.display_description - - snapshot_metadata = None - if args.metadata is not None: - snapshot_metadata = shell_utils.extract_metadata(args) - - volume = utils.find_volume(cs, args.volume) - snapshot = cs.volume_snapshots.create(volume.id, - args.force, - args.name, - args.description, - metadata=snapshot_metadata) - shell_utils.print_volume_snapshot(snapshot) - - @utils.arg('snapshot', metavar='', nargs='+', help='Name or ID of the snapshot(s) to delete.') diff --git a/cinderclient/v3/volume_snapshots.py b/cinderclient/v3/volume_snapshots.py index ce7f4e07a..9a94422f1 100644 --- a/cinderclient/v3/volume_snapshots.py +++ b/cinderclient/v3/volume_snapshots.py @@ -15,10 +15,18 @@ """Volume snapshot interface (v3 extension).""" +from oslo_utils import strutils + from cinderclient import api_versions from cinderclient.apiclient import base as common_base from cinderclient import base +MV_3_66_FORCE_FLAG_ERROR = ( + "Since microversion 3.66 of the Block Storage API, the 'force' option is " + "invalid for this request. For backward compatibility, however, when the " + "'force' flag is passed with a value evaluating to True, it is silently " + "ignored.") + class Snapshot(base.Resource): """A Snapshot is a point-in-time snapshot of an openstack volume.""" @@ -80,6 +88,7 @@ class SnapshotManager(base.ManagerWithFind): """Manage :class:`Snapshot` resources.""" resource_class = Snapshot + @api_versions.wraps("3.0", "3.65") def create(self, volume_id, force=False, name=None, description=None, metadata=None): @@ -106,6 +115,42 @@ def create(self, volume_id, force=False, 'metadata': snapshot_metadata}} return self._create('/snapshots', body, 'snapshot') + @api_versions.wraps("3.66") + def create(self, volume_id, force=None, # noqa: F811 + name=None, description=None, metadata=None): + + """Creates a snapshot of the given volume. + + :param volume_id: The ID of the volume to snapshot. + :param force: This is technically not valid after mv 3.66, but the + API silently accepts force=True for backward compatibility, so this + function will, too + :param name: Name of the snapshot + :param description: Description of the snapshot + :param metadata: Metadata of the snapshot + :raises: ValueError if 'force' is not passed with a value that + evaluates to true + :rtype: :class:`Snapshot` + """ + + if metadata is None: + snapshot_metadata = {} + else: + snapshot_metadata = metadata + + body = {'snapshot': {'volume_id': volume_id, + 'name': name, + 'description': description, + 'metadata': snapshot_metadata}} + if force is not None: + try: + force = strutils.bool_from_string(force, strict=True) + if not force: + raise ValueError() + except ValueError: + raise ValueError(MV_3_66_FORCE_FLAG_ERROR) + return self._create('/snapshots', body, 'snapshot') + def get(self, snapshot_id): """Shows snapshot details. diff --git a/releasenotes/notes/support-bs-mv-3.66-5214deb20d164056.yaml b/releasenotes/notes/support-bs-mv-3.66-5214deb20d164056.yaml new file mode 100644 index 000000000..c4028b04f --- /dev/null +++ b/releasenotes/notes/support-bs-mv-3.66-5214deb20d164056.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Adds support for Block Storage API version 3.66, which drops the + requirement of a 'force' flag to create a snapshot of an in-use + volume. Although the 'force' flag is invalid for the ``snapshot-create`` + call for API versions 3.66 and higher, for backward compatibility the + cinderclient follows the Block Storage API in silently ignoring the + flag when it is passed with a value that evaluates to True. From fab6ddf30c4bc0add1551d5de05b9bf336317063 Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Wed, 25 Aug 2021 15:23:13 -0400 Subject: [PATCH 087/159] Prepare for Xena cinderclient release Includes prelude plus a note for change I524490aa988f. Change-Id: I233faad57c9708cae9544c965fd0d94abdf6d684 --- .../notes/xena-release-688918a69ada3a58.yaml | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 releasenotes/notes/xena-release-688918a69ada3a58.yaml diff --git a/releasenotes/notes/xena-release-688918a69ada3a58.yaml b/releasenotes/notes/xena-release-688918a69ada3a58.yaml new file mode 100644 index 000000000..3bff19acf --- /dev/null +++ b/releasenotes/notes/xena-release-688918a69ada3a58.yaml @@ -0,0 +1,21 @@ +--- +prelude: | + The Xena release of the python-cinderclient supports Block Storage + API version 3 through microversion 3.66. (The maximum microversion + of the Block Storage API in the Xena release is 3.66.) +upgrade: + - | + The python-cinderclient no longer supports version 2 of the Block + Storage API. The last version of the python-cinderclient supporting + that API is the 7.x series. +features: + - | + Supports Block Storage API version 3.65, which displays a boolean + ``consumes_quota`` field on volume and snapshot detail responses + and which allows filtering volume and snapshot list responses using + the standard ``--filters [ [ ...]]`` option + to the ``cinder list`` or ``cinder snapshot-list`` commands. + + Filtering by this field may not always be possible in a cloud. + Use the ``cinder list-filters`` command to see what filters are + available in the cloud you are using. From 055998d2af3891b623ffcad26d73576474fa7d5c Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Fri, 3 Sep 2021 14:56:40 +0000 Subject: [PATCH 088/159] 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: I1a2ed0695125b5d0733299f3efd263fe61868ee7 --- 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 8acb39c47..f9f9bfd46 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ :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 12963364446015908e7974d9d03dff0974e3c9d7 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Fri, 3 Sep 2021 14:56:42 +0000 Subject: [PATCH 089/159] 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: Ie02a19f3bd51825ee3a4b122ef265eb2b3752a8a --- .zuul.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.zuul.yaml b/.zuul.yaml index 28853f469..3bfb656bc 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -43,7 +43,7 @@ - check-requirements - lib-forward-testing-python3 - openstack-cover-jobs - - openstack-python3-xena-jobs + - openstack-python3-yoga-jobs - publish-openstack-docs-pti - release-notes-jobs-python3 check: From 39a0a92760b4f17772b26a25797cefe4a8047584 Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Thu, 23 Sep 2021 19:20:36 -0400 Subject: [PATCH 090/159] Correct "Increase default quotas for zuul jobs" It turns out that you can't use the devstack post_config phase for this because it happens too late. The quota settings must be made to cinder.conf before cinder-manage db_sync is called where they're used to set the max limit for the tables used by the db quotas driver. Depends-on: https://review.opendev.org/c/openstack/devstack/+/803521 Change-Id: I3bb7d9f9bede063573b24fb5a33a364c53d52434 --- .zuul.yaml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index 3bfb656bc..6181f716a 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -11,11 +11,9 @@ devstack_localrc: USE_PYTHON3: true VOLUME_BACKING_FILE_SIZE: 16G - devstack_local_conf: - post-config: - $CINDER_CONF: - DEFAULT: - quota_volumes: 25 + CINDER_QUOTA_VOLUMES: 25 + CINDER_QUOTA_BACKUPS: 25 + CINDER_QUOTA_SNAPSHOTS: 25 irrelevant-files: - ^.*\.rst$ - ^doc/.*$ From e268778d3efd449ed88de7744005b228751d2a0a Mon Sep 17 00:00:00 2001 From: whoami-rajat Date: Mon, 1 Nov 2021 11:57:44 -0400 Subject: [PATCH 091/159] Improve help text of volume create command This patch adds information about whichever default type is set for API will be used during volume creation if we don't provide a volume type in volume create command. This patch also improves the help text of `cinder type-default` command. Change-Id: I0c437b2c4f02c12d17c04719cbeff8521647ae15 --- cinderclient/v3/shell.py | 5 ++++- cinderclient/v3/shell_base.py | 8 +++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/cinderclient/v3/shell.py b/cinderclient/v3/shell.py index 18f94d316..27f2c7fcc 100644 --- a/cinderclient/v3/shell.py +++ b/cinderclient/v3/shell.py @@ -649,7 +649,10 @@ def do_reset_state(cs, args): @utils.arg('--volume-type', metavar='', default=None, - help='Volume type. Default=None.') + help='Volume type. Default=None, that is, use the default ' + 'volume type configured for the Block Storage API. You ' + "can see what type this is by using the 'cinder type-default'" + ' command.') @utils.arg('--volume_type', help=argparse.SUPPRESS) @utils.arg('--availability-zone', diff --git a/cinderclient/v3/shell_base.py b/cinderclient/v3/shell_base.py index 0fc1b5d7e..0dee47eb9 100644 --- a/cinderclient/v3/shell_base.py +++ b/cinderclient/v3/shell_base.py @@ -693,7 +693,13 @@ def do_type_list(cs, args): def do_type_default(cs, args): - """List the default volume type.""" + """List the default volume type. + + The Block Storage service allows configuration of a default + type for each project, as well as the system default, so use + this command to determine what your effective default volume + type is. + """ vtype = cs.volume_types.default() shell_utils.print_volume_type_list([vtype]) From 6afd886cdd25d1c761d80f6c43303a09187349d5 Mon Sep 17 00:00:00 2001 From: tushargite96 Date: Thu, 25 Nov 2021 12:35:17 +0530 Subject: [PATCH 092/159] Updating python testing as per Yoga testing runtime Yoga testing runtime[1] has been updated to add py39 testing as voting and as we are testing py3.6 and py3.9 we do not need to test py3.7|8 explicitly. 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 makes other required changes in zuul.yaml and update the classifier in setup.cfg file. [1] https://governance.openstack.org/tc/reference/runtimes/yoga.html Change-Id: Ib5b93ca7863fcc0b6d00fb4d52a5f299d1903056 --- .zuul.yaml | 11 ++++++----- setup.cfg | 1 + tox.ini | 7 +------ 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index 6181f716a..c9e5b5087 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -30,11 +30,12 @@ tox_envlist: functional-py36 - job: - name: python-cinderclient-functional-py38 + name: python-cinderclient-functional-py39 parent: python-cinderclient-functional-base + nodeset: devstack-single-node-centos-9-stream vars: - python_version: 3.8 - tox_envlist: functional-py38 + python_version: 3.9 + tox_envlist: functional-py39 - project: templates: @@ -47,10 +48,10 @@ check: jobs: - python-cinderclient-functional-py36 - - python-cinderclient-functional-py38 + - python-cinderclient-functional-py39 - openstack-tox-pylint: voting: false gate: jobs: - python-cinderclient-functional-py36 - - python-cinderclient-functional-py38 + - python-cinderclient-functional-py39 diff --git a/setup.cfg b/setup.cfg index b54ff289e..7506b72c2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -20,6 +20,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 d158bf462..dc8777c3a 100644 --- a/tox.ini +++ b/tox.ini @@ -100,12 +100,7 @@ setenv = {[testenv:functional]setenv} passenv = {[testenv:functional]passenv} commands = {[testenv:functional]commands} -[testenv:functional-py37] -setenv = {[testenv:functional]setenv} -passenv = {[testenv:functional]passenv} -commands = {[testenv:functional]commands} - -[testenv:functional-py38] +[testenv:functional-py39] setenv = {[testenv:functional]setenv} passenv = {[testenv:functional]passenv} commands = {[testenv:functional]commands} From ef4991dc7aa20480cdc4f48ea9874cce1916a4f0 Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Tue, 8 Feb 2022 23:15:13 +0900 Subject: [PATCH 093/159] Add Python 3 only classifier Python 2 support was removed during Ussuri cycle. This change adds the classifier to clearly state that only Python 3 is supported. This allows us to keep the setup.cfg file in cinder and the one in cinderclient more consistent. Change-Id: I5ad342f2b60348a0ba79c95415fdf1ae39714558 --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index b54ff289e..125675e75 100644 --- a/setup.cfg +++ b/setup.cfg @@ -16,6 +16,7 @@ classifier = License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python + Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 From da5ecebb3353618542aa84c2078b03f1a9f6ad83 Mon Sep 17 00:00:00 2001 From: Gorka Eguileor Date: Tue, 8 Feb 2022 17:51:17 +0100 Subject: [PATCH 094/159] Add support for collect-timing option When we run "cinder help" we can see that there is a --collect-timing option: --collect-timing Collect per-API call timing information. This is a keystone session option that we are not currently acting on from a user perspective. This patch adds support for this option, and we'll be able to see the timing in a similar way as we do with OSC: $ cinder --collect-timing api-version +------+---------+---------+-------------+ | ID | Status | Version | Min_version | +------+---------+---------+-------------+ | v3.0 | CURRENT | 3.66 | 3.0 | +------+---------+---------+-------------+ +--------+------------------------------------------------+----------+ | method | url | seconds | +--------+------------------------------------------------+----------+ | GET | http://192.168.121.243/identity | 0.003591 | | POST | http://192.168.121.243/identity/v3/auth/tokens | 0.016649 | | GET | http://192.168.121.243/volume/ | 0.004012 | | GET | http://192.168.121.243/volume/ | 0.004543 | +--------+------------------------------------------------+----------+ The patch formats the "elapsed" time attribute into seconds and renames the column to "seconds" to make it more user friendly similar to OSC. If we didn't it would look like 0:00:00.003744 Closes-Bug: #1960337 Change-Id: Ia6b31794bf60a351007cc4476a76b9bcb76bf378 --- cinderclient/shell.py | 17 +++++++++++++++++ .../notes/collect-timing-ce6d521d40d422fb.yaml | 6 ++++++ 2 files changed, 23 insertions(+) create mode 100644 releasenotes/notes/collect-timing-ce6d521d40d422fb.yaml diff --git a/cinderclient/shell.py b/cinderclient/shell.py index dc9190ab4..ad7876c6e 100644 --- a/cinderclient/shell.py +++ b/cinderclient/shell.py @@ -705,6 +705,12 @@ def main(self, argv): if not auth_session: auth_session = self._get_keystone_session() + # collect_timing is a keystone session option + if (not isinstance(auth_session, session.Session) + and getattr(args, 'collect_timing', False) is True): + raise exc.AuthorizationFailure("Provided auth plugin doesn't " + "support collect_timing option") + insecure = self.options.insecure client_args = dict( @@ -805,6 +811,17 @@ def main(self, argv): print("To display trace use next command:\n" "osprofiler trace show --html %s " % trace_id) + if getattr(args, 'collect_timing', False) is True: + self._print_timings(auth_session) + + def _print_timings(self, session): + timings = session.get_timings() + utils.print_list( + timings, + fields=('method', 'url', 'seconds'), + sortby_index=None, + formatters={'seconds': lambda r: r.elapsed.total_seconds()}) + def _discover_client(self, current_client, os_api_version, diff --git a/releasenotes/notes/collect-timing-ce6d521d40d422fb.yaml b/releasenotes/notes/collect-timing-ce6d521d40d422fb.yaml new file mode 100644 index 000000000..aa2251766 --- /dev/null +++ b/releasenotes/notes/collect-timing-ce6d521d40d422fb.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + `Bug #1960337 `_: Added + support for ``collect-timing`` parameter to see the timings of REST API + requests from the client when using Keystone authentication. From 1aa1ea0250b82ef6e26795de623a58a918339635 Mon Sep 17 00:00:00 2001 From: Eric Harney Date: Fri, 11 Dec 2020 12:24:20 -0500 Subject: [PATCH 095/159] Move tempest requirement to functional env We don't need to track this in test-reqs for unit tests, etc. since it's only used for functional tests. Change-Id: I2e9a55d0f4530e9f3fd0f6c1c48cf5ee19c841da --- test-requirements.txt | 1 - tox.ini | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index e0d7c93c1..0886bd1a8 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -10,7 +10,6 @@ coverage>=5.5 # Apache-2.0 ddt>=1.4.1 # MIT fixtures>=3.0.0 # Apache-2.0/BSD requests-mock>=1.2.0 # Apache-2.0 -tempest>=26.0.0 # Apache-2.0 testtools>=2.4.0 # MIT stestr>=3.1.0 # Apache-2.0 oslo.serialization>=4.1.0 # Apache-2.0 diff --git a/tox.ini b/tox.ini index dc8777c3a..63af14450 100644 --- a/tox.ini +++ b/tox.ini @@ -77,6 +77,9 @@ deps = commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [testenv:functional] +deps = + {[testenv]deps} + tempest>=26.0.0 commands = stestr run {posargs} setenv = {[testenv]setenv} @@ -96,11 +99,13 @@ setenv = passenv = OS_* [testenv:functional-py36] +deps = {[testenv:functional]deps} setenv = {[testenv:functional]setenv} passenv = {[testenv:functional]passenv} commands = {[testenv:functional]commands} [testenv:functional-py39] +deps = {[testenv:functional]deps} setenv = {[testenv:functional]setenv} passenv = {[testenv:functional]passenv} commands = {[testenv:functional]commands} From 12075cb71067563f60de929e61f79eae33b2c2ec Mon Sep 17 00:00:00 2001 From: whoami-rajat Date: Mon, 1 Oct 2018 12:55:43 +0530 Subject: [PATCH 096/159] Add volume reimage command A new reimage API will be introduced on cinder API side with change in depends on. This patch provides the CLI support for the same by adding a reimage command. Implements: blueprint add-volume-re-image-api Change-Id: I37c254d4caf2f416e456ff6a78b5a4df4e08a176 --- cinderclient/api_versions.py | 2 +- cinderclient/tests/unit/v3/fakes.py | 2 ++ cinderclient/tests/unit/v3/fakes_base.py | 2 ++ cinderclient/tests/unit/v3/test_shell.py | 15 +++++++++++++ cinderclient/tests/unit/v3/test_volumes.py | 12 ++++++++++ cinderclient/v3/shell.py | 20 +++++++++++++++++ cinderclient/v3/volumes.py | 22 +++++++++++++++++++ .../reimage-volume-fea3a1178662e65a.yaml | 10 +++++++++ 8 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/reimage-volume-fea3a1178662e65a.yaml diff --git a/cinderclient/api_versions.py b/cinderclient/api_versions.py index 48e00b741..dc2a88d0c 100644 --- a/cinderclient/api_versions.py +++ b/cinderclient/api_versions.py @@ -26,7 +26,7 @@ # key is unsupported version, value is appropriate supported alternative REPLACEMENT_VERSIONS = {"1": "3", "2": "3"} -MAX_VERSION = "3.66" +MAX_VERSION = "3.68" MIN_VERSION = "3.0" _SUBSTITUTIONS = {} diff --git a/cinderclient/tests/unit/v3/fakes.py b/cinderclient/tests/unit/v3/fakes.py index d39dd4baa..f21c2ab2a 100644 --- a/cinderclient/tests/unit/v3/fakes.py +++ b/cinderclient/tests/unit/v3/fakes.py @@ -453,6 +453,8 @@ def post_groups_1234_action(self, body, **kw): 'failover_replication', 'list_replication_targets', 'reset_status'): assert action in body + elif action == 'os-reimage': + assert 'image_id' in body[action] else: raise AssertionError("Unexpected action: %s" % action) return (resp, {}, {}) diff --git a/cinderclient/tests/unit/v3/fakes_base.py b/cinderclient/tests/unit/v3/fakes_base.py index ec75ff079..b5f272844 100644 --- a/cinderclient/tests/unit/v3/fakes_base.py +++ b/cinderclient/tests/unit/v3/fakes_base.py @@ -550,6 +550,8 @@ def post_volumes_1234_action(self, body, **kw): _body = body elif action == 'revert': assert 'snapshot_id' in body[action] + elif action == 'os-reimage': + assert 'image_id' in body[action] else: raise AssertionError("Unexpected action: %s" % action) return (resp, {}, _body) diff --git a/cinderclient/tests/unit/v3/test_shell.py b/cinderclient/tests/unit/v3/test_shell.py index 8f005253c..9eb6ce3ad 100644 --- a/cinderclient/tests/unit/v3/test_shell.py +++ b/cinderclient/tests/unit/v3/test_shell.py @@ -1895,3 +1895,18 @@ def test_do_backup_restore(self, 'volume_id': '1234', 'volume_name': volume_name, }) + + def test_reimage(self): + self.run_command('--os-volume-api-version 3.68 reimage 1234 1') + expected = {'os-reimage': {'image_id': '1', + 'reimage_reserved': False}} + self.assert_called('POST', '/volumes/1234/action', body=expected) + + @ddt.data('False', 'True') + def test_reimage_reserved(self, reimage_reserved): + self.run_command( + '--os-volume-api-version 3.68 reimage --reimage-reserved %s 1234 1' + % reimage_reserved) + expected = {'os-reimage': {'image_id': '1', + 'reimage_reserved': reimage_reserved}} + self.assert_called('POST', '/volumes/1234/action', body=expected) diff --git a/cinderclient/tests/unit/v3/test_volumes.py b/cinderclient/tests/unit/v3/test_volumes.py index c733a0917..1c2f8a2b6 100644 --- a/cinderclient/tests/unit/v3/test_volumes.py +++ b/cinderclient/tests/unit/v3/test_volumes.py @@ -201,3 +201,15 @@ def test_migrate_cluster(self): 'force_host_copy': False, 'lock_volume': False}}) self._assert_request_id(vol) + + @ddt.data(False, True) + def test_reimage(self, reimage_reserved): + cs = fakes.FakeClient(api_versions.APIVersion('3.68')) + v = cs.volumes.get('1234') + self._assert_request_id(v) + vol = cs.volumes.reimage(v, '1', reimage_reserved) + cs.assert_called('POST', '/volumes/1234/action', + {'os-reimage': {'image_id': '1', + 'reimage_reserved': + reimage_reserved}}) + self._assert_request_id(vol) diff --git a/cinderclient/v3/shell.py b/cinderclient/v3/shell.py index 27f2c7fcc..60239c72a 100644 --- a/cinderclient/v3/shell.py +++ b/cinderclient/v3/shell.py @@ -2858,3 +2858,23 @@ def do_default_type_unset(cs, args): except Exception as e: print("Unset for default volume type for project %s failed: %s" % (project_id, e)) + + +@api_versions.wraps('3.68') +@utils.arg('volume', + metavar='', + help='Name or ID of volume to reimage') +@utils.arg('image_id', + metavar='', + help='The image id of the image that will be used to reimage ' + 'the volume.') +@utils.arg('--reimage-reserved', + metavar='', + default=False, + help='Enables or disables reimage for a volume that is in ' + 'reserved state otherwise only volumes in "available" ' + ' or "error" status may be re-imaged. Default=False.') +def do_reimage(cs, args): + """Rebuilds a volume, overwriting all content with the specified image""" + volume = utils.find_volume(cs, args.volume) + volume.reimage(args.image_id, args.reimage_reserved) diff --git a/cinderclient/v3/volumes.py b/cinderclient/v3/volumes.py index 42527f7e7..0479dc351 100644 --- a/cinderclient/v3/volumes.py +++ b/cinderclient/v3/volumes.py @@ -67,6 +67,10 @@ def manage(self, host, ref, name=None, description=None, metadata=metadata, bootable=bootable, cluster=cluster) + def reimage(self, image_id, reimage_reserved=False): + """Rebuilds the volume with the new specified image""" + self.manager.reimage(self, image_id, reimage_reserved) + class VolumeManager(volumes_base.VolumeManager): resource_class = Volume @@ -282,3 +286,21 @@ def get_pools(self, detail, search_opts): # noqa: F811 search_opts=options) return self._get(url, None) + + @api_versions.wraps('3.68') + def reimage(self, volume, image_id, reimage_reserved=False): + """Reimage a volume + + .. warning:: This is a destructive action and the contents of the + volume will be lost. + + :param volume: Volume to reimage. + :param reimage_reserved: Boolean to enable or disable reimage + of a volume that is in 'reserved' state otherwise only + volumes in 'available' status may be re-imaged. + :param image_id: The image id. + """ + return self._action('os-reimage', + volume, + {'image_id': image_id, + 'reimage_reserved': reimage_reserved}) diff --git a/releasenotes/notes/reimage-volume-fea3a1178662e65a.yaml b/releasenotes/notes/reimage-volume-fea3a1178662e65a.yaml new file mode 100644 index 000000000..a95fb1fb9 --- /dev/null +++ b/releasenotes/notes/reimage-volume-fea3a1178662e65a.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + A new ``cinder reimage`` command and related python API binding has been + added which allows a user to replace the current content of a specified + volume with the data of a specified image supplied by the Image service + (Glance). (Note that this is a destructive action, that is, all data + currently contained in the volume is destroyed when the volume is + re-imaged.) This feature requires Block Storage API microversion 3.68 + or greater. From ee59b68d717b871d034139bbb9c3546e4b17d950 Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Thu, 24 Feb 2022 14:40:51 -0500 Subject: [PATCH 097/159] Prepare for Yoga cinderclient release Add a release note prelude for the Yoga release. Change-Id: Id601999762596713c94e7805a7d76dbcf31edf24 --- releasenotes/notes/yoga-release-dcd35c98f6be478e.yaml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 releasenotes/notes/yoga-release-dcd35c98f6be478e.yaml diff --git a/releasenotes/notes/yoga-release-dcd35c98f6be478e.yaml b/releasenotes/notes/yoga-release-dcd35c98f6be478e.yaml new file mode 100644 index 000000000..70e2e1cca --- /dev/null +++ b/releasenotes/notes/yoga-release-dcd35c98f6be478e.yaml @@ -0,0 +1,5 @@ +--- +prelude: | + The Yoga release of the python-cinderclient supports Block Storage + API version 3 through microversion 3.68. (The maximum microversion + of the Block Storage API in the Yoga release is 3.68.) From d60630c3e1be737880b5e0da2109d7016892c098 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Fri, 25 Feb 2022 12:43:42 +0000 Subject: [PATCH 098/159] 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: I96aa28bd09724d3f14935e1dbf13f7e791261ccb --- 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 f9f9bfd46..d27b37c01 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ :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 2c7d463f376c4cb6259291a925ad6cfc48c00f09 Mon Sep 17 00:00:00 2001 From: Ghanshyam Mann Date: Wed, 11 May 2022 21:31:17 -0500 Subject: [PATCH 099/159] Update python testing as per zed cycle testing 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 but to run on ubuntu focal as c8s does not seems to have py38. Also updating the python classifier also to reflect the same. [1] https://governance.openstack.org/tc/reference/runtimes/zed.html Change-Id: Ic26a360d2bb09fa6622d1acaa5021c5afbc70240 --- .zuul.yaml | 16 ++++++++-------- ...drop-python-3-6-and-3-7-fe2dc753e456b527.yaml | 6 ++++++ setup.cfg | 4 +--- tox.ini | 2 +- 4 files changed, 16 insertions(+), 12 deletions(-) create mode 100644 releasenotes/notes/drop-python-3-6-and-3-7-fe2dc753e456b527.yaml diff --git a/.zuul.yaml b/.zuul.yaml index c9e5b5087..0ae7d9f11 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -21,13 +21,13 @@ - ^cinderclient/tests/unit/.*$ - job: - name: python-cinderclient-functional-py36 + name: python-cinderclient-functional-py38 parent: python-cinderclient-functional-base - # need to specify a platform that has python 3.6 available - nodeset: devstack-single-node-centos-8-stream + # need to specify a platform that has python 3.8 available + nodeset: openstack-single-node-focal vars: - python_version: 3.6 - tox_envlist: functional-py36 + python_version: 3.8 + tox_envlist: functional-py38 - job: name: python-cinderclient-functional-py39 @@ -42,16 +42,16 @@ - 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: jobs: - - python-cinderclient-functional-py36 + - python-cinderclient-functional-py38 - python-cinderclient-functional-py39 - openstack-tox-pylint: voting: false gate: jobs: - - python-cinderclient-functional-py36 + - python-cinderclient-functional-py38 - python-cinderclient-functional-py39 diff --git a/releasenotes/notes/drop-python-3-6-and-3-7-fe2dc753e456b527.yaml b/releasenotes/notes/drop-python-3-6-and-3-7-fe2dc753e456b527.yaml new file mode 100644 index 000000000..5915647ac --- /dev/null +++ b/releasenotes/notes/drop-python-3-6-and-3-7-fe2dc753e456b527.yaml @@ -0,0 +1,6 @@ +--- +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 aedf182da..7b3c7982f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -6,7 +6,7 @@ description_file = author = OpenStack author_email = openstack-discuss@lists.openstack.org home_page = https://docs.openstack.org/python-cinderclient/latest/ -python_requires = >=3.6 +python_requires = >=3.8 classifier = Development Status :: 5 - Production/Stable Environment :: Console @@ -18,8 +18,6 @@ classifier = Programming Language :: Python 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 diff --git a/tox.ini b/tox.ini index 63af14450..cda4d2673 100644 --- a/tox.ini +++ b/tox.ini @@ -98,7 +98,7 @@ setenv = # TLS (https) server certificate. passenv = OS_* -[testenv:functional-py36] +[testenv:functional-py38] deps = {[testenv:functional]deps} setenv = {[testenv:functional]setenv} passenv = {[testenv:functional]passenv} From 1f3b6634857e346e2be225a5552469213f329194 Mon Sep 17 00:00:00 2001 From: zack chen Date: Wed, 29 Jun 2022 14:38:18 +0800 Subject: [PATCH 100/159] Fix extension loading from python path Commit 3502a5591a654ae57741c6738994ffa9d8457696 broke extension loading from pythonpath. Incompatible on python3.6.8, python3.8, python3.9. Put it back. Closes-Bug: #1980184 Change-Id: I5b67c93e3c789252d9bd35ee69dddbe1b556dec4 --- cinderclient/client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cinderclient/client.py b/cinderclient/client.py index 6beb381fa..2c1006ff8 100644 --- a/cinderclient/client.py +++ b/cinderclient/client.py @@ -797,6 +797,8 @@ def discover_extensions(version): def _discover_via_python_path(): for (module_loader, name, ispkg) in pkgutil.iter_modules(): if name.endswith('cinderclient_ext'): + if not hasattr(module_loader, 'load_module'): + module_loader = module_loader.find_module(name) module = module_loader.load_module(name) yield name, module From 90eb9d2be62b5eef179edc7d5ab2cb424a6119e0 Mon Sep 17 00:00:00 2001 From: Cyril Roelandt Date: Thu, 28 Jul 2022 20:48:58 +0200 Subject: [PATCH 101/159] Python3.11: Fix argparse-related test failures In Python3.11, the following code crashes: $ cat test.py import argparse parser = argparse.ArgumentParser(description='Short sample app') subparsers = parser.add_subparsers() subparsers.add_parser('foo') subparsers.add_parser('foo') $ python3.11 test.py Traceback (most recent call last): File "/tmp/arg.py", line 6, in subparsers.add_parser('foo') File "/usr/lib/python3.11/argparse.py", line 1197, in add_parser raise ArgumentError(self, _('conflicting subparser: %s') % name) argparse.ArgumentError: argument {foo}: conflicting subparser: foo It is now forbidden to use add_parser() multiple times with the same arguments, which is exactly what we do in the following tests: - cinderclient.tests.unit.test_shell.TestLoadVersionedActions.test_load_versioned_actions - cinderclient.tests.unit.test_shell.TestLoadVersionedActions.test_load_actions_with_versioned_args In order to fix the tests failures, we make sure to reset the parser and subparsers before calling add_parser(). While not strictly necessary, we split those tests into two functions, for readability purposes. Closes-Bug: #1983054 Change-Id: I2d4cb81a394f3c10e5f01945d894746a904fb5df --- cinderclient/tests/unit/test_shell.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/cinderclient/tests/unit/test_shell.py b/cinderclient/tests/unit/test_shell.py index 682d50920..8f236c63e 100644 --- a/cinderclient/tests/unit/test_shell.py +++ b/cinderclient/tests/unit/test_shell.py @@ -376,7 +376,7 @@ def setUp(self): self.mock_completion() - def test_load_versioned_actions(self): + def test_load_versioned_actions_v3_0(self): parser = cinderclient.shell.CinderClientArgumentParser() subparsers = parser.add_subparsers(metavar='') shell = cinderclient.shell.OpenStackCinderShell() @@ -388,6 +388,10 @@ def test_load_versioned_actions(self): "fake_action 3.0 to 3.1", shell.subcommands['fake-action'].get_default('func')()) + def test_load_versioned_actions_v3_2(self): + parser = cinderclient.shell.CinderClientArgumentParser() + subparsers = parser.add_subparsers(metavar='') + shell = cinderclient.shell.OpenStackCinderShell() shell.subcommands = {} shell._find_actions(subparsers, fake_actions_module, api_versions.APIVersion("3.2"), False, []) @@ -521,7 +525,7 @@ def test_load_versioned_actions_with_args_and_help(self, mock_add_arg): @mock.patch.object(cinderclient.shell.CinderClientArgumentParser, 'add_argument') - def test_load_actions_with_versioned_args(self, mock_add_arg): + def test_load_actions_with_versioned_args_v36(self, mock_add_arg): parser = cinderclient.shell.CinderClientArgumentParser(add_help=False) subparsers = parser.add_subparsers(metavar='') shell = cinderclient.shell.OpenStackCinderShell() @@ -533,8 +537,13 @@ def test_load_actions_with_versioned_args(self, mock_add_arg): self.assertNotIn(mock.call('--foo', help="second foo"), mock_add_arg.call_args_list) - mock_add_arg.reset_mock() - + @mock.patch.object(cinderclient.shell.CinderClientArgumentParser, + 'add_argument') + def test_load_actions_with_versioned_args_v39(self, mock_add_arg): + parser = cinderclient.shell.CinderClientArgumentParser(add_help=False) + subparsers = parser.add_subparsers(metavar='') + shell = cinderclient.shell.OpenStackCinderShell() + shell.subcommands = {} shell._find_actions(subparsers, fake_actions_module, api_versions.APIVersion("3.9"), False, []) self.assertNotIn(mock.call('--foo', help="first foo"), From 730a8c7728a8481d4c74e4c93b2619025f7267ea Mon Sep 17 00:00:00 2001 From: Alan Bishop Date: Tue, 9 Aug 2022 13:51:16 -0700 Subject: [PATCH 102/159] Bump API max version to 3.70 Bump MAX_VERSION to 3.70 to support the following: - 3.69 - Allow null value for shared_targets - 3.70 - Support encrypted volume transfers Implements: bp/transfer-encrypted-volume Depends-On: I459f06504e90025c9c0b539981d3d56a2a9394c7 Change-Id: I11072d6d8a185037c7f4cdd52c45933b0cccaf05 --- cinderclient/api_versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cinderclient/api_versions.py b/cinderclient/api_versions.py index dc2a88d0c..5f6ad6537 100644 --- a/cinderclient/api_versions.py +++ b/cinderclient/api_versions.py @@ -26,7 +26,7 @@ # key is unsupported version, value is appropriate supported alternative REPLACEMENT_VERSIONS = {"1": "3", "2": "3"} -MAX_VERSION = "3.68" +MAX_VERSION = "3.70" MIN_VERSION = "3.0" _SUBSTITUTIONS = {} From 51edff7f3415fdf67f2ddb30151f09d4c1ca281a Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Fri, 9 Sep 2022 10:28:11 +0000 Subject: [PATCH 103/159] 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: I593949dae5dd2faf94a639ced8feca80323329f5 --- 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 d27b37c01..340b17f27 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ :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 c354adb368853b513eed1830362920b899d0de87 Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Fri, 9 Sep 2022 09:36:36 -0400 Subject: [PATCH 104/159] Update bindep for ubuntu jammy Python 3.10 unit tests are failing on jammy because the 'python-dev' package is not available on that platform. See py310 failures on https://review.opendev.org/c/openstack/python-cinderclient/+/856719/1 Change-Id: I01501b9dac831c71ac62a2cc624dc0c4933c9c15 --- bindep.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindep.txt b/bindep.txt index 2dbd41a1b..b437edde2 100644 --- a/bindep.txt +++ b/bindep.txt @@ -6,7 +6,7 @@ libffi-dev [platform:dpkg] libffi-devel [platform:rpm] libssl-dev [platform:ubuntu-xenial] locales [platform:debian] -python-dev [platform:dpkg] +python-dev [platform:dpkg !platform:ubuntu-jammy] python-devel [platform:rpm !platform:centos-8] python3-all-dev [platform:ubuntu !platform:ubuntu-precise] python3-dev [platform:dpkg] From 6f67187b8255ae231f82a9deaaf9156c868153a0 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Fri, 9 Sep 2022 10:28:12 +0000 Subject: [PATCH 105/159] Add Python3 antelope unit tests This is an automatically generated patch to ensure unit testing is in place for all the of the tested runtimes for antelope. See also the PTI in governance [1]. [1]: https://governance.openstack.org/tc/reference/project-testing-interface.html Change-Id: If67262fe0f9269e5cb25ebb365e78ebc6cbb3b70 --- .zuul.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.zuul.yaml b/.zuul.yaml index 0ae7d9f11..e29487bf1 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -42,7 +42,7 @@ - check-requirements - lib-forward-testing-python3 - openstack-cover-jobs - - openstack-python3-zed-jobs + - openstack-python3-antelope-jobs - publish-openstack-docs-pti - release-notes-jobs-python3 check: From 8a664728b6a29d3c4fd866d57b677f0e519b90fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Beraud?= Date: Mon, 7 Nov 2022 11:03:56 +0100 Subject: [PATCH 106/159] Remove python-dev from bindep It is no longer supported by jammy and lead us to the following errors with the announce-release job. ``` No package matching 'python-dev' is available ``` Change-Id: I12a31b044d5f88a26984d13adff08dd9c778fe0c --- bindep.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/bindep.txt b/bindep.txt index b437edde2..52426cfd8 100644 --- a/bindep.txt +++ b/bindep.txt @@ -6,8 +6,6 @@ libffi-dev [platform:dpkg] libffi-devel [platform:rpm] libssl-dev [platform:ubuntu-xenial] locales [platform:debian] -python-dev [platform:dpkg !platform:ubuntu-jammy] -python-devel [platform:rpm !platform:centos-8] python3-all-dev [platform:ubuntu !platform:ubuntu-precise] python3-dev [platform:dpkg] python3-devel [platform:rpm] From 9df653571d4da06c25222189be27e87a6da75628 Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Tue, 8 Nov 2022 11:58:38 -0500 Subject: [PATCH 107/159] Handle downgraded client for snapshot-create When a CLI user specifies --os-volume api-version 3.66, the shell will execute the appropriate shell code, but if the server only supports < 3.66, the client is automatically downgraded and correctly uses the pre-3.66 SnapshotManager.create() method. In that case, the 'force' parameter, which is technically not allowed in mv 3.66 (but which silently accepts a True value for backward compatibility), will have a value of None, which the pre-3.66 code happily passes to cinder as '"force": null' in the request body, and which then fails the Block Storage API request-schema check. Handle this situation by detecting a None 'force' value and setting it to its pre-3.66 default value of False. Change-Id: I3ad8283c2a9aaac58c8d2b50fa7ac86b617e5dd3 Closes-bug: #1995883 --- cinderclient/tests/unit/v3/test_shell.py | 76 +++++++++++++++++++ cinderclient/v3/shell.py | 2 + cinderclient/v3/volume_snapshots.py | 14 ++++ ...5883-force-flag-none-3a7bb87f655bcf42.yaml | 8 ++ 4 files changed, 100 insertions(+) create mode 100644 releasenotes/notes/bug-1995883-force-flag-none-3a7bb87f655bcf42.yaml diff --git a/cinderclient/tests/unit/v3/test_shell.py b/cinderclient/tests/unit/v3/test_shell.py index 9eb6ce3ad..7c5f1109c 100644 --- a/cinderclient/tests/unit/v3/test_shell.py +++ b/cinderclient/tests/unit/v3/test_shell.py @@ -99,6 +99,14 @@ def run_command(self, cmd): api_versions.APIVersion('3.99'))): self.shell.main(cmd.split()) + def run_command_with_server_api_max(self, api_max, cmd): + # version negotiation will use the supplied api_max, which must be + # a string value, as the server's max supported version + with mock.patch('cinderclient.api_versions._get_server_version_range', + return_value=(api_versions.APIVersion('3.0'), + api_versions.APIVersion(api_max))): + self.shell.main(cmd.split()) + def assert_called(self, method, url, body=None, partial_body=None, **kwargs): return self.shell.cs.assert_called(method, url, body, @@ -918,6 +926,41 @@ def test_snapshot_create_pre_3_66(self, force_value, mock_find_vol): f'snapshot-create --force {force_value} 123456') self.assert_called_anytime('POST', '/snapshots', body=snap_body_3_65) + @mock.patch('cinderclient.shell.CinderClientArgumentParser.exit') + def test_snapshot_create_pre_3_66_with_naked_force( + self, mock_exit): + mock_exit.side_effect = Exception("mock exit") + try: + self.run_command('--os-volume-api-version 3.65 ' + 'snapshot-create --force 123456') + except Exception as e: + # ignore the exception (it's raised to simulate an exit), + # but make sure it's the exception we expect + self.assertEqual('mock exit', str(e)) + + exit_code = mock_exit.call_args.args[0] + self.assertEqual(2, exit_code) + + @mock.patch('cinderclient.utils.find_resource') + def test_snapshot_create_pre_3_66_with_force_None( + self, mock_find_vol): + """We will let the API detect the problematic value.""" + mock_find_vol.return_value = volumes.Volume( + self, {'id': '123456'}, loaded=True) + snap_body_3_65 = { + 'snapshot': { + 'volume_id': '123456', + # note: this is a string, NOT None! + 'force': 'None', + 'name': None, + 'description': None, + 'metadata': {} + } + } + self.run_command('--os-volume-api-version 3.65 ' + 'snapshot-create --force None 123456') + self.assert_called_anytime('POST', '/snapshots', body=snap_body_3_65) + SNAP_BODY_3_66 = { 'snapshot': { 'volume_id': '123456', @@ -952,6 +995,17 @@ def test_snapshot_create_3_66_with_force_not_true( f'snapshot-create --force {f_val} 123456') self.assertIn('not allowed after microversion 3.65', str(uae)) + @mock.patch('cinderclient.utils.find_resource') + def test_snapshot_create_3_66_with_force_None( + self, mock_find_vol): + mock_find_vol.return_value = volumes.Volume( + self, {'id': '123456'}, loaded=True) + uae = self.assertRaises(exceptions.UnsupportedAttribute, + self.run_command, + '--os-volume-api-version 3.66 ' + 'snapshot-create --force None 123456') + self.assertIn('not allowed after microversion 3.65', str(uae)) + @mock.patch('cinderclient.utils.find_resource') def test_snapshot_create_3_66(self, mock_find_vol): mock_find_vol.return_value = volumes.Volume( @@ -961,6 +1015,28 @@ def test_snapshot_create_3_66(self, mock_find_vol): self.assert_called_anytime('POST', '/snapshots', body=self.SNAP_BODY_3_66) + @mock.patch('cinderclient.utils.find_resource') + def test_snapshot_create_3_66_not_supported(self, mock_find_vol): + mock_find_vol.return_value = volumes.Volume( + self, {'id': '123456'}, loaded=True) + self.run_command_with_server_api_max( + '3.64', + '--os-volume-api-version 3.66 snapshot-create 123456') + # call should be made, but will use the pre-3.66 request body + # because the client in use has been downgraded to 3.64 + pre_3_66_request_body = { + 'snapshot': { + 'volume_id': '123456', + # default value is False + 'force': False, + 'name': None, + 'description': None, + 'metadata': {} + } + } + self.assert_called_anytime('POST', '/snapshots', + body=pre_3_66_request_body) + def test_snapshot_manageable_list(self): self.run_command('--os-volume-api-version 3.8 ' 'snapshot-manageable-list fakehost') diff --git a/cinderclient/v3/shell.py b/cinderclient/v3/shell.py index 60239c72a..2ea1848dd 100644 --- a/cinderclient/v3/shell.py +++ b/cinderclient/v3/shell.py @@ -2213,6 +2213,7 @@ def do_snapshot_list(cs, args): 'than forcing it to be available. From microversion 3.66, ' 'all snapshots are "forced" and this option is invalid. ' 'Default=False.') +# FIXME: is this second declaration of --force really necessary? @utils.arg('--force', metavar='', nargs='?', @@ -2253,6 +2254,7 @@ def do_snapshot_create(cs, args): snapshot_metadata = shell_utils.extract_metadata(args) volume = utils.find_volume(cs, args.volume) + snapshot = cs.volume_snapshots.create(volume.id, args.force, args.name, diff --git a/cinderclient/v3/volume_snapshots.py b/cinderclient/v3/volume_snapshots.py index 9a94422f1..cb1c3baeb 100644 --- a/cinderclient/v3/volume_snapshots.py +++ b/cinderclient/v3/volume_snapshots.py @@ -108,6 +108,20 @@ def create(self, volume_id, force=False, else: snapshot_metadata = metadata + # Bug #1995883: it's possible for the shell to use the user- + # specified 3.66 do_snapshot_create function, but if the server + # only supports < 3.66, the client will have been downgraded and + # will use this function. In that case, the 'force' parameter will + # be None, which means that the user didn't specify a value for it, + # so we set it to the pre-3.66 default value of False. + # + # NOTE: we know this isn't a problem for current client consumers + # because a null value for 'force' has never been allowed by the + # Block Storage API v3, so there's no reason for anyone to directly + # call this method passing force=None. + if force is None: + force = False + body = {'snapshot': {'volume_id': volume_id, 'force': force, 'name': name, diff --git a/releasenotes/notes/bug-1995883-force-flag-none-3a7bb87f655bcf42.yaml b/releasenotes/notes/bug-1995883-force-flag-none-3a7bb87f655bcf42.yaml new file mode 100644 index 000000000..87964d159 --- /dev/null +++ b/releasenotes/notes/bug-1995883-force-flag-none-3a7bb87f655bcf42.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - | + `Bug #1995883 + `_: + Fixed bad format request body generated for the snapshot-create + action when the client supports mv 3.66 or greater but the Block + Storage API being contacted supports < 3.66. From 2d7ae2cd38a2194f98b71d4d98c4273e9ad33e72 Mon Sep 17 00:00:00 2001 From: Eric Harney Date: Wed, 9 Nov 2022 12:28:38 -0500 Subject: [PATCH 108/159] Move print operations to shell_utils Move more code to shell_utils that is only used for shell operations. The benefit of this is that the cinderclient library becomes lighter-weight, because users of the lib no longer have to import prettytable and extra code. Change-Id: I7bf6bd91ee5746d1ad4bd4504f3a056d03ae86a9 --- cinderclient/shell_utils.py | 130 ++++++++++++++++++--- cinderclient/tests/unit/test_shell.py | 2 +- cinderclient/tests/unit/test_utils.py | 18 +-- cinderclient/tests/unit/v3/test_shell.py | 12 +- cinderclient/utils.py | 97 --------------- cinderclient/v3/contrib/list_extensions.py | 4 +- cinderclient/v3/shell.py | 99 ++++++++-------- cinderclient/v3/shell_base.py | 98 ++++++++-------- 8 files changed, 231 insertions(+), 229 deletions(-) diff --git a/cinderclient/shell_utils.py b/cinderclient/shell_utils.py index cd8f1628f..65e840057 100644 --- a/cinderclient/shell_utils.py +++ b/cinderclient/shell_utils.py @@ -15,6 +15,8 @@ import sys import time +import prettytable + from cinderclient import exceptions from cinderclient import utils @@ -24,13 +26,109 @@ _quota_infos = ['Type', 'In_use', 'Reserved', 'Limit', 'Allocated'] +def _print(pt, order): + print(pt.get_string(sortby=order)) + + +def _pretty_format_dict(data_dict): + formatted_data = [] + + for k in sorted(data_dict): + formatted_data.append("%s : %s" % (k, data_dict[k])) + + return "\n".join(formatted_data) + + +def print_list(objs, fields, exclude_unavailable=False, formatters=None, + sortby_index=0): + '''Prints a list of objects. + + @param objs: Objects to print + @param fields: Fields on each object to be printed + @param exclude_unavailable: Boolean to decide if unavailable fields are + removed + @param formatters: Custom field formatters + @param sortby_index: Results sorted against the key in the fields list at + this index; if None then the object order is not + altered + ''' + formatters = formatters or {} + mixed_case_fields = ['serverId'] + removed_fields = [] + rows = [] + + for o in objs: + row = [] + for field in fields: + if field in removed_fields: + continue + if field in formatters: + row.append(formatters[field](o)) + else: + if field in mixed_case_fields: + field_name = field.replace(' ', '_') + else: + field_name = field.lower().replace(' ', '_') + if isinstance(o, dict) and field in o: + data = o[field] + else: + if not hasattr(o, field_name) and exclude_unavailable: + removed_fields.append(field) + continue + else: + data = getattr(o, field_name, '') + if data is None: + data = '-' + if isinstance(data, str) and "\r" in data: + data = data.replace("\r", " ") + row.append(data) + rows.append(row) + + for f in removed_fields: + fields.remove(f) + + pt = prettytable.PrettyTable((f for f in fields), caching=False) + pt.align = 'l' + for row in rows: + count = 0 + # Converts unicode values in dictionary to string + for part in row: + count = count + 1 + if isinstance(part, dict): + row[count - 1] = part + pt.add_row(row) + + if sortby_index is None: + order_by = None + else: + order_by = fields[sortby_index] + _print(pt, order_by) + + +def print_dict(d, property="Property", formatters=None): + pt = prettytable.PrettyTable([property, 'Value'], caching=False) + pt.align = 'l' + formatters = formatters or {} + + for r in d.items(): + r = list(r) + + if r[0] in formatters: + if isinstance(r[1], dict): + r[1] = _pretty_format_dict(r[1]) + if isinstance(r[1], str) and "\r" in r[1]: + r[1] = r[1].replace("\r", " ") + pt.add_row(r) + _print(pt, property) + + def print_volume_image(image_resp_tuple): # image_resp_tuple = tuple (response, body) image = image_resp_tuple[1] vt = image['os-volume_upload_image'].get('volume_type') if vt is not None: image['os-volume_upload_image']['volume_type'] = vt.get('name') - utils.print_dict(image['os-volume_upload_image']) + print_dict(image['os-volume_upload_image']) def poll_for_status(poll_fn, obj_id, action, final_ok_states, @@ -120,7 +218,7 @@ def find_message(cs, message): def print_volume_snapshot(snapshot): - utils.print_dict(snapshot._info) + print_dict(snapshot._info) def translate_keys(collection, convert): @@ -188,16 +286,16 @@ def extract_metadata(args, type='user_metadata'): def print_volume_type_list(vtypes): - utils.print_list(vtypes, ['ID', 'Name', 'Description', 'Is_Public']) + print_list(vtypes, ['ID', 'Name', 'Description', 'Is_Public']) def print_group_type_list(gtypes): - utils.print_list(gtypes, ['ID', 'Name', 'Description']) + print_list(gtypes, ['ID', 'Name', 'Description']) def print_resource_filter_list(filters): formatter = {'Filters': lambda resource: ', '.join(resource.filters)} - utils.print_list(filters, ['Resource', 'Filters'], formatters=formatter) + print_list(filters, ['Resource', 'Filters'], formatters=formatter) def quota_show(quotas): @@ -211,7 +309,7 @@ def quota_show(quotas): if not good_name: continue quota_dict[resource] = getattr(quotas, resource, None) - utils.print_dict(quota_dict) + print_dict(quota_dict) def quota_usage_show(quotas): @@ -228,7 +326,7 @@ def quota_usage_show(quotas): quota_info['Type'] = resource quota_info = dict((k.capitalize(), v) for k, v in quota_info.items()) quota_list.append(quota_info) - utils.print_list(quota_list, _quota_infos) + print_list(quota_list, _quota_infos) def quota_update(manager, identifier, args): @@ -266,26 +364,26 @@ def print_volume_encryption_type_list(encryption_types): :param encryption_types: a list of :class: VolumeEncryptionType instances """ - utils.print_list(encryption_types, ['Volume Type ID', 'Provider', - 'Cipher', 'Key Size', - 'Control Location']) + print_list(encryption_types, ['Volume Type ID', 'Provider', + 'Cipher', 'Key Size', + 'Control Location']) def print_qos_specs(qos_specs): # formatters defines field to be converted from unicode to string - utils.print_dict(qos_specs._info, formatters=['specs']) + print_dict(qos_specs._info, formatters=['specs']) def print_qos_specs_list(q_specs): - utils.print_list(q_specs, ['ID', 'Name', 'Consumer', 'specs']) + print_list(q_specs, ['ID', 'Name', 'Consumer', 'specs']) def print_qos_specs_and_associations_list(q_specs): - utils.print_list(q_specs, ['ID', 'Name', 'Consumer', 'specs']) + print_list(q_specs, ['ID', 'Name', 'Consumer', 'specs']) def print_associations_list(associations): - utils.print_list(associations, ['Association_Type', 'Name', 'ID']) + print_list(associations, ['Association_Type', 'Name', 'ID']) def _poll_for_status(poll_fn, obj_id, info, action, final_ok_states, @@ -305,7 +403,7 @@ def _poll_for_status(poll_fn, obj_id, info, action, final_ok_states, if status in final_ok_states: break elif status == "error": - utils.print_dict(info) + print_dict(info) if global_request_id: search_opts = { 'request_id': global_request_id @@ -317,5 +415,5 @@ def _poll_for_status(poll_fn, obj_id, info, action, final_ok_states, fault_msg = "Unknown error. Operation failed." raise exceptions.ResourceInErrorState(obj, fault_msg) elif time_elapsed == timeout_period: - utils.print_dict(info) + print_dict(info) raise exceptions.TimeoutException(obj, action) diff --git a/cinderclient/tests/unit/test_shell.py b/cinderclient/tests/unit/test_shell.py index 682d50920..e545edc7f 100644 --- a/cinderclient/tests/unit/test_shell.py +++ b/cinderclient/tests/unit/test_shell.py @@ -545,7 +545,7 @@ def test_load_actions_with_versioned_args(self, mock_add_arg): class ShellUtilsTest(utils.TestCase): - @mock.patch.object(cinderclient.utils, 'print_dict') + @mock.patch.object(cinderclient.shell_utils, 'print_dict') def test_print_volume_image(self, mock_print_dict): response = {'os-volume_upload_image': {'name': 'myimg1'}} image_resp_tuple = (202, response) diff --git a/cinderclient/tests/unit/test_utils.py b/cinderclient/tests/unit/test_utils.py index cce4498b4..69b0d0454 100644 --- a/cinderclient/tests/unit/test_utils.py +++ b/cinderclient/tests/unit/test_utils.py @@ -220,7 +220,7 @@ def test_print_list_with_list(self): Row = collections.namedtuple('Row', ['a', 'b']) to_print = [Row(a=3, b=4), Row(a=1, b=2)] with CaptureStdout() as cso: - utils.print_list(to_print, ['a', 'b']) + shell_utils.print_list(to_print, ['a', 'b']) # Output should be sorted by the first key (a) self.assertEqual("""\ +---+---+ @@ -235,7 +235,7 @@ def test_print_list_with_None_data(self): Row = collections.namedtuple('Row', ['a', 'b']) to_print = [Row(a=3, b=None), Row(a=1, b=2)] with CaptureStdout() as cso: - utils.print_list(to_print, ['a', 'b']) + shell_utils.print_list(to_print, ['a', 'b']) # Output should be sorted by the first key (a) self.assertEqual("""\ +---+---+ @@ -250,7 +250,7 @@ def test_print_list_with_list_sortby(self): Row = collections.namedtuple('Row', ['a', 'b']) to_print = [Row(a=4, b=3), Row(a=2, b=1)] with CaptureStdout() as cso: - utils.print_list(to_print, ['a', 'b'], sortby_index=1) + shell_utils.print_list(to_print, ['a', 'b'], sortby_index=1) # Output should be sorted by the second key (b) self.assertEqual("""\ +---+---+ @@ -265,7 +265,7 @@ def test_print_list_with_list_no_sort(self): Row = collections.namedtuple('Row', ['a', 'b']) to_print = [Row(a=3, b=4), Row(a=1, b=2)] with CaptureStdout() as cso: - utils.print_list(to_print, ['a', 'b'], sortby_index=None) + shell_utils.print_list(to_print, ['a', 'b'], sortby_index=None) # Output should be in the order given self.assertEqual("""\ +---+---+ @@ -283,7 +283,7 @@ def gen_rows(): for row in [Row(a=1, b=2), Row(a=3, b=4)]: yield row with CaptureStdout() as cso: - utils.print_list(gen_rows(), ['a', 'b']) + shell_utils.print_list(gen_rows(), ['a', 'b']) self.assertEqual("""\ +---+---+ | a | b | @@ -297,7 +297,7 @@ def test_print_list_with_return(self): Row = collections.namedtuple('Row', ['a', 'b']) to_print = [Row(a=3, b='a\r'), Row(a=1, b='c\rd')] with CaptureStdout() as cso: - utils.print_list(to_print, ['a', 'b']) + shell_utils.print_list(to_print, ['a', 'b']) # Output should be sorted by the first key (a) self.assertEqual("""\ +---+-----+ @@ -314,13 +314,13 @@ class PrintDictTestCase(test_utils.TestCase): def test__pretty_format_dict(self): content = {'key1': 'value1', 'key2': 'value2'} expected = "key1 : value1\nkey2 : value2" - result = utils._pretty_format_dict(content) + result = shell_utils._pretty_format_dict(content) self.assertEqual(expected, result) def test_print_dict_with_return(self): d = {'a': 'A', 'b': 'B', 'c': 'C', 'd': 'test\rcarriage\n\rreturn'} with CaptureStdout() as cso: - utils.print_dict(d) + shell_utils.print_dict(d) self.assertEqual("""\ +----------+---------------+ | Property | Value | @@ -337,7 +337,7 @@ def test_print_dict_with_dict_inside(self): content = {'a': 'A', 'b': 'B', 'f_key': {'key1': 'value1', 'key2': 'value2'}} with CaptureStdout() as cso: - utils.print_dict(content, formatters='f_key') + shell_utils.print_dict(content, formatters='f_key') self.assertEqual("""\ +----------+---------------+ | Property | Value | diff --git a/cinderclient/tests/unit/v3/test_shell.py b/cinderclient/tests/unit/v3/test_shell.py index 9eb6ce3ad..3a7c6db6c 100644 --- a/cinderclient/tests/unit/v3/test_shell.py +++ b/cinderclient/tests/unit/v3/test_shell.py @@ -344,7 +344,7 @@ def test_list_with_group_id_after_3_10(self, version): self.run_command(command) self.assert_called('GET', '/volumes/detail?group_id=fake_id') - @mock.patch("cinderclient.utils.print_list") + @mock.patch("cinderclient.shell_utils.print_list") def test_list_duplicate_fields(self, mock_print): self.run_command('list --field Status,id,Size,status') self.assert_called('GET', '/volumes/detail') @@ -520,7 +520,7 @@ def test_attachment_list(self, cmd, expected): self.run_command(command) self.assert_called('GET', '/attachments%s' % expected) - @mock.patch('cinderclient.utils.print_list') + @mock.patch('cinderclient.shell_utils.print_list') @mock.patch.object(cinderclient.v3.attachments.VolumeAttachmentManager, 'list') def test_attachment_list_setattr(self, mock_list, mock_print): @@ -1209,7 +1209,7 @@ def test_service_get_log_before_3_32(self, get_levels_mock): get_levels_mock.assert_not_called() @mock.patch('cinderclient.v3.services.ServiceManager.get_log_levels') - @mock.patch('cinderclient.utils.print_list') + @mock.patch('cinderclient.shell_utils.print_list') def test_service_get_log_no_params(self, print_mock, get_levels_mock): self.run_command('--os-volume-api-version 3.32 service-get-log') get_levels_mock.assert_called_once_with('', '', '') @@ -1220,7 +1220,7 @@ def test_service_get_log_no_params(self, print_mock, get_levels_mock): @ddt.data('*', 'cinder-api', 'cinder-volume', 'cinder-scheduler', 'cinder-backup') @mock.patch('cinderclient.v3.services.ServiceManager.get_log_levels') - @mock.patch('cinderclient.utils.print_list') + @mock.patch('cinderclient.shell_utils.print_list') def test_service_get_log(self, binary, print_mock, get_levels_mock): server = 'host1' prefix = 'sqlalchemy' @@ -1378,7 +1378,7 @@ def test_backup_with_az(self): 'availability_zone': 'AZ2'}} self.assert_called('POST', '/backups', body=expected) - @mock.patch("cinderclient.utils.print_list") + @mock.patch("cinderclient.shell_utils.print_list") def test_snapshot_list(self, mock_print_list): """Ensure we always present all existing fields when listing snaps.""" self.run_command('--os-volume-api-version 3.65 snapshot-list') @@ -1847,7 +1847,7 @@ def test_restore_with_volume_type_and_az_no_name(self): }, ) @ddt.unpack - @mock.patch('cinderclient.utils.print_dict') + @mock.patch('cinderclient.shell_utils.print_dict') @mock.patch('cinderclient.tests.unit.v3.fakes_base._stub_restore') def test_do_backup_restore(self, mock_stub_restore, diff --git a/cinderclient/utils.py b/cinderclient/utils.py index 99acc03ef..565c61a3f 100644 --- a/cinderclient/utils.py +++ b/cinderclient/utils.py @@ -18,7 +18,6 @@ from urllib import parse import uuid -import prettytable import stevedore from cinderclient import exceptions @@ -106,76 +105,6 @@ def isunauthenticated(f): return getattr(f, 'unauthenticated', False) -def _print(pt, order): - print(pt.get_string(sortby=order)) - - -def print_list(objs, fields, exclude_unavailable=False, formatters=None, - sortby_index=0): - '''Prints a list of objects. - - @param objs: Objects to print - @param fields: Fields on each object to be printed - @param exclude_unavailable: Boolean to decide if unavailable fields are - removed - @param formatters: Custom field formatters - @param sortby_index: Results sorted against the key in the fields list at - this index; if None then the object order is not - altered - ''' - formatters = formatters or {} - mixed_case_fields = ['serverId'] - removed_fields = [] - rows = [] - - for o in objs: - row = [] - for field in fields: - if field in removed_fields: - continue - if field in formatters: - row.append(formatters[field](o)) - else: - if field in mixed_case_fields: - field_name = field.replace(' ', '_') - else: - field_name = field.lower().replace(' ', '_') - if isinstance(o, dict) and field in o: - data = o[field] - else: - if not hasattr(o, field_name) and exclude_unavailable: - removed_fields.append(field) - continue - else: - data = getattr(o, field_name, '') - if data is None: - data = '-' - if isinstance(data, str) and "\r" in data: - data = data.replace("\r", " ") - row.append(data) - rows.append(row) - - for f in removed_fields: - fields.remove(f) - - pt = prettytable.PrettyTable((f for f in fields), caching=False) - pt.align = 'l' - for row in rows: - count = 0 - # Converts unicode values in dictionary to string - for part in row: - count = count + 1 - if isinstance(part, dict): - row[count - 1] = part - pt.add_row(row) - - if sortby_index is None: - order_by = None - else: - order_by = fields[sortby_index] - _print(pt, order_by) - - def build_query_param(params, sort=False): """parse list to url query parameters""" @@ -206,32 +135,6 @@ def build_query_param(params, sort=False): return query_string -def _pretty_format_dict(data_dict): - formatted_data = [] - - for k in sorted(data_dict): - formatted_data.append("%s : %s" % (k, data_dict[k])) - - return "\n".join(formatted_data) - - -def print_dict(d, property="Property", formatters=None): - pt = prettytable.PrettyTable([property, 'Value'], caching=False) - pt.align = 'l' - formatters = formatters or {} - - for r in d.items(): - r = list(r) - - if r[0] in formatters: - if isinstance(r[1], dict): - r[1] = _pretty_format_dict(r[1]) - if isinstance(r[1], str) and "\r" in r[1]: - r[1] = r[1].replace("\r", " ") - pt.add_row(r) - _print(pt, property) - - def find_resource(manager, name_or_id, **kwargs): """Helper for the _find_* methods.""" is_group = kwargs.pop('is_group', False) diff --git a/cinderclient/v3/contrib/list_extensions.py b/cinderclient/v3/contrib/list_extensions.py index 937d34b53..548cbec46 100644 --- a/cinderclient/v3/contrib/list_extensions.py +++ b/cinderclient/v3/contrib/list_extensions.py @@ -14,7 +14,7 @@ # under the License. from cinderclient import base -from cinderclient import utils +from cinderclient import shell_utils class ListExtResource(base.Resource): @@ -41,4 +41,4 @@ def do_list_extensions(client, _args): """Lists all available os-api extensions.""" extensions = client.list_extensions.show_all() fields = ["Name", "Summary", "Alias", "Updated"] - utils.print_list(extensions, fields) + shell_utils.print_list(extensions, fields) diff --git a/cinderclient/v3/shell.py b/cinderclient/v3/shell.py index 60239c72a..974460b17 100644 --- a/cinderclient/v3/shell.py +++ b/cinderclient/v3/shell.py @@ -197,7 +197,7 @@ def do_backup_list(cs, args): sortby_index = None else: sortby_index = 0 - utils.print_list(backups, columns, sortby_index=sortby_index) + shell_utils.print_list(backups, columns, sortby_index=sortby_index) if show_count: print("Backup in total: %s" % total_count) @@ -282,7 +282,7 @@ def do_backup_restore(cs, args): info.update(restore._info) info.pop('links', None) - utils.print_dict(info) + shell_utils.print_dict(info) @utils.arg('--detail', @@ -316,7 +316,7 @@ def do_get_pools(cs, args): backend['name'] = info['name'] if args.detail: backend.update(info['capabilities']) - utils.print_dict(backend) + shell_utils.print_dict(backend) AppendFilters.filters = [] @@ -518,7 +518,7 @@ def do_list(cs, args): sortby_index = None else: sortby_index = 0 - utils.print_list(volumes, key_list, exclude_unavailable=True, + shell_utils.print_list(volumes, key_list, exclude_unavailable=True, sortby_index=sortby_index) if show_count: print("Volume in total: %s" % total_count) @@ -747,7 +747,7 @@ def do_create(cs, args): volume = cs.volumes.get(volume.id) info.update(volume._info) - utils.print_dict(info) + shell_utils.print_dict(info) with cs.volumes.completion_cache('uuid', cinderclient.v3.volumes.Volume, @@ -812,7 +812,7 @@ def do_summary(cs, args): if cs.api_version >= api_versions.APIVersion("3.36"): formatters.append('metadata') - utils.print_dict(info['volume-summary'], formatters=formatters) + shell_utils.print_dict(info['volume-summary'], formatters=formatters) @api_versions.wraps('3.11') @@ -853,7 +853,7 @@ def do_group_type_show(cs, args): info.update(gtype._info) info.pop('links', None) - utils.print_dict(info, formatters=['group_specs']) + shell_utils.print_dict(info, formatters=['group_specs']) @api_versions.wraps('3.11') @@ -881,7 +881,7 @@ def do_group_type_update(cs, args): def do_group_specs_list(cs, args): """Lists current group types and specs.""" gtypes = cs.group_types.list() - utils.print_list(gtypes, ['ID', 'Name', 'group_specs']) + shell_utils.print_list(gtypes, ['ID', 'Name', 'group_specs']) @api_versions.wraps('3.11') @@ -1170,7 +1170,7 @@ def do_cluster_list(cs, args): if args.detailed: columns.extend(('Num Hosts', 'Num Down Hosts', 'Last Heartbeat', 'Disabled Reason', 'Created At', 'Updated at')) - utils.print_list(clusters, columns) + shell_utils.print_list(clusters, columns) @api_versions.wraps('3.7') @@ -1181,7 +1181,7 @@ def do_cluster_list(cs, args): def do_cluster_show(cs, args): """Show detailed information on a clustered service.""" cluster = cs.clusters.show(args.name, args.binary) - utils.print_dict(cluster.to_dict()) + shell_utils.print_dict(cluster.to_dict()) @api_versions.wraps('3.7') @@ -1192,7 +1192,7 @@ def do_cluster_show(cs, args): def do_cluster_enable(cs, args): """Enables clustered services.""" cluster = cs.clusters.update(args.name, args.binary, disabled=False) - utils.print_dict(cluster.to_dict()) + shell_utils.print_dict(cluster.to_dict()) @api_versions.wraps('3.7') @@ -1206,7 +1206,7 @@ def do_cluster_disable(cs, args): """Disables clustered services.""" cluster = cs.clusters.update(args.name, args.binary, disabled=True, disabled_reason=args.reason) - utils.print_dict(cluster.to_dict()) + shell_utils.print_dict(cluster.to_dict()) @api_versions.wraps('3.24') @@ -1251,12 +1251,12 @@ def do_work_cleanup(cs, args): if cleaning: print('Following services will be cleaned:') - utils.print_list(cleaning, columns) + shell_utils.print_list(cleaning, columns) if unavailable: print('There are no alternative nodes to do cleanup for the following ' 'services:') - utils.print_list(unavailable, columns) + shell_utils.print_list(unavailable, columns) if not (cleaning or unavailable): print('No cleanable services matched cleanup criteria.') @@ -1337,7 +1337,7 @@ def do_manage(cs, args): volume = cs.volumes.get(volume.id) info.update(volume._info) info.pop('links', None) - utils.print_dict(info) + shell_utils.print_dict(info) @api_versions.wraps('3.8') @@ -1394,7 +1394,7 @@ def do_manageable_list(cs, args): columns = ['reference', 'size', 'safe_to_manage'] if detailed: columns.extend(['reason_not_safe', 'cinder_id', 'extra_info']) - utils.print_list(volumes, columns, sortby_index=None) + shell_utils.print_list(volumes, columns, sortby_index=None) @api_versions.wraps('3.13') @@ -1427,7 +1427,7 @@ def do_group_list(cs, args): groups = cs.groups.list(search_opts=search_opts) columns = ['ID', 'Status', 'Name'] - utils.print_list(groups, columns) + shell_utils.print_list(groups, columns) with cs.groups.completion_cache( 'uuid', @@ -1469,7 +1469,7 @@ def do_group_show(cs, args): info.update(group._info) info.pop('links', None) - utils.print_dict(info) + shell_utils.print_dict(info) @api_versions.wraps('3.13') @@ -1505,7 +1505,7 @@ def do_group_create(cs, args): info.update(group._info) info.pop('links', None) - utils.print_dict(info) + shell_utils.print_dict(info) with cs.groups.completion_cache('uuid', cinderclient.v3.groups.Group, @@ -1556,7 +1556,7 @@ def do_group_create_from_src(cs, args): args.description) info.pop('links', None) - utils.print_dict(info) + shell_utils.print_dict(info) @api_versions.wraps('3.13') @@ -1696,7 +1696,8 @@ def do_group_list_replication_targets(cs, args): cs, args.group).list_replication_targets() rep_targets = replication_targets.get('replication_targets') if rep_targets and len(rep_targets) > 0: - utils.print_list(rep_targets, [key for key in rep_targets[0].keys()]) + shell_utils.print_list(rep_targets, + [key for key in rep_targets[0].keys()]) @api_versions.wraps('3.14') @@ -1743,7 +1744,7 @@ def do_group_snapshot_list(cs, args): group_snapshots = cs.group_snapshots.list(search_opts=search_opts) columns = ['ID', 'Status', 'Name'] - utils.print_list(group_snapshots, columns) + shell_utils.print_list(group_snapshots, columns) AppendFilters.filters = [] @@ -1758,7 +1759,7 @@ def do_group_snapshot_show(cs, args): info.update(group_snapshot._info) info.pop('links', None) - utils.print_dict(info) + shell_utils.print_dict(info) @api_versions.wraps('3.14') @@ -1786,7 +1787,7 @@ def do_group_snapshot_create(cs, args): info.update(group_snapshot._info) info.pop('links', None) - utils.print_dict(info) + shell_utils.print_dict(info) @api_versions.wraps('3.14') @@ -1841,7 +1842,7 @@ def do_service_list(cs, args): columns.append("Disabled Reason") if cs.api_version.matches('3.49'): columns.extend(["Backend State"]) - utils.print_list(result, columns) + shell_utils.print_list(result, columns) @api_versions.wraps('3.8') @@ -1900,7 +1901,7 @@ def do_snapshot_manageable_list(cs, args): columns = ['reference', 'size', 'safe_to_manage', 'source_reference'] if detailed: columns.extend(['reason_not_safe', 'cinder_id', 'extra_info']) - utils.print_list(snapshots, columns, sortby_index=None) + shell_utils.print_list(snapshots, columns, sortby_index=None) @api_versions.wraps("3.0") @@ -1908,7 +1909,7 @@ def do_api_version(cs, args): """Display the server API version information.""" columns = ['ID', 'Status', 'Version', 'Min_version'] response = cs.services.server_api_version() - utils.print_list(response, columns) + shell_utils.print_list(response, columns) @api_versions.wraps("3.40") @@ -2010,7 +2011,7 @@ def do_message_list(cs, args): sortby_index = None else: sortby_index = 0 - utils.print_list(messages, columns, sortby_index=sortby_index) + shell_utils.print_list(messages, columns, sortby_index=sortby_index) AppendFilters.filters = [] @@ -2024,7 +2025,7 @@ def do_message_show(cs, args): message = shell_utils.find_message(cs, args.message) info.update(message._info) info.pop('links', None) - utils.print_dict(info) + shell_utils.print_dict(info) @api_versions.wraps("3.3") @@ -2179,11 +2180,11 @@ def do_snapshot_list(cs, args): # It's the server's responsibility to return the appropriate fields for the # requested microversion, we present all known fields and skip those that # are missing. - utils.print_list(snapshots, - ['ID', 'Volume ID', 'Status', 'Name', 'Size', - 'Consumes Quota', 'User ID'], - exclude_unavailable=True, - sortby_index=sortby_index) + shell_utils.print_list(snapshots, + ['ID', 'Volume ID', 'Status', 'Name', 'Size', + 'Consumes Quota', 'User ID'], + exclude_unavailable=True, + sortby_index=sortby_index) if show_count: print("Snapshot in total: %s" % total_count) @@ -2409,7 +2410,7 @@ def do_attachment_list(cs, args): sortby_index = None else: sortby_index = 0 - utils.print_list(attachments, columns, sortby_index=sortby_index) + shell_utils.print_list(attachments, columns, sortby_index=sortby_index) AppendFilters.filters = [] @@ -2422,13 +2423,13 @@ def do_attachment_show(cs, args): attachment = cs.attachments.show(args.attachment) attachment_dict = attachment.to_dict() connection_dict = attachment_dict.pop('connection_info', {}) - utils.print_dict(attachment_dict) + shell_utils.print_dict(attachment_dict) # TODO(jdg): Need to add checks here like admin/policy for displaying the # connection_info, this is still experimental so we'll leave it enabled for # now if connection_dict: - utils.print_dict(connection_dict) + shell_utils.print_dict(connection_dict) @api_versions.wraps('3.27') @@ -2501,9 +2502,9 @@ def do_attachment_create(cs, args): mode) connector_dict = attachment.pop('connection_info', None) - utils.print_dict(attachment) + shell_utils.print_dict(attachment) if connector_dict: - utils.print_dict(connector_dict) + shell_utils.print_dict(connector_dict) @api_versions.wraps('3.27') @@ -2555,9 +2556,9 @@ def do_attachment_update(cs, args): connector) attachment_dict = attachment.to_dict() connector_dict = attachment_dict.pop('connection_info', None) - utils.print_dict(attachment_dict) + shell_utils.print_dict(attachment_dict) if connector_dict: - utils.print_dict(connector_dict) + shell_utils.print_dict(connector_dict) @api_versions.wraps('3.27') @@ -2596,7 +2597,7 @@ def do_version_list(cs, args): {'v': api_versions.MAX_VERSION}) print("\nServer supported API versions:") - utils.print_list(result, columns) + shell_utils.print_list(result, columns) @api_versions.wraps('3.32') @@ -2639,7 +2640,7 @@ def do_service_get_log(cs, args): log_levels = cs.services.get_log_levels(args.binary, args.server, args.prefix) columns = ('Binary', 'Host', 'Prefix', 'Level') - utils.print_list(log_levels, columns) + shell_utils.print_list(log_levels, columns) @utils.arg('volume', metavar='', @@ -2716,7 +2717,7 @@ def do_backup_create(cs, args): if 'links' in info: info.pop('links') - utils.print_dict(info) + shell_utils.print_dict(info) with cs.backups.completion_cache( 'uuid', @@ -2757,7 +2758,7 @@ def do_transfer_create(cs, args): info.update(transfer._info) info.pop('links', None) - utils.print_dict(info) + shell_utils.print_dict(info) @utils.arg('--all-tenants', @@ -2805,7 +2806,7 @@ def do_transfer_list(cs, args): transfers = cs.transfers.list(search_opts=search_opts, sort=sort) columns = ['ID', 'Volume ID', 'Name'] - utils.print_list(transfers, columns) + shell_utils.print_list(transfers, columns) AppendFilters.filters = [] @@ -2822,7 +2823,7 @@ def do_default_type_set(cs, args): project = args.project default_type = cs.default_types.create(volume_type, project) - utils.print_dict(default_type._info) + shell_utils.print_dict(default_type._info) @api_versions.wraps('3.62') @@ -2837,9 +2838,9 @@ def do_default_type_list(cs, args): default_types = cs.default_types.list(project_id) columns = ['Volume Type ID', 'Project ID'] if project_id: - utils.print_dict(default_types._info) + shell_utils.print_dict(default_types._info) else: - utils.print_list(default_types, columns) + shell_utils.print_list(default_types, columns) @api_versions.wraps('3.62') diff --git a/cinderclient/v3/shell_base.py b/cinderclient/v3/shell_base.py index 0dee47eb9..56d9df44e 100644 --- a/cinderclient/v3/shell_base.py +++ b/cinderclient/v3/shell_base.py @@ -163,7 +163,7 @@ def do_list(cs, args): sortby_index = None else: sortby_index = 0 - utils.print_list(volumes, key_list, exclude_unavailable=True, + shell_utils.print_list(volumes, key_list, exclude_unavailable=True, sortby_index=sortby_index) @@ -181,9 +181,9 @@ def do_show(cs, args): info.pop('links', None) info = _translate_attachments(info) - utils.print_dict(info, - formatters=['metadata', 'volume_image_metadata', - 'attachment_ids', 'attached_servers']) + shell_utils.print_dict(info, + formatters=['metadata', 'volume_image_metadata', + 'attachment_ids', 'attached_servers']) class CheckSizeArgForCreate(argparse.Action): @@ -325,7 +325,7 @@ def do_create(cs, args): info.pop('links', None) info = _translate_attachments(info) - utils.print_dict(info) + shell_utils.print_dict(info) @utils.arg('--cascade', @@ -581,9 +581,9 @@ def do_snapshot_list(cs, args): else: sortby_index = 0 - utils.print_list(snapshots, - ['ID', 'Volume ID', 'Status', 'Name', 'Size'], - sortby_index=sortby_index) + shell_utils.print_list(snapshots, + ['ID', 'Volume ID', 'Status', 'Name', 'Size'], + sortby_index=sortby_index) @utils.arg('snapshot', @@ -714,7 +714,7 @@ def do_type_show(cs, args): info.update(vtype._info) info.pop('links', None) - utils.print_dict(info, formatters=['extra_specs']) + shell_utils.print_dict(info, formatters=['extra_specs']) @utils.arg('id', @@ -746,7 +746,7 @@ def do_type_update(cs, args): def do_extra_specs_list(cs, args): """Lists current volume types and extra specs.""" vtypes = cs.volume_types.list() - utils.print_list(vtypes, ['ID', 'Name', 'extra_specs']) + shell_utils.print_list(vtypes, ['ID', 'Name', 'extra_specs']) @utils.arg('name', @@ -821,7 +821,7 @@ def do_type_access_list(cs, args): access_list = cs.volume_type_access.list(volume_type) columns = ['Volume_type_ID', 'Project_ID'] - utils.print_list(access_list, columns) + shell_utils.print_list(access_list, columns) @utils.arg('--volume-type', metavar='', required=True, @@ -972,7 +972,7 @@ def do_absolute_limits(cs, args): """Lists absolute limits for a user.""" limits = cs.limits.get(args.tenant).absolute columns = ['Name', 'Value'] - utils.print_list(limits, columns) + shell_utils.print_list(limits, columns) @utils.arg('tenant', @@ -984,7 +984,7 @@ def do_rate_limits(cs, args): """Lists rate limits for a user.""" limits = cs.limits.get(args.tenant).rate columns = ['Verb', 'URI', 'Value', 'Remain', 'Unit', 'Next_Available'] - utils.print_list(limits, columns) + shell_utils.print_list(limits, columns) @utils.arg('volume', @@ -1133,7 +1133,7 @@ def do_backup_create(cs, args): if 'links' in info: info.pop('links') - utils.print_dict(info) + shell_utils.print_dict(info) @utils.arg('backup', metavar='', help='Name or ID of backup.') @@ -1144,7 +1144,7 @@ def do_backup_show(cs, args): info.update(backup._info) info.pop('links', None) - utils.print_dict(info) + shell_utils.print_dict(info) @utils.arg('--all-tenants', @@ -1211,7 +1211,7 @@ def do_backup_list(cs, args): sortby_index = None else: sortby_index = 0 - utils.print_list(backups, columns, sortby_index=sortby_index) + shell_utils.print_list(backups, columns, sortby_index=sortby_index) @utils.arg('--force', @@ -1273,7 +1273,7 @@ def do_backup_restore(cs, args): info.pop('links', None) - utils.print_dict(info) + shell_utils.print_dict(info) @utils.arg('backup', metavar='', @@ -1281,7 +1281,7 @@ def do_backup_restore(cs, args): def do_backup_export(cs, args): """Export backup metadata record.""" info = cs.backups.export_record(args.backup) - utils.print_dict(info) + shell_utils.print_dict(info) @utils.arg('backup_service', metavar='', @@ -1293,7 +1293,7 @@ def do_backup_import(cs, args): info = cs.backups.import_record(args.backup_service, args.backup_url) info.pop('links', None) - utils.print_dict(info) + shell_utils.print_dict(info) @utils.arg('backup', metavar='', nargs='+', @@ -1345,7 +1345,7 @@ def do_transfer_create(cs, args): info.update(transfer._info) info.pop('links', None) - utils.print_dict(info) + shell_utils.print_dict(info) @utils.arg('transfer', metavar='', @@ -1367,7 +1367,7 @@ def do_transfer_accept(cs, args): info.update(transfer._info) info.pop('links', None) - utils.print_dict(info) + shell_utils.print_dict(info) @utils.arg('--all-tenants', @@ -1391,7 +1391,7 @@ def do_transfer_list(cs, args): } transfers = cs.transfers.list(search_opts=search_opts) columns = ['ID', 'Volume ID', 'Name'] - utils.print_list(transfers, columns) + shell_utils.print_list(transfers, columns) @utils.arg('transfer', metavar='', @@ -1403,7 +1403,7 @@ def do_transfer_show(cs, args): info.update(transfer._info) info.pop('links', None) - utils.print_dict(info) + shell_utils.print_dict(info) @utils.arg('volume', metavar='', @@ -1441,7 +1441,7 @@ def do_service_list(cs, args): # so as not to add the column when the extended ext is not enabled. if result and hasattr(result[0], 'disabled_reason'): columns.append("Disabled Reason") - utils.print_list(result, columns) + shell_utils.print_list(result, columns) @utils.arg('host', metavar='', help='Host name.') @@ -1450,7 +1450,7 @@ def do_service_enable(cs, args): """Enables the service.""" result = cs.services.enable(args.host, args.binary) columns = ["Host", "Binary", "Status"] - utils.print_list([result], columns) + shell_utils.print_list([result], columns) @utils.arg('host', metavar='', help='Host name.') @@ -1466,7 +1466,7 @@ def do_service_disable(cs, args): args.reason) else: result = cs.services.disable(args.host, args.binary) - utils.print_list([result], columns) + shell_utils.print_list([result], columns) def treeizeAvailabilityZone(zone): @@ -1525,13 +1525,13 @@ def do_availability_zone_list(cs, _args): for zone in availability_zones: result += treeizeAvailabilityZone(zone) shell_utils.translate_availability_zone_keys(result) - utils.print_list(result, ['Name', 'Status']) + shell_utils.print_list(result, ['Name', 'Status']) def do_encryption_type_list(cs, args): """Shows encryption type details for volume types. Admin only.""" result = cs.volume_encryption_types.list() - utils.print_list(result, ['Volume Type ID', 'Provider', 'Cipher', + shell_utils.print_list(result, ['Volume Type ID', 'Provider', 'Cipher', 'Key Size', 'Control Location']) @@ -1796,7 +1796,7 @@ def do_snapshot_metadata(cs, args): if args.action == 'set': metadata = snapshot.set_metadata(metadata) - utils.print_dict(metadata._info) + shell_utils.print_dict(metadata._info) elif args.action == 'unset': snapshot.delete_metadata(list(metadata.keys())) @@ -1806,7 +1806,7 @@ def do_snapshot_metadata(cs, args): def do_snapshot_metadata_show(cs, args): """Shows snapshot metadata.""" snapshot = shell_utils.find_volume_snapshot(cs, args.snapshot) - utils.print_dict(snapshot._info['metadata'], 'Metadata-property') + shell_utils.print_dict(snapshot._info['metadata'], 'Metadata-property') @utils.arg('volume', metavar='', @@ -1814,7 +1814,7 @@ def do_snapshot_metadata_show(cs, args): def do_metadata_show(cs, args): """Shows volume metadata.""" volume = utils.find_volume(cs, args.volume) - utils.print_dict(volume._info['metadata'], 'Metadata-property') + shell_utils.print_dict(volume._info['metadata'], 'Metadata-property') @utils.arg('volume', metavar='', @@ -1823,7 +1823,7 @@ def do_image_metadata_show(cs, args): """Shows volume image metadata.""" volume = utils.find_volume(cs, args.volume) resp, body = volume.show_image_metadata(volume) - utils.print_dict(body['metadata'], 'Metadata-property') + shell_utils.print_dict(body['metadata'], 'Metadata-property') @utils.arg('volume', @@ -1839,7 +1839,7 @@ def do_metadata_update_all(cs, args): volume = utils.find_volume(cs, args.volume) metadata = shell_utils.extract_metadata(args) metadata = volume.update_all_metadata(metadata) - utils.print_dict(metadata['metadata'], 'Metadata-property') + shell_utils.print_dict(metadata['metadata'], 'Metadata-property') @utils.arg('snapshot', @@ -1855,7 +1855,7 @@ def do_snapshot_metadata_update_all(cs, args): snapshot = shell_utils.find_volume_snapshot(cs, args.snapshot) metadata = shell_utils.extract_metadata(args) metadata = snapshot.update_all_metadata(metadata) - utils.print_dict(metadata) + shell_utils.print_dict(metadata) @utils.arg('volume', metavar='', help='ID of volume to update.') @@ -1954,7 +1954,7 @@ def do_manage(cs, args): volume = cs.volumes.get(volume.id) info.update(volume._info) info.pop('links', None) - utils.print_dict(info) + shell_utils.print_dict(info) @utils.arg('volume', metavar='', @@ -1978,7 +1978,7 @@ def do_consisgroup_list(cs, args): consistencygroups = cs.consistencygroups.list() columns = ['ID', 'Status', 'Name'] - utils.print_list(consistencygroups, columns) + shell_utils.print_list(consistencygroups, columns) @utils.arg('consistencygroup', @@ -1992,7 +1992,7 @@ def do_consisgroup_show(cs, args): info.update(consistencygroup._info) info.pop('links', None) - utils.print_dict(info) + shell_utils.print_dict(info) @utils.arg('volumetypes', @@ -2023,7 +2023,7 @@ def do_consisgroup_create(cs, args): info.update(consistencygroup._info) info.pop('links', None) - utils.print_dict(info) + shell_utils.print_dict(info) @utils.arg('--cgsnapshot', @@ -2061,7 +2061,7 @@ def do_consisgroup_create_from_src(cs, args): args.description) info.pop('links', None) - utils.print_dict(info) + shell_utils.print_dict(info) @utils.arg('consistencygroup', @@ -2166,7 +2166,7 @@ def do_cgsnapshot_list(cs, args): cgsnapshots = cs.cgsnapshots.list(search_opts=search_opts) columns = ['ID', 'Status', 'Name'] - utils.print_list(cgsnapshots, columns) + shell_utils.print_list(cgsnapshots, columns) @utils.arg('cgsnapshot', @@ -2179,7 +2179,7 @@ def do_cgsnapshot_show(cs, args): info.update(cgsnapshot._info) info.pop('links', None) - utils.print_dict(info) + shell_utils.print_dict(info) @utils.arg('consistencygroup', @@ -2207,7 +2207,7 @@ def do_cgsnapshot_create(cs, args): info.update(cgsnapshot._info) info.pop('links', None) - utils.print_dict(info) + shell_utils.print_dict(info) @utils.arg('cgsnapshot', @@ -2241,7 +2241,7 @@ def do_get_pools(cs, args): backend['name'] = info['name'] if args.detail: backend.update(info['capabilities']) - utils.print_dict(backend) + shell_utils.print_dict(backend) @utils.arg('host', @@ -2256,9 +2256,9 @@ def do_get_capabilities(cs, args): infos.update(capabilities._info) prop = infos.pop('properties', None) - utils.print_dict(infos, "Volume stats") - utils.print_dict(prop, "Backend properties", - formatters=sorted(prop.keys())) + shell_utils.print_dict(infos, "Volume stats") + shell_utils.print_dict(prop, "Backend properties", + formatters=sorted(prop.keys())) @utils.arg('volume', @@ -2308,7 +2308,7 @@ def do_snapshot_manage(cs, args): snapshot = cs.volume_snapshots.get(snapshot.id) info.update(snapshot._info) info.pop('links', None) - utils.print_dict(info) + shell_utils.print_dict(info) @utils.arg('snapshot', metavar='', @@ -2378,7 +2378,7 @@ def do_manageable_list(cs, args): columns = ['reference', 'size', 'safe_to_manage'] if detailed: columns.extend(['reason_not_safe', 'cinder_id', 'extra_info']) - utils.print_list(volumes, columns, sortby_index=None) + shell_utils.print_list(volumes, columns, sortby_index=None) @utils.arg('host', @@ -2422,4 +2422,4 @@ def do_snapshot_manageable_list(cs, args): columns = ['reference', 'size', 'safe_to_manage', 'source_reference'] if detailed: columns.extend(['reason_not_safe', 'cinder_id', 'extra_info']) - utils.print_list(snapshots, columns, sortby_index=None) + shell_utils.print_list(snapshots, columns, sortby_index=None) From dec8d4a9ee9208b33a08141d04879369d7210da0 Mon Sep 17 00:00:00 2001 From: Eric Harney Date: Mon, 7 Nov 2022 14:44:51 -0500 Subject: [PATCH 109/159] Add test coverage for shell snapshot-create w/ metadata This path is not currently unit tested. Change-Id: If38c6352f5e1f0a50e4a0d29fbdd5263ccba3b29 --- cinderclient/tests/unit/v3/test_shell.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/cinderclient/tests/unit/v3/test_shell.py b/cinderclient/tests/unit/v3/test_shell.py index 7c5f1109c..841cab99e 100644 --- a/cinderclient/tests/unit/v3/test_shell.py +++ b/cinderclient/tests/unit/v3/test_shell.py @@ -970,6 +970,15 @@ def test_snapshot_create_pre_3_66_with_force_None( } } + SNAP_BODY_3_66_W_METADATA = { + 'snapshot': { + 'volume_id': '123456', + 'name': None, + 'description': None, + 'metadata': {'a': 'b'} + } + } + @ddt.data(True, 'true', 'on', '1') @mock.patch('cinderclient.utils.find_resource') def test_snapshot_create_3_66_with_force_true(self, f_val, mock_find_vol): @@ -1037,6 +1046,15 @@ def test_snapshot_create_3_66_not_supported(self, mock_find_vol): self.assert_called_anytime('POST', '/snapshots', body=pre_3_66_request_body) + @mock.patch('cinderclient.utils.find_resource') + def test_snapshot_create_w_metadata(self, mock_find_vol): + mock_find_vol.return_value = volumes.Volume( + self, {'id': '123456'}, loaded=True) + self.run_command('--os-volume-api-version 3.66 ' + 'snapshot-create 123456 --metadata a=b') + self.assert_called_anytime('POST', '/snapshots', + body=self.SNAP_BODY_3_66_W_METADATA) + def test_snapshot_manageable_list(self): self.run_command('--os-volume-api-version 3.8 ' 'snapshot-manageable-list fakehost') From 20506ef3a8b5fb1b7a9b0a2c31dbe5f60eea2130 Mon Sep 17 00:00:00 2001 From: Cyril Roelandt Date: Thu, 28 Jul 2022 20:14:36 +0200 Subject: [PATCH 110/159] Python3.11: fix crashes in ShellTest In Python3.11, global flags must be placed right at the start of a regular expression. The following regex: r'.*?(?m)^Lists all volumes.', must become: r'(?m).*?^Lists all volumes.', However, since we are using re.MULTILINE, we actually do not need to use a global flag. This commit fixes the following tests in Python3.11: - cinderclient.tests.unit.test_shell.ShellTest.test_help_arg_no_subcommand - cinderclient.tests.unit.test_shell.ShellTest.test_help - cinderclient.tests.unit.test_shell.ShellTest.test_help_on_subcommand - cinderclient.tests.unit.test_shell.ShellTest.test_help_on_subcommand_mv Closes-Bug: #1983047 Change-Id: If20abef109ddd7107c83b5886beb666a6550a640 --- cinderclient/tests/unit/test_shell.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cinderclient/tests/unit/test_shell.py b/cinderclient/tests/unit/test_shell.py index 8f236c63e..48086897d 100644 --- a/cinderclient/tests/unit/test_shell.py +++ b/cinderclient/tests/unit/test_shell.py @@ -120,9 +120,9 @@ def test_help(self): # Some expected help output, including microversioned commands required = [ r'.*?^usage: ', - r'.*?(?m)^\s+create\s+Creates a volume.', - r'.*?(?m)^\s+summary\s+Get volumes summary.', - r'.*?(?m)^Run "cinder help SUBCOMMAND" for help on a subcommand.', + r'.*?^\s+create\s+Creates a volume.', + r'.*?^\s+summary\s+Get volumes summary.', + r'.*?^Run "cinder help SUBCOMMAND" for help on a subcommand.', ] help_text = self.shell('help') for r in required: @@ -132,7 +132,7 @@ def test_help(self): def test_help_on_subcommand(self): required = [ r'.*?^usage: cinder list', - r'.*?(?m)^Lists all volumes.', + r'.*?^Lists all volumes.', ] help_text = self.shell('help list') for r in required: @@ -142,7 +142,7 @@ def test_help_on_subcommand(self): def test_help_on_subcommand_mv(self): required = [ r'.*?^usage: cinder summary', - r'.*?(?m)^Get volumes summary.', + r'.*?^Get volumes summary.', ] help_text = self.shell('help summary') for r in required: @@ -152,9 +152,9 @@ def test_help_on_subcommand_mv(self): def test_help_arg_no_subcommand(self): required = [ r'.*?^usage: ', - r'.*?(?m)^\s+create\s+Creates a volume.', - r'.*?(?m)^\s+summary\s+Get volumes summary.', - r'.*?(?m)^Run "cinder help SUBCOMMAND" for help on a subcommand.', + r'.*?^\s+create\s+Creates a volume.', + r'.*?^\s+summary\s+Get volumes summary.', + r'.*?^Run "cinder help SUBCOMMAND" for help on a subcommand.', ] help_text = self.shell('--os-volume-api-version 3.40') for r in required: From c66b9911b8dcf89910fcab45e9a8211e30419ccf Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Wed, 18 Jan 2023 11:25:08 -0500 Subject: [PATCH 111/159] Continue using tox 3 Use the classic cinderclient tox.ini that has worked so well in the past with tox 3; only change is to add the tox<4 requires statement. Also adjust .zuul.yaml to express our preference for tox<4 in the case that zuul has to install tox. Change-Id: Ib1f55f9431033ad043507c6f751ee9bfe57d5cbb --- .zuul.yaml | 2 ++ tox.ini | 1 + 2 files changed, 3 insertions(+) diff --git a/.zuul.yaml b/.zuul.yaml index e29487bf1..64d85ead0 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -38,6 +38,8 @@ tox_envlist: functional-py39 - project: + vars: + ensure_tox_version: '<4' templates: - check-requirements - lib-forward-testing-python3 diff --git a/tox.ini b/tox.ini index cda4d2673..1cd674d12 100644 --- a/tox.ini +++ b/tox.ini @@ -2,6 +2,7 @@ distribute = False envlist = py3,pep8 minversion = 3.18.0 +requires = tox<4 skipsdist = True # this allows tox to infer the base python from the environment name # and override any basepython configured in this file From 59677fb2e5bd07c08454cb6696deab13054e56ad Mon Sep 17 00:00:00 2001 From: Eric Harney Date: Wed, 4 Jan 2023 12:12:47 -0500 Subject: [PATCH 112/159] remove simplejson requirement The json lib has been included in CPython for ages, so the ImportError case never happens. Change-Id: I669bc397323a0e83d4a9768b124a7d99bafaf38a --- cinderclient/client.py | 6 +----- requirements.txt | 1 - 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/cinderclient/client.py b/cinderclient/client.py index 2c1006ff8..c99a2e7a6 100644 --- a/cinderclient/client.py +++ b/cinderclient/client.py @@ -20,6 +20,7 @@ import hashlib import importlib.util import itertools +import json import logging import os import pkgutil @@ -46,11 +47,6 @@ except ImportError: from time import sleep -try: - import json -except ImportError: - import simplejson as json - try: osprofiler_web = importutils.try_import("osprofiler.web") except Exception: diff --git a/requirements.txt b/requirements.txt index 6f8e90b3f..4c8197620 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,6 @@ pbr>=5.5.0 # Apache-2.0 PrettyTable>=0.7.2 # BSD keystoneauth1>=4.3.1 # Apache-2.0 -simplejson>=3.5.1 # MIT oslo.i18n>=5.0.1 # Apache-2.0 oslo.utils>=4.8.0 # Apache-2.0 requests>=2.25.1 # Apache-2.0 From 5436a5f1b6b60f8e97a77e7b1c3c393c4820844e Mon Sep 17 00:00:00 2001 From: Rajat Dhasmana Date: Mon, 13 Feb 2023 16:11:09 +0000 Subject: [PATCH 113/159] Update minimum requirements This patch updates keystoneauth1 minimum version. The current used version 4.3.1 was released in 22 Feb, 2021 which is ~2 years old. Updating the version to 5.0.0 released in 15 July, 2022 and upper-constraints have 5.1.1 released on 3 Jan, 2023. Change-Id: If8a958345ae62006b8e6221a60ff3217e31823ac --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 4c8197620..054f367b3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ # process, which may cause wedges in the gate later. pbr>=5.5.0 # Apache-2.0 PrettyTable>=0.7.2 # BSD -keystoneauth1>=4.3.1 # Apache-2.0 +keystoneauth1>=5.0.0 # Apache-2.0 oslo.i18n>=5.0.1 # Apache-2.0 oslo.utils>=4.8.0 # Apache-2.0 requests>=2.25.1 # Apache-2.0 From c2dced2dcfd05b269f6fbeeabb5919e88ef60e28 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Fri, 24 Feb 2023 17:35:09 +0000 Subject: [PATCH 114/159] 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: I4d6270b745e171516de982102dbc6e5d9010da19 --- 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 340b17f27..93123f422 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ :maxdepth: 1 unreleased + 2023.1 zed yoga xena From a75c5bfbc4f38915d8e731a806c53d1500e8e09e Mon Sep 17 00:00:00 2001 From: Eric Harney Date: Thu, 9 Mar 2023 11:29:12 -0500 Subject: [PATCH 115/159] Add Python 3.10 to setup.cfg metadata We support 3.10 as of Antelope. Change-Id: Ia32f0488f4fe90527b1fbd0cb663e33e8537a458 --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 7b3c7982f..b512f8374 100644 --- a/setup.cfg +++ b/setup.cfg @@ -20,6 +20,7 @@ classifier = Programming Language :: Python :: 3 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 [files] packages = From e16ae767c44bce98113e3742da78caf43b8ec73a Mon Sep 17 00:00:00 2001 From: Eric Harney Date: Thu, 9 Mar 2023 14:04:10 -0500 Subject: [PATCH 116/159] Remove USE_PYTHON3 setting from .zuul.yaml We don't need this anymore since all of our jobs are Python 3. Change-Id: Ib7e85a72a28ff507cb1371b365b6bfa5fbf70d81 --- .zuul.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.zuul.yaml b/.zuul.yaml index 64d85ead0..7ac5c7dcf 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -9,7 +9,6 @@ vars: openrc_enable_export: true devstack_localrc: - USE_PYTHON3: true VOLUME_BACKING_FILE_SIZE: 16G CINDER_QUOTA_VOLUMES: 25 CINDER_QUOTA_BACKUPS: 25 From 1260de9e61ea276c8f67c39437286b11fcb136fc Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Wed, 15 Feb 2023 20:18:38 -0500 Subject: [PATCH 117/159] Use tox 4 Changes: - eliminate whitespace in passenv values - removed skipsdist=True, which in tox 4 appears to prevent os-brick from being installed in the testenvs - made 4.4.1 the tox minversion to catch relevant bugfixes; since tox is not constrained, we'll actually be using the most recent version of tox 4 - had to adjust the functional testenv's setenv, which wasn't overriding OS_TEST_PATH like it did with tox 3 - used generative section names to define the various functional py3 testenvs we want to support locally (namely, py38, py39, py310, and py311) This patch makes tox 4 the default so that we can hopefully catch problems locally before they block the gate. Change-Id: I01c4d44efa64650cdb46fac34a770a7aa5881baf --- tox.ini | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/tox.ini b/tox.ini index 1cd674d12..b14c24e82 100644 --- a/tox.ini +++ b/tox.ini @@ -1,9 +1,10 @@ [tox] distribute = False envlist = py3,pep8 -minversion = 3.18.0 -requires = tox<4 -skipsdist = True +minversion = 4.4.1 +# specify virtualenv here to keep local runs consistent with the +# gate (it sets the versions of pip, setuptools, and wheel) +requires = virtualenv>=20.17.1 # this allows tox to infer the base python from the environment name # and override any basepython configured in this file ignore_basepython_conflict=true @@ -17,7 +18,9 @@ setenv = OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_TEST_TIMEOUT=60 -passenv = *_proxy *_PROXY +passenv = + *_proxy + *_PROXY deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} @@ -83,8 +86,13 @@ deps = tempest>=26.0.0 commands = stestr run {posargs} setenv = - {[testenv]setenv} - OS_TEST_PATH = ./cinderclient/tests/functional + # can't use {[testenv]setenv} here due to tox 4 issue + # https://github.com/tox-dev/tox/issues/2831 + VIRTUAL_ENV={envdir} + OS_STDOUT_CAPTURE=1 + OS_STDERR_CAPTURE=1 + OS_TEST_TIMEOUT=60 + OS_TEST_PATH=./cinderclient/tests/functional OS_VOLUME_API_VERSION = 3 # must define this here so it can be inherited by the -py3* environments OS_CINDERCLIENT_EXEC_DIR = {envdir}/bin @@ -99,13 +107,7 @@ setenv = # TLS (https) server certificate. passenv = OS_* -[testenv:functional-py38] -deps = {[testenv:functional]deps} -setenv = {[testenv:functional]setenv} -passenv = {[testenv:functional]passenv} -commands = {[testenv:functional]commands} - -[testenv:functional-py39] +[testenv:functional-py{3,38,39,310,311}] deps = {[testenv:functional]deps} setenv = {[testenv:functional]setenv} passenv = {[testenv:functional]passenv} @@ -121,4 +123,3 @@ import-order-style = pep8 [doc8] ignore-path=.tox,*.egg-info,doc/src/api,doc/source/drivers.rst,doc/build,.eggs/*/EGG-INFO/*.txt,doc/source/configuration/tables,./*.txt,releasenotes/build,doc/source/cli/details.rst extension=.txt,.rst,.inc - From 26115ba7b19303de7c15072353f7087030bcf293 Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Mon, 17 Apr 2023 18:58:23 -0400 Subject: [PATCH 118/159] Update functional jobs for 2023.2 Based on the python runtimes for 2023.2 [0], we should be running functional jobs on python 3.9 and python 3.10. [0] https://governance.openstack.org/tc/reference/runtimes/2023.2.html Change-Id: Ifc7dc6588af420aa56cd862dd9f558a4506eb1a0 --- .zuul.yaml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index 64d85ead0..db5434a9a 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -20,15 +20,6 @@ - ^releasenotes/.*$ - ^cinderclient/tests/unit/.*$ -- job: - name: python-cinderclient-functional-py38 - parent: python-cinderclient-functional-base - # need to specify a platform that has python 3.8 available - nodeset: openstack-single-node-focal - vars: - python_version: 3.8 - tox_envlist: functional-py38 - - job: name: python-cinderclient-functional-py39 parent: python-cinderclient-functional-base @@ -37,6 +28,15 @@ python_version: 3.9 tox_envlist: functional-py39 +- job: + name: python-cinderclient-functional-py310 + parent: python-cinderclient-functional-base + # python 3.10 is the default in ubuntu 22.04 (jammy) + nodeset: openstack-single-node-jammy + vars: + python_version: 3.10 + tox_envlist: functional-py310 + - project: vars: ensure_tox_version: '<4' @@ -49,11 +49,11 @@ - release-notes-jobs-python3 check: jobs: - - python-cinderclient-functional-py38 - python-cinderclient-functional-py39 + - python-cinderclient-functional-py310 - openstack-tox-pylint: voting: false gate: jobs: - - python-cinderclient-functional-py38 - python-cinderclient-functional-py39 + - python-cinderclient-functional-py310 From 1cb748bb829c17083b558117d8a971f5600d3c9f Mon Sep 17 00:00:00 2001 From: Eric Harney Date: Thu, 24 Aug 2023 14:25:53 -0400 Subject: [PATCH 119/159] Mock sleep for client tests This takes 7s of sleep time off of a unit test run. Change-Id: I88f2e68abf92819d68f7692917354b9edbebfde5 --- cinderclient/tests/unit/test_http.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cinderclient/tests/unit/test_http.py b/cinderclient/tests/unit/test_http.py index 534a216f9..75276f8f0 100644 --- a/cinderclient/tests/unit/test_http.py +++ b/cinderclient/tests/unit/test_http.py @@ -195,6 +195,7 @@ def request(*args, **kwargs): @mock.patch.object(requests, "request", request) @mock.patch('time.time', mock.Mock(return_value=1234)) + @mock.patch.object(client, 'sleep', mock.Mock()) def test_get_call(): resp, body = cl.get("/hi") @@ -212,6 +213,7 @@ def request(*args, **kwargs): @mock.patch.object(requests, "request", request) @mock.patch('time.time', mock.Mock(return_value=1234)) + @mock.patch.object(client, 'sleep', mock.Mock()) def test_get_call(): resp, body = cl.get("/hi") @@ -225,12 +227,14 @@ def test_rate_limit_overlimit_exception(self): bad_413_request, mock_request] + @mock.patch.object(client, 'sleep', mock.Mock()) def request(*args, **kwargs): next_request = self.requests.pop(0) return next_request(*args, **kwargs) @mock.patch.object(requests, "request", request) @mock.patch('time.time', mock.Mock(return_value=1234)) + @mock.patch.object(client, 'sleep', mock.Mock()) def test_get_call(): resp, body = cl.get("/hi") self.assertRaises(exceptions.OverLimit, test_get_call) @@ -247,6 +251,7 @@ def request(*args, **kwargs): @mock.patch.object(requests, "request", request) @mock.patch('time.time', mock.Mock(return_value=1234)) + @mock.patch.object(client, 'sleep', mock.Mock()) def test_get_call(): resp, body = cl.get("/hi") return resp, body @@ -266,6 +271,7 @@ def request(*args, **kwargs): @mock.patch.object(requests, "request", request) @mock.patch('time.time', mock.Mock(return_value=1234)) + @mock.patch.object(client, 'sleep', mock.Mock()) def test_get_call(): resp, body = cl.get("/hi") @@ -300,6 +306,7 @@ def request(*args, **kwargs): @mock.patch.object(requests, "request", request) @mock.patch('time.time', mock.Mock(return_value=1234)) + @mock.patch.object(client, 'sleep', mock.Mock()) def test_get_call(): resp, body = cl.get("/hi") @@ -405,6 +412,7 @@ def request(*args, **kwargs): @mock.patch.object(requests, "request", request) @mock.patch('time.time', mock.Mock(return_value=1234)) + @mock.patch.object(client, 'sleep', mock.Mock()) def test_get_call(): resp, body = cl.get("/hi") From f1f14dfbb77dd24e2a9c8688bb6ec688d0f8229c Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Wed, 30 Aug 2023 16:35:07 -0400 Subject: [PATCH 120/159] Use tox 4.11.0 We're hitting https://github.com/tox-dev/pyproject-api/issues/101 on the functional jobs in the gate, which are using the system tox. So tell tox to upgrade itself to the version that gets around that issue. Change-Id: Ifabdce8876d287c52cdec9777393fcb7c82c8fc1 --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index b14c24e82..6689038fc 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ [tox] distribute = False envlist = py3,pep8 -minversion = 4.4.1 +minversion = 4.11.0 # specify virtualenv here to keep local runs consistent with the # gate (it sets the versions of pip, setuptools, and wheel) requires = virtualenv>=20.17.1 From 1c3fb4f0683656349f5eabf07651527f9dee6d36 Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Fri, 1 Sep 2023 17:52:27 -0400 Subject: [PATCH 121/159] Use generic testing template Change If402f9ae0ca06fec0 replaced cycle-specific testing templates that had to be changed in each project's zuul config file with a generic template that only needs to be updated in one place, namely, in the openstack-zuul-jobs repo. Apparently cinderclient didn't get the memo, so we fix that now. Change-Id: I1144ed99b98e91035c88bc4cc8a065ab0249a012 --- .zuul.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.zuul.yaml b/.zuul.yaml index 2b3c32a29..3d7092104 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -43,7 +43,7 @@ - check-requirements - lib-forward-testing-python3 - openstack-cover-jobs - - openstack-python3-antelope-jobs + - openstack-python3-jobs - publish-openstack-docs-pti - release-notes-jobs-python3 check: From cf2c27cc97e11b21fdb4e88fe5178d526da55540 Mon Sep 17 00:00:00 2001 From: Rajat Dhasmana Date: Tue, 5 Sep 2023 17:14:56 +0530 Subject: [PATCH 122/159] Honour all_tenants in consistency group list Although consistency groups are deprecated, the CLI command and API support still exists. Currently it accepts the --all-tenant parameter but doesn't pass it to the API request. This patch corrects that behavior. The test for consistency group were removed with the removal of v2 API and also no tests existed for consistency group list so no point in adding them at this point. Change-Id: Ib3557efa50941d75d7f7f0cac01404f5c2db4526 --- cinderclient/v3/shell_base.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cinderclient/v3/shell_base.py b/cinderclient/v3/shell_base.py index 56d9df44e..034071b05 100644 --- a/cinderclient/v3/shell_base.py +++ b/cinderclient/v3/shell_base.py @@ -1975,7 +1975,9 @@ def do_unmanage(cs, args): help='Shows details for all tenants. Admin only.') def do_consisgroup_list(cs, args): """Lists all consistency groups.""" - consistencygroups = cs.consistencygroups.list() + search_opts = {'all_tenants': args.all_tenants} + + consistencygroups = cs.consistencygroups.list(search_opts=search_opts) columns = ['ID', 'Status', 'Name'] shell_utils.print_list(consistencygroups, columns) From 877af0770ced560f0234f202873eabc1e6036f6f Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Fri, 8 Sep 2023 14:56:23 +0000 Subject: [PATCH 123/159] 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: Ibade8b4870bb3a785ab99f06a46be72f1c35f33c --- 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 93123f422..2a58ce82c 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ :maxdepth: 1 unreleased + 2023.2 2023.1 zed yoga From ccbb9ea5e5014898925be1f6a8ab4232bdeba6f9 Mon Sep 17 00:00:00 2001 From: Gorka Eguileor Date: Wed, 13 Sep 2023 14:23:19 +0200 Subject: [PATCH 124/159] Fixed request logging When passing http_log_debug to cinderclient.client.Client we didn't honor it and log the requests if a session is provided. Closes-Bug: 2035372 Change-Id: I505debd650ddce739665b1c9630f17a3b981279f --- cinderclient/client.py | 4 ++++ cinderclient/tests/unit/test_client.py | 22 +++++++++++++++++++ .../http_log_debug-ff023f069afde3fe.yaml | 7 ++++++ 3 files changed, 33 insertions(+) create mode 100644 releasenotes/notes/http_log_debug-ff023f069afde3fe.yaml diff --git a/cinderclient/client.py b/cinderclient/client.py index c99a2e7a6..85ccf7d5c 100644 --- a/cinderclient/client.py +++ b/cinderclient/client.py @@ -175,6 +175,7 @@ class SessionClient(adapter.LegacyJsonAdapter): def __init__(self, *args, **kwargs): apiver = kwargs.pop('api_version', None) or api_versions.APIVersion() + self.http_log_debug = kwargs.pop('http_log_debug', False) if not isinstance(apiver, api_versions.APIVersion): apiver = api_versions.APIVersion(str(apiver)) if apiver.ver_minor != 0: @@ -185,6 +186,8 @@ def __init__(self, *args, **kwargs): def request(self, *args, **kwargs): kwargs.setdefault('authenticated', False) + if self.http_log_debug: + kwargs.setdefault('logger', self._logger) # Note(tpatil): The standard call raises errors from # keystoneauth, here we need to raise the cinderclient errors. @@ -721,6 +724,7 @@ def _construct_http_client(username=None, password=None, project_id=None, region_name=region_name, retries=retries, api_version=api_version, + http_log_debug=http_log_debug, **kwargs) else: # FIXME(jamielennox): username and password are now optional. Need diff --git a/cinderclient/tests/unit/test_client.py b/cinderclient/tests/unit/test_client.py index b7cd3c63a..ba0208ead 100644 --- a/cinderclient/tests/unit/test_client.py +++ b/cinderclient/tests/unit/test_client.py @@ -246,6 +246,28 @@ def test_keystone_request_raises_auth_failure_exception( # is not getting called. self.assertFalse(mock_from_resp.called) + @mock.patch('keystoneauth1.adapter.LegacyJsonAdapter.request', + return_value=(mock.Mock(), mock.Mock())) + @ddt.data(True, False, None) + def test_http_log_debug_request(self, http_log_debug, mock_request): + args_req = (mock.sentinel.url, mock.sentinel.OP) + kwargs_req = {'raise_exc': False} + kwargs_expect = {'authenticated': False} + kwargs_expect.update(kwargs_req) + + kwargs = {'api_version': '3.0'} + if isinstance(http_log_debug, bool): + kwargs['http_log_debug'] = http_log_debug + if http_log_debug: + kwargs_expect['logger'] = mock.ANY + + cs = cinderclient.client.SessionClient(self, **kwargs) + + res = cs.request(*args_req, **kwargs_req) + + mock_request.assert_called_once_with(*args_req, **kwargs_expect) + self.assertEqual(mock_request.return_value, res) + class ClientTestSensitiveInfo(utils.TestCase): def test_req_does_not_log_sensitive_info(self): diff --git a/releasenotes/notes/http_log_debug-ff023f069afde3fe.yaml b/releasenotes/notes/http_log_debug-ff023f069afde3fe.yaml new file mode 100644 index 000000000..6a72cdc49 --- /dev/null +++ b/releasenotes/notes/http_log_debug-ff023f069afde3fe.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + `Bug #2035372 + `_: Fixed + not honoring ``http_log_debug`` parameter in ``cinderclient.client.Client`` + when also providing a session. From ba5e68e36b3296942c0c5124a836c45dcfc8e79c Mon Sep 17 00:00:00 2001 From: Ghanshyam Mann Date: Mon, 8 Jan 2024 20:14:54 -0800 Subject: [PATCH 125/159] 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: Iecc91f97afea4916a0ee619362b1a5eebd966f24 --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index b512f8374..6203eaf33 100644 --- a/setup.cfg +++ b/setup.cfg @@ -21,6 +21,7 @@ classifier = Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 [files] packages = From 9f8a5c767e3743505e2186c44289420f2a50e5dd Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Mon, 5 Feb 2024 16:43:35 +0000 Subject: [PATCH 126/159] 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 Cinder project's maintenance of the Yoga series. This strategy was agreed upon at today's cinder weekly meeting: https://meetings.opendev.org/irclogs/%23openstack-meeting-alt/%23openstack-meeting-alt.2024-02-07.log.html#t2024-02-07T14:06:09 Change-Id: Ib6af80aae60ba359bf02ceeb7626b1c0e0610142 --- 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 dd8ba1268145131417924dfb76876091623a6cdd Mon Sep 17 00:00:00 2001 From: Konrad Gube Date: Mon, 13 Feb 2023 13:43:16 +0100 Subject: [PATCH 127/159] Add os-extend_volume_completion volume action. Change-Id: Ifdeab1a8cd634bbe63c25fae17448c0789b297c9 --- cinderclient/api_versions.py | 2 +- cinderclient/tests/unit/v3/fakes.py | 2 ++ cinderclient/tests/unit/v3/fakes_base.py | 2 ++ cinderclient/tests/unit/v3/test_volumes.py | 10 ++++++++++ cinderclient/v3/volumes.py | 16 ++++++++++++++++ 5 files changed, 31 insertions(+), 1 deletion(-) diff --git a/cinderclient/api_versions.py b/cinderclient/api_versions.py index 5f6ad6537..25818fb56 100644 --- a/cinderclient/api_versions.py +++ b/cinderclient/api_versions.py @@ -26,7 +26,7 @@ # key is unsupported version, value is appropriate supported alternative REPLACEMENT_VERSIONS = {"1": "3", "2": "3"} -MAX_VERSION = "3.70" +MAX_VERSION = "3.71" MIN_VERSION = "3.0" _SUBSTITUTIONS = {} diff --git a/cinderclient/tests/unit/v3/fakes.py b/cinderclient/tests/unit/v3/fakes.py index f21c2ab2a..7dfee24d1 100644 --- a/cinderclient/tests/unit/v3/fakes.py +++ b/cinderclient/tests/unit/v3/fakes.py @@ -455,6 +455,8 @@ def post_groups_1234_action(self, body, **kw): assert action in body elif action == 'os-reimage': assert 'image_id' in body[action] + elif action == 'os-extend_volume_completion': + assert 'error' in body[action] else: raise AssertionError("Unexpected action: %s" % action) return (resp, {}, {}) diff --git a/cinderclient/tests/unit/v3/fakes_base.py b/cinderclient/tests/unit/v3/fakes_base.py index b5f272844..fdbbf1052 100644 --- a/cinderclient/tests/unit/v3/fakes_base.py +++ b/cinderclient/tests/unit/v3/fakes_base.py @@ -552,6 +552,8 @@ def post_volumes_1234_action(self, body, **kw): assert 'snapshot_id' in body[action] elif action == 'os-reimage': assert 'image_id' in body[action] + elif action == 'os-extend_volume_completion': + assert 'error' in body[action] else: raise AssertionError("Unexpected action: %s" % action) return (resp, {}, _body) diff --git a/cinderclient/tests/unit/v3/test_volumes.py b/cinderclient/tests/unit/v3/test_volumes.py index 1c2f8a2b6..b970d7d28 100644 --- a/cinderclient/tests/unit/v3/test_volumes.py +++ b/cinderclient/tests/unit/v3/test_volumes.py @@ -213,3 +213,13 @@ def test_reimage(self, reimage_reserved): 'reimage_reserved': reimage_reserved}}) self._assert_request_id(vol) + + @ddt.data(False, True) + def test_complete_volume_extend(self, error): + cs = fakes.FakeClient(api_versions.APIVersion('3.71')) + v = cs.volumes.get('1234') + self._assert_request_id(v) + vol = cs.volumes.extend_volume_completion(v, error) + cs.assert_called('POST', '/volumes/1234/action', + {'os-extend_volume_completion': {'error': error}}) + self._assert_request_id(vol) diff --git a/cinderclient/v3/volumes.py b/cinderclient/v3/volumes.py index 0479dc351..9751fae53 100644 --- a/cinderclient/v3/volumes.py +++ b/cinderclient/v3/volumes.py @@ -71,6 +71,10 @@ def reimage(self, image_id, reimage_reserved=False): """Rebuilds the volume with the new specified image""" self.manager.reimage(self, image_id, reimage_reserved) + def extend_volume_completion(self, volume, error=False): + """Complete extending an attached volume""" + self.manager.extend_volume_completion(self, volume, error) + class VolumeManager(volumes_base.VolumeManager): resource_class = Volume @@ -304,3 +308,15 @@ def reimage(self, volume, image_id, reimage_reserved=False): volume, {'image_id': image_id, 'reimage_reserved': reimage_reserved}) + + @api_versions.wraps('3.71') + def extend_volume_completion(self, volume, error=False): + """Complete extending an attached volume. + + :param volume: The UUID of the extended volume + :param error: Used to indicate if an error has occured that requires + Cinder to roll back the extend operation. + """ + return self._action('os-extend_volume_completion', + volume, + {'error': error}) From e199e6fd605baa3fdfd40214fe43119065079a85 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Tue, 5 Mar 2024 19:25:55 +0000 Subject: [PATCH 128/159] 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 Cinder project's maintenance of the Victoria series. This strategy is what we used for the yoga transition, and was discussed at a cinder weekly meeting: https://meetings.opendev.org/irclogs/%23openstack-meeting-alt/%23openstack-meeting-alt.2024-02-07.log.html#t2024-02-07T14:06:09 Change-Id: Iefd9121ad051b1dfd7ebd67dc5572f39252b2c1d --- 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 274da7f65f19ad1fe8c228e0f3ba364bc7d267f7 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Tue, 5 Mar 2024 19:27:46 +0000 Subject: [PATCH 129/159] 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 Cinder project's maintenance of the Wallaby series. This strategy is what we used for the yoga transition, and was discussed at a cinder weekly meeting: https://meetings.opendev.org/irclogs/%23openstack-meeting-alt/%23openstack-meeting-alt.2024-02-07.log.html#t2024-02-07T14:06:09 Change-Id: I7410621b98fd9b9b24c654cfe533f0542f8d6874 --- 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 6d0ffb5314c57bbf5388776c46f413dcbec2f5c3 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Tue, 5 Mar 2024 19:29:27 +0000 Subject: [PATCH 130/159] 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 Cinder project's maintenance of the Xena series. This strategy is what we used for the yoga transition, and was discussed at a cinder weekly meeting: https://meetings.opendev.org/irclogs/%23openstack-meeting-alt/%23openstack-meeting-alt.2024-02-07.log.html#t2024-02-07T14:06:09 Change-Id: Ia0ecad37108abef7819518119950a279b8070a0c --- 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 c3131dbd862d6afbf42081d230dbf2422536ed1f Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Fri, 8 Mar 2024 13:53:18 +0000 Subject: [PATCH 131/159] 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: I7c0921f70283c5d1ddabdda01994980068e93a0b --- 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 2a58ce82c..8d5056968 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ :maxdepth: 1 unreleased + 2024.1 2023.2 2023.1 zed From a58bb92b28eb941cdbe3817b585d2b814b0c9147 Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Wed, 10 Apr 2024 09:55:48 -0400 Subject: [PATCH 132/159] Update CI for Dalmatian Updates: - added functional py312 testenv to tox.ini (not needed for unit tests) - removed py38 classifier from setup.cfg, but did not change python_requires (currently it's >=3.8) - run py311 func job in gate (was py310) Change-Id: I661e397e597582404dcb33044844b7af7c64de5f --- .zuul.yaml | 14 +++++++------- setup.cfg | 1 - tox.ini | 2 +- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index 3d7092104..c56b8754d 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -28,13 +28,13 @@ tox_envlist: functional-py39 - job: - name: python-cinderclient-functional-py310 + name: python-cinderclient-functional-py311 parent: python-cinderclient-functional-base - # python 3.10 is the default in ubuntu 22.04 (jammy) - nodeset: openstack-single-node-jammy + # use debian bookworm, where py3.11 is the default + nodeset: devstack-single-node-debian-bookworm vars: - python_version: 3.10 - tox_envlist: functional-py310 + python_version: 3.11 + tox_envlist: functional-py311 - project: vars: @@ -49,10 +49,10 @@ check: jobs: - python-cinderclient-functional-py39 - - python-cinderclient-functional-py310 + - python-cinderclient-functional-py311 - openstack-tox-pylint: voting: false gate: jobs: - python-cinderclient-functional-py39 - - python-cinderclient-functional-py310 + - python-cinderclient-functional-py311 diff --git a/setup.cfg b/setup.cfg index 6203eaf33..559bc273c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -18,7 +18,6 @@ classifier = Programming Language :: Python 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 diff --git a/tox.ini b/tox.ini index 6689038fc..dab45cab6 100644 --- a/tox.ini +++ b/tox.ini @@ -107,7 +107,7 @@ setenv = # TLS (https) server certificate. passenv = OS_* -[testenv:functional-py{3,38,39,310,311}] +[testenv:functional-py{3,38,39,310,311,312}] deps = {[testenv:functional]deps} setenv = {[testenv:functional]setenv} passenv = {[testenv:functional]passenv} From 585c14fdf05ad57f817e5262e54458fa8a8d44ac Mon Sep 17 00:00:00 2001 From: Tatsuya Hayashino Date: Mon, 15 Apr 2024 12:16:56 +0900 Subject: [PATCH 133/159] Fix cinder command execution issue with a token This commit fixes the issue where the following error occurs when executing cinder commands using a token: > ERROR: argument --os-token: conflicting option string: --os-token This issue arises because `os_token` is added to the parser in `keystoneauth1.loading.register_auth_argparse_arguments()`, and then `os_token` is set again by `parser.add_argument('--os-token')`. Closes-Bug: #2061349 Change-Id: I8d1ff0f202bec24ed2982108b6cbba1b7981b356 --- cinderclient/shell.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cinderclient/shell.py b/cinderclient/shell.py index ad7876c6e..b8aa28c4f 100644 --- a/cinderclient/shell.py +++ b/cinderclient/shell.py @@ -331,10 +331,7 @@ def _append_global_identity_args(self, parser): parser.add_argument('--os_region_name', help=argparse.SUPPRESS) - parser.add_argument( - '--os-token', metavar='', - default=utils.env('OS_TOKEN'), - help=_('Defaults to env[OS_TOKEN].')) + parser.set_defaults(os_token=utils.env('OS_TOKEN')) parser.add_argument( '--os_token', help=argparse.SUPPRESS) From 8e5fae8102e1cd3e10cb42d4e9973f09b006409f Mon Sep 17 00:00:00 2001 From: Rajat Dhasmana Date: Fri, 21 Jun 2024 10:53:32 +0530 Subject: [PATCH 134/159] Fix: transfer-delete command All of the cinder resource delete commands accept multiple resources as arguments but in case of transfer-delete, we can only pass one transfer. This patch fixes the issue and allows multiple transfers to be supplied to the transfer-delete command. Closes-Bug: #2069992 Change-Id: Iaccb5dc72e8648b628ff6f1ca2594644b6682c8a --- cinderclient/tests/unit/v3/fakes_base.py | 11 +++++++++++ cinderclient/tests/unit/v3/test_shell.py | 9 +++++++++ cinderclient/v3/shell_base.py | 15 ++++++++++++--- ...delete-multiple-transfer-43a76c403e7c7e7c.yaml | 5 +++++ 4 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/fix-transfer-delete-multiple-transfer-43a76c403e7c7e7c.yaml diff --git a/cinderclient/tests/unit/v3/fakes_base.py b/cinderclient/tests/unit/v3/fakes_base.py index fdbbf1052..b85401d7f 100644 --- a/cinderclient/tests/unit/v3/fakes_base.py +++ b/cinderclient/tests/unit/v3/fakes_base.py @@ -1032,6 +1032,14 @@ def get_qos_specs_1B6B6A04_A927_4AEB_810B_B7BAAD49F57C_disassociate_all( # VolumeTransfers # + def get_os_volume_transfer_1234(self, **kw): + base_uri = 'http://localhost:8776' + tenant_id = '0fa851f6668144cf9cd8c8419c1646c1' + transfer1 = '1234' + return (200, {}, + {'transfer': + _stub_transfer_full(transfer1, base_uri, tenant_id)}) + def get_os_volume_transfer_5678(self, **kw): base_uri = 'http://localhost:8776' tenant_id = '0fa851f6668144cf9cd8c8419c1646c1' @@ -1050,6 +1058,9 @@ def get_os_volume_transfer_detail(self, **kw): _stub_transfer_full(transfer1, base_uri, tenant_id), _stub_transfer_full(transfer2, base_uri, tenant_id)]}) + def delete_os_volume_transfer_1234(self, **kw): + return (202, {}, None) + def delete_os_volume_transfer_5678(self, **kw): return (202, {}, None) diff --git a/cinderclient/tests/unit/v3/test_shell.py b/cinderclient/tests/unit/v3/test_shell.py index ee7162040..89e89d842 100644 --- a/cinderclient/tests/unit/v3/test_shell.py +++ b/cinderclient/tests/unit/v3/test_shell.py @@ -1653,6 +1653,15 @@ def test_list_transfer_sorty_not_sorty(self): url = ('/volume-transfers/detail') self.assert_called('GET', url) + def test_delete_transfer(self): + self.run_command('transfer-delete 1234') + self.assert_called('DELETE', '/os-volume-transfer/1234') + + def test_delete_transfers(self): + self.run_command('transfer-delete 1234 5678') + self.assert_called_anytime('DELETE', '/os-volume-transfer/1234') + self.assert_called_anytime('DELETE', '/os-volume-transfer/5678') + def test_subcommand_parser(self): """Ensure that all the expected commands show up. diff --git a/cinderclient/v3/shell_base.py b/cinderclient/v3/shell_base.py index 034071b05..25d99bb0f 100644 --- a/cinderclient/v3/shell_base.py +++ b/cinderclient/v3/shell_base.py @@ -1348,12 +1348,21 @@ def do_transfer_create(cs, args): shell_utils.print_dict(info) -@utils.arg('transfer', metavar='', +@utils.arg('transfer', metavar='', nargs='+', help='Name or ID of transfer to delete.') def do_transfer_delete(cs, args): """Undoes a transfer.""" - transfer = shell_utils.find_transfer(cs, args.transfer) - transfer.delete() + failure_count = 0 + for t in args.transfer: + try: + transfer = shell_utils.find_transfer(cs, t) + transfer.delete() + except Exception as e: + failure_count += 1 + print("Delete for volume transfer %s failed: %s" % (t, e)) + if failure_count == len(args.transfer): + raise exceptions.CommandError("Unable to delete any of the specified " + "volume transfers.") @utils.arg('transfer', metavar='', diff --git a/releasenotes/notes/fix-transfer-delete-multiple-transfer-43a76c403e7c7e7c.yaml b/releasenotes/notes/fix-transfer-delete-multiple-transfer-43a76c403e7c7e7c.yaml new file mode 100644 index 000000000..7d34c14d7 --- /dev/null +++ b/releasenotes/notes/fix-transfer-delete-multiple-transfer-43a76c403e7c7e7c.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + `Bug #2069992 `_: + Fixed transfer-delete command to accept multiple transfers. From 7a271a7d9ff6777bac2bfff4c81bbe8255fee559 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Tue, 30 Apr 2024 10:59:55 +0000 Subject: [PATCH 135/159] reno: Update master for unmaintained/zed Update the zed release notes configuration to build from zed-eom, which is the last point at which the cinder project team had responsibility for the branch. Change-Id: I10150deaaa60b0e5a5c240523d487a3fb15856de --- 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..1c3dd1e0c 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: zed-eom From 01f0aa115a637e8333d55991103a54283be24ef7 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Fri, 6 Sep 2024 14:04:55 +0000 Subject: [PATCH 136/159] 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: Ie9ddca92a35a895872ae0fdf0b20b75bbb59473f --- 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 8d5056968..b6c7f287b 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ :maxdepth: 1 unreleased + 2024.2 2024.1 2023.2 2023.1 From 125e3d71b1b4a4d6aa43d2393a7ad9860378fd4b Mon Sep 17 00:00:00 2001 From: Eric Harney Date: Fri, 13 Sep 2024 12:00:09 -0400 Subject: [PATCH 137/159] Fix pdf doc build This gets pdf docs building again. Change-Id: I11fee23dc75f57a344d5d3adcbbe6813a3ae7703 --- doc/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 0c5ce55f9..d00b7659c 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -136,4 +136,4 @@ 'preamble': r'\setcounter{tocdepth}{3}', } -latex_additional_files = ['cinderclient.sty'] +latex_additional_files = [] From e23f23c8d89aed5526d892b05c1a11fa5a37b182 Mon Sep 17 00:00:00 2001 From: Eric Harney Date: Mon, 23 Sep 2024 11:33:01 -0400 Subject: [PATCH 138/159] Tests: Fix test_shell in py3.13 Whitespace handling has changed in py3.13 around this area -- rework the test to pass on 3.13 as well as older versions by ignoring whitespace around the expected description. There may be a more complete fix in shell.py's _find_actions but I didn't find it for now. Closes-Bug: #2081633 Change-Id: I83a6a2bc311545811c5c32192494ee82b7903770 --- cinderclient/tests/unit/test_shell.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cinderclient/tests/unit/test_shell.py b/cinderclient/tests/unit/test_shell.py index c5d64af0a..6b67a8eab 100644 --- a/cinderclient/tests/unit/test_shell.py +++ b/cinderclient/tests/unit/test_shell.py @@ -437,12 +437,14 @@ def test_load_versioned_actions_with_help(self): expected_help = ("help message (Supported by API versions " "%(start)s - %(end)s)") % { 'start': '3.0', 'end': '3.3'} - expected_desc = ("help message\n\n " - "This will not show up in help message\n ") + self.assertIn('help message', + mock_add_parser.call_args_list[0][1]['description']) + self.assertIn('This will not show up in help message', + mock_add_parser.call_args_list[0][1]['description']) mock_add_parser.assert_any_call( 'fake-action', help=expected_help, - description=expected_desc, + description=mock.ANY, add_help=False, formatter_class=cinderclient.shell.OpenStackHelpFormatter) From ed0b6a7701723a2eea51f2d23e9e9d45bfb4b7cd Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Fri, 15 Nov 2024 17:05:35 +0000 Subject: [PATCH 139/159] reno: Update master for unmaintained/2023.1 Update the 2023.1 (Antelope) release notes configuration to build from the 2023.1-eom tag, which is the last point at which the cinder project team had responsibility for the branch. Change-Id: I8f72a92b3dac4d47d2cb49e7364a4e0c517110cb --- 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..cd913284d 100644 --- a/releasenotes/source/2023.1.rst +++ b/releasenotes/source/2023.1.rst @@ -3,4 +3,4 @@ =========================== .. release-notes:: - :branch: stable/2023.1 + :branch: 2023.1-eom From 31204545085b6767af2b01daa0ec0633b38cf83d Mon Sep 17 00:00:00 2001 From: Ghanshyam Mann Date: Wed, 11 Dec 2024 19:53:45 -0800 Subject: [PATCH 140/159] Fix the pep8 error on Noble pep8 job failing on Noble with below error. Bumping the hacking version to 7.0.0 to fix it - AttributeError: 'EntryPoints' object has no attribute 'get' Closes-Bug: #2088356 Change-Id: Ib168ad6842dfcf5cac8a85db973e339d2225b444 --- cinderclient/tests/unit/v3/fakes.py | 6 +++--- cinderclient/tests/unit/v3/fakes_base.py | 12 ++++++------ cinderclient/v3/volumes_base.py | 7 ++++--- test-requirements.txt | 2 +- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/cinderclient/tests/unit/v3/fakes.py b/cinderclient/tests/unit/v3/fakes.py index 7dfee24d1..203b3aceb 100644 --- a/cinderclient/tests/unit/v3/fakes.py +++ b/cinderclient/tests/unit/v3/fakes.py @@ -401,16 +401,16 @@ def post_group_types_1_group_specs(self, body, **kw): return (200, {}, {'group_specs': {'k': 'v'}}) def delete_group_types_1_group_specs_k(self, **kw): - return(204, {}, None) + return (204, {}, None) def delete_group_types_1_group_specs_m(self, **kw): - return(204, {}, None) + return (204, {}, None) def delete_group_types_1(self, **kw): return (202, {}, None) def delete_group_types_3_group_specs_k(self, **kw): - return(204, {}, None) + return (204, {}, None) def delete_group_types_3(self, **kw): return (202, {}, None) diff --git a/cinderclient/tests/unit/v3/fakes_base.py b/cinderclient/tests/unit/v3/fakes_base.py index b85401d7f..9702b42c2 100644 --- a/cinderclient/tests/unit/v3/fakes_base.py +++ b/cinderclient/tests/unit/v3/fakes_base.py @@ -772,16 +772,16 @@ def post_types_1_extra_specs(self, body, **kw): return (200, {}, {'extra_specs': {'k': 'v'}}) def delete_types_1_extra_specs_k(self, **kw): - return(204, {}, None) + return (204, {}, None) def delete_types_1_extra_specs_m(self, **kw): - return(204, {}, None) + return (204, {}, None) def delete_types_1(self, **kw): return (202, {}, None) def delete_types_3_extra_specs_k(self, **kw): - return(204, {}, None) + return (204, {}, None) def delete_types_3(self, **kw): return (202, {}, None) @@ -936,13 +936,13 @@ def post_backups_1234_restore(self, **kw): {'restore': _stub_restore()}) def post_backups_76a17945_3c6f_435c_975b_b5685db10b62_action(self, **kw): - return(200, {}, None) + return (200, {}, None) def post_backups_1234_action(self, **kw): - return(200, {}, None) + return (200, {}, None) def post_backups_5678_action(self, **kw): - return(200, {}, None) + return (200, {}, None) def get_backups_76a17945_3c6f_435c_975b_b5685db10b62_export_record(self, **kw): diff --git a/cinderclient/v3/volumes_base.py b/cinderclient/v3/volumes_base.py index 3b00b59d7..c41361cdf 100644 --- a/cinderclient/v3/volumes_base.py +++ b/cinderclient/v3/volumes_base.py @@ -33,7 +33,7 @@ def update(self, **kwargs): return self.manager.update(self, **kwargs) def attach(self, instance_uuid, mountpoint, mode='rw', host_name=None): - """Inform Cinder that the given volume is attached to the given instance. + """Inform Cinder if the given volume is attached to the given instance. Calling this method will not actually ask Cinder to attach a volume, but to mark it on the DB as attached. If the volume @@ -54,9 +54,10 @@ def attach(self, instance_uuid, mountpoint, mode='rw', host_name=None): host_name) def detach(self): - """Inform Cinder that the given volume is detached from the given instance. + """Inform Cinder that the given volume is detached. - Calling this method will not actually ask Cinder to detach + This inform Cinder that the given volume is detached from the given + instance. Calling this method will not actually ask Cinder to detach a volume, but to mark it on the DB as detached. If the volume is not actually detached from the given instance, inconsistent data will result. diff --git a/test-requirements.txt b/test-requirements.txt index 0886bd1a8..b7780b39c 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -3,7 +3,7 @@ # process, which may cause wedges in the gate later. # Hacking already pins down pep8, pyflakes and flake8 -hacking>=4.0.0,<4.1.0 # Apache-2.0 +hacking>=7.0.0,<7.1.0 # Apache-2.0 flake8-import-order # LGPLv3 docutils>=0.16 coverage>=5.5 # Apache-2.0 From 0549d6d9a1e4b3759f30a51829ffa4b00f8b3a41 Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Thu, 12 Dec 2024 22:33:13 +0900 Subject: [PATCH 141/159] Fix outdated notes in requirement files Recent pip no longer requires specific order and can resolve dependencies automatically. Also add a note to explain that lower bounds of requirements are no longer tested and these are now maintained on best-effort-basis. Change-Id: I8287f379cb8d17254df8064af69279fe3361e7a4 --- doc/requirements.txt | 3 --- requirements.txt | 6 +++--- test-requirements.txt | 4 ---- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index 2eec3c6c4..ec6aec6cf 100644 --- a/doc/requirements.txt +++ b/doc/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. # These are needed for docs generation openstackdocstheme>=2.2.1 # Apache-2.0 reno>=3.2.0 # Apache-2.0 diff --git a/requirements.txt b/requirements.txt index 054f367b3..96b4ee4e7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ -# 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. +# Requirements lower bounds listed here are our best effort to keep them up to +# date but we do not test them so no guarantee of having them all correct. If +# you find any incorrect lower bounds, let us know or propose a fix. pbr>=5.5.0 # Apache-2.0 PrettyTable>=0.7.2 # BSD keystoneauth1>=5.0.0 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index b7780b39c..a898b7bf7 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,8 +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 already pins down pep8, pyflakes and flake8 - hacking>=7.0.0,<7.1.0 # Apache-2.0 flake8-import-order # LGPLv3 docutils>=0.16 From 9df662e053a42afa667706d542f2896e288d69ab Mon Sep 17 00:00:00 2001 From: Cyril Roelandt Date: Tue, 12 Nov 2024 23:01:12 +0100 Subject: [PATCH 142/159] Use time.sleep() instead of eventlet.sleep() In a patched environment, time.sleep will be eventlet.sleep, so this does not have any actual impact, but it removes one direct dependency on eventlet. Change-Id: I54112d061fa8118a9a6d91abf07442d8834425b5 --- cinderclient/client.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cinderclient/client.py b/cinderclient/client.py index c99a2e7a6..f065c9fc9 100644 --- a/cinderclient/client.py +++ b/cinderclient/client.py @@ -25,6 +25,7 @@ import os import pkgutil import re +from time import sleep import urllib from urllib import parse as urlparse @@ -42,10 +43,6 @@ from cinderclient import exceptions import cinderclient.extension -try: - from eventlet import sleep -except ImportError: - from time import sleep try: osprofiler_web = importutils.try_import("osprofiler.web") From a0aaed8c010bda0aff485bcd375ec811ef76f772 Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Mon, 13 Jan 2025 23:26:25 +0900 Subject: [PATCH 143/159] Adapt unit tests to keystoneauth 5.9.0 Since keystoneauth 5.9.0[1], LegacyJSONAdapter no longer inherits the base Adapter class. Update mocks according to that change. [1] 0383309ced09813d31150a1495b18073dc944308 Change-Id: I8495db0ca59681bb7888bb8b7b353f8b16aa42fc --- cinderclient/tests/unit/test_client.py | 8 ++++---- cinderclient/tests/unit/test_shell.py | 2 +- requirements.txt | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cinderclient/tests/unit/test_client.py b/cinderclient/tests/unit/test_client.py index b7cd3c63a..c9f075338 100644 --- a/cinderclient/tests/unit/test_client.py +++ b/cinderclient/tests/unit/test_client.py @@ -110,7 +110,7 @@ def test_get_base_url(self, url, expected_base, mock_get_endpoint): cs = cinderclient.client.SessionClient(self, api_version='3.0') self.assertEqual(expected_base, cs._get_base_url()) - @mock.patch.object(adapter.Adapter, 'request') + @mock.patch.object(adapter.LegacyJsonAdapter, '_request') @mock.patch.object(exceptions, 'from_response') def test_sessionclient_request_method( self, mock_from_resp, mock_request): @@ -155,7 +155,7 @@ def test_sessionclient_request_method( self.assertEqual(202, response.status_code) self.assertFalse(mock_from_resp.called) - @mock.patch.object(adapter.Adapter, 'request') + @mock.patch.object(adapter.LegacyJsonAdapter, '_request') def test_sessionclient_request_method_raises_badrequest( self, mock_request): kwargs = { @@ -193,7 +193,7 @@ def test_sessionclient_request_method_raises_badrequest( mock.sentinel.url, 'POST', **kwargs) self.assertIsNotNone(session_client._logger) - @mock.patch.object(adapter.Adapter, 'request') + @mock.patch.object(adapter.LegacyJsonAdapter, '_request') def test_sessionclient_request_method_raises_overlimit( self, mock_request): resp = { @@ -232,7 +232,7 @@ def test_keystone_request_raises_auth_failure_exception( } } - with mock.patch.object(adapter.Adapter, 'request', + with mock.patch.object(adapter.LegacyJsonAdapter, '_request', side_effect= keystone_exception.AuthorizationFailure()): session_client = cinderclient.client.SessionClient( diff --git a/cinderclient/tests/unit/test_shell.py b/cinderclient/tests/unit/test_shell.py index c5d64af0a..52dcc88c3 100644 --- a/cinderclient/tests/unit/test_shell.py +++ b/cinderclient/tests/unit/test_shell.py @@ -232,7 +232,7 @@ def test_cinder_service_name(self): self.list_volumes_on_service(count) @mock.patch('keystoneauth1.identity.v2.Password') - @mock.patch('keystoneauth1.adapter.Adapter.get_token', + @mock.patch('keystoneauth1.adapter.LegacyJsonAdapter.get_token', side_effect=ks_exc.ConnectFailure()) @mock.patch('keystoneauth1.discover.Discover', side_effect=ks_exc.ConnectFailure()) diff --git a/requirements.txt b/requirements.txt index 96b4ee4e7..8cea5ad4f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ # you find any incorrect lower bounds, let us know or propose a fix. pbr>=5.5.0 # Apache-2.0 PrettyTable>=0.7.2 # BSD -keystoneauth1>=5.0.0 # Apache-2.0 +keystoneauth1>=5.9.0 # Apache-2.0 oslo.i18n>=5.0.1 # Apache-2.0 oslo.utils>=4.8.0 # Apache-2.0 requests>=2.25.1 # Apache-2.0 From 3a5a0411f2007f86a1da5fd9131fac075238eea1 Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Thu, 24 Oct 2024 18:10:39 +0900 Subject: [PATCH 144/159] 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: I848996fa6d35d0dddb3f9001977637ec4a752eda --- releasenotes/notes/remove-py38-9ff5e159cfa29d23.yaml | 5 +++++ setup.cfg | 3 ++- tox.ini | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/remove-py38-9ff5e159cfa29d23.yaml diff --git a/releasenotes/notes/remove-py38-9ff5e159cfa29d23.yaml b/releasenotes/notes/remove-py38-9ff5e159cfa29d23.yaml new file mode 100644 index 000000000..040316360 --- /dev/null +++ b/releasenotes/notes/remove-py38-9ff5e159cfa29d23.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 559bc273c..800e6901b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -6,7 +6,7 @@ description_file = author = OpenStack author_email = openstack-discuss@lists.openstack.org home_page = https://docs.openstack.org/python-cinderclient/latest/ -python_requires = >=3.8 +python_requires = >=3.9 classifier = Development Status :: 5 - Production/Stable Environment :: Console @@ -21,6 +21,7 @@ classifier = Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 + Programming Language :: Python :: 3.12 [files] packages = diff --git a/tox.ini b/tox.ini index dab45cab6..82f6b98f8 100644 --- a/tox.ini +++ b/tox.ini @@ -107,7 +107,7 @@ setenv = # TLS (https) server certificate. passenv = OS_* -[testenv:functional-py{3,38,39,310,311,312}] +[testenv:functional-py{3,39,310,311,312}] deps = {[testenv:functional]deps} setenv = {[testenv:functional]setenv} passenv = {[testenv:functional]passenv} From 6efcf1c97d47e9cb9ae65b862e12759a00a54ecd Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Tue, 18 Mar 2025 09:04:54 +0000 Subject: [PATCH 145/159] 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: I92b2b5a8f6d65db77296bfe93b59d3a6c3d62b1b --- 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 b6c7f287b..a66bca7b0 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ :maxdepth: 1 unreleased + 2025.1 2024.2 2024.1 2023.2 From 7cda64277f49a9074c0edf46aadd46cd1a5e975f Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Mon, 28 Apr 2025 16:23:53 -0400 Subject: [PATCH 146/159] Update python versions for testing Given the 2025.2 (Flamingo) python runtimes [0], drop the functional-py39 and functional-py311 jobs and add functional-py310 and functional-py312 jobs. Also adjust tox.ini to drop the functional-py39 definition and add functional-py313. [0] https://governance.openstack.org/tc/reference/runtimes/2025.2.html Change-Id: Icd1a079cfab9367e52ac679fc86c3d055f4e9ee4 --- .zuul.yaml | 27 ++++++++++++++------------- tox.ini | 2 +- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index c56b8754d..dec90e3da 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -20,21 +20,22 @@ - ^cinderclient/tests/unit/.*$ - job: - name: python-cinderclient-functional-py39 + name: python-cinderclient-functional-py310 parent: python-cinderclient-functional-base - nodeset: devstack-single-node-centos-9-stream + # Python 3.10 is the default on Ubuntu 22.04 (Jammy) + nodeset: openstack-single-node-jammy vars: - python_version: 3.9 - tox_envlist: functional-py39 + python_version: 3.10 + tox_envlist: functional-py310 - job: - name: python-cinderclient-functional-py311 + name: python-cinderclient-functional-py312 parent: python-cinderclient-functional-base - # use debian bookworm, where py3.11 is the default - nodeset: devstack-single-node-debian-bookworm + # Python 3.12 is the default on Ubuntu 24.04 (Noble) + nodeset: openstack-single-node-noble vars: - python_version: 3.11 - tox_envlist: functional-py311 + python_version: 3.12 + tox_envlist: functional-py312 - project: vars: @@ -48,11 +49,11 @@ - release-notes-jobs-python3 check: jobs: - - python-cinderclient-functional-py39 - - python-cinderclient-functional-py311 + - python-cinderclient-functional-py310 + - python-cinderclient-functional-py312 - openstack-tox-pylint: voting: false gate: jobs: - - python-cinderclient-functional-py39 - - python-cinderclient-functional-py311 + - python-cinderclient-functional-py310 + - python-cinderclient-functional-py312 diff --git a/tox.ini b/tox.ini index 82f6b98f8..8e97545c7 100644 --- a/tox.ini +++ b/tox.ini @@ -107,7 +107,7 @@ setenv = # TLS (https) server certificate. passenv = OS_* -[testenv:functional-py{3,39,310,311,312}] +[testenv:functional-py{3,310,311,312,313}] deps = {[testenv:functional]deps} setenv = {[testenv:functional]setenv} passenv = {[testenv:functional]passenv} From 0125495f92ecd0248a204974a96b7403dc160fbe Mon Sep 17 00:00:00 2001 From: Ivan Anfimov Date: Tue, 29 Apr 2025 15:06:16 +0000 Subject: [PATCH 147/159] Remove tags from README The tags framework has been discontinued for a long time. https://governance.openstack.org/tc/reference/tags/ https://governance.openstack.org/tc/resolutions/20211224-tags-framework-removal.html Change-Id: I1ffae10bd034de52bb8287d8b0e3103e9e9835ae --- README.rst | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/README.rst b/README.rst index 63d67c7eb..2740e963e 100644 --- a/README.rst +++ b/README.rst @@ -1,12 +1,4 @@ -======================== -Team and repository tags -======================== - -.. image:: https://governance.openstack.org/tc/badges/python-cinderclient.svg - :target: https://governance.openstack.org/tc/reference/tags/index.html - -.. Change things from this point on - +=========================================== Python bindings to the OpenStack Cinder API =========================================== From 314bf711a29c436e0ff454153763f96f104572ab Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Thu, 26 Jun 2025 10:38:00 +0900 Subject: [PATCH 148/159] 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: I6767a26645b8139b60a0ca7960ca4ba391eecc09 --- releasenotes/notes/remove-py39-88ae5d7e3cf12f7d.yaml | 5 +++++ setup.cfg | 3 +-- 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/remove-py39-88ae5d7e3cf12f7d.yaml diff --git a/releasenotes/notes/remove-py39-88ae5d7e3cf12f7d.yaml b/releasenotes/notes/remove-py39-88ae5d7e3cf12f7d.yaml new file mode 100644 index 000000000..eaf3014b9 --- /dev/null +++ b/releasenotes/notes/remove-py39-88ae5d7e3cf12f7d.yaml @@ -0,0 +1,5 @@ +--- +upgrade: + - | + Support for Python 3.9 has been removed. Now Python 3.10 is the minimum + version supported. diff --git a/setup.cfg b/setup.cfg index 800e6901b..b8970d39f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -6,7 +6,7 @@ description_file = author = OpenStack author_email = openstack-discuss@lists.openstack.org home_page = https://docs.openstack.org/python-cinderclient/latest/ -python_requires = >=3.9 +python_requires = >=3.10 classifier = Development Status :: 5 - Production/Stable Environment :: Console @@ -18,7 +18,6 @@ classifier = Programming Language :: Python 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 59824da6c71053d1181833c084b67f30a61fcfe1 Mon Sep 17 00:00:00 2001 From: Johannes Kulik Date: Fri, 11 Jul 2025 10:54:20 +0200 Subject: [PATCH 149/159] Fix talking to older servers Cinderclient currently supports version 3.71 as defined in `cinderclient.api_versions.MAX_VERSION`. When doing version discovery, we create a temporary client with the MAX_VERSION and use this client to fetch the available versions. If the server doesn't support 3.71, yet, the version discovery request fails with: cinderclient.exceptions.NotAcceptable: Version 3.71 is not supported by the API. Minimum is 3.0 and maximum is 3.70. (HTTP 406) ERROR: Version 3.71 is not supported by the API. Minimum is 3.0 and maximum is 3.70. (HTTP 406) To fix this, we instead create a client with the MIN_VERSION, because the versions endpoint should be available there already. NOTE: Even when specifying an `--os-volume-api-version 3.70` the request fails, because version discovery still takes place in case we have to downgrade from the requested version. Change-Id: I38b71cea6b92da7f451e2a02d55900fe18e9aab0 Signed-off-by: Johannes Kulik Closes-Bug: #1998596 --- cinderclient/shell.py | 8 ++++---- releasenotes/notes/bug-1998596-5cac70cc68b3d6a5.yaml | 6 ++++++ 2 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/bug-1998596-5cac70cc68b3d6a5.yaml diff --git a/cinderclient/shell.py b/cinderclient/shell.py index b8aa28c4f..ae473839b 100644 --- a/cinderclient/shell.py +++ b/cinderclient/shell.py @@ -771,11 +771,11 @@ def main(self, argv): "using --os-volume-api-version option.") raise exc.UnsupportedVersion(msg) - API_MAX_VERSION = api_versions.APIVersion(api_versions.MAX_VERSION) + API_MIN_VERSION = api_versions.APIVersion(api_versions.MIN_VERSION) # FIXME: the endpoint_api_version[0] can ONLY be '3' now, so the # above line should probably be ripped out and this condition removed if endpoint_api_version[0] == '3': - disc_client = client.Client(API_MAX_VERSION, + disc_client = client.Client(API_MIN_VERSION, os_username, os_password, os_project_name, @@ -840,9 +840,9 @@ def _discover_client(self, if not os_service_type: os_service_type = self._discover_service_type(discovered_version) - API_MAX_VERSION = api_versions.APIVersion(api_versions.MAX_VERSION) + API_MIN_VERSION = api_versions.APIVersion(api_versions.MIN_VERSION) - if (discovered_version != API_MAX_VERSION or + if (discovered_version != API_MIN_VERSION or os_service_type != 'volume' or os_endpoint_type != DEFAULT_CINDER_ENDPOINT_TYPE): client_args['service_type'] = os_service_type diff --git a/releasenotes/notes/bug-1998596-5cac70cc68b3d6a5.yaml b/releasenotes/notes/bug-1998596-5cac70cc68b3d6a5.yaml new file mode 100644 index 000000000..781e11907 --- /dev/null +++ b/releasenotes/notes/bug-1998596-5cac70cc68b3d6a5.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + `Bug #1998596 `_: + fixed version discovery if the server was older than the maximum supported + version defined in python-cinderclient. From 1d8c7becaf2c1363afe843d2a5a86b1707a8e3ff Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Fri, 5 Sep 2025 12:20:07 +0000 Subject: [PATCH 150/159] 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: Ib3f514d8fc3a6ee1d553dae0aa2700a7a42bdfee 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 a66bca7b0..5f35d4467 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ :maxdepth: 1 unreleased + 2025.2 2025.1 2024.2 2024.1 From b32c8e2f652197155e120853ad016a3d84337068 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Fri, 31 Oct 2025 11:37:11 +0000 Subject: [PATCH 151/159] reno: Update master for unmaintained/2024.1 Update the 2024.1 release notes configuration to build from unmaintained/2024.1. Change-Id: I47b46a51807e308ad9fbeab8568bf8308fa8637d 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 08b0a68a57ee8467d765c5f41f2eac4915ad799f Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Sat, 21 Feb 2026 20:06:26 +0900 Subject: [PATCH 152/159] Remove unused Makefile for doc We've not been using this for long since we introduced the tox targets for doc lint and build. Also remove .gitignore in the subdirectory to maintain ignored files in a single place. Change-Id: Ib1779deb1b7fecbaf04766cae506ae5a25dafec4 Signed-off-by: Takashi Kajinami --- doc/.gitignore | 1 - doc/Makefile | 90 -------------------------------------------------- 2 files changed, 91 deletions(-) delete mode 100644 doc/.gitignore delete mode 100644 doc/Makefile diff --git a/doc/.gitignore b/doc/.gitignore deleted file mode 100644 index 567609b12..000000000 --- a/doc/.gitignore +++ /dev/null @@ -1 +0,0 @@ -build/ diff --git a/doc/Makefile b/doc/Makefile deleted file mode 100644 index f948ac903..000000000 --- a/doc/Makefile +++ /dev/null @@ -1,90 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -SPHINXSOURCE = source -PAPER = -BUILDDIR = build - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(SPHINXSOURCE) - -.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - -rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/python-cinderclient.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/python-cinderclient.qhc" - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ - "run these through (pdf)latex." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." From 650b5c5b8e08e7656a215048c0ee43117ccdcf2d Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Mon, 23 Feb 2026 18:33:37 -0500 Subject: [PATCH 153/159] Update CI for Gazpacho Add Python 3.13 functional tests, as per https://governance.openstack.org/tc/reference/runtimes/2026.1.html Given that our functional tests are devstack-based, the python 3.10 functional job is made non-voting because there is no supported py3.10 platform for devstack following change I796eddac96313584f4a. Change-Id: If75514f573ea16f7984919f4380ddd99f282a85a Signed-off-by: Brian Rosmaita --- .zuul.yaml | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index dec90e3da..fbc442f4a 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -2,6 +2,12 @@ name: python-cinderclient-functional-base abstract: true parent: devstack-tox-functional + description: | + Abstract job for defining devstack-based functional test jobs for + python-cinderclient. Jobs for particular python versions use this + job as a parent, and control what verison of python is used by + specifying a nodeset having the desired version of python as the + system default. timeout: 4500 required-projects: - openstack/cinder @@ -25,17 +31,23 @@ # Python 3.10 is the default on Ubuntu 22.04 (Jammy) nodeset: openstack-single-node-jammy vars: - python_version: 3.10 + devstack_localrc: + # current master devstack no longer supports jammy as a platform + # (change I796eddac96313584f4a), so we must force install it + FORCE: 'yes' + bindep_profile: test py310 + python_version: '3.10' tox_envlist: functional-py310 - job: - name: python-cinderclient-functional-py312 + name: python-cinderclient-functional-py313 parent: python-cinderclient-functional-base - # Python 3.12 is the default on Ubuntu 24.04 (Noble) - nodeset: openstack-single-node-noble + # Python 3.13 is the default on Debian Trixie + nodeset: devstack-single-node-debian-trixie vars: - python_version: 3.12 - tox_envlist: functional-py312 + bindep_profile: test py310 + python_version: '3.13' + tox_envlist: functional-py313 - project: vars: @@ -49,11 +61,13 @@ - release-notes-jobs-python3 check: jobs: - - python-cinderclient-functional-py310 - - python-cinderclient-functional-py312 + - python-cinderclient-functional-py310: + # non-voting because devstack support on python 3.10 + # is no longer tested (see change I796eddac96313584f4a) + voting: false + - python-cinderclient-functional-py313 - openstack-tox-pylint: voting: false gate: jobs: - - python-cinderclient-functional-py310 - - python-cinderclient-functional-py312 + - python-cinderclient-functional-py313 From 4a193c8aa473da08e591e28f389ef45653d95c79 Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Wed, 25 Feb 2026 15:32:56 -0500 Subject: [PATCH 154/159] Update classifiers for Gazpacho Gazpacho supports python 3.10 through 3.13 [0]. [0] https://governance.openstack.org/tc/reference/runtimes/2026.1.html Change-Id: Id7db3e34dd9e8c7cac3cd0f1b111426ef9fb231d Signed-off-by: Brian Rosmaita --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index b8970d39f..31026abc8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -21,6 +21,7 @@ classifier = Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 + Programming Language :: Python :: 3.13 [files] packages = From db819a6584850571d828ee141bc4c8aabc6c1de7 Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Wed, 25 Feb 2026 08:52:59 -0500 Subject: [PATCH 155/159] Add pyproject.toml file Based on cinder change I7f7437d06b411726ede946dd58750c04bc0abd3c Change-Id: Ic15385e36ecf92dc6dda6c5315be5ef0ec41455d Signed-off-by: Brian Rosmaita --- 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..5e862a959 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["pbr>=6.0.0", "setuptools>=64.0.0"] +build-backend = "pbr.build" From 437551758ef8805b772a2883e34fa4a7d0624094 Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Wed, 25 Feb 2026 17:05:46 -0500 Subject: [PATCH 156/159] Fix pylint job The old version of pylint we were using couldn't be installed because of conflicting requirements versions, so update it to the most recent pylint, 4.0.5, which supports python 3.14, since we will be supporting it too. Also update our lintstack scripts to use the modern name for do_exit, namely, 'exit'. Change-Id: I8d8bf8064aaea4e5de25634f0d86fb5e619b8503 Signed-off-by: Brian Rosmaita --- tools/lintstack.py | 2 +- tox.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/lintstack.py b/tools/lintstack.py index e6516c7e3..52251e875 100755 --- a/tools/lintstack.py +++ b/tools/lintstack.py @@ -153,7 +153,7 @@ def run_pylint(): args = [ "--msg-template='{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}'", "-E", "cinderclient"] - lint.Run(args, reporter=reporter, do_exit=False) + lint.Run(args, reporter=reporter, exit=False) val = buff.getvalue() buff.close() return val diff --git a/tox.ini b/tox.ini index 8e97545c7..ee03dcd22 100644 --- a/tox.ini +++ b/tox.ini @@ -40,7 +40,7 @@ commands = deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt - pylint==2.6.0 + pylint==4.0.5 commands = bash tools/lintstack.sh allowlist_externals = bash From b917248874a2274a290d96d30d03576b448c2bc6 Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Wed, 25 Feb 2026 21:59:39 -0500 Subject: [PATCH 157/159] Migrate setup configuration to pyproject.toml Also bump the minimum pbr. Change-Id: I228713f9444c956f6f6bf9eb4a5b7796e9f76e46 Signed-off-by: Brian Rosmaita --- pyproject.toml | 45 ++++++++++++++++++++++++++++++++++++++++++++- setup.cfg | 33 --------------------------------- 2 files changed, 44 insertions(+), 34 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5e862a959..fa180a17d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,46 @@ [build-system] -requires = ["pbr>=6.0.0", "setuptools>=64.0.0"] +requires = ["pbr>=7.0.0"] build-backend = "pbr.build" + +[project] +name = "python-cinderclient" +description = "OpenStack Block Storage API Client Library" +authors = [ + {name = "OpenStack", email = "openstack-discuss@lists.openstack.org"}, + ] +readme = {file = "README.rst", content-type = "text/x-rst"} +license = {text = "Apache-2.0"} +dynamic = ["version", "dependencies"] +requires-python = ">=3.10" +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: Console", + "Environment :: OpenStack", + "Intended Audience :: Information Technology", + "Intended Audience :: System Administrators", + "License :: OSI Approved :: Apache Software License", + "Operating System :: POSIX :: Linux", + "Programming Language :: Python", + "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", + ] + +[project.urls] +Homepage = "https://docs.openstack.org/python-cinderclient/latest/" +Repository = "https://opendev.org/openstack/python-cinderclient" +Bugs = "https://launchpad.net/python-cinderclient" + +[tool.setuptools] +packages = [ + "cinderclient", + ] + +[project.scripts] +cinder = "cinderclient.shell:main" + +[project.entry-points."keystoneauth1.plugin"] +noauth = "cinderclient.contrib.noauth:CinderNoAuthLoader" diff --git a/setup.cfg b/setup.cfg index 31026abc8..964093097 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,35 +1,2 @@ [metadata] name = python-cinderclient -summary = OpenStack Block Storage API Client Library -description_file = - README.rst -author = OpenStack -author_email = openstack-discuss@lists.openstack.org -home_page = https://docs.openstack.org/python-cinderclient/latest/ -python_requires = >=3.10 -classifier = - Development Status :: 5 - Production/Stable - Environment :: Console - Environment :: OpenStack - Intended Audience :: Information Technology - Intended Audience :: System Administrators - License :: OSI Approved :: Apache Software License - Operating System :: POSIX :: Linux - Programming Language :: Python - 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 = - cinderclient - -[entry_points] -console_scripts = - cinder = cinderclient.shell:main - -keystoneauth1.plugin = - noauth = cinderclient.contrib.noauth:CinderNoAuthLoader From 8dbb93ba0bc67bfb2a1edc41127a038a414b406d Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Tue, 3 Mar 2026 08:59:33 +0000 Subject: [PATCH 158/159] 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: I34d6e7577233dd1ea425c1ddf5aaf477c674d895 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 5f35d4467..fd726e48d 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ :maxdepth: 1 unreleased + 2026.1 2025.2 2025.1 2024.2 From 3100de0e712f9c17028ad3e09b63a04ea3d3ca15 Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Mon, 23 Mar 2026 20:11:55 -0400 Subject: [PATCH 159/159] Add periodic jobs for cinderclient This has become a low-traffic repository, and during gazpacho development the gates had broken but we didn't notice until people were trying to merge changes just before the client freeze. So add periodic weekly jobs so we will get some advance notice when a job has broken. Change-Id: Ie74e41b060b66a74b21778c20e8766c018303968 Signed-off-by: Brian Rosmaita --- .zuul.yaml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.zuul.yaml b/.zuul.yaml index fbc442f4a..1eca8230f 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -68,6 +68,24 @@ - python-cinderclient-functional-py313 - openstack-tox-pylint: voting: false + periodic-weekly: + jobs: + - openstack-tox-pep8: + branches: master + - openstack-tox-py310: + branches: master + - openstack-tox-py313: + branches: master + - openstack-tox-py314: + branches: master + - openstack-tox-docs: + branches: master + - build-openstack-releasenotes: + vars: + sphinx_python: python3 + branches: master + - python-cinderclient-functional-py313: + branches: master gate: jobs: - python-cinderclient-functional-py313