diff options
41 files changed, 618 insertions, 392 deletions
@@ -1,4 +1,4 @@ [gerrit] -host=review.openstack.org +host=review.opendev.org port=29418 project=openstack/python-cinderclient.git @@ -1,18 +1,16 @@ - job: - name: cinderclient-dsvm-functional - parent: legacy-dsvm-base - run: playbooks/legacy/cinderclient-dsvm-functional/run.yaml - post-run: playbooks/legacy/cinderclient-dsvm-functional/post.yaml - timeout: 4200 - voting: false + name: python-cinderclient-functional + parent: devstack + run: playbooks/python-cinderclient-functional.yaml + post-run: playbooks/post.yaml + timeout: 4500 required-projects: - - openstack-infra/devstack-gate - openstack/cinder - openstack/python-cinderclient - irrelevant-files: - - ^.*\.rst$ - - ^doc/.*$ - - ^releasenotes/.*$ + vars: + devstack_localrc: + USE_PYTHON3: true + VOLUME_BACKING_FILE_SIZE: 16G - project: templates: @@ -22,12 +20,11 @@ - openstack-cover-jobs - openstack-lower-constraints-jobs - openstack-python-jobs - - openstack-python36-jobs - - openstack-python37-jobs + - openstack-python3-train-jobs - publish-openstack-docs-pti - release-notes-jobs-python3 check: jobs: - - cinderclient-dsvm-functional + - python-cinderclient-functional - openstack-tox-pylint: voting: false @@ -23,21 +23,15 @@ command-line tool. You may also want to look at the `OpenStack API documentation`_. .. _OpenStack CLI Reference: https://docs.openstack.org/python-openstackclient/latest/cli/ -.. _OpenStack API documentation: https://developer.openstack.org/api-guide/quick-start/ +.. _OpenStack API documentation: https://docs.openstack.org/api-quick-start/ The project is hosted on `Launchpad`_, where bugs can be filed. The code is hosted on `OpenStack`_. Patches must be submitted using `Gerrit`_. -.. _OpenStack: https://git.openstack.org/cgit/openstack/python-cinderclient +.. _OpenStack: https://opendev.org/openstack/python-cinderclient .. _Launchpad: https://launchpad.net/python-cinderclient .. _Gerrit: https://docs.openstack.org/infra/manual/developers.html#development-workflow -This code is a fork of `Jacobian's python-cloudservers`__. If you need API support -for the Rackspace API solely or the BSD license, you should use that repository. -python-cinderclient is licensed under the Apache License like the rest of OpenStack. - -__ https://github.com/rackerlabs/python-cloudservers - * License: Apache License, Version 2.0 * `PyPi`_ - package installation * `Online Documentation`_ @@ -51,7 +45,7 @@ __ https://github.com/rackerlabs/python-cloudservers .. _Online Documentation: https://docs.openstack.org/python-cinderclient/latest/ .. _Blueprints: https://blueprints.launchpad.net/python-cinderclient .. _Bugs: https://bugs.launchpad.net/python-cinderclient -.. _Source: https://git.openstack.org/cgit/openstack/python-cinderclient +.. _Source: https://opendev.org/openstack/python-cinderclient .. _How to Contribute: https://docs.openstack.org/infra/manual/developers.html .. _Specs: https://specs.openstack.org/openstack/cinder-specs/ @@ -199,12 +193,6 @@ You'll find complete documentation on the shell by running readonly-mode-update Updates volume read-only access-mode flag. rename Renames a volume. - replication-promote - Promote a secondary volume to primary for a - relationship. - replication-reenable - Sync the secondary volume with primary for a - relationship. reset-state Explicitly updates the volume state in the Cinder database. retype Changes the volume type for a volume. diff --git a/cinderclient/api_versions.py b/cinderclient/api_versions.py index f9e20f6..54d2fce 100644 --- a/cinderclient/api_versions.py +++ b/cinderclient/api_versions.py @@ -29,7 +29,7 @@ LOG = logging.getLogger(__name__) # key is a deprecated version and value is an alternative version. DEPRECATED_VERSIONS = {"2": "3"} DEPRECATED_VERSION = "2.0" -MAX_VERSION = "3.52" +MAX_VERSION = "3.59" MIN_VERSION = "3.0" _SUBSTITUTIONS = {} @@ -160,6 +160,9 @@ class APIVersion(object): return "%s.%s" % (self.ver_major, "latest") return "%s.%s" % (self.ver_major, self.ver_minor) + def get_major_version(self): + return "%s" % self.ver_major + class VersionedMethod(object): @@ -275,19 +278,32 @@ def discover_version(client, requested_version): server_start_version, server_end_version = _get_server_version_range( client) - valid_version = requested_version 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}) - valid_version = APIVersion(DEPRECATED_VERSION) - else: - valid_version = _validate_requested_version( - requested_version, - server_start_version, - server_end_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 + # requested version + valid_version = _validate_requested_version( + requested_version, + server_start_version, + server_end_version) + + # see if we need to downgrade for the client + client_max = APIVersion(MAX_VERSION) + if client_max < valid_version: + msg = _("Requested version %(requested_version)s is " + "not supported. Downgrading requested version " + "to %(actual_version)s.") + LOG.debug(msg, { + "requested_version": requested_version, + "actual_version": client_max}) + valid_version = client_max - _validate_server_version(server_start_version, server_end_version) return valid_version diff --git a/cinderclient/base.py b/cinderclient/base.py index da83573..e84eb2f 100644 --- a/cinderclient/base.py +++ b/cinderclient/base.py @@ -91,12 +91,8 @@ class Manager(common_base.HookableMixin): except KeyError: pass - # FIXME(eharney): This is probably a bug - we should only call - # completion_cache for the shell, not here. - with self.completion_cache('human_id', obj_class, mode="w"): - with self.completion_cache('uuid', obj_class, mode="w"): - items_new = [obj_class(self, res, loaded=True) - for res in data if res] + items_new = [obj_class(self, res, loaded=True) + for res in data if res] if limit: limit = int(limit) margin = limit - len(items) @@ -269,7 +265,7 @@ class Manager(common_base.HookableMixin): # pair username = utils.env('OS_USERNAME', 'CINDER_USERNAME') url = utils.env('OS_URL', 'CINDER_URL') - uniqifier = hashlib.sha1(username.encode('utf-8') + + uniqifier = hashlib.sha1(username.encode('utf-8') + # nosec url.encode('utf-8')).hexdigest() cache_dir = os.path.expanduser(os.path.join(base_dir, uniqifier)) @@ -330,9 +326,7 @@ class Manager(common_base.HookableMixin): if return_raw: return common_base.DictWithMeta(body[response_key], resp) - with self.completion_cache('human_id', self.resource_class, mode="a"): - with self.completion_cache('uuid', self.resource_class, mode="a"): - return self.resource_class(self, body[response_key], resp=resp) + return self.resource_class(self, body[response_key], resp=resp) def _delete(self, url): resp, body = self.api.client.delete(url) diff --git a/cinderclient/client.py b/cinderclient/client.py index 687533e..57dc523 100644 --- a/cinderclient/client.py +++ b/cinderclient/client.py @@ -70,10 +70,14 @@ for svc in ('volume', 'volumev2', 'volumev3'): discover.add_catalog_discover_hack(svc, re.compile(r'/v[12]/\w+/?$'), '/') -def get_server_version(url): +def get_server_version(url, insecure=False, cacert=None): """Queries the server via the naked endpoint and gets version info. :param url: url of the cinder endpoint + :param insecure: Explicitly allow client to perform "insecure" TLS + (https) requests + :param cacert: Specify a CA bundle file to use in verifying a TLS + (https) server certificate :returns: APIVersion object for min and max version supported by the server """ @@ -104,7 +108,14 @@ def get_server_version(url): # leave as is without cropping. version_url = url - response = requests.get(version_url) + if insecure: + verify_cert = False + else: + if cacert: + verify_cert = cacert + else: + verify_cert = True + response = requests.get(version_url, verify=verify_cert) data = json.loads(response.text) versions = data['versions'] for version in versions: @@ -124,9 +135,9 @@ def get_server_version(url): api_versions.APIVersion(current_version)) -def get_highest_client_server_version(url): +def get_highest_client_server_version(url, insecure=False, cacert=None): """Returns highest supported version by client and server as a string.""" - min_server, max_server = get_server_version(url) + min_server, max_server = get_server_version(url, insecure, cacert) max_client = api_versions.APIVersion(api_versions.MAX_VERSION) return min(max_server, max_client).get_string() diff --git a/cinderclient/shell.py b/cinderclient/shell.py index ecc5862..75e42e9 100644 --- a/cinderclient/shell.py +++ b/cinderclient/shell.py @@ -516,6 +516,21 @@ class OpenStackCinderShell(object): else: return argv + @staticmethod + def _validate_input_api_version(options): + if not options.os_volume_api_version: + api_version = api_versions.APIVersion(api_versions.MAX_VERSION) + else: + api_version = api_versions.get_api_version( + options.os_volume_api_version) + return api_version + + @staticmethod + def downgrade_warning(requested, discovered): + logger.warning("API version %s requested, " % requested.get_string()) + logger.warning("downgrading to %s based on server support." % + discovered.get_string()) + def main(self, argv): # Parse args once to find version and debug settings parser = self.get_base_parser() @@ -527,14 +542,7 @@ class OpenStackCinderShell(object): do_help = ('help' in argv) or ( '--help' in argv) or ('-h' in argv) or not argv - if not options.os_volume_api_version: - use_version = DEFAULT_MAJOR_OS_VOLUME_API_VERSION - if do_help: - use_version = api_versions.MAX_VERSION - api_version = api_versions.get_api_version(use_version) - else: - api_version = api_versions.get_api_version( - options.os_volume_api_version) + api_version = self._validate_input_api_version(options) # build available subcommands based on version major_version_string = "%s" % api_version.ver_major @@ -670,9 +678,7 @@ class OpenStackCinderShell(object): insecure = self.options.insecure - self.cs = client.Client( - api_version, os_username, - os_password, os_project_name, os_auth_url, + client_args = dict( region_name=os_region_name, tenant_id=os_project_id, endpoint_type=endpoint_type, @@ -689,6 +695,11 @@ class OpenStackCinderShell(object): session=auth_session, logger=self.ks_logger if auth_session else self.client_logger) + self.cs = client.Client( + api_version, os_username, + os_password, os_project_name, os_auth_url, + **client_args) + try: if not utils.isunauthenticated(args.func): self.cs.authenticate() @@ -718,6 +729,28 @@ class OpenStackCinderShell(object): "to the default API version: %s", endpoint_api_version) + API_MAX_VERSION = api_versions.APIVersion(api_versions.MAX_VERSION) + if endpoint_api_version[0] == '3': + disc_client = client.Client(API_MAX_VERSION, + os_username, + os_password, + os_project_name, + os_auth_url, + **client_args) + self.cs, discovered_version = self._discover_client( + disc_client, + api_version, + args.os_endpoint_type, + args.service_type, + os_username, + os_password, + os_project_name, + os_auth_url, + client_args) + + if discovered_version < api_version: + self.downgrade_warning(api_version, discovered_version) + profile = osprofiler_profiler and options.profile if profile: osprofiler_profiler.init(options.profile) @@ -731,6 +764,56 @@ class OpenStackCinderShell(object): print("To display trace use next command:\n" "osprofiler trace show --html %s " % trace_id) + def _discover_client(self, + current_client, + os_api_version, + os_endpoint_type, + os_service_type, + os_username, + os_password, + os_project_name, + 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) + + if not os_endpoint_type: + os_endpoint_type = DEFAULT_CINDER_ENDPOINT_TYPE + + if not os_service_type: + os_service_type = self._discover_service_type(discovered_version) + + API_MAX_VERSION = api_versions.APIVersion(api_versions.MAX_VERSION) + + if (discovered_version != API_MAX_VERSION or + os_service_type != 'volume' or + os_endpoint_type != DEFAULT_CINDER_ENDPOINT_TYPE): + client_args['service_type'] = os_service_type + client_args['endpoint_type'] = os_endpoint_type + + return (client.Client(discovered_version, + os_username, + os_password, + os_project_name, + os_auth_url, + **client_args), + discovered_version) + else: + return current_client, discovered_version + + def _discover_service_type(self, discovered_version): + SERVICE_TYPES = {'1': 'volume', '2': 'volumev2', '3': 'volumev3'} + major_version = discovered_version.get_major_version() + service_type = SERVICE_TYPES[major_version] + return service_type + def _run_extension_hooks(self, hook_type, *args, **kwargs): """Runs hooks for all registered extensions.""" for extension in self.extensions: diff --git a/cinderclient/shell_utils.py b/cinderclient/shell_utils.py index 451552e..411dd17 100644 --- a/cinderclient/shell_utils.py +++ b/cinderclient/shell_utils.py @@ -26,12 +26,13 @@ _quota_resources = ['volumes', 'snapshots', 'gigabytes', _quota_infos = ['Type', 'In_use', 'Reserved', 'Limit', 'Allocated'] -def print_volume_image(image): - if 'volume_type' in image[1]['os-volume_upload_image']: - volume_type_name = ( - image[1]['os-volume_upload_image']['volume_type']['name']) - image[1]['os-volume_upload_image']['volume_type'] = volume_type_name - utils.print_dict(image[1]['os-volume_upload_image']) +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']) def poll_for_status(poll_fn, obj_id, action, final_ok_states, @@ -246,6 +247,9 @@ def quota_update(manager, identifier, args): if not skip_validation: updates['skip_validation'] = skip_validation quota_show(manager.update(identifier, **updates)) + else: + msg = 'Must supply at least one quota field to update.' + raise exceptions.ClientException(code=1, message=msg) def find_volume_type(cs, vtype): diff --git a/cinderclient/tests/functional/hooks/post_test_hook.sh b/cinderclient/tests/functional/hooks/post_test_hook.sh deleted file mode 100755 index 91d04d6..0000000 --- a/cinderclient/tests/functional/hooks/post_test_hook.sh +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/bash -xe - -# 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. - -# This script is executed inside post_test_hook function in devstack gate. - -# Default gate uses /opt/stack/new... but some of us may install differently -STACK_DIR=$BASE/new/devstack - -function generate_testr_results { - if [ -f .testrepository/0 ]; then - sudo .tox/functional/bin/testr last --subunit > $WORKSPACE/testrepository.subunit - sudo mv $WORKSPACE/testrepository.subunit $BASE/logs/testrepository.subunit - sudo /usr/os-testr-env/bin/subunit2html $BASE/logs/testrepository.subunit $BASE/logs/testr_results.html - sudo gzip -9 $BASE/logs/testrepository.subunit - sudo gzip -9 $BASE/logs/testr_results.html - sudo chown $USER:$USER $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz - sudo chmod a+r $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz - fi -} - -export CINDERCLIENT_DIR="$BASE/new/python-cinderclient" - -sudo chown -R $USER:stack $CINDERCLIENT_DIR - -# Get admin credentials -cd $STACK_DIR -source openrc admin admin - -# Go to the cinderclient dir -cd $CINDERCLIENT_DIR - -# Run tests -echo "Running cinderclient functional test suite" -set +e -# Preserve env for OS_ credentials -sudo -E -H -u $USER tox -efunctional -EXIT_CODE=$? -set -e - -# Collect and parse result -generate_testr_results -exit $EXIT_CODE diff --git a/cinderclient/tests/unit/test_api_versions.py b/cinderclient/tests/unit/test_api_versions.py index 559d814..02e4450 100644 --- a/cinderclient/tests/unit/test_api_versions.py +++ b/cinderclient/tests/unit/test_api_versions.py @@ -216,7 +216,12 @@ class DiscoverVersionTestCase(utils.TestCase): ("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 - ("3.5", "3.8", "3.0", "3.7", "3.8", False, "3.7"), # downgraded + # downgraded because of server: + ("3.5", "3.8", "3.0", "3.7", "3.8", False, "3.7"), + # downgraded because of client: + ("3.5", "3.8", "3.0", "3.9", "3.9", False, "3.8"), + # 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.1", "3.11", "3.4", "3.7", "3.7", False), # Requested in range diff --git a/cinderclient/tests/unit/test_client.py b/cinderclient/tests/unit/test_client.py index d83768f..6556362 100644 --- a/cinderclient/tests/unit/test_client.py +++ b/cinderclient/tests/unit/test_client.py @@ -353,6 +353,41 @@ class GetAPIVersionTestCase(utils.TestCase): self.assertEqual(max_version, api_versions.APIVersion('3.16')) @mock.patch('cinderclient.client.requests.get') + def test_get_server_version_insecure(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/" + + cinderclient.client.get_server_version(url, True) + + mock_request.assert_called_once_with(expected_url, verify=False) + + @mock.patch('cinderclient.client.requests.get') + def test_get_server_version_cacert(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/" + + cacert = '/path/to/cert' + cinderclient.client.get_server_version(url, cacert=cacert) + + mock_request.assert_called_once_with(expected_url, verify=cacert) + + @mock.patch('cinderclient.client.requests.get') @ddt.data('3.12', '3.40') def test_get_highest_client_server_version(self, version, mock_request): diff --git a/cinderclient/tests/unit/test_shell.py b/cinderclient/tests/unit/test_shell.py index b94b8b9..7050892 100644 --- a/cinderclient/tests/unit/test_shell.py +++ b/cinderclient/tests/unit/test_shell.py @@ -513,3 +513,31 @@ class TestLoadVersionedActions(utils.TestCase): mock_add_arg.call_args_list) self.assertIn(mock.call('--foo', help="second foo"), mock_add_arg.call_args_list) + + +class ShellUtilsTest(utils.TestCase): + + @mock.patch.object(cinderclient.utils, 'print_dict') + def test_print_volume_image(self, mock_print_dict): + response = {'os-volume_upload_image': {'name': 'myimg1'}} + image_resp_tuple = (202, response) + cinderclient.shell_utils.print_volume_image(image_resp_tuple) + + response = {'os-volume_upload_image': + {'name': 'myimg2', + 'volume_type': None}} + image_resp_tuple = (202, response) + cinderclient.shell_utils.print_volume_image(image_resp_tuple) + + response = {'os-volume_upload_image': + {'name': 'myimg3', + 'volume_type': {'id': '1234', 'name': 'sometype'}}} + image_resp_tuple = (202, response) + cinderclient.shell_utils.print_volume_image(image_resp_tuple) + + mock_print_dict.assert_has_calls( + (mock.call({'name': 'myimg1'}), + mock.call({'name': 'myimg2', + 'volume_type': None}), + mock.call({'name': 'myimg3', + 'volume_type': 'sometype'}))) diff --git a/cinderclient/tests/unit/v2/fakes.py b/cinderclient/tests/unit/v2/fakes.py index 18aa99f..bd8bb34 100644 --- a/cinderclient/tests/unit/v2/fakes.py +++ b/cinderclient/tests/unit/v2/fakes.py @@ -531,10 +531,6 @@ class FakeHTTPClient(base_client.HTTPClient): assert list(body[action]) == ['bootable'] elif action == 'os-unmanage': assert body[action] is None - elif action == 'os-promote-replica': - assert body[action] is None - elif action == 'os-reenable-replica': - assert body[action] is None elif action == 'os-set_image_metadata': assert list(body[action]) == ['metadata'] elif action == 'os-unset_image_metadata': @@ -1241,12 +1237,6 @@ class FakeHTTPClient(base_client.HTTPClient): snapshot.update(kw['body']['snapshot']) return (202, {}, {'snapshot': snapshot}) - def post_os_promote_replica_1234(self, **kw): - return (202, {}, {}) - - def post_os_reenable_replica_1234(self, **kw): - return (202, {}, {}) - def get_scheduler_stats_get_pools(self, **kw): stats = [ { diff --git a/cinderclient/tests/unit/v2/test_shell.py b/cinderclient/tests/unit/v2/test_shell.py index 50aad0e..1bc562b 100644 --- a/cinderclient/tests/unit/v2/test_shell.py +++ b/cinderclient/tests/unit/v2/test_shell.py @@ -1131,16 +1131,6 @@ class ShellTest(utils.TestCase): self.assert_called('POST', '/volumes/1234/action', body={'os-unmanage': None}) - def test_replication_promote(self): - self.run_command('replication-promote 1234') - self.assert_called('POST', '/volumes/1234/action', - body={'os-promote-replica': None}) - - def test_replication_reenable(self): - self.run_command('replication-reenable 1234') - self.assert_called('POST', '/volumes/1234/action', - body={'os-reenable-replica': None}) - def test_create_snapshot_from_volume_with_metadata(self): """ Tests create snapshot with --metadata parameter. diff --git a/cinderclient/tests/unit/v2/test_volumes.py b/cinderclient/tests/unit/v2/test_volumes.py index 78d67ea..5240507 100644 --- a/cinderclient/tests/unit/v2/test_volumes.py +++ b/cinderclient/tests/unit/v2/test_volumes.py @@ -300,22 +300,6 @@ class VolumesTest(utils.TestCase): cs.volume_snapshots.list_manageable('host1', detailed=True) cs.assert_called('GET', '/os-snapshot-manage/detail?host=host1') - def test_replication_promote(self): - v = cs.volumes.get('1234') - self._assert_request_id(v) - vol = cs.volumes.promote(v) - cs.assert_called('POST', '/volumes/1234/action', - {'os-promote-replica': None}) - self._assert_request_id(vol) - - def test_replication_reenable(self): - v = cs.volumes.get('1234') - self._assert_request_id(v) - vol = cs.volumes.reenable(v) - cs.assert_called('POST', '/volumes/1234/action', - {'os-reenable-replica': None}) - self._assert_request_id(vol) - def test_get_pools(self): vol = cs.volumes.get_pools('') cs.assert_called('GET', '/scheduler-stats/get_pools') diff --git a/cinderclient/tests/unit/v3/fakes.py b/cinderclient/tests/unit/v3/fakes.py index 928adb1..3fb6a36 100644 --- a/cinderclient/tests/unit/v3/fakes.py +++ b/cinderclient/tests/unit/v3/fakes.py @@ -30,6 +30,18 @@ fake_attachment = {'attachment': { 'instance': 'e84fda45-4de4-4ce4-8f39-fc9d3b0aa05e', 'volume_id': '557ad76c-ce54-40a3-9e91-c40d21665cc3', }} +fake_attachment_list = {'attachments': [ + {'instance': 'instance_1', + 'name': 'attachment-1', + 'volume_id': 'fake_volume_1', + 'status': 'reserved', + 'id': 'attachmentid_1'}, + {'instance': 'instance_2', + 'name': 'attachment-2', + 'volume_id': 'fake_volume_2', + 'status': 'reserverd', + 'id': 'attachmentid_2'}]} + fake_connection_info = { 'auth_password': 'i6h9E5HQqSkcGX3H', 'attachment_id': 'a232e9ae', @@ -289,15 +301,7 @@ class FakeHTTPClient(fake_v2.FakeHTTPClient): return (200, {}, fake_attachment) def get_attachments(self, **kw): - return (200, {}, { - 'attachments': [{'instance': 1, - 'name': 'attachment-1', - 'volume_id': 'fake_volume_1', - 'status': 'reserved'}, - {'instance': 2, - 'name': 'attachment-2', - 'volume_id': 'fake_volume_2', - 'status': 'reserverd'}]}) + return (200, {}, fake_attachment_list) def post_attachments_a232e9ae_action(self, **kw): # noqa: E501 attached_fake = fake_attachment diff --git a/cinderclient/tests/unit/v3/test_shell.py b/cinderclient/tests/unit/v3/test_shell.py index b145d8e..f4aa598 100644 --- a/cinderclient/tests/unit/v3/test_shell.py +++ b/cinderclient/tests/unit/v3/test_shell.py @@ -46,11 +46,13 @@ import six from six.moves.urllib import parse import cinderclient +from cinderclient import api_versions from cinderclient import base from cinderclient import client from cinderclient import exceptions from cinderclient import shell from cinderclient import utils as cinderclient_utils +from cinderclient.v3 import attachments from cinderclient.v3 import volume_snapshots from cinderclient.v3 import volumes @@ -91,7 +93,12 @@ class ShellTest(utils.TestCase): self.cs = mock.Mock() def run_command(self, cmd): - self.shell.main(cmd.split()) + # Ensure the version negotiation indicates that + # all versions are supported + with mock.patch('cinderclient.api_versions._get_server_version_range', + return_value=(api_versions.APIVersion('3.0'), + api_versions.APIVersion('3.99'))): + self.shell.main(cmd.split()) def assert_called(self, method, url, body=None, partial_body=None, **kwargs): @@ -265,7 +272,17 @@ class ShellTest(utils.TestCase): {six.text_type('key'): six.text_type('value')}})) self.assert_call_contained(parse.urlencode({'is_public': None})) - def test_type_list_no_filters(self): + def test_type_list_public(self): + self.run_command('--os-volume-api-version 3.52 type-list ' + '--filters is_public=True') + self.assert_called('GET', '/types?is_public=True') + + def test_type_list_private(self): + self.run_command('--os-volume-api-version 3.52 type-list ' + '--filters is_public=False') + self.assert_called('GET', '/types?is_public=False') + + def test_type_list_public_private(self): self.run_command('--os-volume-api-version 3.52 type-list') self.assert_called('GET', '/types?is_public=None') @@ -284,6 +301,14 @@ class ShellTest(utils.TestCase): mock_print.assert_called_once_with(mock.ANY, key_list, exclude_unavailable=True, sortby_index=0) + @mock.patch("cinderclient.shell.OpenStackCinderShell.downgrade_warning") + def test_list_version_downgrade(self, mock_warning): + self.run_command('--os-volume-api-version 3.998 list') + mock_warning.assert_called_once_with( + api_versions.APIVersion('3.998'), + api_versions.APIVersion(api_versions.MAX_VERSION) + ) + def test_list_availability_zone(self): self.run_command('availability-zone-list') self.assert_called('GET', '/os-availability-zone') @@ -404,6 +429,21 @@ class ShellTest(utils.TestCase): self.run_command(command) self.assert_called('GET', '/attachments%s' % expected) + @mock.patch('cinderclient.utils.print_list') + @mock.patch.object(cinderclient.v3.attachments.VolumeAttachmentManager, + 'list') + def test_attachment_list_setattr(self, mock_list, mock_print): + command = '--os-volume-api-version 3.27 attachment-list ' + fake_attachment = [attachments.VolumeAttachment(mock.ANY, attachment) + for attachment in fakes.fake_attachment_list['attachments']] + mock_list.return_value = fake_attachment + self.run_command(command) + for attach in fake_attachment: + setattr(attach, 'server_id', getattr(attach, 'instance')) + columns = ['ID', 'Volume ID', 'Status', 'Server ID'] + mock_print.assert_called_once_with(fake_attachment, columns, + sortby_index=0) + def test_revert_to_snapshot(self): original = cinderclient_utils.find_resource @@ -555,6 +595,20 @@ class ShellTest(utils.TestCase): self.run_command('--os-volume-api-version 3.11 group-type-list') self.assert_called_anytime('GET', '/group_types?is_public=None') + def test_group_type_list_public(self): + self.run_command('--os-volume-api-version 3.52 group-type-list ' + '--filters is_public=True') + self.assert_called('GET', '/group_types?is_public=True') + + def test_group_type_list_private(self): + self.run_command('--os-volume-api-version 3.52 group-type-list ' + '--filters is_public=False') + self.assert_called('GET', '/group_types?is_public=False') + + def test_group_type_list_public_private(self): + self.run_command('--os-volume-api-version 3.52 group-type-list') + self.assert_called('GET', '/group_types?is_public=None') + def test_group_type_show(self): self.run_command('--os-volume-api-version 3.11 ' 'group-type-show 1') @@ -1351,3 +1405,24 @@ class ShellTest(utils.TestCase): 'no_snapshots': True }} self.assert_called('POST', '/volume-transfers', body=expected) + + def test_list_transfer_sort_key(self): + self.run_command( + '--os-volume-api-version 3.59 transfer-list --sort=id') + url = ('/volume-transfers/detail?%s' % + parse.urlencode([('sort_key', 'id')])) + self.assert_called('GET', url) + + def test_list_transfer_sort_key_dir(self): + self.run_command( + '--os-volume-api-version 3.59 transfer-list --sort=id:asc') + url = ('/volume-transfers/detail?%s' % + parse.urlencode([('sort_dir', 'asc'), + ('sort_key', 'id')])) + self.assert_called('GET', url) + + def test_list_transfer_sorty_not_sorty(self): + self.run_command( + '--os-volume-api-version 3.59 transfer-list') + url = ('/volume-transfers/detail') + self.assert_called('GET', url) diff --git a/cinderclient/v2/shell.py b/cinderclient/v2/shell.py index cfc9631..fb87a4f 100644 --- a/cinderclient/v2/shell.py +++ b/cinderclient/v2/shell.py @@ -2042,25 +2042,6 @@ def do_unmanage(cs, args): cs.volumes.unmanage(volume.id) -@utils.arg('volume', metavar='<volume>', - help='Name or ID of the volume to promote. ' - 'The volume should have the replica volume created with ' - 'source-replica argument.') -def do_replication_promote(cs, args): - """Promote a secondary volume to primary for a relationship.""" - volume = utils.find_volume(cs, args.volume) - cs.volumes.promote(volume.id) - - -@utils.arg('volume', metavar='<volume>', - help='Name or ID of the volume to reenable replication. ' - 'The replication-status of the volume should be inactive.') -def do_replication_reenable(cs, args): - """Sync the secondary volume with primary for a relationship.""" - volume = utils.find_volume(cs, args.volume) - cs.volumes.reenable(volume.id) - - @utils.arg('--all-tenants', dest='all_tenants', metavar='<0|1>', diff --git a/cinderclient/v2/volumes.py b/cinderclient/v2/volumes.py index 18a6e25..6500fe8 100644 --- a/cinderclient/v2/volumes.py +++ b/cinderclient/v2/volumes.py @@ -219,14 +219,6 @@ class Volume(base.Resource): """Unmanage a volume.""" return self.manager.unmanage(volume) - def promote(self, volume): - """Promote secondary to be primary in relationship.""" - return self.manager.promote(volume) - - def reenable(self, volume): - """Sync the secondary volume with primary for a relationship.""" - return self.manager.reenable(volume) - def get_pools(self, detail): """Show pool information for backends.""" return self.manager.get_pools(detail) @@ -352,6 +344,8 @@ class VolumeManager(base.ManagerWithFind): def _action(self, action, volume, info=None, **kwargs): """Perform a volume "action." + + :returns: tuple (response, body) """ body = {action: info} self.run_hooks('modify_body_for_action', body, **kwargs) @@ -637,14 +631,6 @@ class VolumeManager(base.ManagerWithFind): """Unmanage a volume.""" return self._action('os-unmanage', volume, None) - def promote(self, volume): - """Promote secondary to be primary in relationship.""" - return self._action('os-promote-replica', volume, None) - - def reenable(self, volume): - """Sync the secondary volume with primary for a relationship.""" - return self._action('os-reenable-replica', volume, None) - def get_pools(self, detail): """Show pool information for backends.""" query_string = "" diff --git a/cinderclient/v3/group_types.py b/cinderclient/v3/group_types.py index 636c192..74ea9b7 100644 --- a/cinderclient/v3/group_types.py +++ b/cinderclient/v3/group_types.py @@ -16,6 +16,8 @@ """Group Type interface.""" +from six.moves.urllib import parse + from cinderclient import api_versions from cinderclient import base @@ -84,9 +86,14 @@ class GroupTypeManager(base.ManagerWithFind): :rtype: list of :class:`GroupType`. """ + if not search_opts: + search_opts = dict() + query_string = '' - if not is_public: - query_string = '?is_public=%s' % is_public + if 'is_public' not in search_opts: + search_opts['is_public'] = is_public + + query_string = "?%s" % parse.urlencode(search_opts) return self._list("/group_types%s" % (query_string), "group_types") @api_versions.wraps("3.11") diff --git a/cinderclient/v3/shell.py b/cinderclient/v3/shell.py index bc79860..436c80c 100644 --- a/cinderclient/v3/shell.py +++ b/cinderclient/v3/shell.py @@ -181,6 +181,9 @@ def do_backup_list(cs, args): shell_utils.translate_volume_snapshot_keys(backups) columns = ['ID', 'Volume ID', 'Status', 'Name', 'Size', 'Object Count', 'Container'] + if cs.api_version >= api_versions.APIVersion('3.56'): + columns.append('User ID') + if args.sort: sortby_index = None else: @@ -750,9 +753,20 @@ def do_summary(cs, args): @api_versions.wraps('3.11') +@utils.arg('--filters', + type=six.text_type, + nargs='*', + start_version='3.52', + metavar='<key=value>', + default=None, + help="Filter key and value pairs. Admin only.") def do_group_type_list(cs, args): """Lists available 'group types'. (Admin only will see private types)""" - gtypes = cs.group_types.list() + search_opts = {} + # Update search option with `filters` + if hasattr(args, 'filters') and args.filters is not None: + search_opts.update(shell_utils.extract_filters(args.filters)) + gtypes = cs.group_types.list(search_opts=search_opts) shell_utils.print_group_type_list(gtypes) @@ -961,19 +975,20 @@ def do_upload_to_image(cs, args): """Uploads volume to Image Service as an image.""" volume = utils.find_volume(cs, args.volume) if cs.api_version >= api_versions.APIVersion("3.1"): - shell_utils.print_volume_image( - volume.upload_to_image(args.force, - args.image_name, - args.container_format, - args.disk_format, - args.visibility, - args.protected)) + (resp, body) = volume.upload_to_image(args.force, + args.image_name, + args.container_format, + args.disk_format, + args.visibility, + args.protected) + + shell_utils.print_volume_image((resp, body)) else: - shell_utils.print_volume_image( - volume.upload_to_image(args.force, - args.image_name, - args.container_format, - args.disk_format)) + (resp, body) = volume.upload_to_image(args.force, + args.image_name, + args.container_format, + args.disk_format) + shell_utils.print_volume_image((resp, body)) @utils.arg('volume', metavar='<volume>', help='ID of volume to migrate.') @@ -1348,6 +1363,20 @@ def do_group_list(cs, args): columns = ['ID', 'Status', 'Name'] utils.print_list(groups, columns) + with cs.groups.completion_cache( + 'uuid', + cinderclient.v3.groups.Group, + mode='w'): + for group in groups: + cs.groups.write_to_completion_cache('uuid', group.id) + with cs.groups.completion_cache('name', + cinderclient.v3.groups.Group, + mode='w'): + for group in groups: + if group.name is None: + continue + cs.groups.write_to_completion_cache('name', group.name) + @api_versions.wraps('3.13') @utils.arg('--list-volume', @@ -1411,6 +1440,17 @@ def do_group_create(cs, args): info.pop('links', None) utils.print_dict(info) + with cs.groups.completion_cache('uuid', + cinderclient.v3.groups.Group, + mode='a'): + cs.groups.write_to_completion_cache('uuid', group.id) + + if group.name is not None: + with cs.groups.completion_cache('name', + cinderclient.v3.groups.Group, + mode='a'): + cs.groups.write_to_completion_cache('name', group.name) + @api_versions.wraps('3.14') @utils.arg('--group-snapshot', @@ -2151,6 +2191,8 @@ def do_attachment_list(cs, args): marker=args.marker, limit=args.limit, sort=args.sort) + for attachment in attachments: + setattr(attachment, 'server_id', getattr(attachment, 'instance', None)) columns = ['ID', 'Volume ID', 'Status', 'Server ID'] if args.sort: sortby_index = None @@ -2500,3 +2542,53 @@ def do_transfer_create(cs, args): info.pop('links', None) utils.print_dict(info) + + +@utils.arg('--all-tenants', + dest='all_tenants', + metavar='<0|1>', + nargs='?', + type=int, + const=1, + default=0, + help='Shows details for all tenants. Admin only.') +@utils.arg('--all_tenants', + nargs='?', + type=int, + const=1, + help=argparse.SUPPRESS) +@utils.arg('--sort', + metavar='<key>[:<direction>]', + default=None, + help='Sort keys and directions in the form of <key>[:<asc|desc>].', + start_version='3.59') +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, + } + + sort = getattr(args, 'sort', None) + sort_key = None + sort_dir = None + if sort: + # We added this feature with sort_key and sort_dir, but that was a + # mistake as we've deprecated that construct a long time ago and should + # be removing it in favor of --sort. Too late for the service side, but + # to make the client experience consistent, we handle the compatibility + # here. + sort_args = sort.split(':') + if len(sort_args) > 2: + raise exceptions.CommandError( + 'Invalid sort parameter provided. Argument must be in the ' + 'form "key[:<asc|desc>]".') + + sort_key = sort_args[0] + if len(sort_args) == 2: + sort_dir = sort_args[1] + + transfers = cs.transfers.list( + search_opts=search_opts, sort_key=sort_key, sort_dir=sort_dir) + columns = ['ID', 'Volume ID', 'Name'] + utils.print_list(transfers, columns) diff --git a/cinderclient/v3/volume_transfers.py b/cinderclient/v3/volume_transfers.py index 39e1a2e..fe790f2 100644 --- a/cinderclient/v3/volume_transfers.py +++ b/cinderclient/v3/volume_transfers.py @@ -16,7 +16,6 @@ """Volume transfer interface (v3 extension).""" from cinderclient import base -from cinderclient import utils from cinderclient.v2 import volume_transfers @@ -63,25 +62,24 @@ class VolumeTransferManager(volume_transfers.VolumeTransferManager): return self._get("/os-volume-transfer/%s" % transfer_id, "transfer") - def list(self, detailed=True, search_opts=None): + def list(self, detailed=True, search_opts=None, sort_key=None, + sort_dir=None): """Get a list of all volume transfer. :param detailed: Get detailed object information. :param search_opts: Filtering options. + :param sort_key: Optional key to sort on. + :param sort_dir: Optional direction to sort. :rtype: list of :class:`VolumeTransfer` """ - query_string = utils.build_query_param(search_opts) - - detail = "" - if detailed: - detail = "/detail" - + resource_type = 'os-volume-transfer' if self.api_version.matches('3.55'): - return self._list("/volume-transfers%s%s" % (detail, query_string), - "transfers") + resource_type = 'volume-transfers' - return self._list("/os-volume-transfer%s%s" % (detail, query_string), - "transfers") + url = self._build_list_url(resource_type, detailed=detailed, + search_opts=search_opts, + sort_key=sort_key, sort_dir=sort_dir) + return self._list(url, 'transfers') def delete(self, transfer_id): """Delete a volume transfer. diff --git a/cinderclient/v3/volume_types.py b/cinderclient/v3/volume_types.py index 4d24756..c55a4a4 100644 --- a/cinderclient/v3/volume_types.py +++ b/cinderclient/v3/volume_types.py @@ -99,7 +99,8 @@ class VolumeTypeManager(base.ManagerWithFind): # Need to keep backwards compatibility with is_public usage. If it # isn't included then cinder will assume you want is_public=True, which # negatively affects the results. - search_opts['is_public'] = is_public + if 'is_public' not in search_opts: + search_opts['is_public'] = is_public query_string = "?%s" % parse.urlencode(search_opts) return self._list("/types%s" % query_string, "volume_types") diff --git a/cinderclient/v3/volumes.py b/cinderclient/v3/volumes.py index 33850a3..d4b9637 100644 --- a/cinderclient/v3/volumes.py +++ b/cinderclient/v3/volumes.py @@ -37,6 +37,7 @@ class Volume(volumes.Volume): 3.1-latest). :param protected: Boolean to decide whether prevents image from being deleted (allowed for 3.1-latest). + :returns: tuple (response, body) """ if self.manager.api_version >= api_versions.APIVersion("3.1"): visibility = 'private' if visibility is None else visibility diff --git a/doc/requirements.txt b/doc/requirements.txt index 8bbc886..bf81d84 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -2,6 +2,7 @@ # 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.18.1 # Apache-2.0 +openstackdocstheme>=1.20.0 # Apache-2.0 reno>=2.5.0 # Apache-2.0 -sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD +sphinx!=1.6.6,!=1.6.7,>=1.6.2,<2.0.0;python_version=='2.7' # BSD +sphinx!=1.6.6,!=1.6.7,!=2.1.0,>=1.6.2;python_version>='3.4' # BSD diff --git a/doc/source/cli/details.rst b/doc/source/cli/details.rst index 87e18f5..9ced4c6 100644 --- a/doc/source/cli/details.rst +++ b/doc/source/cli/details.rst @@ -23,8 +23,6 @@ Block Storage service (cinder) command-line client The cinder client is the command-line interface (CLI) for the Block Storage service (cinder) API and its extensions. -This chapter documents :command:`cinder` version ``2.2.0``. - For help on a specific :command:`cinder` command, enter: .. code-block:: console @@ -693,14 +691,6 @@ cinder usage ``rename`` Renames a volume. -``replication-promote`` - Promote a secondary volume to primary for a - relationship. - -``replication-reenable`` - Sync the secondary volume with primary for a - relationship. - ``reset-state`` Explicitly updates the entity state in the Cinder database. diff --git a/doc/source/conf.py b/doc/source/conf.py index 3cfc842..6642cbf 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -14,7 +14,7 @@ import os import sys -import pbr.version +import openstackdocstheme sys.setrecursionlimit(4000) @@ -48,17 +48,12 @@ source_suffix = '.rst' master_doc = 'index' # General information about the project. -project = 'python-cinderclient' copyright = 'OpenStack Contributors' -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -version_info = pbr.version.VersionInfo('python-cinderclient') -# The short X.Y version. -version = version_info.version_string() -# The full version, including alpha/beta/rc tags. -release = version_info.release_string() +# done by the openstackdocstheme ext +# project = 'python-cinderclient' +# version = version_info.version_string() +# release = version_info.release_string() # List of directories, relative to source directory, that shouldn't be searched # for source files. @@ -84,7 +79,7 @@ html_theme = 'openstackdocs' # 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' +# html_last_updated_fmt = '%Y-%m-%d %H:%M' # -- Options for manual page output ------------------------------------------ @@ -98,3 +93,50 @@ man_pages = [ repository_name = 'openstack/python-cinderclient' bug_project = 'cinderclient' bug_tag = '' + + +# -- Options for LaTeX output ------------------------------------------------- + +# The paper size ('letter' or 'a4'). +# latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +# latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (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'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +# latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +# latex_use_parts = False + +# Additional stuff for the LaTeX preamble. +# latex_preamble = '' + +# Documents to append as an appendix to all manuals. +# latex_appendices = [] + +# If false, no module index is generated. +# latex_use_modindex = True + +# Disable usage of xindy https://bugzilla.redhat.com/show_bug.cgi?id=1643664 +latex_use_xindy = False + +latex_domain_indices = False + +latex_elements = { + 'makeindex': '', + 'printindex': '', + 'preamble': r'\setcounter{tocdepth}{3}', +} + +latex_additional_files = ['cinderclient.sty'] diff --git a/doc/source/user/shell.rst b/doc/source/user/shell.rst index 0d03b01..50d8feb 100644 --- a/doc/source/user/shell.rst +++ b/doc/source/user/shell.rst @@ -40,6 +40,14 @@ For example, in Bash you'd use:: export OS_AUTH_URL=http://auth.example.com:5000/v3 export OS_VOLUME_API_VERSION=3 +If OS_VOLUME_API_VERSION is not set, the highest version +supported by the server will be used. + +If OS_VOLUME_API_VERSION exceeds the highest version +supported by the server, the highest version supported by +both the client and server will be used. A warning +message is printed when this occurs. + From there, all shell commands take the form:: cinder <command> [arguments...] diff --git a/playbooks/legacy/cinderclient-dsvm-functional/post.yaml b/playbooks/legacy/cinderclient-dsvm-functional/post.yaml deleted file mode 100644 index dac8753..0000000 --- a/playbooks/legacy/cinderclient-dsvm-functional/post.yaml +++ /dev/null @@ -1,80 +0,0 @@ -- hosts: primary - tasks: - - - name: Copy files from {{ ansible_user_dir }}/workspace/ on node - synchronize: - src: '{{ ansible_user_dir }}/workspace/' - dest: '{{ zuul.executor.log_root }}' - mode: pull - copy_links: true - verify_host: true - rsync_opts: - - --include=**/*nose_results.html - - --include=*/ - - --exclude=* - - --prune-empty-dirs - - - name: Copy files from {{ ansible_user_dir }}/workspace/ on node - synchronize: - src: '{{ ansible_user_dir }}/workspace/' - dest: '{{ zuul.executor.log_root }}' - mode: pull - copy_links: true - verify_host: true - rsync_opts: - - --include=**/*testr_results.html.gz - - --include=*/ - - --exclude=* - - --prune-empty-dirs - - - name: Copy files from {{ ansible_user_dir }}/workspace/ on node - synchronize: - src: '{{ ansible_user_dir }}/workspace/' - dest: '{{ zuul.executor.log_root }}' - mode: pull - copy_links: true - verify_host: true - rsync_opts: - - --include=/.testrepository/tmp* - - --include=*/ - - --exclude=* - - --prune-empty-dirs - - - name: Copy files from {{ ansible_user_dir }}/workspace/ on node - synchronize: - src: '{{ ansible_user_dir }}/workspace/' - dest: '{{ zuul.executor.log_root }}' - mode: pull - copy_links: true - verify_host: true - rsync_opts: - - --include=**/*testrepository.subunit.gz - - --include=*/ - - --exclude=* - - --prune-empty-dirs - - - name: Copy files from {{ ansible_user_dir }}/workspace/ on node - synchronize: - src: '{{ ansible_user_dir }}/workspace/' - dest: '{{ zuul.executor.log_root }}/tox' - mode: pull - copy_links: true - verify_host: true - rsync_opts: - - --include=/.tox/*/log/* - - --include=*/ - - --exclude=* - - --prune-empty-dirs - - - name: Copy files from {{ ansible_user_dir }}/workspace/ on node - synchronize: - src: '{{ ansible_user_dir }}/workspace/' - dest: '{{ zuul.executor.log_root }}' - mode: pull - copy_links: true - verify_host: true - rsync_opts: - - --include=/logs/** - - --include=*/ - - --exclude=* - - --prune-empty-dirs diff --git a/playbooks/legacy/cinderclient-dsvm-functional/run.yaml b/playbooks/legacy/cinderclient-dsvm-functional/run.yaml deleted file mode 100644 index 8e6f9b1..0000000 --- a/playbooks/legacy/cinderclient-dsvm-functional/run.yaml +++ /dev/null @@ -1,49 +0,0 @@ -- hosts: all - name: Autoconverted job legacy-cinderclient-dsvm-functional from old job gate-cinderclient-dsvm-functional-ubuntu-xenial-nv - tasks: - - - name: Ensure legacy workspace directory - file: - path: '{{ ansible_user_dir }}/workspace' - state: directory - - - shell: - cmd: | - set -e - set -x - cat > clonemap.yaml << EOF - clonemap: - - name: openstack-infra/devstack-gate - dest: devstack-gate - EOF - /usr/zuul-env/bin/zuul-cloner -m clonemap.yaml --cache-dir /opt/git \ - https://git.openstack.org \ - openstack-infra/devstack-gate - executable: /bin/bash - chdir: '{{ ansible_user_dir }}/workspace' - environment: '{{ zuul | zuul_legacy_vars }}' - - - shell: - cmd: | - set -e - set -x - export PYTHONUNBUFFERED=true - export BRANCH_OVERRIDE=default - export DEVSTACK_PROJECT_FROM_GIT=python-cinderclient - export DEVSTACK_LOCAL_CONFIG="VOLUME_BACKING_FILE_SIZE=16G" - if [ "$BRANCH_OVERRIDE" != "default" ] ; then - export OVERRIDE_ZUUL_BRANCH=$BRANCH_OVERRIDE - fi - if [ "" == "-identity-v3-only" ] ; then - export DEVSTACK_LOCAL_CONFIG+=$'\n'"ENABLE_IDENTITY_V2=False" - fi - function post_test_hook { - # Configure and run functional tests - $BASE/new/python-cinderclient/cinderclient/tests/functional/hooks/post_test_hook.sh - } - export -f post_test_hook - cp devstack-gate/devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh - ./safe-devstack-vm-gate-wrap.sh - executable: /bin/bash - chdir: '{{ ansible_user_dir }}/workspace' - environment: '{{ zuul | zuul_legacy_vars }}' diff --git a/playbooks/post.yaml b/playbooks/post.yaml new file mode 100644 index 0000000..9860e2a --- /dev/null +++ b/playbooks/post.yaml @@ -0,0 +1,6 @@ +- 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 new file mode 100644 index 0000000..ea7d2db --- /dev/null +++ b/playbooks/python-cinderclient-functional.yaml @@ -0,0 +1,14 @@ +- hosts: all + roles: + - 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_envlist: functional + tox_install_siblings: false + environment: "{{ os_env_vars }}" diff --git a/releasenotes/notes/adding-option-is-public-to-type-list-9a16bd9c2b8eb65a.yaml b/releasenotes/notes/adding-option-is-public-to-type-list-9a16bd9c2b8eb65a.yaml new file mode 100644 index 0000000..4f85a31 --- /dev/null +++ b/releasenotes/notes/adding-option-is-public-to-type-list-9a16bd9c2b8eb65a.yaml @@ -0,0 +1,13 @@ +--- +upgrade: + - | + Adding ``is_public`` support in ``--filters`` option for ``type-list`` + and ``group-type-list`` command. + This option is used to filter volume types and group types on the basis + of visibility. + This option has 3 possible values : True, False, None with details as + follows : + + * True: List public types only + * False: List private types only + * None: List both public and private types diff --git a/releasenotes/notes/backup-user-id-059ccea871893a0b.yaml b/releasenotes/notes/backup-user-id-059ccea871893a0b.yaml new file mode 100644 index 0000000..abafaa7 --- /dev/null +++ b/releasenotes/notes/backup-user-id-059ccea871893a0b.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Starting with API microversion 3.56, ``backup-list`` and ``backup-show`` + will include the ``User ID`` denoting the user that created the backup. diff --git a/releasenotes/notes/bug-1826286-c9b68709a0d63d06.yaml b/releasenotes/notes/bug-1826286-c9b68709a0d63d06.yaml new file mode 100644 index 0000000..dec2def --- /dev/null +++ b/releasenotes/notes/bug-1826286-c9b68709a0d63d06.yaml @@ -0,0 +1,9 @@ +--- +fixes: + - | + The ``discover_version`` function in the ``cinderclient.api_versions`` + module was documented to return the most recent API version supported + by both the client and the target Block Storage API endpoint, but it + was not taking into account the highest API version supported by the + client. Its behavior has been corrected in this release. + [Bug `1826286 <https://bugs.launchpad.net/bugs/1826286>`_] diff --git a/releasenotes/notes/cli-api-ver-negotiation-9f8fd8b77ae299fd.yaml b/releasenotes/notes/cli-api-ver-negotiation-9f8fd8b77ae299fd.yaml new file mode 100644 index 0000000..4501850 --- /dev/null +++ b/releasenotes/notes/cli-api-ver-negotiation-9f8fd8b77ae299fd.yaml @@ -0,0 +1,12 @@ +--- +features: + - | + Automatic version negotiation for the cinderclient CLI. + If an API version is not specified, the CLI will use the newest + supported by the client and the server. + If an API version newer than the server supports is requested, + the CLI will fall back to the newest version supported by the server + and issue a warning message. + This does not affect cinderclient library usage. + + diff --git a/releasenotes/notes/remove-replv1-cli-61d5722438f888b6.yaml b/releasenotes/notes/remove-replv1-cli-61d5722438f888b6.yaml new file mode 100644 index 0000000..9aa9f5c --- /dev/null +++ b/releasenotes/notes/remove-replv1-cli-61d5722438f888b6.yaml @@ -0,0 +1,4 @@ +--- +prelude: > + The replication v1 have been removed from cinder, the volume promote/reenable + replication on the command line have now been removed. diff --git a/releasenotes/notes/transfer-sort-ca622e9b8da605c1.yaml b/releasenotes/notes/transfer-sort-ca622e9b8da605c1.yaml new file mode 100644 index 0000000..5080f97 --- /dev/null +++ b/releasenotes/notes/transfer-sort-ca622e9b8da605c1.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Starting with microversion 3.59, the ``cinder transfer-list`` command now + supports the ``--sort`` argument to sort the returned results. This + argument takes either just the attribute to sort on, or the attribute and + the sort direction. Examples include ``cinder transfer-list --sort=id`` and + ``cinder transfer-list --sort=name:asc``. diff --git a/roles/get-os-environment/defaults/main.yaml b/roles/get-os-environment/defaults/main.yaml new file mode 100644 index 0000000..91190b3 --- /dev/null +++ b/roles/get-os-environment/defaults/main.yaml @@ -0,0 +1,2 @@ +--- +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 new file mode 100644 index 0000000..b3f457b --- /dev/null +++ b/roles/get-os-environment/tasks/main.yaml @@ -0,0 +1,12 @@ +- 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 }}" @@ -1,6 +1,6 @@ [tox] distribute = False -envlist = py36,py27,pep8 +envlist = py27,py37,pep8 minversion = 2.0 skipsdist = True @@ -16,7 +16,7 @@ setenv = passenv = *_proxy *_PROXY deps = - -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} + -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = find . -type f -name "*.pyc" -delete @@ -31,7 +31,7 @@ commands = flake8 [testenv:pylint] basepython = python3 deps = - -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} + -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt pylint==1.9.1 commands = bash tools/lintstack.sh @@ -55,15 +55,27 @@ commands = [testenv:docs] basepython = python3 deps = - -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} + -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt -commands = sphinx-build -b html doc/source doc/build/html +commands = sphinx-build -W -b html doc/source doc/build/html + +[testenv:pdf-docs] +basepython = python3 +deps = + {[testenv:docs]deps} +commands = + {[testenv:docs]commands} + sphinx-build -W -b latex doc/source doc/build/pdf + make -C doc/build/pdf +whitelist_externals = + make + cp [testenv:releasenotes] basepython = python3 deps = - -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} + -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html |