Retire Packaging Deb project repos

This commit is part of a series to retire the Packaging Deb
project. Step 2 is to remove all content from the project
repos, replacing it with a README notification where to find
ongoing work, and how to recover the repo if needed at some
future point (as in
https://docs.openstack.org/infra/manual/drivers.html#retiring-a-project).

Change-Id: Ia7c707c0fcfd231fad4a2eae3b3f88824d8257f8
This commit is contained in:
Tony Breeds 2017-09-12 15:56:52 -06:00
parent 8daff0334e
commit 5737a46900
237 changed files with 14 additions and 34190 deletions

View File

@ -1,7 +0,0 @@
[run]
branch = True
source = cinderclient
omit = cinderclient/tests/*
[report]
ignore_errors = True

18
.gitignore vendored
View File

@ -1,18 +0,0 @@
/.*
!.gitignore
!.mailmap
!.testr.conf
.*.sw?
subunit.log
*,cover
cover
covhtml
*.pyc
AUTHORS
ChangeLog
doc/build
releasenotes/build
build
dist
cinderclient/versioninfo
python_cinderclient.egg-info

View File

@ -1,4 +0,0 @@
[gerrit]
host=review.openstack.org
port=29418
project=openstack/python-cinderclient.git

View File

@ -1,16 +0,0 @@
Antony Messerli <amesserl@rackspace.com> root <root@debian.ohthree.com>
<amesserl@rackspace.com> <root@debian.ohthree.com>
<brian.waldon@rackspace.com> <bcwaldon@gmail.com>
Chris Behrens <cbehrens+github@codestud.com> comstud <cbehrens+github@codestud.com>
<cbehrens+github@codestud.com> <cbehrens@codestud.com>
Johannes Erdfelt <johannes.erdfelt@rackspace.com> jerdfelt <johannes@erdfelt.com>
<johannes.erdfelt@rackspace.com> <johannes@erdfelt.com>
<josh@jk0.org> <jkearney@nova.(none)>
<sandy@darksecretsoftware.com> <sandy.walsh@rackspace.com>
<sandy@darksecretsoftware.com> <sandy@sandywalsh.com>
Andy Smith <github@anarkystic.com> termie <github@anarkystic.com>
<chmouel.boudjnah@rackspace.co.uk> <chmouel@chmouel.com>
<matt.dietz@rackspace.com> <matthew.dietz@gmail.com>
Nikolay Sokolov <nsokolov@griddynamics.com> Nokolay Sokolov <nsokolov@griddynamics.com>
Nikolay Sokolov <nsokolov@griddynamics.com> Nokolay Sokolov <chemikadze@gmail.com>
<skanddh@gmail.com> <seungkyu.ahn@samsung.com>

View File

@ -1,4 +0,0 @@
[DEFAULT]
test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} ${PYTHON:-python} -m subunit.run discover -t ./ ${OS_TEST_PATH:-./cinderclient/tests/unit} $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list

View File

@ -1,16 +0,0 @@
If you would like to contribute to the development of OpenStack,
you must follow the steps in this page:
http://docs.openstack.org/infra/manual/developers.html
Once those steps have been completed, changes to OpenStack
should be submitted for review via the Gerrit tool, following
the workflow documented at:
http://docs.openstack.org/infra/manual/developers.html#development-workflow
Pull requests submitted through GitHub will be ignored.
Bugs should be filed on Launchpad, not in GitHub's issue tracker:
https://bugs.launchpad.net/python-cinderclient

View File

@ -1,47 +0,0 @@
Cinder Client Style Commandments
================================
- Step 1: Read the OpenStack Style Commandments
http://docs.openstack.org/developer/hacking/
- Step 2: Read on
Cinder Client Specific Commandments
-----------------------------------
General
-------
- Use 'raise' instead of 'raise e' to preserve original traceback or exception being reraised::
except Exception as e:
...
raise e # BAD
except Exception:
...
raise # OKAY
Release Notes
-------------
- Any patch that makes a change significant to the end consumer or deployer of an
OpenStack environment should include a release note (new features, upgrade impacts,
deprecated functionality, significant bug fixes, etc.)
- Cinder Client uses Reno for release notes management. See the `Reno Documentation`_
for more details on its usage.
.. _Reno Documentation: http://docs.openstack.org/developer/reno/
- As a quick example, when adding a new shell command for Awesome Storage Feature, one
could perform the following steps to include a release note for the new feature:
$ tox -e venv -- reno new add-awesome-command
$ vi releasenotes/notes/add-awesome-command-bb8bb8bb8bb8bb81.yaml
Remove the extra template text from the release note and update the details so it
looks something like:
---
features:
- Added shell command `cinder be-awesome` for Awesome Storage Feature.
- Include the generated release notes file when submitting your patch for review.

208
LICENSE
View File

@ -1,208 +0,0 @@
Copyright (c) 2009 Jacob Kaplan-Moss - initial codebase (< v2.1)
Copyright (c) 2011 Rackspace - OpenStack extensions (>= v2.1)
All rights reserved.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
--- License for python-cinderclient versions prior to 2.1 ---
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of this project nor the names of its contributors may
be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

14
README Normal file
View File

@ -0,0 +1,14 @@
This project is no longer maintained.
The contents of this repository are still available in the Git
source code management system. To see the contents of this
repository before it reached its end of life, please check out the
previous commit with "git checkout HEAD^1".
For ongoing work on maintaining OpenStack packages in the Debian
distribution, please see the Debian OpenStack packaging team at
https://wiki.debian.org/OpenStack/.
For any further questions, please email
openstack-dev@lists.openstack.org or join #openstack-dev on
Freenode.

View File

@ -1,369 +0,0 @@
========================
Team and repository tags
========================
.. image:: https://governance.openstack.org/badges/python-cinderclient.svg
:target: https://governance.openstack.org/reference/tags/index.html
.. Change things from this point on
Python bindings to the OpenStack Cinder API
===========================================
.. image:: https://img.shields.io/pypi/v/python-cinderclient.svg
:target: https://pypi.python.org/pypi/python-cinderclient/
:alt: Latest Version
.. image:: https://img.shields.io/pypi/dm/python-cinderclient.svg
:target: https://pypi.python.org/pypi/python-cinderclient/
:alt: Downloads
This is a client for the OpenStack Cinder API. There's a Python API (the
``cinderclient`` module), and a command-line script (``cinder``). Each
implements 100% of the OpenStack Cinder API.
See the `OpenStack CLI Reference`_ for information on how to use the ``cinder``
command-line tool. You may also want to look at the
`OpenStack API documentation`_.
.. _OpenStack CLI Reference: http://docs.openstack.org/cli-reference/overview.html
.. _OpenStack API documentation: http://developer.openstack.org/api-ref.html
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
.. _Launchpad: https://launchpad.net/python-cinderclient
.. _Gerrit: http://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/jacobian-archive/python-cloudservers
* License: Apache License, Version 2.0
* `PyPi`_ - package installation
* `Online Documentation`_
* `Blueprints`_ - feature specifications
* `Bugs`_ - issue tracking
* `Source`_
* `Specs`_
* `How to Contribute`_
.. _PyPi: https://pypi.python.org/pypi/python-cinderclient
.. _Online Documentation: http://docs.openstack.org/developer/python-cinderclient
.. _Blueprints: https://blueprints.launchpad.net/python-cinderclient
.. _Bugs: https://bugs.launchpad.net/python-cinderclient
.. _Source: https://git.openstack.org/cgit/openstack/python-cinderclient
.. _How to Contribute: http://docs.openstack.org/infra/manual/developers.html
.. _Specs: http://specs.openstack.org/openstack/cinder-specs/
.. contents:: Contents:
:local:
Command-line API
----------------
Installing this package gets you a shell command, ``cinder``, that you
can use to interact with any Rackspace compatible API (including OpenStack).
You'll need to provide your OpenStack username and password. You can do this
with the ``--os-username``, ``--os-password`` and ``--os-tenant-name``
params, but it's easier to just set them as environment variables::
export OS_USERNAME=openstack
export OS_PASSWORD=yadayada
export OS_TENANT_NAME=myproject
You will also need to define the authentication url with ``--os-auth-url``
and the version of the API with ``--os-volume-api-version``. Or set them as
environment variables as well. Since Block Storage API V2 is officially
deprecated, you are encouraged to set ``OS_VOLUME_API_VERSION=3``. If you
are using Keystone, you need to set the ``OS_AUTH_URL`` to the keystone
endpoint::
export OS_AUTH_URL=http://controller:5000/v3
export OS_VOLUME_API_VERSION=3
Since Keystone can return multiple regions in the Service Catalog, you
can specify the one you want with ``--os-region-name`` (or
``export OS_REGION_NAME``). It defaults to the first in the list returned.
You'll find complete documentation on the shell by running
``cinder help``::
usage: cinder [--version] [-d] [--os-auth-system <auth-system>]
[--service-type <service-type>] [--service-name <service-name>]
[--volume-service-name <volume-service-name>]
[--os-endpoint-type <os-endpoint-type>]
[--endpoint-type <endpoint-type>]
[--os-volume-api-version <volume-api-ver>]
[--bypass-url <bypass-url>] [--retries <retries>]
[--profile HMAC_KEY] [--os-auth-strategy <auth-strategy>]
[--os-username <auth-user-name>] [--os-password <auth-password>]
[--os-tenant-name <auth-tenant-name>]
[--os-tenant-id <auth-tenant-id>] [--os-auth-url <auth-url>]
[--os-user-id <auth-user-id>]
[--os-user-domain-id <auth-user-domain-id>]
[--os-user-domain-name <auth-user-domain-name>]
[--os-project-id <auth-project-id>]
[--os-project-name <auth-project-name>]
[--os-project-domain-id <auth-project-domain-id>]
[--os-project-domain-name <auth-project-domain-name>]
[--os-region-name <region-name>] [--os-token <token>]
[--os-url <url>] [--insecure] [--os-cacert <ca-certificate>]
[--os-cert <certificate>] [--os-key <key>] [--timeout <seconds>]
<subcommand> ...
Command-line interface to the OpenStack Cinder API.
Positional arguments:
<subcommand>
absolute-limits Lists absolute limits for a user.
api-version Display the server API version information. (Supported
by API versions 3.0 - 3.latest)
availability-zone-list
Lists all availability zones.
backup-create Creates a volume backup.
backup-delete Removes one or more backups.
backup-export Export backup metadata record.
backup-import Import backup metadata record.
backup-list Lists all backups.
backup-reset-state Explicitly updates the backup state.
backup-restore Restores a backup.
backup-show Shows backup details.
cgsnapshot-create Creates a cgsnapshot.
cgsnapshot-delete Removes one or more cgsnapshots.
cgsnapshot-list Lists all cgsnapshots.
cgsnapshot-show Shows cgsnapshot details.
consisgroup-create Creates a consistency group.
consisgroup-create-from-src
Creates a consistency group from a cgsnapshot or a
source CG.
consisgroup-delete Removes one or more consistency groups.
consisgroup-list Lists all consistency groups.
consisgroup-show Shows details of a consistency group.
consisgroup-update Updates a consistency group.
create Creates a volume.
credentials Shows user credentials returned from auth.
delete Removes one or more volumes.
encryption-type-create
Creates encryption type for a volume type. Admin only.
encryption-type-delete
Deletes encryption type for a volume type. Admin only.
encryption-type-list
Shows encryption type details for volume types. Admin
only.
encryption-type-show
Shows encryption type details for a volume type. Admin
only.
encryption-type-update
Update encryption type information for a volume type
(Admin Only).
endpoints Discovers endpoints registered by authentication
service.
extend Attempts to extend size of an existing volume.
extra-specs-list Lists current volume types and extra specs.
failover-host Failover a replicating cinder-volume host.
force-delete Attempts force-delete of volume, regardless of state.
freeze-host Freeze and disable the specified cinder-volume host.
get-capabilities Show backend volume stats and properties. Admin only.
get-pools Show pool information for backends. Admin only.
image-metadata Sets or deletes volume image metadata.
image-metadata-show
Shows volume image metadata.
list Lists all volumes.
manage Manage an existing volume.
metadata Sets or deletes volume metadata.
metadata-show Shows volume metadata.
metadata-update-all
Updates volume metadata.
migrate Migrates volume to a new host.
qos-associate Associates qos specs with specified volume type.
qos-create Creates a qos specs.
qos-delete Deletes a specified qos specs.
qos-disassociate Disassociates qos specs from specified volume type.
qos-disassociate-all
Disassociates qos specs from all its associations.
qos-get-association
Lists all associations for specified qos specs.
qos-key Sets or unsets specifications for a qos spec.
qos-list Lists qos specs.
qos-show Shows qos specs details.
quota-class-show Lists quotas for a quota class.
quota-class-update Updates quotas for a quota class.
quota-defaults Lists default quotas for a tenant.
quota-delete Delete the quotas for a tenant.
quota-show Lists quotas for a tenant.
quota-update Updates quotas for a tenant.
quota-usage Lists quota usage for a tenant.
rate-limits Lists rate limits for a user.
readonly-mode-update
Updates volume read-only access-mode flag.
rename Renames a volume.
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.
service-disable Disables the service.
service-enable Enables the service.
service-list Lists all services. Filter by host and service binary.
(Supported by API versions 3.0 - 3.latest)
set-bootable Update bootable status of a volume.
show Shows volume details.
snapshot-create Creates a snapshot.
snapshot-delete Removes one or more snapshots.
snapshot-list Lists all snapshots.
snapshot-manage Manage an existing snapshot.
snapshot-metadata Sets or deletes snapshot metadata.
snapshot-metadata-show
Shows snapshot metadata.
snapshot-metadata-update-all
Updates snapshot metadata.
snapshot-rename Renames a snapshot.
snapshot-reset-state
Explicitly updates the snapshot state.
snapshot-show Shows snapshot details.
snapshot-unmanage Stop managing a snapshot.
thaw-host Thaw and enable the specified cinder-volume host.
transfer-accept Accepts a volume transfer.
transfer-create Creates a volume transfer.
transfer-delete Undoes a transfer.
transfer-list Lists all transfers.
transfer-show Shows transfer details.
type-access-add Adds volume type access for the given project.
type-access-list Print access information about the given volume type.
type-access-remove Removes volume type access for the given project.
type-create Creates a volume type.
type-default List the default volume type.
type-delete Deletes volume type or types.
type-key Sets or unsets extra_spec for a volume type.
type-list Lists available 'volume types'.
type-show Show volume type details.
type-update Updates volume type name, description, and/or
is_public.
unmanage Stop managing a volume.
upload-to-image Uploads volume to Image Service as an image.
version-list List all API versions. (Supported by API versions 3.0
- 3.latest)
bash-completion Prints arguments for bash_completion.
help Shows help about this program or one of its
subcommands.
list-extensions
Optional arguments:
--version show program's version number and exit
-d, --debug Shows debugging output.
--os-auth-system <auth-system>
Defaults to env[OS_AUTH_SYSTEM].
--service-type <service-type>
Service type. For most actions, default is volume.
--service-name <service-name>
Service name. Default=env[CINDER_SERVICE_NAME].
--volume-service-name <volume-service-name>
Volume service name.
Default=env[CINDER_VOLUME_SERVICE_NAME].
--os-endpoint-type <os-endpoint-type>
Endpoint type, which is publicURL or internalURL.
Default=env[OS_ENDPOINT_TYPE] or nova
env[CINDER_ENDPOINT_TYPE] or publicURL.
--endpoint-type <endpoint-type>
DEPRECATED! Use --os-endpoint-type.
--os-volume-api-version <volume-api-ver>
Block Storage API version. Accepts X, X.Y (where X is
major and Y is minor
part).Default=env[OS_VOLUME_API_VERSION].
--bypass-url <bypass-url>
Use this API endpoint instead of the Service Catalog.
Defaults to env[CINDERCLIENT_BYPASS_URL].
--retries <retries> Number of retries.
--profile HMAC_KEY HMAC key to use for encrypting context data for
performance profiling of operation. This key needs to
match the one configured on the cinder api server.
Without key the profiling will not be triggered even
if osprofiler is enabled on server side.
--os-auth-strategy <auth-strategy>
Authentication strategy (Env: OS_AUTH_STRATEGY,
default keystone). For now, any other value will
disable the authentication.
--os-username <auth-user-name>
OpenStack user name. Default=env[OS_USERNAME].
--os-password <auth-password>
Password for OpenStack user. Default=env[OS_PASSWORD].
--os-tenant-name <auth-tenant-name>
Tenant name. Default=env[OS_TENANT_NAME].
--os-tenant-id <auth-tenant-id>
ID for the tenant. Default=env[OS_TENANT_ID].
--os-auth-url <auth-url>
URL for the authentication service.
Default=env[OS_AUTH_URL].
--os-user-id <auth-user-id>
Authentication user ID (Env: OS_USER_ID).
--os-user-domain-id <auth-user-domain-id>
OpenStack user domain ID. Defaults to
env[OS_USER_DOMAIN_ID].
--os-user-domain-name <auth-user-domain-name>
OpenStack user domain name. Defaults to
env[OS_USER_DOMAIN_NAME].
--os-project-id <auth-project-id>
Another way to specify tenant ID. This option is
mutually exclusive with --os-tenant-id. Defaults to
env[OS_PROJECT_ID].
--os-project-name <auth-project-name>
Another way to specify tenant name. This option is
mutually exclusive with --os-tenant-name. Defaults to
env[OS_PROJECT_NAME].
--os-project-domain-id <auth-project-domain-id>
Defaults to env[OS_PROJECT_DOMAIN_ID].
--os-project-domain-name <auth-project-domain-name>
Defaults to env[OS_PROJECT_DOMAIN_NAME].
--os-region-name <region-name>
Region name. Default=env[OS_REGION_NAME].
--os-token <token> Defaults to env[OS_TOKEN].
--os-url <url> Defaults to env[OS_URL].
API Connection Options:
Options controlling the HTTP API Connections
--insecure Explicitly allow client to perform "insecure" TLS
(https) requests. The server's certificate will not be
verified against any certificate authorities. This
option should be used with caution.
--os-cacert <ca-certificate>
Specify a CA bundle file to use in verifying a TLS
(https) server certificate. Defaults to
env[OS_CACERT].
--os-cert <certificate>
Defaults to env[OS_CERT].
--os-key <key> Defaults to env[OS_KEY].
--timeout <seconds> Set request timeout (in seconds).
Run "cinder help SUBCOMMAND" for help on a subcommand.
If you want to get a particular version API help message, you can add
``--os-volume-api-version <volume-api-ver>`` in help command, like
this::
cinder --os-volume-api-version 3.28 help
Python API
----------
There's also a complete Python API, but it has not yet been documented.
Quick-start using keystone::
# use v3 auth with http://controller:5000/v3
>>> from cinderclient.v3 import client
>>> nt = client.Client(USERNAME, PASSWORD, PROJECT_ID, AUTH_URL)
>>> nt.volumes.list()
[...]
See release notes and more at `<http://docs.openstack.org/developer/python-cinderclient/>`_.

View File

@ -1,27 +0,0 @@
# Copyright (c) 2012 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import pbr.version
__all__ = ['__version__']
version_info = pbr.version.VersionInfo('python-cinderclient')
# We have a circular import problem when we first run python setup.py sdist
# It's harmless, so deflect it.
try:
__version__ = version_info.version_string()
except AttributeError:
__version__ = None

View File

@ -1,44 +0,0 @@
# Copyright 2016 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""oslo.i18n integration module.
See http://docs.openstack.org/developer/oslo.i18n/usage.html .
"""
import oslo_i18n
DOMAIN = "cinderclient"
_translators = oslo_i18n.TranslatorFactory(domain=DOMAIN)
# The primary translation function using the well-known name "_"
_ = _translators.primary
# The contextual translation function using the name "_C"
# requires oslo.i18n >=2.1.0
_C = _translators.contextual_form
# The plural translation function using the name "_P"
# requires oslo.i18n >=2.1.0
_P = _translators.plural_form
def get_available_languages():
return oslo_i18n.get_available_languages(DOMAIN)
def enable_lazy():
return oslo_i18n.enable_lazy()

View File

@ -1,407 +0,0 @@
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import functools
import logging
import os
import pkgutil
import re
from oslo_utils import strutils
from cinderclient import exceptions
from cinderclient import utils
from cinderclient._i18n import _
LOG = logging.getLogger(__name__)
# key is a deprecated version and value is an alternative version.
DEPRECATED_VERSIONS = {"1": "2"}
DEPRECATED_VERSION = "2.0"
MAX_VERSION = "3.40"
MIN_VERSION = "3.0"
_SUBSTITUTIONS = {}
_type_error_msg = "'%(other)s' should be an instance of '%(cls)s'"
class APIVersion(object):
"""This class represents an API Version with convenience
methods for manipulation and comparison of version
numbers that we need to do to implement microversions.
"""
def __init__(self, version_str=None):
"""Create an API version object."""
self.ver_major = 0
self.ver_minor = 0
if version_str is not None:
match = re.match(r"^([1-9]\d*)\.([1-9]\d*|0|latest)$", version_str)
if match:
self.ver_major = int(match.group(1))
if match.group(2) == "latest":
# NOTE(andreykurilin): Infinity allows to easily determine
# latest version and doesn't require any additional checks
# in comparison methods.
self.ver_minor = float("inf")
else:
self.ver_minor = int(match.group(2))
else:
msg = (_("Invalid format of client version '%s'. "
"Expected format 'X.Y', where X is a major part and Y "
"is a minor part of version.") % version_str)
raise exceptions.UnsupportedVersion(msg)
def __str__(self):
"""Debug/Logging representation of object."""
if self.is_latest():
return "Latest API Version Major: %s" % self.ver_major
return ("API Version Major: %s, Minor: %s"
% (self.ver_major, self.ver_minor))
def __repr__(self):
if self:
return "<APIVersion: %s>" % self.get_string()
return "<APIVersion: null>"
def __bool__(self):
return self.ver_major != 0 or self.ver_minor != 0
__nonzero__ = __bool__
def is_latest(self):
return self.ver_minor == float("inf")
def __lt__(self, other):
if not isinstance(other, APIVersion):
raise TypeError(_type_error_msg % {"other": other,
"cls": self.__class__})
return ((self.ver_major, self.ver_minor) <
(other.ver_major, other.ver_minor))
def __eq__(self, other):
if not isinstance(other, APIVersion):
raise TypeError(_type_error_msg % {"other": other,
"cls": self.__class__})
return ((self.ver_major, self.ver_minor) ==
(other.ver_major, other.ver_minor))
def __gt__(self, other):
if not isinstance(other, APIVersion):
raise TypeError(_type_error_msg % {"other": other,
"cls": self.__class__})
return ((self.ver_major, self.ver_minor) >
(other.ver_major, other.ver_minor))
def __le__(self, other):
return self < other or self == other
def __ne__(self, other):
return not self.__eq__(other)
def __ge__(self, other):
return self > other or self == other
def matches(self, min_version, max_version=None):
"""Returns whether the version object represents a version
greater than or equal to the minimum version and less than
or equal to the maximum version.
:param min_version: Minimum acceptable version.
:param max_version: Maximum acceptable version.
:returns: boolean
If min_version is null then there is no minimum limit.
If max_version is null then there is no maximum limit.
If self is null then raise ValueError
"""
if not self:
raise ValueError("Null APIVersion doesn't support 'matches'.")
if isinstance(min_version, str):
min_version = APIVersion(version_str=min_version)
if isinstance(max_version, str):
max_version = APIVersion(version_str=max_version)
# This will work when they are None and when they are version 0.0
if not min_version and not max_version:
return True
if not max_version:
return min_version <= self
if not min_version:
return self <= max_version
return min_version <= self <= max_version
def get_string(self):
"""Converts object to string representation which if used to create
an APIVersion object results in the same version.
"""
if not self:
raise ValueError("Null APIVersion cannot be converted to string.")
elif self.is_latest():
return "%s.%s" % (self.ver_major, "latest")
return "%s.%s" % (self.ver_major, self.ver_minor)
class VersionedMethod(object):
def __init__(self, name, start_version, end_version, func):
"""Versioning information for a single method
:param name: Name of the method
:param start_version: Minimum acceptable version
:param end_version: Maximum acceptable_version
:param func: Method to call
Minimum and maximums are inclusive
"""
self.name = name
self.start_version = start_version
self.end_version = end_version
self.func = func
def __str__(self):
return ("Version Method %s: min: %s, max: %s"
% (self.name, self.start_version, self.end_version))
def __repr__(self):
return "<VersionedMethod %s>" % self.name
def get_available_major_versions():
# NOTE(andreykurilin): available clients version should not be
# hardcoded, so let's discover them.
matcher = re.compile(r"v[0-9]*$")
submodules = pkgutil.iter_modules([os.path.dirname(__file__)])
available_versions = [name[1:] for loader, name, ispkg in submodules
if matcher.search(name)]
return available_versions
def check_major_version(api_version):
"""Checks major part of ``APIVersion`` obj is supported.
:raises cinderclient.exceptions.UnsupportedVersion: if major part is not
supported
"""
available_versions = get_available_major_versions()
if (api_version and str(api_version.ver_major) not in available_versions):
if len(available_versions) == 1:
msg = ("Invalid client version '%(version)s'. "
"Major part should be '%(major)s'") % {
"version": api_version.get_string(),
"major": available_versions[0]}
else:
msg = ("Invalid client version '%(version)s'. "
"Major part must be one of: '%(major)s'") % {
"version": api_version.get_string(),
"major": ", ".join(available_versions)}
raise exceptions.UnsupportedVersion(msg)
def get_api_version(version_string):
"""Returns checked APIVersion object"""
version_string = str(version_string)
if version_string in DEPRECATED_VERSIONS:
LOG.warning("Version %(deprecated_version)s is deprecated, use "
"alternative version %(alternative)s instead.",
{"deprecated_version": version_string,
"alternative": DEPRECATED_VERSIONS[version_string]})
if strutils.is_int_like(version_string):
version_string = "%s.0" % version_string
api_version = APIVersion(version_string)
check_major_version(api_version)
return api_version
def _get_server_version_range(client):
versions = client.services.server_api_version()
if not versions:
return APIVersion(), APIVersion()
for version in versions:
if '3.' in version.version:
return APIVersion(version.min_version), APIVersion(version.version)
def get_highest_version(client):
"""Queries the server version info and returns highest supported
microversion
:param client: client object
:returns: APIVersion
"""
server_start_version, server_end_version = _get_server_version_range(
client)
return server_end_version
def discover_version(client, requested_version):
"""Checks ``requested_version`` and returns the most recent version
supported by both the API and the client.
:param client: client object
:param requested_version: requested version represented by APIVersion obj
:returns: APIVersion
"""
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)
_validate_server_version(server_start_version, server_end_version)
return valid_version
def _validate_requested_version(requested_version,
server_start_version,
server_end_version):
"""Validates the requested version.
Checks 'requested_version' is within the min/max range supported by the
server. If 'requested_version' is not within range then attempts to
downgrade to 'server_end_version'. Otherwise an UnsupportedVersion
exception is thrown.
:param requested_version: requestedversion represented by APIVersion obj
:param server_start_version: APIVersion object representing server min
:param server_end_version: APIVersion object representing server max
"""
valid_version = requested_version
if not requested_version.matches(server_start_version, server_end_version):
if server_end_version <= requested_version:
if (APIVersion(MIN_VERSION) <= server_end_version and
server_end_version <= APIVersion(MAX_VERSION)):
msg = _("Requested version %(requested_version)s is "
"not supported. Downgrading requested version "
"to %(server_end_version)s.")
LOG.debug(msg, {
"requested_version": requested_version,
"server_end_version": server_end_version})
valid_version = server_end_version
else:
raise exceptions.UnsupportedVersion(
_("The specified version isn't supported by server. The valid "
"version range is '%(min)s' to '%(max)s'") % {
"min": server_start_version.get_string(),
"max": server_end_version.get_string()})
return valid_version
def _validate_server_version(server_start_version, server_end_version):
"""Validates the server version.
Checks that the 'server_end_version' is greater than the minimum version
supported by the client. Then checks that the 'server_start_version' is
less than the maximum version supported by the client.
:param server_start_version:
:param server_end_version:
:return:
"""
if APIVersion(MIN_VERSION) > server_end_version:
raise exceptions.UnsupportedVersion(
_("Server's version is too old. The client's valid version range "
"is '%(client_min)s' to '%(client_max)s'. The server valid "
"version range is '%(server_min)s' to '%(server_max)s'.") % {
'client_min': MIN_VERSION,
'client_max': MAX_VERSION,
'server_min': server_start_version.get_string(),
'server_max': server_end_version.get_string()})
elif APIVersion(MAX_VERSION) < server_start_version:
raise exceptions.UnsupportedVersion(
_("Server's version is too new. The client's valid version range "
"is '%(client_min)s' to '%(client_max)s'. The server valid "
"version range is '%(server_min)s' to '%(server_max)s'.") % {
'client_min': MIN_VERSION,
'client_max': MAX_VERSION,
'server_min': server_start_version.get_string(),
'server_max': server_end_version.get_string()})
def update_headers(headers, api_version):
"""Set 'OpenStack-API-Version' header if api_version is not
null
"""
if api_version and api_version.ver_minor != 0:
headers["OpenStack-API-Version"] = "volume " + api_version.get_string()
def add_substitution(versioned_method):
_SUBSTITUTIONS.setdefault(versioned_method.name, [])
_SUBSTITUTIONS[versioned_method.name].append(versioned_method)
def get_substitutions(func_name, api_version=None):
substitutions = _SUBSTITUTIONS.get(func_name, [])
if api_version:
return [m for m in substitutions
if api_version.matches(m.start_version, m.end_version)]
return substitutions
def wraps(start_version, end_version=None):
start_version = APIVersion(start_version)
if end_version:
end_version = APIVersion(end_version)
else:
end_version = APIVersion("%s.latest" % start_version.ver_major)
def decor(func):
func.versioned = True
name = utils.get_function_name(func)
versioned_method = VersionedMethod(name, start_version,
end_version, func)
add_substitution(versioned_method)
@functools.wraps(func)
def substitution(obj, *args, **kwargs):
methods = get_substitutions(name, obj.api_version)
if not methods:
raise exceptions.VersionNotFoundForAPIMethod(
obj.api_version.get_string(), name)
method = max(methods, key=lambda f: f.start_version)
return method.func(obj, *args, **kwargs)
if hasattr(func, 'arguments'):
for cli_args, cli_kwargs in func.arguments:
utils.add_arg(substitution, *cli_args, **cli_kwargs)
return substitution
return decor

View File

@ -1,571 +0,0 @@
# Copyright 2010 Jacob Kaplan-Moss
# Copyright 2011 OpenStack Foundation
# Copyright 2012 Grid Dynamics
# Copyright 2013 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Base utilities to build API operation managers and objects on top of.
"""
# E1102: %s is not callable
# pylint: disable=E1102
import abc
import copy
from requests import Response
import six
from six.moves.urllib import parse
from cinderclient.apiclient import exceptions
from oslo_utils import encodeutils
from oslo_utils import strutils
def getid(obj):
"""Return id if argument is a Resource.
Abstracts the common pattern of allowing both an object or an object's ID
(UUID) as a parameter when dealing with relationships.
"""
try:
if obj.uuid:
return obj.uuid
except AttributeError:
pass
try:
return obj.id
except AttributeError:
return obj
# TODO(aababilov): call run_hooks() in HookableMixin's child classes
class HookableMixin(object):
"""Mixin so classes can register and run hooks."""
_hooks_map = {}
@classmethod
def add_hook(cls, hook_type, hook_func):
"""Add a new hook of specified type.
:param cls: class that registers hooks
:param hook_type: hook type, e.g., '__pre_parse_args__'
:param hook_func: hook function
"""
if hook_type not in cls._hooks_map:
cls._hooks_map[hook_type] = []
cls._hooks_map[hook_type].append(hook_func)
@classmethod
def run_hooks(cls, hook_type, *args, **kwargs):
"""Run all hooks of specified type.
:param cls: class that registers hooks
:param hook_type: hook type, e.g., '__pre_parse_args__'
:param **args: args to be passed to every hook function
:param **kwargs: kwargs to be passed to every hook function
"""
hook_funcs = cls._hooks_map.get(hook_type) or []
for hook_func in hook_funcs:
hook_func(*args, **kwargs)
class BaseManager(HookableMixin):
"""Basic manager type providing common operations.
Managers interact with a particular type of API (servers, flavors, images,
etc.) and provide CRUD operations for them.
"""
resource_class = None
def __init__(self, client):
"""Initializes BaseManager with `client`.
:param client: instance of BaseClient descendant for HTTP requests
"""
super(BaseManager, self).__init__()
self.client = client
def _list(self, url, response_key, obj_class=None, json=None):
"""List the collection.
:param url: a partial URL, e.g., '/servers'
:param response_key: the key to be looked up in response dictionary,
e.g., 'servers'
:param obj_class: class for constructing the returned objects
(self.resource_class will be used by default)
:param json: data that will be encoded as JSON and passed in POST
request (GET will be sent by default)
"""
if json:
body = self.client.post(url, json=json).json()
else:
body = self.client.get(url).json()
if obj_class is None:
obj_class = self.resource_class
data = body[response_key]
# NOTE(ja): keystone returns values as list as {'values': [ ... ]}
# unlike other services which just return the list...
try:
data = data['values']
except (KeyError, TypeError):
pass
return [obj_class(self, res, loaded=True) for res in data if res]
def _get(self, url, response_key):
"""Get an object from collection.
:param url: a partial URL, e.g., '/servers'
:param response_key: the key to be looked up in response dictionary,
e.g., 'server'
"""
body = self.client.get(url).json()
return self.resource_class(self, body[response_key], loaded=True)
def _head(self, url):
"""Retrieve request headers for an object.
:param url: a partial URL, e.g., '/servers'
"""
resp = self.client.head(url)
return resp.status_code == 204
def _post(self, url, json, response_key, return_raw=False):
"""Create an object.
:param url: a partial URL, e.g., '/servers'
:param json: data that will be encoded as JSON and passed in POST
request (GET will be sent by default)
:param response_key: the key to be looked up in response dictionary,
e.g., 'servers'
:param return_raw: flag to force returning raw JSON instead of
Python object of self.resource_class
"""
body = self.client.post(url, json=json).json()
if return_raw:
return body[response_key]
return self.resource_class(self, body[response_key])
def _put(self, url, json=None, response_key=None):
"""Update an object with PUT method.
:param url: a partial URL, e.g., '/servers'
:param json: data that will be encoded as JSON and passed in POST
request (GET will be sent by default)
:param response_key: the key to be looked up in response dictionary,
e.g., 'servers'
"""
resp = self.client.put(url, json=json)
# PUT requests may not return a body
if resp.content:
body = resp.json()
if response_key is not None:
return self.resource_class(self, body[response_key])
else:
return self.resource_class(self, body)
def _patch(self, url, json=None, response_key=None):
"""Update an object with PATCH method.
:param url: a partial URL, e.g., '/servers'
:param json: data that will be encoded as JSON and passed in POST
request (GET will be sent by default)
:param response_key: the key to be looked up in response dictionary,
e.g., 'servers'
"""
body = self.client.patch(url, json=json).json()
if response_key is not None:
return self.resource_class(self, body[response_key])
else:
return self.resource_class(self, body)
def _delete(self, url):
"""Delete an object.
:param url: a partial URL, e.g., '/servers/my-server'
"""
return self.client.delete(url)
@six.add_metaclass(abc.ABCMeta)
class ManagerWithFind(BaseManager):
"""Manager with additional `find()`/`findall()` methods."""
@abc.abstractmethod
def list(self):
pass
def find(self, **kwargs):
"""Find a single item with attributes matching ``**kwargs``.
This isn't very efficient: it loads the entire list then filters on
the Python side.
"""
matches = self.findall(**kwargs)
num_matches = len(matches)
if num_matches == 0:
msg = "No %s matching %s." % (self.resource_class.__name__, kwargs)
raise exceptions.NotFound(msg)
elif num_matches > 1:
raise exceptions.NoUniqueMatch()
else:
return matches[0]
def findall(self, **kwargs):
"""Find all items with attributes matching ``**kwargs``.
This isn't very efficient: it loads the entire list then filters on
the Python side.
"""
found = []
searches = kwargs.items()
for obj in self.list():
try:
if all(getattr(obj, attr) == value
for (attr, value) in searches):
found.append(obj)
except AttributeError:
continue
return found
class CrudManager(BaseManager):
"""Base manager class for manipulating entities.
Children of this class are expected to define a `collection_key` and `key`.
- `collection_key`: Usually a plural noun by convention (e.g. `entities`);
used to refer collections in both URL's (e.g. `/v3/entities`) and JSON
objects containing a list of member resources (e.g. `{'entities': [{},
{}, {}]}`).
- `key`: Usually a singular noun by convention (e.g. `entity`); used to
refer to an individual member of the collection.
"""
collection_key = None
key = None
def build_url(self, base_url=None, **kwargs):
"""Builds a resource URL for the given kwargs.
Given an example collection where `collection_key = 'entities'` and
`key = 'entity'`, the following URL's could be generated.
By default, the URL will represent a collection of entities, e.g.::
/entities
If kwargs contains an `entity_id`, then the URL will represent a
specific member, e.g.::
/entities/{entity_id}
:param base_url: if provided, the generated URL will be appended to it
"""
url = base_url if base_url is not None else ''
url += '/%s' % self.collection_key
# do we have a specific entity?
entity_id = kwargs.get('%s_id' % self.key)
if entity_id is not None:
url += '/%s' % entity_id
return url
def _filter_kwargs(self, kwargs):
"""Drop null values and handle ids."""
for key, ref in kwargs.copy().items():
if ref is None:
kwargs.pop(key)
else:
if isinstance(ref, Resource):
kwargs.pop(key)
kwargs['%s_id' % key] = getid(ref)
return kwargs
def create(self, **kwargs):
kwargs = self._filter_kwargs(kwargs)
return self._post(
self.build_url(**kwargs),
{self.key: kwargs},
self.key)
def get(self, **kwargs):
kwargs = self._filter_kwargs(kwargs)
return self._get(
self.build_url(**kwargs),
self.key)
def head(self, **kwargs):
kwargs = self._filter_kwargs(kwargs)
return self._head(self.build_url(**kwargs))
def list(self, base_url=None, **kwargs):
"""List the collection.
:param base_url: if provided, the generated URL will be appended to it
"""
kwargs = self._filter_kwargs(kwargs)
return self._list(
'%(base_url)s%(query)s' % {
'base_url': self.build_url(base_url=base_url, **kwargs),
'query': '?%s' % parse.urlencode(kwargs) if kwargs else '',
},
self.collection_key)
def put(self, base_url=None, **kwargs):
"""Update an element.
:param base_url: if provided, the generated URL will be appended to it
"""
kwargs = self._filter_kwargs(kwargs)
return self._put(self.build_url(base_url=base_url, **kwargs))
def update(self, **kwargs):
kwargs = self._filter_kwargs(kwargs)
params = kwargs.copy()
params.pop('%s_id' % self.key)
return self._patch(
self.build_url(**kwargs),
{self.key: params},
self.key)
def delete(self, **kwargs):
kwargs = self._filter_kwargs(kwargs)
return self._delete(
self.build_url(**kwargs))
def find(self, base_url=None, **kwargs):
"""Find a single item with attributes matching ``**kwargs``.
:param base_url: if provided, the generated URL will be appended to it
"""
kwargs = self._filter_kwargs(kwargs)
rl = self._list(
'%(base_url)s%(query)s' % {
'base_url': self.build_url(base_url=base_url, **kwargs),
'query': '?%s' % parse.urlencode(kwargs) if kwargs else '',
},
self.collection_key)
num = len(rl)
if num == 0:
msg = "No %s matching %s." % (self.resource_class.__name__, kwargs)
raise exceptions.NotFound(404, msg)
elif num > 1:
raise exceptions.NoUniqueMatch
else:
return rl[0]
class Extension(HookableMixin):
"""Extension descriptor."""
SUPPORTED_HOOKS = ('__pre_parse_args__', '__post_parse_args__')
manager_class = None
def __init__(self, name, module):
super(Extension, self).__init__()
self.name = name
self.module = module
self._parse_extension_module()
def _parse_extension_module(self):
self.manager_class = None
for attr_name, attr_value in self.module.__dict__.items():
if attr_name in self.SUPPORTED_HOOKS:
self.add_hook(attr_name, attr_value)
else:
try:
if issubclass(attr_value, BaseManager):
self.manager_class = attr_value
except TypeError:
pass
def __repr__(self):
return "<Extension '%s'>" % self.name
class RequestIdMixin(object):
"""Wrapper class to expose x-openstack-request-id to the caller."""
def setup(self):
self.x_openstack_request_ids = []
@property
def request_ids(self):
return self.x_openstack_request_ids
def append_request_ids(self, resp):
"""Add request_ids as an attribute to the object
:param resp: list, Response object or string
"""
if resp is None:
return
if isinstance(resp, list):
# Add list of request_ids if response is of type list.
for resp_obj in resp:
self._append_request_id(resp_obj)
else:
# Add request_ids if response contains single object.
self._append_request_id(resp)
def _append_request_id(self, resp):
if isinstance(resp, Response):
# Extract 'x-openstack-request-id' from headers if
# response is a Response object.
request_id = resp.headers.get('x-openstack-request-id')
self.x_openstack_request_ids.append(request_id)
else:
# If resp is of type string (in case of encryption type list)
self.x_openstack_request_ids.append(resp)
class Resource(RequestIdMixin):
"""Base class for OpenStack resources (tenant, user, etc.).
This is pretty much just a bag for attributes.
"""
HUMAN_ID = False
NAME_ATTR = 'name'
def __init__(self, manager, info, loaded=False, resp=None):
"""Populate and bind to a manager.
:param manager: BaseManager object
:param info: dictionary representing resource attributes
:param loaded: prevent lazy-loading if set to True
:param resp: Response or list of Response objects
"""
self.manager = manager
self._info = info
self._add_details(info)
self._loaded = loaded
if resp and hasattr(resp, "headers"):
self._checksum = resp.headers.get("Etag")
self.setup()
self.append_request_ids(resp)
def __repr__(self):
reprkeys = sorted(k
for k in self.__dict__.keys()
if k[0] != '_' and
k not in ['manager', 'x_openstack_request_ids'])
info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys)
return "<%s %s>" % (self.__class__.__name__, info)
@property
def human_id(self):
"""Human-readable ID which can be used for bash completion.
"""
if self.NAME_ATTR in self.__dict__ and self.HUMAN_ID:
return strutils.to_slug(getattr(self, self.NAME_ATTR))
return None
def _add_details(self, info):
for (k, v) in info.items():
try:
setattr(self, k, v)
except AttributeError:
# In this case we already defined the attribute on the class
continue
except UnicodeEncodeError:
setattr(self, encodeutils.safe_encode(k), v)
self._info[k] = v
def __getattr__(self, k):
if k not in self.__dict__ or k not in self._info:
# NOTE(bcwaldon): disallow lazy-loading if already loaded once
if not self.is_loaded():
self.get()
return self.__getattr__(k)
raise AttributeError(k)
else:
if k in self.__dict__:
return self.__dict__[k]
return self._info[k]
@property
def api_version(self):
return self.manager.api_version
def get(self):
# set_loaded() first ... so if we have to bail, we know we tried.
self.set_loaded(True)
if not hasattr(self.manager, 'get'):
return
new = self.manager.get(self.id)
if new:
self._add_details(new._info)
def __eq__(self, other):
if not isinstance(other, Resource):
return NotImplemented
# two resources of different types are not equal
if not isinstance(other, self.__class__):
return False
return self._info == other._info
def __ne__(self, other):
return not self.__eq__(other)
def is_loaded(self):
return self._loaded
def set_loaded(self, val):
self._loaded = val
def to_dict(self):
return copy.deepcopy(self._info)
class ListWithMeta(list, RequestIdMixin):
def __init__(self, values, resp):
super(ListWithMeta, self).__init__(values)
self.setup()
self.append_request_ids(resp)
class DictWithMeta(dict, RequestIdMixin):
def __init__(self, values, resp):
super(DictWithMeta, self).__init__(values)
self.setup()
self.append_request_ids(resp)
class TupleWithMeta(tuple, RequestIdMixin):
def __new__(cls, values, resp):
return super(TupleWithMeta, cls).__new__(cls, values)
def __init__(self, values, resp):
self.setup()
self.append_request_ids(resp)

View File

@ -1,368 +0,0 @@
# Copyright 2010 Jacob Kaplan-Moss
# Copyright 2011 OpenStack Foundation
# Copyright 2011 Piston Cloud Computing, Inc.
# Copyright 2013 Alessio Ababilov
# Copyright 2013 Grid Dynamics
# Copyright 2013 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
OpenStack Client interface. Handles the REST calls and responses.
"""
# E0202: An attribute inherited from %s hide this method
# pylint: disable=E0202
import logging
import time
try:
import simplejson as json
except ImportError:
import json
import hashlib
import requests
from cinderclient.apiclient import exceptions
from oslo_utils import encodeutils
from oslo_utils import importutils
_logger = logging.getLogger(__name__)
SENSITIVE_HEADERS = ('X-Auth-Token', 'X-Subject-Token',)
class HTTPClient(object):
"""This client handles sending HTTP requests to OpenStack servers.
Features:
- share authentication information between several clients to different
services (e.g., for compute and image clients);
- reissue authentication request for expired tokens;
- encode/decode JSON bodies;
- raise exceptions on HTTP errors;
- pluggable authentication;
- store authentication information in a keyring;
- store time spent for requests;
- register clients for particular services, so one can use
`http_client.identity` or `http_client.compute`;
- log requests and responses in a format that is easy to copy-and-paste
into terminal and send the same request with curl.
"""
user_agent = "cinderclient.apiclient"
def __init__(self,
auth_plugin,
region_name=None,
endpoint_type="publicURL",
original_ip=None,
verify=True,
cert=None,
timeout=None,
timings=False,
keyring_saver=None,
debug=False,
user_agent=None,
http=None):
self.auth_plugin = auth_plugin
self.endpoint_type = endpoint_type
self.region_name = region_name
self.original_ip = original_ip
self.timeout = timeout
self.verify = verify
self.cert = cert
self.keyring_saver = keyring_saver
self.debug = debug
self.user_agent = user_agent or self.user_agent
self.times = [] # [("item", starttime, endtime), ...]
self.timings = timings
# requests within the same session can reuse TCP connections from pool
self.http = http or requests.Session()
self.cached_token = None
def _safe_header(self, name, value):
if name in SENSITIVE_HEADERS:
encoded = value.encode('utf-8')
hashed = hashlib.sha1(encoded)
digested = hashed.hexdigest()
return encodeutils.safe_decode(name), "{SHA1}%s" % digested
else:
return (encodeutils.safe_decode(name),
encodeutils.safe_decode(value))
def _http_log_req(self, method, url, kwargs):
if not self.debug:
return
string_parts = [
"curl -i",
"-X '%s'" % method,
"'%s'" % url,
]
for element in kwargs['headers']:
header = ("-H '%s: %s'" %
self._safe_header(element, kwargs['headers'][element]))
string_parts.append(header)
_logger.debug("REQ: %s", " ".join(string_parts))
if 'data' in kwargs:
_logger.debug("REQ BODY: %s\n", (kwargs['data']))
def _http_log_resp(self, resp):
if not self.debug:
return
_logger.debug(
"RESP: [%s] %s\n",
resp.status_code,
resp.headers)
if resp._content_consumed:
_logger.debug(
"RESP BODY: %s\n",
resp.text)
def serialize(self, kwargs):
if kwargs.get('json') is not None:
kwargs['headers']['Content-Type'] = 'application/json'
kwargs['data'] = json.dumps(kwargs.pop('json'))
def get_timings(self):
return self.times
def reset_timings(self):
self.times = []
def request(self, method, url, **kwargs):
"""Send an http request with the specified characteristics.
Wrapper around `requests.Session.request` to handle tasks such as
setting headers, JSON encoding/decoding, and error handling.
:param method: method of HTTP request
:param url: URL of HTTP request
:param kwargs: any other parameter that can be passed to
' requests.Session.request (such as `headers`) or `json`
that will be encoded as JSON and used as `data` argument
"""
kwargs.setdefault("headers", kwargs.get("headers", {}))
kwargs["headers"]["User-Agent"] = self.user_agent
if self.original_ip:
kwargs["headers"]["Forwarded"] = "for=%s;by=%s" % (
self.original_ip, self.user_agent)
if self.timeout is not None:
kwargs.setdefault("timeout", self.timeout)
kwargs.setdefault("verify", self.verify)
if self.cert is not None:
kwargs.setdefault("cert", self.cert)
self.serialize(kwargs)
self._http_log_req(method, url, kwargs)
if self.timings:
start_time = time.time()
resp = self.http.request(method, url, **kwargs)
if self.timings:
self.times.append(("%s %s" % (method, url),
start_time, time.time()))
self._http_log_resp(resp)
if resp.status_code >= 400:
_logger.debug(
"Request returned failure status: %s",
resp.status_code)
raise exceptions.from_response(resp, method, url)
return resp
@staticmethod
def concat_url(endpoint, url):
"""Concatenate endpoint and final URL.
E.g., "http://keystone/v2.0/" and "/tokens" are concatenated to
"http://keystone/v2.0/tokens".
:param endpoint: the base URL
:param url: the final URL
"""
return "%s/%s" % (endpoint.rstrip("/"), url.strip("/"))
def client_request(self, client, method, url, **kwargs):
"""Send an http request using `client`'s endpoint and specified `url`.
If request was rejected as unauthorized (possibly because the token is
expired), issue one authorization attempt and send the request once
again.
:param client: instance of BaseClient descendant
:param method: method of HTTP request
:param url: URL of HTTP request
:param kwargs: any other parameter that can be passed to
' `HTTPClient.request`
"""
filter_args = {
"endpoint_type": client.endpoint_type or self.endpoint_type,
"service_type": client.service_type,
}
token, endpoint = (self.cached_token, client.cached_endpoint)
just_authenticated = False
if not (token and endpoint):
try:
token, endpoint = self.auth_plugin.token_and_endpoint(
**filter_args)
except exceptions.EndpointException:
pass
if not (token and endpoint):
self.authenticate()
just_authenticated = True
token, endpoint = self.auth_plugin.token_and_endpoint(
**filter_args)
if not (token and endpoint):
raise exceptions.AuthorizationFailure(
"Cannot find endpoint or token for request")
old_token_endpoint = (token, endpoint)
kwargs.setdefault("headers", {})["X-Auth-Token"] = token
self.cached_token = token
client.cached_endpoint = endpoint
# Perform the request once. If we get Unauthorized, then it
# might be because the auth token expired, so try to
# re-authenticate and try again. If it still fails, bail.
try:
return self.request(
method, self.concat_url(endpoint, url), **kwargs)
except exceptions.Unauthorized as unauth_ex:
if just_authenticated:
raise
self.cached_token = None
client.cached_endpoint = None
self.authenticate()
try:
token, endpoint = self.auth_plugin.token_and_endpoint(
**filter_args)
except exceptions.EndpointException:
raise unauth_ex
if (not (token and endpoint) or
old_token_endpoint == (token, endpoint)):
raise unauth_ex
self.cached_token = token
client.cached_endpoint = endpoint
kwargs["headers"]["X-Auth-Token"] = token
return self.request(
method, self.concat_url(endpoint, url), **kwargs)
def add_client(self, base_client_instance):
"""Add a new instance of :class:`BaseClient` descendant.
`self` will store a reference to `base_client_instance`.
Example:
>>> def test_clients():
... from keystoneclient.auth import keystone
... from openstack.common.apiclient import client
... auth = keystone.KeystoneAuthPlugin(
... username="user", password="pass", tenant_name="tenant",
... auth_url="http://auth:5000/v2.0")
... openstack_client = client.HTTPClient(auth)
... # create nova client
... from novaclient.v1_1 import client
... client.Client(openstack_client)
... # create keystone client
... from keystoneclient.v2_0 import client
... client.Client(openstack_client)
... # use them
... openstack_client.identity.tenants.list()
... openstack_client.compute.servers.list()
"""
service_type = base_client_instance.service_type
if service_type and not hasattr(self, service_type):
setattr(self, service_type, base_client_instance)
def authenticate(self):
self.auth_plugin.authenticate(self)
# Store the authentication results in the keyring for later requests
if self.keyring_saver:
self.keyring_saver.save(self)
class BaseClient(object):
"""Top-level object to access the OpenStack API.
This client uses :class:`HTTPClient` to send requests. :class:`HTTPClient`
will handle a bunch of issues such as authentication.
"""
service_type = None
endpoint_type = None # "publicURL" will be used
cached_endpoint = None
def __init__(self, http_client, extensions=None):
self.http_client = http_client
http_client.add_client(self)
# Add in any extensions...
if extensions:
for extension in extensions:
if extension.manager_class:
setattr(self, extension.name,
extension.manager_class(self))
def client_request(self, method, url, **kwargs):
return self.http_client.client_request(
self, method, url, **kwargs)
def head(self, url, **kwargs):
return self.client_request("HEAD", url, **kwargs)
def get(self, url, **kwargs):
return self.client_request("GET", url, **kwargs)
def post(self, url, **kwargs):
return self.client_request("POST", url, **kwargs)
def put(self, url, **kwargs):
return self.client_request("PUT", url, **kwargs)
def delete(self, url, **kwargs):
return self.client_request("DELETE", url, **kwargs)
def patch(self, url, **kwargs):
return self.client_request("PATCH", url, **kwargs)
@staticmethod
def get_class(api_name, version, version_map):
"""Returns the client class for the requested API version
:param api_name: the name of the API, e.g. 'compute', 'image', etc
:param version: the requested API version
:param version_map: a dict of client classes keyed by version
:rtype: a client class for the requested API version
"""
try:
client_path = version_map[str(version)]
except (KeyError, ValueError):
msg = "Invalid %s client version '%s'. must be one of: %s" % (
(api_name, version, ', '.join(version_map.keys())))
raise exceptions.UnsupportedVersion(msg)
return importutils.import_class(client_path)

View File

@ -1,442 +0,0 @@
# Copyright 2010 Jacob Kaplan-Moss
# Copyright 2011 Nebula, Inc.
# Copyright 2013 Alessio Ababilov
# Copyright 2013 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Exception definitions.
"""
import inspect
import sys
class ClientException(Exception):
"""The base exception class for all exceptions this library raises.
"""
pass
class MissingArgs(ClientException):
"""Supplied arguments are not sufficient for calling a function."""
def __init__(self, missing):
self.missing = missing
msg = "Missing argument(s): %s" % ", ".join(missing)
super(MissingArgs, self).__init__(msg)
class ValidationError(ClientException):
"""Error in validation on API client side."""
pass
class UnsupportedVersion(ClientException):
"""User is trying to use an unsupported version of the API."""
pass
class CommandError(ClientException):
"""Error in CLI tool."""
pass
class AuthorizationFailure(ClientException):
"""Cannot authorize API client."""
pass
class ConnectionRefused(ClientException):
"""Cannot connect to API service."""
pass
class AuthPluginOptionsMissing(AuthorizationFailure):
"""Auth plugin misses some options."""
def __init__(self, opt_names):
super(AuthPluginOptionsMissing, self).__init__(
"Authentication failed. Missing options: %s" %
", ".join(opt_names))
self.opt_names = opt_names
class AuthSystemNotFound(AuthorizationFailure):
"""User has specified a AuthSystem that is not installed."""
def __init__(self, auth_system):
super(AuthSystemNotFound, self).__init__(
"AuthSystemNotFound: %s" % repr(auth_system))
self.auth_system = auth_system
class NoUniqueMatch(ClientException):
"""Multiple entities found instead of one."""
pass
class EndpointException(ClientException):
"""Something is rotten in Service Catalog."""
pass
class EndpointNotFound(EndpointException):
"""Could not find requested endpoint in Service Catalog."""
pass
class AmbiguousEndpoints(EndpointException):
"""Found more than one matching endpoint in Service Catalog."""
def __init__(self, endpoints=None):
super(AmbiguousEndpoints, self).__init__(
"AmbiguousEndpoints: %s" % repr(endpoints))
self.endpoints = endpoints
class HttpError(ClientException):
"""The base exception class for all HTTP exceptions.
"""
http_status = 0
message = "HTTP Error"
def __init__(self, message=None, details=None,
response=None, request_id=None,
url=None, method=None, http_status=None):
self.http_status = http_status or self.http_status
self.message = message or self.message
self.details = details
self.request_id = request_id
self.response = response
self.url = url
self.method = method
formatted_string = "%s (HTTP %s)" % (self.message, self.http_status)
if request_id:
formatted_string += " (Request-ID: %s)" % request_id
super(HttpError, self).__init__(formatted_string)
class HTTPClientError(HttpError):
"""Client-side HTTP error.
Exception for cases in which the client seems to have erred.
"""
message = "HTTP Client Error"
class HttpServerError(HttpError):
"""Server-side HTTP error.
Exception for cases in which the server is aware that it has
erred or is incapable of performing the request.
"""
message = "HTTP Server Error"
class BadRequest(HTTPClientError):
"""HTTP 400 - Bad Request.
The request cannot be fulfilled due to bad syntax.
"""
http_status = 400
message = "Bad Request"
class Unauthorized(HTTPClientError):
"""HTTP 401 - Unauthorized.
Similar to 403 Forbidden, but specifically for use when authentication
is required and has failed or has not yet been provided.
"""
http_status = 401
message = "Unauthorized"
class PaymentRequired(HTTPClientError):
"""HTTP 402 - Payment Required.
Reserved for future use.
"""
http_status = 402
message = "Payment Required"
class Forbidden(HTTPClientError):
"""HTTP 403 - Forbidden.
The request was a valid request, but the server is refusing to respond
to it.
"""
http_status = 403
message = "Forbidden"
class NotFound(HTTPClientError):
"""HTTP 404 - Not Found.
The requested resource could not be found but may be available again
in the future.
"""
http_status = 404
message = "Not Found"
class MethodNotAllowed(HTTPClientError):
"""HTTP 405 - Method Not Allowed.
A request was made of a resource using a request method not supported
by that resource.
"""
http_status = 405
message = "Method Not Allowed"
class NotAcceptable(HTTPClientError):
"""HTTP 406 - Not Acceptable.
The requested resource is only capable of generating content not
acceptable according to the Accept headers sent in the request.
"""
http_status = 406
message = "Not Acceptable"
class ProxyAuthenticationRequired(HTTPClientError):
"""HTTP 407 - Proxy Authentication Required.
The client must first authenticate itself with the proxy.
"""
http_status = 407
message = "Proxy Authentication Required"
class RequestTimeout(HTTPClientError):
"""HTTP 408 - Request Timeout.
The server timed out waiting for the request.
"""
http_status = 408
message = "Request Timeout"
class Conflict(HTTPClientError):
"""HTTP 409 - Conflict.
Indicates that the request could not be processed because of conflict
in the request, such as an edit conflict.
"""
http_status = 409
message = "Conflict"
class Gone(HTTPClientError):
"""HTTP 410 - Gone.
Indicates that the resource requested is no longer available and will
not be available again.
"""
http_status = 410
message = "Gone"
class LengthRequired(HTTPClientError):
"""HTTP 411 - Length Required.
The request did not specify the length of its content, which is
required by the requested resource.
"""
http_status = 411
message = "Length Required"
class PreconditionFailed(HTTPClientError):
"""HTTP 412 - Precondition Failed.
The server does not meet one of the preconditions that the requester
put on the request.
"""
http_status = 412
message = "Precondition Failed"
class RequestEntityTooLarge(HTTPClientError):
"""HTTP 413 - Request Entity Too Large.
The request is larger than the server is willing or able to process.
"""
http_status = 413
message = "Request Entity Too Large"
def __init__(self, *args, **kwargs):
try:
self.retry_after = int(kwargs.pop('retry_after'))
except (KeyError, ValueError):
self.retry_after = 0
super(RequestEntityTooLarge, self).__init__(*args, **kwargs)
class RequestUriTooLong(HTTPClientError):
"""HTTP 414 - Request-URI Too Long.
The URI provided was too long for the server to process.
"""
http_status = 414
message = "Request-URI Too Long"
class UnsupportedMediaType(HTTPClientError):
"""HTTP 415 - Unsupported Media Type.
The request entity has a media type which the server or resource does
not support.
"""
http_status = 415
message = "Unsupported Media Type"
class RequestedRangeNotSatisfiable(HTTPClientError):
"""HTTP 416 - Requested Range Not Satisfiable.
The client has asked for a portion of the file, but the server cannot
supply that portion.
"""
http_status = 416
message = "Requested Range Not Satisfiable"
class ExpectationFailed(HTTPClientError):
"""HTTP 417 - Expectation Failed.
The server cannot meet the requirements of the Expect request-header field.
"""
http_status = 417
message = "Expectation Failed"
class UnprocessableEntity(HTTPClientError):
"""HTTP 422 - Unprocessable Entity.
The request was well-formed but was unable to be followed due to semantic
errors.
"""
http_status = 422
message = "Unprocessable Entity"
class InternalServerError(HttpServerError):
"""HTTP 500 - Internal Server Error.
A generic error message, given when no more specific message is suitable.
"""
http_status = 500
message = "Internal Server Error"
# NotImplemented is a python keyword.
class HttpNotImplemented(HttpServerError):
"""HTTP 501 - Not Implemented.
The server either does not recognize the request method, or it lacks
the ability to fulfill the request.
"""
http_status = 501
message = "Not Implemented"
class BadGateway(HttpServerError):
"""HTTP 502 - Bad Gateway.
The server was acting as a gateway or proxy and received an invalid
response from the upstream server.
"""
http_status = 502
message = "Bad Gateway"
class ServiceUnavailable(HttpServerError):
"""HTTP 503 - Service Unavailable.
The server is currently unavailable.
"""
http_status = 503
message = "Service Unavailable"
class GatewayTimeout(HttpServerError):
"""HTTP 504 - Gateway Timeout.
The server was acting as a gateway or proxy and did not receive a timely
response from the upstream server.
"""
http_status = 504
message = "Gateway Timeout"
class HttpVersionNotSupported(HttpServerError):
"""HTTP 505 - HttpVersion Not Supported.
The server does not support the HTTP protocol version used in the request.
"""
http_status = 505
message = "HTTP Version Not Supported"
# _code_map contains all the classes that have http_status attribute.
_code_map = dict(
(getattr(obj, 'http_status', None), obj)
for name, obj in vars(sys.modules[__name__]).items()
if inspect.isclass(obj) and getattr(obj, 'http_status', False)
)
def from_response(response, method, url):
"""Returns an instance of :class:`HttpError` or subclass based on response.
:param response: instance of `requests.Response` class
:param method: HTTP method used for request
:param url: URL used for request
"""
kwargs = {
"http_status": response.status_code,
"response": response,
"method": method,
"url": url,
"request_id": response.headers.get("x-compute-request-id"),
}
if "retry-after" in response.headers:
kwargs["retry_after"] = response.headers["retry-after"]
content_type = response.headers.get("Content-Type", "")
if content_type.startswith("application/json"):
try:
body = response.json()
except ValueError:
pass
else:
if hasattr(body, "keys"):
error = body[list(body.keys())[0]]
kwargs["message"] = error.get("message", None)
kwargs["details"] = error.get("details", None)
elif content_type.startswith("text/"):
kwargs["details"] = response.text
try:
cls = _code_map[response.status_code]
except KeyError:
if 500 <= response.status_code < 600:
cls = HttpServerError
elif 400 <= response.status_code < 500:
cls = HTTPClientError
else:
cls = HttpError
return cls(**kwargs)

View File

@ -1,427 +0,0 @@
# Copyright 2010 Jacob Kaplan-Moss
# Copyright (c) 2011 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Base utilities to build API operation managers and objects on top of.
"""
import abc
import contextlib
import hashlib
import os
import six
from six.moves.urllib import parse
from cinderclient.apiclient import base as common_base
from cinderclient import exceptions
from cinderclient import utils
# Valid sort directions and client sort keys
SORT_DIR_VALUES = ('asc', 'desc')
SORT_KEY_VALUES = ('id', 'status', 'size', 'availability_zone', 'name',
'bootable', 'created_at', 'reference')
SORT_MANAGEABLE_KEY_VALUES = ('size', 'reference')
# Mapping of client keys to actual sort keys
SORT_KEY_MAPPINGS = {'name': 'display_name'}
# Additional sort keys for resources
SORT_KEY_ADD_VALUES = {
'backups': ('data_timestamp', ),
'messages': ('resource_type', 'event_id', 'resource_uuid',
'message_level', 'guaranteed_until', 'request_id'),
}
Resource = common_base.Resource
def getid(obj):
"""
Abstracts the common pattern of allowing both an object or an object's ID
as a parameter when dealing with relationships.
"""
try:
return obj.id
except AttributeError:
return obj
class Manager(common_base.HookableMixin):
"""
Managers interact with a particular type of API (servers, flavors, images,
etc.) and provide CRUD operations for them.
"""
resource_class = None
def __init__(self, api):
self.api = api
@property
def api_version(self):
return self.api.api_version
def _list(self, url, response_key, obj_class=None, body=None,
limit=None, items=None):
resp = None
if items is None:
items = []
if body:
resp, body = self.api.client.post(url, body=body)
else:
resp, body = self.api.client.get(url)
if obj_class is None:
obj_class = self.resource_class
data = body[response_key]
# NOTE(ja): keystone returns values as list as {'values': [ ... ]}
# unlike other services which just return the list...
if isinstance(data, dict):
try:
data = data['values']
except KeyError:
pass
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]
if limit:
limit = int(limit)
margin = limit - len(items)
if margin <= len(items_new):
# If the limit is reached, return the items.
items = items + items_new[:margin]
return common_base.ListWithMeta(items, resp)
else:
items = items + items_new
else:
items = items + items_new
# It is possible that the length of the list we request is longer
# than osapi_max_limit, so we have to retrieve multiple times to
# get the complete list.
next = None
link_name = response_key + '_links'
if link_name in body:
links = body[link_name]
if links:
for link in links:
if 'rel' in link and 'next' == link['rel']:
next = link['href']
break
if next:
# As long as the 'next' link is not empty, keep requesting it
# till there is no more items.
items = self._list(next, response_key, obj_class, None,
limit, items)
return common_base.ListWithMeta(items, resp)
def _build_list_url(self, resource_type, detailed=True, search_opts=None,
marker=None, limit=None, sort_key=None, sort_dir=None,
sort=None, offset=None):
if search_opts is None:
search_opts = {}
query_params = {}
for key, val in search_opts.items():
if val:
query_params[key] = val
if marker:
query_params['marker'] = marker
if limit:
query_params['limit'] = limit
if sort:
query_params['sort'] = self._format_sort_param(sort,
resource_type)
else:
# sort_key and sort_dir deprecated in kilo, prefer sort
if sort_key:
query_params['sort_key'] = self._format_sort_key_param(
sort_key,
resource_type)
if sort_dir:
query_params['sort_dir'] = self._format_sort_dir_param(
sort_dir)
if offset:
query_params['offset'] = offset
query_params = utils.unicode_key_value_to_string(query_params)
# Transform the dict to a sequence of two-element tuples in fixed
# order, then the encoded string will be consistent in Python 2&3.
query_string = ""
if query_params:
params = sorted(query_params.items(), key=lambda x: x[0])
query_string = "?%s" % parse.urlencode(params)
detail = ""
if detailed:
detail = "/detail"
return ("/%(resource_type)s%(detail)s%(query_string)s" %
{"resource_type": resource_type, "detail": detail,
"query_string": query_string})
def _format_sort_param(self, sort, resource_type=None):
'''Formats the sort information into the sort query string parameter.
The input sort information can be any of the following:
- Comma-separated string in the form of <key[:dir]>
- List of strings in the form of <key[:dir]>
- List of either string keys, or tuples of (key, dir)
For example, the following import sort values are valid:
- 'key1:dir1,key2,key3:dir3'
- ['key1:dir1', 'key2', 'key3:dir3']
- [('key1', 'dir1'), 'key2', ('key3', dir3')]
:param sort: Input sort information
:returns: Formatted query string parameter or None
:raise ValueError: If an invalid sort direction or invalid sort key is
given
'''
if not sort:
return None
if isinstance(sort, six.string_types):
# Convert the string into a list for consistent validation
sort = [s for s in sort.split(',') if s]
sort_array = []
for sort_item in sort:
if isinstance(sort_item, tuple):
sort_key = sort_item[0]
sort_dir = sort_item[1]
else:
sort_key, _sep, sort_dir = sort_item.partition(':')
sort_key = sort_key.strip()
sort_key = self._format_sort_key_param(sort_key, resource_type)
if sort_dir:
sort_dir = sort_dir.strip()
if sort_dir not in SORT_DIR_VALUES:
msg = ('sort_dir must be one of the following: %s.'
% ', '.join(SORT_DIR_VALUES))
raise ValueError(msg)
sort_array.append('%s:%s' % (sort_key, sort_dir))
else:
sort_array.append(sort_key)
return ','.join(sort_array)
def _format_sort_key_param(self, sort_key, resource_type=None):
valid_sort_keys = SORT_KEY_VALUES
if resource_type:
add_sort_keys = SORT_KEY_ADD_VALUES.get(resource_type, None)
if add_sort_keys:
valid_sort_keys += add_sort_keys
if sort_key in valid_sort_keys:
return SORT_KEY_MAPPINGS.get(sort_key, sort_key)
msg = ('sort_key must be one of the following: %s.' %
', '.join(valid_sort_keys))
raise ValueError(msg)
def _format_sort_dir_param(self, sort_dir):
if sort_dir in SORT_DIR_VALUES:
return sort_dir
msg = ('sort_dir must be one of the following: %s.'
% ', '.join(SORT_DIR_VALUES))
raise ValueError(msg)
@contextlib.contextmanager
def completion_cache(self, cache_type, obj_class, mode):
"""
The completion cache store items that can be used for bash
autocompletion, like UUIDs or human-friendly IDs.
A resource listing will clear and repopulate the cache.
A resource create will append to the cache.
Delete is not handled because listings are assumed to be performed
often enough to keep the cache reasonably up-to-date.
"""
base_dir = utils.env('CINDERCLIENT_UUID_CACHE_DIR',
default="~/.cinderclient")
# NOTE(sirp): Keep separate UUID caches for each username + endpoint
# pair
username = utils.env('OS_USERNAME', 'CINDER_USERNAME')
url = utils.env('OS_URL', 'CINDER_URL')
uniqifier = hashlib.md5(username.encode('utf-8') +
url.encode('utf-8')).hexdigest()
cache_dir = os.path.expanduser(os.path.join(base_dir, uniqifier))
try:
os.makedirs(cache_dir, 0o755)
except OSError:
# NOTE(kiall): This is typically either permission denied while
# attempting to create the directory, or the directory
# already exists. Either way, don't fail.
pass
resource = obj_class.__name__.lower()
filename = "%s-%s-cache" % (resource, cache_type.replace('_', '-'))
path = os.path.join(cache_dir, filename)
cache_attr = "_%s_cache" % cache_type
try:
setattr(self, cache_attr, open(path, mode))
except IOError:
# NOTE(kiall): This is typically a permission denied while
# attempting to write the cache file.
pass
try:
yield
finally:
cache = getattr(self, cache_attr, None)
if cache:
cache.close()
try:
delattr(self, cache_attr)
except AttributeError:
# NOTE(kiall): If this attr is deleted by another
# operation, don't fail any way.
pass
def write_to_completion_cache(self, cache_type, val):
cache = getattr(self, "_%s_cache" % cache_type, None)
if cache:
cache.write("%s\n" % val)
def _get(self, url, response_key=None):
resp, body = self.api.client.get(url)
if response_key:
return self.resource_class(self, body[response_key], loaded=True,
resp=resp)
else:
return self.resource_class(self, body, loaded=True, resp=resp)
def _create(self, url, body, response_key, return_raw=False, **kwargs):
self.run_hooks('modify_body_for_create', body, **kwargs)
resp, body = self.api.client.post(url, body=body)
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)
def _delete(self, url):
resp, body = self.api.client.delete(url)
return common_base.TupleWithMeta((resp, body), resp)
def _update(self, url, body, response_key=None, **kwargs):
self.run_hooks('modify_body_for_update', body, **kwargs)
resp, body = self.api.client.put(url, body=body, **kwargs)
if response_key:
return self.resource_class(self, body[response_key], loaded=True,
resp=resp)
# (NOTE)ankit: In case of qos_specs.unset_keys method, None is
# returned back to the caller and in all other cases dict is
# returned but in order to return request_ids to the caller, it's
# not possible to return None so returning DictWithMeta for all cases.
body = body or {}
return common_base.DictWithMeta(body, resp)
def _get_with_base_url(self, url, response_key=None):
resp, body = self.api.client.get_with_base_url(url)
if response_key:
return [self.resource_class(self, res, loaded=True)
for res in body[response_key] if res]
else:
return self.resource_class(self, body, loaded=True)
class ManagerWithFind(six.with_metaclass(abc.ABCMeta, Manager)):
"""
Like a `Manager`, but with additional `find()`/`findall()` methods.
"""
@abc.abstractmethod
def list(self):
pass
def find(self, **kwargs):
"""
Find a single item with attributes matching ``**kwargs``.
This isn't very efficient for search options which require the
Python side filtering(e.g. 'human_id')
"""
matches = self.findall(**kwargs)
num_matches = len(matches)
if num_matches == 0:
msg = "No %s matching %s." % (self.resource_class.__name__, kwargs)
raise exceptions.NotFound(404, msg)
elif num_matches > 1:
raise exceptions.NoUniqueMatch
else:
matches[0].append_request_ids(matches.request_ids)
return matches[0]
def findall(self, **kwargs):
"""
Find all items with attributes matching ``**kwargs``.
This isn't very efficient for search options which require the
Python side filtering(e.g. 'human_id')
"""
# Want to search for all tenants here so that when attempting to delete
# that a user like admin doesn't get a failure when trying to delete
# another tenant's volume by name.
search_opts = {'all_tenants': 1}
# Pass 'name' or 'display_name' search_opts to server filtering to
# increase search performance.
if 'name' in kwargs:
search_opts['name'] = kwargs['name']
elif 'display_name' in kwargs:
search_opts['display_name'] = kwargs['display_name']
found = common_base.ListWithMeta([], None)
# list_volume is used for group query, it's not resource's property.
list_volume = kwargs.pop('list_volume', False)
searches = kwargs.items()
if list_volume:
listing = self.list(search_opts=search_opts,
list_volume=list_volume)
else:
listing = self.list(search_opts=search_opts)
found.append_request_ids(listing.request_ids)
# Not all resources attributes support filters on server side
# (e.g. 'human_id' doesn't), so when doing findall some client
# side filtering is still needed.
for obj in listing:
try:
if all(getattr(obj, attr) == value
for (attr, value) in searches):
found.append(obj)
except AttributeError:
continue
return found

View File

@ -1,787 +0,0 @@
# Copyright (c) 2011 OpenStack Foundation
# Copyright 2010 Jacob Kaplan-Moss
# Copyright 2011 Piston Cloud Computing, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
OpenStack Client interface. Handles the REST calls and responses.
"""
from __future__ import print_function
import glob
import hashlib
import imp
import itertools
import logging
import os
import pkgutil
import re
import six
from keystoneauth1 import access
from keystoneauth1 import adapter
from keystoneauth1.identity import base
from keystoneauth1 import discover
from oslo_utils import encodeutils
from oslo_utils import importutils
from oslo_utils import strutils
osprofiler_web = importutils.try_import("osprofiler.web") # noqa
import requests
import six.moves.urllib.parse as urlparse
from cinderclient import api_versions
from cinderclient import exceptions
import cinderclient.extension
from cinderclient._i18n import _
try:
from eventlet import sleep
except ImportError:
from time import sleep
try:
import json
except ImportError:
import simplejson as json
_VALID_VERSIONS = ['v1', 'v2', 'v3']
V3_SERVICE_TYPE = 'volumev3'
V2_SERVICE_TYPE = 'volumev2'
V1_SERVICE_TYPE = 'volume'
SERVICE_TYPES = {'1': V1_SERVICE_TYPE,
'2': V2_SERVICE_TYPE,
'3': V3_SERVICE_TYPE}
REQ_ID_HEADER = 'X-OpenStack-Request-ID'
# tell keystoneclient that we can ignore the /v1|v2/{project_id} component of
# the service catalog when doing discovery lookups
for svc in ('volume', 'volumev2', 'volumev3'):
discover.add_catalog_discover_hack(svc, re.compile('/v[12]/\w+/?$'), '/')
def get_server_version(url):
"""Queries the server via the naked endpoint and gets version info.
:param url: url of the cinder endpoint
:returns: APIVersion object for min and max version supported by
the server
"""
logger = logging.getLogger(__name__)
try:
scheme, netloc, path, query, frag = urlparse.urlsplit(url)
response = requests.get(scheme + '://' + netloc)
data = json.loads(response.text)
versions = data['versions']
for version in versions:
if '3.' in version['version']:
return (api_versions.APIVersion(version['min_version']),
api_versions.APIVersion(version['version']))
except exceptions.ClientException as e:
logger.warning("Error in server version query:%s\n"
"Returning APIVersion 2.0", six.text_type(e.message))
return api_versions.APIVersion("2.0"), api_versions.APIVersion("2.0")
def get_highest_client_server_version(url):
"""Returns highest supported version by client and server as a string."""
min_server, max_server = get_server_version(url)
max_client = api_versions.APIVersion(api_versions.MAX_VERSION)
return min(max_server, max_client).get_string()
def get_volume_api_from_url(url):
scheme, netloc, path, query, frag = urlparse.urlsplit(url)
components = path.split("/")
for version in _VALID_VERSIONS:
if version in components:
return version[1:]
msg = (_("Invalid url: '%(url)s'. It must include one of: %(version)s.")
% {'url': url, 'version': ', '.join(_VALID_VERSIONS)})
raise exceptions.UnsupportedVersion(msg)
def _log_request_id(logger, resp, service_name):
request_id = resp.headers.get('x-openstack-request-id')
if request_id:
logger.debug('%(method)s call to %(service_type)s for %(url)s '
'used request id %(response_request_id)s',
{'method': resp.request.method,
'service_type': service_name,
'url': resp.url, 'response_request_id': request_id})
class SessionClient(adapter.LegacyJsonAdapter):
def __init__(self, *args, **kwargs):
self.api_version = kwargs.pop('api_version', None)
self.api_version = self.api_version or api_versions.APIVersion()
self.retries = kwargs.pop('retries', 0)
self._logger = logging.getLogger(__name__)
super(SessionClient, self).__init__(*args, **kwargs)
def request(self, *args, **kwargs):
kwargs.setdefault('headers', kwargs.get('headers', {}))
api_versions.update_headers(kwargs["headers"], self.api_version)
kwargs.setdefault('authenticated', False)
# Note(tpatil): The standard call raises errors from
# keystoneauth, here we need to raise the cinderclient errors.
raise_exc = kwargs.pop('raise_exc', True)
resp, body = super(SessionClient, self).request(*args,
raise_exc=False,
**kwargs)
if raise_exc and resp.status_code >= 400:
raise exceptions.from_response(resp, body)
if not self.global_request_id:
self.global_request_id = resp.headers.get('x-openstack-request-id')
return resp, body
def _cs_request(self, url, method, **kwargs):
# this function is mostly redundant but makes compatibility easier
kwargs.setdefault('authenticated', True)
attempts = 0
while True:
attempts += 1
try:
return self.request(url, method, **kwargs)
except exceptions.OverLimit as overlim:
if attempts > self.retries or overlim.retry_after < 1:
raise
msg = "Retrying after %s seconds." % overlim.retry_after
self._logger.debug(msg)
sleep(overlim.retry_after)
def get(self, url, **kwargs):
return self._cs_request(url, 'GET', **kwargs)
def post(self, url, **kwargs):
return self._cs_request(url, 'POST', **kwargs)
def put(self, url, **kwargs):
return self._cs_request(url, 'PUT', **kwargs)
def delete(self, url, **kwargs):
return self._cs_request(url, 'DELETE', **kwargs)
def _get_base_url(self):
endpoint = self.get_endpoint()
base_url = '/'.join(endpoint.split('/')[:3]) + '/'
return base_url
def get_volume_api_version_from_endpoint(self):
try:
version = get_volume_api_from_url(self.get_endpoint())
except exceptions.UnsupportedVersion as e:
msg = (_("Service catalog returned invalid url.\n"
"%s") % six.text_type(e.message))
raise exceptions.UnsupportedVersion(msg)
return version
def authenticate(self, auth=None):
self.invalidate(auth)
return self.get_token(auth)
@property
def service_catalog(self):
# NOTE(jamielennox): This is ugly and should be deprecated.
auth = self.auth or self.session.auth
if isinstance(auth, base.BaseIdentityPlugin):
return auth.get_access(self.session).service_catalog
raise AttributeError('There is no service catalog for this type of '
'auth plugin.')
def _cs_request_base_url(self, url, method, **kwargs):
base_url = self._get_base_url()
return self._cs_request(
base_url + url,
method,
**kwargs)
def get_with_base_url(self, url, **kwargs):
return self._cs_request_base_url(url, 'GET', **kwargs)
class HTTPClient(object):
SENSITIVE_HEADERS = ('X-Auth-Token', 'X-Subject-Token',)
USER_AGENT = 'python-cinderclient'
def __init__(self, user, password, projectid, auth_url=None,
insecure=False, timeout=None, tenant_id=None,
proxy_tenant_id=None, proxy_token=None, region_name=None,
endpoint_type='publicURL', service_type=None,
service_name=None, volume_service_name=None,
bypass_url=None, retries=None,
http_log_debug=False, cacert=None,
auth_system='keystone', auth_plugin=None, api_version=None,
logger=None, user_domain_name='Default',
project_domain_name='Default', global_request_id=None):
self.user = user
self.password = password
self.projectid = projectid
self.tenant_id = tenant_id
self.api_version = api_version or api_versions.APIVersion()
self.global_request_id = global_request_id
if auth_system and auth_system != 'keystone' and not auth_plugin:
raise exceptions.AuthSystemNotFound(auth_system)
if not auth_url and auth_system and auth_system != 'keystone':
auth_url = auth_plugin.get_auth_url()
if not auth_url:
raise exceptions.EndpointNotFound()
self.auth_url = auth_url.rstrip('/') if auth_url else None
self.version = 'v1'
self.region_name = region_name
self.endpoint_type = endpoint_type
self.service_type = service_type
self.service_name = service_name
self.volume_service_name = volume_service_name
self.bypass_url = bypass_url.rstrip('/') if bypass_url else bypass_url
self.retries = int(retries or 0)
self.http_log_debug = http_log_debug
self.management_url = self.bypass_url or None
self.auth_token = None
self.proxy_token = proxy_token
self.proxy_tenant_id = proxy_tenant_id
self.timeout = timeout
self.user_domain_name = user_domain_name
self.project_domain_name = project_domain_name
if insecure:
self.verify_cert = False
else:
if cacert:
self.verify_cert = cacert
else:
self.verify_cert = True
self.auth_system = auth_system
self.auth_plugin = auth_plugin
self._logger = logger or logging.getLogger(__name__)
def _safe_header(self, name, value):
if name in HTTPClient.SENSITIVE_HEADERS:
encoded = value.encode('utf-8')
hashed = hashlib.sha1(encoded)
digested = hashed.hexdigest()
return encodeutils.safe_decode(name), "{SHA1}%s" % digested
else:
return (encodeutils.safe_decode(name),
encodeutils.safe_decode(value))
def http_log_req(self, args, kwargs):
if not self.http_log_debug:
return
string_parts = ['curl -i']
for element in args:
if element in ('GET', 'POST', 'DELETE', 'PUT'):
string_parts.append(' -X %s' % element)
else:
string_parts.append(' %s' % element)
for element in kwargs['headers']:
header = ("-H '%s: %s'" %
self._safe_header(element, kwargs['headers'][element]))
string_parts.append(header)
if 'data' in kwargs:
data = strutils.mask_password(kwargs['data'])
string_parts.append(" -d '%s'" % (data))
self._logger.debug("\nREQ: %s\n" % "".join(string_parts))
def http_log_resp(self, resp):
if not self.http_log_debug:
return
self._logger.debug(
"RESP: [%s] %s\nRESP BODY: %s\n",
resp.status_code,
resp.headers,
strutils.mask_password(resp.text))
# if service name is None then use service_type for logging
service = self.service_name or self.service_type
_log_request_id(self._logger, resp, service)
def request(self, url, method, **kwargs):
kwargs.setdefault('headers', kwargs.get('headers', {}))
kwargs['headers']['User-Agent'] = self.USER_AGENT
kwargs['headers']['Accept'] = 'application/json'
if osprofiler_web:
kwargs['headers'].update(osprofiler_web.get_trace_id_headers())
if 'body' in kwargs:
kwargs['headers']['Content-Type'] = 'application/json'
kwargs['data'] = json.dumps(kwargs.pop('body'))
api_versions.update_headers(kwargs["headers"], self.api_version)
if self.global_request_id:
kwargs['headers'].setdefault(REQ_ID_HEADER, self.global_request_id)
if self.timeout:
kwargs.setdefault('timeout', self.timeout)
self.http_log_req((url, method,), kwargs)
resp = requests.request(
method,
url,
verify=self.verify_cert,
**kwargs)
self.http_log_resp(resp)
body = None
if resp.text:
try:
body = json.loads(resp.text)
except ValueError as e:
self._logger.debug("Load http response text error: %s", e)
if resp.status_code >= 400:
raise exceptions.from_response(resp, body)
return resp, body
def _cs_request(self, url, method, **kwargs):
auth_attempts = 0
attempts = 0
backoff = 1
while True:
attempts += 1
if not self.management_url or not self.auth_token:
self.authenticate()
kwargs.setdefault('headers', {})['X-Auth-Token'] = self.auth_token
if self.projectid:
kwargs['headers']['X-Auth-Project-Id'] = self.projectid
try:
if not url.startswith(self.management_url):
url = self.management_url + url
resp, body = self.request(url, method, **kwargs)
return resp, body
except exceptions.BadRequest as e:
if attempts > self.retries:
raise
except exceptions.Unauthorized:
if auth_attempts > 0:
raise
self._logger.debug("Unauthorized, reauthenticating.")
self.management_url = self.auth_token = None
# First reauth. Discount this attempt.
attempts -= 1
auth_attempts += 1
continue
except exceptions.OverLimit as overlim:
if attempts > self.retries or overlim.retry_after < 1:
raise
msg = "Retrying after %s seconds." % overlim.retry_after
self._logger.debug(msg)
sleep(overlim.retry_after)
continue
except exceptions.ClientException as e:
if attempts > self.retries:
raise
if 500 <= e.code <= 599:
pass
else:
raise
except requests.exceptions.ConnectionError as e:
self._logger.debug("Connection error: %s" % e)
if attempts > self.retries:
msg = 'Unable to establish connection: %s' % e
raise exceptions.ConnectionError(msg)
except requests.exceptions.Timeout as e:
self._logger.debug("Timeout error: %s" % e)
if attempts > self.retries:
raise
self._logger.debug(
"Failed attempt(%s of %s), retrying in %s seconds" %
(attempts, self.retries, backoff))
sleep(backoff)
backoff *= 2
def get(self, url, **kwargs):
return self._cs_request(url, 'GET', **kwargs)
def post(self, url, **kwargs):
return self._cs_request(url, 'POST', **kwargs)
def put(self, url, **kwargs):
return self._cs_request(url, 'PUT', **kwargs)
def delete(self, url, **kwargs):
return self._cs_request(url, 'DELETE', **kwargs)
def get_volume_api_version_from_endpoint(self):
try:
version = get_volume_api_from_url(self.management_url)
except exceptions.UnsupportedVersion as e:
if self.management_url == self.bypass_url:
msg = (_("Invalid url was specified in --os-endpoint or "
"environment variable CINDERCLIENT_BYPASS_URL.\n"
"%s") % six.text_type(e.message))
else:
msg = (_("Service catalog returned invalid url.\n"
"%s") % six.text_type(e.message))
raise exceptions.UnsupportedVersion(msg)
return version
def _extract_service_catalog(self, url, resp, body, extract_token=True):
"""See what the auth service told us and process the response.
We may get redirected to another site, fail or actually get
back a service catalog with a token and our endpoints.
"""
# content must always present
if resp.status_code == 200 or resp.status_code == 201:
try:
self.auth_url = url
self.auth_ref = access.create(resp=resp, body=body)
self.service_catalog = self.auth_ref.service_catalog
if extract_token:
self.auth_token = self.auth_ref.auth_token
management_url = self.service_catalog.url_for(
region_name=self.region_name,
interface=self.endpoint_type,
service_type=self.service_type,
service_name=self.service_name)
self.management_url = management_url.rstrip('/')
return None
except exceptions.AmbiguousEndpoints:
print("Found more than one valid endpoint. Use a more "
"restrictive filter")
raise
except ValueError:
# ValueError is raised when you pass an invalid response to
# access.create. This should never happen in reality if the
# status code is 200.
raise exceptions.AuthorizationFailure()
except exceptions.EndpointNotFound:
print("Could not find any suitable endpoint. Correct region?")
raise
elif resp.status_code == 305:
return resp['location']
else:
raise exceptions.from_response(resp, body)
def _fetch_endpoints_from_auth(self, url):
"""We have a token, but don't know the final endpoint for
the region. We have to go back to the auth service and
ask again. This request requires an admin-level token
to work. The proxy token supplied could be from a low-level enduser.
We can't get this from the keystone service endpoint, we have to use
the admin endpoint.
This will overwrite our admin token with the user token.
"""
# GET ...:5001/v2.0/tokens/#####/endpoints
url = '/'.join([url, 'tokens', '%s?belongsTo=%s'
% (self.proxy_token, self.proxy_tenant_id)])
self._logger.debug("Using Endpoint URL: %s" % url)
resp, body = self.request(url, "GET",
headers={'X-Auth-Token': self.auth_token})
return self._extract_service_catalog(url, resp, body,
extract_token=False)
def set_management_url(self, url):
self.management_url = url
def authenticate(self):
magic_tuple = urlparse.urlsplit(self.auth_url)
scheme, netloc, path, query, frag = magic_tuple
port = magic_tuple.port
if port is None:
port = 80
path_parts = path.split('/')
for part in path_parts:
if len(part) > 0 and part[0] == 'v':
self.version = part
break
# TODO(sandy): Assume admin endpoint is 35357 for now.
# Ideally this is going to have to be provided by the service catalog.
new_netloc = netloc.replace(':%d' % port, ':%d' % (35357,))
admin_url = urlparse.urlunsplit((scheme, new_netloc,
path, query, frag))
auth_url = self.auth_url
if self.version == "v2.0" or self.version == "v3":
while auth_url:
if not self.auth_system or self.auth_system == 'keystone':
auth_url = self._v2_or_v3_auth(auth_url)
# Are we acting on behalf of another user via an
# existing token? If so, our actual endpoints may
# be different than that of the admin token.
if self.proxy_token:
if self.bypass_url:
self.set_management_url(self.bypass_url)
else:
self._fetch_endpoints_from_auth(admin_url)
# Since keystone no longer returns the user token
# with the endpoints any more, we need to replace
# our service account token with the user token.
self.auth_token = self.proxy_token
else:
try:
while auth_url:
auth_url = self._v1_auth(auth_url)
# In some configurations cinder makes redirection to
# v2.0 keystone endpoint. Also, new location does not contain
# real endpoint, only hostname and port.
except exceptions.AuthorizationFailure:
if auth_url.find('v2.0') < 0:
auth_url = auth_url + '/v2.0'
self._v2_or_v3_auth(auth_url)
if self.bypass_url:
self.set_management_url(self.bypass_url)
elif not self.management_url:
raise exceptions.Unauthorized('Cinder Client')
def _v1_auth(self, url):
if self.proxy_token:
raise exceptions.NoTokenLookupException()
headers = {'X-Auth-User': self.user,
'X-Auth-Key': self.password}
if self.projectid:
headers['X-Auth-Project-Id'] = self.projectid
resp, body = self.request(url, 'GET', headers=headers)
if resp.status_code in (200, 204): # in some cases we get No Content
try:
mgmt_header = 'x-server-management-url'
self.management_url = resp.headers[mgmt_header].rstrip('/')
self.auth_token = resp.headers['x-auth-token']
self.auth_url = url
except (KeyError, TypeError):
raise exceptions.AuthorizationFailure()
elif resp.status_code == 305:
return resp.headers['location']
else:
raise exceptions.from_response(resp, body)
def _v2_or_v3_auth(self, url):
"""Authenticate against a v2.0 auth service."""
if self.version == "v3":
body = {
"auth": {
"identity": {
"methods": ["password"],
"password": {"user": {
"domain": {"name": self.user_domain_name},
"name": self.user,
"password": self.password}}},
}
}
scope = {"project": {"domain": {"name": self.project_domain_name}}}
if self.projectid:
scope['project']['name'] = self.projectid
elif self.tenant_id:
scope['project']['id'] = self.tenant_id
body["auth"]["scope"] = scope
else:
body = {"auth": {
"passwordCredentials": {"username": self.user,
"password": self.password}}}
if self.projectid:
body['auth']['tenantName'] = self.projectid
elif self.tenant_id:
body['auth']['tenantId'] = self.tenant_id
self._authenticate(url, body)
def _authenticate(self, url, body):
"""Authenticate and extract the service catalog."""
if self.version == 'v3':
token_url = url + "/auth/tokens"
else:
token_url = url + "/tokens"
# Make sure we follow redirects when trying to reach Keystone
resp, body = self.request(
token_url,
"POST",
body=body,
allow_redirects=True)
return self._extract_service_catalog(url, resp, body)
def _construct_http_client(username=None, password=None, project_id=None,
auth_url=None, insecure=False, timeout=None,
proxy_tenant_id=None, proxy_token=None,
region_name=None, endpoint_type='publicURL',
service_type='volume',
service_name=None, volume_service_name=None,
bypass_url=None, retries=None,
http_log_debug=False,
auth_system='keystone', auth_plugin=None,
cacert=None, tenant_id=None,
session=None,
auth=None, api_version=None,
**kwargs):
if session:
kwargs.setdefault('user_agent', 'python-cinderclient')
kwargs.setdefault('interface', endpoint_type)
return SessionClient(session=session,
auth=auth,
service_type=service_type,
service_name=service_name,
region_name=region_name,
retries=retries,
api_version=api_version,
**kwargs)
else:
# FIXME(jamielennox): username and password are now optional. Need
# to test that they were provided in this mode.
logger = kwargs.get('logger')
return HTTPClient(username,
password,
projectid=project_id,
auth_url=auth_url,
insecure=insecure,
timeout=timeout,
tenant_id=tenant_id,
proxy_token=proxy_token,
proxy_tenant_id=proxy_tenant_id,
region_name=region_name,
endpoint_type=endpoint_type,
service_type=service_type,
service_name=service_name,
volume_service_name=volume_service_name,
bypass_url=bypass_url,
retries=retries,
http_log_debug=http_log_debug,
cacert=cacert,
auth_system=auth_system,
auth_plugin=auth_plugin,
logger=logger,
api_version=api_version
)
def _get_client_class_and_version(version):
if not isinstance(version, api_versions.APIVersion):
version = api_versions.get_api_version(version)
else:
api_versions.check_major_version(version)
if version.is_latest():
raise exceptions.UnsupportedVersion(
_("The version should be explicit, not latest."))
return version, importutils.import_class(
"cinderclient.v%s.client.Client" % version.ver_major)
def get_client_class(version):
version_map = {
'1': 'cinderclient.v1.client.Client',
'2': 'cinderclient.v2.client.Client',
'3': 'cinderclient.v3.client.Client',
}
try:
client_path = version_map[str(version)]
except (KeyError, ValueError):
msg = "Invalid client version '%s'. must be one of: %s" % (
(version, ', '.join(version_map)))
raise exceptions.UnsupportedVersion(msg)
return importutils.import_class(client_path)
def discover_extensions(version):
extensions = []
for name, module in itertools.chain(
_discover_via_python_path(),
_discover_via_contrib_path(version)):
extension = cinderclient.extension.Extension(name, module)
extensions.append(extension)
return extensions
def _discover_via_python_path():
for (module_loader, name, ispkg) in pkgutil.iter_modules():
if name.endswith('cinderclient_ext'):
if not hasattr(module_loader, 'load_module'):
# Python 2.6 compat: actually get an ImpImporter obj
module_loader = module_loader.find_module(name)
module = module_loader.load_module(name)
yield name, module
def _discover_via_contrib_path(version):
module_path = os.path.dirname(os.path.abspath(__file__))
version_str = "v%s" % version.replace('.', '_')
ext_path = os.path.join(module_path, version_str, 'contrib')
ext_glob = os.path.join(ext_path, "*.py")
for ext_path in glob.iglob(ext_glob):
name = os.path.basename(ext_path)[:-3]
if name == "__init__":
continue
module = imp.load_source(name, ext_path)
yield name, module
def Client(version, *args, **kwargs):
"""Initialize client object based on given version.
HOW-TO:
The simplest way to create a client instance is initialization with your
credentials::
.. code-block:: python
>>> from cinderclient import client
>>> cinder = client.Client(VERSION, USERNAME, PASSWORD,
... PROJECT_NAME, AUTH_URL)
Here ``VERSION`` can be a string or
``cinderclient.api_versions.APIVersion`` obj. If you prefer string value,
you can use ``1`` (deprecated now), ``2``, or ``3.X``
(where X is a microversion).
Alternatively, you can create a client instance using the keystoneclient
session API. See "The cinderclient Python API" page at
python-cinderclient's doc.
"""
api_version, client_class = _get_client_class_and_version(version)
return client_class(api_version=api_version,
*args, **kwargs)

View File

@ -1,77 +0,0 @@
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
from keystoneauth1 import loading
from keystoneauth1 import plugin
class CinderNoAuthPlugin(plugin.BaseAuthPlugin):
def __init__(self, user_id, project_id=None, roles=None, endpoint=None):
self._user_id = user_id
self._project_id = project_id if project_id else user_id
self._endpoint = endpoint
self._roles = roles
self.auth_token = '%s:%s' % (self._user_id,
self._project_id)
def get_headers(self, session, **kwargs):
return {'x-user-id': self._user_id,
'x-project-id': self._project_id,
'X-Auth-Token': self.auth_token}
def get_user_id(self, session, **kwargs):
return self._user_id
def get_project_id(self, session, **kwargs):
return self._project_id
def get_endpoint(self, session, **kwargs):
return '%s/%s' % (self._endpoint, self._project_id)
def invalidate(self):
pass
class CinderOpt(loading.Opt):
@property
def argparse_args(self):
return ['--%s' % o.name for o in self._all_opts]
@property
def argparse_default(self):
# select the first ENV that is not false-y or return None
for o in self._all_opts:
v = os.environ.get('Cinder_%s' % o.name.replace('-', '_').upper())
if v:
return v
return self.default
class CinderNoAuthLoader(loading.BaseLoader):
plugin_class = CinderNoAuthPlugin
def get_options(self):
options = super(CinderNoAuthLoader, self).get_options()
options.extend([
CinderOpt('user-id', help='User ID', required=True,
metavar="<cinder user id>"),
CinderOpt('project-id', help='Project ID',
metavar="<cinder project id>"),
CinderOpt('endpoint', help='Cinder endpoint',
dest="endpoint", required=True,
metavar="<cinder endpoint>"),
])
return options

View File

@ -1,291 +0,0 @@
# Copyright 2010 Jacob Kaplan-Moss
#
# 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.
"""
Exception definitions.
"""
from datetime import datetime
from oslo_utils import timeutils
class ResourceInErrorState(Exception):
"""When resource is in Error state"""
def __init__(self, obj, fault_msg):
msg = "'%s' resource is in the error state" % obj.__class__.__name__
if fault_msg:
msg += " due to '%s'" % fault_msg
self.message = "%s." % msg
def __str__(self):
return self.message
class TimeoutException(Exception):
"""When an action exceeds the timeout period to complete the action"""
def __init__(self, obj, action):
self.message = ("The '%(action)s' of the '%(object_name)s' exceeded "
"the timeout period." % {"action": action,
"object_name": obj.__class__.__name__})
def __str__(self):
return self.message
class UnsupportedVersion(Exception):
"""Indicates that the user is trying to use an unsupported
version of the API.
"""
pass
class UnsupportedAttribute(AttributeError):
"""Indicates that the user is trying to transmit the argument to a method,
which is not supported by selected version.
"""
def __init__(self, argument_name, start_version, end_version):
if start_version and end_version:
self.message = (
"'%(name)s' argument is only allowed for microversions "
"%(start)s - %(end)s." % {"name": argument_name,
"start": start_version.get_string(),
"end": end_version.get_string()})
elif start_version:
self.message = (
"'%(name)s' argument is only allowed since microversion "
"%(start)s." % {"name": argument_name,
"start": start_version.get_string()})
elif end_version:
self.message = (
"'%(name)s' argument is not allowed after microversion "
"%(end)s." % {"name": argument_name,
"end": end_version.get_string()})
def __str__(self):
return self.message
class InvalidAPIVersion(Exception):
pass
class CommandError(Exception):
pass
class AuthorizationFailure(Exception):
pass
class NoUniqueMatch(Exception):
pass
class AuthSystemNotFound(Exception):
"""When the user specifies an AuthSystem but not installed."""
def __init__(self, auth_system):
self.auth_system = auth_system
def __str__(self):
return "AuthSystemNotFound: %s" % repr(self.auth_system)
class NoTokenLookupException(Exception):
"""This form of authentication does not support looking up
endpoints from an existing token.
"""
pass
class EndpointNotFound(Exception):
"""Could not find Service or Region in Service Catalog."""
pass
class ConnectionError(Exception):
"""Could not open a connection to the API service."""
pass
class AmbiguousEndpoints(Exception):
"""Found more than one matching endpoint in Service Catalog."""
def __init__(self, endpoints=None):
self.endpoints = endpoints
def __str__(self):
return "AmbiguousEndpoints: %s" % repr(self.endpoints)
class ClientException(Exception):
"""
The base exception class for all exceptions this library raises.
"""
def __init__(self, code, message=None, details=None,
request_id=None, response=None):
self.code = code
# NOTE(mriedem): Use getattr on self.__class__.message since
# BaseException.message was dropped in python 3, see PEP 0352.
self.message = message or getattr(self.__class__, 'message', None)
self.details = details
self.request_id = request_id
def __str__(self):
formatted_string = "%s" % self.message
if self.code >= 100:
# HTTP codes start at 100.
formatted_string += " (HTTP %s)" % self.code
if self.request_id:
formatted_string += " (Request-ID: %s)" % self.request_id
return formatted_string
class BadRequest(ClientException):
"""
HTTP 400 - Bad request: you sent some malformed data.
"""
http_status = 400
message = "Bad request"
class Unauthorized(ClientException):
"""
HTTP 401 - Unauthorized: bad credentials.
"""
http_status = 401
message = "Unauthorized"
class Forbidden(ClientException):
"""
HTTP 403 - Forbidden: your credentials don't give you access to this
resource.
"""
http_status = 403
message = "Forbidden"
class NotFound(ClientException):
"""
HTTP 404 - Not found
"""
http_status = 404
message = "Not found"
class NotAcceptable(ClientException):
"""
HTTP 406 - Not Acceptable
"""
http_status = 406
message = "Not Acceptable"
class OverLimit(ClientException):
"""
HTTP 413 - Over limit: you're over the API limits for this time period.
"""
http_status = 413
message = "Over limit"
def __init__(self, code, message=None, details=None,
request_id=None, response=None):
super(OverLimit, self).__init__(code, message=message,
details=details, request_id=request_id,
response=response)
self.retry_after = 0
self._get_rate_limit(response)
def _get_rate_limit(self, resp):
if (resp is not None) and resp.headers:
utc_now = timeutils.utcnow()
value = resp.headers.get('Retry-After', '0')
try:
value = datetime.strptime(value, '%a, %d %b %Y %H:%M:%S %Z')
if value > utc_now:
self.retry_after = ((value - utc_now).seconds)
else:
self.retry_after = 0
except ValueError:
self.retry_after = int(value)
# NotImplemented is a python keyword.
class HTTPNotImplemented(ClientException):
"""
HTTP 501 - Not Implemented: the server does not support this operation.
"""
http_status = 501
message = "Not Implemented"
# In Python 2.4 Exception is old-style and thus doesn't have a __subclasses__()
# so we can do this:
# _code_map = dict((c.http_status, c)
# for c in ClientException.__subclasses__())
#
# Instead, we have to hardcode it:
_code_map = dict((c.http_status, c) for c in [BadRequest, Unauthorized,
Forbidden, NotFound,
NotAcceptable,
OverLimit, HTTPNotImplemented])
def from_response(response, body):
"""
Return an instance of a ClientException or subclass
based on a requests response.
Usage::
resp, body = requests.request(...)
if resp.status_code != 200:
raise exceptions.from_response(resp, resp.text)
"""
cls = _code_map.get(response.status_code, ClientException)
if response.headers:
request_id = response.headers.get('x-compute-request-id')
else:
request_id = None
if body:
message = "n/a"
details = "n/a"
if hasattr(body, 'keys'):
# Only in webob>=1.6.0
if 'message' in body:
message = body.get('message')
details = body.get('details')
else:
error = body[list(body)[0]]
message = error.get('message', message)
details = error.get('details', details)
return cls(code=response.status_code, message=message, details=details,
request_id=request_id, response=response)
else:
return cls(code=response.status_code, request_id=request_id,
message=response.reason, response=response)
class VersionNotFoundForAPIMethod(Exception):
msg_fmt = "API version '%(vers)s' is not supported on '%(method)s' method."
def __init__(self, version, method):
self.version = version
self.method = method
def __str__(self):
return self.msg_fmt % {"vers": self.version, "method": self.method}

View File

@ -1,40 +0,0 @@
# Copyright (c) 2011 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient.apiclient import base as common_base
from cinderclient import base
from cinderclient import utils
class Extension(common_base.HookableMixin):
"""Extension descriptor."""
SUPPORTED_HOOKS = ('__pre_parse_args__', '__post_parse_args__')
def __init__(self, name, module):
self.name = name
self.module = module
self._parse_extension_module()
def _parse_extension_module(self):
self.manager_class = None
for attr_name, attr_value in list(self.module.__dict__.items()):
if attr_name in self.SUPPORTED_HOOKS:
self.add_hook(attr_name, attr_value)
elif utils.safe_issubclass(attr_value, base.Manager):
self.manager_class = attr_value
def __repr__(self):
return "<Extension '%s'>" % self.name

View File

@ -1,979 +0,0 @@
# Copyright 2011-2014 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Command-line interface to the OpenStack Cinder API.
"""
from __future__ import print_function
import argparse
import getpass
import logging
import sys
import requests
import six
from keystoneauth1 import discover
from keystoneauth1 import loading
from keystoneauth1 import session
from keystoneauth1.identity import v2 as v2_auth
from keystoneauth1.identity import v3 as v3_auth
from keystoneauth1.exceptions import DiscoveryFailure
from oslo_utils import encodeutils
from oslo_utils import importutils
osprofiler_profiler = importutils.try_import("osprofiler.profiler") # noqa
import requests
import six
import six.moves.urllib.parse as urlparse
import cinderclient
from cinderclient import api_versions
from cinderclient import client
from cinderclient import exceptions as exc
from cinderclient import utils
from cinderclient import _i18n
from cinderclient._i18n import _
# Enable i18n lazy translation
_i18n.enable_lazy()
DEFAULT_MAJOR_OS_VOLUME_API_VERSION = "3"
DEFAULT_CINDER_ENDPOINT_TYPE = 'publicURL'
V1_SHELL = 'cinderclient.v1.shell'
V2_SHELL = 'cinderclient.v2.shell'
V3_SHELL = 'cinderclient.v3.shell'
HINT_HELP_MSG = (" [hint: use '--os-volume-api-version' flag to show help "
"message for proper version]")
logging.basicConfig()
logger = logging.getLogger(__name__)
class CinderClientArgumentParser(argparse.ArgumentParser):
def __init__(self, *args, **kwargs):
super(CinderClientArgumentParser, self).__init__(*args, **kwargs)
def error(self, message):
"""error(message: string)
Prints a usage message incorporating the message to stderr and
exits.
"""
self.print_usage(sys.stderr)
# FIXME(lzyeval): if changes occur in argparse.ArgParser._check_value
choose_from = ' (choose from'
progparts = self.prog.partition(' ')
self.exit(2, "error: %(errmsg)s\nTry '%(mainp)s help %(subp)s'"
" for more information.\n" %
{'errmsg': message.split(choose_from)[0],
'mainp': progparts[0],
'subp': progparts[2]})
def _get_option_tuples(self, option_string):
"""Avoid ambiguity in argument abbreviation.
The idea of this method is to override the default behaviour to
avoid ambiguity in the abbreviation feature of argparse.
In the case that the ambiguity is generated by 2 or more parameters
and only one is visible in the help and the others are with
help=argparse.SUPPRESS, the ambiguity is solved by taking the visible
one.
The use case is for parameters that are left hidden for backward
compatibility.
"""
result = super(CinderClientArgumentParser, self)._get_option_tuples(
option_string)
if len(result) > 1:
aux = [x for x in result if x[0].help != argparse.SUPPRESS]
if len(aux) == 1:
result = aux
return result
class OpenStackCinderShell(object):
def __init__(self):
self.ks_logger = None
self.client_logger = None
def get_base_parser(self):
parser = CinderClientArgumentParser(
prog='cinder',
description=__doc__.strip(),
epilog=_('Run "cinder help SUBCOMMAND" for help on a subcommand.'),
add_help=False,
formatter_class=OpenStackHelpFormatter,
)
# Global arguments
parser.add_argument('-h', '--help',
action='store_true',
help=argparse.SUPPRESS)
parser.add_argument('--version',
action='version',
version=cinderclient.__version__)
parser.add_argument('-d', '--debug',
action='store_true',
default=utils.env('CINDERCLIENT_DEBUG',
default=False),
help=_('Shows debugging output.'))
parser.add_argument('--os-auth-system',
metavar='<os-auth-system>',
dest='os_auth_type',
default=utils.env('OS_AUTH_SYSTEM'),
help=_('DEPRECATED! Use --os-auth-type. '
'Defaults to env[OS_AUTH_SYSTEM].'))
parser.add_argument('--os_auth_system',
help=argparse.SUPPRESS)
parser.add_argument('--os-auth-type',
metavar='<os-auth-type>',
dest='os_auth_type',
default=utils.env('OS_AUTH_TYPE'),
help=_('Defaults to env[OS_AUTH_TYPE].'))
parser.add_argument('--os_auth_type',
help=argparse.SUPPRESS)
parser.add_argument('--service-type',
metavar='<service-type>',
help=_('Service type. '
'For most actions, default is volume.'))
parser.add_argument('--service_type',
help=argparse.SUPPRESS)
parser.add_argument('--service-name',
metavar='<service-name>',
default=utils.env('CINDER_SERVICE_NAME'),
help=_('Service name. '
'Default=env[CINDER_SERVICE_NAME].'))
parser.add_argument('--service_name',
help=argparse.SUPPRESS)
parser.add_argument('--volume-service-name',
metavar='<volume-service-name>',
default=utils.env('CINDER_VOLUME_SERVICE_NAME'),
help=_('Volume service name. '
'Default=env[CINDER_VOLUME_SERVICE_NAME].'))
parser.add_argument('--volume_service_name',
help=argparse.SUPPRESS)
parser.add_argument('--os-endpoint-type',
metavar='<os-endpoint-type>',
default=utils.env('CINDER_ENDPOINT_TYPE',
default=utils.env('OS_ENDPOINT_TYPE',
default=DEFAULT_CINDER_ENDPOINT_TYPE)),
help=_('Endpoint type, which is publicURL or '
'internalURL. '
'Default=env[OS_ENDPOINT_TYPE] or '
'nova env[CINDER_ENDPOINT_TYPE] or %s.')
% DEFAULT_CINDER_ENDPOINT_TYPE)
parser.add_argument('--os_endpoint_type',
help=argparse.SUPPRESS)
parser.add_argument('--endpoint-type',
metavar='<endpoint-type>',
dest='os_endpoint_type',
help=_('DEPRECATED! Use --os-endpoint-type.'))
parser.add_argument('--endpoint_type',
dest='os_endpoint_type',
help=argparse.SUPPRESS)
parser.add_argument('--os-volume-api-version',
metavar='<volume-api-ver>',
default=utils.env('OS_VOLUME_API_VERSION',
default=None),
help=_('Block Storage API version. '
'Accepts X, X.Y (where X is major and Y is minor '
'part).'
'Default=env[OS_VOLUME_API_VERSION].'))
parser.add_argument('--os_volume_api_version',
help=argparse.SUPPRESS)
parser.add_argument('--bypass-url',
metavar='<bypass-url>',
dest='os_endpoint',
default=utils.env('CINDERCLIENT_BYPASS_URL',
default=utils.env('CINDER_ENDPOINT')),
help=_("DEPRECATED! Use os_endpoint. "
"Use this API endpoint instead of the "
"Service Catalog. Defaults to "
"env[CINDERCLIENT_BYPASS_URL]."))
parser.add_argument('--bypass_url',
help=argparse.SUPPRESS)
parser.add_argument('--os-endpoint',
metavar='<os-endpoint>',
dest='os_endpoint',
default=utils.env('CINDER_ENDPOINT'),
help=_("Use this API endpoint instead of the "
"Service Catalog. Defaults to "
"env[CINDER_ENDPOINT]."))
parser.add_argument('--os_endpoint',
help=argparse.SUPPRESS)
parser.add_argument('--retries',
metavar='<retries>',
type=int,
default=0,
help=_('Number of retries.'))
if osprofiler_profiler:
parser.add_argument('--profile',
metavar='HMAC_KEY',
help=_('HMAC key to use for encrypting '
'context data for performance profiling '
'of operation. This key needs to match the '
'one configured on the cinder api server. '
'Without key the profiling will not be '
'triggered even if osprofiler is enabled '
'on server side.'))
self._append_global_identity_args(parser)
return parser
def _append_global_identity_args(self, parser):
# FIXME(bklei): these are global identity (Keystone) arguments which
# should be consistent and shared by all service clients. Therefore,
# they should be provided by python-keystoneclient. We will need to
# refactor this code once this functionality is available in
# python-keystoneclient.
parser.add_argument(
'--os-auth-strategy', metavar='<auth-strategy>',
default=utils.env('OS_AUTH_STRATEGY', default='keystone'),
help=_('Authentication strategy (Env: OS_AUTH_STRATEGY'
', default keystone). For now, any other value will'
' disable the authentication.'))
parser.add_argument(
'--os_auth_strategy',
help=argparse.SUPPRESS)
parser.add_argument('--os-username',
metavar='<auth-user-name>',
default=utils.env('OS_USERNAME',
'CINDER_USERNAME'),
help=_('OpenStack user name. '
'Default=env[OS_USERNAME].'))
parser.add_argument('--os_username',
help=argparse.SUPPRESS)
parser.add_argument('--os-password',
metavar='<auth-password>',
default=utils.env('OS_PASSWORD',
'CINDER_PASSWORD'),
help=_('Password for OpenStack user. '
'Default=env[OS_PASSWORD].'))
parser.add_argument('--os_password',
help=argparse.SUPPRESS)
parser.add_argument('--os-tenant-name',
metavar='<auth-tenant-name>',
default=utils.env('OS_TENANT_NAME',
'OS_PROJECT_NAME',
'CINDER_PROJECT_ID'),
help=_('Tenant name. '
'Default=env[OS_TENANT_NAME].'))
parser.add_argument('--os_tenant_name',
help=argparse.SUPPRESS)
parser.add_argument('--os-tenant-id',
metavar='<auth-tenant-id>',
default=utils.env('OS_TENANT_ID',
'OS_PROJECT_ID',
'CINDER_TENANT_ID'),
help=_('ID for the tenant. '
'Default=env[OS_TENANT_ID].'))
parser.add_argument('--os_tenant_id',
help=argparse.SUPPRESS)
parser.add_argument('--os-auth-url',
metavar='<auth-url>',
default=utils.env('OS_AUTH_URL',
'CINDER_URL'),
help=_('URL for the authentication service. '
'Default=env[OS_AUTH_URL].'))
parser.add_argument('--os_auth_url',
help=argparse.SUPPRESS)
parser.add_argument(
'--os-user-id', metavar='<auth-user-id>',
default=utils.env('OS_USER_ID'),
help=_('Authentication user ID (Env: OS_USER_ID).'))
parser.add_argument(
'--os_user_id',
help=argparse.SUPPRESS)
parser.add_argument(
'--os-user-domain-id',
metavar='<auth-user-domain-id>',
default=utils.env('OS_USER_DOMAIN_ID'),
help=_('OpenStack user domain ID. '
'Defaults to env[OS_USER_DOMAIN_ID].'))
parser.add_argument(
'--os_user_domain_id',
help=argparse.SUPPRESS)
parser.add_argument(
'--os-user-domain-name',
metavar='<auth-user-domain-name>',
default=utils.env('OS_USER_DOMAIN_NAME'),
help=_('OpenStack user domain name. '
'Defaults to env[OS_USER_DOMAIN_NAME].'))
parser.add_argument(
'--os_user_domain_name',
help=argparse.SUPPRESS)
parser.add_argument(
'--os-project-id',
metavar='<auth-project-id>',
default=utils.env('OS_PROJECT_ID'),
help=_('Another way to specify tenant ID. '
'This option is mutually exclusive with '
' --os-tenant-id. '
'Defaults to env[OS_PROJECT_ID].'))
parser.add_argument(
'--os_project_id',
help=argparse.SUPPRESS)
parser.add_argument(
'--os-project-name',
metavar='<auth-project-name>',
default=utils.env('OS_PROJECT_NAME'),
help=_('Another way to specify tenant name. '
'This option is mutually exclusive with '
' --os-tenant-name. '
'Defaults to env[OS_PROJECT_NAME].'))
parser.add_argument(
'--os_project_name',
help=argparse.SUPPRESS)
parser.add_argument(
'--os-project-domain-id',
metavar='<auth-project-domain-id>',
default=utils.env('OS_PROJECT_DOMAIN_ID'),
help=_('Defaults to env[OS_PROJECT_DOMAIN_ID].'))
parser.add_argument(
'--os-project-domain-name',
metavar='<auth-project-domain-name>',
default=utils.env('OS_PROJECT_DOMAIN_NAME'),
help=_('Defaults to env[OS_PROJECT_DOMAIN_NAME].'))
parser.add_argument('--os-region-name',
metavar='<region-name>',
default=utils.env('OS_REGION_NAME',
'CINDER_REGION_NAME'),
help=_('Region name. '
'Default=env[OS_REGION_NAME].'))
parser.add_argument('--os_region_name',
help=argparse.SUPPRESS)
parser.add_argument(
'--os-token', metavar='<token>',
default=utils.env('OS_TOKEN'),
help=_('Defaults to env[OS_TOKEN].'))
parser.add_argument(
'--os_token',
help=argparse.SUPPRESS)
parser.add_argument(
'--os-url', metavar='<url>',
default=utils.env('OS_URL'),
help=_('Defaults to env[OS_URL].'))
parser.add_argument(
'--os_url',
help=argparse.SUPPRESS)
# Register the CLI arguments that have moved to the session object.
loading.register_session_argparse_arguments(parser)
parser.set_defaults(insecure=utils.env('CINDERCLIENT_INSECURE',
default=False))
def get_subcommand_parser(self, version, do_help=False, input_args=None):
parser = self.get_base_parser()
self.subcommands = {}
subparsers = parser.add_subparsers(metavar='<subcommand>')
if version.ver_major == 2:
actions_module = importutils.import_module(V2_SHELL)
elif version.ver_major == 3:
actions_module = importutils.import_module(V3_SHELL)
else:
actions_module = importutils.import_module(V1_SHELL)
self._find_actions(subparsers, actions_module, version, do_help,
input_args)
self._find_actions(subparsers, self, version, do_help, input_args)
for extension in self.extensions:
self._find_actions(subparsers, extension.module, version, do_help,
input_args)
self._add_bash_completion_subparser(subparsers)
return parser
def _add_bash_completion_subparser(self, subparsers):
subparser = subparsers.add_parser(
'bash_completion',
add_help=False,
formatter_class=OpenStackHelpFormatter)
self.subcommands['bash_completion'] = subparser
subparser.set_defaults(func=self.do_bash_completion)
def _build_versioned_help_message(self, start_version, end_version):
if start_version and end_version:
msg = (_(" (Supported by API versions %(start)s - %(end)s)")
% {"start": start_version.get_string(),
"end": end_version.get_string()})
elif start_version:
msg = (_(" (Supported by API version %(start)s and later)")
% {"start": start_version.get_string()})
else:
msg = (_(" (Supported until API version %(end)s)")
% {"end": end_version.get_string()})
return six.text_type(msg)
def _find_actions(self, subparsers, actions_module, version,
do_help, input_args):
for attr in (a for a in dir(actions_module) if a.startswith('do_')):
# I prefer to be hyphen-separated instead of underscores.
command = attr[3:].replace('_', '-')
callback = getattr(actions_module, attr)
desc = callback.__doc__ or ''
action_help = desc.strip().split('\n')[0]
if hasattr(callback, "versioned"):
additional_msg = ""
subs = api_versions.get_substitutions(
utils.get_function_name(callback))
if do_help:
additional_msg = self._build_versioned_help_message(
subs[0].start_version, subs[-1].end_version)
if version.is_latest():
additional_msg += HINT_HELP_MSG
subs = [versioned_method for versioned_method in subs
if version.matches(versioned_method.start_version,
versioned_method.end_version)]
if not subs:
# There is no proper versioned method.
continue
# Use the "latest" substitution.
callback = subs[-1].func
desc = callback.__doc__ or desc
action_help = desc.strip().split('\n')[0]
action_help += additional_msg
arguments = getattr(callback, 'arguments', [])
subparser = subparsers.add_parser(
command,
help=action_help,
description=desc,
add_help=False,
formatter_class=OpenStackHelpFormatter)
subparser.add_argument('-h', '--help',
action='help',
help=argparse.SUPPRESS,)
self.subcommands[command] = subparser
# NOTE(ntpttr): We get a counter for each argument in this
# command here because during the microversion check we only
# want to raise an exception if no version of the argument
# matches the current microversion. The exception will only
# be raised after the last instance of a particular argument
# fails the check.
arg_counter = dict()
for (args, kwargs) in arguments:
arg_counter[args[0]] = arg_counter.get(args[0], 0) + 1
for (args, kwargs) in arguments:
start_version = kwargs.get("start_version", None)
start_version = api_versions.APIVersion(start_version)
end_version = kwargs.get('end_version', None)
end_version = api_versions.APIVersion(end_version)
if do_help and (start_version or end_version):
kwargs["help"] = kwargs.get("help", "") + (
self._build_versioned_help_message(start_version,
end_version))
if not version.matches(start_version, end_version):
if args[0] in input_args and command == input_args[0]:
if arg_counter[args[0]] == 1:
# This is the last version of this argument,
# raise the exception.
raise exc.UnsupportedAttribute(args[0],
start_version, end_version)
arg_counter[args[0]] -= 1
continue
kw = kwargs.copy()
kw.pop("start_version", None)
kw.pop("end_version", None)
subparser.add_argument(*args, **kw)
subparser.set_defaults(func=callback)
def setup_debugging(self, debug):
if not debug:
return
streamhandler = logging.StreamHandler()
streamformat = "%(levelname)s (%(module)s:%(lineno)d) %(message)s"
streamhandler.setFormatter(logging.Formatter(streamformat))
logger.setLevel(logging.DEBUG if debug else logging.WARNING)
logger.addHandler(streamhandler)
self.client_logger = logging.getLogger(client.__name__)
ch = logging.StreamHandler()
self.client_logger.setLevel(logging.DEBUG)
self.client_logger.addHandler(ch)
if hasattr(requests, 'logging'):
requests.logging.getLogger(requests.__name__).addHandler(ch)
self.ks_logger = logging.getLogger("keystoneauth")
self.ks_logger.setLevel(logging.DEBUG)
def _delimit_metadata_args(self, argv):
"""This function adds -- separator at the appropriate spot
"""
word = '--metadata'
tmp = []
# flag is true in between metadata option and next option
metadata_options = False
if word in argv:
for arg in argv:
if arg == word:
metadata_options = True
elif metadata_options:
if arg.startswith('--'):
metadata_options = False
elif '=' not in arg:
tmp.append(u'--')
metadata_options = False
tmp.append(arg)
return tmp
else:
return argv
def main(self, argv):
# Parse args once to find version and debug settings
parser = self.get_base_parser()
(options, args) = parser.parse_known_args(argv)
self.setup_debugging(options.debug)
api_version_input = True
self.options = options
do_help = ('help' in argv) or (
'--help' in argv) or ('-h' in argv) or not argv
if not options.os_volume_api_version:
api_version = api_versions.get_api_version(
DEFAULT_MAJOR_OS_VOLUME_API_VERSION)
else:
api_version = api_versions.get_api_version(
options.os_volume_api_version)
# build available subcommands based on version
major_version_string = "%s" % api_version.ver_major
self.extensions = client.discover_extensions(major_version_string)
self._run_extension_hooks('__pre_parse_args__')
subcommand_parser = self.get_subcommand_parser(api_version,
do_help, args)
self.parser = subcommand_parser
if options.help or not argv:
subcommand_parser.print_help()
return 0
argv = self._delimit_metadata_args(argv)
args = subcommand_parser.parse_args(argv)
self._run_extension_hooks('__post_parse_args__', args)
# Short-circuit and deal with help right away.
if args.func == self.do_help:
self.do_help(args)
return 0
elif args.func == self.do_bash_completion:
self.do_bash_completion(args)
return 0
(os_username, os_password, os_tenant_name, os_auth_url,
os_region_name, os_tenant_id, endpoint_type,
service_type, service_name, volume_service_name, os_endpoint,
cacert, os_auth_type) = (
args.os_username, args.os_password,
args.os_tenant_name, args.os_auth_url,
args.os_region_name, args.os_tenant_id,
args.os_endpoint_type,
args.service_type, args.service_name,
args.volume_service_name,
args.os_endpoint, args.os_cacert,
args.os_auth_type)
auth_session = None
if os_auth_type and os_auth_type != "keystone":
auth_plugin = loading.load_auth_from_argparse_arguments(
self.options)
auth_session = loading.load_session_from_argparse_arguments(
self.options, auth=auth_plugin)
else:
auth_plugin = None
if not service_type:
service_type = client.SERVICE_TYPES[major_version_string]
# FIXME(usrleon): Here should be restrict for project id same as
# for os_username or os_password but for compatibility it is not.
# V3 stuff
project_info_provided = ((self.options.os_tenant_name or
self.options.os_tenant_id) or
(self.options.os_project_name and
(self.options.os_project_domain_name or
self.options.os_project_domain_id)) or
self.options.os_project_id)
# NOTE(e0ne): if auth_session exists it means auth plugin created
# session and we don't need to check for password and other
# authentification-related things.
if not utils.isunauthenticated(args.func) and not auth_session:
if not os_password:
# No password, If we've got a tty, try prompting for it
if hasattr(sys.stdin, 'isatty') and sys.stdin.isatty():
# Check for Ctl-D
try:
os_password = getpass.getpass('OS Password: ')
# Initialize options.os_password with password
# input from tty. It is used in _get_keystone_session.
options.os_password = os_password
except EOFError:
pass
# No password because we didn't have a tty or the
# user Ctl-D when prompted.
if not os_password:
raise exc.CommandError("You must provide a password "
"through --os-password, "
"env[OS_PASSWORD] "
"or, prompted response.")
if not project_info_provided:
raise exc.CommandError(_(
"You must provide a tenant_name, tenant_id, "
"project_id or project_name (with "
"project_domain_name or project_domain_id) via "
" --os-tenant-name (env[OS_TENANT_NAME]),"
" --os-tenant-id (env[OS_TENANT_ID]),"
" --os-project-id (env[OS_PROJECT_ID])"
" --os-project-name (env[OS_PROJECT_NAME]),"
" --os-project-domain-id "
"(env[OS_PROJECT_DOMAIN_ID])"
" --os-project-domain-name "
"(env[OS_PROJECT_DOMAIN_NAME])"
))
if not os_auth_url:
raise exc.CommandError(
"You must provide an authentication URL "
"through --os-auth-url or env[OS_AUTH_URL].")
if not project_info_provided:
raise exc.CommandError(_(
"You must provide a tenant_name, tenant_id, "
"project_id or project_name (with "
"project_domain_name or project_domain_id) via "
" --os-tenant-name (env[OS_TENANT_NAME]),"
" --os-tenant-id (env[OS_TENANT_ID]),"
" --os-project-id (env[OS_PROJECT_ID])"
" --os-project-name (env[OS_PROJECT_NAME]),"
" --os-project-domain-id "
"(env[OS_PROJECT_DOMAIN_ID])"
" --os-project-domain-name "
"(env[OS_PROJECT_DOMAIN_NAME])"
))
if not os_auth_url and not auth_plugin:
raise exc.CommandError(
"You must provide an authentication URL "
"through --os-auth-url or env[OS_AUTH_URL].")
if not auth_session:
auth_session = self._get_keystone_session()
insecure = self.options.insecure
self.cs = client.Client(
api_version, os_username,
os_password, os_tenant_name, os_auth_url,
region_name=os_region_name,
tenant_id=os_tenant_id,
endpoint_type=endpoint_type,
extensions=self.extensions,
service_type=service_type,
service_name=service_name,
volume_service_name=volume_service_name,
bypass_url=os_endpoint,
retries=options.retries,
http_log_debug=args.debug,
insecure=insecure,
cacert=cacert, auth_system=os_auth_type,
auth_plugin=auth_plugin,
session=auth_session,
logger=self.ks_logger if auth_session else self.client_logger)
try:
if not utils.isunauthenticated(args.func):
self.cs.authenticate()
except exc.Unauthorized:
raise exc.CommandError("OpenStack credentials are not valid.")
except exc.AuthorizationFailure:
raise exc.CommandError("Unable to authorize user.")
endpoint_api_version = None
# Try to get the API version from the endpoint URL. If that fails fall
# back to trying to use what the user specified via
# --os-volume-api-version or with the OS_VOLUME_API_VERSION environment
# variable. Fail safe is to use the default API setting.
try:
endpoint_api_version = \
self.cs.get_volume_api_version_from_endpoint()
except exc.UnsupportedVersion:
endpoint_api_version = options.os_volume_api_version
if api_version_input:
logger.warning("Cannot determine the API version from "
"the endpoint URL. Falling back to the "
"user-specified version: %s",
endpoint_api_version)
else:
logger.warning("Cannot determine the API version from the "
"endpoint URL or user input. Falling back "
"to the default API version: %s",
endpoint_api_version)
profile = osprofiler_profiler and options.profile
if profile:
osprofiler_profiler.init(options.profile)
try:
args.func(self.cs, args)
finally:
if profile:
trace_id = osprofiler_profiler.get().get_base_id()
print("Trace ID: %s" % trace_id)
print("To display trace use next command:\n"
"osprofiler trace show --html %s " % trace_id)
def _run_extension_hooks(self, hook_type, *args, **kwargs):
"""Runs hooks for all registered extensions."""
for extension in self.extensions:
extension.run_hooks(hook_type, *args, **kwargs)
def do_bash_completion(self, args):
"""Prints arguments for bash_completion.
Prints all commands and options to stdout so that the
cinder.bash_completion script does not have to hard code them.
"""
commands = set()
options = set()
for sc_str, sc in list(self.subcommands.items()):
commands.add(sc_str)
for option in sc._optionals._option_string_actions:
options.add(option)
commands.remove('bash-completion')
commands.remove('bash_completion')
print(' '.join(commands | options))
@utils.arg('command', metavar='<subcommand>', nargs='?',
help='Shows help for <subcommand>.')
def do_help(self, args):
"""
Shows help about this program or one of its subcommands.
"""
if args.command:
if args.command in self.subcommands:
self.subcommands[args.command].print_help()
else:
raise exc.CommandError("'%s' is not a valid subcommand" %
args.command)
else:
self.parser.print_help()
def get_v2_auth(self, v2_auth_url):
username = self.options.os_username
password = self.options.os_password
tenant_id = self.options.os_tenant_id or self.options.os_project_id
tenant_name = (self.options.os_tenant_name or
self.options.os_project_name)
return v2_auth.Password(
v2_auth_url,
username=username,
password=password,
tenant_id=tenant_id,
tenant_name=tenant_name)
def get_v3_auth(self, v3_auth_url):
username = self.options.os_username
user_id = self.options.os_user_id
user_domain_name = self.options.os_user_domain_name
user_domain_id = self.options.os_user_domain_id
password = self.options.os_password
project_id = self.options.os_project_id or self.options.os_tenant_id
project_name = (self.options.os_project_name or
self.options.os_tenant_name)
project_domain_name = self.options.os_project_domain_name
project_domain_id = self.options.os_project_domain_id
return v3_auth.Password(
v3_auth_url,
username=username,
password=password,
user_id=user_id,
user_domain_name=user_domain_name,
user_domain_id=user_domain_id,
project_id=project_id,
project_name=project_name,
project_domain_name=project_domain_name,
project_domain_id=project_domain_id,
)
def _discover_auth_versions(self, session, auth_url):
# discover the API versions the server is supporting based on the
# given URL
v2_auth_url = None
v3_auth_url = None
try:
ks_discover = discover.Discover(session=session, url=auth_url)
v2_auth_url = ks_discover.url_for('2.0')
v3_auth_url = ks_discover.url_for('3.0')
except DiscoveryFailure:
# Discovery response mismatch. Raise the error
raise
except Exception:
# Some public clouds throw some other exception or doesn't support
# discovery. In that case try to determine version from auth_url
# API version from the original URL
url_parts = urlparse.urlparse(auth_url)
(scheme, netloc, path, params, query, fragment) = url_parts
path = path.lower()
if path.startswith('/v3'):
v3_auth_url = auth_url
elif path.startswith('/v2'):
v2_auth_url = auth_url
else:
raise exc.CommandError('Unable to determine the Keystone'
' version to authenticate with '
'using the given auth_url.')
return (v2_auth_url, v3_auth_url)
def _get_keystone_session(self, **kwargs):
# first create a Keystone session
cacert = self.options.os_cacert or None
cert = self.options.os_cert or None
if cert and self.options.os_key:
cert = cert, self.options.os_key
insecure = self.options.insecure or False
if insecure:
verify = False
else:
verify = cacert or True
ks_session = session.Session(verify=verify, cert=cert)
# discover the supported keystone versions using the given url
(v2_auth_url, v3_auth_url) = self._discover_auth_versions(
session=ks_session,
auth_url=self.options.os_auth_url)
username = self.options.os_username or None
user_domain_name = self.options.os_user_domain_name or None
user_domain_id = self.options.os_user_domain_id or None
auth = None
if v3_auth_url and v2_auth_url:
# support both v2 and v3 auth. Use v3 if possible.
if username:
if user_domain_name or user_domain_id:
# use v3 auth
auth = self.get_v3_auth(v3_auth_url)
else:
# use v2 auth
auth = self.get_v2_auth(v2_auth_url)
elif v3_auth_url:
# support only v3
auth = self.get_v3_auth(v3_auth_url)
elif v2_auth_url:
# support only v2
auth = self.get_v2_auth(v2_auth_url)
else:
raise exc.CommandError('Unable to determine the Keystone version '
'to authenticate with using the given '
'auth_url.')
ks_session.auth = auth
return ks_session
# I'm picky about my shell help.
class OpenStackHelpFormatter(argparse.HelpFormatter):
def start_section(self, heading):
# Title-case the headings
heading = '%s%s' % (heading[0].upper(), heading[1:])
super(OpenStackHelpFormatter, self).start_section(heading)
def main():
try:
if sys.version_info >= (3, 0):
OpenStackCinderShell().main(sys.argv[1:])
else:
OpenStackCinderShell().main([encodeutils.safe_decode(item)
for item in sys.argv[1:]])
except KeyboardInterrupt:
print("... terminating cinder client", file=sys.stderr)
sys.exit(130)
except Exception as e:
logger.debug(e, exc_info=1)
print("ERROR: %s" % six.text_type(e), file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()

View File

@ -1,312 +0,0 @@
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import print_function
import sys
import time
from cinderclient import utils
from cinderclient import exceptions
_quota_resources = ['volumes', 'snapshots', 'gigabytes',
'backups', 'backup_gigabytes',
'per_volume_gigabytes', 'groups', ]
_quota_infos = ['Type', 'In_use', 'Reserved', 'Limit']
def print_volume_image(image):
utils.print_dict(image[1]['os-volume_upload_image'])
def poll_for_status(poll_fn, obj_id, action, final_ok_states,
poll_period=5, show_progress=True):
"""Blocks while an action occurs. Periodically shows progress."""
def print_progress(progress):
if show_progress:
msg = ('\rInstance %(action)s... %(progress)s%% complete'
% dict(action=action, progress=progress))
else:
msg = '\rInstance %(action)s...' % dict(action=action)
sys.stdout.write(msg)
sys.stdout.flush()
print()
while True:
obj = poll_fn(obj_id)
status = obj.status.lower()
progress = getattr(obj, 'progress', None) or 0
if status in final_ok_states:
print_progress(100)
print("\nFinished")
break
elif status == "error":
print("\nError %(action)s instance" % {'action': action})
break
else:
print_progress(progress)
time.sleep(poll_period)
def find_volume_snapshot(cs, snapshot):
"""Gets a volume snapshot by name or ID."""
return utils.find_resource(cs.volume_snapshots, snapshot)
def find_vtype(cs, vtype):
"""Gets a volume type by name or ID."""
return utils.find_resource(cs.volume_types, vtype)
def find_gtype(cs, gtype):
"""Gets a group type by name or ID."""
return utils.find_resource(cs.group_types, gtype)
def find_backup(cs, backup):
"""Gets a backup by name or ID."""
return utils.find_resource(cs.backups, backup)
def find_consistencygroup(cs, consistencygroup):
"""Gets a consistency group by name or ID."""
return utils.find_resource(cs.consistencygroups, consistencygroup)
def find_group(cs, group, **kwargs):
"""Gets a group by name or ID."""
kwargs['is_group'] = True
return utils.find_resource(cs.groups, group, **kwargs)
def find_cgsnapshot(cs, cgsnapshot):
"""Gets a cgsnapshot by name or ID."""
return utils.find_resource(cs.cgsnapshots, cgsnapshot)
def find_group_snapshot(cs, group_snapshot):
"""Gets a group_snapshot by name or ID."""
return utils.find_resource(cs.group_snapshots, group_snapshot)
def find_transfer(cs, transfer):
"""Gets a transfer by name or ID."""
return utils.find_resource(cs.transfers, transfer)
def find_qos_specs(cs, qos_specs):
"""Gets a qos specs by ID."""
return utils.find_resource(cs.qos_specs, qos_specs)
def find_message(cs, message):
"""Gets a message by ID."""
return utils.find_resource(cs.messages, message)
def print_volume_snapshot(snapshot):
utils.print_dict(snapshot._info)
def translate_keys(collection, convert):
for item in collection:
keys = item.__dict__
for from_key, to_key in convert:
if from_key in keys and to_key not in keys:
setattr(item, to_key, item._info[from_key])
def translate_volume_keys(collection):
convert = [('volumeType', 'volume_type'),
('os-vol-tenant-attr:tenant_id', 'tenant_id')]
translate_keys(collection, convert)
def translate_volume_snapshot_keys(collection):
convert = [('volumeId', 'volume_id')]
translate_keys(collection, convert)
def translate_availability_zone_keys(collection):
convert = [('zoneName', 'name'), ('zoneState', 'status')]
translate_keys(collection, convert)
def extract_filters(args):
filters = {}
for f in args:
if '=' in f:
(key, value) = f.split('=', 1)
if value.startswith('{') and value.endswith('}'):
value = _build_internal_dict(value[1:-1])
filters[key] = value
else:
print("WARNING: Ignoring the filter %s while showing result." % f)
return filters
def _build_internal_dict(content):
result = {}
for pair in content.split(','):
k, v = pair.split(':', 1)
result.update({k.strip(): v.strip()})
return result
def extract_metadata(args, type='user_metadata'):
metadata = {}
if type == 'image_metadata':
args_metadata = args.image_metadata
else:
args_metadata = args.metadata
for metadatum in args_metadata:
# unset doesn't require a val, so we have the if/else
if '=' in metadatum:
(key, value) = metadatum.split('=', 1)
else:
key = metadatum
value = None
metadata[key] = value
return metadata
def print_volume_type_list(vtypes):
utils.print_list(vtypes, ['ID', 'Name', 'Description', 'Is_Public'])
def print_group_type_list(gtypes):
utils.print_list(gtypes, ['ID', 'Name', 'Description'])
def print_resource_filter_list(filters):
formatter = {'Filters': lambda resource: ', '.join(resource.filters)}
utils.print_list(filters, ['Resource', 'Filters'], formatters=formatter)
def quota_show(quotas):
quotas_info_dict = utils.unicode_key_value_to_string(quotas._info)
quota_dict = {}
for resource in quotas_info_dict.keys():
good_name = False
for name in _quota_resources:
if resource.startswith(name):
good_name = True
if not good_name:
continue
quota_dict[resource] = getattr(quotas, resource, None)
utils.print_dict(quota_dict)
def quota_usage_show(quotas):
quota_list = []
quotas_info_dict = utils.unicode_key_value_to_string(quotas._info)
for resource in quotas_info_dict.keys():
good_name = False
for name in _quota_resources:
if resource.startswith(name):
good_name = True
if not good_name:
continue
quota_info = getattr(quotas, resource, None)
quota_info['Type'] = resource
quota_info = dict((k.capitalize(), v) for k, v in quota_info.items())
quota_list.append(quota_info)
utils.print_list(quota_list, _quota_infos)
def quota_update(manager, identifier, args):
updates = {}
for resource in _quota_resources:
val = getattr(args, resource, None)
if val is not None:
if args.volume_type:
resource = resource + '_%s' % args.volume_type
updates[resource] = val
if updates:
quota_show(manager.update(identifier, **updates))
def find_volume_type(cs, vtype):
"""Gets a volume type by name or ID."""
return utils.find_resource(cs.volume_types, vtype)
def find_group_type(cs, gtype):
"""Gets a group type by name or ID."""
return utils.find_resource(cs.group_types, gtype)
def print_volume_encryption_type_list(encryption_types):
"""
Lists volume encryption types.
:param encryption_types: a list of :class: VolumeEncryptionType instances
"""
utils.print_list(encryption_types, ['Volume Type ID', 'Provider',
'Cipher', 'Key Size',
'Control Location'])
def print_qos_specs(qos_specs):
# formatters defines field to be converted from unicode to string
utils.print_dict(qos_specs._info, formatters=['specs'])
def print_qos_specs_list(q_specs):
utils.print_list(q_specs, ['ID', 'Name', 'Consumer', 'specs'])
def print_qos_specs_and_associations_list(q_specs):
utils.print_list(q_specs, ['ID', 'Name', 'Consumer', 'specs'])
def print_associations_list(associations):
utils.print_list(associations, ['Association_Type', 'Name', 'ID'])
def _poll_for_status(poll_fn, obj_id, info, action, final_ok_states,
timeout_period, global_request_id=None, messages=None,
poll_period=2, status_field="status"):
"""Block while an action is being performed."""
time_elapsed = 0
while True:
time.sleep(poll_period)
time_elapsed += poll_period
obj = poll_fn(obj_id)
status = getattr(obj, status_field)
info[status_field] = status
if status:
status = status.lower()
if status in final_ok_states:
break
elif status == "error":
utils.print_dict(info)
if global_request_id:
search_opts = {
'request_id': global_request_id
}
message_list = messages.list(search_opts=search_opts)
try:
fault_msg = message_list[0].user_message
except IndexError:
fault_msg = "Unknown error. Operation failed."
raise exceptions.ResourceInErrorState(obj, fault_msg)
elif time_elapsed == timeout_period:
utils.print_dict(info)
raise exceptions.TimeoutException(obj, action)

View File

@ -1,170 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import time
import six
from tempest.lib.cli import base
from tempest.lib.cli import output_parser
from tempest.lib import exceptions
_CREDS_FILE = 'functional_creds.conf'
def credentials():
"""Retrieves credentials to run functional tests
Credentials are either read from the environment or from a config file
('functional_creds.conf'). Environment variables override those from the
config file.
The 'functional_creds.conf' file is the clean and new way to use (by
default tox 2.0 does not pass environment variables).
"""
username = os.environ.get('OS_USERNAME')
password = os.environ.get('OS_PASSWORD')
tenant_name = (os.environ.get('OS_TENANT_NAME') or
os.environ.get('OS_PROJECT_NAME'))
auth_url = os.environ.get('OS_AUTH_URL')
config = six.moves.configparser.RawConfigParser()
if config.read(_CREDS_FILE):
username = username or config.get('admin', 'user')
password = password or config.get('admin', 'pass')
tenant_name = tenant_name or config.get('admin', 'tenant')
auth_url = auth_url or config.get('auth', 'uri')
return {
'username': username,
'password': password,
'tenant_name': tenant_name,
'uri': auth_url
}
class ClientTestBase(base.ClientTestBase):
"""Cinder base class, issues calls to cinderclient.
"""
def setUp(self):
super(ClientTestBase, self).setUp()
self.clients = self._get_clients()
self.parser = output_parser
def _get_clients(self):
cli_dir = os.environ.get(
'OS_CINDERCLIENT_EXEC_DIR',
os.path.join(os.path.abspath('.'), '.tox/functional/bin'))
return base.CLIClient(cli_dir=cli_dir, **credentials())
def cinder(self, *args, **kwargs):
return self.clients.cinder(*args,
**kwargs)
def assertTableHeaders(self, output_lines, field_names):
"""Verify that output table has headers item listed in field_names.
:param output_lines: output table from cmd
:param field_names: field names from the output table of the cmd
"""
table = self.parser.table(output_lines)
headers = table['headers']
for field in field_names:
self.assertIn(field, headers)
def assert_object_details(self, expected, items):
"""Check presence of common object properties.
:param expected: expected object properties
:param items: object properties
"""
for value in expected:
self.assertIn(value, items)
def _get_property_from_output(self, output):
"""Create a dictionary from an output
:param output: the output of the cmd
"""
obj = {}
items = self.parser.listing(output)
for item in items:
obj[item['Property']] = six.text_type(item['Value'])
return obj
def object_cmd(self, object_name, cmd):
return (object_name + '-' + cmd if object_name != 'volume' else cmd)
def wait_for_object_status(self, object_name, object_id, status,
timeout=60):
"""Wait until object reaches given status.
:param object_name: object name
:param object_id: uuid4 id of an object
:param status: expected status of an object
:param timeout: timeout in seconds
"""
cmd = self.object_cmd(object_name, 'show')
start_time = time.time()
while time.time() - start_time < timeout:
if status in self.cinder(cmd, params=object_id):
break
else:
self.fail("%s %s did not reach status %s after %d seconds."
% (object_name, object_id, status, timeout))
def check_object_deleted(self, object_name, object_id, timeout=60):
"""Check that object deleted successfully.
:param object_name: object name
:param object_id: uuid4 id of an object
:param timeout: timeout in seconds
"""
cmd = self.object_cmd(object_name, 'show')
try:
start_time = time.time()
while time.time() - start_time < timeout:
if object_id not in self.cinder(cmd, params=object_id):
break
except exceptions.CommandFailed:
pass
else:
self.fail("%s %s not deleted after %d seconds."
% (object_name, object_id, timeout))
def object_create(self, object_name, params):
"""Create an object.
:param object_name: object name
:param params: parameters to cinder command
:return: object dictionary
"""
cmd = self.object_cmd(object_name, 'create')
output = self.cinder(cmd, params=params)
object = self._get_property_from_output(output)
self.addCleanup(self.object_delete, object_name, object['id'])
self.wait_for_object_status(object_name, object['id'], 'available')
return object
def object_delete(self, object_name, object_id):
"""Delete specified object by ID.
:param object_name: object name
:param object_id: uuid4 id of an object
"""
cmd = self.object_cmd(object_name, 'list')
cmd_delete = self.object_cmd(object_name, 'delete')
if object_id in self.cinder(cmd):
self.cinder(cmd_delete, params=object_id)

View File

@ -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 jenkins:jenkins $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 jenkins: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 jenkins tox -efunctional
EXIT_CODE=$?
set -e
# Collect and parse result
generate_testr_results
exit $EXIT_CODE

View File

@ -1,95 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient.tests.functional import base
class CinderVolumeTests(base.ClientTestBase):
"""Check of base cinder volume commands."""
VOLUME_PROPERTY = ('attachments', 'availability_zone', 'bootable',
'created_at', 'description', 'encrypted', 'id',
'metadata', 'name', 'size', 'status',
'user_id', 'volume_type')
def test_volume_create_delete_id(self):
"""Create and delete a volume by ID."""
volume = self.object_create('volume', params='1')
self.assert_object_details(self.VOLUME_PROPERTY, volume.keys())
self.object_delete('volume', volume['id'])
self.check_object_deleted('volume', volume['id'])
def test_volume_create_delete_name(self):
"""Create and delete a volume by name."""
volume = self.object_create('volume',
params='1 --name TestVolumeNamedCreate')
self.cinder('delete', params='TestVolumeNamedCreate')
self.check_object_deleted('volume', volume['id'])
def test_volume_show(self):
"""Show volume details."""
volume = self.object_create('volume', params='1 --name TestVolumeShow')
output = self.cinder('show', params='TestVolumeShow')
volume = self._get_property_from_output(output)
self.assertEqual('TestVolumeShow', volume['name'])
self.assert_object_details(self.VOLUME_PROPERTY, volume.keys())
self.object_delete('volume', volume['id'])
self.check_object_deleted('volume', volume['id'])
def test_volume_extend(self):
"""Extend a volume size."""
volume = self.object_create('volume',
params='1 --name TestVolumeExtend')
self.cinder('extend', params="%s %s" % (volume['id'], 2))
self.wait_for_object_status('volume', volume['id'], 'available')
output = self.cinder('show', params=volume['id'])
volume = self._get_property_from_output(output)
self.assertEqual('2', volume['size'])
self.object_delete('volume', volume['id'])
self.check_object_deleted('volume', volume['id'])
class CinderSnapshotTests(base.ClientTestBase):
"""Check of base cinder snapshot commands."""
SNAPSHOT_PROPERTY = ('created_at', 'description', 'metadata', 'id',
'name', 'size', 'status', 'volume_id')
def test_snapshot_create_and_delete(self):
"""Create a volume snapshot and then delete."""
volume = self.object_create('volume', params='1')
snapshot = self.object_create('snapshot', params=volume['id'])
self.assert_object_details(self.SNAPSHOT_PROPERTY, snapshot.keys())
self.object_delete('snapshot', snapshot['id'])
self.check_object_deleted('snapshot', snapshot['id'])
self.object_delete('volume', volume['id'])
self.check_object_deleted('volume', volume['id'])
class CinderBackupTests(base.ClientTestBase):
"""Check of base cinder backup commands."""
BACKUP_PROPERTY = ('id', 'name', 'volume_id')
def test_backup_create_and_delete(self):
"""Create a volume backup and then delete."""
volume = self.object_create('volume', params='1')
backup = self.object_create('backup', params=volume['id'])
self.assert_object_details(self.BACKUP_PROPERTY, backup.keys())
self.object_delete('volume', volume['id'])
self.check_object_deleted('volume', volume['id'])
self.object_delete('backup', backup['id'])
self.check_object_deleted('backup', backup['id'])

View File

@ -1,100 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient.tests.functional import base
class CinderClientReadOnlyTests(base.ClientTestBase):
"""Basic read-only test for cinderclient.
Simple check of base list commands, verify they
respond and include the expected headers in the
resultant table.
Not intended for testing things that require actual
resource creation/manipulation, thus the name 'read-only'.
"""
# Commands in order listed in 'cinder help'
def test_absolute_limits(self):
limits = self.cinder('absolute-limits')
self.assertTableHeaders(limits, ['Name', 'Value'])
def test_availability_zones(self):
zone_list = self.cinder('availability-zone-list')
self.assertTableHeaders(zone_list, ['Name', 'Status'])
def test_backup_list(self):
backup_list = self.cinder('backup-list')
self.assertTableHeaders(backup_list, ['ID', 'Volume ID', 'Status',
'Name', 'Size', 'Object Count',
'Container'])
def test_encryption_type_list(self):
encrypt_list = self.cinder('encryption-type-list')
self.assertTableHeaders(encrypt_list, ['Volume Type ID', 'Provider',
'Cipher', 'Key Size',
'Control Location'])
def test_endpoints(self):
out = self.cinder('endpoints')
tables = self.parser.tables(out)
for table in tables:
headers = table['headers']
self.assertGreaterEqual(2, len(headers))
self.assertEqual('Value', headers[1])
def test_extra_specs_list(self):
extra_specs_list = self.cinder('extra-specs-list')
self.assertTableHeaders(extra_specs_list, ['ID', 'Name',
'extra_specs'])
def test_list(self):
list = self.cinder('list')
self.assertTableHeaders(list, ['ID', 'Status', 'Name', 'Size',
'Volume Type', 'Bootable',
'Attached to'])
def test_qos_list(self):
qos_list = self.cinder('qos-list')
self.assertTableHeaders(qos_list, ['ID', 'Name', 'Consumer', 'specs'])
def test_rate_limits(self):
rate_limits = self.cinder('rate-limits')
self.assertTableHeaders(rate_limits, ['Verb', 'URI', 'Value', 'Remain',
'Unit', 'Next_Available'])
def test_service_list(self):
service_list = self.cinder('service-list')
self.assertTableHeaders(service_list, ['Binary', 'Host', 'Zone',
'Status', 'State',
'Updated_at'])
def test_snapshot_list(self):
snapshot_list = self.cinder('snapshot-list')
self.assertTableHeaders(snapshot_list, ['ID', 'Volume ID', 'Status',
'Name', 'Size'])
def test_transfer_list(self):
transfer_list = self.cinder('transfer-list')
self.assertTableHeaders(transfer_list, ['ID', 'Volume ID', 'Name'])
def test_type_list(self):
type_list = self.cinder('type-list')
self.assertTableHeaders(type_list, ['ID', 'Name'])
def test_list_extensions(self):
list_extensions = self.cinder('list-extensions')
self.assertTableHeaders(list_extensions, ['Name', 'Summary', 'Alias',
'Updated'])

View File

@ -1,53 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import six
from cinderclient.tests.functional import base
class CinderSnapshotTests(base.ClientTestBase):
"""Check of cinder snapshot commands."""
def setUp(self):
super(CinderSnapshotTests, self).setUp()
self.volume = self.object_create('volume', params='1')
def test_snapshot_create_description(self):
"""Test steps:
1) create volume in Setup()
2) create snapshot with description
3) check that snapshot has right description
"""
description = 'test_description'
snapshot = self.object_create('snapshot',
params='--description {0} {1}'.
format(description, self.volume['id']))
self.assertEqual(description, snapshot['description'])
self.object_delete('snapshot', snapshot['id'])
self.check_object_deleted('snapshot', snapshot['id'])
def test_snapshot_create_metadata(self):
"""Test steps:
1) create volume in Setup()
2) create snapshot with metadata
3) check that metadata complies entered
"""
snapshot = self.object_create(
'snapshot',
params='--metadata test_metadata=test_date {0}'.format(
self.volume['id']))
self.assertEqual(six.text_type({u'test_metadata': u'test_date'}),
snapshot['metadata'])
self.object_delete('snapshot', snapshot['id'])
self.check_object_deleted('snapshot', snapshot['id'])

View File

@ -1,114 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import unittest
import six
import ddt
from tempest.lib import exceptions
from cinderclient.tests.functional import base
@ddt.ddt
class CinderVolumeNegativeTests(base.ClientTestBase):
"""Check of cinder volume create commands."""
@ddt.data(
('', (r'Size is a required parameter')),
('-1', (r'Invalid volume size provided for create request')),
('0', (r'Invalid input received')),
('size', (r'invalid int value')),
('0.2', (r'invalid int value')),
('2 GB', (r'unrecognized arguments')),
('999999999', (r'VolumeSizeExceedsAvailableQuota')),
)
@ddt.unpack
def test_volume_create_with_incorrect_size(self, value, ex_text):
six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text,
self.object_create, 'volume', params=value)
class CinderVolumeTests(base.ClientTestBase):
"""Check of cinder volume create commands."""
def setUp(self):
super(CinderVolumeTests, self).setUp()
self.volume = self.object_create('volume', params='1')
def test_volume_create_from_snapshot(self):
"""Test steps:
1) create volume in Setup()
2) create snapshot
3) create volume from snapshot
4) check that volume from snapshot has been successfully created
"""
snapshot = self.object_create('snapshot', params=self.volume['id'])
volume_from_snapshot = self.object_create('volume',
params='--snapshot-id {0} 1'.
format(snapshot['id']))
self.object_delete('snapshot', snapshot['id'])
self.check_object_deleted('snapshot', snapshot['id'])
cinder_list = self.cinder('list')
self.assertIn(volume_from_snapshot['id'], cinder_list)
def test_volume_create_from_volume(self):
"""Test steps:
1) create volume in Setup()
2) create volume from volume
3) check that volume from volume has been successfully created
"""
volume_from_volume = self.object_create('volume',
params='--source-volid {0} 1'.
format(self.volume['id']))
cinder_list = self.cinder('list')
self.assertIn(volume_from_volume['id'], cinder_list)
class CinderVolumeTestsWithParameters(base.ClientTestBase):
"""Check of cinder volume create commands with parameters."""
def test_volume_create_description(self):
"""Test steps:
1) create volume with description
2) check that volume has right description
"""
volume_description = 'test_description'
volume = self.object_create('volume',
params='--description {0} 1'.
format(volume_description))
self.assertEqual(volume_description, volume['description'])
@unittest.skip("Skip until multiattach will be supported")
def test_volume_create_multiattach(self):
"""Test steps:
1) create volume and allow multiattach
2) check that multiattach is true
"""
volume = self.object_create('volume',
params='--allow-multiattach 1')
self.assertEqual('True', volume['multiattach'])
def test_volume_create_metadata(self):
"""Test steps:
1) create volume with metadata
2) check that metadata complies entered
"""
volume = self.object_create(
'volume', params='--metadata test_metadata=test_date 1')
self.assertEqual(six.text_type({u'test_metadata': u'test_date'}),
volume['metadata'])

View File

@ -1,56 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import six
import ddt
from tempest.lib import exceptions
from cinderclient.tests.functional import base
@ddt.ddt
class CinderVolumeExtendNegativeTests(base.ClientTestBase):
"""Check of cinder volume extend command."""
def setUp(self):
super(CinderVolumeExtendNegativeTests, self).setUp()
self.volume = self.object_create('volume', params='1')
@ddt.data(
('', (r'too few arguments|the following arguments are required')),
('-1', (r'New size for extend must be greater than current size')),
('0', (r'Invalid input received')),
('size', (r'invalid int value')),
('0.2', (r'invalid int value')),
('2 GB', (r'unrecognized arguments')),
('999999999', (r'VolumeSizeExceedsAvailableQuota')),
)
@ddt.unpack
def test_volume_extend_with_incorrect_size(self, value, ex_text):
six.assertRaisesRegex(
self, exceptions.CommandFailed, ex_text, self.cinder, 'extend',
params='{0} {1}'.format(self.volume['id'], value))
@ddt.data(
('', (r'too few arguments|the following arguments are required')),
('1234-1234-1234', (r'No volume with a name or ID of')),
('my_volume', (r'No volume with a name or ID of')),
('1234 1234', (r'unrecognized arguments'))
)
@ddt.unpack
def test_volume_extend_with_incorrect_volume_id(self, value, ex_text):
six.assertRaisesRegex(
self, exceptions.CommandFailed, ex_text, self.cinder, 'extend',
params='{0} 2'.format(value))

View File

@ -1,65 +0,0 @@
# Copyright 2016 FUJITSU LIMITED
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient import api_versions
from cinderclient import utils
@api_versions.wraps("3.0", "3.1")
def do_fake_action():
"""help message
This will not show up in help message
"""
return "fake_action 3.0 to 3.1"
@api_versions.wraps("3.2", "3.3")
def do_fake_action():
return "fake_action 3.2 to 3.3"
@api_versions.wraps("3.6")
@utils.arg(
'--foo',
start_version='3.7')
def do_another_fake_action():
return "another_fake_action"
@utils.arg(
'--foo',
start_version='3.1',
end_version='3.2')
@utils.arg(
'--bar',
help='bar help',
start_version='3.3',
end_version='3.4')
def do_fake_action2():
return "fake_action2"
@utils.arg(
'--foo',
help='first foo',
start_version='3.6',
end_version='3.7')
@utils.arg(
'--foo',
help='second foo',
start_version='3.8')
def do_fake_action3():
return "fake_action3"

View File

@ -1,124 +0,0 @@
# 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.
"""
A fake server that "responds" to API methods with pre-canned responses.
All of these responses come from the spec, so if for some reason the spec's
wrong the tests might raise AssertionError. I've indicated in comments the
places where actual behavior differs from the spec.
"""
from __future__ import print_function
def assert_has_keys(dict, required=None, optional=None):
required = required or []
optional = optional or []
for k in required:
try:
assert k in dict
except AssertionError:
extra_keys = set(dict).difference(set(required + optional))
raise AssertionError("found unexpected keys: %s" %
list(extra_keys))
class FakeClient(object):
def _dict_match(self, partial, real):
result = True
try:
for key, value in partial.items():
if isinstance(value, dict):
result = self._dict_match(value, real[key])
else:
assert real[key] == value
result = True
except (AssertionError, KeyError):
result = False
return result
def assert_called(self, method, url, body=None,
partial_body=None, pos=-1, **kwargs):
"""
Assert than an API method was just called.
"""
expected = (method, url)
called = self.client.callstack[pos][0:2]
assert self.client.callstack, ("Expected %s %s but no calls "
"were made." % expected)
assert expected == called, 'Expected %s %s; got %s %s' % (
expected + called)
if body is not None:
actual_body = self.client.callstack[pos][2]
assert actual_body == body, ("body mismatch. expected:\n" +
str(body) + "\n" +
"actual:\n" + str(actual_body))
if partial_body is not None:
try:
assert self._dict_match(partial_body,
self.client.callstack[pos][2])
except AssertionError:
print(self.client.callstack[pos][2])
print("does not contain")
print(partial_body)
raise
def assert_called_anytime(self, method, url, body=None, partial_body=None):
"""
Assert than an API method was called anytime in the test.
"""
expected = (method, url)
assert self.client.callstack, ("Expected %s %s but no calls "
"were made." % expected)
found = False
for entry in self.client.callstack:
if expected == entry[0:2]:
found = True
break
assert found, 'Expected %s %s; got %s' % (
expected + (self.client.callstack, ))
if body is not None:
try:
assert entry[2] == body
except AssertionError:
print(entry[2])
print("!=")
print(body)
raise
if partial_body is not None:
try:
assert self._dict_match(partial_body, entry[2])
except AssertionError:
print(entry[2])
print("does not contain")
print(partial_body)
raise
def clear_callstack(self):
self.client.callstack = []
def authenticate(self):
pass

View File

@ -1,87 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from datetime import datetime
from cinderclient.tests.unit.fixture_data import base
# FIXME(jamielennox): use timeutils from oslo
FORMAT = '%Y-%m-%d %H:%M:%S'
REQUEST_ID = 'req-test-request-id'
class Fixture(base.Fixture):
base_url = 'os-availability-zone'
def setUp(self):
super(Fixture, self).setUp()
get_availability = {
"availabilityZoneInfo": [
{
"zoneName": "zone-1",
"zoneState": {"available": True},
"hosts": None,
},
{
"zoneName": "zone-2",
"zoneState": {"available": False},
"hosts": None,
},
]
}
self.requests.register_uri(
'GET', self.url(), json=get_availability,
headers={'x-openstack-request-id': REQUEST_ID}
)
updated_1 = datetime(2012, 12, 26, 14, 45, 25, 0).strftime(FORMAT)
updated_2 = datetime(2012, 12, 26, 14, 45, 24, 0).strftime(FORMAT)
get_detail = {
"availabilityZoneInfo": [
{
"zoneName": "zone-1",
"zoneState": {"available": True},
"hosts": {
"fake_host-1": {
"cinder-volume": {
"active": True,
"available": True,
"updated_at": updated_1,
}
}
}
},
{
"zoneName": "internal",
"zoneState": {"available": True},
"hosts": {
"fake_host-1": {
"cinder-sched": {
"active": True,
"available": True,
"updated_at": updated_2,
}
}
}
},
{
"zoneName": "zone-2",
"zoneState": {"available": False},
"hosts": None,
},
]
}
self.requests.register_uri(
'GET', self.url('detail'), json=get_detail,
headers={'x-openstack-request-id': REQUEST_ID}
)

View File

@ -1,38 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import fixtures
IDENTITY_URL = 'http://identityserver:5000/v2.0'
VOLUME_URL = 'http://volume.host'
class Fixture(fixtures.Fixture):
base_url = None
json_headers = {'Content-Type': 'application/json'}
def __init__(self, requests,
volume_url=VOLUME_URL,
identity_url=IDENTITY_URL):
super(Fixture, self).__init__()
self.requests = requests
self.volume_url = volume_url
self.identity_url = identity_url
def url(self, *args):
url_args = [self.volume_url]
if self.base_url:
url_args.append(self.base_url)
return '/'.join(str(a).strip('/') for a in tuple(url_args) + args)

View File

@ -1,64 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1 import fixture
from cinderclient.tests.unit.fixture_data import base
from cinderclient.v1 import client as v1client
from cinderclient.v2 import client as v2client
class Base(base.Fixture):
def __init__(self, *args, **kwargs):
super(Base, self).__init__(*args, **kwargs)
self.token = fixture.V2Token()
self.token.set_scope()
def setUp(self):
super(Base, self).setUp()
auth_url = '%s/tokens' % self.identity_url
self.requests.register_uri('POST', auth_url,
json=self.token,
headers=self.json_headers)
class V1(Base):
def __init__(self, *args, **kwargs):
super(V1, self).__init__(*args, **kwargs)
svc = self.token.add_service('volume')
svc.add_endpoint(self.volume_url)
def new_client(self):
return v1client.Client(username='xx',
api_key='xx',
project_id='xx',
auth_url=self.identity_url)
class V2(Base):
def __init__(self, *args, **kwargs):
super(V2, self).__init__(*args, **kwargs)
svc = self.token.add_service('volumev2')
svc.add_endpoint(self.volume_url)
def new_client(self):
return v2client.Client(username='xx',
api_key='xx',
project_id='xx',
auth_url=self.identity_url)

View File

@ -1,263 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import copy
import json
from oslo_utils import uuidutils
# these are copied from python-keystoneclient tests
BASE_HOST = 'http://keystone.example.com'
BASE_URL = "%s:5000/" % BASE_HOST
UPDATED = '2013-03-06T00:00:00Z'
V2_URL = "%sv2.0" % BASE_URL
V2_DESCRIBED_BY_HTML = {'href': 'http://docs.openstack.org/api/'
'openstack-identity-service/2.0/content/',
'rel': 'describedby',
'type': 'text/html'}
V2_DESCRIBED_BY_PDF = {'href': 'http://docs.openstack.org/api/openstack-ident'
'ity-service/2.0/identity-dev-guide-2.0.pdf',
'rel': 'describedby',
'type': 'application/pdf'}
V2_VERSION = {'id': 'v2.0',
'links': [{'href': V2_URL, 'rel': 'self'},
V2_DESCRIBED_BY_HTML, V2_DESCRIBED_BY_PDF],
'status': 'stable',
'updated': UPDATED}
V3_URL = "%sv3" % BASE_URL
V3_MEDIA_TYPES = [{'base': 'application/json',
'type': 'application/vnd.openstack.identity-v3+json'},
{'base': 'application/xml',
'type': 'application/vnd.openstack.identity-v3+xml'}]
V3_VERSION = {'id': 'v3.0',
'links': [{'href': V3_URL, 'rel': 'self'}],
'media-types': V3_MEDIA_TYPES,
'status': 'stable',
'updated': UPDATED}
WRONG_VERSION_RESPONSE = {'id': 'v2.0',
'links': [V2_DESCRIBED_BY_HTML, V2_DESCRIBED_BY_PDF],
'status': 'stable',
'updated': UPDATED}
def _create_version_list(versions):
return json.dumps({'versions': {'values': versions}})
def _create_single_version(version):
return json.dumps({'version': version})
V3_VERSION_LIST = _create_version_list([V3_VERSION, V2_VERSION])
V2_VERSION_LIST = _create_version_list([V2_VERSION])
V3_VERSION_ENTRY = _create_single_version(V3_VERSION)
V2_VERSION_ENTRY = _create_single_version(V2_VERSION)
CINDER_ENDPOINT = 'http://www.cinder.com/v1'
def _get_normalized_token_data(**kwargs):
ref = copy.deepcopy(kwargs)
# normalized token data
ref['user_id'] = ref.get('user_id', uuidutils.generate_uuid(dashed=False))
ref['username'] = ref.get('username',
uuidutils.generate_uuid(dashed=False))
ref['project_id'] = ref.get('project_id',
ref.get('tenant_id', uuidutils.generate_uuid(
dashed=False)))
ref['project_name'] = ref.get('project_name',
ref.get('tenant_name',
uuidutils.generate_uuid(
dashed=False)))
ref['user_domain_id'] = ref.get('user_domain_id',
uuidutils.generate_uuid(dashed=False))
ref['user_domain_name'] = ref.get('user_domain_name',
uuidutils.generate_uuid(dashed=False))
ref['project_domain_id'] = ref.get('project_domain_id',
uuidutils.generate_uuid(dashed=False))
ref['project_domain_name'] = ref.get('project_domain_name',
uuidutils.generate_uuid(dashed=False))
ref['roles'] = ref.get('roles',
[{'name': uuidutils.generate_uuid(dashed=False),
'id': uuidutils.generate_uuid(dashed=False)}])
ref['roles_link'] = ref.get('roles_link', [])
ref['cinder_url'] = ref.get('cinder_url', CINDER_ENDPOINT)
return ref
def generate_v2_project_scoped_token(**kwargs):
"""Generate a Keystone V2 token based on auth request."""
ref = _get_normalized_token_data(**kwargs)
token = uuidutils.generate_uuid(dashed=False)
o = {'access': {'token': {'id': token,
'expires': '2099-05-22T00:02:43.941430Z',
'issued_at': '2013-05-21T00:02:43.941473Z',
'tenant': {'enabled': True,
'id': ref.get('project_id'),
'name': ref.get('project_id')
}
},
'user': {'id': ref.get('user_id'),
'name': uuidutils.generate_uuid(dashed=False),
'username': ref.get('username'),
'roles': ref.get('roles'),
'roles_links': ref.get('roles_links')
}
}}
# Add endpoint Keystone
o['access']['serviceCatalog'] = [
{
'endpoints': [
{
'publicURL': ref.get('auth_url'),
'adminURL': ref.get('auth_url'),
'internalURL': ref.get('auth_url'),
'id': uuidutils.generate_uuid(dashed=False),
'region': 'RegionOne'
}],
'endpoint_links': [],
'name': 'keystone',
'type': 'identity'
}
]
cinder_endpoint = {
'endpoints': [
{
'publicURL': 'public_' + ref.get('cinder_url'),
'internalURL': 'internal_' + ref.get('cinder_url'),
'adminURL': 'admin_' + (ref.get('auth_url') or ""),
'id': uuidutils.generate_uuid(dashed=False),
'region': 'RegionOne'
}
],
'endpoints_links': [],
'name': None,
'type': 'volumev2'
}
# Add multiple Cinder endpoints
for count in range(1, 4):
# Copy the endpoint and create a service name
endpoint_copy = copy.deepcopy(cinder_endpoint)
name = "cinder%i" % count
# Assign the service name and a unique endpoint
endpoint_copy['endpoints'][0]['publicURL'] = \
'http://%s.api.com/v2' % name
endpoint_copy['name'] = name
o['access']['serviceCatalog'].append(endpoint_copy)
return token, o
def generate_v3_project_scoped_token(**kwargs):
"""Generate a Keystone V3 token based on auth request."""
ref = _get_normalized_token_data(**kwargs)
o = {'token': {'expires_at': '2099-05-22T00:02:43.941430Z',
'issued_at': '2013-05-21T00:02:43.941473Z',
'methods': ['password'],
'project': {'id': ref.get('project_id'),
'name': ref.get('project_name'),
'domain': {'id': ref.get('project_domain_id'),
'name': ref.get(
'project_domain_name')
}
},
'user': {'id': ref.get('user_id'),
'name': ref.get('username'),
'domain': {'id': ref.get('user_domain_id'),
'name': ref.get('user_domain_name')
}
},
'roles': ref.get('roles')
}}
# we only care about Neutron and Keystone endpoints
o['token']['catalog'] = [
{'endpoints': [
{
'id': uuidutils.generate_uuid(dashed=False),
'interface': 'public',
'region': 'RegionOne',
'url': 'public_' + ref.get('cinder_url')
},
{
'id': uuidutils.generate_uuid(dashed=False),
'interface': 'internal',
'region': 'RegionOne',
'url': 'internal_' + ref.get('cinder_url')
},
{
'id': uuidutils.generate_uuid(dashed=False),
'interface': 'admin',
'region': 'RegionOne',
'url': 'admin_' + ref.get('cinder_url')
}],
'id': uuidutils.generate_uuid(dashed=False),
'type': 'network'},
{'endpoints': [
{
'id': uuidutils.generate_uuid(dashed=False),
'interface': 'public',
'region': 'RegionOne',
'url': ref.get('auth_url')
},
{
'id': uuidutils.generate_uuid(dashed=False),
'interface': 'admin',
'region': 'RegionOne',
'url': ref.get('auth_url')
}],
'id': uuidutils.generate_uuid(dashed=False),
'type': 'identity'}]
# token ID is conveyed via the X-Subject-Token header so we are generating
# one to stash there
token_id = uuidutils.generate_uuid(dashed=False)
return token_id, o
def keystone_request_callback(request, context):
context.headers['Content-Type'] = 'application/json'
if request.url == BASE_URL:
return V3_VERSION_LIST
elif request.url == BASE_URL + "/v2.0":
token_id, token_data = generate_v2_project_scoped_token()
return token_data
elif request.url.startswith("http://multiple.service.names"):
token_id, token_data = generate_v2_project_scoped_token()
return json.dumps(token_data)
elif request.url == BASE_URL + "/v3":
token_id, token_data = generate_v3_project_scoped_token()
context.headers["X-Subject-Token"] = token_id
context.status_code = 201
return token_data
elif "wrongdiscoveryresponse.discovery.com" in request.url:
return str(WRONG_VERSION_RESPONSE)
else:
context.status_code = 500
return str(WRONG_VERSION_RESPONSE)

View File

@ -1,66 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient.tests.unit.fixture_data import base
REQUEST_ID = 'req-test-request-id'
def _stub_snapshot(**kwargs):
snapshot = {
"created_at": "2012-08-28T16:30:31.000000",
"display_description": None,
"display_name": None,
"id": '11111111-1111-1111-1111-111111111111',
"size": 1,
"status": "available",
"volume_id": '00000000-0000-0000-0000-000000000000',
}
snapshot.update(kwargs)
return snapshot
class Fixture(base.Fixture):
base_url = 'snapshots'
def setUp(self):
super(Fixture, self).setUp()
snapshot_1234 = _stub_snapshot(id='1234')
self.requests.register_uri(
'GET', self.url('1234'),
json={'snapshot': snapshot_1234},
headers={'x-openstack-request-id': REQUEST_ID}
)
def action_1234(request, context):
return ''
self.requests.register_uri(
'POST', self.url('1234', 'action'),
text=action_1234, status_code=202,
headers={'x-openstack-request-id': REQUEST_ID}
)
self.requests.register_uri(
'GET', self.url('detail?limit=2&marker=1234'),
status_code=200, json={'snapshots': []},
headers={'x-openstack-request-id': REQUEST_ID}
)
self.requests.register_uri(
'GET', self.url('detail?sort=id'),
status_code=200, json={'snapshots': []},
headers={'x-openstack-request-id': REQUEST_ID}
)

View File

@ -1,261 +0,0 @@
# Copyright 2016 Mirantis
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import ddt
import mock
from cinderclient import api_versions
from cinderclient import exceptions
from cinderclient.v3 import client
from cinderclient.tests.unit import utils
from cinderclient.tests.unit import test_utils
@ddt.ddt
class APIVersionTestCase(utils.TestCase):
def test_valid_version_strings(self):
def _test_string(version, exp_major, exp_minor):
v = api_versions.APIVersion(version)
self.assertEqual(v.ver_major, exp_major)
self.assertEqual(v.ver_minor, exp_minor)
_test_string("1.1", 1, 1)
_test_string("2.10", 2, 10)
_test_string("5.234", 5, 234)
_test_string("12.5", 12, 5)
_test_string("2.0", 2, 0)
_test_string("2.200", 2, 200)
def test_null_version(self):
v = api_versions.APIVersion()
self.assertFalse(v)
def test_not_null_version(self):
v = api_versions.APIVersion('1.1')
self.assertTrue(v)
@ddt.data("2", "200", "2.1.4", "200.23.66.3", "5 .3", "5. 3", "5.03",
"02.1", "2.001", "", " 2.1", "2.1 ")
def test_invalid_version_strings(self, version_string):
self.assertRaises(exceptions.UnsupportedVersion,
api_versions.APIVersion, version_string)
def test_version_comparisons(self):
v1 = api_versions.APIVersion("2.0")
v2 = api_versions.APIVersion("2.5")
v3 = api_versions.APIVersion("5.23")
v4 = api_versions.APIVersion("2.0")
v_null = api_versions.APIVersion()
self.assertLess(v1, v2)
self.assertGreater(v3, v2)
self.assertNotEqual(v1, v2)
self.assertEqual(v1, v4)
self.assertNotEqual(v1, v_null)
self.assertEqual(v_null, v_null)
self.assertRaises(TypeError, v1.__le__, "2.1")
def test_version_matches(self):
v1 = api_versions.APIVersion("2.0")
v2 = api_versions.APIVersion("2.5")
v3 = api_versions.APIVersion("2.45")
v4 = api_versions.APIVersion("3.3")
v5 = api_versions.APIVersion("3.23")
v6 = api_versions.APIVersion("2.0")
v7 = api_versions.APIVersion("3.3")
v8 = api_versions.APIVersion("4.0")
v_null = api_versions.APIVersion()
self.assertTrue(v2.matches(v1, v3))
self.assertTrue(v2.matches(v1, v_null))
self.assertTrue(v1.matches(v6, v2))
self.assertTrue(v4.matches(v2, v7))
self.assertTrue(v4.matches(v_null, v7))
self.assertTrue(v4.matches(v_null, v8))
self.assertFalse(v1.matches(v2, v3))
self.assertFalse(v5.matches(v2, v4))
self.assertFalse(v2.matches(v3, v1))
self.assertRaises(ValueError, v_null.matches, v1, v3)
def test_get_string(self):
v1_string = "3.23"
v1 = api_versions.APIVersion(v1_string)
self.assertEqual(v1_string, v1.get_string())
self.assertRaises(ValueError,
api_versions.APIVersion().get_string)
class ManagerTest(utils.TestCase):
def test_api_version(self):
# The function manager.return_api_version has two versions,
# when called with api version 3.1 it should return the
# string '3.1' and when called with api version 3.2 or higher
# it should return the string '3.2'.
version = api_versions.APIVersion('3.1')
api = client.Client(api_version=version)
manager = test_utils.FakeManagerWithApi(api)
self.assertEqual('3.1', manager.return_api_version())
version = api_versions.APIVersion('3.2')
api = client.Client(api_version=version)
manager = test_utils.FakeManagerWithApi(api)
self.assertEqual('3.2', manager.return_api_version())
# pick up the highest version
version = api_versions.APIVersion('3.3')
api = client.Client(api_version=version)
manager = test_utils.FakeManagerWithApi(api)
self.assertEqual('3.2', manager.return_api_version())
version = api_versions.APIVersion('3.0')
api = client.Client(api_version=version)
manager = test_utils.FakeManagerWithApi(api)
# An exception will be returned here because the function
# return_api_version doesn't support version 3.0
self.assertRaises(exceptions.VersionNotFoundForAPIMethod,
manager.return_api_version)
class UpdateHeadersTestCase(utils.TestCase):
def test_api_version_is_null(self):
headers = {}
api_versions.update_headers(headers, api_versions.APIVersion())
self.assertEqual({}, headers)
def test_api_version_is_major(self):
headers = {}
api_versions.update_headers(headers, api_versions.APIVersion("7.0"))
self.assertEqual({}, headers)
def test_api_version_is_not_null(self):
api_version = api_versions.APIVersion("2.3")
headers = {}
api_versions.update_headers(headers, api_version)
self.assertEqual(
{"OpenStack-API-Version": "volume " + api_version.get_string()},
headers)
class GetAPIVersionTestCase(utils.TestCase):
def test_get_available_client_versions(self):
output = api_versions.get_available_major_versions()
self.assertNotEqual([], output)
def test_wrong_format(self):
self.assertRaises(exceptions.UnsupportedVersion,
api_versions.get_api_version, "something_wrong")
def test_wrong_major_version(self):
self.assertRaises(exceptions.UnsupportedVersion,
api_versions.get_api_version, "4")
@mock.patch("cinderclient.api_versions.get_available_major_versions")
@mock.patch("cinderclient.api_versions.APIVersion")
def test_only_major_part_is_presented(self, mock_apiversion,
mock_get_majors):
mock_get_majors.return_value = [
str(mock_apiversion.return_value.ver_major)]
version = 7
self.assertEqual(mock_apiversion.return_value,
api_versions.get_api_version(version))
mock_apiversion.assert_called_once_with("%s.0" % str(version))
@mock.patch("cinderclient.api_versions.get_available_major_versions")
@mock.patch("cinderclient.api_versions.APIVersion")
def test_major_and_minor_parts_is_presented(self, mock_apiversion,
mock_get_majors):
version = "2.7"
mock_get_majors.return_value = [
str(mock_apiversion.return_value.ver_major)]
self.assertEqual(mock_apiversion.return_value,
api_versions.get_api_version(version))
mock_apiversion.assert_called_once_with(version)
@ddt.ddt
class DiscoverVersionTestCase(utils.TestCase):
def setUp(self):
super(DiscoverVersionTestCase, self).setUp()
self.orig_max = api_versions.MAX_VERSION
self.orig_min = api_versions.MIN_VERSION or None
self.addCleanup(self._clear_fake_version)
self.fake_client = mock.MagicMock()
def _clear_fake_version(self):
api_versions.MAX_VERSION = self.orig_max
api_versions.MIN_VERSION = self.orig_min
def _mock_returned_server_version(self, server_version,
server_min_version):
version_mock = mock.MagicMock(version=server_version,
min_version=server_min_version,
status='CURRENT')
val = [version_mock]
if not server_version and not server_min_version:
val = []
self.fake_client.services.server_api_version.return_value = val
@ddt.data(
("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
("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
("3.1", "3.11", None, None, "3.7", False), # Server w/o support
("3.5", "3.5", "3.0", "3.5", "1.0", True) # Requested too old
)
@ddt.unpack
def test_microversion(self, client_min, client_max, server_min, server_max,
requested_version, exp_range, end_version=None,
ret_val=None):
if ret_val is not None:
self.fake_client.services.server_api_version.return_value = ret_val
else:
self._mock_returned_server_version(server_max, server_min)
api_versions.MAX_VERSION = client_max
api_versions.MIN_VERSION = client_min
if exp_range:
self.assertRaisesRegexp(exceptions.UnsupportedVersion,
".*range is '%s' to '%s'.*" %
(server_min, server_max),
api_versions.discover_version,
self.fake_client,
api_versions.APIVersion(requested_version))
else:
discovered_version = api_versions.discover_version(
self.fake_client,
api_versions.APIVersion(requested_version))
version = requested_version
if server_min is None and server_max is None:
version = api_versions.DEPRECATED_VERSION
elif end_version is not None:
version = end_version
self.assertEqual(version,
discovered_version.get_string())
self.assertTrue(
self.fake_client.services.server_api_version.called)
def test_get_highest_version(self):
self._mock_returned_server_version("3.14", "3.0")
highest_version = api_versions.get_highest_version(self.fake_client)
self.assertEqual("3.14", highest_version.get_string())
self.assertTrue(self.fake_client.services.server_api_version.called)

View File

@ -1,43 +0,0 @@
# Copyright 2012 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient.contrib import noauth
from cinderclient.tests.unit import utils
class CinderNoAuthPluginTest(utils.TestCase):
def setUp(self):
super(CinderNoAuthPluginTest, self).setUp()
self.plugin = noauth.CinderNoAuthPlugin('user', 'project',
endpoint='example.com')
def test_auth_token(self):
auth_token = 'user:project'
self.assertEqual(auth_token, self.plugin.auth_token)
def test_auth_token_no_project(self):
auth_token = 'user:user'
plugin = noauth.CinderNoAuthPlugin('user')
self.assertEqual(auth_token, plugin.auth_token)
def test_get_headers(self):
headers = {'x-user-id': 'user',
'x-project-id': 'project',
'X-Auth-Token': 'user:project'}
self.assertEqual(headers, self.plugin.get_headers(None))
def test_get_endpoint(self):
endpoint = 'example.com/project'
self.assertEqual(endpoint, self.plugin.get_endpoint(None))

View File

@ -1,188 +0,0 @@
# -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import mock
from requests import Response
import six
from cinderclient import api_versions
from cinderclient.apiclient import base as common_base
from cinderclient import base
from cinderclient.v3 import client
from cinderclient import exceptions
from cinderclient.v3 import volumes
from cinderclient.tests.unit import utils
from cinderclient.tests.unit import test_utils
from cinderclient.tests.unit.v1 import fakes
cs = fakes.FakeClient()
REQUEST_ID = 'req-test-request-id'
def create_response_obj_with_header():
resp = Response()
resp.headers['x-openstack-request-id'] = REQUEST_ID
resp.headers['Etag'] = 'd5103bf7b26ff0310200d110da3ed186'
resp.status_code = 200
return resp
class BaseTest(utils.TestCase):
def test_resource_repr(self):
r = base.Resource(None, dict(foo="bar", baz="spam"))
self.assertEqual("<Resource baz=spam, foo=bar>", repr(r))
self.assertNotIn("x_openstack_request_ids", repr(r))
def test_add_non_ascii_attr_to_resource(self):
info = {'gigabytes_тест': -1,
'volumes_тест': -1,
'id': 'admin'}
res = base.Resource(None, info)
for key, value in info.items():
self.assertEqual(value, getattr(res, key, None))
def test_getid(self):
self.assertEqual(4, base.getid(4))
class TmpObject(object):
id = 4
self.assertEqual(4, base.getid(TmpObject))
def test_eq(self):
# Two resources with same ID: never equal if their info is not equal
r1 = base.Resource(None, {'id': 1, 'name': 'hi'})
r2 = base.Resource(None, {'id': 1, 'name': 'hello'})
self.assertNotEqual(r1, r2)
# Two resources with same ID: equal if their info is equal
r1 = base.Resource(None, {'id': 1, 'name': 'hello'})
r2 = base.Resource(None, {'id': 1, 'name': 'hello'})
self.assertEqual(r1, r2)
# Two resources of different types: never equal
r1 = base.Resource(None, {'id': 1})
r2 = volumes.Volume(None, {'id': 1})
self.assertNotEqual(r1, r2)
# Two resources with no ID: equal if their info is equal
r1 = base.Resource(None, {'name': 'joe', 'age': 12})
r2 = base.Resource(None, {'name': 'joe', 'age': 12})
self.assertEqual(r1, r2)
def test_findall_invalid_attribute(self):
# Make sure findall with an invalid attribute doesn't cause errors.
# The following should not raise an exception.
cs.volumes.findall(vegetable='carrot')
# However, find() should raise an error
self.assertRaises(exceptions.NotFound,
cs.volumes.find,
vegetable='carrot')
def test_to_dict(self):
r1 = base.Resource(None, {'id': 1, 'name': 'hi'})
self.assertEqual({'id': 1, 'name': 'hi'}, r1.to_dict())
def test_resource_object_with_request_ids(self):
resp_obj = create_response_obj_with_header()
r = base.Resource(None, {"name": "1"}, resp=resp_obj)
self.assertEqual([REQUEST_ID], r.request_ids)
def test_api_version(self):
version = api_versions.APIVersion('3.1')
api = client.Client(api_version=version)
manager = test_utils.FakeManagerWithApi(api)
r1 = base.Resource(manager, {'id': 1})
self.assertEqual(version, r1.api_version)
@mock.patch('cinderclient.utils.unicode_key_value_to_string',
side_effect=lambda x: x)
def test_build_list_url_failed(self, fake_encode):
# NOTE(mdovgal): This test is reasonable only for py27 version,
# due to issue with parse.urlencode method only in py27
if six.PY2:
arguments = dict(resource_type = 'volumes',
search_opts = {'all_tenants': 1,
'name': u'ффф'})
manager = base.Manager(None)
self.assertRaises(UnicodeEncodeError,
manager._build_list_url,
**arguments)
def test__list_no_link(self):
api = mock.Mock()
api.client.get.return_value = (mock.sentinel.resp,
{'resp_keys': [{'name': '1'}]})
manager = test_utils.FakeManager(api)
res = manager._list(mock.sentinel.url, 'resp_keys')
api.client.get.assert_called_once_with(mock.sentinel.url)
result = [r.name for r in res]
self.assertListEqual(['1'], result)
def test__list_with_link(self):
api = mock.Mock()
api.client.get.side_effect = [
(mock.sentinel.resp,
{'resp_keys': [{'name': '1'}],
'resp_keys_links': [{'rel': 'next', 'href': mock.sentinel.u2}]}),
(mock.sentinel.resp,
{'resp_keys': [{'name': '2'}],
'resp_keys_links': [{'rel': 'next', 'href': mock.sentinel.u3}]}),
(mock.sentinel.resp,
{'resp_keys': [{'name': '3'}],
'resp_keys_links': [{'rel': 'next', 'href': None}]}),
]
manager = test_utils.FakeManager(api)
res = manager._list(mock.sentinel.url, 'resp_keys')
api.client.get.assert_has_calls([mock.call(mock.sentinel.url),
mock.call(mock.sentinel.u2),
mock.call(mock.sentinel.u3)])
result = [r.name for r in res]
self.assertListEqual(['1', '2', '3'], result)
class ListWithMetaTest(utils.TestCase):
def test_list_with_meta(self):
resp = create_response_obj_with_header()
obj = common_base.ListWithMeta([], resp)
self.assertEqual([], obj)
# Check request_ids attribute is added to obj
self.assertTrue(hasattr(obj, 'request_ids'))
self.assertEqual([REQUEST_ID], obj.request_ids)
class DictWithMetaTest(utils.TestCase):
def test_dict_with_meta(self):
resp = create_response_obj_with_header()
obj = common_base.DictWithMeta([], resp)
self.assertEqual({}, obj)
# Check request_ids attribute is added to obj
self.assertTrue(hasattr(obj, 'request_ids'))
self.assertEqual([REQUEST_ID], obj.request_ids)
class TupleWithMetaTest(utils.TestCase):
def test_tuple_with_meta(self):
resp = create_response_obj_with_header()
obj = common_base.TupleWithMeta((), resp)
self.assertEqual((), obj)
# Check request_ids attribute is added to obj
self.assertTrue(hasattr(obj, 'request_ids'))
self.assertEqual([REQUEST_ID], obj.request_ids)

View File

@ -1,357 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import logging
import ddt
import fixtures
from keystoneauth1 import adapter
from keystoneauth1 import exceptions as keystone_exception
import mock
from oslo_serialization import jsonutils
import six
import cinderclient.client
import cinderclient.v1.client
import cinderclient.v2.client
from cinderclient import api_versions
from cinderclient import exceptions
from cinderclient import utils
from cinderclient.tests.unit import utils
from cinderclient.tests.unit.v3 import fakes
class ClientTest(utils.TestCase):
def test_get_client_class_v1(self):
output = cinderclient.client.get_client_class('1')
self.assertEqual(cinderclient.v1.client.Client, output)
def test_get_client_class_v2(self):
output = cinderclient.client.get_client_class('2')
self.assertEqual(cinderclient.v2.client.Client, output)
def test_get_client_class_unknown(self):
self.assertRaises(cinderclient.exceptions.UnsupportedVersion,
cinderclient.client.get_client_class, '0')
@mock.patch.object(cinderclient.client.HTTPClient, '__init__')
@mock.patch('cinderclient.client.SessionClient')
def test_construct_http_client_endpoint_url(
self, session_mock, httpclient_mock):
os_endpoint = 'http://example.com/'
httpclient_mock.return_value = None
cinderclient.client._construct_http_client(
bypass_url=os_endpoint)
self.assertTrue(httpclient_mock.called)
self.assertEqual(os_endpoint,
httpclient_mock.call_args[1].get('bypass_url'))
session_mock.assert_not_called()
def test_log_req(self):
self.logger = self.useFixture(
fixtures.FakeLogger(
format="%(message)s",
level=logging.DEBUG,
nuke_handlers=True
)
)
kwargs = {
'headers': {"X-Foo": "bar"},
'data': ('{"auth": {"tenantName": "fakeService",'
' "passwordCredentials": {"username": "fakeUser",'
' "password": "fakePassword"}}}')
}
cs = cinderclient.client.HTTPClient("user", None, None,
"http://127.0.0.1:5000")
cs.http_log_debug = True
cs.http_log_req('PUT', kwargs)
output = self.logger.output.split('\n')
self.assertNotIn("fakePassword", output[1])
self.assertIn("fakeUser", output[1])
def test_versions(self):
v1_url = 'http://fakeurl/v1/tenants'
v2_url = 'http://fakeurl/v2/tenants'
unknown_url = 'http://fakeurl/v9/tenants'
self.assertEqual('1',
cinderclient.client.get_volume_api_from_url(v1_url))
self.assertEqual('2',
cinderclient.client.get_volume_api_from_url(v2_url))
self.assertRaises(cinderclient.exceptions.UnsupportedVersion,
cinderclient.client.get_volume_api_from_url,
unknown_url)
@mock.patch('cinderclient.client.SessionClient.get_endpoint')
def test_get_base_url(self, mock_get_endpoint):
url = 'http://192.168.122.104:8776/v3/de50d1f33a38415fadfd3e1dea28f4d3'
mock_get_endpoint.return_value = url
cs = cinderclient.client.SessionClient(self, api_version='3.0')
self.assertEqual('http://192.168.122.104:8776/', cs._get_base_url())
@mock.patch.object(adapter.Adapter, 'request')
@mock.patch.object(exceptions, 'from_response')
def test_sessionclient_request_method(
self, mock_from_resp, mock_request):
kwargs = {
"body": {
"volume": {
"status": "creating",
"imageRef": "username",
"attach_status": "detached"
},
"authenticated": "True"
}
}
resp = {
"text": {
"volume": {
"status": "creating",
"id": "431253c0-e203-4da2-88df-60c756942aaf",
"size": 1
}
},
"code": 202
}
request_id = "req-f551871a-4950-4225-9b2c-29a14c8f075e"
mock_response = utils.TestResponse({
"status_code": 202,
"text": six.b(json.dumps(resp)),
"headers": {"x-openstack-request-id": request_id},
})
# 'request' method of Adaptor will return 202 response
mock_request.return_value = mock_response
session_client = cinderclient.client.SessionClient(session=mock.Mock())
response, body = session_client.request(mock.sentinel.url,
'POST', **kwargs)
self.assertIsNotNone(session_client._logger)
# In this case, from_response method will not get called
# because response status_code is < 400
self.assertEqual(202, response.status_code)
self.assertFalse(mock_from_resp.called)
@mock.patch.object(adapter.Adapter, 'request')
def test_sessionclient_request_method_raises_badrequest(
self, mock_request):
kwargs = {
"body": {
"volume": {
"status": "creating",
"imageRef": "username",
"attach_status": "detached"
},
"authenticated": "True"
}
}
resp = {
"badRequest": {
"message": "Invalid image identifier or unable to access "
"requested image.",
"code": 400
}
}
mock_response = utils.TestResponse({
"status_code": 400,
"text": six.b(json.dumps(resp)),
})
# 'request' method of Adaptor will return 400 response
mock_request.return_value = mock_response
session_client = cinderclient.client.SessionClient(
session=mock.Mock())
# 'from_response' method will raise BadRequest because
# resp.status_code is 400
self.assertRaises(exceptions.BadRequest, session_client.request,
mock.sentinel.url, 'POST', **kwargs)
self.assertIsNotNone(session_client._logger)
@mock.patch.object(adapter.Adapter, 'request')
def test_sessionclient_request_method_raises_overlimit(
self, mock_request):
resp = {
"overLimitFault": {
"message": "This request was rate-limited.",
"code": 413
}
}
mock_response = utils.TestResponse({
"status_code": 413,
"text": six.b(json.dumps(resp)),
})
# 'request' method of Adaptor will return 413 response
mock_request.return_value = mock_response
session_client = cinderclient.client.SessionClient(
session=mock.Mock())
self.assertRaises(exceptions.OverLimit, session_client.request,
mock.sentinel.url, 'GET')
self.assertIsNotNone(session_client._logger)
@mock.patch.object(exceptions, 'from_response')
def test_keystone_request_raises_auth_failure_exception(
self, mock_from_resp):
kwargs = {
"body": {
"volume": {
"status": "creating",
"imageRef": "username",
"attach_status": "detached"
},
"authenticated": "True"
}
}
with mock.patch.object(adapter.Adapter, 'request',
side_effect=
keystone_exception.AuthorizationFailure()):
session_client = cinderclient.client.SessionClient(
session=mock.Mock())
self.assertRaises(keystone_exception.AuthorizationFailure,
session_client.request,
mock.sentinel.url, 'POST', **kwargs)
# As keystonesession.request method will raise
# AuthorizationFailure exception, check exceptions.from_response
# is not getting called.
self.assertFalse(mock_from_resp.called)
class ClientTestSensitiveInfo(utils.TestCase):
def test_req_does_not_log_sensitive_info(self):
self.logger = self.useFixture(
fixtures.FakeLogger(
format="%(message)s",
level=logging.DEBUG,
nuke_handlers=True
)
)
secret_auth_token = "MY_SECRET_AUTH_TOKEN"
kwargs = {
'headers': {"X-Auth-Token": secret_auth_token},
'data': ('{"auth": {"tenantName": "fakeService",'
' "passwordCredentials": {"username": "fakeUser",'
' "password": "fakePassword"}}}')
}
cs = cinderclient.client.HTTPClient("user", None, None,
"http://127.0.0.1:5000")
cs.http_log_debug = True
cs.http_log_req('PUT', kwargs)
output = self.logger.output.split('\n')
self.assertNotIn(secret_auth_token, output[1])
def test_resp_does_not_log_sensitive_info(self):
self.logger = self.useFixture(
fixtures.FakeLogger(
format="%(message)s",
level=logging.DEBUG,
nuke_handlers=True
)
)
cs = cinderclient.client.HTTPClient("user", None, None,
"http://127.0.0.1:5000")
resp = mock.Mock()
resp.status_code = 200
resp.headers = {
'x-compute-request-id': 'req-f551871a-4950-4225-9b2c-29a14c8f075e'
}
auth_password = "kk4qD6CpKFLyz9JD"
body = {
"connection_info": {
"driver_volume_type": "iscsi",
"data": {
"auth_password": auth_password,
"target_discovered": False,
"encrypted": False,
"qos_specs": None,
"target_iqn": ("iqn.2010-10.org.openstack:volume-"
"a2f33dcc-1bb7-45ba-b8fc-5b38179120f8"),
"target_portal": "10.0.100.186:3260",
"volume_id": "a2f33dcc-1bb7-45ba-b8fc-5b38179120f8",
"target_lun": 1,
"access_mode": "rw",
"auth_username": "s4BfSfZ67Bo2mnpuFWY8",
"auth_method": "CHAP"
}
}
}
resp.text = jsonutils.dumps(body)
cs.http_log_debug = True
cs.http_log_resp(resp)
output = self.logger.output.split('\n')
self.assertIn('***', output[1], output)
self.assertNotIn(auth_password, output[1], output)
@ddt.ddt
class GetAPIVersionTestCase(utils.TestCase):
@mock.patch('cinderclient.client.requests.get')
def test_get_server_version(self, mock_request):
mock_response = utils.TestResponse({
"status_code": 200,
"text": json.dumps(fakes.fake_request_get())
})
mock_request.return_value = mock_response
url = "http://192.168.122.127:8776/v3/e5526285ebd741b1819393f772f11fc3"
min_version, max_version = cinderclient.client.get_server_version(url)
self.assertEqual(min_version, api_versions.APIVersion('3.0'))
self.assertEqual(max_version, api_versions.APIVersion('3.16'))
url = "https://192.168.122.127:8776/v3/e55285ebd741b1819393f772f11fc3"
min_version, max_version = cinderclient.client.get_server_version(url)
self.assertEqual(min_version, api_versions.APIVersion('3.0'))
self.assertEqual(max_version, api_versions.APIVersion('3.16'))
@mock.patch('cinderclient.client.requests.get')
@ddt.data('3.12', '3.40')
def test_get_highest_client_server_version(self, version, mock_request):
mock_response = utils.TestResponse({
"status_code": 200,
"text": json.dumps(fakes.fake_request_get())
})
mock_request.return_value = mock_response
url = "http://192.168.122.127:8776/v3/e5526285ebd741b1819393f772f11fc3"
with mock.patch.object(api_versions, 'MAX_VERSION', version):
highest = (
cinderclient.client.get_highest_client_server_version(url))
expected = version if version == '3.12' else '3.16'
self.assertEqual(expected, highest)

View File

@ -1,64 +0,0 @@
# Copyright 2015 IBM Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Tests the cinderclient.exceptions module."""
import datetime
import mock
import requests
from cinderclient import exceptions
from cinderclient.tests.unit import utils
class ExceptionsTest(utils.TestCase):
def test_from_response_no_body_message(self):
# Tests that we get ClientException back since we don't have 500 mapped
response = requests.Response()
response.status_code = 500
body = {'keys': ({})}
ex = exceptions.from_response(response, body)
self.assertIs(exceptions.ClientException, type(ex))
self.assertEqual('n/a', ex.message)
def test_from_response_overlimit(self):
response = requests.Response()
response.status_code = 413
response.headers = {"Retry-After": '10'}
body = {'keys': ({})}
ex = exceptions.from_response(response, body)
self.assertEqual(10, ex.retry_after)
self.assertIs(exceptions.OverLimit, type(ex))
@mock.patch('oslo_utils.timeutils.utcnow',
return_value=datetime.datetime(2016, 6, 30, 12, 41, 55))
def test_from_response_overlimit_gmt(self, mock_utcnow):
response = requests.Response()
response.status_code = 413
response.headers = {"Retry-After": "Thu, 30 Jun 2016 12:43:20 GMT"}
body = {'keys': ({})}
ex = exceptions.from_response(response, body)
self.assertEqual(85, ex.retry_after)
self.assertIs(exceptions.OverLimit, type(ex))
self.assertTrue(mock_utcnow.called)
def test_from_response_overlimit_without_header(self):
response = requests.Response()
response.status_code = 413
response.headers = {}
body = {'keys': ({})}
ex = exceptions.from_response(response, body)
self.assertEqual(0, ex.retry_after)
self.assertIs(exceptions.OverLimit, type(ex))

View File

@ -1,390 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import mock
import requests
import uuid
from cinderclient import client
from cinderclient import exceptions
from cinderclient.tests.unit import utils
fake_response = utils.TestResponse({
"status_code": 200,
"text": '{"hi": "there"}',
})
mock_request = mock.Mock(return_value=(fake_response))
fake_201_response = utils.TestResponse({
"status_code": 201,
"text": '{"hi": "there"}',
})
mock_201_request = mock.Mock(return_value=(fake_201_response))
refused_response = utils.TestResponse({
"status_code": 400,
"text": '[Errno 111] Connection refused',
})
refused_mock_request = mock.Mock(return_value=(refused_response))
bad_400_response = utils.TestResponse({
"status_code": 400,
"text": '',
})
bad_400_request = mock.Mock(return_value=(bad_400_response))
bad_401_response = utils.TestResponse({
"status_code": 401,
"text": '{"error": {"message": "FAILED!", "details": "DETAILS!"}}',
})
bad_401_request = mock.Mock(return_value=(bad_401_response))
bad_413_response = utils.TestResponse({
"status_code": 413,
"headers": {"Retry-After": "1", "x-compute-request-id": "1234"},
})
bad_413_request = mock.Mock(return_value=(bad_413_response))
bad_500_response = utils.TestResponse({
"status_code": 500,
"text": '{"error": {"message": "FAILED!", "details": "DETAILS!"}}',
})
bad_500_request = mock.Mock(return_value=(bad_500_response))
connection_error_request = mock.Mock(
side_effect=requests.exceptions.ConnectionError)
timeout_error_request = mock.Mock(
side_effect=requests.exceptions.Timeout)
def get_client(retries=0, **kwargs):
cl = client.HTTPClient("username", "password",
"project_id", "auth_test", retries=retries,
**kwargs)
return cl
def get_authed_client(retries=0, **kwargs):
cl = get_client(retries=retries, **kwargs)
cl.management_url = "http://example.com"
cl.auth_token = "token"
cl.get_service_url = mock.Mock(return_value="http://example.com")
return cl
def get_authed_endpoint_url(retries=0):
cl = client.HTTPClient("username", "password",
"project_id", "auth_test",
bypass_url="volume/v100/", retries=retries)
cl.auth_token = "token"
return cl
class ClientTest(utils.TestCase):
def test_get(self):
cl = get_authed_client()
@mock.patch.object(requests, "request", mock_request)
@mock.patch('time.time', mock.Mock(return_value=1234))
def test_get_call():
resp, body = cl.get("/hi")
headers = {"X-Auth-Token": "token",
"X-Auth-Project-Id": "project_id",
"User-Agent": cl.USER_AGENT,
'Accept': 'application/json', }
mock_request.assert_called_with(
"GET",
"http://example.com/hi",
headers=headers,
**self.TEST_REQUEST_BASE)
# Automatic JSON parsing
self.assertEqual({"hi": "there"}, body)
test_get_call()
def test_get_global_id(self):
global_id = "req-%s" % uuid.uuid4()
cl = get_authed_client(global_request_id=global_id)
@mock.patch.object(requests, "request", mock_request)
@mock.patch('time.time', mock.Mock(return_value=1234))
def test_get_call():
resp, body = cl.get("/hi")
headers = {"X-Auth-Token": "token",
"X-Auth-Project-Id": "project_id",
"X-OpenStack-Request-ID": global_id,
"User-Agent": cl.USER_AGENT,
'Accept': 'application/json', }
mock_request.assert_called_with(
"GET",
"http://example.com/hi",
headers=headers,
**self.TEST_REQUEST_BASE)
# Automatic JSON parsing
self.assertEqual({"hi": "there"}, body)
test_get_call()
def test_get_reauth_0_retries(self):
cl = get_authed_client(retries=0)
self.requests = [bad_401_request, mock_request]
def request(*args, **kwargs):
next_request = self.requests.pop(0)
return next_request(*args, **kwargs)
def reauth():
cl.management_url = "http://example.com"
cl.auth_token = "token"
@mock.patch.object(cl, 'authenticate', reauth)
@mock.patch.object(requests, "request", request)
@mock.patch('time.time', mock.Mock(return_value=1234))
def test_get_call():
resp, body = cl.get("/hi")
test_get_call()
self.assertEqual([], self.requests)
def test_get_retry_500(self):
cl = get_authed_client(retries=1)
self.requests = [bad_500_request, mock_request]
def request(*args, **kwargs):
next_request = self.requests.pop(0)
return next_request(*args, **kwargs)
@mock.patch.object(requests, "request", request)
@mock.patch('time.time', mock.Mock(return_value=1234))
def test_get_call():
resp, body = cl.get("/hi")
test_get_call()
self.assertEqual([], self.requests)
def test_get_retry_connection_error(self):
cl = get_authed_client(retries=1)
self.requests = [connection_error_request, mock_request]
def request(*args, **kwargs):
next_request = self.requests.pop(0)
return next_request(*args, **kwargs)
@mock.patch.object(requests, "request", request)
@mock.patch('time.time', mock.Mock(return_value=1234))
def test_get_call():
resp, body = cl.get("/hi")
test_get_call()
self.assertEqual([], self.requests)
def test_rate_limit_overlimit_exception(self):
cl = get_authed_client(retries=1)
self.requests = [bad_413_request,
bad_413_request,
mock_request]
def request(*args, **kwargs):
next_request = self.requests.pop(0)
return next_request(*args, **kwargs)
@mock.patch.object(requests, "request", request)
@mock.patch('time.time', mock.Mock(return_value=1234))
def test_get_call():
resp, body = cl.get("/hi")
self.assertRaises(exceptions.OverLimit, test_get_call)
self.assertEqual([mock_request], self.requests)
def test_rate_limit(self):
cl = get_authed_client(retries=1)
self.requests = [bad_413_request, mock_request]
def request(*args, **kwargs):
next_request = self.requests.pop(0)
return next_request(*args, **kwargs)
@mock.patch.object(requests, "request", request)
@mock.patch('time.time', mock.Mock(return_value=1234))
def test_get_call():
resp, body = cl.get("/hi")
return resp, body
resp, body = test_get_call()
self.assertEqual(200, resp.status_code)
self.assertEqual([], self.requests)
def test_retry_limit(self):
cl = get_authed_client(retries=1)
self.requests = [bad_500_request, bad_500_request, mock_request]
def request(*args, **kwargs):
next_request = self.requests.pop(0)
return next_request(*args, **kwargs)
@mock.patch.object(requests, "request", request)
@mock.patch('time.time', mock.Mock(return_value=1234))
def test_get_call():
resp, body = cl.get("/hi")
self.assertRaises(exceptions.ClientException, test_get_call)
self.assertEqual([mock_request], self.requests)
def test_get_no_retry_400(self):
cl = get_authed_client(retries=0)
self.requests = [bad_400_request, mock_request]
def request(*args, **kwargs):
next_request = self.requests.pop(0)
return next_request(*args, **kwargs)
@mock.patch.object(requests, "request", request)
@mock.patch('time.time', mock.Mock(return_value=1234))
def test_get_call():
resp, body = cl.get("/hi")
self.assertRaises(exceptions.BadRequest, test_get_call)
self.assertEqual([mock_request], self.requests)
def test_get_retry_400_socket(self):
cl = get_authed_client(retries=1)
self.requests = [bad_400_request, mock_request]
def request(*args, **kwargs):
next_request = self.requests.pop(0)
return next_request(*args, **kwargs)
@mock.patch.object(requests, "request", request)
@mock.patch('time.time', mock.Mock(return_value=1234))
def test_get_call():
resp, body = cl.get("/hi")
test_get_call()
self.assertEqual([], self.requests)
def test_get_no_auth_url(self):
client.HTTPClient("username", "password",
"project_id", retries=0)
def test_post(self):
cl = get_authed_client()
@mock.patch.object(requests, "request", mock_request)
def test_post_call():
cl.post("/hi", body=[1, 2, 3])
headers = {
"X-Auth-Token": "token",
"X-Auth-Project-Id": "project_id",
"Content-Type": "application/json",
'Accept': 'application/json',
"User-Agent": cl.USER_AGENT
}
mock_request.assert_called_with(
"POST",
"http://example.com/hi",
headers=headers,
data='[1, 2, 3]',
**self.TEST_REQUEST_BASE)
test_post_call()
def test_os_endpoint_url(self):
cl = get_authed_endpoint_url()
self.assertEqual("volume/v100", cl.bypass_url)
self.assertEqual("volume/v100", cl.management_url)
def test_auth_failure(self):
cl = get_client()
# response must not have x-server-management-url header
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
self.assertRaises(exceptions.AuthorizationFailure,
cl.authenticate)
test_auth_call()
def test_auth_with_keystone_v3(self):
cl = get_authed_client()
cl.auth_url = 'http://example.com:5000/v3'
@mock.patch.object(cl, "_extract_service_catalog", mock.Mock())
@mock.patch.object(requests, "request", mock_201_request)
def test_auth_call():
cl.authenticate()
headers = {
"Content-Type": "application/json",
'Accept': 'application/json',
"User-Agent": cl.USER_AGENT
}
data = {
"auth": {
"scope": {
"project": {
"domain": {"name": "Default"},
"name": "project_id"
}
},
"identity": {
"methods": ["password"],
"password": {
"user": {"domain": {"name": "Default"},
"password": "password", "name": "username"
}
}
}
}
}
# Check data, we cannot do it on the call because the JSON
# dictionary to string can generated different strings.
actual_data = mock_201_request.call_args[1]['data']
self.assertDictEqual(data, json.loads(actual_data))
mock_201_request.assert_called_with(
"POST",
"http://example.com:5000/v3/auth/tokens",
headers=headers,
allow_redirects=True,
data=actual_data,
**self.TEST_REQUEST_BASE)
test_auth_call()
def test_get_retry_timeout_error(self):
cl = get_authed_client(retries=1)
self.requests = [timeout_error_request, mock_request]
def request(*args, **kwargs):
next_request = self.requests.pop(0)
return next_request(*args, **kwargs)
@mock.patch.object(requests, "request", request)
@mock.patch('time.time', mock.Mock(return_value=1234))
def test_get_call():
resp, body = cl.get("/hi")
test_get_call()
self.assertEqual([], self.requests)

View File

@ -1,445 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import argparse
import re
import sys
import unittest
import fixtures
import keystoneauth1.exceptions as ks_exc
from keystoneauth1.exceptions import DiscoveryFailure
from keystoneauth1 import session
import mock
import requests_mock
from six import moves
from testtools import matchers
import cinderclient
from cinderclient import api_versions
from cinderclient.contrib import noauth
from cinderclient import exceptions
from cinderclient import shell
from cinderclient.tests.unit import fake_actions_module
from cinderclient.tests.unit.fixture_data import keystone_client
from cinderclient.tests.unit import utils
class ShellTest(utils.TestCase):
FAKE_ENV = {
'OS_USERNAME': 'username',
'OS_PASSWORD': 'password',
'OS_TENANT_NAME': 'tenant_name',
'OS_AUTH_URL': 'http://no.where/v2.0',
}
# Patch os.environ to avoid required auth info.
def make_env(self, exclude=None, include=None):
env = dict((k, v) for k, v in self.FAKE_ENV.items() if k != exclude)
env.update(include or {})
self.useFixture(fixtures.MonkeyPatch('os.environ', env))
def setUp(self):
super(ShellTest, self).setUp()
for var in self.FAKE_ENV:
self.useFixture(fixtures.EnvironmentVariable(var,
self.FAKE_ENV[var]))
def shell(self, argstr):
orig = sys.stdout
try:
sys.stdout = moves.StringIO()
_shell = shell.OpenStackCinderShell()
_shell.main(argstr.split())
except SystemExit:
exc_type, exc_value, exc_traceback = sys.exc_info()
self.assertEqual(0, exc_value.code)
finally:
out = sys.stdout.getvalue()
sys.stdout.close()
sys.stdout = orig
return out
def test_help_unknown_command(self):
self.assertRaises(exceptions.CommandError, self.shell, 'help foofoo')
def test_help(self):
required = [
'.*?^usage: ',
'.*?(?m)^\s+create\s+Creates a volume.',
'.*?(?m)^Run "cinder help SUBCOMMAND" for help on a subcommand.',
]
help_text = self.shell('help')
for r in required:
self.assertThat(help_text,
matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE))
def test_help_on_subcommand(self):
required = [
'.*?^usage: cinder list',
'.*?(?m)^Lists all volumes.',
]
help_text = self.shell('help list')
for r in required:
self.assertThat(help_text,
matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE))
def register_keystone_auth_fixture(self, mocker, url):
mocker.register_uri('GET', url,
text=keystone_client.keystone_request_callback)
@requests_mock.Mocker()
def test_version_discovery(self, mocker):
_shell = shell.OpenStackCinderShell()
sess = session.Session()
os_auth_url = "https://wrongdiscoveryresponse.discovery.com:35357/v2.0"
self.register_keystone_auth_fixture(mocker, os_auth_url)
self.assertRaises(DiscoveryFailure,
_shell._discover_auth_versions,
sess,
auth_url=os_auth_url)
os_auth_url = "https://DiscoveryNotSupported.discovery.com:35357/v2.0"
self.register_keystone_auth_fixture(mocker, os_auth_url)
v2_url, v3_url = _shell._discover_auth_versions(sess,
auth_url=os_auth_url)
self.assertEqual(os_auth_url, v2_url, "Expected v2 url")
self.assertIsNone(v3_url, "Expected no v3 url")
os_auth_url = "https://DiscoveryNotSupported.discovery.com:35357/v3.0"
self.register_keystone_auth_fixture(mocker, os_auth_url)
v2_url, v3_url = _shell._discover_auth_versions(sess,
auth_url=os_auth_url)
self.assertEqual(os_auth_url, v3_url, "Expected v3 url")
self.assertIsNone(v2_url, "Expected no v2 url")
@requests_mock.Mocker()
def list_volumes_on_service(self, count, mocker):
os_auth_url = "http://multiple.service.names/v2.0"
mocker.register_uri('POST', os_auth_url + "/tokens",
text=keystone_client.keystone_request_callback)
mocker.register_uri('GET',
"http://cinder%i.api.com/v2/volumes/detail"
% count, text='{"volumes": []}')
self.make_env(include={'OS_AUTH_URL': os_auth_url,
'CINDER_SERVICE_NAME': 'cinder%i' % count})
_shell = shell.OpenStackCinderShell()
_shell.main(['list'])
@unittest.skip("Skip cuz I broke it")
def test_cinder_service_name(self):
# Failing with 'No mock address' means we are not
# choosing the correct endpoint
for count in range(1, 4):
self.list_volumes_on_service(count)
@mock.patch('keystoneauth1.identity.v2.Password')
@mock.patch('keystoneauth1.adapter.Adapter.get_token',
side_effect=ks_exc.ConnectFailure())
@mock.patch('keystoneauth1.discover.Discover',
side_effect=ks_exc.ConnectFailure())
@mock.patch('sys.stdin', side_effect=mock.Mock)
@mock.patch('getpass.getpass', return_value='password')
def test_password_prompted(self, mock_getpass, mock_stdin, mock_discover,
mock_token, mock_password):
self.make_env(exclude='OS_PASSWORD')
_shell = shell.OpenStackCinderShell()
self.assertRaises(ks_exc.ConnectFailure, _shell.main, ['list'])
mock_getpass.assert_called_with('OS Password: ')
# Verify that Password() is called with value of param 'password'
# equal to mock_getpass.return_value.
mock_password.assert_called_with(
self.FAKE_ENV['OS_AUTH_URL'],
password=mock_getpass.return_value,
tenant_id='',
tenant_name=self.FAKE_ENV['OS_TENANT_NAME'],
username=self.FAKE_ENV['OS_USERNAME'])
@requests_mock.Mocker()
def test_noauth_plugin(self, mocker):
os_auth_url = "http://example.com/v2"
mocker.register_uri('GET',
"%s/admin/volumes/detail"
% os_auth_url, text='{"volumes": []}')
_shell = shell.OpenStackCinderShell()
args = ['--os-endpoint', os_auth_url,
'--os-auth-type', 'noauth', '--os-user-id',
'admin', '--os-project-id', 'admin', 'list']
_shell.main(args)
self.assertIsInstance(_shell.cs.client.session.auth,
noauth.CinderNoAuthPlugin)
@mock.patch.object(cinderclient.client.HTTPClient, 'authenticate',
side_effect=exceptions.Unauthorized('No'))
# Easiest way to make cinderclient use httpclient is a None session
@mock.patch.object(cinderclient.shell.OpenStackCinderShell,
'_get_keystone_session', return_value=None)
def test_http_client_insecure(self, mock_authenticate, mock_session):
self.make_env(include={'CINDERCLIENT_INSECURE': True})
_shell = shell.OpenStackCinderShell()
# This "fails" but instantiates the client.
self.assertRaises(exceptions.CommandError, _shell.main, ['list'])
self.assertEqual(False, _shell.cs.client.verify_cert)
@mock.patch.object(cinderclient.client.SessionClient, 'authenticate',
side_effect=exceptions.Unauthorized('No'))
def test_session_client_debug_logger(self, mock_session):
_shell = shell.OpenStackCinderShell()
# This "fails" but instantiates the client.
self.assertRaises(exceptions.CommandError, _shell.main,
['--debug', 'list'])
# In case of SessionClient when --debug switch is specified
# 'keystoneauth' logger should be initialized.
self.assertEqual('keystoneauth', _shell.cs.client.logger.name)
@mock.patch('keystoneauth1.session.Session.__init__',
side_effect=RuntimeError())
def test_http_client_with_cert(self, mock_session):
_shell = shell.OpenStackCinderShell()
# We crash the command after Session instantiation because this test
# focuses only on arguments provided to Session.__init__
args = '--os-cert', 'minnie', 'list'
self.assertRaises(RuntimeError, _shell.main, args)
mock_session.assert_called_once_with(cert='minnie', verify=mock.ANY)
@mock.patch('keystoneauth1.session.Session.__init__',
side_effect=RuntimeError())
def test_http_client_with_cert_and_key(self, mock_session):
_shell = shell.OpenStackCinderShell()
# We crash the command after Session instantiation because this test
# focuses only on arguments provided to Session.__init__
args = '--os-cert', 'minnie', '--os-key', 'mickey', 'list'
self.assertRaises(RuntimeError, _shell.main, args)
mock_session.assert_called_once_with(cert=('minnie', 'mickey'),
verify=mock.ANY)
class CinderClientArgumentParserTest(utils.TestCase):
def test_ambiguity_solved_for_one_visible_argument(self):
parser = shell.CinderClientArgumentParser(add_help=False)
parser.add_argument('--test-parameter',
dest='visible_param',
action='store_true')
parser.add_argument('--test_parameter',
dest='hidden_param',
action='store_true',
help=argparse.SUPPRESS)
opts = parser.parse_args(['--test'])
# visible argument must be set
self.assertTrue(opts.visible_param)
self.assertFalse(opts.hidden_param)
def test_raise_ambiguity_error_two_visible_argument(self):
parser = shell.CinderClientArgumentParser(add_help=False)
parser.add_argument('--test-parameter',
dest="visible_param1",
action='store_true')
parser.add_argument('--test_parameter',
dest="visible_param2",
action='store_true')
self.assertRaises(SystemExit, parser.parse_args, ['--test'])
def test_raise_ambiguity_error_two_hidden_argument(self):
parser = shell.CinderClientArgumentParser(add_help=False)
parser.add_argument('--test-parameter',
dest="hidden_param1",
action='store_true',
help=argparse.SUPPRESS)
parser.add_argument('--test_parameter',
dest="hidden_param2",
action='store_true',
help=argparse.SUPPRESS)
self.assertRaises(SystemExit, parser.parse_args, ['--test'])
class TestLoadVersionedActions(utils.TestCase):
def test_load_versioned_actions(self):
parser = cinderclient.shell.CinderClientArgumentParser()
subparsers = parser.add_subparsers(metavar='<subcommand>')
shell = cinderclient.shell.OpenStackCinderShell()
shell.subcommands = {}
shell._find_actions(subparsers, fake_actions_module,
api_versions.APIVersion("3.0"), False, [])
self.assertIn('fake-action', shell.subcommands.keys())
self.assertEqual(
"fake_action 3.0 to 3.1",
shell.subcommands['fake-action'].get_default('func')())
shell.subcommands = {}
shell._find_actions(subparsers, fake_actions_module,
api_versions.APIVersion("3.2"), False, [])
self.assertIn('fake-action', shell.subcommands.keys())
self.assertEqual(
"fake_action 3.2 to 3.3",
shell.subcommands['fake-action'].get_default('func')())
self.assertIn('fake-action2', shell.subcommands.keys())
self.assertEqual(
"fake_action2",
shell.subcommands['fake-action2'].get_default('func')())
def test_load_versioned_actions_not_in_version_range(self):
parser = cinderclient.shell.CinderClientArgumentParser()
subparsers = parser.add_subparsers(metavar='<subcommand>')
shell = cinderclient.shell.OpenStackCinderShell()
shell.subcommands = {}
shell._find_actions(subparsers, fake_actions_module,
api_versions.APIVersion('3.10000'), False, [])
self.assertNotIn('fake-action', shell.subcommands.keys())
self.assertIn('fake-action2', shell.subcommands.keys())
def test_load_versioned_actions_unsupported_input(self):
parser = cinderclient.shell.CinderClientArgumentParser()
subparsers = parser.add_subparsers(metavar='<subcommand>')
shell = cinderclient.shell.OpenStackCinderShell()
shell.subcommands = {}
self.assertRaises(exceptions.UnsupportedAttribute,
shell._find_actions, subparsers, fake_actions_module,
api_versions.APIVersion('3.6'), False,
['another-fake-action', '--foo'])
def test_load_versioned_actions_with_help(self):
parser = cinderclient.shell.CinderClientArgumentParser()
subparsers = parser.add_subparsers(metavar='<subcommand>')
shell = cinderclient.shell.OpenStackCinderShell()
shell.subcommands = {}
with mock.patch.object(subparsers, 'add_parser') as mock_add_parser:
shell._find_actions(subparsers, fake_actions_module,
api_versions.APIVersion("3.1"), True, [])
self.assertIn('fake-action', shell.subcommands.keys())
expected_help = ("help message (Supported by API versions "
"%(start)s - %(end)s)") % {
'start': '3.0', 'end': '3.3'}
expected_desc = ("help message\n\n "
"This will not show up in help message\n ")
mock_add_parser.assert_any_call(
'fake-action',
help=expected_help,
description=expected_desc,
add_help=False,
formatter_class=cinderclient.shell.OpenStackHelpFormatter)
def test_load_versioned_actions_with_help_on_latest(self):
parser = cinderclient.shell.CinderClientArgumentParser()
subparsers = parser.add_subparsers(metavar='<subcommand>')
shell = cinderclient.shell.OpenStackCinderShell()
shell.subcommands = {}
with mock.patch.object(subparsers, 'add_parser') as mock_add_parser:
shell._find_actions(subparsers, fake_actions_module,
api_versions.APIVersion("3.latest"), True, [])
self.assertIn('another-fake-action', shell.subcommands.keys())
expected_help = (" (Supported by API versions %(start)s - "
"%(end)s)%(hint)s") % {
'start': '3.6', 'end': '3.latest',
'hint': cinderclient.shell.HINT_HELP_MSG}
mock_add_parser.assert_any_call(
'another-fake-action',
help=expected_help,
description='',
add_help=False,
formatter_class=cinderclient.shell.OpenStackHelpFormatter)
@mock.patch.object(cinderclient.shell.CinderClientArgumentParser,
'add_argument')
def test_load_versioned_actions_with_args(self, mock_add_arg):
parser = cinderclient.shell.CinderClientArgumentParser(add_help=False)
subparsers = parser.add_subparsers(metavar='<subcommand>')
shell = cinderclient.shell.OpenStackCinderShell()
shell.subcommands = {}
shell._find_actions(subparsers, fake_actions_module,
api_versions.APIVersion("3.1"), False, [])
self.assertIn('fake-action2', shell.subcommands.keys())
mock_add_arg.assert_has_calls([
mock.call('-h', '--help', action='help', help='==SUPPRESS=='),
mock.call('--foo')])
@mock.patch.object(cinderclient.shell.CinderClientArgumentParser,
'add_argument')
def test_load_versioned_actions_with_args2(self, mock_add_arg):
parser = cinderclient.shell.CinderClientArgumentParser(add_help=False)
subparsers = parser.add_subparsers(metavar='<subcommand>')
shell = cinderclient.shell.OpenStackCinderShell()
shell.subcommands = {}
shell._find_actions(subparsers, fake_actions_module,
api_versions.APIVersion("3.4"), False, [])
self.assertIn('fake-action2', shell.subcommands.keys())
mock_add_arg.assert_has_calls([
mock.call('-h', '--help', action='help', help='==SUPPRESS=='),
mock.call('--bar', help="bar help")])
@mock.patch.object(cinderclient.shell.CinderClientArgumentParser,
'add_argument')
def test_load_versioned_actions_with_args_not_in_version_range(
self, mock_add_arg):
parser = cinderclient.shell.CinderClientArgumentParser(add_help=False)
subparsers = parser.add_subparsers(metavar='<subcommand>')
shell = cinderclient.shell.OpenStackCinderShell()
shell.subcommands = {}
shell._find_actions(subparsers, fake_actions_module,
api_versions.APIVersion("3.10000"), False, [])
self.assertIn('fake-action2', shell.subcommands.keys())
mock_add_arg.assert_has_calls([
mock.call('-h', '--help', action='help', help='==SUPPRESS==')])
@mock.patch.object(cinderclient.shell.CinderClientArgumentParser,
'add_argument')
def test_load_versioned_actions_with_args_and_help(self, mock_add_arg):
parser = cinderclient.shell.CinderClientArgumentParser(add_help=False)
subparsers = parser.add_subparsers(metavar='<subcommand>')
shell = cinderclient.shell.OpenStackCinderShell()
shell.subcommands = {}
shell._find_actions(subparsers, fake_actions_module,
api_versions.APIVersion("3.4"), True, [])
mock_add_arg.assert_has_calls([
mock.call('-h', '--help', action='help', help='==SUPPRESS=='),
mock.call('--bar',
help="bar help (Supported by API versions"
" 3.3 - 3.4)")])
@mock.patch.object(cinderclient.shell.CinderClientArgumentParser,
'add_argument')
def test_load_actions_with_versioned_args(self, mock_add_arg):
parser = cinderclient.shell.CinderClientArgumentParser(add_help=False)
subparsers = parser.add_subparsers(metavar='<subcommand>')
shell = cinderclient.shell.OpenStackCinderShell()
shell.subcommands = {}
shell._find_actions(subparsers, fake_actions_module,
api_versions.APIVersion("3.6"), False, [])
self.assertIn(mock.call('--foo', help="first foo"),
mock_add_arg.call_args_list)
self.assertNotIn(mock.call('--foo', help="second foo"),
mock_add_arg.call_args_list)
mock_add_arg.reset_mock()
shell._find_actions(subparsers, fake_actions_module,
api_versions.APIVersion("3.9"), False, [])
self.assertNotIn(mock.call('--foo', help="first foo"),
mock_add_arg.call_args_list)
self.assertIn(mock.call('--foo', help="second foo"),
mock_add_arg.call_args_list)

View File

@ -1,362 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import collections
import ddt
import sys
import mock
from six import moves
import six
from cinderclient import api_versions
from cinderclient.apiclient import base as common_base
from cinderclient import exceptions
from cinderclient import shell_utils
from cinderclient import utils
from cinderclient import base
from cinderclient.tests.unit import utils as test_utils
from cinderclient.tests.unit.v2 import fakes
UUID = '8e8ec658-c7b0-4243-bdf8-6f7f2952c0d0'
class FakeResource(object):
NAME_ATTR = 'name'
def __init__(self, _id, properties, **kwargs):
self.id = _id
try:
self.name = properties['name']
except KeyError:
pass
def append_request_ids(self, resp):
pass
class FakeManager(base.ManagerWithFind):
resource_class = FakeResource
resources = [
FakeResource('1234', {'name': 'entity_one'}),
FakeResource(UUID, {'name': 'entity_two'}),
FakeResource('5678', {'name': '9876'})
]
def get(self, resource_id, **kwargs):
for resource in self.resources:
if resource.id == str(resource_id):
return resource
raise exceptions.NotFound(resource_id)
def list(self, search_opts, **kwargs):
return common_base.ListWithMeta(self.resources, fakes.REQUEST_ID)
class FakeManagerWithApi(base.Manager):
@api_versions.wraps('3.1')
def return_api_version(self):
return '3.1'
@api_versions.wraps('3.2')
def return_api_version(self):
return '3.2'
class FakeDisplayResource(object):
NAME_ATTR = 'display_name'
def __init__(self, _id, properties):
self.id = _id
try:
self.display_name = properties['display_name']
except KeyError:
pass
def append_request_ids(self, resp):
pass
class FakeDisplayManager(FakeManager):
resource_class = FakeDisplayResource
resources = [
FakeDisplayResource('4242', {'display_name': 'entity_three'}),
]
class FindResourceTestCase(test_utils.TestCase):
def setUp(self):
super(FindResourceTestCase, self).setUp()
self.manager = FakeManager(None)
def test_find_none(self):
self.manager.find = mock.Mock(side_effect=self.manager.find)
self.assertRaises(exceptions.CommandError,
utils.find_resource,
self.manager,
'asdf')
self.assertEqual(2, self.manager.find.call_count)
def test_find_by_integer_id(self):
output = utils.find_resource(self.manager, 1234)
self.assertEqual(self.manager.get('1234'), output)
def test_find_by_str_id(self):
output = utils.find_resource(self.manager, '1234')
self.assertEqual(self.manager.get('1234'), output)
def test_find_by_uuid(self):
output = utils.find_resource(self.manager, UUID)
self.assertEqual(self.manager.get(UUID), output)
def test_find_by_str_name(self):
output = utils.find_resource(self.manager, 'entity_one')
self.assertEqual(self.manager.get('1234'), output)
def test_find_by_str_displayname(self):
display_manager = FakeDisplayManager(None)
output = utils.find_resource(display_manager, 'entity_three')
self.assertEqual(display_manager.get('4242'), output)
def test_find_by_group_id(self):
output = utils.find_resource(self.manager, 1234, is_group=True,
list_volume=True)
self.assertEqual(self.manager.get('1234', list_volume=True), output)
def test_find_by_group_name(self):
display_manager = FakeDisplayManager(None)
output = utils.find_resource(display_manager, 'entity_three',
is_group=True, list_volume=True)
self.assertEqual(display_manager.get('4242', list_volume=True),
output)
class CaptureStdout(object):
"""Context manager for capturing stdout from statements in its block."""
def __enter__(self):
self.real_stdout = sys.stdout
self.stringio = moves.StringIO()
sys.stdout = self.stringio
return self
def __exit__(self, *args):
sys.stdout = self.real_stdout
self.stringio.seek(0)
self.read = self.stringio.read
class BuildQueryParamTestCase(test_utils.TestCase):
def test_build_param_without_sort_switch(self):
dict_param = {
'key1': 'val1',
'key2': 'val2',
'key3': 'val3',
}
result = utils.build_query_param(dict_param, True)
self.assertIn('key1=val1', result)
self.assertIn('key2=val2', result)
self.assertIn('key3=val3', result)
def test_build_param_with_sort_switch(self):
dict_param = {
'key1': 'val1',
'key2': 'val2',
'key3': 'val3',
}
result = utils.build_query_param(dict_param, True)
expected = "?key1=val1&key2=val2&key3=val3"
self.assertEqual(expected, result)
def test_build_param_with_none(self):
dict_param = {
'key1': 'val1',
'key2': None,
'key3': False,
'key4': ''
}
result_1 = utils.build_query_param(dict_param)
result_2 = utils.build_query_param(None)
expected = "?key1=val1"
self.assertEqual(expected, result_1)
self.assertFalse(result_2)
@ddt.ddt
class ExtractFilterTestCase(test_utils.TestCase):
@ddt.data({'content': ['key1=value1'],
'expected': {'key1': 'value1'}},
{'content': ['key1={key2:value2}'],
'expected': {'key1': {'key2': 'value2'}}},
{'content': ['key1=value1', 'key2={key22:value22}'],
'expected': {'key1': 'value1', 'key2': {'key22': 'value22'}}})
@ddt.unpack
def test_extract_filters(self, content, expected):
result = shell_utils.extract_filters(content)
self.assertEqual(expected, result)
class PrintListTestCase(test_utils.TestCase):
def test_print_list_with_list(self):
Row = collections.namedtuple('Row', ['a', 'b'])
to_print = [Row(a=3, b=4), Row(a=1, b=2)]
with CaptureStdout() as cso:
utils.print_list(to_print, ['a', 'b'])
# Output should be sorted by the first key (a)
self.assertEqual("""\
+---+---+
| a | b |
+---+---+
| 1 | 2 |
| 3 | 4 |
+---+---+
""", cso.read())
def test_print_list_with_None_data(self):
Row = collections.namedtuple('Row', ['a', 'b'])
to_print = [Row(a=3, b=None), Row(a=1, b=2)]
with CaptureStdout() as cso:
utils.print_list(to_print, ['a', 'b'])
# Output should be sorted by the first key (a)
self.assertEqual("""\
+---+---+
| a | b |
+---+---+
| 1 | 2 |
| 3 | - |
+---+---+
""", cso.read())
def test_print_list_with_list_sortby(self):
Row = collections.namedtuple('Row', ['a', 'b'])
to_print = [Row(a=4, b=3), Row(a=2, b=1)]
with CaptureStdout() as cso:
utils.print_list(to_print, ['a', 'b'], sortby_index=1)
# Output should be sorted by the second key (b)
self.assertEqual("""\
+---+---+
| a | b |
+---+---+
| 2 | 1 |
| 4 | 3 |
+---+---+
""", cso.read())
def test_print_list_with_list_no_sort(self):
Row = collections.namedtuple('Row', ['a', 'b'])
to_print = [Row(a=3, b=4), Row(a=1, b=2)]
with CaptureStdout() as cso:
utils.print_list(to_print, ['a', 'b'], sortby_index=None)
# Output should be in the order given
self.assertEqual("""\
+---+---+
| a | b |
+---+---+
| 3 | 4 |
| 1 | 2 |
+---+---+
""", cso.read())
def test_print_list_with_generator(self):
Row = collections.namedtuple('Row', ['a', 'b'])
def gen_rows():
for row in [Row(a=1, b=2), Row(a=3, b=4)]:
yield row
with CaptureStdout() as cso:
utils.print_list(gen_rows(), ['a', 'b'])
self.assertEqual("""\
+---+---+
| a | b |
+---+---+
| 1 | 2 |
| 3 | 4 |
+---+---+
""", cso.read())
def test_print_list_with_return(self):
Row = collections.namedtuple('Row', ['a', 'b'])
to_print = [Row(a=3, b='a\r'), Row(a=1, b='c\rd')]
with CaptureStdout() as cso:
utils.print_list(to_print, ['a', 'b'])
# Output should be sorted by the first key (a)
self.assertEqual("""\
+---+-----+
| a | b |
+---+-----+
| 1 | c d |
| 3 | a |
+---+-----+
""", cso.read())
def test_unicode_key_value_to_string(self):
src = {u'key': u'\u70fd\u7231\u5a77'}
expected = {'key': '\xe7\x83\xbd\xe7\x88\xb1\xe5\xa9\xb7'}
if six.PY2:
self.assertEqual(expected, utils.unicode_key_value_to_string(src))
else:
# u'xxxx' in PY3 is str, we will not get extra 'u' from cli
# output in PY3
self.assertEqual(src, utils.unicode_key_value_to_string(src))
class PrintDictTestCase(test_utils.TestCase):
def test__pretty_format_dict(self):
content = {'key1': 'value1', 'key2': 'value2'}
expected = "key1 : value1\nkey2 : value2"
result = utils._pretty_format_dict(content)
self.assertEqual(expected, result)
def test_print_dict_with_return(self):
d = {'a': 'A', 'b': 'B', 'c': 'C', 'd': 'test\rcarriage\n\rreturn'}
with CaptureStdout() as cso:
utils.print_dict(d)
self.assertEqual("""\
+----------+---------------+
| Property | Value |
+----------+---------------+
| a | A |
| b | B |
| c | C |
| d | test carriage |
| | return |
+----------+---------------+
""", cso.read())
def test_print_dict_with_dict_inside(self):
content = {'a': 'A', 'b': 'B', 'f_key':
{'key1': 'value1', 'key2': 'value2'}}
with CaptureStdout() as cso:
utils.print_dict(content, formatters='f_key')
self.assertEqual("""\
+----------+---------------+
| Property | Value |
+----------+---------------+
| a | A |
| b | B |
| f_key | key1 : value1 |
| | key2 : value2 |
+----------+---------------+
""", cso.read())

View File

@ -1,120 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import os
import fixtures
import requests
from requests_mock.contrib import fixture as requests_mock_fixture
import six
import testtools
REQUEST_ID = ['req-test-request-id']
class TestCase(testtools.TestCase):
TEST_REQUEST_BASE = {
'verify': True,
}
def setUp(self):
super(TestCase, self).setUp()
if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
os.environ.get('OS_STDOUT_CAPTURE') == '1'):
stdout = self.useFixture(fixtures.StringStream('stdout')).stream
self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
os.environ.get('OS_STDERR_CAPTURE') == '1'):
stderr = self.useFixture(fixtures.StringStream('stderr')).stream
self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
def _assert_request_id(self, obj, count=1):
self.assertTrue(hasattr(obj, 'request_ids'))
self.assertEqual(REQUEST_ID * count, obj.request_ids)
def assert_called_anytime(self, method, url, body=None,
partial_body=None):
return self.shell.cs.assert_called_anytime(method, url, body,
partial_body)
class FixturedTestCase(TestCase):
client_fixture_class = None
data_fixture_class = None
def setUp(self):
super(FixturedTestCase, self).setUp()
self.requests = self.useFixture(requests_mock_fixture.Fixture())
self.data_fixture = None
self.client_fixture = None
self.cs = None
if self.client_fixture_class:
fix = self.client_fixture_class(self.requests)
self.client_fixture = self.useFixture(fix)
self.cs = self.client_fixture.new_client()
if self.data_fixture_class:
fix = self.data_fixture_class(self.requests)
self.data_fixture = self.useFixture(fix)
def assert_called(self, method, path, body=None):
self.assertEqual(method, self.requests.last_request.method)
self.assertEqual(path, self.requests.last_request.path_url)
if body:
req_data = self.requests.last_request.body
if isinstance(req_data, six.binary_type):
req_data = req_data.decode('utf-8')
if not isinstance(body, six.string_types):
# json load if the input body to match against is not a string
req_data = json.loads(req_data)
self.assertEqual(body, req_data)
class TestResponse(requests.Response):
"""Class used to wrap requests.Response.
Provides some convenience to initialize with a dict.
"""
def __init__(self, data):
super(TestResponse, self).__init__()
self._content = None
self._text = None
if isinstance(data, dict):
self.status_code = data.get('status_code', None)
self.headers = data.get('headers', None)
self.reason = data.get('reason', '')
# Fake text and content attributes to streamline Response creation
text = data.get('text', None)
self._content = text
self._text = text
else:
self.status_code = data
def __eq__(self, other):
return self.__dict__ == other.__dict__
@property
def content(self):
return self._content
@property
def text(self):
return self._text

View File

@ -1,34 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from cinderclient import extension
from cinderclient.v1.contrib import list_extensions
from cinderclient.tests.unit import utils
from cinderclient.tests.unit.v1 import fakes
extensions = [
extension.Extension(list_extensions.__name__.split(".")[-1],
list_extensions),
]
cs = fakes.FakeClient(extensions=extensions)
class ListExtensionsTests(utils.TestCase):
def test_list_extensions(self):
all_exts = cs.list_extensions.show_all()
cs.assert_called('GET', '/extensions')
self.assertGreater(len(all_exts), 0)
for r in all_exts:
self.assertGreater(len(r.summary), 0)

View File

@ -1,800 +0,0 @@
# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
# Copyright (c) 2011 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from datetime import datetime
import six.moves.urllib.parse as urlparse
from cinderclient import client as base_client
from cinderclient.tests.unit import fakes
import cinderclient.tests.unit.utils as utils
from cinderclient.v1 import client
def _stub_volume(**kwargs):
volume = {
'id': '00000000-0000-0000-0000-000000000000',
'display_name': None,
'display_description': None,
"attachments": [],
"bootable": "false",
"availability_zone": "cinder",
"created_at": "2012-08-27T00:00:00.000000",
"metadata": {},
"size": 1,
"snapshot_id": None,
"status": "available",
"volume_type": "None",
}
volume.update(kwargs)
return volume
def _stub_snapshot(**kwargs):
snapshot = {
"created_at": "2012-08-28T16:30:31.000000",
"display_description": None,
"display_name": None,
"id": '11111111-1111-1111-1111-111111111111',
"size": 1,
"status": "available",
"volume_id": '00000000-0000-0000-0000-000000000000',
}
snapshot.update(kwargs)
return snapshot
def _self_href(base_uri, tenant_id, backup_id):
return '%s/v1/%s/backups/%s' % (base_uri, tenant_id, backup_id)
def _bookmark_href(base_uri, tenant_id, backup_id):
return '%s/%s/backups/%s' % (base_uri, tenant_id, backup_id)
def _stub_backup_full(id, base_uri, tenant_id):
return {
'id': id,
'name': 'backup',
'description': 'nightly backup',
'volume_id': '712f4980-5ac1-41e5-9383-390aa7c9f58b',
'container': 'volumebackups',
'object_count': 220,
'size': 10,
'availability_zone': 'az1',
'created_at': '2013-04-12T08:16:37.000000',
'status': 'available',
'links': [
{
'href': _self_href(base_uri, tenant_id, id),
'rel': 'self'
},
{
'href': _bookmark_href(base_uri, tenant_id, id),
'rel': 'bookmark'
}
]
}
def _stub_backup(id, base_uri, tenant_id):
return {
'id': id,
'name': 'backup',
'links': [
{
'href': _self_href(base_uri, tenant_id, id),
'rel': 'self'
},
{
'href': _bookmark_href(base_uri, tenant_id, id),
'rel': 'bookmark'
}
]
}
def _stub_restore():
return {'volume_id': '712f4980-5ac1-41e5-9383-390aa7c9f58b'}
def _stub_qos_full(id, base_uri, tenant_id, name=None, specs=None):
if not name:
name = 'fake-name'
if not specs:
specs = {}
return {
'qos_specs': {
'id': id,
'name': name,
'consumer': 'back-end',
'specs': specs,
},
'links': {
'href': _bookmark_href(base_uri, tenant_id, id),
'rel': 'bookmark'
}
}
def _stub_qos_associates(id, name):
return {
'assoications_type': 'volume_type',
'name': name,
'id': id,
}
def _stub_transfer_full(id, base_uri, tenant_id):
return {
'id': id,
'name': 'transfer',
'volume_id': '8c05f861-6052-4df6-b3e0-0aebfbe686cc',
'created_at': '2013-04-12T08:16:37.000000',
'auth_key': '123456',
'links': [
{
'href': _self_href(base_uri, tenant_id, id),
'rel': 'self'
},
{
'href': _bookmark_href(base_uri, tenant_id, id),
'rel': 'bookmark'
}
]
}
def _stub_transfer(id, base_uri, tenant_id):
return {
'id': id,
'name': 'transfer',
'volume_id': '8c05f861-6052-4df6-b3e0-0aebfbe686cc',
'links': [
{
'href': _self_href(base_uri, tenant_id, id),
'rel': 'self'
},
{
'href': _bookmark_href(base_uri, tenant_id, id),
'rel': 'bookmark'
}
]
}
def _stub_extend(id, new_size):
return {'volume_id': '712f4980-5ac1-41e5-9383-390aa7c9f58b'}
class FakeClient(fakes.FakeClient, client.Client):
def __init__(self, api_version=None, *args, **kwargs):
client.Client.__init__(self, 'username', 'password',
'project_id', 'auth_url',
extensions=kwargs.get('extensions'))
self.api_version = api_version
self.client = FakeHTTPClient(**kwargs)
def get_volume_api_version_from_endpoint(self):
return self.client.get_volume_api_version_from_endpoint()
class FakeHTTPClient(base_client.HTTPClient):
def __init__(self, **kwargs):
self.username = 'username'
self.password = 'password'
self.auth_url = 'auth_url'
self.callstack = []
self.management_url = 'http://10.0.2.15:8776/v1/fake'
def _cs_request(self, url, method, **kwargs):
# Check that certain things are called correctly
if method in ['GET', 'DELETE']:
assert 'body' not in kwargs
elif method == 'PUT':
assert 'body' in kwargs
# Call the method
args = urlparse.parse_qsl(urlparse.urlparse(url)[4])
kwargs.update(args)
munged_url = url.rsplit('?', 1)[0]
munged_url = munged_url.strip('/').replace('/', '_').replace('.', '_')
munged_url = munged_url.replace('-', '_')
callback = "%s_%s" % (method.lower(), munged_url)
if not hasattr(self, callback):
raise AssertionError('Called unknown API method: %s %s, '
'expected fakes method name: %s' %
(method, url, callback))
# Note the call
self.callstack.append((method, url, kwargs.get('body', None)))
status, headers, body = getattr(self, callback)(**kwargs)
r = utils.TestResponse({
"status_code": status,
"text": body,
"headers": headers,
})
return r, body
def get_volume_api_version_from_endpoint(self):
magic_tuple = urlparse.urlsplit(self.management_url)
scheme, netloc, path, query, frag = magic_tuple
return path.lstrip('/').split('/')[0][1:]
#
# Snapshots
#
def get_snapshots_detail(self, **kw):
return (200, {}, {'snapshots': [
_stub_snapshot(),
]})
def get_snapshots_1234(self, **kw):
return (200, {}, {'snapshot': _stub_snapshot(id='1234')})
def get_snapshots_5678(self, **kw):
return (200, {}, {'snapshot': _stub_snapshot(id='5678')})
def put_snapshots_1234(self, **kw):
snapshot = _stub_snapshot(id='1234')
snapshot.update(kw['body']['snapshot'])
return (200, {}, {'snapshot': snapshot})
def post_snapshots_1234_action(self, body, **kw):
_body = None
resp = 202
assert len(list(body)) == 1
action = list(body)[0]
if action == 'os-reset_status':
assert 'status' in body['os-reset_status']
elif action == 'os-update_snapshot_status':
assert 'status' in body['os-update_snapshot_status']
else:
raise AssertionError("Unexpected action: %s" % action)
return (resp, {}, _body)
def post_snapshots_5678_action(self, body, **kw):
return self.post_snapshots_1234_action(body, **kw)
def delete_snapshots_1234(self, **kw):
return (202, {}, {})
def delete_snapshots_5678(self, **kw):
return (202, {}, {})
#
# Volumes
#
def put_volumes_1234(self, **kw):
volume = _stub_volume(id='1234')
volume.update(kw['body']['volume'])
return (200, {}, {'volume': volume})
def get_volumes(self, **kw):
return (200, {}, {"volumes": [
{'id': 1234, 'display_name': 'sample-volume'},
{'id': 5678, 'display_name': 'sample-volume2'}
]})
# TODO(jdg): This will need to change
# at the very least it's not complete
def get_volumes_detail(self, **kw):
return (200, {}, {"volumes": [
{'id': kw.get('id', 1234),
'display_name': 'sample-volume',
'attachments': [{'server_id': 1234}]},
]})
def get_volumes_1234(self, **kw):
r = {'volume': self.get_volumes_detail(id=1234)[2]['volumes'][0]}
return (200, {}, r)
def get_volumes_5678(self, **kw):
r = {'volume': self.get_volumes_detail(id=5678)[2]['volumes'][0]}
return (200, {}, r)
def get_volumes_1234_encryption(self, **kw):
r = {'encryption_key_id': 'id'}
return (200, {}, r)
def post_volumes_1234_action(self, body, **kw):
_body = None
resp = 202
assert len(list(body)) == 1
action = list(body)[0]
if action == 'os-attach':
keys = sorted(list(body[action]))
assert (keys == ['instance_uuid', 'mode', 'mountpoint'] or
keys == ['host_name', 'mode', 'mountpoint'])
elif action == 'os-detach':
assert body[action] is None
elif action == 'os-reserve':
assert body[action] is None
elif action == 'os-unreserve':
assert body[action] is None
elif action == 'os-initialize_connection':
assert list(body[action]) == ['connector']
return (202, {}, {'connection_info': 'foos'})
elif action == 'os-terminate_connection':
assert list(body[action]) == ['connector']
elif action == 'os-begin_detaching':
assert body[action] is None
elif action == 'os-roll_detaching':
assert body[action] is None
elif action == 'os-reset_status':
assert 'status' in body[action]
elif action == 'os-extend':
assert list(body[action]) == ['new_size']
elif action == 'os-migrate_volume':
assert 'host' in body[action]
assert 'force_host_copy' in body[action]
elif action == 'os-update_readonly_flag':
assert list(body[action]) == ['readonly']
elif action == 'os-set_bootable':
assert list(body[action]) == ['bootable']
else:
raise AssertionError("Unexpected action: %s" % action)
return (resp, {}, _body)
def post_volumes_5678_action(self, body, **kw):
return self.post_volumes_1234_action(body, **kw)
def post_volumes(self, **kw):
return (202, {}, {'volume': {}})
def delete_volumes_1234(self, **kw):
return (202, {}, None)
def delete_volumes_5678(self, **kw):
return (202, {}, None)
#
# Quotas
#
def get_os_quota_sets_test(self, **kw):
return (200, {}, {'quota_set': {
'tenant_id': 'test',
'metadata_items': [],
'volumes': 1,
'snapshots': 1,
'gigabytes': 1,
'backups': 1,
'backup_gigabytes': 1}})
def get_os_quota_sets_test_defaults(self):
return (200, {}, {'quota_set': {
'tenant_id': 'test',
'metadata_items': [],
'volumes': 1,
'snapshots': 1,
'gigabytes': 1,
'backups': 1,
'backup_gigabytes': 1}})
def put_os_quota_sets_test(self, body, **kw):
assert list(body) == ['quota_set']
fakes.assert_has_keys(body['quota_set'],
required=['tenant_id'])
return (200, {}, {'quota_set': {
'tenant_id': 'test',
'metadata_items': [],
'volumes': 2,
'snapshots': 2,
'gigabytes': 1,
'backups': 1,
'backup_gigabytes': 1}})
def delete_os_quota_sets_1234(self, **kw):
return (200, {}, {})
def delete_os_quota_sets_test(self, **kw):
return (200, {}, {})
#
# Quota Classes
#
def get_os_quota_class_sets_test(self, **kw):
return (200, {}, {'quota_class_set': {
'class_name': 'test',
'metadata_items': [],
'volumes': 1,
'snapshots': 1,
'gigabytes': 1,
'backups': 1,
'backup_gigabytes': 1}})
def put_os_quota_class_sets_test(self, body, **kw):
assert list(body) == ['quota_class_set']
fakes.assert_has_keys(body['quota_class_set'],
required=['class_name'])
return (200, {}, {'quota_class_set': {
'class_name': 'test',
'metadata_items': [],
'volumes': 2,
'snapshots': 2,
'gigabytes': 1,
'backups': 1,
'backup_gigabytes': 1}})
#
# VolumeTypes
#
def get_types(self, **kw):
return (200, {}, {
'volume_types': [{'id': 1,
'name': 'test-type-1',
'extra_specs': {}},
{'id': 2,
'name': 'test-type-2',
'extra_specs': {}}]})
def get_types_1(self, **kw):
return (200, {}, {'volume_type': {'id': 1,
'name': 'test-type-1',
'extra_specs': {}}})
def get_types_2(self, **kw):
return (200, {}, {'volume_type': {'id': 2,
'name': 'test-type-2',
'extra_specs': {}}})
def post_types(self, body, **kw):
return (202, {}, {'volume_type': {'id': 3,
'name': 'test-type-3',
'extra_specs': {}}})
def post_types_1_extra_specs(self, body, **kw):
assert list(body) == ['extra_specs']
return (200, {}, {'extra_specs': {'k': 'v'}})
def delete_types_1_extra_specs_k(self, **kw):
return(204, {}, None)
def delete_types_1_extra_specs_m(self, **kw):
return(204, {}, None)
def delete_types_1(self, **kw):
return (202, {}, None)
#
# VolumeEncryptionTypes
#
def get_types_1_encryption(self, **kw):
return (200, {}, {'id': 1, 'volume_type_id': 1, 'provider': 'test',
'cipher': 'test', 'key_size': 1,
'control_location': 'front-end'})
def get_types_2_encryption(self, **kw):
return (200, {}, {})
def post_types_2_encryption(self, body, **kw):
return (200, {}, {'encryption': body})
def put_types_1_encryption_1(self, body, **kw):
return (200, {}, {})
def delete_types_1_encryption_provider(self, **kw):
return (202, {}, None)
#
# Set/Unset metadata
#
def delete_volumes_1234_metadata_test_key(self, **kw):
return (204, {}, None)
def delete_volumes_1234_metadata_key1(self, **kw):
return (204, {}, None)
def delete_volumes_1234_metadata_key2(self, **kw):
return (204, {}, None)
def post_volumes_1234_metadata(self, **kw):
return (204, {}, {'metadata': {'test_key': 'test_value'}})
#
# List all extensions
#
def get_extensions(self, **kw):
exts = [
{
"alias": "FAKE-1",
"description": "Fake extension number 1",
"links": [],
"name": "Fake1",
"namespace": ("http://docs.openstack.org/"
"/ext/fake1/api/v1.1"),
"updated": "2011-06-09T00:00:00+00:00"
},
{
"alias": "FAKE-2",
"description": "Fake extension number 2",
"links": [],
"name": "Fake2",
"namespace": ("http://docs.openstack.org/"
"/ext/fake1/api/v1.1"),
"updated": "2011-06-09T00:00:00+00:00"
},
]
return (200, {}, {"extensions": exts, })
#
# VolumeBackups
#
def get_backups_76a17945_3c6f_435c_975b_b5685db10b62(self, **kw):
base_uri = 'http://localhost:8776'
tenant_id = '0fa851f6668144cf9cd8c8419c1646c1'
backup1 = '76a17945-3c6f-435c-975b-b5685db10b62'
return (200, {},
{'backup': _stub_backup_full(backup1, base_uri, tenant_id)})
def get_backups_detail(self, **kw):
base_uri = 'http://localhost:8776'
tenant_id = '0fa851f6668144cf9cd8c8419c1646c1'
backup1 = '76a17945-3c6f-435c-975b-b5685db10b62'
backup2 = 'd09534c6-08b8-4441-9e87-8976f3a8f699'
return (200, {},
{'backups': [
_stub_backup_full(backup1, base_uri, tenant_id),
_stub_backup_full(backup2, base_uri, tenant_id)]})
def delete_backups_76a17945_3c6f_435c_975b_b5685db10b62(self, **kw):
return (202, {}, None)
def post_backups(self, **kw):
base_uri = 'http://localhost:8776'
tenant_id = '0fa851f6668144cf9cd8c8419c1646c1'
backup1 = '76a17945-3c6f-435c-975b-b5685db10b62'
return (202, {},
{'backup': _stub_backup(backup1, base_uri, tenant_id)})
def post_backups_76a17945_3c6f_435c_975b_b5685db10b62_restore(self, **kw):
return (200, {},
{'restore': _stub_restore()})
def post_backups_1234_restore(self, **kw):
return (200, {},
{'restore': _stub_restore()})
#
# QoSSpecs
#
def get_qos_specs_1B6B6A04_A927_4AEB_810B_B7BAAD49F57C(self, **kw):
base_uri = 'http://localhost:8776'
tenant_id = '0fa851f6668144cf9cd8c8419c1646c1'
qos_id1 = '1B6B6A04-A927-4AEB-810B-B7BAAD49F57C'
return (200, {},
_stub_qos_full(qos_id1, base_uri, tenant_id))
def get_qos_specs(self, **kw):
base_uri = 'http://localhost:8776'
tenant_id = '0fa851f6668144cf9cd8c8419c1646c1'
qos_id1 = '1B6B6A04-A927-4AEB-810B-B7BAAD49F57C'
qos_id2 = '0FD8DD14-A396-4E55-9573-1FE59042E95B'
return (200, {},
{'qos_specs': [
_stub_qos_full(qos_id1, base_uri, tenant_id, 'name-1'),
_stub_qos_full(qos_id2, base_uri, tenant_id)]})
def post_qos_specs(self, **kw):
base_uri = 'http://localhost:8776'
tenant_id = '0fa851f6668144cf9cd8c8419c1646c1'
qos_id = '1B6B6A04-A927-4AEB-810B-B7BAAD49F57C'
qos_name = 'qos-name'
return (202, {},
_stub_qos_full(qos_id, base_uri, tenant_id, qos_name))
def put_qos_specs_1B6B6A04_A927_4AEB_810B_B7BAAD49F57C(self, **kw):
return (202, {}, None)
def put_qos_specs_1B6B6A04_A927_4AEB_810B_B7BAAD49F57C_delete_keys(
self, **kw):
return (202, {}, None)
def delete_qos_specs_1B6B6A04_A927_4AEB_810B_B7BAAD49F57C(self, **kw):
return (202, {}, None)
def get_qos_specs_1B6B6A04_A927_4AEB_810B_B7BAAD49F57C_associations(
self, **kw):
type_id1 = '4230B13A-7A37-4E84-B777-EFBA6FCEE4FF'
type_id2 = '4230B13A-AB37-4E84-B777-EFBA6FCEE4FF'
type_name1 = 'type1'
type_name2 = 'type2'
return (202, {},
{'qos_associations': [
_stub_qos_associates(type_id1, type_name1),
_stub_qos_associates(type_id2, type_name2)]})
def get_qos_specs_1B6B6A04_A927_4AEB_810B_B7BAAD49F57C_associate(
self, **kw):
return (202, {}, None)
def get_qos_specs_1B6B6A04_A927_4AEB_810B_B7BAAD49F57C_disassociate(
self, **kw):
return (202, {}, None)
def get_qos_specs_1B6B6A04_A927_4AEB_810B_B7BAAD49F57C_disassociate_all(
self, **kw):
return (202, {}, None)
#
# VolumeTransfers
#
def get_os_volume_transfer_5678(self, **kw):
base_uri = 'http://localhost:8776'
tenant_id = '0fa851f6668144cf9cd8c8419c1646c1'
transfer1 = '5678'
return (200, {},
{'transfer':
_stub_transfer_full(transfer1, base_uri, tenant_id)})
def get_os_volume_transfer_detail(self, **kw):
base_uri = 'http://localhost:8776'
tenant_id = '0fa851f6668144cf9cd8c8419c1646c1'
transfer1 = '5678'
transfer2 = 'f625ec3e-13dd-4498-a22a-50afd534cc41'
return (200, {},
{'transfers': [
_stub_transfer_full(transfer1, base_uri, tenant_id),
_stub_transfer_full(transfer2, base_uri, tenant_id)]})
def delete_os_volume_transfer_5678(self, **kw):
return (202, {}, None)
def post_os_volume_transfer(self, **kw):
base_uri = 'http://localhost:8776'
tenant_id = '0fa851f6668144cf9cd8c8419c1646c1'
transfer1 = '5678'
return (202, {},
{'transfer': _stub_transfer(transfer1, base_uri, tenant_id)})
def post_os_volume_transfer_5678_accept(self, **kw):
base_uri = 'http://localhost:8776'
tenant_id = '0fa851f6668144cf9cd8c8419c1646c1'
transfer1 = '5678'
return (200, {},
{'transfer': _stub_transfer(transfer1, base_uri, tenant_id)})
#
# Services
#
def get_os_services(self, **kw):
host = kw.get('host', None)
binary = kw.get('binary', None)
services = [
{
'binary': 'cinder-volume',
'host': 'host1',
'zone': 'cinder',
'status': 'enabled',
'state': 'up',
'updated_at': datetime(2012, 10, 29, 13, 42, 2)
},
{
'binary': 'cinder-volume',
'host': 'host2',
'zone': 'cinder',
'status': 'disabled',
'state': 'down',
'updated_at': datetime(2012, 9, 18, 8, 3, 38)
},
{
'binary': 'cinder-scheduler',
'host': 'host2',
'zone': 'cinder',
'status': 'disabled',
'state': 'down',
'updated_at': datetime(2012, 9, 18, 8, 3, 38)
},
]
if host:
services = [i for i in services if i['host'] == host]
if binary:
services = [i for i in services if i['binary'] == binary]
return (200, {}, {'services': services})
def put_os_services_enable(self, body, **kw):
return (200, {}, {'host': body['host'], 'binary': body['binary'],
'status': 'enabled'})
def put_os_services_disable(self, body, **kw):
return (200, {}, {'host': body['host'], 'binary': body['binary'],
'status': 'disabled'})
def put_os_services_disable_log_reason(self, body, **kw):
return (200, {}, {'host': body['host'], 'binary': body['binary'],
'status': 'disabled',
'disabled_reason': body['disabled_reason']})
def get_os_availability_zone(self, **kw):
return (200, {}, {
"availabilityZoneInfo": [
{
"zoneName": "zone-1",
"zoneState": {"available": True},
"hosts": None,
},
{
"zoneName": "zone-2",
"zoneState": {"available": False},
"hosts": None,
},
]
})
def get_os_availability_zone_detail(self, **kw):
return (200, {}, {
"availabilityZoneInfo": [
{
"zoneName": "zone-1",
"zoneState": {"available": True},
"hosts": {
"fake_host-1": {
"cinder-volume": {
"active": True,
"available": True,
"updated_at":
datetime(2012, 12, 26, 14, 45, 25, 0)
}
}
}
},
{
"zoneName": "internal",
"zoneState": {"available": True},
"hosts": {
"fake_host-1": {
"cinder-sched": {
"active": True,
"available": True,
"updated_at":
datetime(2012, 12, 26, 14, 45, 24, 0)
}
}
}
},
{
"zoneName": "zone-2",
"zoneState": {"available": False},
"hosts": None,
},
]
})
def post_snapshots_1234_metadata(self, **kw):
return (200, {}, {"metadata": {"key1": "val1", "key2": "val2"}})
def delete_snapshots_1234_metadata_key1(self, **kw):
return (200, {}, None)
def delete_snapshots_1234_metadata_key2(self, **kw):
return (200, {}, None)
def put_volumes_1234_metadata(self, **kw):
return (200, {}, {"metadata": {"key1": "val1", "key2": "val2"}})
def put_snapshots_1234_metadata(self, **kw):
return (200, {}, {"metadata": {"key1": "val1", "key2": "val2"}})

View File

@ -1,338 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import mock
import requests
from cinderclient.v1 import client
from cinderclient import exceptions
from cinderclient.tests.unit import utils
class AuthenticateAgainstKeystoneTests(utils.TestCase):
def test_authenticate_success(self):
cs = client.Client("username", "password", "project_id",
"http://localhost:8776/v1", service_type='volume')
resp = {
"access": {
"token": {
"expires": "2014-11-01T03:32:15-05:00",
"id": "FAKE_ID",
},
"serviceCatalog": [
{
"type": "volume",
"endpoints": [
{
"region": "RegionOne",
"adminURL": "http://localhost:8776/v1",
"internalURL": "http://localhost:8776/v1",
"publicURL": "http://localhost:8776/v1",
},
],
},
],
},
}
auth_response = utils.TestResponse({
"status_code": 200,
"text": json.dumps(resp),
})
mock_request = mock.Mock(return_value=(auth_response))
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
cs.client.authenticate()
headers = {
'User-Agent': cs.client.USER_AGENT,
'Content-Type': 'application/json',
'Accept': 'application/json',
}
body = {
'auth': {
'passwordCredentials': {
'username': cs.client.user,
'password': cs.client.password,
},
'tenantName': cs.client.projectid,
},
}
token_url = cs.client.auth_url + "/tokens"
mock_request.assert_called_with(
"POST",
token_url,
headers=headers,
data=json.dumps(body),
allow_redirects=True,
**self.TEST_REQUEST_BASE)
endpoints = resp["access"]["serviceCatalog"][0]['endpoints']
public_url = endpoints[0]["publicURL"].rstrip('/')
self.assertEqual(public_url, cs.client.management_url)
token_id = resp["access"]["token"]["id"]
self.assertEqual(token_id, cs.client.auth_token)
test_auth_call()
def test_authenticate_tenant_id(self):
cs = client.Client("username", "password",
auth_url="http://localhost:8776/v1",
tenant_id='tenant_id', service_type='volume')
resp = {
"access": {
"token": {
"expires": "2014-11-01T03:32:15-05:00",
"id": "FAKE_ID",
"tenant": {
"description": None,
"enabled": True,
"id": "tenant_id",
"name": "demo"
} # tenant associated with token
},
"serviceCatalog": [
{
"type": "volume",
"endpoints": [
{
"region": "RegionOne",
"adminURL": "http://localhost:8776/v1",
"internalURL": "http://localhost:8776/v1",
"publicURL": "http://localhost:8776/v1",
},
],
},
],
},
}
auth_response = utils.TestResponse({
"status_code": 200,
"text": json.dumps(resp),
})
mock_request = mock.Mock(return_value=(auth_response))
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
cs.client.authenticate()
headers = {
'User-Agent': cs.client.USER_AGENT,
'Content-Type': 'application/json',
'Accept': 'application/json',
}
body = {
'auth': {
'passwordCredentials': {
'username': cs.client.user,
'password': cs.client.password,
},
'tenantId': cs.client.tenant_id,
},
}
token_url = cs.client.auth_url + "/tokens"
mock_request.assert_called_with(
"POST",
token_url,
headers=headers,
data=json.dumps(body),
allow_redirects=True,
**self.TEST_REQUEST_BASE)
endpoints = resp["access"]["serviceCatalog"][0]['endpoints']
public_url = endpoints[0]["publicURL"].rstrip('/')
self.assertEqual(public_url, cs.client.management_url)
token_id = resp["access"]["token"]["id"]
self.assertEqual(token_id, cs.client.auth_token)
tenant_id = resp["access"]["token"]["tenant"]["id"]
self.assertEqual(tenant_id, cs.client.tenant_id)
test_auth_call()
def test_authenticate_failure(self):
cs = client.Client("username", "password", "project_id",
"http://localhost:8776/v1")
resp = {"unauthorized": {"message": "Unauthorized", "code": "401"}}
auth_response = utils.TestResponse({
"status_code": 401,
"text": json.dumps(resp),
})
mock_request = mock.Mock(return_value=(auth_response))
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
self.assertRaises(exceptions.Unauthorized, cs.client.authenticate)
test_auth_call()
def test_auth_redirect(self):
cs = client.Client("username", "password", "project_id",
"http://localhost:8776/v1", service_type='volume')
dict_correct_response = {
"access": {
"token": {
"expires": "2014-11-01T03:32:15-05:00",
"id": "FAKE_ID",
},
"serviceCatalog": [
{
"type": "volume",
"endpoints": [
{
"adminURL": "http://localhost:8776/v1",
"region": "RegionOne",
"internalURL": "http://localhost:8776/v1",
"publicURL": "http://localhost:8776/v1/",
},
],
},
],
},
}
correct_response = json.dumps(dict_correct_response)
dict_responses = [
{"headers": {'location': 'http://127.0.0.1:5001'},
"status_code": 305,
"text": "Use proxy"},
# Configured on admin port, cinder redirects to v2.0 port.
# When trying to connect on it, keystone auth succeed by v1.0
# protocol (through headers) but tokens are being returned in
# body (looks like keystone bug). Leaved for compatibility.
{"headers": {},
"status_code": 200,
"text": correct_response},
{"headers": {},
"status_code": 200,
"text": correct_response}
]
responses = [(utils.TestResponse(resp)) for resp in dict_responses]
def side_effect(*args, **kwargs):
return responses.pop(0)
mock_request = mock.Mock(side_effect=side_effect)
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
cs.client.authenticate()
headers = {
'User-Agent': cs.client.USER_AGENT,
'Content-Type': 'application/json',
'Accept': 'application/json',
}
body = {
'auth': {
'passwordCredentials': {
'username': cs.client.user,
'password': cs.client.password,
},
'tenantName': cs.client.projectid,
},
}
token_url = cs.client.auth_url + "/tokens"
mock_request.assert_called_with(
"POST",
token_url,
headers=headers,
data=json.dumps(body),
allow_redirects=True,
**self.TEST_REQUEST_BASE)
resp = dict_correct_response
endpoints = resp["access"]["serviceCatalog"][0]['endpoints']
public_url = endpoints[0]["publicURL"].rstrip('/')
self.assertEqual(public_url, cs.client.management_url)
token_id = resp["access"]["token"]["id"]
self.assertEqual(token_id, cs.client.auth_token)
test_auth_call()
class AuthenticationTests(utils.TestCase):
def test_authenticate_success(self):
cs = client.Client("username", "password", "project_id", "auth_url")
management_url = 'https://localhost/v1.1/443470'
auth_response = utils.TestResponse({
'status_code': 204,
'headers': {
'x-server-management-url': management_url,
'x-auth-token': '1b751d74-de0c-46ae-84f0-915744b582d1',
},
})
mock_request = mock.Mock(return_value=(auth_response))
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
cs.client.authenticate()
headers = {
'Accept': 'application/json',
'X-Auth-User': 'username',
'X-Auth-Key': 'password',
'X-Auth-Project-Id': 'project_id',
'User-Agent': cs.client.USER_AGENT
}
mock_request.assert_called_with(
"GET",
cs.client.auth_url,
headers=headers,
**self.TEST_REQUEST_BASE)
self.assertEqual(auth_response.headers['x-server-management-url'],
cs.client.management_url)
self.assertEqual(auth_response.headers['x-auth-token'],
cs.client.auth_token)
test_auth_call()
def test_authenticate_failure(self):
cs = client.Client("username", "password", "project_id", "auth_url")
auth_response = utils.TestResponse({"status_code": 401})
mock_request = mock.Mock(return_value=(auth_response))
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
self.assertRaises(exceptions.Unauthorized, cs.client.authenticate)
test_auth_call()
def test_auth_automatic(self):
cs = client.Client("username", "password", "project_id", "auth_url")
http_client = cs.client
http_client.management_url = ''
mock_request = mock.Mock(return_value=(None, None))
@mock.patch.object(http_client, 'request', mock_request)
@mock.patch.object(http_client, 'authenticate')
def test_auth_call(m):
http_client.get('/')
self.assertTrue(m.called)
self.assertTrue(mock_request.called)
test_auth_call()
def test_auth_manual(self):
cs = client.Client("username", "password", "project_id", "auth_url")
@mock.patch.object(cs.client, 'authenticate')
def test_auth_call(m):
cs.authenticate()
self.assertTrue(m.called)
test_auth_call()

View File

@ -1,88 +0,0 @@
# Copyright 2011-2013 OpenStack Foundation
# Copyright 2013 IBM Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import six
from cinderclient.v1 import availability_zones
from cinderclient.v1 import shell
from cinderclient.tests.unit.fixture_data import client
from cinderclient.tests.unit.fixture_data import availability_zones as azfixture # noqa
from cinderclient.tests.unit import utils
class AvailabilityZoneTest(utils.FixturedTestCase):
client_fixture_class = client.V1
data_fixture_class = azfixture.Fixture
def _assertZone(self, zone, name, status):
self.assertEqual(name, zone.zoneName)
self.assertEqual(status, zone.zoneState)
def test_list_availability_zone(self):
zones = self.cs.availability_zones.list(detailed=False)
self.assert_called('GET', '/os-availability-zone')
for zone in zones:
self.assertIsInstance(zone,
availability_zones.AvailabilityZone)
self.assertEqual(2, len(zones))
l0 = [six.u('zone-1'), six.u('available')]
l1 = [six.u('zone-2'), six.u('not available')]
z0 = shell._treeizeAvailabilityZone(zones[0])
z1 = shell._treeizeAvailabilityZone(zones[1])
self.assertEqual((1, 1), (len(z0), len(z1)))
self._assertZone(z0[0], l0[0], l0[1])
self._assertZone(z1[0], l1[0], l1[1])
def test_detail_availability_zone(self):
zones = self.cs.availability_zones.list(detailed=True)
self.assert_called('GET', '/os-availability-zone/detail')
for zone in zones:
self.assertIsInstance(zone,
availability_zones.AvailabilityZone)
self.assertEqual(3, len(zones))
l0 = [six.u('zone-1'), six.u('available')]
l1 = [six.u('|- fake_host-1'), six.u('')]
l2 = [six.u('| |- cinder-volume'),
six.u('enabled :-) 2012-12-26 14:45:25')]
l3 = [six.u('internal'), six.u('available')]
l4 = [six.u('|- fake_host-1'), six.u('')]
l5 = [six.u('| |- cinder-sched'),
six.u('enabled :-) 2012-12-26 14:45:24')]
l6 = [six.u('zone-2'), six.u('not available')]
z0 = shell._treeizeAvailabilityZone(zones[0])
z1 = shell._treeizeAvailabilityZone(zones[1])
z2 = shell._treeizeAvailabilityZone(zones[2])
self.assertEqual((3, 3, 1), (len(z0), len(z1), len(z2)))
self._assertZone(z0[0], l0[0], l0[1])
self._assertZone(z0[1], l1[0], l1[1])
self._assertZone(z0[2], l2[0], l2[1])
self._assertZone(z1[0], l3[0], l3[1])
self._assertZone(z1[1], l4[0], l4[1])
self._assertZone(z1[2], l5[0], l5[1])
self._assertZone(z2[0], l6[0], l6[1])

View File

@ -1,164 +0,0 @@
# Copyright 2014 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import mock
from cinderclient.tests.unit import utils
from cinderclient.v1 import limits
def _get_default_RateLimit(verb="verb1", uri="uri1", regex="regex1",
value="value1",
remain="remain1", unit="unit1",
next_available="next1"):
return limits.RateLimit(verb, uri, regex, value, remain, unit,
next_available)
class TestLimits(utils.TestCase):
def test_repr(self):
l = limits.Limits(None, {"foo": "bar"})
self.assertEqual("<Limits>", repr(l))
def test_absolute(self):
l = limits.Limits(None,
{"absolute": {"name1": "value1", "name2": "value2"}})
l1 = limits.AbsoluteLimit("name1", "value1")
l2 = limits.AbsoluteLimit("name2", "value2")
for item in l.absolute:
self.assertIn(item, [l1, l2])
def test_rate(self):
l = limits.Limits(None,
{
"rate": [
{
"uri": "uri1",
"regex": "regex1",
"limit": [
{
"verb": "verb1",
"value": "value1",
"remaining": "remain1",
"unit": "unit1",
"next-available": "next1",
},
],
},
{
"uri": "uri2",
"regex": "regex2",
"limit": [
{
"verb": "verb2",
"value": "value2",
"remaining": "remain2",
"unit": "unit2",
"next-available": "next2",
},
],
},
],
})
l1 = limits.RateLimit("verb1", "uri1", "regex1", "value1", "remain1",
"unit1", "next1")
l2 = limits.RateLimit("verb2", "uri2", "regex2", "value2", "remain2",
"unit2", "next2")
for item in l.rate:
self.assertIn(item, [l1, l2])
class TestRateLimit(utils.TestCase):
def test_equal(self):
l1 = _get_default_RateLimit()
l2 = _get_default_RateLimit()
self.assertEqual(l1, l2)
def test_not_equal_verbs(self):
l1 = _get_default_RateLimit()
l2 = _get_default_RateLimit(verb="verb2")
self.assertNotEqual(l1, l2)
def test_not_equal_uris(self):
l1 = _get_default_RateLimit()
l2 = _get_default_RateLimit(uri="uri2")
self.assertNotEqual(l1, l2)
def test_not_equal_regexps(self):
l1 = _get_default_RateLimit()
l2 = _get_default_RateLimit(regex="regex2")
self.assertNotEqual(l1, l2)
def test_not_equal_values(self):
l1 = _get_default_RateLimit()
l2 = _get_default_RateLimit(value="value2")
self.assertNotEqual(l1, l2)
def test_not_equal_remains(self):
l1 = _get_default_RateLimit()
l2 = _get_default_RateLimit(remain="remain2")
self.assertNotEqual(l1, l2)
def test_not_equal_units(self):
l1 = _get_default_RateLimit()
l2 = _get_default_RateLimit(unit="unit2")
self.assertNotEqual(l1, l2)
def test_not_equal_next_available(self):
l1 = _get_default_RateLimit()
l2 = _get_default_RateLimit(next_available="next2")
self.assertNotEqual(l1, l2)
def test_repr(self):
l1 = _get_default_RateLimit()
self.assertEqual("<RateLimit: method=verb1 uri=uri1>", repr(l1))
class TestAbsoluteLimit(utils.TestCase):
def test_equal(self):
l1 = limits.AbsoluteLimit("name1", "value1")
l2 = limits.AbsoluteLimit("name1", "value1")
self.assertEqual(l1, l2)
def test_not_equal_values(self):
l1 = limits.AbsoluteLimit("name1", "value1")
l2 = limits.AbsoluteLimit("name1", "value2")
self.assertNotEqual(l1, l2)
def test_not_equal_names(self):
l1 = limits.AbsoluteLimit("name1", "value1")
l2 = limits.AbsoluteLimit("name2", "value1")
self.assertNotEqual(l1, l2)
def test_repr(self):
l1 = limits.AbsoluteLimit("name1", "value1")
self.assertEqual("<AbsoluteLimit: name=name1>", repr(l1))
class TestLimitsManager(utils.TestCase):
def test_get(self):
api = mock.Mock()
api.client.get.return_value = (
None,
{"limits": {"absolute": {"name1": "value1", }},
"no-limits": {"absolute": {"name2": "value2", }}})
l1 = limits.AbsoluteLimit("name1", "value1")
limitsManager = limits.LimitsManager(api)
lim = limitsManager.get()
self.assertIsInstance(lim, limits.Limits)
for l in lim.absolute:
self.assertEqual(l1, l)

View File

@ -1,79 +0,0 @@
# Copyright (C) 2013 eBay Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient.tests.unit import utils
from cinderclient.tests.unit.v1 import fakes
cs = fakes.FakeClient()
class QoSSpecsTest(utils.TestCase):
def test_create(self):
specs = dict(k1='v1', k2='v2')
cs.qos_specs.create('qos-name', specs)
cs.assert_called('POST', '/qos-specs')
def test_get(self):
qos_id = '1B6B6A04-A927-4AEB-810B-B7BAAD49F57C'
cs.qos_specs.get(qos_id)
cs.assert_called('GET', '/qos-specs/%s' % qos_id)
def test_list(self):
cs.qos_specs.list()
cs.assert_called('GET', '/qos-specs')
def test_delete(self):
cs.qos_specs.delete('1B6B6A04-A927-4AEB-810B-B7BAAD49F57C')
cs.assert_called('DELETE',
'/qos-specs/1B6B6A04-A927-4AEB-810B-B7BAAD49F57C?'
'force=False')
def test_set_keys(self):
body = {'qos_specs': dict(k1='v1')}
qos_id = '1B6B6A04-A927-4AEB-810B-B7BAAD49F57C'
cs.qos_specs.set_keys(qos_id, body)
cs.assert_called('PUT', '/qos-specs/%s' % qos_id)
def test_unset_keys(self):
qos_id = '1B6B6A04-A927-4AEB-810B-B7BAAD49F57C'
body = {'keys': ['k1']}
cs.qos_specs.unset_keys(qos_id, body)
cs.assert_called('PUT', '/qos-specs/%s/delete_keys' % qos_id)
def test_get_associations(self):
qos_id = '1B6B6A04-A927-4AEB-810B-B7BAAD49F57C'
cs.qos_specs.get_associations(qos_id)
cs.assert_called('GET', '/qos-specs/%s/associations' % qos_id)
def test_associate(self):
qos_id = '1B6B6A04-A927-4AEB-810B-B7BAAD49F57C'
type_id = '4230B13A-7A37-4E84-B777-EFBA6FCEE4FF'
cs.qos_specs.associate(qos_id, type_id)
cs.assert_called('GET', '/qos-specs/%s/associate?vol_type_id=%s'
% (qos_id, type_id))
def test_disassociate(self):
qos_id = '1B6B6A04-A927-4AEB-810B-B7BAAD49F57C'
type_id = '4230B13A-7A37-4E84-B777-EFBA6FCEE4FF'
cs.qos_specs.disassociate(qos_id, type_id)
cs.assert_called('GET', '/qos-specs/%s/disassociate?vol_type_id=%s'
% (qos_id, type_id))
def test_disassociate_all(self):
qos_id = '1B6B6A04-A927-4AEB-810B-B7BAAD49F57C'
cs.qos_specs.disassociate_all(qos_id)
cs.assert_called('GET', '/qos-specs/%s/disassociate_all' % qos_id)

View File

@ -1,59 +0,0 @@
# Copyright (c) 2011 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient.tests.unit import utils
from cinderclient.tests.unit.v1 import fakes
cs = fakes.FakeClient()
class QuotaClassSetsTest(utils.TestCase):
def test_class_quotas_get(self):
class_name = 'test'
cs.quota_classes.get(class_name)
cs.assert_called('GET', '/os-quota-class-sets/%s' % class_name)
def test_update_quota(self):
q = cs.quota_classes.get('test')
q.update(volumes=2, snapshots=2, gigabytes=2000,
backups=2, backup_gigabytes=2000)
cs.assert_called('PUT', '/os-quota-class-sets/test')
def test_refresh_quota(self):
q = cs.quota_classes.get('test')
q2 = cs.quota_classes.get('test')
self.assertEqual(q.volumes, q2.volumes)
self.assertEqual(q.snapshots, q2.snapshots)
self.assertEqual(q.gigabytes, q2.gigabytes)
self.assertEqual(q.backups, q2.backups)
self.assertEqual(q.backup_gigabytes, q2.backup_gigabytes)
q2.volumes = 0
self.assertNotEqual(q.volumes, q2.volumes)
q2.snapshots = 0
self.assertNotEqual(q.snapshots, q2.snapshots)
q2.gigabytes = 0
self.assertNotEqual(q.gigabytes, q2.gigabytes)
q2.backups = 0
self.assertNotEqual(q.backups, q2.backups)
q2.backup_gigabytes = 0
self.assertNotEqual(q.backup_gigabytes, q2.backup_gigabytes)
q2.get()
self.assertEqual(q.volumes, q2.volumes)
self.assertEqual(q.snapshots, q2.snapshots)
self.assertEqual(q.gigabytes, q2.gigabytes)
self.assertEqual(q.backups, q2.backups)
self.assertEqual(q.backup_gigabytes, q2.backup_gigabytes)

View File

@ -1,62 +0,0 @@
# Copyright (c) 2011 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient.tests.unit import utils
from cinderclient.tests.unit.v1 import fakes
cs = fakes.FakeClient()
class QuotaSetsTest(utils.TestCase):
def test_tenant_quotas_get(self):
tenant_id = 'test'
cs.quotas.get(tenant_id)
cs.assert_called('GET', '/os-quota-sets/%s?usage=False' % tenant_id)
def test_tenant_quotas_defaults(self):
tenant_id = 'test'
cs.quotas.defaults(tenant_id)
cs.assert_called('GET', '/os-quota-sets/%s/defaults' % tenant_id)
def test_update_quota(self):
q = cs.quotas.get('test')
q.update(volumes=2)
q.update(snapshots=2)
q.update(backups=2)
cs.assert_called('PUT', '/os-quota-sets/test')
def test_refresh_quota(self):
q = cs.quotas.get('test')
q2 = cs.quotas.get('test')
self.assertEqual(q.volumes, q2.volumes)
self.assertEqual(q.snapshots, q2.snapshots)
self.assertEqual(q.backups, q2.backups)
q2.volumes = 0
self.assertNotEqual(q.volumes, q2.volumes)
q2.snapshots = 0
self.assertNotEqual(q.snapshots, q2.snapshots)
q2.backups = 0
self.assertNotEqual(q.backups, q2.backups)
q2.get()
self.assertEqual(q.volumes, q2.volumes)
self.assertEqual(q.snapshots, q2.snapshots)
self.assertEqual(q.backups, q2.backups)
def test_delete_quota(self):
tenant_id = 'test'
cs.quotas.delete(tenant_id)
cs.assert_called('DELETE', '/os-quota-sets/test')

View File

@ -1,91 +0,0 @@
# Copyright (c) 2013 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient.tests.unit import utils
from cinderclient.tests.unit.v1 import fakes
from cinderclient.v1 import services
cs = fakes.FakeClient()
FAKE_SERVICE = {"host": "host1",
'binary': 'cinder-volume',
"status": "enable",
"availability_zone": "nova"}
class ServicesTest(utils.TestCase):
def test_list_services(self):
svs = cs.services.list()
cs.assert_called('GET', '/os-services')
self.assertEqual(3, len(svs))
[self.assertIsInstance(s, services.Service) for s in svs]
def test_list_services_with_hostname(self):
svs = cs.services.list(host='host2')
cs.assert_called('GET', '/os-services?host=host2')
self.assertEqual(2, len(svs))
[self.assertIsInstance(s, services.Service) for s in svs]
[self.assertEqual('host2', s.host) for s in svs]
def test_list_services_with_binary(self):
svs = cs.services.list(binary='cinder-volume')
cs.assert_called('GET', '/os-services?binary=cinder-volume')
self.assertEqual(2, len(svs))
[self.assertIsInstance(s, services.Service) for s in svs]
[self.assertEqual('cinder-volume', s.binary) for s in svs]
def test_list_services_with_host_binary(self):
svs = cs.services.list('host2', 'cinder-volume')
cs.assert_called('GET', '/os-services?host=host2&binary=cinder-volume')
self.assertEqual(1, len(svs))
[self.assertIsInstance(s, services.Service) for s in svs]
[self.assertEqual('host2', s.host) for s in svs]
[self.assertEqual('cinder-volume', s.binary) for s in svs]
def test_services_enable(self):
s = cs.services.enable('host1', 'cinder-volume')
values = {"host": "host1", 'binary': 'cinder-volume'}
cs.assert_called('PUT', '/os-services/enable', values)
self.assertIsInstance(s, services.Service)
self.assertEqual('enabled', s.status)
def test_services_disable(self):
s = cs.services.disable('host1', 'cinder-volume')
values = {"host": "host1", 'binary': 'cinder-volume'}
cs.assert_called('PUT', '/os-services/disable', values)
self.assertIsInstance(s, services.Service)
self.assertEqual('disabled', s.status)
def test_services_disable_log_reason(self):
s = cs.services.disable_log_reason(
'host1', 'cinder-volume', 'disable bad host')
values = {"host": "host1", 'binary': 'cinder-volume',
"disabled_reason": "disable bad host"}
cs.assert_called('PUT', '/os-services/disable-log-reason', values)
self.assertIsInstance(s, services.Service)
self.assertEqual('disabled', s.status)
def test___repr__(self):
"""
Unit test for Service.__repr__
Verify that one Service object can be printed.
"""
svs = services.Service(None, FAKE_SERVICE)
self.assertEqual(
"<Service: binary=%s host=%s>" % (FAKE_SERVICE['binary'],
FAKE_SERVICE['host']), repr(svs))

View File

@ -1,489 +0,0 @@
# Copyright 2010 Jacob Kaplan-Moss
# Copyright (c) 2011 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import fixtures
import mock
from requests_mock.contrib import fixture as requests_mock_fixture
from cinderclient import client
from cinderclient import exceptions
from cinderclient import shell
from cinderclient.v1 import shell as shell_v1
from cinderclient.tests.unit.v1 import fakes
from cinderclient.tests.unit import utils
from cinderclient.tests.unit.fixture_data import keystone_client
@mock.patch.object(client, 'Client', fakes.FakeClient)
class ShellTest(utils.TestCase):
FAKE_ENV = {
'CINDER_USERNAME': 'username',
'CINDER_PASSWORD': 'password',
'CINDER_PROJECT_ID': 'project_id',
'OS_VOLUME_API_VERSION': '1',
'CINDER_URL': keystone_client.BASE_URL,
}
# Patch os.environ to avoid required auth info.
def setUp(self):
"""Run before each test."""
super(ShellTest, self).setUp()
for var in self.FAKE_ENV:
self.useFixture(fixtures.EnvironmentVariable(var,
self.FAKE_ENV[var]))
self.shell = shell.OpenStackCinderShell()
# HACK(bcwaldon): replace this when we start using stubs
self.old_client_class = client.Client
client.Client = fakes.FakeClient
self.requests = self.useFixture(requests_mock_fixture.Fixture())
self.requests.register_uri(
'GET', keystone_client.BASE_URL,
text=keystone_client.keystone_request_callback)
def run_command(self, cmd):
self.shell.main(cmd.split())
def assert_called(self, method, url, body=None, **kwargs):
return self.shell.cs.assert_called(method, url, body, **kwargs)
def assert_called_anytime(self, method, url, body=None):
return self.shell.cs.assert_called_anytime(method, url, body)
def test_extract_metadata(self):
# mimic the result of argparse's parse_args() method
class Arguments(object):
def __init__(self, metadata=None):
self.metadata = metadata or []
inputs = [
([], {}),
(["key=value"], {"key": "value"}),
(["key"], {"key": None}),
(["k1=v1", "k2=v2"], {"k1": "v1", "k2": "v2"}),
(["k1=v1", "k2"], {"k1": "v1", "k2": None}),
(["k1", "k2=v2"], {"k1": None, "k2": "v2"})
]
for input in inputs:
args = Arguments(metadata=input[0])
self.assertEqual(input[1], shell_v1._extract_metadata(args))
def test_translate_volume_keys(self):
cs = fakes.FakeClient()
v = cs.volumes.list()[0]
setattr(v, 'os-vol-tenant-attr:tenant_id', 'fake_tenant')
setattr(v, '_info', {'attachments': [{'server_id': 1234}],
'id': 1234, 'display_name': 'sample-volume',
'os-vol-tenant-attr:tenant_id': 'fake_tenant'})
shell_v1._translate_volume_keys([v])
self.assertEqual('fake_tenant', v.tenant_id)
def test_list(self):
self.run_command('list')
# NOTE(jdg): we default to detail currently
self.assert_called('GET', '/volumes/detail')
def test_list_filter_tenant_with_all_tenants(self):
self.run_command('list --tenant=123 --all-tenants 1')
self.assert_called('GET',
'/volumes/detail?all_tenants=1&project_id=123')
def test_list_filter_tenant_without_all_tenants(self):
self.run_command('list --tenant=123')
self.assert_called('GET',
'/volumes/detail?all_tenants=1&project_id=123')
def test_metadata_args_with_limiter(self):
self.run_command('create --metadata key1="--test1" 1')
expected = {'volume': {'snapshot_id': None,
'display_description': None,
'source_volid': None,
'status': 'creating',
'size': 1,
'volume_type': None,
'imageRef': None,
'availability_zone': None,
'attach_status': 'detached',
'user_id': None,
'project_id': None,
'metadata': {'key1': '"--test1"'},
'display_name': None}}
self.assert_called_anytime('POST', '/volumes', expected)
def test_metadata_args_limiter_display_name(self):
self.run_command('create --metadata key1="--t1" --display-name="t" 1')
expected = {'volume': {'snapshot_id': None,
'display_description': None,
'source_volid': None,
'status': 'creating',
'size': 1,
'volume_type': None,
'imageRef': None,
'availability_zone': None,
'attach_status': 'detached',
'user_id': None,
'project_id': None,
'metadata': {'key1': '"--t1"'},
'display_name': '"t"'}}
self.assert_called_anytime('POST', '/volumes', expected)
def test_delimit_metadata_args(self):
self.run_command('create --metadata key1="test1" key2="test2" 1')
expected = {'volume': {'snapshot_id': None,
'display_description': None,
'source_volid': None,
'status': 'creating',
'size': 1,
'volume_type': None,
'imageRef': None,
'availability_zone': None,
'attach_status': 'detached',
'user_id': None,
'project_id': None,
'metadata': {'key1': '"test1"',
'key2': '"test2"'},
'display_name': None}}
self.assert_called_anytime('POST', '/volumes', expected)
def test_delimit_metadata_args_display_name(self):
self.run_command('create --metadata key1="t1" --display-name="t" 1')
expected = {'volume': {'snapshot_id': None,
'display_description': None,
'source_volid': None,
'status': 'creating',
'size': 1,
'volume_type': None,
'imageRef': None,
'availability_zone': None,
'attach_status': 'detached',
'user_id': None,
'project_id': None,
'metadata': {'key1': '"t1"'},
'display_name': '"t"'}}
self.assert_called_anytime('POST', '/volumes', expected)
def test_list_filter_status(self):
self.run_command('list --status=available')
self.assert_called('GET', '/volumes/detail?status=available')
def test_list_filter_display_name(self):
self.run_command('list --display-name=1234')
self.assert_called('GET', '/volumes/detail?display_name=1234')
def test_list_all_tenants(self):
self.run_command('list --all-tenants=1')
self.assert_called('GET', '/volumes/detail?all_tenants=1')
def test_list_availability_zone(self):
self.run_command('availability-zone-list')
self.assert_called('GET', '/os-availability-zone')
def test_list_limit(self):
self.run_command('list --limit=10')
self.assert_called('GET', '/volumes/detail?limit=10')
def test_show(self):
self.run_command('show 1234')
self.assert_called('GET', '/volumes/1234')
def test_delete(self):
self.run_command('delete 1234')
self.assert_called('DELETE', '/volumes/1234')
def test_delete_by_name(self):
self.run_command('delete sample-volume')
self.assert_called_anytime('GET', '/volumes/detail?all_tenants=1&'
'display_name=sample-volume')
self.assert_called('DELETE', '/volumes/1234')
def test_delete_multiple(self):
self.run_command('delete 1234 5678')
self.assert_called_anytime('DELETE', '/volumes/1234')
self.assert_called('DELETE', '/volumes/5678')
def test_backup(self):
self.run_command('backup-create 1234')
self.assert_called('POST', '/backups')
def test_restore(self):
self.run_command('backup-restore 1234')
self.assert_called('POST', '/backups/1234/restore')
def test_snapshot_list_filter_volume_id(self):
self.run_command('snapshot-list --volume-id=1234')
self.assert_called('GET', '/snapshots/detail?volume_id=1234')
def test_snapshot_list_filter_status_and_volume_id(self):
self.run_command('snapshot-list --status=available --volume-id=1234')
self.assert_called('GET', '/snapshots/detail?'
'status=available&volume_id=1234')
def test_rename(self):
# basic rename with positional arguments
self.run_command('rename 1234 new-name')
expected = {'volume': {'display_name': 'new-name'}}
self.assert_called('PUT', '/volumes/1234', body=expected)
# change description only
self.run_command('rename 1234 --display-description=new-description')
expected = {'volume': {'display_description': 'new-description'}}
self.assert_called('PUT', '/volumes/1234', body=expected)
# rename and change description
self.run_command('rename 1234 new-name '
'--display-description=new-description')
expected = {'volume': {
'display_name': 'new-name',
'display_description': 'new-description',
}}
self.assert_called('PUT', '/volumes/1234', body=expected)
# Call rename with no arguments
self.assertRaises(SystemExit, self.run_command, 'rename')
def test_rename_snapshot(self):
# basic rename with positional arguments
self.run_command('snapshot-rename 1234 new-name')
expected = {'snapshot': {'display_name': 'new-name'}}
self.assert_called('PUT', '/snapshots/1234', body=expected)
# change description only
self.run_command('snapshot-rename 1234 '
'--display-description=new-description')
expected = {'snapshot': {'display_description': 'new-description'}}
self.assert_called('PUT', '/snapshots/1234', body=expected)
# snapshot-rename and change description
self.run_command('snapshot-rename 1234 new-name '
'--display-description=new-description')
expected = {'snapshot': {
'display_name': 'new-name',
'display_description': 'new-description',
}}
self.assert_called('PUT', '/snapshots/1234', body=expected)
# Call snapshot-rename with no arguments
self.assertRaises(SystemExit, self.run_command, 'snapshot-rename')
def test_set_metadata_set(self):
self.run_command('metadata 1234 set key1=val1 key2=val2')
self.assert_called('POST', '/volumes/1234/metadata',
{'metadata': {'key1': 'val1', 'key2': 'val2'}})
def test_set_metadata_delete_dict(self):
self.run_command('metadata 1234 unset key1=val1 key2=val2')
self.assert_called('DELETE', '/volumes/1234/metadata/key1')
self.assert_called('DELETE', '/volumes/1234/metadata/key2', pos=-2)
def test_set_metadata_delete_keys(self):
self.run_command('metadata 1234 unset key1 key2')
self.assert_called('DELETE', '/volumes/1234/metadata/key1')
self.assert_called('DELETE', '/volumes/1234/metadata/key2', pos=-2)
def test_reset_state(self):
self.run_command('reset-state 1234')
expected = {'os-reset_status': {'status': 'available'}}
self.assert_called('POST', '/volumes/1234/action', body=expected)
def test_reset_state_attach(self):
self.run_command('reset-state --state in-use 1234')
expected = {'os-reset_status': {'status': 'in-use'}}
self.assert_called('POST', '/volumes/1234/action', body=expected)
def test_reset_state_with_flag(self):
self.run_command('reset-state --state error 1234')
expected = {'os-reset_status': {'status': 'error'}}
self.assert_called('POST', '/volumes/1234/action', body=expected)
def test_reset_state_multiple(self):
self.run_command('reset-state 1234 5678 --state error')
expected = {'os-reset_status': {'status': 'error'}}
self.assert_called_anytime('POST', '/volumes/1234/action',
body=expected)
self.assert_called_anytime('POST', '/volumes/5678/action',
body=expected)
def test_reset_state_two_with_one_nonexistent(self):
cmd = 'reset-state 1234 123456789'
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
expected = {'os-reset_status': {'status': 'available'}}
self.assert_called_anytime('POST', '/volumes/1234/action',
body=expected)
def test_reset_state_one_with_one_nonexistent(self):
cmd = 'reset-state 123456789'
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
def test_snapshot_reset_state(self):
self.run_command('snapshot-reset-state 1234')
expected = {'os-reset_status': {'status': 'available'}}
self.assert_called('POST', '/snapshots/1234/action', body=expected)
def test_snapshot_reset_state_with_flag(self):
self.run_command('snapshot-reset-state --state error 1234')
expected = {'os-reset_status': {'status': 'error'}}
self.assert_called('POST', '/snapshots/1234/action', body=expected)
def test_snapshot_reset_state_multiple(self):
self.run_command('snapshot-reset-state 1234 5678')
expected = {'os-reset_status': {'status': 'available'}}
self.assert_called_anytime('POST', '/snapshots/1234/action',
body=expected)
self.assert_called_anytime('POST', '/snapshots/5678/action',
body=expected)
def test_encryption_type_list(self):
"""
Test encryption-type-list shell command.
Verify a series of GET requests are made:
- one to get the volume type list information
- one per volume type to retrieve the encryption type information
"""
self.run_command('encryption-type-list')
self.assert_called_anytime('GET', '/types')
self.assert_called_anytime('GET', '/types/1/encryption')
self.assert_called_anytime('GET', '/types/2/encryption')
def test_encryption_type_show(self):
"""
Test encryption-type-show shell command.
Verify two GET requests are made per command invocation:
- one to get the volume type information
- one to get the encryption type information
"""
self.run_command('encryption-type-show 1')
self.assert_called('GET', '/types/1/encryption')
self.assert_called_anytime('GET', '/types/1')
def test_encryption_type_create(self):
"""
Test encryption-type-create shell command.
Verify GET and POST requests are made per command invocation:
- one GET request to retrieve the relevant volume type information
- one POST request to create the new encryption type
"""
expected = {'encryption': {'cipher': None, 'key_size': None,
'provider': 'TestProvider',
'control_location': 'front-end'}}
self.run_command('encryption-type-create 2 TestProvider')
self.assert_called('POST', '/types/2/encryption', body=expected)
self.assert_called_anytime('GET', '/types/2')
def test_encryption_type_update(self):
"""
Test encryption-type-update shell command.
Verify two GETs/one PUT requests are made per command invocation:
- one GET request to retrieve the relevant volume type information
- one GET request to retrieve the relevant encryption type information
- one PUT request to update the encryption type information
"""
self.skipTest("Not implemented")
def test_encryption_type_delete(self):
"""
Test encryption-type-delete shell command.
Verify one GET/one DELETE requests are made per command invocation:
- one GET request to retrieve the relevant volume type information
- one DELETE request to delete the encryption type information
"""
self.run_command('encryption-type-delete 1')
self.assert_called('DELETE', '/types/1/encryption/provider')
self.assert_called_anytime('GET', '/types/1')
def test_migrate_volume(self):
self.run_command('migrate 1234 fakehost --force-host-copy=True')
expected = {'os-migrate_volume': {'force_host_copy': 'True',
'host': 'fakehost'}}
self.assert_called('POST', '/volumes/1234/action', body=expected)
def test_snapshot_metadata_set(self):
self.run_command('snapshot-metadata 1234 set key1=val1 key2=val2')
self.assert_called('POST', '/snapshots/1234/metadata',
{'metadata': {'key1': 'val1', 'key2': 'val2'}})
def test_snapshot_metadata_unset_dict(self):
self.run_command('snapshot-metadata 1234 unset key1=val1 key2=val2')
self.assert_called_anytime('DELETE', '/snapshots/1234/metadata/key1')
self.assert_called_anytime('DELETE', '/snapshots/1234/metadata/key2')
def test_snapshot_metadata_unset_keys(self):
self.run_command('snapshot-metadata 1234 unset key1 key2')
self.assert_called_anytime('DELETE', '/snapshots/1234/metadata/key1')
self.assert_called_anytime('DELETE', '/snapshots/1234/metadata/key2')
def test_volume_metadata_update_all(self):
self.run_command('metadata-update-all 1234 key1=val1 key2=val2')
self.assert_called('PUT', '/volumes/1234/metadata',
{'metadata': {'key1': 'val1', 'key2': 'val2'}})
def test_snapshot_metadata_update_all(self):
self.run_command('snapshot-metadata-update-all\
1234 key1=val1 key2=val2')
self.assert_called('PUT', '/snapshots/1234/metadata',
{'metadata': {'key1': 'val1', 'key2': 'val2'}})
def test_readonly_mode_update(self):
self.run_command('readonly-mode-update 1234 True')
expected = {'os-update_readonly_flag': {'readonly': True}}
self.assert_called('POST', '/volumes/1234/action', body=expected)
self.run_command('readonly-mode-update 1234 False')
expected = {'os-update_readonly_flag': {'readonly': False}}
self.assert_called('POST', '/volumes/1234/action', body=expected)
def test_service_disable(self):
self.run_command('service-disable host cinder-volume')
self.assert_called('PUT', '/os-services/disable',
{"binary": "cinder-volume", "host": "host"})
def test_services_disable_with_reason(self):
cmd = 'service-disable host cinder-volume --reason no_reason'
self.run_command(cmd)
body = {'host': 'host', 'binary': 'cinder-volume',
'disabled_reason': 'no_reason'}
self.assert_called('PUT', '/os-services/disable-log-reason', body)
def test_service_enable(self):
self.run_command('service-enable host cinder-volume')
self.assert_called('PUT', '/os-services/enable',
{"binary": "cinder-volume", "host": "host"})
def test_snapshot_delete(self):
self.run_command('snapshot-delete 1234')
self.assert_called('DELETE', '/snapshots/1234')
def test_quota_delete(self):
self.run_command('quota-delete 1234')
self.assert_called('DELETE', '/os-quota-sets/1234')
def test_snapshot_delete_multiple(self):
self.run_command('snapshot-delete 1234 5678')
self.assert_called('DELETE', '/snapshots/5678')
def test_list_transfer(self):
self.run_command('transfer-list')
self.assert_called('GET', '/os-volume-transfer/detail')
def test_list_transfer_all_tenants(self):
self.run_command('transfer-list --all-tenants=1')
self.assert_called('GET', '/os-volume-transfer/detail?all_tenants=1')

View File

@ -1,36 +0,0 @@
# Copyright 2013 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient.tests.unit import utils
from cinderclient.tests.unit.fixture_data import client
from cinderclient.tests.unit.fixture_data import snapshots
class SnapshotActionsTest(utils.FixturedTestCase):
client_fixture_class = client.V1
data_fixture_class = snapshots.Fixture
def test_update_snapshot_status(self):
s = self.cs.volume_snapshots.get('1234')
stat = {'status': 'available'}
self.cs.volume_snapshots.update_snapshot_status(s, stat)
self.assert_called('POST', '/snapshots/1234/action')
def test_update_snapshot_status_with_progress(self):
s = self.cs.volume_snapshots.get('1234')
stat = {'status': 'available', 'progress': '73%'}
self.cs.volume_snapshots.update_snapshot_status(s, stat)
self.assert_called('POST', '/snapshots/1234/action')

View File

@ -1,53 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from cinderclient.v1 import volume_types
from cinderclient.tests.unit import utils
from cinderclient.tests.unit.v1 import fakes
cs = fakes.FakeClient()
class TypesTest(utils.TestCase):
def test_list_types(self):
tl = cs.volume_types.list()
cs.assert_called('GET', '/types')
for t in tl:
self.assertIsInstance(t, volume_types.VolumeType)
def test_create(self):
t = cs.volume_types.create('test-type-3')
cs.assert_called('POST', '/types')
self.assertIsInstance(t, volume_types.VolumeType)
def test_set_key(self):
t = cs.volume_types.get(1)
t.set_keys({'k': 'v'})
cs.assert_called('POST',
'/types/1/extra_specs',
{'extra_specs': {'k': 'v'}})
def test_unset_keys(self):
t = cs.volume_types.get(1)
t.unset_keys(['k'])
cs.assert_called('DELETE', '/types/1/extra_specs/k')
def test_unset_multiple_keys(self):
t = cs.volume_types.get(1)
t.unset_keys(['k', 'm'])
cs.assert_called_anytime('DELETE', '/types/1/extra_specs/k')
cs.assert_called_anytime('DELETE', '/types/1/extra_specs/m')
def test_delete(self):
cs.volume_types.delete(1)
cs.assert_called('DELETE', '/types/1')

View File

@ -1,53 +0,0 @@
# Copyright (C) 2013 Hewlett-Packard Development Company, L.P.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient.tests.unit import utils
from cinderclient.tests.unit.v1 import fakes
cs = fakes.FakeClient()
class VolumeBackupsTest(utils.TestCase):
def test_create(self):
cs.backups.create('2b695faf-b963-40c8-8464-274008fbcef4')
cs.assert_called('POST', '/backups')
def test_get(self):
backup_id = '76a17945-3c6f-435c-975b-b5685db10b62'
cs.backups.get(backup_id)
cs.assert_called('GET', '/backups/%s' % backup_id)
def test_list(self):
cs.backups.list()
cs.assert_called('GET', '/backups/detail')
def test_delete(self):
b = cs.backups.list()[0]
b.delete()
cs.assert_called('DELETE',
'/backups/76a17945-3c6f-435c-975b-b5685db10b62')
cs.backups.delete('76a17945-3c6f-435c-975b-b5685db10b62')
cs.assert_called('DELETE',
'/backups/76a17945-3c6f-435c-975b-b5685db10b62')
cs.backups.delete(b)
cs.assert_called('DELETE',
'/backups/76a17945-3c6f-435c-975b-b5685db10b62')
def test_restore(self):
backup_id = '76a17945-3c6f-435c-975b-b5685db10b62'
cs.restores.restore(backup_id)
cs.assert_called('POST', '/backups/%s/restore' % backup_id)

View File

@ -1,118 +0,0 @@
# Copyright (c) 2013 The Johns Hopkins University/Applied Physics Laboratory
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient.v1.volume_encryption_types import VolumeEncryptionType
from cinderclient.tests.unit import utils
from cinderclient.tests.unit.v1 import fakes
cs = fakes.FakeClient()
FAKE_ENCRY_TYPE = {'provider': 'Test',
'key_size': None,
'cipher': None,
'control_location': None,
'volume_type_id': '65922555-7bc0-47e9-8d88-c7fdbcac4781',
'encryption_id': '62daf814-cf9b-401c-8fc8-f84d7850fb7c'}
class VolumeEncryptionTypesTest(utils.TestCase):
"""
Test suite for the Volume Encryption Types Resource and Manager.
"""
def test_list(self):
"""
Unit test for VolumeEncryptionTypesManager.list
Verify that a series of GET requests are made:
- one GET request for the list of volume types
- one GET request per volume type for encryption type information
Verify that all returned information is :class: VolumeEncryptionType
"""
encryption_types = cs.volume_encryption_types.list()
cs.assert_called_anytime('GET', '/types')
cs.assert_called_anytime('GET', '/types/2/encryption')
cs.assert_called_anytime('GET', '/types/1/encryption')
for encryption_type in encryption_types:
self.assertIsInstance(encryption_type, VolumeEncryptionType)
def test_get(self):
"""
Unit test for VolumeEncryptionTypesManager.get
Verify that one GET request is made for the volume type encryption
type information. Verify that returned information is :class:
VolumeEncryptionType
"""
encryption_type = cs.volume_encryption_types.get(1)
cs.assert_called('GET', '/types/1/encryption')
self.assertIsInstance(encryption_type, VolumeEncryptionType)
def test_get_no_encryption(self):
"""
Unit test for VolumeEncryptionTypesManager.get
Verify that a request on a volume type with no associated encryption
type information returns a VolumeEncryptionType with no attributes.
"""
encryption_type = cs.volume_encryption_types.get(2)
self.assertIsInstance(encryption_type, VolumeEncryptionType)
self.assertFalse(hasattr(encryption_type, 'id'),
'encryption type has an id')
def test_create(self):
"""
Unit test for VolumeEncryptionTypesManager.create
Verify that one POST request is made for the encryption type creation.
Verify that encryption type creation returns a VolumeEncryptionType.
"""
result = cs.volume_encryption_types.create(2, {'provider': 'Test',
'key_size': None,
'cipher': None,
'control_location':
None})
cs.assert_called('POST', '/types/2/encryption')
self.assertIsInstance(result, VolumeEncryptionType)
def test_update(self):
"""
Unit test for VolumeEncryptionTypesManager.update
"""
self.skipTest("Not implemented")
def test_delete(self):
"""
Unit test for VolumeEncryptionTypesManager.delete
Verify that one DELETE request is made for encryption type deletion
Verify that encryption type deletion returns None
"""
result = cs.volume_encryption_types.delete(1)
cs.assert_called('DELETE', '/types/1/encryption/provider')
self.assertIsInstance(result, tuple)
self.assertEqual(202, result[0].status_code)
def test___repr__(self):
"""
Unit test for VolumeEncryptionTypes.__repr__
Verify that one encryption type can be printed
"""
encry_type = VolumeEncryptionType(None, FAKE_ENCRY_TYPE)
self.assertEqual(
"<VolumeEncryptionType: %s>" % FAKE_ENCRY_TYPE['encryption_id'],
repr(encry_type))

View File

@ -1,51 +0,0 @@
# Copyright (C) 2013 Hewlett-Packard Development Company, L.P.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient.tests.unit import utils
from cinderclient.tests.unit.v1 import fakes
cs = fakes.FakeClient()
class VolumeTransfersTest(utils.TestCase):
def test_create(self):
cs.transfers.create('1234')
cs.assert_called('POST', '/os-volume-transfer')
def test_get(self):
transfer_id = '5678'
cs.transfers.get(transfer_id)
cs.assert_called('GET', '/os-volume-transfer/%s' % transfer_id)
def test_list(self):
cs.transfers.list()
cs.assert_called('GET', '/os-volume-transfer/detail')
def test_delete(self):
b = cs.transfers.list()[0]
b.delete()
cs.assert_called('DELETE', '/os-volume-transfer/5678')
cs.transfers.delete('5678')
cs.assert_called('DELETE', '/os-volume-transfer/5678')
cs.transfers.delete(b)
cs.assert_called('DELETE', '/os-volume-transfer/5678')
def test_accept(self):
transfer_id = '5678'
auth_key = '12345'
cs.transfers.accept(transfer_id, auth_key)
cs.assert_called('POST', '/os-volume-transfer/%s/accept' % transfer_id)

View File

@ -1,118 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from cinderclient.tests.unit import utils
from cinderclient.tests.unit.v1 import fakes
cs = fakes.FakeClient()
class VolumesTest(utils.TestCase):
def test_delete_volume(self):
v = cs.volumes.list()[0]
v.delete()
cs.assert_called('DELETE', '/volumes/1234')
cs.volumes.delete('1234')
cs.assert_called('DELETE', '/volumes/1234')
cs.volumes.delete(v)
cs.assert_called('DELETE', '/volumes/1234')
def test_create_volume(self):
cs.volumes.create(1)
cs.assert_called('POST', '/volumes')
def test_attach(self):
v = cs.volumes.get('1234')
cs.volumes.attach(v, 1, '/dev/vdc', mode='rw')
cs.assert_called('POST', '/volumes/1234/action')
def test_attach_to_host(self):
v = cs.volumes.get('1234')
cs.volumes.attach(v, None, None, host_name='test', mode='rw')
cs.assert_called('POST', '/volumes/1234/action')
def test_detach(self):
v = cs.volumes.get('1234')
cs.volumes.detach(v)
cs.assert_called('POST', '/volumes/1234/action')
def test_reserve(self):
v = cs.volumes.get('1234')
cs.volumes.reserve(v)
cs.assert_called('POST', '/volumes/1234/action')
def test_unreserve(self):
v = cs.volumes.get('1234')
cs.volumes.unreserve(v)
cs.assert_called('POST', '/volumes/1234/action')
def test_begin_detaching(self):
v = cs.volumes.get('1234')
cs.volumes.begin_detaching(v)
cs.assert_called('POST', '/volumes/1234/action')
def test_roll_detaching(self):
v = cs.volumes.get('1234')
cs.volumes.roll_detaching(v)
cs.assert_called('POST', '/volumes/1234/action')
def test_initialize_connection(self):
v = cs.volumes.get('1234')
cs.volumes.initialize_connection(v, {})
cs.assert_called('POST', '/volumes/1234/action')
def test_terminate_connection(self):
v = cs.volumes.get('1234')
cs.volumes.terminate_connection(v, {})
cs.assert_called('POST', '/volumes/1234/action')
def test_set_metadata(self):
cs.volumes.set_metadata(1234, {'k1': 'v1'})
cs.assert_called('POST', '/volumes/1234/metadata',
{'metadata': {'k1': 'v1'}})
def test_delete_metadata(self):
keys = ['key1']
cs.volumes.delete_metadata(1234, keys)
cs.assert_called('DELETE', '/volumes/1234/metadata/key1')
def test_extend(self):
v = cs.volumes.get('1234')
cs.volumes.extend(v, 2)
cs.assert_called('POST', '/volumes/1234/action')
def test_get_encryption_metadata(self):
cs.volumes.get_encryption_metadata('1234')
cs.assert_called('GET', '/volumes/1234/encryption')
def test_migrate(self):
v = cs.volumes.get('1234')
cs.volumes.migrate_volume(v, 'dest', False)
cs.assert_called('POST', '/volumes/1234/action')
def test_metadata_update_all(self):
cs.volumes.update_all_metadata(1234, {'k1': 'v1'})
cs.assert_called('PUT', '/volumes/1234/metadata',
{'metadata': {'k1': 'v1'}})
def test_readonly_mode_update(self):
v = cs.volumes.get('1234')
cs.volumes.update_readonly_flag(v, True)
cs.assert_called('POST', '/volumes/1234/action')
def test_set_bootable(self):
v = cs.volumes.get('1234')
cs.volumes.set_bootable(v, True)
cs.assert_called('POST', '/volumes/1234/action')

View File

@ -1 +0,0 @@
BLAH

View File

@ -1,36 +0,0 @@
# Copyright (c) 2013 OpenStack Foundation
#
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient import extension
from cinderclient.v2.contrib import list_extensions
from cinderclient.tests.unit import utils
from cinderclient.tests.unit.v1 import fakes
extensions = [
extension.Extension(list_extensions.__name__.split(".")[-1],
list_extensions),
]
cs = fakes.FakeClient(extensions=extensions)
class ListExtensionsTests(utils.TestCase):
def test_list_extensions(self):
all_exts = cs.list_extensions.show_all()
cs.assert_called('GET', '/extensions')
self.assertGreater(len(all_exts), 0)
for r in all_exts:
self.assertGreater(len(r.summary), 0)

File diff suppressed because it is too large Load Diff

View File

@ -1,341 +0,0 @@
# Copyright (c) 2013 OpenStack Foundation
#
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import json
import mock
import requests
from cinderclient import exceptions
from cinderclient.v2 import client
from cinderclient.tests.unit import utils
class AuthenticateAgainstKeystoneTests(utils.TestCase):
def test_authenticate_success(self):
cs = client.Client("username", "password", "project_id",
"http://localhost:8776/v2", service_type='volumev2')
resp = {
"access": {
"token": {
"expires": "2014-11-01T03:32:15-05:00",
"id": "FAKE_ID",
},
"serviceCatalog": [
{
"type": "volumev2",
"endpoints": [
{
"region": "RegionOne",
"adminURL": "http://localhost:8776/v2",
"internalURL": "http://localhost:8776/v2",
"publicURL": "http://localhost:8776/v2",
},
],
},
],
},
}
auth_response = utils.TestResponse({
"status_code": 200,
"text": json.dumps(resp),
})
mock_request = mock.Mock(return_value=(auth_response))
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
cs.client.authenticate()
headers = {
'User-Agent': cs.client.USER_AGENT,
'Content-Type': 'application/json',
'Accept': 'application/json',
}
body = {
'auth': {
'passwordCredentials': {
'username': cs.client.user,
'password': cs.client.password,
},
'tenantName': cs.client.projectid,
},
}
token_url = cs.client.auth_url + "/tokens"
mock_request.assert_called_with(
"POST",
token_url,
headers=headers,
data=json.dumps(body),
allow_redirects=True,
**self.TEST_REQUEST_BASE)
endpoints = resp["access"]["serviceCatalog"][0]['endpoints']
public_url = endpoints[0]["publicURL"].rstrip('/')
self.assertEqual(public_url, cs.client.management_url)
token_id = resp["access"]["token"]["id"]
self.assertEqual(token_id, cs.client.auth_token)
test_auth_call()
def test_authenticate_tenant_id(self):
cs = client.Client("username", "password",
auth_url="http://localhost:8776/v2",
tenant_id='tenant_id', service_type='volumev2')
resp = {
"access": {
"token": {
"expires": "2014-11-01T03:32:15-05:00",
"id": "FAKE_ID",
"tenant": {
"description": None,
"enabled": True,
"id": "tenant_id",
"name": "demo"
} # tenant associated with token
},
"serviceCatalog": [
{
"type": 'volumev2',
"endpoints": [
{
"region": "RegionOne",
"adminURL": "http://localhost:8776/v2",
"internalURL": "http://localhost:8776/v2",
"publicURL": "http://localhost:8776/v2",
},
],
},
],
},
}
auth_response = utils.TestResponse({
"status_code": 200,
"text": json.dumps(resp),
})
mock_request = mock.Mock(return_value=(auth_response))
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
cs.client.authenticate()
headers = {
'User-Agent': cs.client.USER_AGENT,
'Content-Type': 'application/json',
'Accept': 'application/json',
}
body = {
'auth': {
'passwordCredentials': {
'username': cs.client.user,
'password': cs.client.password,
},
'tenantId': cs.client.tenant_id,
},
}
token_url = cs.client.auth_url + "/tokens"
mock_request.assert_called_with(
"POST",
token_url,
headers=headers,
data=json.dumps(body),
allow_redirects=True,
**self.TEST_REQUEST_BASE)
endpoints = resp["access"]["serviceCatalog"][0]['endpoints']
public_url = endpoints[0]["publicURL"].rstrip('/')
self.assertEqual(public_url, cs.client.management_url)
token_id = resp["access"]["token"]["id"]
self.assertEqual(token_id, cs.client.auth_token)
tenant_id = resp["access"]["token"]["tenant"]["id"]
self.assertEqual(tenant_id, cs.client.tenant_id)
test_auth_call()
def test_authenticate_failure(self):
cs = client.Client("username", "password", "project_id",
"http://localhost:8776/v2")
resp = {"unauthorized": {"message": "Unauthorized", "code": "401"}}
auth_response = utils.TestResponse({
"status_code": 401,
"text": json.dumps(resp),
})
mock_request = mock.Mock(return_value=(auth_response))
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
self.assertRaises(exceptions.Unauthorized, cs.client.authenticate)
test_auth_call()
def test_auth_redirect(self):
cs = client.Client("username", "password", "project_id",
"http://localhost:8776/v2", service_type='volumev2')
dict_correct_response = {
"access": {
"token": {
"expires": "2014-11-01T03:32:15-05:00",
"id": "FAKE_ID",
},
"serviceCatalog": [
{
"type": "volumev2",
"endpoints": [
{
"adminURL": "http://localhost:8776/v2",
"region": "RegionOne",
"internalURL": "http://localhost:8776/v2",
"publicURL": "http://localhost:8776/v2/",
},
],
},
],
},
}
correct_response = json.dumps(dict_correct_response)
dict_responses = [
{"headers": {'location': 'http://127.0.0.1:5001'},
"status_code": 305,
"text": "Use proxy"},
# Configured on admin port, cinder redirects to v2.0 port.
# When trying to connect on it, keystone auth succeed by v1.0
# protocol (through headers) but tokens are being returned in
# body (looks like keystone bug). Leaved for compatibility.
{"headers": {},
"status_code": 200,
"text": correct_response},
{"headers": {},
"status_code": 200,
"text": correct_response}
]
responses = [(utils.TestResponse(resp)) for resp in dict_responses]
def side_effect(*args, **kwargs):
return responses.pop(0)
mock_request = mock.Mock(side_effect=side_effect)
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
cs.client.authenticate()
headers = {
'User-Agent': cs.client.USER_AGENT,
'Content-Type': 'application/json',
'Accept': 'application/json',
}
body = {
'auth': {
'passwordCredentials': {
'username': cs.client.user,
'password': cs.client.password,
},
'tenantName': cs.client.projectid,
},
}
token_url = cs.client.auth_url + "/tokens"
mock_request.assert_called_with(
"POST",
token_url,
headers=headers,
data=json.dumps(body),
allow_redirects=True,
**self.TEST_REQUEST_BASE)
resp = dict_correct_response
endpoints = resp["access"]["serviceCatalog"][0]['endpoints']
public_url = endpoints[0]["publicURL"].rstrip('/')
self.assertEqual(public_url, cs.client.management_url)
token_id = resp["access"]["token"]["id"]
self.assertEqual(token_id, cs.client.auth_token)
test_auth_call()
class AuthenticationTests(utils.TestCase):
def test_authenticate_success(self):
cs = client.Client("username", "password", "project_id", "auth_url")
management_url = 'https://localhost/v2.1/443470'
auth_response = utils.TestResponse({
'status_code': 204,
'headers': {
'x-server-management-url': management_url,
'x-auth-token': '1b751d74-de0c-46ae-84f0-915744b582d1',
},
})
mock_request = mock.Mock(return_value=(auth_response))
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
cs.client.authenticate()
headers = {
'Accept': 'application/json',
'X-Auth-User': 'username',
'X-Auth-Key': 'password',
'X-Auth-Project-Id': 'project_id',
'User-Agent': cs.client.USER_AGENT
}
mock_request.assert_called_with(
"GET",
cs.client.auth_url,
headers=headers,
**self.TEST_REQUEST_BASE)
self.assertEqual(auth_response.headers['x-server-management-url'],
cs.client.management_url)
self.assertEqual(auth_response.headers['x-auth-token'],
cs.client.auth_token)
test_auth_call()
def test_authenticate_failure(self):
cs = client.Client("username", "password", "project_id", "auth_url")
auth_response = utils.TestResponse({"status_code": 401})
mock_request = mock.Mock(return_value=(auth_response))
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
self.assertRaises(exceptions.Unauthorized, cs.client.authenticate)
test_auth_call()
def test_auth_automatic(self):
cs = client.Client("username", "password", "project_id", "auth_url")
http_client = cs.client
http_client.management_url = ''
mock_request = mock.Mock(return_value=(None, None))
@mock.patch.object(http_client, 'request', mock_request)
@mock.patch.object(http_client, 'authenticate')
def test_auth_call(m):
http_client.get('/')
self.assertTrue(m.called)
self.assertTrue(mock_request.called)
test_auth_call()
def test_auth_manual(self):
cs = client.Client("username", "password", "project_id", "auth_url")
@mock.patch.object(cs.client, 'authenticate')
def test_auth_call(m):
cs.authenticate()
self.assertTrue(m.called)
test_auth_call()

View File

@ -1,90 +0,0 @@
# Copyright 2011-2013 OpenStack Foundation
# Copyright 2013 IBM Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import six
from cinderclient.v2 import availability_zones
from cinderclient.v2 import shell
from cinderclient.tests.unit.fixture_data import client
from cinderclient.tests.unit.fixture_data import availability_zones as azfixture # noqa
from cinderclient.tests.unit import utils
class AvailabilityZoneTest(utils.FixturedTestCase):
client_fixture_class = client.V2
data_fixture_class = azfixture.Fixture
def _assertZone(self, zone, name, status):
self.assertEqual(name, zone.zoneName)
self.assertEqual(status, zone.zoneState)
def test_list_availability_zone(self):
zones = self.cs.availability_zones.list(detailed=False)
self.assert_called('GET', '/os-availability-zone')
self._assert_request_id(zones)
for zone in zones:
self.assertIsInstance(zone,
availability_zones.AvailabilityZone)
self.assertEqual(2, len(zones))
l0 = [six.u('zone-1'), six.u('available')]
l1 = [six.u('zone-2'), six.u('not available')]
z0 = shell.treeizeAvailabilityZone(zones[0])
z1 = shell.treeizeAvailabilityZone(zones[1])
self.assertEqual((1, 1), (len(z0), len(z1)))
self._assertZone(z0[0], l0[0], l0[1])
self._assertZone(z1[0], l1[0], l1[1])
def test_detail_availability_zone(self):
zones = self.cs.availability_zones.list(detailed=True)
self.assert_called('GET', '/os-availability-zone/detail')
self._assert_request_id(zones)
for zone in zones:
self.assertIsInstance(zone,
availability_zones.AvailabilityZone)
self.assertEqual(3, len(zones))
l0 = [six.u('zone-1'), six.u('available')]
l1 = [six.u('|- fake_host-1'), six.u('')]
l2 = [six.u('| |- cinder-volume'),
six.u('enabled :-) 2012-12-26 14:45:25')]
l3 = [six.u('internal'), six.u('available')]
l4 = [six.u('|- fake_host-1'), six.u('')]
l5 = [six.u('| |- cinder-sched'),
six.u('enabled :-) 2012-12-26 14:45:24')]
l6 = [six.u('zone-2'), six.u('not available')]
z0 = shell.treeizeAvailabilityZone(zones[0])
z1 = shell.treeizeAvailabilityZone(zones[1])
z2 = shell.treeizeAvailabilityZone(zones[2])
self.assertEqual((3, 3, 1), (len(z0), len(z1), len(z2)))
self._assertZone(z0[0], l0[0], l0[1])
self._assertZone(z0[1], l1[0], l1[1])
self._assertZone(z0[2], l2[0], l2[1])
self._assertZone(z1[0], l3[0], l3[1])
self._assertZone(z1[1], l4[0], l4[1])
self._assertZone(z1[2], l5[0], l5[1])
self._assertZone(z2[0], l6[0], l6[1])

View File

@ -1,54 +0,0 @@
# Copyright (c) 2015 Hitachi Data Systems, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient.v2.capabilities import Capabilities
from cinderclient.tests.unit import utils
from cinderclient.tests.unit.v2 import fakes
cs = fakes.FakeClient()
FAKE_CAPABILITY = {
'namespace': 'OS::Storage::Capabilities::fake',
'vendor_name': 'OpenStack',
'volume_backend_name': 'lvm',
'pool_name': 'pool',
'storage_protocol': 'iSCSI',
'properties': {
'compression': {
'title': 'Compression',
'description': 'Enables compression.',
'type': 'boolean',
},
},
}
class CapabilitiesTest(utils.TestCase):
def test_get_capabilities(self):
capabilities = cs.capabilities.get('host')
cs.assert_called('GET', '/capabilities/host')
self.assertEqual(FAKE_CAPABILITY, capabilities._info)
self._assert_request_id(capabilities)
def test___repr__(self):
"""
Unit test for Capabilities.__repr__
Verify that Capabilities object can be printed.
"""
cap = Capabilities(None, FAKE_CAPABILITY)
self.assertEqual(
"<Capabilities: %s>" % FAKE_CAPABILITY['namespace'], repr(cap))

View File

@ -1,94 +0,0 @@
# Copyright (C) 2012 - 2014 EMC Corporation.
#
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient.tests.unit import utils
from cinderclient.tests.unit.v2 import fakes
cs = fakes.FakeClient()
class cgsnapshotsTest(utils.TestCase):
def test_delete_cgsnapshot(self):
v = cs.cgsnapshots.list()[0]
vol = v.delete()
self._assert_request_id(vol)
cs.assert_called('DELETE', '/cgsnapshots/1234')
vol = cs.cgsnapshots.delete('1234')
cs.assert_called('DELETE', '/cgsnapshots/1234')
self._assert_request_id(vol)
vol = cs.cgsnapshots.delete(v)
cs.assert_called('DELETE', '/cgsnapshots/1234')
self._assert_request_id(vol)
def test_create_cgsnapshot(self):
vol = cs.cgsnapshots.create('cgsnap')
cs.assert_called('POST', '/cgsnapshots')
self._assert_request_id(vol)
def test_create_cgsnapshot_with_cg_id(self):
vol = cs.cgsnapshots.create('1234')
expected = {'cgsnapshot': {'status': 'creating',
'description': None,
'user_id': None,
'name': None,
'consistencygroup_id': '1234',
'project_id': None}}
cs.assert_called('POST', '/cgsnapshots', body=expected)
self._assert_request_id(vol)
def test_update_cgsnapshot(self):
v = cs.cgsnapshots.list()[0]
expected = {'cgsnapshot': {'name': 'cgs2'}}
vol = v.update(name='cgs2')
cs.assert_called('PUT', '/cgsnapshots/1234', body=expected)
self._assert_request_id(vol)
vol = cs.cgsnapshots.update('1234', name='cgs2')
cs.assert_called('PUT', '/cgsnapshots/1234', body=expected)
self._assert_request_id(vol)
vol = cs.cgsnapshots.update(v, name='cgs2')
cs.assert_called('PUT', '/cgsnapshots/1234', body=expected)
self._assert_request_id(vol)
def test_update_cgsnapshot_no_props(self):
cs.cgsnapshots.update('1234')
def test_list_cgsnapshot(self):
lst = cs.cgsnapshots.list()
cs.assert_called('GET', '/cgsnapshots/detail')
self._assert_request_id(lst)
def test_list_cgsnapshot_detailed_false(self):
lst = cs.cgsnapshots.list(detailed=False)
cs.assert_called('GET', '/cgsnapshots')
self._assert_request_id(lst)
def test_list_cgsnapshot_with_search_opts(self):
lst = cs.cgsnapshots.list(search_opts={'foo': 'bar'})
cs.assert_called('GET', '/cgsnapshots/detail?foo=bar')
self._assert_request_id(lst)
def test_list_cgsnapshot_with_empty_search_opt(self):
lst = cs.cgsnapshots.list(search_opts={'foo': 'bar', '123': None})
cs.assert_called('GET', '/cgsnapshots/detail?foo=bar')
self._assert_request_id(lst)
def test_get_cgsnapshot(self):
cgsnapshot_id = '1234'
vol = cs.cgsnapshots.get(cgsnapshot_id)
cs.assert_called('GET', '/cgsnapshots/%s' % cgsnapshot_id)
self._assert_request_id(vol)

View File

@ -1,174 +0,0 @@
# Copyright (C) 2012 - 2014 EMC Corporation.
#
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient.tests.unit import utils
from cinderclient.tests.unit.v2 import fakes
cs = fakes.FakeClient()
class ConsistencygroupsTest(utils.TestCase):
def test_delete_consistencygroup(self):
v = cs.consistencygroups.list()[0]
vol = v.delete(force='True')
self._assert_request_id(vol)
cs.assert_called('POST', '/consistencygroups/1234/delete')
vol = cs.consistencygroups.delete('1234', force=True)
self._assert_request_id(vol)
cs.assert_called('POST', '/consistencygroups/1234/delete')
vol = cs.consistencygroups.delete(v, force=True)
self._assert_request_id(vol)
cs.assert_called('POST', '/consistencygroups/1234/delete')
def test_create_consistencygroup(self):
vol = cs.consistencygroups.create('type1,type2', 'cg')
cs.assert_called('POST', '/consistencygroups')
self._assert_request_id(vol)
def test_create_consistencygroup_with_volume_types(self):
vol = cs.consistencygroups.create('type1,type2', 'cg')
expected = {'consistencygroup': {'status': 'creating',
'description': None,
'availability_zone': None,
'user_id': None,
'name': 'cg',
'volume_types': 'type1,type2',
'project_id': None}}
cs.assert_called('POST', '/consistencygroups', body=expected)
self._assert_request_id(vol)
def test_update_consistencygroup_name(self):
v = cs.consistencygroups.list()[0]
expected = {'consistencygroup': {'name': 'cg2'}}
vol = v.update(name='cg2')
cs.assert_called('PUT', '/consistencygroups/1234', body=expected)
self._assert_request_id(vol)
vol = cs.consistencygroups.update('1234', name='cg2')
cs.assert_called('PUT', '/consistencygroups/1234', body=expected)
self._assert_request_id(vol)
vol = cs.consistencygroups.update(v, name='cg2')
cs.assert_called('PUT', '/consistencygroups/1234', body=expected)
self._assert_request_id(vol)
def test_update_consistencygroup_description(self):
v = cs.consistencygroups.list()[0]
expected = {'consistencygroup': {'description': 'cg2 desc'}}
vol = v.update(description='cg2 desc')
cs.assert_called('PUT', '/consistencygroups/1234', body=expected)
self._assert_request_id(vol)
vol = cs.consistencygroups.update('1234', description='cg2 desc')
cs.assert_called('PUT', '/consistencygroups/1234', body=expected)
self._assert_request_id(vol)
vol = cs.consistencygroups.update(v, description='cg2 desc')
cs.assert_called('PUT', '/consistencygroups/1234', body=expected)
self._assert_request_id(vol)
def test_update_consistencygroup_add_volumes(self):
v = cs.consistencygroups.list()[0]
uuids = 'uuid1,uuid2'
expected = {'consistencygroup': {'add_volumes': uuids}}
vol = v.update(add_volumes=uuids)
cs.assert_called('PUT', '/consistencygroups/1234', body=expected)
self._assert_request_id(vol)
vol = cs.consistencygroups.update('1234', add_volumes=uuids)
cs.assert_called('PUT', '/consistencygroups/1234', body=expected)
self._assert_request_id(vol)
vol = cs.consistencygroups.update(v, add_volumes=uuids)
cs.assert_called('PUT', '/consistencygroups/1234', body=expected)
self._assert_request_id(vol)
def test_update_consistencygroup_remove_volumes(self):
v = cs.consistencygroups.list()[0]
uuids = 'uuid3,uuid4'
expected = {'consistencygroup': {'remove_volumes': uuids}}
vol = v.update(remove_volumes=uuids)
cs.assert_called('PUT', '/consistencygroups/1234', body=expected)
self._assert_request_id(vol)
vol = cs.consistencygroups.update('1234', remove_volumes=uuids)
cs.assert_called('PUT', '/consistencygroups/1234', body=expected)
self._assert_request_id(vol)
vol = cs.consistencygroups.update(v, remove_volumes=uuids)
cs.assert_called('PUT', '/consistencygroups/1234', body=expected)
self._assert_request_id(vol)
def test_update_consistencygroup_none(self):
self.assertIsNone(cs.consistencygroups.update('1234'))
def test_update_consistencygroup_no_props(self):
cs.consistencygroups.update('1234')
def test_create_consistencygroup_from_src_snap(self):
vol = cs.consistencygroups.create_from_src('5678', None, name='cg')
expected = {
'consistencygroup-from-src': {
'status': 'creating',
'description': None,
'user_id': None,
'name': 'cg',
'cgsnapshot_id': '5678',
'project_id': None,
'source_cgid': None
}
}
cs.assert_called('POST', '/consistencygroups/create_from_src',
body=expected)
self._assert_request_id(vol)
def test_create_consistencygroup_from_src_cg(self):
vol = cs.consistencygroups.create_from_src(None, '5678', name='cg')
expected = {
'consistencygroup-from-src': {
'status': 'creating',
'description': None,
'user_id': None,
'name': 'cg',
'source_cgid': '5678',
'project_id': None,
'cgsnapshot_id': None
}
}
cs.assert_called('POST', '/consistencygroups/create_from_src',
body=expected)
self._assert_request_id(vol)
def test_list_consistencygroup(self):
lst = cs.consistencygroups.list()
cs.assert_called('GET', '/consistencygroups/detail')
self._assert_request_id(lst)
def test_list_consistencygroup_detailed_false(self):
lst = cs.consistencygroups.list(detailed=False)
cs.assert_called('GET', '/consistencygroups')
self._assert_request_id(lst)
def test_list_consistencygroup_with_search_opts(self):
lst = cs.consistencygroups.list(search_opts={'foo': 'bar'})
cs.assert_called('GET', '/consistencygroups/detail?foo=bar')
self._assert_request_id(lst)
def test_list_consistencygroup_with_empty_search_opt(self):
lst = cs.consistencygroups.list(
search_opts={'foo': 'bar', 'abc': None}
)
cs.assert_called('GET', '/consistencygroups/detail?foo=bar')
self._assert_request_id(lst)
def test_get_consistencygroup(self):
consistencygroup_id = '1234'
vol = cs.consistencygroups.get(consistencygroup_id)
cs.assert_called('GET', '/consistencygroups/%s' % consistencygroup_id)
self._assert_request_id(vol)

View File

@ -1,179 +0,0 @@
# Copyright 2014 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import ddt
import mock
from cinderclient.tests.unit import utils
from cinderclient.v2 import limits
REQUEST_ID = 'req-test-request-id'
def _get_default_RateLimit(verb="verb1", uri="uri1", regex="regex1",
value="value1",
remain="remain1", unit="unit1",
next_available="next1"):
return limits.RateLimit(verb, uri, regex, value, remain, unit,
next_available)
class TestLimits(utils.TestCase):
def test_repr(self):
l = limits.Limits(None, {"foo": "bar"}, resp=REQUEST_ID)
self.assertEqual("<Limits>", repr(l))
self._assert_request_id(l)
def test_absolute(self):
l = limits.Limits(None,
{"absolute": {"name1": "value1", "name2": "value2"}},
resp=REQUEST_ID)
l1 = limits.AbsoluteLimit("name1", "value1")
l2 = limits.AbsoluteLimit("name2", "value2")
for item in l.absolute:
self.assertIn(item, [l1, l2])
self._assert_request_id(l)
def test_rate(self):
l = limits.Limits(None,
{
"rate": [
{
"uri": "uri1",
"regex": "regex1",
"limit": [
{
"verb": "verb1",
"value": "value1",
"remaining": "remain1",
"unit": "unit1",
"next-available": "next1",
},
],
},
{
"uri": "uri2",
"regex": "regex2",
"limit": [
{
"verb": "verb2",
"value": "value2",
"remaining": "remain2",
"unit": "unit2",
"next-available": "next2",
},
],
},
],
},
resp=REQUEST_ID)
l1 = limits.RateLimit("verb1", "uri1", "regex1", "value1", "remain1",
"unit1", "next1")
l2 = limits.RateLimit("verb2", "uri2", "regex2", "value2", "remain2",
"unit2", "next2")
for item in l.rate:
self.assertIn(item, [l1, l2])
self._assert_request_id(l)
class TestRateLimit(utils.TestCase):
def test_equal(self):
l1 = _get_default_RateLimit()
l2 = _get_default_RateLimit()
self.assertEqual(l1, l2)
def test_not_equal_verbs(self):
l1 = _get_default_RateLimit()
l2 = _get_default_RateLimit(verb="verb2")
self.assertNotEqual(l1, l2)
def test_not_equal_uris(self):
l1 = _get_default_RateLimit()
l2 = _get_default_RateLimit(uri="uri2")
self.assertNotEqual(l1, l2)
def test_not_equal_regexps(self):
l1 = _get_default_RateLimit()
l2 = _get_default_RateLimit(regex="regex2")
self.assertNotEqual(l1, l2)
def test_not_equal_values(self):
l1 = _get_default_RateLimit()
l2 = _get_default_RateLimit(value="value2")
self.assertNotEqual(l1, l2)
def test_not_equal_remains(self):
l1 = _get_default_RateLimit()
l2 = _get_default_RateLimit(remain="remain2")
self.assertNotEqual(l1, l2)
def test_not_equal_units(self):
l1 = _get_default_RateLimit()
l2 = _get_default_RateLimit(unit="unit2")
self.assertNotEqual(l1, l2)
def test_not_equal_next_available(self):
l1 = _get_default_RateLimit()
l2 = _get_default_RateLimit(next_available="next2")
self.assertNotEqual(l1, l2)
def test_repr(self):
l1 = _get_default_RateLimit()
self.assertEqual("<RateLimit: method=verb1 uri=uri1>", repr(l1))
class TestAbsoluteLimit(utils.TestCase):
def test_equal(self):
l1 = limits.AbsoluteLimit("name1", "value1")
l2 = limits.AbsoluteLimit("name1", "value1")
self.assertEqual(l1, l2)
def test_not_equal_values(self):
l1 = limits.AbsoluteLimit("name1", "value1")
l2 = limits.AbsoluteLimit("name1", "value2")
self.assertNotEqual(l1, l2)
def test_not_equal_names(self):
l1 = limits.AbsoluteLimit("name1", "value1")
l2 = limits.AbsoluteLimit("name2", "value1")
self.assertNotEqual(l1, l2)
def test_repr(self):
l1 = limits.AbsoluteLimit("name1", "value1")
self.assertEqual("<AbsoluteLimit: name=name1>", repr(l1))
@ddt.ddt
class TestLimitsManager(utils.TestCase):
@ddt.data(None, 'test')
def test_get(self, tenant_id):
api = mock.Mock()
api.client.get.return_value = (
None,
{"limits": {"absolute": {"name1": "value1", }},
"no-limits": {"absolute": {"name2": "value2", }}})
l1 = limits.AbsoluteLimit("name1", "value1")
limitsManager = limits.LimitsManager(api)
lim = limitsManager.get(tenant_id)
query_str = ''
if tenant_id:
query_str = '?tenant_id=%s' % tenant_id
api.client.get.assert_called_once_with('/limits%s' % query_str)
self.assertIsInstance(lim, limits.Limits)
for l in lim.absolute:
self.assertEqual(l1, l)

View File

@ -1,46 +0,0 @@
# Copyright (C) 2015 Hewlett-Packard Development Company, L.P.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient.v2.pools import Pool
from cinderclient.tests.unit import utils
from cinderclient.tests.unit.v2 import fakes
cs = fakes.FakeClient()
class PoolsTest(utils.TestCase):
def test_get_pool_stats(self):
sl = cs.pools.list()
cs.assert_called('GET', '/scheduler-stats/get_pools')
self._assert_request_id(sl)
for s in sl:
self.assertIsInstance(s, Pool)
self.assertTrue(hasattr(s, "name"))
self.assertFalse(hasattr(s, "capabilities"))
# basic list should not have volume_backend_name (or any other
# entries from capabilities)
self.assertFalse(hasattr(s, "volume_backend_name"))
def test_get_detail_pool_stats(self):
sl = cs.pools.list(detailed=True)
self._assert_request_id(sl)
cs.assert_called('GET', '/scheduler-stats/get_pools?detail=True')
for s in sl:
self.assertIsInstance(s, Pool)
self.assertTrue(hasattr(s, "name"))
self.assertFalse(hasattr(s, "capabilities"))
# detail list should have a volume_backend_name (from capabilities)
self.assertTrue(hasattr(s, "volume_backend_name"))

View File

@ -1,89 +0,0 @@
# Copyright (C) 2013 eBay Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient.tests.unit import utils
from cinderclient.tests.unit.v2 import fakes
cs = fakes.FakeClient()
class QoSSpecsTest(utils.TestCase):
def test_create(self):
specs = dict(k1='v1', k2='v2')
qos = cs.qos_specs.create('qos-name', specs)
cs.assert_called('POST', '/qos-specs')
self._assert_request_id(qos)
def test_get(self):
qos_id = '1B6B6A04-A927-4AEB-810B-B7BAAD49F57C'
qos = cs.qos_specs.get(qos_id)
cs.assert_called('GET', '/qos-specs/%s' % qos_id)
self._assert_request_id(qos)
def test_list(self):
lst = cs.qos_specs.list()
cs.assert_called('GET', '/qos-specs')
self._assert_request_id(lst)
def test_delete(self):
qos = cs.qos_specs.delete('1B6B6A04-A927-4AEB-810B-B7BAAD49F57C')
cs.assert_called('DELETE',
'/qos-specs/1B6B6A04-A927-4AEB-810B-B7BAAD49F57C?'
'force=False')
self._assert_request_id(qos)
def test_set_keys(self):
body = {'qos_specs': dict(k1='v1')}
qos_id = '1B6B6A04-A927-4AEB-810B-B7BAAD49F57C'
qos = cs.qos_specs.set_keys(qos_id, body)
cs.assert_called('PUT', '/qos-specs/%s' % qos_id)
self._assert_request_id(qos)
def test_unset_keys(self):
qos_id = '1B6B6A04-A927-4AEB-810B-B7BAAD49F57C'
body = {'keys': ['k1']}
qos = cs.qos_specs.unset_keys(qos_id, body)
cs.assert_called('PUT', '/qos-specs/%s/delete_keys' % qos_id)
self._assert_request_id(qos)
def test_get_associations(self):
qos_id = '1B6B6A04-A927-4AEB-810B-B7BAAD49F57C'
qos = cs.qos_specs.get_associations(qos_id)
cs.assert_called('GET', '/qos-specs/%s/associations' % qos_id)
self._assert_request_id(qos)
def test_associate(self):
qos_id = '1B6B6A04-A927-4AEB-810B-B7BAAD49F57C'
type_id = '4230B13A-7A37-4E84-B777-EFBA6FCEE4FF'
qos = cs.qos_specs.associate(qos_id, type_id)
cs.assert_called('GET', '/qos-specs/%s/associate?vol_type_id=%s'
% (qos_id, type_id))
self._assert_request_id(qos)
def test_disassociate(self):
qos_id = '1B6B6A04-A927-4AEB-810B-B7BAAD49F57C'
type_id = '4230B13A-7A37-4E84-B777-EFBA6FCEE4FF'
qos = cs.qos_specs.disassociate(qos_id, type_id)
cs.assert_called('GET', '/qos-specs/%s/disassociate?vol_type_id=%s'
% (qos_id, type_id))
self._assert_request_id(qos)
def test_disassociate_all(self):
qos_id = '1B6B6A04-A927-4AEB-810B-B7BAAD49F57C'
qos = cs.qos_specs.disassociate_all(qos_id)
cs.assert_called('GET', '/qos-specs/%s/disassociate_all' % qos_id)
self._assert_request_id(qos)

View File

@ -1,68 +0,0 @@
# Copyright (c) 2013 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient.tests.unit import utils
from cinderclient.tests.unit.v2 import fakes
cs = fakes.FakeClient()
class QuotaClassSetsTest(utils.TestCase):
def test_class_quotas_get(self):
class_name = 'test'
cls = cs.quota_classes.get(class_name)
cs.assert_called('GET', '/os-quota-class-sets/%s' % class_name)
self._assert_request_id(cls)
def test_update_quota(self):
q = cs.quota_classes.get('test')
q.update(volumes=2, snapshots=2, gigabytes=2000,
backups=2, backup_gigabytes=2000,
per_volume_gigabytes=100)
cs.assert_called('PUT', '/os-quota-class-sets/test')
self._assert_request_id(q)
def test_refresh_quota(self):
q = cs.quota_classes.get('test')
q2 = cs.quota_classes.get('test')
self.assertEqual(q.volumes, q2.volumes)
self.assertEqual(q.snapshots, q2.snapshots)
self.assertEqual(q.gigabytes, q2.gigabytes)
self.assertEqual(q.backups, q2.backups)
self.assertEqual(q.backup_gigabytes, q2.backup_gigabytes)
self.assertEqual(q.per_volume_gigabytes, q2.per_volume_gigabytes)
q2.volumes = 0
self.assertNotEqual(q.volumes, q2.volumes)
q2.snapshots = 0
self.assertNotEqual(q.snapshots, q2.snapshots)
q2.gigabytes = 0
self.assertNotEqual(q.gigabytes, q2.gigabytes)
q2.backups = 0
self.assertNotEqual(q.backups, q2.backups)
q2.backup_gigabytes = 0
self.assertNotEqual(q.backup_gigabytes, q2.backup_gigabytes)
q2.per_volume_gigabytes = 0
self.assertNotEqual(q.per_volume_gigabytes, q2.per_volume_gigabytes)
q2.get()
self.assertEqual(q.volumes, q2.volumes)
self.assertEqual(q.snapshots, q2.snapshots)
self.assertEqual(q.gigabytes, q2.gigabytes)
self.assertEqual(q.backups, q2.backups)
self.assertEqual(q.backup_gigabytes, q2.backup_gigabytes)
self.assertEqual(q.per_volume_gigabytes, q2.per_volume_gigabytes)
self._assert_request_id(q)
self._assert_request_id(q2)

View File

@ -1,83 +0,0 @@
# Copyright (c) 2013 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient.tests.unit import utils
from cinderclient.tests.unit.v2 import fakes
cs = fakes.FakeClient()
class QuotaSetsTest(utils.TestCase):
def test_tenant_quotas_get(self):
tenant_id = 'test'
quota = cs.quotas.get(tenant_id)
cs.assert_called('GET', '/os-quota-sets/%s?usage=False' % tenant_id)
self._assert_request_id(quota)
def test_tenant_quotas_defaults(self):
tenant_id = 'test'
quota = cs.quotas.defaults(tenant_id)
cs.assert_called('GET', '/os-quota-sets/%s/defaults' % tenant_id)
self._assert_request_id(quota)
def test_update_quota(self):
q = cs.quotas.get('test')
q.update(volumes=2)
q.update(snapshots=2)
q.update(gigabytes=2000)
q.update(backups=2)
q.update(backup_gigabytes=2000)
q.update(per_volume_gigabytes=100)
cs.assert_called('PUT', '/os-quota-sets/test')
self._assert_request_id(q)
def test_refresh_quota(self):
q = cs.quotas.get('test')
q2 = cs.quotas.get('test')
self.assertEqual(q.volumes, q2.volumes)
self.assertEqual(q.snapshots, q2.snapshots)
self.assertEqual(q.gigabytes, q2.gigabytes)
self.assertEqual(q.backups, q2.backups)
self.assertEqual(q.backup_gigabytes, q2.backup_gigabytes)
self.assertEqual(q.per_volume_gigabytes, q2.per_volume_gigabytes)
q2.volumes = 0
self.assertNotEqual(q.volumes, q2.volumes)
q2.snapshots = 0
self.assertNotEqual(q.snapshots, q2.snapshots)
q2.gigabytes = 0
self.assertNotEqual(q.gigabytes, q2.gigabytes)
q2.backups = 0
self.assertNotEqual(q.backups, q2.backups)
q2.backup_gigabytes = 0
self.assertNotEqual(q.backup_gigabytes, q2.backup_gigabytes)
q2.per_volume_gigabytes = 0
self.assertNotEqual(q.per_volume_gigabytes, q2.per_volume_gigabytes)
q2.get()
self.assertEqual(q.volumes, q2.volumes)
self.assertEqual(q.snapshots, q2.snapshots)
self.assertEqual(q.gigabytes, q2.gigabytes)
self.assertEqual(q.backups, q2.backups)
self.assertEqual(q.backup_gigabytes, q2.backup_gigabytes)
self.assertEqual(q.per_volume_gigabytes, q2.per_volume_gigabytes)
self._assert_request_id(q)
self._assert_request_id(q2)
def test_delete_quota(self):
tenant_id = 'test'
quota = cs.quotas.delete(tenant_id)
cs.assert_called('DELETE', '/os-quota-sets/test')
self._assert_request_id(quota)

View File

@ -1,85 +0,0 @@
# Copyright (c) 2013 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient.tests.unit import utils
from cinderclient.tests.unit.v2 import fakes
from cinderclient.v2 import services
cs = fakes.FakeClient()
class ServicesTest(utils.TestCase):
def test_list_services(self):
svs = cs.services.list()
cs.assert_called('GET', '/os-services')
self.assertEqual(3, len(svs))
for service in svs:
self.assertIsInstance(service, services.Service)
# Make sure cluster fields from v3.7 are not there
self.assertFalse(hasattr(service, 'cluster'))
self._assert_request_id(svs)
def test_list_services_with_hostname(self):
svs = cs.services.list(host='host2')
cs.assert_called('GET', '/os-services?host=host2')
self.assertEqual(2, len(svs))
[self.assertIsInstance(s, services.Service) for s in svs]
[self.assertEqual('host2', s.host) for s in svs]
self._assert_request_id(svs)
def test_list_services_with_binary(self):
svs = cs.services.list(binary='cinder-volume')
cs.assert_called('GET', '/os-services?binary=cinder-volume')
self.assertEqual(2, len(svs))
[self.assertIsInstance(s, services.Service) for s in svs]
[self.assertEqual('cinder-volume', s.binary) for s in svs]
self._assert_request_id(svs)
def test_list_services_with_host_binary(self):
svs = cs.services.list('host2', 'cinder-volume')
cs.assert_called('GET', '/os-services?host=host2&binary=cinder-volume')
self.assertEqual(1, len(svs))
[self.assertIsInstance(s, services.Service) for s in svs]
[self.assertEqual('host2', s.host) for s in svs]
[self.assertEqual('cinder-volume', s.binary) for s in svs]
self._assert_request_id(svs)
def test_services_enable(self):
s = cs.services.enable('host1', 'cinder-volume')
values = {"host": "host1", 'binary': 'cinder-volume'}
cs.assert_called('PUT', '/os-services/enable', values)
self.assertIsInstance(s, services.Service)
self.assertEqual('enabled', s.status)
self._assert_request_id(s)
def test_services_disable(self):
s = cs.services.disable('host1', 'cinder-volume')
values = {"host": "host1", 'binary': 'cinder-volume'}
cs.assert_called('PUT', '/os-services/disable', values)
self.assertIsInstance(s, services.Service)
self.assertEqual('disabled', s.status)
self._assert_request_id(s)
def test_services_disable_log_reason(self):
s = cs.services.disable_log_reason(
'host1', 'cinder-volume', 'disable bad host')
values = {"host": "host1", 'binary': 'cinder-volume',
"disabled_reason": "disable bad host"}
cs.assert_called('PUT', '/os-services/disable-log-reason', values)
self.assertIsInstance(s, services.Service)
self.assertEqual('disabled', s.status)
self._assert_request_id(s)

File diff suppressed because it is too large Load Diff

View File

@ -1,58 +0,0 @@
# Copyright 2013 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient.tests.unit import utils
from cinderclient.tests.unit.fixture_data import client
from cinderclient.tests.unit.fixture_data import snapshots
class SnapshotActionsTest(utils.FixturedTestCase):
client_fixture_class = client.V2
data_fixture_class = snapshots.Fixture
def test_update_snapshot_status(self):
snap = self.cs.volume_snapshots.get('1234')
self._assert_request_id(snap)
stat = {'status': 'available'}
stats = self.cs.volume_snapshots.update_snapshot_status(snap, stat)
self.assert_called('POST', '/snapshots/1234/action')
self._assert_request_id(stats)
def test_update_snapshot_status_with_progress(self):
s = self.cs.volume_snapshots.get('1234')
self._assert_request_id(s)
stat = {'status': 'available', 'progress': '73%'}
stats = self.cs.volume_snapshots.update_snapshot_status(s, stat)
self.assert_called('POST', '/snapshots/1234/action')
self._assert_request_id(stats)
def test_list_snapshots_with_marker_limit(self):
lst = self.cs.volume_snapshots.list(marker=1234, limit=2)
self.assert_called('GET', '/snapshots/detail?limit=2&marker=1234')
self._assert_request_id(lst)
def test_list_snapshots_with_sort(self):
lst = self.cs.volume_snapshots.list(sort="id")
self.assert_called('GET', '/snapshots/detail?sort=id')
self._assert_request_id(lst)
def test_snapshot_unmanage(self):
s = self.cs.volume_snapshots.get('1234')
self._assert_request_id(s)
snap = self.cs.volume_snapshots.unmanage(s)
self.assert_called('POST', '/snapshots/1234/action',
{'os-unmanage': None})
self._assert_request_id(snap)

View File

@ -1,45 +0,0 @@
# Copyright (c) 2013 OpenStack Foundation
#
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient.v2 import volume_type_access
from cinderclient.tests.unit import utils
from cinderclient.tests.unit.v2 import fakes
cs = fakes.FakeClient()
PROJECT_UUID = '11111111-1111-1111-111111111111'
class TypeAccessTest(utils.TestCase):
def test_list(self):
access = cs.volume_type_access.list(volume_type='3')
cs.assert_called('GET', '/types/3/os-volume-type-access')
self._assert_request_id(access)
for a in access:
self.assertIsInstance(a, volume_type_access.VolumeTypeAccess)
def test_add_project_access(self):
access = cs.volume_type_access.add_project_access('3', PROJECT_UUID)
cs.assert_called('POST', '/types/3/action',
{'addProjectAccess': {'project': PROJECT_UUID}})
self._assert_request_id(access)
def test_remove_project_access(self):
access = cs.volume_type_access.remove_project_access('3', PROJECT_UUID)
cs.assert_called('POST', '/types/3/action',
{'removeProjectAccess': {'project': PROJECT_UUID}})
self._assert_request_id(access)

View File

@ -1,127 +0,0 @@
# Copyright (c) 2013 OpenStack Foundation
#
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient.v2 import volume_types
from cinderclient.tests.unit import utils
from cinderclient.tests.unit.v2 import fakes
cs = fakes.FakeClient()
class TypesTest(utils.TestCase):
def test_list_types(self):
tl = cs.volume_types.list()
cs.assert_called('GET', '/types?is_public=None')
self._assert_request_id(tl)
for t in tl:
self.assertIsInstance(t, volume_types.VolumeType)
def test_list_types_not_public(self):
t1 = cs.volume_types.list(is_public=None)
cs.assert_called('GET', '/types?is_public=None')
self._assert_request_id(t1)
def test_create(self):
t = cs.volume_types.create('test-type-3', 'test-type-3-desc')
cs.assert_called('POST', '/types',
{'volume_type': {
'name': 'test-type-3',
'description': 'test-type-3-desc',
'os-volume-type-access:is_public': True
}})
self.assertIsInstance(t, volume_types.VolumeType)
self._assert_request_id(t)
def test_create_non_public(self):
t = cs.volume_types.create('test-type-3', 'test-type-3-desc', False)
cs.assert_called('POST', '/types',
{'volume_type': {
'name': 'test-type-3',
'description': 'test-type-3-desc',
'os-volume-type-access:is_public': False
}})
self.assertIsInstance(t, volume_types.VolumeType)
self._assert_request_id(t)
def test_update(self):
t = cs.volume_types.update('1', 'test_type_1', 'test_desc_1', False)
cs.assert_called('PUT',
'/types/1',
{'volume_type': {'name': 'test_type_1',
'description': 'test_desc_1',
'is_public': False}})
self.assertIsInstance(t, volume_types.VolumeType)
self._assert_request_id(t)
def test_update_name(self):
"""Test volume_type update shell command
Verify that only name is updated and the description and
is_public properties remains unchanged.
"""
# create volume_type with is_public True
t = cs.volume_types.create('test-type-3', 'test_type-3-desc', True)
self.assertTrue(t.is_public)
# update name only
t1 = cs.volume_types.update(t.id, 'test-type-2')
cs.assert_called('PUT',
'/types/3',
{'volume_type': {'name': 'test-type-2',
'description': None}})
# verify that name is updated and the description
# and is_public are the same.
self.assertEqual('test-type-2', t1.name)
self.assertEqual('test_type-3-desc', t1.description)
self.assertTrue(t1.is_public)
def test_get(self):
t = cs.volume_types.get('1')
cs.assert_called('GET', '/types/1')
self.assertIsInstance(t, volume_types.VolumeType)
self._assert_request_id(t)
def test_default(self):
t = cs.volume_types.default()
cs.assert_called('GET', '/types/default')
self.assertIsInstance(t, volume_types.VolumeType)
self._assert_request_id(t)
def test_set_key(self):
t = cs.volume_types.get(1)
res = t.set_keys({'k': 'v'})
cs.assert_called('POST',
'/types/1/extra_specs',
{'extra_specs': {'k': 'v'}})
self._assert_request_id(res)
def test_unset_keys(self):
t = cs.volume_types.get(1)
res = t.unset_keys(['k'])
cs.assert_called('DELETE', '/types/1/extra_specs/k')
self._assert_request_id(res)
def test_unset_multiple_keys(self):
t = cs.volume_types.get(1)
res = t.unset_keys(['k', 'm'])
cs.assert_called_anytime('DELETE', '/types/1/extra_specs/k')
cs.assert_called_anytime('DELETE', '/types/1/extra_specs/m')
self._assert_request_id(res, count=2)
def test_delete(self):
t = cs.volume_types.delete(1)
cs.assert_called('DELETE', '/types/1')
self._assert_request_id(t)

View File

@ -1,167 +0,0 @@
# Copyright (C) 2013 Hewlett-Packard Development Company, L.P.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient.tests.unit import utils
from cinderclient.tests.unit.v2 import fakes
from cinderclient.v2 import volume_backups_restore
cs = fakes.FakeClient()
class VolumeBackupsTest(utils.TestCase):
def test_create(self):
vol = cs.backups.create('2b695faf-b963-40c8-8464-274008fbcef4')
cs.assert_called('POST', '/backups')
self._assert_request_id(vol)
def test_create_full(self):
vol = cs.backups.create('2b695faf-b963-40c8-8464-274008fbcef4',
None, None, False)
cs.assert_called('POST', '/backups')
self._assert_request_id(vol)
def test_create_incremental(self):
vol = cs.backups.create('2b695faf-b963-40c8-8464-274008fbcef4',
None, None, True)
cs.assert_called('POST', '/backups')
self._assert_request_id(vol)
def test_create_force(self):
vol = cs.backups.create('2b695faf-b963-40c8-8464-274008fbcef4',
None, None, False, True)
cs.assert_called('POST', '/backups')
self._assert_request_id(vol)
def test_create_snapshot(self):
cs.backups.create('2b695faf-b963-40c8-8464-274008fbcef4',
None, None, False, False,
'3c706gbg-c074-51d9-9575-385119gcdfg5')
cs.assert_called('POST', '/backups')
def test_get(self):
backup_id = '76a17945-3c6f-435c-975b-b5685db10b62'
back = cs.backups.get(backup_id)
cs.assert_called('GET', '/backups/%s' % backup_id)
self._assert_request_id(back)
def test_list(self):
lst = cs.backups.list()
cs.assert_called('GET', '/backups/detail')
self._assert_request_id(lst)
def test_list_with_pagination(self):
lst = cs.backups.list(limit=2, marker=100)
cs.assert_called('GET', '/backups/detail?limit=2&marker=100')
self._assert_request_id(lst)
def test_sorted_list(self):
lst = cs.backups.list(sort="id")
cs.assert_called('GET', '/backups/detail?sort=id')
self._assert_request_id(lst)
def test_sorted_list_by_data_timestamp(self):
cs.backups.list(sort="data_timestamp")
cs.assert_called('GET', '/backups/detail?sort=data_timestamp')
def test_delete(self):
b = cs.backups.list()[0]
del_back = b.delete()
cs.assert_called('DELETE',
'/backups/76a17945-3c6f-435c-975b-b5685db10b62')
self._assert_request_id(del_back)
del_back = cs.backups.delete('76a17945-3c6f-435c-975b-b5685db10b62')
cs.assert_called('DELETE',
'/backups/76a17945-3c6f-435c-975b-b5685db10b62')
self._assert_request_id(del_back)
del_back = cs.backups.delete(b)
cs.assert_called('DELETE',
'/backups/76a17945-3c6f-435c-975b-b5685db10b62')
self._assert_request_id(del_back)
def test_force_delete_with_True_force_param_value(self):
"""Tests delete backup with force parameter set to True"""
b = cs.backups.list()[0]
del_back = b.delete(force=True)
expected_body = {'os-force_delete': None}
cs.assert_called('POST',
'/backups/76a17945-3c6f-435c-975b-b5685db10b62/action',
expected_body)
self._assert_request_id(del_back)
def test_force_delete_with_false_force_param_vaule(self):
"""To delete backup with force parameter set to False"""
b = cs.backups.list()[0]
del_back = b.delete(force=False)
cs.assert_called('DELETE',
'/backups/76a17945-3c6f-435c-975b-b5685db10b62')
self._assert_request_id(del_back)
del_back = cs.backups.delete('76a17945-3c6f-435c-975b-b5685db10b62')
cs.assert_called('DELETE',
'/backups/76a17945-3c6f-435c-975b-b5685db10b62')
self._assert_request_id(del_back)
del_back = cs.backups.delete(b)
cs.assert_called('DELETE',
'/backups/76a17945-3c6f-435c-975b-b5685db10b62')
self._assert_request_id(del_back)
def test_restore(self):
backup_id = '76a17945-3c6f-435c-975b-b5685db10b62'
info = cs.restores.restore(backup_id)
cs.assert_called('POST', '/backups/%s/restore' % backup_id)
self.assertIsInstance(info,
volume_backups_restore.VolumeBackupsRestore)
self._assert_request_id(info)
def test_restore_with_name(self):
backup_id = '76a17945-3c6f-435c-975b-b5685db10b62'
name = 'restore_vol'
info = cs.restores.restore(backup_id, name=name)
expected_body = {'restore': {'volume_id': None, 'name': name}}
cs.assert_called('POST', '/backups/%s/restore' % backup_id,
body=expected_body)
self.assertIsInstance(info,
volume_backups_restore.VolumeBackupsRestore)
def test_reset_state(self):
b = cs.backups.list()[0]
api = '/backups/76a17945-3c6f-435c-975b-b5685db10b62/action'
st = b.reset_state(state='error')
cs.assert_called('POST', api)
self._assert_request_id(st)
st = cs.backups.reset_state('76a17945-3c6f-435c-975b-b5685db10b62',
state='error')
cs.assert_called('POST', api)
self._assert_request_id(st)
st = cs.backups.reset_state(b, state='error')
cs.assert_called('POST', api)
self._assert_request_id(st)
def test_record_export(self):
backup_id = '76a17945-3c6f-435c-975b-b5685db10b62'
export = cs.backups.export_record(backup_id)
cs.assert_called('GET',
'/backups/%s/export_record' % backup_id)
self._assert_request_id(export)
def test_record_import(self):
backup_service = 'fake-backup-service'
backup_url = 'fake-backup-url'
expected_body = {'backup-record': {'backup_service': backup_service,
'backup_url': backup_url}}
impt = cs.backups.import_record(backup_service, backup_url)
cs.assert_called('POST', '/backups/import_record', expected_body)
self._assert_request_id(impt)

View File

@ -1,134 +0,0 @@
# Copyright (c) 2013 The Johns Hopkins University/Applied Physics Laboratory
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient.v2.volume_encryption_types import VolumeEncryptionType
from cinderclient.tests.unit import utils
from cinderclient.tests.unit.v2 import fakes
cs = fakes.FakeClient()
FAKE_ENCRY_TYPE = {'provider': 'Test',
'key_size': None,
'cipher': None,
'control_location': None,
'volume_type_id': '65922555-7bc0-47e9-8d88-c7fdbcac4781',
'encryption_id': '62daf814-cf9b-401c-8fc8-f84d7850fb7c'}
class VolumeEncryptionTypesTest(utils.TestCase):
"""
Test suite for the Volume Encryption Types Resource and Manager.
"""
def test_list(self):
"""
Unit test for VolumeEncryptionTypesManager.list
Verify that a series of GET requests are made:
- one GET request for the list of volume types
- one GET request per volume type for encryption type information
Verify that all returned information is :class: VolumeEncryptionType
"""
encryption_types = cs.volume_encryption_types.list()
cs.assert_called_anytime('GET', '/types?is_public=None')
cs.assert_called_anytime('GET', '/types/2/encryption')
cs.assert_called_anytime('GET', '/types/1/encryption')
for encryption_type in encryption_types:
self.assertIsInstance(encryption_type, VolumeEncryptionType)
self._assert_request_id(encryption_type)
def test_get(self):
"""
Unit test for VolumeEncryptionTypesManager.get
Verify that one GET request is made for the volume type encryption
type information. Verify that returned information is :class:
VolumeEncryptionType
"""
encryption_type = cs.volume_encryption_types.get(1)
cs.assert_called('GET', '/types/1/encryption')
self.assertIsInstance(encryption_type, VolumeEncryptionType)
self._assert_request_id(encryption_type)
def test_get_no_encryption(self):
"""
Unit test for VolumeEncryptionTypesManager.get
Verify that a request on a volume type with no associated encryption
type information returns a VolumeEncryptionType with no attributes.
"""
encryption_type = cs.volume_encryption_types.get(2)
self.assertIsInstance(encryption_type, VolumeEncryptionType)
self.assertFalse(hasattr(encryption_type, 'id'),
'encryption type has an id')
self._assert_request_id(encryption_type)
def test_create(self):
"""
Unit test for VolumeEncryptionTypesManager.create
Verify that one POST request is made for the encryption type creation.
Verify that encryption type creation returns a VolumeEncryptionType.
"""
result = cs.volume_encryption_types.create(2, {'provider': 'Test',
'key_size': None,
'cipher': None,
'control_location':
None})
cs.assert_called('POST', '/types/2/encryption')
self.assertIsInstance(result, VolumeEncryptionType)
self._assert_request_id(result)
def test_update(self):
"""
Unit test for VolumeEncryptionTypesManager.update
Verify that one PUT request is made for encryption type update
Verify that an empty encryption-type update returns the original
encryption-type information.
"""
expected = {'id': 1, 'volume_type_id': 1, 'provider': 'test',
'cipher': 'test', 'key_size': 1,
'control_location': 'front-end'}
result = cs.volume_encryption_types.update(1, {})
cs.assert_called('PUT', '/types/1/encryption/provider')
self.assertEqual(expected, result,
"empty update must yield original data")
self._assert_request_id(result)
def test_delete(self):
"""
Unit test for VolumeEncryptionTypesManager.delete
Verify that one DELETE request is made for encryption type deletion
Verify that encryption type deletion returns None
"""
result = cs.volume_encryption_types.delete(1)
cs.assert_called('DELETE', '/types/1/encryption/provider')
self.assertIsInstance(result, tuple)
self.assertEqual(202, result[0].status_code)
self._assert_request_id(result)
def test___repr__(self):
"""
Unit test for VolumeEncryptionTypes.__repr__
Verify that one encryption type can be printed
"""
encry_type = VolumeEncryptionType(None, FAKE_ENCRY_TYPE)
self.assertEqual(
"<VolumeEncryptionType: %s>" % FAKE_ENCRY_TYPE['encryption_id'],
repr(encry_type))

View File

@ -1,58 +0,0 @@
# Copyright (C) 2013 Hewlett-Packard Development Company, L.P.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient.tests.unit import utils
from cinderclient.tests.unit.v2 import fakes
cs = fakes.FakeClient()
class VolumeTransfersTest(utils.TestCase):
def test_create(self):
vol = cs.transfers.create('1234')
cs.assert_called('POST', '/os-volume-transfer')
self._assert_request_id(vol)
def test_get(self):
transfer_id = '5678'
vol = cs.transfers.get(transfer_id)
cs.assert_called('GET', '/os-volume-transfer/%s' % transfer_id)
self._assert_request_id(vol)
def test_list(self):
lst = cs.transfers.list()
cs.assert_called('GET', '/os-volume-transfer/detail')
self._assert_request_id(lst)
def test_delete(self):
b = cs.transfers.list()[0]
vol = b.delete()
cs.assert_called('DELETE', '/os-volume-transfer/5678')
self._assert_request_id(vol)
vol = cs.transfers.delete('5678')
self._assert_request_id(vol)
cs.assert_called('DELETE', '/os-volume-transfer/5678')
vol = cs.transfers.delete(b)
cs.assert_called('DELETE', '/os-volume-transfer/5678')
self._assert_request_id(vol)
def test_accept(self):
transfer_id = '5678'
auth_key = '12345'
vol = cs.transfers.accept(transfer_id, auth_key)
cs.assert_called('POST', '/os-volume-transfer/%s/accept' % transfer_id)
self._assert_request_id(vol)

View File

@ -1,386 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2013 OpenStack Foundation
#
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient.tests.unit import utils
from cinderclient.tests.unit.v2 import fakes
from cinderclient.v2.volumes import Volume
cs = fakes.FakeClient()
class VolumesTest(utils.TestCase):
def test_list_volumes_with_marker_limit(self):
lst = cs.volumes.list(marker=1234, limit=2)
cs.assert_called('GET', '/volumes/detail?limit=2&marker=1234')
self._assert_request_id(lst)
def test_list_volumes_with_sort_key_dir(self):
lst = cs.volumes.list(sort_key='id', sort_dir='asc')
cs.assert_called('GET', '/volumes/detail?sort_dir=asc&sort_key=id')
self._assert_request_id(lst)
def test_list_volumes_with_invalid_sort_key(self):
self.assertRaises(ValueError,
cs.volumes.list, sort_key='invalid', sort_dir='asc')
def test_list_volumes_with_invalid_sort_dir(self):
self.assertRaises(ValueError,
cs.volumes.list, sort_key='id', sort_dir='invalid')
def test__list(self):
# There only 2 volumes available for our tests, so we set limit to 2.
limit = 2
url = "/volumes?limit=%s" % limit
response_key = "volumes"
fake_volume1234 = Volume(self, {'id': 1234,
'name': 'sample-volume'},
loaded=True)
fake_volume5678 = Volume(self, {'id': 5678,
'name': 'sample-volume2'},
loaded=True)
fake_volumes = [fake_volume1234, fake_volume5678]
# osapi_max_limit is 1000 by default. If limit is less than
# osapi_max_limit, we can get 2 volumes back.
volumes = cs.volumes._list(url, response_key, limit=limit)
self._assert_request_id(volumes)
cs.assert_called('GET', url)
self.assertEqual(fake_volumes, volumes)
# When we change the osapi_max_limit to 1, the next link should be
# generated. If limit equals 2 and id passed as an argument, we can
# still get 2 volumes back, because the method _list will fetch the
# volume from the next link.
cs.client.osapi_max_limit = 1
volumes = cs.volumes._list(url, response_key, limit=limit)
self.assertEqual(fake_volumes, volumes)
self._assert_request_id(volumes)
cs.client.osapi_max_limit = 1000
def test_delete_volume(self):
v = cs.volumes.list()[0]
del_v = v.delete()
cs.assert_called('DELETE', '/volumes/1234')
self._assert_request_id(del_v)
del_v = cs.volumes.delete('1234')
cs.assert_called('DELETE', '/volumes/1234')
self._assert_request_id(del_v)
del_v = cs.volumes.delete(v)
cs.assert_called('DELETE', '/volumes/1234')
self._assert_request_id(del_v)
def test_create_volume(self):
vol = cs.volumes.create(1)
cs.assert_called('POST', '/volumes')
self._assert_request_id(vol)
def test_create_volume_with_hint(self):
vol = cs.volumes.create(1, scheduler_hints='uuid')
expected = {'volume': {'status': 'creating',
'description': None,
'availability_zone': None,
'source_volid': None,
'snapshot_id': None,
'size': 1,
'user_id': None,
'name': None,
'imageRef': None,
'attach_status': 'detached',
'volume_type': None,
'project_id': None,
'metadata': {},
'source_replica': None,
'consistencygroup_id': None,
'multiattach': False},
'OS-SCH-HNT:scheduler_hints': 'uuid'}
cs.assert_called('POST', '/volumes', body=expected)
self._assert_request_id(vol)
def test_attach(self):
v = cs.volumes.get('1234')
self._assert_request_id(v)
vol = cs.volumes.attach(v, 1, '/dev/vdc', mode='ro')
cs.assert_called('POST', '/volumes/1234/action')
self._assert_request_id(vol)
def test_attach_to_host(self):
v = cs.volumes.get('1234')
self._assert_request_id(v)
vol = cs.volumes.attach(v, None, None, host_name='test', mode='rw')
cs.assert_called('POST', '/volumes/1234/action')
self._assert_request_id(vol)
def test_detach(self):
v = cs.volumes.get('1234')
self._assert_request_id(v)
vol = cs.volumes.detach(v, 'abc123')
cs.assert_called('POST', '/volumes/1234/action')
self._assert_request_id(vol)
def test_reserve(self):
v = cs.volumes.get('1234')
self._assert_request_id(v)
vol = cs.volumes.reserve(v)
cs.assert_called('POST', '/volumes/1234/action')
self._assert_request_id(vol)
def test_unreserve(self):
v = cs.volumes.get('1234')
self._assert_request_id(v)
vol = cs.volumes.unreserve(v)
cs.assert_called('POST', '/volumes/1234/action')
self._assert_request_id(vol)
def test_begin_detaching(self):
v = cs.volumes.get('1234')
cs.volumes.begin_detaching(v)
cs.assert_called('POST', '/volumes/1234/action')
def test_roll_detaching(self):
v = cs.volumes.get('1234')
self._assert_request_id(v)
vol = cs.volumes.roll_detaching(v)
cs.assert_called('POST', '/volumes/1234/action')
self._assert_request_id(vol)
def test_initialize_connection(self):
v = cs.volumes.get('1234')
self._assert_request_id(v)
vol = cs.volumes.initialize_connection(v, {})
cs.assert_called('POST', '/volumes/1234/action')
self._assert_request_id(vol)
def test_terminate_connection(self):
v = cs.volumes.get('1234')
self._assert_request_id(v)
vol = cs.volumes.terminate_connection(v, {})
cs.assert_called('POST', '/volumes/1234/action')
self._assert_request_id(vol)
def test_set_metadata(self):
vol = cs.volumes.set_metadata(1234, {'k1': 'v2', 'тест': 'тест'})
cs.assert_called('POST', '/volumes/1234/metadata',
{'metadata': {'k1': 'v2', 'тест': 'тест'}})
self._assert_request_id(vol)
def test_delete_metadata(self):
keys = ['key1']
vol = cs.volumes.delete_metadata(1234, keys)
cs.assert_called('DELETE', '/volumes/1234/metadata/key1')
self._assert_request_id(vol)
def test_extend(self):
v = cs.volumes.get('1234')
self._assert_request_id(v)
vol = cs.volumes.extend(v, 2)
cs.assert_called('POST', '/volumes/1234/action')
self._assert_request_id(vol)
def test_reset_state(self):
v = cs.volumes.get('1234')
self._assert_request_id(v)
vol = cs.volumes.reset_state(v, 'in-use', attach_status='detached')
cs.assert_called('POST', '/volumes/1234/action')
self._assert_request_id(vol)
def test_reset_state_migration_status(self):
v = cs.volumes.get('1234')
self._assert_request_id(v)
vol = cs.volumes.reset_state(v, 'in-use', attach_status='detached',
migration_status='none')
cs.assert_called('POST', '/volumes/1234/action')
self._assert_request_id(vol)
def test_get_encryption_metadata(self):
vol = cs.volumes.get_encryption_metadata('1234')
cs.assert_called('GET', '/volumes/1234/encryption')
self._assert_request_id(vol)
def test_migrate(self):
v = cs.volumes.get('1234')
self._assert_request_id(v)
vol = cs.volumes.migrate_volume(v, 'dest', False, False)
cs.assert_called('POST', '/volumes/1234/action',
{'os-migrate_volume': {'host': 'dest',
'force_host_copy': False,
'lock_volume': False}})
self._assert_request_id(vol)
def test_migrate_with_lock_volume(self):
v = cs.volumes.get('1234')
self._assert_request_id(v)
vol = cs.volumes.migrate_volume(v, 'dest', False, True)
cs.assert_called('POST', '/volumes/1234/action',
{'os-migrate_volume': {'host': 'dest',
'force_host_copy': False,
'lock_volume': True}})
self._assert_request_id(vol)
def test_metadata_update_all(self):
vol = cs.volumes.update_all_metadata(1234, {'k1': 'v1'})
cs.assert_called('PUT', '/volumes/1234/metadata',
{'metadata': {'k1': 'v1'}})
self._assert_request_id(vol)
def test_readonly_mode_update(self):
v = cs.volumes.get('1234')
self._assert_request_id(v)
vol = cs.volumes.update_readonly_flag(v, True)
cs.assert_called('POST', '/volumes/1234/action')
self._assert_request_id(vol)
def test_retype(self):
v = cs.volumes.get('1234')
self._assert_request_id(v)
vol = cs.volumes.retype(v, 'foo', 'on-demand')
cs.assert_called('POST', '/volumes/1234/action',
{'os-retype': {'new_type': 'foo',
'migration_policy': 'on-demand'}})
self._assert_request_id(vol)
def test_set_bootable(self):
v = cs.volumes.get('1234')
self._assert_request_id(v)
vol = cs.volumes.set_bootable(v, True)
cs.assert_called('POST', '/volumes/1234/action')
self._assert_request_id(vol)
def test_volume_manage(self):
vol = cs.volumes.manage('host1', {'k': 'v'})
expected = {'host': 'host1', 'name': None, 'availability_zone': None,
'description': None, 'metadata': None, 'ref': {'k': 'v'},
'volume_type': None, 'bootable': False}
cs.assert_called('POST', '/os-volume-manage', {'volume': expected})
self._assert_request_id(vol)
def test_volume_manage_bootable(self):
vol = cs.volumes.manage('host1', {'k': 'v'}, bootable=True)
expected = {'host': 'host1', 'name': None, 'availability_zone': None,
'description': None, 'metadata': None, 'ref': {'k': 'v'},
'volume_type': None, 'bootable': True}
cs.assert_called('POST', '/os-volume-manage', {'volume': expected})
self._assert_request_id(vol)
def test_volume_list_manageable(self):
cs.volumes.list_manageable('host1', detailed=False)
cs.assert_called('GET', '/os-volume-manage?host=host1')
def test_volume_list_manageable_detailed(self):
cs.volumes.list_manageable('host1', detailed=True)
cs.assert_called('GET', '/os-volume-manage/detail?host=host1')
def test_volume_unmanage(self):
v = cs.volumes.get('1234')
self._assert_request_id(v)
vol = cs.volumes.unmanage(v)
cs.assert_called('POST', '/volumes/1234/action', {'os-unmanage': None})
self._assert_request_id(vol)
def test_snapshot_manage(self):
vol = cs.volume_snapshots.manage('volume_id1', {'k': 'v'})
expected = {'volume_id': 'volume_id1', 'name': None,
'description': None, 'metadata': None, 'ref': {'k': 'v'}}
cs.assert_called('POST', '/os-snapshot-manage', {'snapshot': expected})
self._assert_request_id(vol)
def test_snapshot_list_manageable(self):
cs.volume_snapshots.list_manageable('host1', detailed=False)
cs.assert_called('GET', '/os-snapshot-manage?host=host1')
def test_snapshot_list_manageable_detailed(self):
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')
self._assert_request_id(vol)
def test_get_pools_detail(self):
vol = cs.volumes.get_pools('--detail')
cs.assert_called('GET', '/scheduler-stats/get_pools?detail=True')
self._assert_request_id(vol)
class FormatSortParamTestCase(utils.TestCase):
def test_format_sort_empty_input(self):
for s in [None, '', []]:
self.assertIsNone(cs.volumes._format_sort_param(s))
def test_format_sort_string_single_key(self):
s = 'id'
self.assertEqual('id', cs.volumes._format_sort_param(s))
def test_format_sort_string_single_key_and_dir(self):
s = 'id:asc'
self.assertEqual('id:asc', cs.volumes._format_sort_param(s))
def test_format_sort_string_multiple(self):
s = 'id:asc,status,size:desc'
self.assertEqual('id:asc,status,size:desc',
cs.volumes._format_sort_param(s))
def test_format_sort_string_mappings(self):
s = 'id:asc,name,size:desc'
self.assertEqual('id:asc,display_name,size:desc',
cs.volumes._format_sort_param(s))
def test_format_sort_whitespace_trailing_comma(self):
s = ' id : asc ,status, size:desc,'
self.assertEqual('id:asc,status,size:desc',
cs.volumes._format_sort_param(s))
def test_format_sort_list_of_strings(self):
s = ['id:asc', 'status', 'size:desc']
self.assertEqual('id:asc,status,size:desc',
cs.volumes._format_sort_param(s))
def test_format_sort_list_of_tuples(self):
s = [('id', 'asc'), 'status', ('size', 'desc')]
self.assertEqual('id:asc,status,size:desc',
cs.volumes._format_sort_param(s))
def test_format_sort_list_of_strings_and_tuples(self):
s = [('id', 'asc'), 'status', 'size:desc']
self.assertEqual('id:asc,status,size:desc',
cs.volumes._format_sort_param(s))
def test_format_sort_invalid_direction(self):
for s in ['id:foo',
'id:asc,status,size:foo',
['id', 'status', 'size:foo'],
['id', 'status', ('size', 'foo')]]:
self.assertRaises(ValueError,
cs.volumes._format_sort_param,
s)

View File

@ -1,605 +0,0 @@
# Copyright (c) 2013 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from datetime import datetime
from cinderclient.tests.unit import fakes
from cinderclient.v3 import client
from cinderclient.tests.unit.v2 import fakes as fake_v2
def _stub_group(detailed=True, **kwargs):
group = {
"name": "test-1",
"id": "1234",
}
if detailed:
details = {
"created_at": "2012-08-28T16:30:31.000000",
"description": "test-1-desc",
"availability_zone": "zone1",
"status": "available",
"group_type": "my_group_type",
}
group.update(details)
group.update(kwargs)
return group
def _stub_group_snapshot(detailed=True, **kwargs):
group_snapshot = {
"name": None,
"id": "5678",
}
if detailed:
details = {
"created_at": "2012-08-28T16:30:31.000000",
"description": None,
"name": None,
"id": "5678",
"status": "available",
"group_id": "1234",
}
group_snapshot.update(details)
group_snapshot.update(kwargs)
return group_snapshot
def _stub_snapshot(**kwargs):
snapshot = {
"created_at": "2012-08-28T16:30:31.000000",
"display_description": None,
"display_name": None,
"id": '11111111-1111-1111-1111-111111111111',
"size": 1,
"status": "available",
"volume_id": '00000000-0000-0000-0000-000000000000',
}
snapshot.update(kwargs)
return snapshot
class FakeClient(fakes.FakeClient, client.Client):
def __init__(self, api_version=None, *args, **kwargs):
client.Client.__init__(self, 'username', 'password',
'project_id', 'auth_url',
extensions=kwargs.get('extensions'))
self.api_version = api_version
global_id = "req-f551871a-4950-4225-9b2c-29a14c8f075e"
self.client = FakeHTTPClient(api_version=api_version,
global_request_id=global_id, **kwargs)
def get_volume_api_version_from_endpoint(self):
return self.client.get_volume_api_version_from_endpoint()
class FakeHTTPClient(fake_v2.FakeHTTPClient):
def __init__(self, **kwargs):
super(FakeHTTPClient, self).__init__()
self.management_url = 'http://10.0.2.15:8776/v3/fake'
vars(self).update(kwargs)
#
# Services
#
def get_os_services(self, **kw):
host = kw.get('host', None)
binary = kw.get('binary', None)
services = [
{
'id': 1,
'binary': 'cinder-volume',
'host': 'host1',
'zone': 'cinder',
'status': 'enabled',
'state': 'up',
'updated_at': datetime(2012, 10, 29, 13, 42, 2),
'cluster': 'cluster1',
},
{
'id': 2,
'binary': 'cinder-volume',
'host': 'host2',
'zone': 'cinder',
'status': 'disabled',
'state': 'down',
'updated_at': datetime(2012, 9, 18, 8, 3, 38),
'cluster': 'cluster1',
},
{
'id': 3,
'binary': 'cinder-scheduler',
'host': 'host2',
'zone': 'cinder',
'status': 'disabled',
'state': 'down',
'updated_at': datetime(2012, 9, 18, 8, 3, 38),
'cluster': 'cluster2',
},
]
if host:
services = [i for i in services if i['host'] == host]
if binary:
services = [i for i in services if i['binary'] == binary]
if not self.api_version.matches('3.7'):
for svc in services:
del svc['cluster']
return (200, {}, {'services': services})
#
# Clusters
#
def _filter_clusters(self, return_keys, **kw):
date = datetime(2012, 10, 29, 13, 42, 2),
clusters = [
{
'id': '1',
'name': 'cluster1@lvmdriver-1',
'state': 'up',
'status': 'enabled',
'binary': 'cinder-volume',
'is_up': 'True',
'disabled': 'False',
'disabled_reason': None,
'num_hosts': '3',
'num_down_hosts': '2',
'updated_at': date,
'created_at': date,
'last_heartbeat': date,
},
{
'id': '2',
'name': 'cluster1@lvmdriver-2',
'state': 'down',
'status': 'enabled',
'binary': 'cinder-volume',
'is_up': 'False',
'disabled': 'False',
'disabled_reason': None,
'num_hosts': '2',
'num_down_hosts': '2',
'updated_at': date,
'created_at': date,
'last_heartbeat': date,
},
{
'id': '3',
'name': 'cluster2',
'state': 'up',
'status': 'disabled',
'binary': 'cinder-backup',
'is_up': 'True',
'disabled': 'True',
'disabled_reason': 'Reason',
'num_hosts': '1',
'num_down_hosts': '0',
'updated_at': date,
'created_at': date,
'last_heartbeat': date,
},
]
for key, value in kw.items():
clusters = [cluster for cluster in clusters
if cluster[key] == str(value)]
result = []
for cluster in clusters:
result.append({key: cluster[key] for key in return_keys})
return result
CLUSTER_SUMMARY_KEYS = ('name', 'binary', 'state', 'status')
CLUSTER_DETAIL_KEYS = (CLUSTER_SUMMARY_KEYS +
('num_hosts', 'num_down_hosts', 'last_heartbeat',
'disabled_reason', 'created_at', 'updated_at'))
def get_clusters(self, **kw):
clusters = self._filter_clusters(self.CLUSTER_SUMMARY_KEYS, **kw)
return (200, {}, {'clusters': clusters})
def get_clusters_detail(self, **kw):
clusters = self._filter_clusters(self.CLUSTER_DETAIL_KEYS, **kw)
return (200, {}, {'clusters': clusters})
def get_clusters_1(self):
res = self.get_clusters_detail(id=1)
return (200, {}, {'cluster': res[2]['clusters'][0]})
def put_clusters_enable(self, body):
res = self.get_clusters(id=1)
return (200, {}, {'cluster': res[2]['clusters'][0]})
def put_clusters_disable(self, body):
res = self.get_clusters(id=3)
return (200, {}, {'cluster': res[2]['clusters'][0]})
#
# Backups
#
def put_backups_1234(self, **kw):
backup = fake_v2._stub_backup(
id='1234',
base_uri='http://localhost:8776',
tenant_id='0fa851f6668144cf9cd8c8419c1646c1')
return (200, {},
{'backups': backup})
#
# Attachments
#
def post_attachments(self, **kw):
return (202, {}, {
'attachment': {'instance': 1234,
'name': 'attachment-1',
'volume_id': 'fake_volume_1',
'status': 'reserved'}})
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'}]})
def get_attachments_1234(self, **kw):
return (200, {}, {
'attachment': {'instance': 1234,
'name': 'attachment-1',
'volume_id': 'fake_volume_1',
'status': 'reserved'}})
def put_attachments_1234(self, **kw):
return (200, {}, {
'attachment': {'instance': 1234,
'name': 'attachment-1',
'volume_id': 'fake_volume_1',
'status': 'reserved'}})
def delete_attachments_1234(self, **kw):
return 204, {}, None
#
# GroupTypes
#
def get_group_types(self, **kw):
return (200, {}, {
'group_types': [{'id': 1,
'name': 'test-type-1',
'description': 'test_type-1-desc',
'group_specs': {}},
{'id': 2,
'name': 'test-type-2',
'description': 'test_type-2-desc',
'group_specs': {}}]})
def get_group_types_1(self, **kw):
return (200, {}, {'group_type': {'id': 1,
'name': 'test-type-1',
'description': 'test_type-1-desc',
'group_specs': {u'key': u'value'}}})
def get_group_types_2(self, **kw):
return (200, {}, {'group_type': {'id': 2,
'name': 'test-type-2',
'description': 'test_type-2-desc',
'group_specs': {}}})
def get_group_types_3(self, **kw):
return (200, {}, {'group_type': {'id': 3,
'name': 'test-type-3',
'description': 'test_type-3-desc',
'group_specs': {},
'is_public': False}})
def get_group_types_default(self, **kw):
return self.get_group_types_1()
def post_group_types(self, body, **kw):
return (202, {}, {'group_type': {'id': 3,
'name': 'test-type-3',
'description': 'test_type-3-desc',
'group_specs': {}}})
def post_group_types_1_group_specs(self, body, **kw):
assert list(body) == ['group_specs']
return (200, {}, {'group_specs': {'k': 'v'}})
def delete_group_types_1_group_specs_k(self, **kw):
return(204, {}, None)
def delete_group_types_1_group_specs_m(self, **kw):
return(204, {}, None)
def delete_group_types_1(self, **kw):
return (202, {}, None)
def delete_group_types_3_group_specs_k(self, **kw):
return(204, {}, None)
def delete_group_types_3(self, **kw):
return (202, {}, None)
def put_group_types_1(self, **kw):
return self.get_group_types_1()
#
# Groups
#
def get_groups_detail(self, **kw):
return (200, {}, {"groups": [
_stub_group(id='1234'),
_stub_group(id='4567')]})
def get_groups(self, **kw):
return (200, {}, {"groups": [
_stub_group(detailed=False, id='1234'),
_stub_group(detailed=False, id='4567')]})
def get_groups_1234(self, **kw):
return (200, {}, {'group':
_stub_group(id='1234')})
def post_groups(self, **kw):
group = _stub_group(id='1234', group_type='my_group_type',
volume_types=['type1', 'type2'])
return (202, {}, {'group': group})
def put_groups_1234(self, **kw):
return (200, {}, {'group': {}})
def post_groups_1234_action(self, body, **kw):
resp = 202
assert len(list(body)) == 1
action = list(body)[0]
if action == 'delete':
assert 'delete-volumes' in body[action]
elif action in ('enable_replication', 'disable_replication',
'failover_replication', 'list_replication_targets'):
assert action in body
else:
raise AssertionError("Unexpected action: %s" % action)
return (resp, {}, {})
def post_groups_action(self, body, **kw):
group = _stub_group(id='1234', group_type='my_group_type',
volume_types=['type1', 'type2'])
resp = 202
assert len(list(body)) == 1
action = list(body)[0]
if action == 'create-from-src':
assert ('group_snapshot_id' in body[action] or
'source_group_id' in body[action])
else:
raise AssertionError("Unexpected action: %s" % action)
return (resp, {}, {'group': group})
#
# group_snapshots
#
def get_group_snapshots_detail(self, **kw):
return (200, {}, {"group_snapshots": [
_stub_group_snapshot(id='1234'),
_stub_group_snapshot(id='4567')]})
def get_group_snapshots(self, **kw):
return (200, {}, {"group_snapshots": [
_stub_group_snapshot(detailed=False, id='1234'),
_stub_group_snapshot(detailed=False, id='4567')]})
def get_group_snapshots_1234(self, **kw):
return (200, {}, {'group_snapshot': _stub_group_snapshot(id='1234')})
def get_group_snapshots_5678(self, **kw):
return (200, {}, {'group_snapshot': _stub_group_snapshot(id='5678')})
def post_group_snapshots(self, **kw):
group_snap = _stub_group_snapshot()
return (202, {}, {'group_snapshot': group_snap})
def put_group_snapshots_1234(self, **kw):
return (200, {}, {'group_snapshot': {}})
def post_groups_1234_action(self, **kw):
return (202, {}, {})
def get_groups_5678(self, **kw):
return (200, {}, {'group':
_stub_group(id='5678')})
def post_groups_5678_action(self, **kw):
return (202, {}, {})
def post_snapshots_1234_action(self, **kw):
return (202, {}, {})
def get_snapshots_1234(self, **kw):
return (200, {}, {'snapshot': _stub_snapshot(id='1234')})
def post_snapshots_5678_action(self, **kw):
return (202, {}, {})
def get_snapshots_5678(self, **kw):
return (200, {}, {'snapshot': _stub_snapshot(id='5678')})
def post_group_snapshots_1234_action(self, **kw):
return (202, {}, {})
def post_group_snapshots_5678_action(self, **kw):
return (202, {}, {})
def get_group_snapshots_5678(self, **kw):
return (200, {}, {'group_snapshot': _stub_group_snapshot(id='5678')})
def delete_group_snapshots_1234(self, **kw):
return (202, {}, {})
#
# Manageable volumes/snapshots
#
def get_manageable_volumes(self, **kw):
vol_id = "volume-ffffffff-0000-ffff-0000-ffffffffffff"
vols = [{"size": 4, "safe_to_manage": False, "actual_size": 4.0,
"reference": {"source-name": vol_id}},
{"size": 5, "safe_to_manage": True, "actual_size": 4.3,
"reference": {"source-name": "myvol"}}]
return (200, {}, {"manageable-volumes": vols})
def get_manageable_volumes_detail(self, **kw):
vol_id = "volume-ffffffff-0000-ffff-0000-ffffffffffff"
vols = [{"size": 4, "reason_not_safe": "volume in use",
"safe_to_manage": False, "extra_info": "qos_setting:high",
"reference": {"source-name": vol_id},
"actual_size": 4.0},
{"size": 5, "reason_not_safe": None, "safe_to_manage": True,
"extra_info": "qos_setting:low", "actual_size": 4.3,
"reference": {"source-name": "myvol"}}]
return (200, {}, {"manageable-volumes": vols})
def get_manageable_snapshots(self, **kw):
snap_id = "snapshot-ffffffff-0000-ffff-0000-ffffffffffff"
snaps = [{"actual_size": 4.0, "size": 4,
"safe_to_manage": False, "source_id_type": "source-name",
"source_cinder_id": "00000000-ffff-0000-ffff-00000000",
"reference": {"source-name": snap_id},
"source_identifier": "volume-00000000-ffff-0000-ffff-000000"},
{"actual_size": 4.3, "reference": {"source-name": "mysnap"},
"source_id_type": "source-name", "source_identifier": "myvol",
"safe_to_manage": True, "source_cinder_id": None, "size": 5}]
return (200, {}, {"manageable-snapshots": snaps})
def get_manageable_snapshots_detail(self, **kw):
snap_id = "snapshot-ffffffff-0000-ffff-0000-ffffffffffff"
snaps = [{"actual_size": 4.0, "size": 4,
"safe_to_manage": False, "source_id_type": "source-name",
"source_cinder_id": "00000000-ffff-0000-ffff-00000000",
"reference": {"source-name": snap_id},
"source_identifier": "volume-00000000-ffff-0000-ffff-000000",
"extra_info": "qos_setting:high",
"reason_not_safe": "snapshot in use"},
{"actual_size": 4.3, "reference": {"source-name": "mysnap"},
"safe_to_manage": True, "source_cinder_id": None,
"source_id_type": "source-name", "identifier": "mysnap",
"source_identifier": "myvol", "size": 5,
"extra_info": "qos_setting:low", "reason_not_safe": None}]
return (200, {}, {"manageable-snapshots": snaps})
#
# Messages
#
def get_messages(self, **kw):
return 200, {}, {'messages': [
{
'id': '1234',
'event_id': 'VOLUME_000002',
'user_message': 'Fake Message',
'created_at': '2012-08-27T00:00:00.000000',
'guaranteed_until': "2013-11-12T21:00:00.000000",
},
{
'id': '12345',
'event_id': 'VOLUME_000002',
'user_message': 'Fake Message',
'created_at': '2012-08-27T00:00:00.000000',
'guaranteed_until': "2013-11-12T21:00:00.000000",
}
]}
def delete_messages_1234(self, **kw):
return 204, {}, None
def delete_messages_12345(self, **kw):
return 204, {}, None
def get_messages_1234(self, **kw):
message = {
'id': '1234',
'event_id': 'VOLUME_000002',
'user_message': 'Fake Message',
'created_at': '2012-08-27T00:00:00.000000',
'guaranteed_until': "2013-11-12T21:00:00.000000",
}
return 200, {}, {'message': message}
def get_messages_12345(self, **kw):
message = {
'id': '12345',
'event_id': 'VOLUME_000002',
'user_message': 'Fake Message',
'created_at': '2012-08-27T00:00:00.000000',
'guaranteed_until': "2013-11-12T21:00:00.000000",
}
return 200, {}, {'message': message}
def put_os_services_set_log(self, body):
return (202, {}, {})
def put_os_services_get_log(self, body):
levels = [{'binary': 'cinder-api', 'host': 'host1',
'levels': {'prefix1': 'DEBUG', 'prefix2': 'INFO'}},
{'binary': 'cinder-volume', 'host': 'host@backend#pool',
'levels': {'prefix3': 'WARNING', 'prefix4': 'ERROR'}}]
return (200, {}, {'log_levels': levels})
#
# resource filters
#
def get_resource_filters(self, **kw):
return 200, {}, {'resource_filters': []}
def fake_request_get():
versions = {'versions': [{'id': 'v1.0',
'links': [{'href': 'http://docs.openstack.org/',
'rel': 'describedby',
'type': 'text/html'},
{'href': 'http://192.168.122.197/v1/',
'rel': 'self'}],
'media-types': [{'base': 'application/json',
'type': 'application/'}],
'min_version': '',
'status': 'DEPRECATED',
'updated': '2016-05-02T20:25:19Z',
'version': ''},
{'id': 'v2.0',
'links': [{'href': 'http://docs.openstack.org/',
'rel': 'describedby',
'type': 'text/html'},
{'href': 'http://192.168.122.197/v2/',
'rel': 'self'}],
'media-types': [{'base': 'application/json',
'type': 'application/'}],
'min_version': '',
'status': 'SUPPORTED',
'updated': '2014-06-28T12:20:21Z',
'version': ''},
{'id': 'v3.0',
'links': [{'href': 'http://docs.openstack.org/',
'rel': 'describedby',
'type': 'text/html'},
{'href': 'http://192.168.122.197/v3/',
'rel': 'self'}],
'media-types': [{'base': 'application/json',
'type': 'application/'}],
'min_version': '3.0',
'status': 'CURRENT',
'updated': '2016-02-08T12:20:21Z',
'version': '3.16'}]}
return versions

View File

@ -1,137 +0,0 @@
# Copyright (c) 2016 Red Hat Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient import api_versions
from cinderclient import exceptions as exc
from cinderclient.tests.unit import utils
from cinderclient.tests.unit.v3 import fakes
import ddt
cs = fakes.FakeClient(api_version=api_versions.APIVersion('3.7'))
@ddt.ddt
class ClusterTest(utils.TestCase):
def _check_fields_present(self, clusters, detailed=False):
expected_keys = {'name', 'binary', 'state', 'status'}
if detailed:
expected_keys.update(('num_hosts', 'num_down_hosts',
'last_heartbeat', 'disabled_reason',
'created_at', 'updated_at'))
for cluster in clusters:
self.assertEqual(expected_keys, set(cluster.to_dict()))
def _assert_call(self, base_url, detailed, params=None, method='GET',
body=None):
url = base_url
if detailed:
url += '/detail'
if params:
url += '?' + params
if body:
cs.assert_called(method, url, body)
else:
cs.assert_called(method, url)
@ddt.data(True, False)
def test_clusters_list(self, detailed):
lst = cs.clusters.list(detailed=detailed)
self._assert_call('/clusters', detailed)
self.assertEqual(3, len(lst))
self._assert_request_id(lst)
self._check_fields_present(lst, detailed)
@ddt.data(True, False)
def test_clusters_list_pre_version(self, detailed):
pre_cs = fakes.FakeClient(api_version=
api_versions.APIVersion('3.6'))
self.assertRaises(exc.VersionNotFoundForAPIMethod,
pre_cs.clusters.list, detailed=detailed)
@ddt.data(True, False)
def test_cluster_list_name(self, detailed):
lst = cs.clusters.list(name='cluster1@lvmdriver-1',
detailed=detailed)
self._assert_call('/clusters', detailed,
'name=cluster1@lvmdriver-1')
self.assertEqual(1, len(lst))
self._assert_request_id(lst)
self._check_fields_present(lst, detailed)
@ddt.data(True, False)
def test_clusters_list_binary(self, detailed):
lst = cs.clusters.list(binary='cinder-volume', detailed=detailed)
self._assert_call('/clusters', detailed, 'binary=cinder-volume')
self.assertEqual(2, len(lst))
self._assert_request_id(lst)
self._check_fields_present(lst, detailed)
@ddt.data(True, False)
def test_clusters_list_is_up(self, detailed):
lst = cs.clusters.list(is_up=True, detailed=detailed)
self._assert_call('/clusters', detailed, 'is_up=True')
self.assertEqual(2, len(lst))
self._assert_request_id(lst)
self._check_fields_present(lst, detailed)
@ddt.data(True, False)
def test_clusters_list_disabled(self, detailed):
lst = cs.clusters.list(disabled=True, detailed=detailed)
self._assert_call('/clusters', detailed, 'disabled=True')
self.assertEqual(1, len(lst))
self._assert_request_id(lst)
self._check_fields_present(lst, detailed)
@ddt.data(True, False)
def test_clusters_list_num_hosts(self, detailed):
lst = cs.clusters.list(num_hosts=1, detailed=detailed)
self._assert_call('/clusters', detailed, 'num_hosts=1')
self.assertEqual(1, len(lst))
self._assert_request_id(lst)
self._check_fields_present(lst, detailed)
@ddt.data(True, False)
def test_clusters_list_num_down_hosts(self, detailed):
lst = cs.clusters.list(num_down_hosts=2, detailed=detailed)
self._assert_call('/clusters', detailed, 'num_down_hosts=2')
self.assertEqual(2, len(lst))
self._assert_request_id(lst)
self._check_fields_present(lst, detailed)
def test_cluster_show(self):
result = cs.clusters.show('1')
self._assert_call('/clusters/1', False)
self._assert_request_id(result)
self._check_fields_present([result], True)
def test_cluster_enable(self):
body = {'binary': 'cinder-volume', 'name': 'cluster@lvmdriver-1'}
result = cs.clusters.update(body['name'], body['binary'], False,
disabled_reason='is ignored')
self._assert_call('/clusters/enable', False, method='PUT', body=body)
self._assert_request_id(result)
self._check_fields_present([result], False)
def test_cluster_disable(self):
body = {'binary': 'cinder-volume', 'name': 'cluster@lvmdriver-1',
'disabled_reason': 'is passed'}
result = cs.clusters.update(body['name'], body['binary'], True,
body['disabled_reason'])
self._assert_call('/clusters/disable', False, method='PUT', body=body)
self._assert_request_id(result)
self._check_fields_present([result], False)

View File

@ -1,102 +0,0 @@
# Copyright (C) 2016 EMC Corporation.
#
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import ddt
from cinderclient.tests.unit import utils
from cinderclient.tests.unit.v3 import fakes
cs = fakes.FakeClient()
@ddt.ddt
class GroupSnapshotsTest(utils.TestCase):
def test_delete_group_snapshot(self):
s1 = cs.group_snapshots.list()[0]
snap = s1.delete()
self._assert_request_id(snap)
cs.assert_called('DELETE', '/group_snapshots/1234')
snap = cs.group_snapshots.delete('1234')
cs.assert_called('DELETE', '/group_snapshots/1234')
self._assert_request_id(snap)
snap = cs.group_snapshots.delete(s1)
cs.assert_called('DELETE', '/group_snapshots/1234')
self._assert_request_id(snap)
def test_create_group_snapshot(self):
snap = cs.group_snapshots.create('group_snap')
cs.assert_called('POST', '/group_snapshots')
self._assert_request_id(snap)
def test_create_group_snapshot_with_group_id(self):
snap = cs.group_snapshots.create('1234')
expected = {'group_snapshot': {'status': 'creating',
'description': None,
'user_id': None,
'name': None,
'group_id': '1234',
'project_id': None}}
cs.assert_called('POST', '/group_snapshots', body=expected)
self._assert_request_id(snap)
def test_update_group_snapshot(self):
s1 = cs.group_snapshots.list()[0]
expected = {'group_snapshot': {'name': 'grp_snap2'}}
snap = s1.update(name='grp_snap2')
cs.assert_called('PUT', '/group_snapshots/1234', body=expected)
self._assert_request_id(snap)
snap = cs.group_snapshots.update('1234', name='grp_snap2')
cs.assert_called('PUT', '/group_snapshots/1234', body=expected)
self._assert_request_id(snap)
snap = cs.group_snapshots.update(s1, name='grp_snap2')
cs.assert_called('PUT', '/group_snapshots/1234', body=expected)
self._assert_request_id(snap)
def test_update_group_snapshot_no_props(self):
ret = cs.group_snapshots.update('1234')
self.assertIsNone(ret)
def test_list_group_snapshot(self):
lst = cs.group_snapshots.list()
cs.assert_called('GET', '/group_snapshots/detail')
self._assert_request_id(lst)
@ddt.data(
{'detailed': True, 'url': '/group_snapshots/detail'},
{'detailed': False, 'url': '/group_snapshots'}
)
@ddt.unpack
def test_list_group_snapshot_detailed(self, detailed, url):
lst = cs.group_snapshots.list(detailed=detailed)
cs.assert_called('GET', url)
self._assert_request_id(lst)
@ddt.data(
{'foo': 'bar'},
{'foo': 'bar', '123': None}
)
def test_list_group_snapshot_with_search_opts(self, opts):
lst = cs.group_snapshots.list(search_opts=opts)
cs.assert_called('GET', '/group_snapshots/detail?foo=bar')
self._assert_request_id(lst)
def test_get_group_snapshot(self):
group_snapshot_id = '1234'
snap = cs.group_snapshots.get(group_snapshot_id)
cs.assert_called('GET', '/group_snapshots/%s' % group_snapshot_id)
self._assert_request_id(snap)

View File

@ -1,111 +0,0 @@
# Copyright (c) 2016 EMC Corporation
#
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient import api_versions
from cinderclient import exceptions as exc
from cinderclient.v3 import group_types
from cinderclient.tests.unit import utils
from cinderclient.tests.unit.v3 import fakes
cs = fakes.FakeClient(api_version=api_versions.APIVersion('3.11'))
pre_cs = fakes.FakeClient(api_version=api_versions.APIVersion('3.10'))
class GroupTypesTest(utils.TestCase):
def test_list_group_types(self):
tl = cs.group_types.list()
cs.assert_called('GET', '/group_types?is_public=None')
self._assert_request_id(tl)
for t in tl:
self.assertIsInstance(t, group_types.GroupType)
def test_list_group_types_pre_version(self):
self.assertRaises(exc.VersionNotFoundForAPIMethod,
pre_cs.group_types.list)
def test_list_group_types_not_public(self):
t1 = cs.group_types.list(is_public=None)
cs.assert_called('GET', '/group_types?is_public=None')
self._assert_request_id(t1)
def test_create(self):
t = cs.group_types.create('test-type-3', 'test-type-3-desc')
cs.assert_called('POST', '/group_types',
{'group_type': {
'name': 'test-type-3',
'description': 'test-type-3-desc',
'is_public': True
}})
self.assertIsInstance(t, group_types.GroupType)
self._assert_request_id(t)
def test_create_non_public(self):
t = cs.group_types.create('test-type-3', 'test-type-3-desc', False)
cs.assert_called('POST', '/group_types',
{'group_type': {
'name': 'test-type-3',
'description': 'test-type-3-desc',
'is_public': False
}})
self.assertIsInstance(t, group_types.GroupType)
self._assert_request_id(t)
def test_update(self):
t = cs.group_types.update('1', 'test_type_1', 'test_desc_1', False)
cs.assert_called('PUT',
'/group_types/1',
{'group_type': {'name': 'test_type_1',
'description': 'test_desc_1',
'is_public': False}})
self.assertIsInstance(t, group_types.GroupType)
self._assert_request_id(t)
def test_get(self):
t = cs.group_types.get('1')
cs.assert_called('GET', '/group_types/1')
self.assertIsInstance(t, group_types.GroupType)
self._assert_request_id(t)
def test_default(self):
t = cs.group_types.default()
cs.assert_called('GET', '/group_types/default')
self.assertIsInstance(t, group_types.GroupType)
self._assert_request_id(t)
def test_set_key(self):
t = cs.group_types.get(1)
res = t.set_keys({'k': 'v'})
cs.assert_called('POST',
'/group_types/1/group_specs',
{'group_specs': {'k': 'v'}})
self._assert_request_id(res)
def test_set_key_pre_version(self):
t = group_types.GroupType(pre_cs, {'id': 1})
self.assertRaises(exc.VersionNotFoundForAPIMethod,
t.set_keys, {'k': 'v'})
def test_unset_keys(self):
t = cs.group_types.get(1)
res = t.unset_keys(['k'])
cs.assert_called('DELETE', '/group_types/1/group_specs/k')
self._assert_request_id(res)
def test_delete(self):
t = cs.group_types.delete(1)
cs.assert_called('DELETE', '/group_types/1')
self._assert_request_id(t)

View File

@ -1,214 +0,0 @@
# Copyright (C) 2016 EMC Corporation.
#
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import ddt
from cinderclient.tests.unit import utils
from cinderclient.tests.unit.v3 import fakes
cs = fakes.FakeClient()
@ddt.ddt
class GroupsTest(utils.TestCase):
def test_delete_group(self):
expected = {'delete': {'delete-volumes': True}}
v = cs.groups.list()[0]
grp = v.delete(delete_volumes=True)
self._assert_request_id(grp)
cs.assert_called('POST', '/groups/1234/action', body=expected)
grp = cs.groups.delete('1234', delete_volumes=True)
self._assert_request_id(grp)
cs.assert_called('POST', '/groups/1234/action', body=expected)
grp = cs.groups.delete(v, delete_volumes=True)
self._assert_request_id(grp)
cs.assert_called('POST', '/groups/1234/action', body=expected)
def test_create_group(self):
grp = cs.groups.create('my_group_type', 'type1,type2', name='group')
cs.assert_called('POST', '/groups')
self._assert_request_id(grp)
def test_create_group_with_volume_types(self):
grp = cs.groups.create('my_group_type', 'type1,type2', name='group')
expected = {'group': {'status': 'creating',
'description': None,
'availability_zone': None,
'user_id': None,
'name': 'group',
'group_type': 'my_group_type',
'volume_types': ['type1', 'type2'],
'project_id': None}}
cs.assert_called('POST', '/groups', body=expected)
self._assert_request_id(grp)
@ddt.data(
{'name': 'group2', 'desc': None, 'add': None, 'remove': None},
{'name': None, 'desc': 'group2 desc', 'add': None, 'remove': None},
{'name': None, 'desc': None, 'add': 'uuid1,uuid2', 'remove': None},
{'name': None, 'desc': None, 'add': None, 'remove': 'uuid3,uuid4'},
)
@ddt.unpack
def test_update_group_name(self, name, desc, add, remove):
v = cs.groups.list()[0]
expected = {'group': {'name': name, 'description': desc,
'add_volumes': add, 'remove_volumes': remove}}
grp = v.update(name=name, description=desc,
add_volumes=add, remove_volumes=remove)
cs.assert_called('PUT', '/groups/1234', body=expected)
self._assert_request_id(grp)
grp = cs.groups.update('1234', name=name, description=desc,
add_volumes=add, remove_volumes=remove)
cs.assert_called('PUT', '/groups/1234', body=expected)
self._assert_request_id(grp)
grp = cs.groups.update(v, name=name, description=desc,
add_volumes=add, remove_volumes=remove)
cs.assert_called('PUT', '/groups/1234', body=expected)
self._assert_request_id(grp)
def test_update_group_none(self):
self.assertIsNone(cs.groups.update('1234'))
def test_update_group_no_props(self):
cs.groups.update('1234')
def test_list_group(self):
lst = cs.groups.list()
cs.assert_called('GET', '/groups/detail')
self._assert_request_id(lst)
def test_list_group_detailed_false(self):
lst = cs.groups.list(detailed=False)
cs.assert_called('GET', '/groups')
self._assert_request_id(lst)
def test_list_group_with_search_opts(self):
lst = cs.groups.list(search_opts={'foo': 'bar'})
cs.assert_called('GET', '/groups/detail?foo=bar')
self._assert_request_id(lst)
def test_list_group_with_volume(self):
lst = cs.groups.list(list_volume=True)
cs.assert_called('GET', '/groups/detail?list_volume=True')
self._assert_request_id(lst)
def test_list_group_with_empty_search_opt(self):
lst = cs.groups.list(
search_opts={'foo': 'bar', 'abc': None}
)
cs.assert_called('GET', '/groups/detail?foo=bar')
self._assert_request_id(lst)
def test_get_group(self):
group_id = '1234'
grp = cs.groups.get(group_id)
cs.assert_called('GET', '/groups/%s' % group_id)
self._assert_request_id(grp)
def test_get_group_with_list_volume(self):
group_id = '1234'
grp = cs.groups.get(group_id, list_volume=True)
cs.assert_called('GET', '/groups/%s?list_volume=True' % group_id)
self._assert_request_id(grp)
def test_create_group_from_src_snap(self):
grp = cs.groups.create_from_src('5678', None, name='group')
expected = {
'create-from-src': {
'status': 'creating',
'description': None,
'user_id': None,
'name': 'group',
'group_snapshot_id': '5678',
'project_id': None,
'source_group_id': None
}
}
cs.assert_called('POST', '/groups/action',
body=expected)
self._assert_request_id(grp)
def test_create_group_from_src_group_(self):
grp = cs.groups.create_from_src(None, '5678', name='group')
expected = {
'create-from-src': {
'status': 'creating',
'description': None,
'user_id': None,
'name': 'group',
'source_group_id': '5678',
'project_id': None,
'group_snapshot_id': None
}
}
cs.assert_called('POST', '/groups/action',
body=expected)
self._assert_request_id(grp)
def test_enable_replication_group(self):
expected = {'enable_replication': {}}
g0 = cs.groups.list()[0]
grp = g0.enable_replication()
self._assert_request_id(grp)
cs.assert_called('POST', '/groups/1234/action', body=expected)
grp = cs.groups.enable_replication('1234')
self._assert_request_id(grp)
cs.assert_called('POST', '/groups/1234/action', body=expected)
grp = cs.groups.enable_replication(g0)
self._assert_request_id(grp)
cs.assert_called('POST', '/groups/1234/action', body=expected)
def test_disable_replication_group(self):
expected = {'disable_replication': {}}
g0 = cs.groups.list()[0]
grp = g0.disable_replication()
self._assert_request_id(grp)
cs.assert_called('POST', '/groups/1234/action', body=expected)
grp = cs.groups.disable_replication('1234')
self._assert_request_id(grp)
cs.assert_called('POST', '/groups/1234/action', body=expected)
grp = cs.groups.disable_replication(g0)
self._assert_request_id(grp)
cs.assert_called('POST', '/groups/1234/action', body=expected)
def test_failover_replication_group(self):
expected = {'failover_replication':
{'allow_attached_volume': False,
'secondary_backend_id': None}}
g0 = cs.groups.list()[0]
grp = g0.failover_replication()
self._assert_request_id(grp)
cs.assert_called('POST', '/groups/1234/action', body=expected)
grp = cs.groups.failover_replication('1234')
self._assert_request_id(grp)
cs.assert_called('POST', '/groups/1234/action', body=expected)
grp = cs.groups.failover_replication(g0)
self._assert_request_id(grp)
cs.assert_called('POST', '/groups/1234/action', body=expected)
def test_list_replication_targets(self):
expected = {'list_replication_targets': {}}
g0 = cs.groups.list()[0]
grp = g0.list_replication_targets()
self._assert_request_id(grp)
cs.assert_called('POST', '/groups/1234/action', body=expected)
grp = cs.groups.list_replication_targets('1234')
self._assert_request_id(grp)
cs.assert_called('POST', '/groups/1234/action', body=expected)
grp = cs.groups.list_replication_targets(g0)
self._assert_request_id(grp)
cs.assert_called('POST', '/groups/1234/action', body=expected)

Some files were not shown because too many files have changed in this diff Show More