summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitreview2
-rw-r--r--.zuul.yaml25
-rw-r--r--README.rst18
-rw-r--r--cinderclient/api_versions.py34
-rw-r--r--cinderclient/base.py14
-rw-r--r--cinderclient/client.py19
-rw-r--r--cinderclient/shell.py105
-rw-r--r--cinderclient/shell_utils.py16
-rwxr-xr-xcinderclient/tests/functional/hooks/post_test_hook.sh53
-rw-r--r--cinderclient/tests/unit/test_api_versions.py7
-rw-r--r--cinderclient/tests/unit/test_client.py35
-rw-r--r--cinderclient/tests/unit/test_shell.py28
-rw-r--r--cinderclient/tests/unit/v2/fakes.py10
-rw-r--r--cinderclient/tests/unit/v2/test_shell.py10
-rw-r--r--cinderclient/tests/unit/v2/test_volumes.py16
-rw-r--r--cinderclient/tests/unit/v3/fakes.py22
-rw-r--r--cinderclient/tests/unit/v3/test_shell.py79
-rw-r--r--cinderclient/v2/shell.py19
-rw-r--r--cinderclient/v2/volumes.py18
-rw-r--r--cinderclient/v3/group_types.py11
-rw-r--r--cinderclient/v3/shell.py118
-rw-r--r--cinderclient/v3/volume_transfers.py22
-rw-r--r--cinderclient/v3/volume_types.py3
-rw-r--r--cinderclient/v3/volumes.py1
-rw-r--r--doc/requirements.txt5
-rw-r--r--doc/source/cli/details.rst10
-rw-r--r--doc/source/conf.py64
-rw-r--r--doc/source/user/shell.rst8
-rw-r--r--playbooks/legacy/cinderclient-dsvm-functional/post.yaml80
-rw-r--r--playbooks/legacy/cinderclient-dsvm-functional/run.yaml49
-rw-r--r--playbooks/post.yaml6
-rw-r--r--playbooks/python-cinderclient-functional.yaml14
-rw-r--r--releasenotes/notes/adding-option-is-public-to-type-list-9a16bd9c2b8eb65a.yaml13
-rw-r--r--releasenotes/notes/backup-user-id-059ccea871893a0b.yaml5
-rw-r--r--releasenotes/notes/bug-1826286-c9b68709a0d63d06.yaml9
-rw-r--r--releasenotes/notes/cli-api-ver-negotiation-9f8fd8b77ae299fd.yaml12
-rw-r--r--releasenotes/notes/remove-replv1-cli-61d5722438f888b6.yaml4
-rw-r--r--releasenotes/notes/transfer-sort-ca622e9b8da605c1.yaml8
-rw-r--r--roles/get-os-environment/defaults/main.yaml2
-rw-r--r--roles/get-os-environment/tasks/main.yaml12
-rw-r--r--tox.ini24
41 files changed, 618 insertions, 392 deletions
diff --git a/.gitreview b/.gitreview
index cb9446e..9b9acbf 100644
--- a/.gitreview
+++ b/.gitreview
@@ -1,4 +1,4 @@
[gerrit]
-host=review.openstack.org
+host=review.opendev.org
port=29418
project=openstack/python-cinderclient.git
diff --git a/.zuul.yaml b/.zuul.yaml
index 2d6e801..594163a 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -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
diff --git a/README.rst b/README.rst
index b20f289..ac616d7 100644
--- a/README.rst
+++ b/README.rst
@@ -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 }}"
diff --git a/tox.ini b/tox.ini
index e4d3db4..bfc523f 100644
--- a/tox.ini
+++ b/tox.ini
@@ -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