python-cinderclient-1.0.8/0000775000175300017540000000000012274343764016653 5ustar jenkinsjenkins00000000000000python-cinderclient-1.0.8/ChangeLog0000664000175300017540000002176412274343764020437 0ustar jenkinsjenkins00000000000000CHANGES ======= 1.0.8 ----- * Revert "Update cinderclient to skip the additional GET during create" * Update release notes for push to pypi * Fixed image_name from image-name in upload-to-image * Add shell tests for snapshot_delete * Sync up with oslo-incubator * Add retype to index.rst * Add volume retype command * Remove RAX-specific auth in cinderclient * disable/enable a service has no output * Fix 'search_opts' unexpected keyword argument error * Remove dependencies on pep8, pyflakes and flake8 * Remove copyright from empty files * Unit tests for limits.py in cinderclient/v1 and v2 * Update cinderclient to skip the additional GET during create * Fix RateLimit.__repr__ - self.method is undefined * Fix inappropriate comment for volume update api * Updates .gitignore for environment files * Updates .gitignore * Discrepancy between README.rst and cinder help * Updated from global requirements * Fix broken argument name in v2 volume extend routine * Updates tox.ini to use new features * Reset-state and snapshot-reset-state for multiple objects * Ignore swap files generated during file edting by vim * Add search_opts into the method list() for VolumeTypeManager * Add assert to delete multiple test * Fix typo in cinderclient * Fix string representation of VolumeBackupsRestore * Fix inappropriate comment for set_metadata * Update HACKING.rst with release note requirement * Add encryption-type-delete to cinderclient * Updated from global requirements * change assertEquals to assertEqual * Add index.rst section for patches merged to master * Update link in HACKING.rst and Make it DRYer 1.0.7 ----- * Update version and index.rst for push * Fix py33 due to readonly and metadata patches * Enable del of other tenants resources by name * Adding volume readonly-mode-update interface to Cinder client * Enable "cinder delete" can delete multiple volumes in one request * Adding Read-Only volume attaching support to Cinder client * Deprecates --volume-id arg for v2 backup-restore * Override endpoint URL check for API version * Fixes broken v1 and v2 api backup-restore * Addition of volume/snapshot_metadata CLI * python3: iteration order of dict is unpredictable * python3: align the order of parameters for urlencode() * python3: sort dict for post_volumes_1234_action test * python3: Refactor dict for python2/python3 compat * Updated from global requirements * Fix DeprecationWarning when printing exception * Fix the failure of fetching the version in cinder endpoint 1.0.6 ----- * Update docs/index.rst with release info for 1.0.6 * Add quota-usage command * Synch up with OSLO-Incubator * Implement qos support * Fix find volume for migrate command * Replace OpenStack LLC with OpenStack Foundation * Error if arguments are not supplied for rename commands * Use v2 endpoint with v2 shell for migration * Add volume name arguments * Implement ability to migrate volume * Fix help messages for name arguments * Added support for running the tests under PyPy with tox * Don't need to init testr explicitly * Add update_snapshot_metadata action * Add volume encryption metadata to cinderclient * Sync strutils from oslo * Fixing erroneous clearing of test callstack * Fixing malformed assert message formatting * Add commandline option --metadata for cinder list * Add print for "backup-create" command * Add missing babel dependency * Add support for multiple cinder endpoints * Updated from global requirements * python3: Fix tox requirements 1.0.5 ----- * Add a couple more things to index before release * Sync with global requirements * convert third-party exception to ConnectionError * Provide cinder CLI man page * Revert "Add evaluation of --force parameter when creating snapshots" * Add timeout parameter in requests * Remove locals() from cinder client code base * Add evaluation of --force parameter when creating snapshots * Updating HACKING file * Add availability-zone-list command * Revert "Use exceptions from oslo" * Changes for volume type quotas * Update to latest openstack/requirements * Add print to the upload-to-image command * Add os-services extension support * Update index.rst * Fix wrong method call for extend subcommand * Implement ability to extend volume for v1 * Sync install_venv_common from oslo * Enable ability to reset state on snapshots * Implement ability to extend volume * Use exceptions from oslo * Fix volume info display error on create with v2 * Implement reset-state (os-reset_status) action * Connectivity between the endpoint version and OS_VOLUME_API_VERSION * python3: Strutils is not needed * python3: Fix traceback while running tests * Implements support migration for volume transfer * python3: Fix traceback while running tests * python3: Fix import compatibility * python3: Fix unicode strings * python3: Update for metaclasses * python3: fix imports compatibility * python3: Basic python3 compatibility * python3: compatibility for iteritems and iterkeys * Remove explicit depend on distribute * python3: Drop mox dependency * Start Gating on Pyflakes and Hacking * Add `snapshots` key support for quota class update * python3: Introduce py33 to tox.ini * Update run_tests and bring back colorizer * Set the correct location for the tests * Only add logging handlers if there currently aren't any * Move tests into cinderclient package * Rename requires files to standard names * Migrate to pbr * Implement scheduler hints for APIv2 * Make ManagerWithFind abstract and fix its descendants * Migrate to flake8 * Add .coveragerc file to show correct code coverage * Allow generator as input to utils.print_list * Fixed do_create() in v2 shell * Add license information 1.0.4 ----- * Update release info in index.rst * Update setup.py prior to next upload to pypi * Add support for volume backups * Fixed unit test name in v1 and v2 tests * Don't print the empty table on list operations * Sync with oslo-incubator copy of setup.py and version.py * Minor typo/message fixes * Remove unused "import sys" * Fix result -> resp typo in unset_keys * Fix X-Auth_Token -> X-Auth-Token header name * Pin prettytable versions 1.0.3 ----- * Decodes input and encodes output * Add OS_TENANT_ID as authentication option * Remove unused tools/rfc.sh * Add debug option processing to run_tests * Add support for snapshot quotas * Catch KeyboardInterrupt * Docs in cinderlcient were never actually updated * Debug output the http body * Fix typo breaking --debug option to cinder client * Fix upload-to-image volume_id help * Handle metadata args the same for all calls * adding v2 support to cinderclient * Allow requests 0.8 and greater * Re-add setuptools-git to setup.py * Avoid UnicodeEncodeError exception on exception * Correct parsing of volume metadata * Update to latest oslo version code * Fixed documentation of the cinder shell command * Change Nova -> Cinder in credentials error message * Add ability to call force_delete from cinderclient * Move from nose to testr * Fixed Version Functionality * Add upload-to-image function to client * Add access to update volume metadata * Move from unittest2 to testtools 1.0.2 ----- * Add list-extensions capability to cinderclient * Use requests module for HTTP/HTTPS * Port some additional logging changes from novaclient * Bring back the output from client.http_log() * Add clone volume support to cinderclient * Update to swapped versioninfo logic * Align cinderclient version code 1.0.1 ----- * Adding bootable column to volume list view * Pin pep8 to 1.3.3 * show help when calling without arguments * Add retries to cinderclient * Fixes setup compatibility issue on Windows * Revert "Add retries to cinderclient." * Add retries to cinderclient * Remove extra-specs from types-list command output * Remove attach/detach code from cinderclient * Add python_cinderclient.egg-info to .gitignore * Fix support for Unicode volume names * Add OpenStack trove classifier for PyPI * Add volume_type extra_specs support to client * add rename and snapshot-rename commands 1.0.0 ----- * Show volume and snapshot data on create * Fix some pep8 issues * Remove unused methods in FakeHTTPClient and unused unittests * Patch for bug #1004382 * add tenant_id and make projectid optional * Add begin_detaching and roll_detaching functions * Add filter options to list and snapshot-list * Fixes bug 1045777 * Implement volume quota support in the cinderclient * Fix PEP8 issues * Change '_' to '-' in options * Correct param comments in docstring * Add the test environment for the virtualenv * Add nosehtmloutput as a test dependency * Add ability to provide metadata on volume creation * Add image id arg to create 0.2 --- * Add availability_zone support for volume creation * Rename bash completion file from nova to cinder * Add all_tenants flag to snapshots and volumes * Add missing parameters to volume create body * Bump pep8 to 1.2 * Add post-tag versioning * Add support for extended_snapshot_attributes * Add client work for new cinder extensions * Move docs to doc * Set pep8 version to 1.1 in test_requires * Auto generate AUTHORS file for python-cinderclient component * Align setup.py and tox with standards 0.0 --- * Initial split from python-novaclient python-cinderclient-1.0.8/HACKING.rst0000664000175300017540000000341112274343712020441 0ustar jenkinsjenkins00000000000000Cinder 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 Text encoding ------------- - All text within python code should be of type 'unicode'. WRONG: >>> s = 'foo' >>> s 'foo' >>> type(s) RIGHT: >>> u = u'foo' >>> u u'foo' >>> type(u) - Transitions between internal unicode and external strings should always be immediately and explicitly encoded or decoded. - All external text that is not explicitly encoded (database storage, commandline arguments, etc.) should be presumed to be encoded as utf-8. WRONG: mystring = infile.readline() myreturnstring = do_some_magic_with(mystring) outfile.write(myreturnstring) RIGHT: mystring = infile.readline() mytext = s.decode('utf-8') returntext = do_some_magic_with(mytext) returnstring = returntext.encode('utf-8') outfile.write(returnstring) Release Notes ------------- - Each patch should add an entry in the doc/source/index.rst file under "MASTER". - On each new release, the entries under "MASTER" will become the release notes for that release, and "MASTER" will be cleared. - The format should match existing release notes. For example, a feature:: * Add support for function foo Or a bug fix:: .. _1241941: http://bugs.launchpad.net/python-cinderclient/+bug/1241941 python-cinderclient-1.0.8/.testr.conf0000664000175300017540000000032212274343712020727 0ustar jenkinsjenkins00000000000000[DEFAULT] test_command=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 ${PYTHON:-python} -m subunit.run discover -t ./ ./cinderclient/tests $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--list python-cinderclient-1.0.8/test-requirements.txt0000664000175300017540000000030712274343712023105 0ustar jenkinsjenkins00000000000000# Hacking already pins down pep8, pyflakes and flake8 hacking>=0.8.0,<0.9 coverage>=3.6 discover fixtures>=0.3.14 mock>=1.0 python-subunit sphinx>=1.1.2,<1.2 testtools>=0.9.32 testrepository>=0.0.17 python-cinderclient-1.0.8/setup.py0000664000175300017540000000141512274343712020357 0ustar jenkinsjenkins00000000000000#!/usr/bin/env python # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT import setuptools setuptools.setup( setup_requires=['pbr'], pbr=True) python-cinderclient-1.0.8/run_tests.sh0000775000175300017540000001545212274343712021240 0ustar jenkinsjenkins00000000000000#!/bin/bash set -eu function usage { echo "Usage: $0 [OPTION]..." echo "Run cinderclient's test suite(s)" echo "" echo " -V, --virtual-env Always use virtualenv. Install automatically if not present" echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment" echo " -s, --no-site-packages Isolate the virtualenv from the global Python environment" echo " -r, --recreate-db Recreate the test database (deprecated, as this is now the default)." echo " -n, --no-recreate-db Don't recreate the test database." echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added." echo " -u, --update Update the virtual environment with any newer package versions" echo " -p, --pep8 Just run PEP8 and HACKING compliance check" echo " -P, --no-pep8 Don't run static code checks" echo " -c, --coverage Generate coverage report" echo " -d, --debug Run tests with testtools instead of testr. This allows you to use the debugger." echo " -h, --help Print this usage message" echo " --hide-elapsed Don't print the elapsed time for each test along with slow test list" echo " --virtual-env-path Location of the virtualenv directory" echo " Default: \$(pwd)" echo " --virtual-env-name Name of the virtualenv directory" echo " Default: .venv" echo " --tools-path Location of the tools directory" echo " Default: \$(pwd)" echo "" echo "Note: with no options specified, the script will try to run the tests in a virtual environment," echo " If no virtualenv is found, the script will ask if you would like to create one. If you " echo " prefer to run tests NOT in a virtual environment, simply pass the -N option." exit } function process_options { i=1 while [ $i -le $# ]; do case "${!i}" in -h|--help) usage;; -V|--virtual-env) always_venv=1; never_venv=0;; -N|--no-virtual-env) always_venv=0; never_venv=1;; -s|--no-site-packages) no_site_packages=1;; -r|--recreate-db) recreate_db=1;; -n|--no-recreate-db) recreate_db=0;; -f|--force) force=1;; -u|--update) update=1;; -p|--pep8) just_pep8=1;; -P|--no-pep8) no_pep8=1;; -c|--coverage) coverage=1;; -d|--debug) debug=1;; --virtual-env-path) (( i++ )) venv_path=${!i} ;; --virtual-env-name) (( i++ )) venv_dir=${!i} ;; --tools-path) (( i++ )) tools_path=${!i} ;; -*) testropts="$testropts ${!i}";; *) testrargs="$testrargs ${!i}" esac (( i++ )) done } tool_path=${tools_path:-$(pwd)} venv_path=${venv_path:-$(pwd)} venv_dir=${venv_name:-.venv} with_venv=tools/with_venv.sh always_venv=0 never_venv=0 force=0 no_site_packages=0 installvenvopts= testrargs= testropts= wrapper="" just_pep8=0 no_pep8=0 coverage=0 debug=0 recreate_db=1 update=0 LANG=en_US.UTF-8 LANGUAGE=en_US:en LC_ALL=C process_options $@ # Make our paths available to other scripts we call export venv_path export venv_dir export venv_name export tools_dir export venv=${venv_path}/${venv_dir} if [ $no_site_packages -eq 1 ]; then installvenvopts="--no-site-packages" fi function run_tests { # Cleanup *pyc ${wrapper} find . -type f -name "*.pyc" -delete if [ $debug -eq 1 ]; then if [ "$testropts" = "" ] && [ "$testrargs" = "" ]; then # Default to running all tests if specific test is not # provided. testrargs="discover ./cinderclient/tests" fi ${wrapper} python -m testtools.run $testropts $testrargs # Short circuit because all of the testr and coverage stuff # below does not make sense when running testtools.run for # debugging purposes. return $? fi if [ $coverage -eq 1 ]; then TESTRTESTS="$TESTRTESTS --coverage" else TESTRTESTS="$TESTRTESTS" fi # Just run the test suites in current environment set +e testrargs=`echo "$testrargs" | sed -e's/^\s*\(.*\)\s*$/\1/'` TESTRTESTS="$TESTRTESTS --testr-args='--subunit $testropts $testrargs'" if [ setup.cfg -nt cinderclient.egg-info/entry_points.txt ] then ${wrapper} python setup.py egg_info fi echo "Running \`${wrapper} $TESTRTESTS\`" if ${wrapper} which subunit-2to1 2>&1 > /dev/null then # subunit-2to1 is present, testr subunit stream should be in version 2 # format. Convert to version one before colorizing. bash -c "${wrapper} $TESTRTESTS | ${wrapper} subunit-2to1 | ${wrapper} tools/colorizer.py" else bash -c "${wrapper} $TESTRTESTS | ${wrapper} tools/colorizer.py" fi RESULT=$? set -e copy_subunit_log if [ $coverage -eq 1 ]; then echo "Generating coverage report in covhtml/" # Don't compute coverage for common code, which is tested elsewhere ${wrapper} coverage combine ${wrapper} coverage html --include='cinderclient/*' --omit='cinderclient/openstack/common/*' -d covhtml -i fi return $RESULT } function copy_subunit_log { LOGNAME=`cat .testrepository/next-stream` LOGNAME=$(($LOGNAME - 1)) LOGNAME=".testrepository/${LOGNAME}" cp $LOGNAME subunit.log } function run_pep8 { echo "Running flake8 ..." bash -c "${wrapper} flake8" } TESTRTESTS="python setup.py testr" if [ $never_venv -eq 0 ] then # Remove the virtual environment if --force used if [ $force -eq 1 ]; then echo "Cleaning virtualenv..." rm -rf ${venv} fi if [ $update -eq 1 ]; then echo "Updating virtualenv..." python tools/install_venv.py $installvenvopts fi if [ -e ${venv} ]; then wrapper="${with_venv}" else if [ $always_venv -eq 1 ]; then # Automatically install the virtualenv python tools/install_venv.py $installvenvopts wrapper="${with_venv}" else echo -e "No virtual environment found...create one? (Y/n) \c" read use_ve if [ "x$use_ve" = "xY" -o "x$use_ve" = "x" -o "x$use_ve" = "xy" ]; then # Install the virtualenv and run the test suite in it python tools/install_venv.py $installvenvopts wrapper=${with_venv} fi fi fi fi # Delete old coverage data from previous runs if [ $coverage -eq 1 ]; then ${wrapper} coverage erase fi if [ $just_pep8 -eq 1 ]; then run_pep8 exit fi if [ $recreate_db -eq 1 ]; then rm -f tests.sqlite fi run_tests # NOTE(sirp): we only want to run pep8 when we're running the full-test suite, # not when we're running tests individually. To handle this, we need to # distinguish between options (testropts), which begin with a '-', and # arguments (testrargs). if [ -z "$testrargs" ]; then if [ $no_pep8 -eq 0 ]; then run_pep8 fi fi python-cinderclient-1.0.8/README.rst0000664000175300017540000001522412274343712020337 0ustar jenkinsjenkins00000000000000Python bindings to the OpenStack Cinder API =========================================== 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 guide`_ for information on how to use the ``cinder`` command-line tool. You may also want to look at the `OpenStack API documentation`_. .. _OpenStack CLI Guide: http://docs.openstack.org/cli/quick-start/content/ .. _OpenStack API documentation: http://docs.openstack.org/api/ The project is hosted on `Launchpad`_, where bugs can be filed. The code is hosted on `Github`_. Patches must be submitted using `Gerrit`_, *not* Github pull requests. .. _Github: https://github.com/openstack/python-cinderclient .. _Launchpad: https://launchpad.net/python-cinderclient .. _Gerrit: http://wiki.openstack.org/GerritWorkflow This code 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. __ http://github.com/jacobian/python-cloudservers .. 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:: export OS_AUTH_URL=http://example.com:8774/v1.1/ export OS_VOLUME_API_VERSION=1 If you are using Keystone, you need to set the OS_AUTH_URL to the keystone endpoint:: export OS_AUTH_URL=http://example.com:5000/v2.0/ 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 [--debug] [--os-username ] [--os-password ] [--os-tenant-name ] [--os-auth-url ] [--os-region-name ] [--service-type ] [--service-name ] [--volume-service-name ] [--endpoint-type ] [--os-volume-api-version ] [--os-cacert ] [--retries ] ... Command-line interface to the OpenStack Cinder API. Positional arguments: absolute-limits Print a list of absolute limits for a user create Add a new volume. credentials Show user credentials returned from auth delete Remove a volume. endpoints Discover endpoints that get returned from the authenticate services extra-specs-list Print a list of current 'volume types and extra specs' (Admin Only). list List all the volumes. quota-class-show List the quotas for a quota class. quota-class-update Update the quotas for a quota class. quota-defaults List the default quotas for a tenant. quota-show List the quotas for a tenant. quota-update Update the quotas for a tenant. rate-limits Print a list of rate limits for a user rename Rename a volume. show Show details about a volume. snapshot-create Add a new snapshot. snapshot-delete Remove a snapshot. snapshot-list List all the snapshots. snapshot-rename Rename a snapshot. snapshot-show Show details about a snapshot. type-create Create a new volume type. type-delete Delete a specific volume type type-key Set or unset extra_spec for a volume type. type-list Print a list of available 'volume types'. bash-completion Prints all of the commands and options to stdout so that the help Display help about this program or one of its subcommands. list-extensions List all the os-api extensions that are available. Optional arguments: --debug Print debugging output --os-username Defaults to env[OS_USERNAME]. --os-password Defaults to env[OS_PASSWORD]. --os-tenant-name Defaults to env[OS_TENANT_NAME]. --os-auth-url Defaults to env[OS_AUTH_URL]. --os-region-name Defaults to env[OS_REGION_NAME]. --service-type Defaults to compute for most actions --service-name Defaults to env[CINDER_SERVICE_NAME] --volume-service-name Defaults to env[CINDER_VOLUME_SERVICE_NAME] --endpoint-type Defaults to env[CINDER_ENDPOINT_TYPE] or publicURL. --os-volume-api-version Accepts 1,defaults to env[OS_VOLUME_API_VERSION]. --os-cacert Specify a CA bundle file to use in verifying a TLS (https) server certificate. Defaults to env[OS_CACERT] --retries Number of retries. Python API ---------- There's also a complete Python API, but it has not yet been documented. Quick-start using keystone:: # use v2.0 auth with http://example.com:5000/v2.0/") >>> from cinderclient.v1 import client >>> nt = client.Client(USER, PASS, TENANT, AUTH_URL, service_type="volume") >>> nt.volumes.list() [...] See release notes and more at ``_. python-cinderclient-1.0.8/requirements.txt0000664000175300017540000000014612274343712022131 0ustar jenkinsjenkins00000000000000pbr>=0.5.21,<1.0 argparse PrettyTable>=0.7,<0.8 requests>=1.1 simplejson>=2.0.9 Babel>=1.3 six>=1.4.1 python-cinderclient-1.0.8/tox.ini0000664000175300017540000000137012274343712020160 0ustar jenkinsjenkins00000000000000[tox] distribute = False envlist = py26,py27,py33,pypy,pep8 minversion = 1.6 skipsdist = True [testenv] usedevelop = True install_command = pip install -U {opts} {packages} setenv = VIRTUAL_ENV={envdir} LANG=en_US.UTF-8 LANGUAGE=en_US:en LC_ALL=C deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = python setup.py testr --testr-args='{posargs}' [testenv:pep8] commands = flake8 [testenv:venv] commands = {posargs} [testenv:cover] commands = python setup.py testr --coverage --testr-args='{posargs}' [tox:jenkins] downloadcache = ~/cache/pip [flake8] show-source = True ignore = F811,F821,H302,H306,H404 exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build,tools python-cinderclient-1.0.8/PKG-INFO0000664000175300017540000002120412274343764017747 0ustar jenkinsjenkins00000000000000Metadata-Version: 1.1 Name: python-cinderclient Version: 1.0.8 Summary: OpenStack Block Storage API Client Library Home-page: http://www.openstack.org/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description: Python bindings to the OpenStack Cinder API =========================================== 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 guide`_ for information on how to use the ``cinder`` command-line tool. You may also want to look at the `OpenStack API documentation`_. .. _OpenStack CLI Guide: http://docs.openstack.org/cli/quick-start/content/ .. _OpenStack API documentation: http://docs.openstack.org/api/ The project is hosted on `Launchpad`_, where bugs can be filed. The code is hosted on `Github`_. Patches must be submitted using `Gerrit`_, *not* Github pull requests. .. _Github: https://github.com/openstack/python-cinderclient .. _Launchpad: https://launchpad.net/python-cinderclient .. _Gerrit: http://wiki.openstack.org/GerritWorkflow This code 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. __ http://github.com/jacobian/python-cloudservers .. 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:: export OS_AUTH_URL=http://example.com:8774/v1.1/ export OS_VOLUME_API_VERSION=1 If you are using Keystone, you need to set the OS_AUTH_URL to the keystone endpoint:: export OS_AUTH_URL=http://example.com:5000/v2.0/ 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 [--debug] [--os-username ] [--os-password ] [--os-tenant-name ] [--os-auth-url ] [--os-region-name ] [--service-type ] [--service-name ] [--volume-service-name ] [--endpoint-type ] [--os-volume-api-version ] [--os-cacert ] [--retries ] ... Command-line interface to the OpenStack Cinder API. Positional arguments: absolute-limits Print a list of absolute limits for a user create Add a new volume. credentials Show user credentials returned from auth delete Remove a volume. endpoints Discover endpoints that get returned from the authenticate services extra-specs-list Print a list of current 'volume types and extra specs' (Admin Only). list List all the volumes. quota-class-show List the quotas for a quota class. quota-class-update Update the quotas for a quota class. quota-defaults List the default quotas for a tenant. quota-show List the quotas for a tenant. quota-update Update the quotas for a tenant. rate-limits Print a list of rate limits for a user rename Rename a volume. show Show details about a volume. snapshot-create Add a new snapshot. snapshot-delete Remove a snapshot. snapshot-list List all the snapshots. snapshot-rename Rename a snapshot. snapshot-show Show details about a snapshot. type-create Create a new volume type. type-delete Delete a specific volume type type-key Set or unset extra_spec for a volume type. type-list Print a list of available 'volume types'. bash-completion Prints all of the commands and options to stdout so that the help Display help about this program or one of its subcommands. list-extensions List all the os-api extensions that are available. Optional arguments: --debug Print debugging output --os-username Defaults to env[OS_USERNAME]. --os-password Defaults to env[OS_PASSWORD]. --os-tenant-name Defaults to env[OS_TENANT_NAME]. --os-auth-url Defaults to env[OS_AUTH_URL]. --os-region-name Defaults to env[OS_REGION_NAME]. --service-type Defaults to compute for most actions --service-name Defaults to env[CINDER_SERVICE_NAME] --volume-service-name Defaults to env[CINDER_VOLUME_SERVICE_NAME] --endpoint-type Defaults to env[CINDER_ENDPOINT_TYPE] or publicURL. --os-volume-api-version Accepts 1,defaults to env[OS_VOLUME_API_VERSION]. --os-cacert Specify a CA bundle file to use in verifying a TLS (https) server certificate. Defaults to env[OS_CACERT] --retries Number of retries. Python API ---------- There's also a complete Python API, but it has not yet been documented. Quick-start using keystone:: # use v2.0 auth with http://example.com:5000/v2.0/") >>> from cinderclient.v1 import client >>> nt = client.Client(USER, PASS, TENANT, AUTH_URL, service_type="volume") >>> nt.volumes.list() [...] See release notes and more at ``_. Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 2.6 python-cinderclient-1.0.8/doc/0000775000175300017540000000000012274343764017420 5ustar jenkinsjenkins00000000000000python-cinderclient-1.0.8/doc/Makefile0000664000175300017540000000616412274343712021060 0ustar jenkinsjenkins00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SPHINXSOURCE = source PAPER = BUILDDIR = build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(SPHINXSOURCE) .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/python-cinderclient.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/python-cinderclient.qhc" latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ "run these through (pdf)latex." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." python-cinderclient-1.0.8/doc/.gitignore0000664000175300017540000000000712274343712021376 0ustar jenkinsjenkins00000000000000build/ python-cinderclient-1.0.8/doc/source/0000775000175300017540000000000012274343764020720 5ustar jenkinsjenkins00000000000000python-cinderclient-1.0.8/doc/source/conf.py0000664000175300017540000001545512274343712022222 0ustar jenkinsjenkins00000000000000# -*- coding: utf-8 -*- # # python-cinderclient documentation build configuration file, created by # sphinx-quickstart on Sun Dec 6 14:19:25 2009. # # This file is execfile()d with current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import os import sys import pbr.version # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.append(os.path.abspath('.')) BASE_DIR = os.path.dirname(os.path.abspath(__file__)) ROOT = os.path.abspath(os.path.join(BASE_DIR, "..", "..")) sys.path.insert(0, ROOT) # -- General configuration ---------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8' # The master toctree document. master_doc = 'index' # General information about the project. project = 'python-cinderclient' copyright = 'OpenStack Contributors' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. version_info = pbr.version.VersionInfo('python-cinderclient') # The short X.Y version. version = version_info.version_string() # The full version, including alpha/beta/rc tags. release = version_info.release_string() # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. #unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = [] # The reST default role (used for this markup: `text`) to use for all # documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] man_pages = [ ('man/cinder', 'cinder', u'Client for OpenStack Block Storage API', [u'OpenStack Contributors'], 1), ] # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. html_theme = 'nature' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_use_modindex = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'python-cinderclientdoc' # -- Options for LaTeX output ------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]) # . latex_documents = [ ('index', 'python-cinderclient.tex', 'python-cinderclient Documentation', 'Rackspace - based on work by Jacob Kaplan-Moss', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_use_modindex = True # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'http://docs.python.org/': None} python-cinderclient-1.0.8/doc/source/index.rst0000664000175300017540000001465612274343712022566 0ustar jenkinsjenkins00000000000000Python API ========== In order to use the python api directly, you must first obtain an auth token and identify which endpoint you wish to speak to. Once you have done so, you can use the API like so:: >>> from cinderclient import client >>> cinder = client.Client('1', $OS_USER_NAME, $OS_PASSWORD, $OS_TENANT_NAME, $OS_AUTH_URL) >>> cinder.volumes.list() [] >>> myvol = cinder.volumes.create(display_name="test-vol", size=1) >>> myvol.id ce06d0a8-5c1b-4e2c-81d2-39eca6bbfb70 >>> cinder.volumes.list() [] >>>myvol.delete Command-line Tool ================= In order to use the CLI, you must provide your OpenStack username, password, tenant, and auth endpoint. Use the corresponding configuration options (``--os-username``, ``--os-password``, ``--os-tenant-id``, and ``--os-auth-url``) or set them in environment variables:: export OS_USERNAME=user export OS_PASSWORD=pass export OS_TENANT_ID=b363706f891f48019483f8bd6503c54b export OS_AUTH_URL=http://auth.example.com:5000/v2.0 Once you've configured your authentication parameters, you can run ``cinder help`` to see a complete listing of available commands. See also :doc:`/man/cinder`. Release Notes ============= 1.0.8 ----- * Add support for reset-state on multiple volumes or snapshots at once * Add volume retype command .. _966329: https://bugs.launchpad.net/python-cinderclient/+bug/966329 .. _1256043: https://bugs.launchpad.net/python-cinderclient/+bug/1256043 .. _1254951: http://bugs.launchpad.net/python-cinderclient/+bug/1254951 .. _1254587: http://bugs.launchpad.net/python-cinderclient/+bug/1254587 .. _1253142: http://bugs.launchpad.net/python-cinderclient/+bug/1253142 .. _1252665: http://bugs.launchpad.net/python-cinderclient/+bug/1252665 .. _1255876: http://bugs.launchpad.net/python-cinderclient/+bug/1255876 .. _1251385: http://bugs.launchpad.net/python-cinderclient/+bug/1251385 .. _1264415: http://bugs.launchpad.net/python-cinderclient/+bug/1264415 .. _1258489: http://bugs.launchpad.net/python-cinderclient/+bug/1258489 .. _1248519: http://bugs.launchpad.net/python-cinderclient/+bug/1248519 .. _1257747: http://bugs.launchpad.net/python-cinderclient/+bug/1257747 1.0.7 ----- * Add support for read-only volumes * Add support for setting snapshot metadata * Deprecate volume-id arg to backup restore in favor of --volume * Add quota-usage command * Fix exception deprecation warning message * Report error when no args supplied to rename cmd .. _1241941: http://bugs.launchpad.net/python-cinderclient/+bug/1241941 .. _1242816: http://bugs.launchpad.net/python-cinderclient/+bug/1242816 .. _1233311: http://bugs.launchpad.net/python-cinderclient/+bug/1233311 .. _1227307: http://bugs.launchpad.net/python-cinderclient/+bug/1227307 .. _1240151: http://bugs.launchpad.net/python-cinderclient/+bug/1240151 .. _1241682: http://bugs.launchpad.net/python-cinderclient/+bug/1241682 1.0.6 ----- * Add support for multiple endpoints * Add response info for backup command * Add metadata option to cinder list command * Add timeout parameter for requests * Add update action for snapshot metadata * Add encryption metadata support * Add volume migrate support * Add support for QoS specs .. _1221104: http://bugs.launchpad.net/python-cinderclient/+bug/1221104 .. _1220590: http://bugs.launchpad.net/python-cinderclient/+bug/1220590 .. _1220147: http://bugs.launchpad.net/python-cinderclient/+bug/1220147 .. _1214176: http://bugs.launchpad.net/python-cinderclient/+bug/1214176 .. _1210874: http://bugs.launchpad.net/python-cinderclient/+bug/1210874 .. _1210296: http://bugs.launchpad.net/python-cinderclient/+bug/1210296 .. _1210292: http://bugs.launchpad.net/python-cinderclient/+bug/1210292 .. _1207635: http://bugs.launchpad.net/python-cinderclient/+bug/1207635 .. _1207609: http://bugs.launchpad.net/python-cinderclient/+bug/1207609 .. _1207260: http://bugs.launchpad.net/python-cinderclient/+bug/1207260 .. _1206968: http://bugs.launchpad.net/python-cinderclient/+bug/1206968 .. _1203471: http://bugs.launchpad.net/python-cinderclient/+bug/1203471 .. _1200214: http://bugs.launchpad.net/python-cinderclient/+bug/1200214 .. _1195014: http://bugs.launchpad.net/python-cinderclient/+bug/1195014 1.0.5 ----- * Add CLI man page * Add Availability Zone list command * Add support for scheduler-hints * Add support to extend volumes * Add support to reset state on volumes and snapshots * Add snapshot support for quota class .. _1190853: http://bugs.launchpad.net/python-cinderclient/+bug/1190853 .. _1190731: http://bugs.launchpad.net/python-cinderclient/+bug/1190731 .. _1169455: http://bugs.launchpad.net/python-cinderclient/+bug/1169455 .. _1188452: http://bugs.launchpad.net/python-cinderclient/+bug/1188452 .. _1180393: http://bugs.launchpad.net/python-cinderclient/+bug/1180393 .. _1182678: http://bugs.launchpad.net/python-cinderclient/+bug/1182678 .. _1179008: http://bugs.launchpad.net/python-cinderclient/+bug/1179008 .. _1180059: http://bugs.launchpad.net/python-cinderclient/+bug/1180059 .. _1170565: http://bugs.launchpad.net/python-cinderclient/+bug/1170565 1.0.4 ----- * Added suport for backup-service commands .. _1163546: http://bugs.launchpad.net/python-cinderclient/+bug/1163546 .. _1161857: http://bugs.launchpad.net/python-cinderclient/+bug/1161857 .. _1160898: http://bugs.launchpad.net/python-cinderclient/+bug/1160898 .. _1161857: http://bugs.launchpad.net/python-cinderclient/+bug/1161857 .. _1156994: http://bugs.launchpad.net/python-cinderclient/+bug/1156994 1.0.3 ----- * Added support for V2 Cinder API * Corected upload-volume-to-image help messaging * Align handling of metadata args for all methods * Update OSLO version * Correct parsing of volume metadata * Enable force delete of volumes and snapshots in error state * Implement clone volume API call * Add list-extensions call to cinderclient * Add bootable column to list output * Add retries to cinderclient operations * Add Type/Extra-Specs support * Add volume and snapshot rename commands .. _1155655: http://bugs.launchpad.net/python-cinderclient/+bug/1155655 .. _1130730: http://bugs.launchpad.net/python-cinderclient/+bug/1130730 .. _1068521: http://bugs.launchpad.net/python-cinderclient/+bug/1068521 .. _1052161: http://bugs.launchpad.net/python-cinderclient/+bug/1052161 .. _1071003: http://bugs.launchpad.net/python-cinderclient/+bug/1071003 .. _1065275: http://bugs.launchpad.net/python-cinderclient/+bug/1065275 .. _1053432: http://bugs.launchpad.net/python-cinderclient/+bug/1053432 python-cinderclient-1.0.8/doc/source/man/0000775000175300017540000000000012274343764021473 5ustar jenkinsjenkins00000000000000python-cinderclient-1.0.8/doc/source/man/cinder.rst0000664000175300017540000000251012274343712023460 0ustar jenkinsjenkins00000000000000============================== :program:`cinder` CLI man page ============================== .. program:: cinder .. highlight:: bash SYNOPSIS ======== :program:`cinder` [options] [command-options] :program:`cinder help` :program:`cinder help` DESCRIPTION =========== The :program:`cinder` command line utility interacts with OpenStack Block Storage Service (Cinder). In order to use the CLI, you must provide your OpenStack username, password, project (historically called tenant), and auth endpoint. You can use configuration options :option:`--os-username`, :option:`--os-password`, :option:`--os-tenant-name` or :option:`--os-tenant-id`, and :option:`--os-auth-url` or set corresponding environment variables:: export OS_USERNAME=user export OS_PASSWORD=pass export OS_TENANT_NAME=myproject export OS_AUTH_URL=http://auth.example.com:5000/v2.0 You can select an API version to use by :option:`--os-volume-api-version` option or by setting corresponding environment variable:: export OS_VOLUME_API_VERSION=2 OPTIONS ======= To get a list of available commands and options run:: cinder help To get usage and options of a command:: cinder help BUGS ==== Cinder client is hosted in Launchpad so you can view current bugs at https://bugs.launchpad.net/python-cinderclient/. python-cinderclient-1.0.8/doc/source/shell.rst0000664000175300017540000000255112274343712022555 0ustar jenkinsjenkins00000000000000The :program:`cinder` shell utility ========================================= .. program:: cinder .. highlight:: bash The :program:`cinder` shell utility interacts with the OpenStack Cinder API from the command line. It supports the entirety of the OpenStack Cinder API. You'll need to provide :program:`cinder` with your OpenStack username and API key. You can do this with the :option:`--os-username`, :option:`--os-password` and :option:`--os-tenant-name` options, but it's easier to just set them as environment variables by setting two environment variables: .. envvar:: OS_USERNAME or CINDER_USERNAME Your OpenStack Cinder username. .. envvar:: OS_PASSWORD or CINDER_PASSWORD Your password. .. envvar:: OS_TENANT_NAME or CINDER_PROJECT_ID Project for work. .. envvar:: OS_AUTH_URL or CINDER_URL The OpenStack API server URL. .. envvar:: OS_VOLUME_API_VERSION The OpenStack Block Storage API version. For example, in Bash you'd use:: export OS_USERNAME=yourname export OS_PASSWORD=yadayadayada export OS_TENANT_NAME=myproject export OS_AUTH_URL=http://... export OS_VOLUME_API_VERSION=1 From there, all shell commands take the form:: cinder [arguments...] Run :program:`cinder help` to get a full list of all possible commands, and run :program:`cinder help ` to get detailed help for that command. python-cinderclient-1.0.8/MANIFEST.in0000664000175300017540000000011012274343712020372 0ustar jenkinsjenkins00000000000000include AUTHORS include ChangeLog exclude .gitignore exclude .gitreview python-cinderclient-1.0.8/LICENSE0000664000175300017540000002707512274343712017664 0ustar jenkinsjenkins00000000000000Copyright (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. python-cinderclient-1.0.8/setup.cfg0000664000175300017540000000174212274343764020500 0ustar jenkinsjenkins00000000000000[metadata] name = python-cinderclient summary = OpenStack Block Storage API Client Library description-file = README.rst author = OpenStack author-email = openstack-dev@lists.openstack.org home-page = http://www.openstack.org/ classifier = Development Status :: 5 - Production/Stable Environment :: Console Environment :: OpenStack Intended Audience :: Information Technology Intended Audience :: System Administrators License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 2.6 [global] setup-hooks = pbr.hooks.setup_hook [files] packages = cinderclient [entry_points] console_scripts = cinder = cinderclient.shell:main [build_sphinx] all_files = 1 source-dir = doc/source build-dir = doc/build [upload_sphinx] upload-dir = doc/build/html [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 python-cinderclient-1.0.8/.mailmap0000664000175300017540000000166412274343712020274 0ustar jenkinsjenkins00000000000000Antony Messerli root Chris Behrens comstud Johannes Erdfelt jerdfelt Andy Smith termie Nikolay Sokolov Nokolay Sokolov Nikolay Sokolov Nokolay Sokolov python-cinderclient-1.0.8/python_cinderclient.egg-info/0000775000175300017540000000000012274343764024411 5ustar jenkinsjenkins00000000000000python-cinderclient-1.0.8/python_cinderclient.egg-info/dependency_links.txt0000664000175300017540000000000112274343764030457 0ustar jenkinsjenkins00000000000000 python-cinderclient-1.0.8/python_cinderclient.egg-info/SOURCES.txt0000664000175300017540000001071512274343764026301 0ustar jenkinsjenkins00000000000000.coveragerc .mailmap .testr.conf AUTHORS ChangeLog HACKING.rst LICENSE MANIFEST.in README.rst openstack-common.conf requirements.txt run_tests.sh setup.cfg setup.py test-requirements.txt tox.ini cinderclient/__init__.py cinderclient/base.py cinderclient/client.py cinderclient/exceptions.py cinderclient/extension.py cinderclient/service_catalog.py cinderclient/shell.py cinderclient/utils.py cinderclient/openstack/__init__.py cinderclient/openstack/common/__init__.py cinderclient/openstack/common/gettextutils.py cinderclient/openstack/common/importutils.py cinderclient/openstack/common/strutils.py cinderclient/openstack/common/apiclient/__init__.py cinderclient/openstack/common/apiclient/auth.py cinderclient/openstack/common/apiclient/base.py cinderclient/openstack/common/apiclient/client.py cinderclient/openstack/common/apiclient/exceptions.py cinderclient/openstack/common/apiclient/fake_client.py cinderclient/openstack/common/py3kcompat/__init__.py cinderclient/openstack/common/py3kcompat/urlutils.py cinderclient/tests/__init__.py cinderclient/tests/fakes.py cinderclient/tests/test_base.py cinderclient/tests/test_client.py cinderclient/tests/test_http.py cinderclient/tests/test_service_catalog.py cinderclient/tests/test_shell.py cinderclient/tests/test_utils.py cinderclient/tests/utils.py cinderclient/tests/v1/__init__.py cinderclient/tests/v1/fakes.py cinderclient/tests/v1/test_auth.py cinderclient/tests/v1/test_availability_zone.py cinderclient/tests/v1/test_limits.py cinderclient/tests/v1/test_qos.py cinderclient/tests/v1/test_quota_classes.py cinderclient/tests/v1/test_quotas.py cinderclient/tests/v1/test_services.py cinderclient/tests/v1/test_shell.py cinderclient/tests/v1/test_snapshot_actions.py cinderclient/tests/v1/test_types.py cinderclient/tests/v1/test_volume_backups.py cinderclient/tests/v1/test_volume_encryption_types.py cinderclient/tests/v1/test_volume_transfers.py cinderclient/tests/v1/test_volumes.py cinderclient/tests/v1/testfile.txt cinderclient/tests/v1/contrib/__init__.py cinderclient/tests/v1/contrib/test_list_extensions.py cinderclient/tests/v2/__init__.py cinderclient/tests/v2/fakes.py cinderclient/tests/v2/test_auth.py cinderclient/tests/v2/test_availability_zone.py cinderclient/tests/v2/test_limits.py cinderclient/tests/v2/test_qos.py cinderclient/tests/v2/test_quota_classes.py cinderclient/tests/v2/test_quotas.py cinderclient/tests/v2/test_services.py cinderclient/tests/v2/test_shell.py cinderclient/tests/v2/test_snapshot_actions.py cinderclient/tests/v2/test_types.py cinderclient/tests/v2/test_volume_backups.py cinderclient/tests/v2/test_volume_encryption_types.py cinderclient/tests/v2/test_volume_transfers.py cinderclient/tests/v2/test_volumes.py cinderclient/tests/v2/contrib/__init__.py cinderclient/tests/v2/contrib/test_list_extensions.py cinderclient/v1/__init__.py cinderclient/v1/availability_zones.py cinderclient/v1/client.py cinderclient/v1/limits.py cinderclient/v1/qos_specs.py cinderclient/v1/quota_classes.py cinderclient/v1/quotas.py cinderclient/v1/services.py cinderclient/v1/shell.py cinderclient/v1/volume_backups.py cinderclient/v1/volume_backups_restore.py cinderclient/v1/volume_encryption_types.py cinderclient/v1/volume_snapshots.py cinderclient/v1/volume_transfers.py cinderclient/v1/volume_types.py cinderclient/v1/volumes.py cinderclient/v1/contrib/__init__.py cinderclient/v1/contrib/list_extensions.py cinderclient/v2/__init__.py cinderclient/v2/availability_zones.py cinderclient/v2/client.py cinderclient/v2/limits.py cinderclient/v2/qos_specs.py cinderclient/v2/quota_classes.py cinderclient/v2/quotas.py cinderclient/v2/services.py cinderclient/v2/shell.py cinderclient/v2/volume_backups.py cinderclient/v2/volume_backups_restore.py cinderclient/v2/volume_encryption_types.py cinderclient/v2/volume_snapshots.py cinderclient/v2/volume_transfers.py cinderclient/v2/volume_types.py cinderclient/v2/volumes.py cinderclient/v2/contrib/__init__.py cinderclient/v2/contrib/list_extensions.py doc/.gitignore doc/Makefile doc/source/conf.py doc/source/index.rst doc/source/shell.rst doc/source/man/cinder.rst python_cinderclient.egg-info/PKG-INFO python_cinderclient.egg-info/SOURCES.txt python_cinderclient.egg-info/dependency_links.txt python_cinderclient.egg-info/entry_points.txt python_cinderclient.egg-info/not-zip-safe python_cinderclient.egg-info/requires.txt python_cinderclient.egg-info/top_level.txt tools/cinder.bash_completion tools/colorizer.py tools/generate_authors.sh tools/install_venv.py tools/install_venv_common.py tools/with_venv.shpython-cinderclient-1.0.8/python_cinderclient.egg-info/top_level.txt0000664000175300017540000000001512274343764027137 0ustar jenkinsjenkins00000000000000cinderclient python-cinderclient-1.0.8/python_cinderclient.egg-info/not-zip-safe0000664000175300017540000000000112274343760026633 0ustar jenkinsjenkins00000000000000 python-cinderclient-1.0.8/python_cinderclient.egg-info/PKG-INFO0000664000175300017540000002120412274343764025505 0ustar jenkinsjenkins00000000000000Metadata-Version: 1.1 Name: python-cinderclient Version: 1.0.8 Summary: OpenStack Block Storage API Client Library Home-page: http://www.openstack.org/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description: Python bindings to the OpenStack Cinder API =========================================== 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 guide`_ for information on how to use the ``cinder`` command-line tool. You may also want to look at the `OpenStack API documentation`_. .. _OpenStack CLI Guide: http://docs.openstack.org/cli/quick-start/content/ .. _OpenStack API documentation: http://docs.openstack.org/api/ The project is hosted on `Launchpad`_, where bugs can be filed. The code is hosted on `Github`_. Patches must be submitted using `Gerrit`_, *not* Github pull requests. .. _Github: https://github.com/openstack/python-cinderclient .. _Launchpad: https://launchpad.net/python-cinderclient .. _Gerrit: http://wiki.openstack.org/GerritWorkflow This code 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. __ http://github.com/jacobian/python-cloudservers .. 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:: export OS_AUTH_URL=http://example.com:8774/v1.1/ export OS_VOLUME_API_VERSION=1 If you are using Keystone, you need to set the OS_AUTH_URL to the keystone endpoint:: export OS_AUTH_URL=http://example.com:5000/v2.0/ 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 [--debug] [--os-username ] [--os-password ] [--os-tenant-name ] [--os-auth-url ] [--os-region-name ] [--service-type ] [--service-name ] [--volume-service-name ] [--endpoint-type ] [--os-volume-api-version ] [--os-cacert ] [--retries ] ... Command-line interface to the OpenStack Cinder API. Positional arguments: absolute-limits Print a list of absolute limits for a user create Add a new volume. credentials Show user credentials returned from auth delete Remove a volume. endpoints Discover endpoints that get returned from the authenticate services extra-specs-list Print a list of current 'volume types and extra specs' (Admin Only). list List all the volumes. quota-class-show List the quotas for a quota class. quota-class-update Update the quotas for a quota class. quota-defaults List the default quotas for a tenant. quota-show List the quotas for a tenant. quota-update Update the quotas for a tenant. rate-limits Print a list of rate limits for a user rename Rename a volume. show Show details about a volume. snapshot-create Add a new snapshot. snapshot-delete Remove a snapshot. snapshot-list List all the snapshots. snapshot-rename Rename a snapshot. snapshot-show Show details about a snapshot. type-create Create a new volume type. type-delete Delete a specific volume type type-key Set or unset extra_spec for a volume type. type-list Print a list of available 'volume types'. bash-completion Prints all of the commands and options to stdout so that the help Display help about this program or one of its subcommands. list-extensions List all the os-api extensions that are available. Optional arguments: --debug Print debugging output --os-username Defaults to env[OS_USERNAME]. --os-password Defaults to env[OS_PASSWORD]. --os-tenant-name Defaults to env[OS_TENANT_NAME]. --os-auth-url Defaults to env[OS_AUTH_URL]. --os-region-name Defaults to env[OS_REGION_NAME]. --service-type Defaults to compute for most actions --service-name Defaults to env[CINDER_SERVICE_NAME] --volume-service-name Defaults to env[CINDER_VOLUME_SERVICE_NAME] --endpoint-type Defaults to env[CINDER_ENDPOINT_TYPE] or publicURL. --os-volume-api-version Accepts 1,defaults to env[OS_VOLUME_API_VERSION]. --os-cacert Specify a CA bundle file to use in verifying a TLS (https) server certificate. Defaults to env[OS_CACERT] --retries Number of retries. Python API ---------- There's also a complete Python API, but it has not yet been documented. Quick-start using keystone:: # use v2.0 auth with http://example.com:5000/v2.0/") >>> from cinderclient.v1 import client >>> nt = client.Client(USER, PASS, TENANT, AUTH_URL, service_type="volume") >>> nt.volumes.list() [...] See release notes and more at ``_. Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 2.6 python-cinderclient-1.0.8/python_cinderclient.egg-info/entry_points.txt0000664000175300017540000000006412274343764027707 0ustar jenkinsjenkins00000000000000[console_scripts] cinder = cinderclient.shell:main python-cinderclient-1.0.8/python_cinderclient.egg-info/requires.txt0000664000175300017540000000013412274343764027007 0ustar jenkinsjenkins00000000000000pbr>=0.5.21,<1.0 PrettyTable>=0.7,<0.8 requests>=1.1 simplejson>=2.0.9 Babel>=1.3 six>=1.4.1python-cinderclient-1.0.8/tools/0000775000175300017540000000000012274343764020013 5ustar jenkinsjenkins00000000000000python-cinderclient-1.0.8/tools/install_venv_common.py0000664000175300017540000001350612274343712024437 0ustar jenkinsjenkins00000000000000# Copyright 2013 OpenStack Foundation # Copyright 2013 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Provides methods needed by installation script for OpenStack development virtual environments. Since this script is used to bootstrap a virtualenv from the system's Python environment, it should be kept strictly compatible with Python 2.6. Synced in from openstack-common """ from __future__ import print_function import optparse import os import subprocess import sys class InstallVenv(object): def __init__(self, root, venv, requirements, test_requirements, py_version, project): self.root = root self.venv = venv self.requirements = requirements self.test_requirements = test_requirements self.py_version = py_version self.project = project def die(self, message, *args): print(message % args, file=sys.stderr) sys.exit(1) def check_python_version(self): if sys.version_info < (2, 6): self.die("Need Python Version >= 2.6") def run_command_with_code(self, cmd, redirect_output=True, check_exit_code=True): """Runs a command in an out-of-process shell. Returns the output of that command. Working directory is self.root. """ if redirect_output: stdout = subprocess.PIPE else: stdout = None proc = subprocess.Popen(cmd, cwd=self.root, stdout=stdout) output = proc.communicate()[0] if check_exit_code and proc.returncode != 0: self.die('Command "%s" failed.\n%s', ' '.join(cmd), output) return (output, proc.returncode) def run_command(self, cmd, redirect_output=True, check_exit_code=True): return self.run_command_with_code(cmd, redirect_output, check_exit_code)[0] def get_distro(self): if (os.path.exists('/etc/fedora-release') or os.path.exists('/etc/redhat-release')): return Fedora( self.root, self.venv, self.requirements, self.test_requirements, self.py_version, self.project) else: return Distro( self.root, self.venv, self.requirements, self.test_requirements, self.py_version, self.project) def check_dependencies(self): self.get_distro().install_virtualenv() def create_virtualenv(self, no_site_packages=True): """Creates the virtual environment and installs PIP. Creates the virtual environment and installs PIP only into the virtual environment. """ if not os.path.isdir(self.venv): print('Creating venv...', end=' ') if no_site_packages: self.run_command(['virtualenv', '-q', '--no-site-packages', self.venv]) else: self.run_command(['virtualenv', '-q', self.venv]) print('done.') else: print("venv already exists...") pass def pip_install(self, *args): self.run_command(['tools/with_venv.sh', 'pip', 'install', '--upgrade'] + list(args), redirect_output=False) def install_dependencies(self): print('Installing dependencies with pip (this can take a while)...') # First things first, make sure our venv has the latest pip and # setuptools and pbr self.pip_install('pip>=1.4') self.pip_install('setuptools') self.pip_install('pbr') self.pip_install('-r', self.requirements, '-r', self.test_requirements) def parse_args(self, argv): """Parses command-line arguments.""" parser = optparse.OptionParser() parser.add_option('-n', '--no-site-packages', action='store_true', help="Do not inherit packages from global Python " "install") return parser.parse_args(argv[1:])[0] class Distro(InstallVenv): def check_cmd(self, cmd): return bool(self.run_command(['which', cmd], check_exit_code=False).strip()) def install_virtualenv(self): if self.check_cmd('virtualenv'): return if self.check_cmd('easy_install'): print('Installing virtualenv via easy_install...', end=' ') if self.run_command(['easy_install', 'virtualenv']): print('Succeeded') return else: print('Failed') self.die('ERROR: virtualenv not found.\n\n%s development' ' requires virtualenv, please install it using your' ' favorite package management tool' % self.project) class Fedora(Distro): """This covers all Fedora-based distributions. Includes: Fedora, RHEL, CentOS, Scientific Linux """ def check_pkg(self, pkg): return self.run_command_with_code(['rpm', '-q', pkg], check_exit_code=False)[1] == 0 def install_virtualenv(self): if self.check_cmd('virtualenv'): return if not self.check_pkg('python-virtualenv'): self.die("Please install 'python-virtualenv'.") super(Fedora, self).install_virtualenv() python-cinderclient-1.0.8/tools/colorizer.py0000775000175300017540000002715512274343712022403 0ustar jenkinsjenkins00000000000000#!/usr/bin/env python # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright (c) 2013, Nebula, Inc. # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # # Colorizer Code is borrowed from Twisted: # Copyright (c) 2001-2010 Twisted Matrix Laboratories. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """Display a subunit stream through a colorized unittest test runner.""" import heapq import subunit import sys import unittest import six import testtools class _AnsiColorizer(object): """ A colorizer is an object that loosely wraps around a stream, allowing callers to write text to the stream in a particular color. Colorizer classes must implement C{supported()} and C{write(text, color)}. """ _colors = dict(black=30, red=31, green=32, yellow=33, blue=34, magenta=35, cyan=36, white=37) def __init__(self, stream): self.stream = stream def supported(cls, stream=sys.stdout): """ A class method that returns True if the current platform supports coloring terminal output using this method. Returns False otherwise. """ if not stream.isatty(): return False # auto color only on TTYs try: import curses except ImportError: return False else: try: try: return curses.tigetnum("colors") > 2 except curses.error: curses.setupterm() return curses.tigetnum("colors") > 2 except Exception: # guess false in case of error return False supported = classmethod(supported) def write(self, text, color): """ Write the given text to the stream in the given color. @param text: Text to be written to the stream. @param color: A string label for a color. e.g. 'red', 'white'. """ color = self._colors[color] self.stream.write('\x1b[%s;1m%s\x1b[0m' % (color, text)) class _Win32Colorizer(object): """ See _AnsiColorizer docstring. """ def __init__(self, stream): import win32console red, green, blue, bold = (win32console.FOREGROUND_RED, win32console.FOREGROUND_GREEN, win32console.FOREGROUND_BLUE, win32console.FOREGROUND_INTENSITY) self.stream = stream self.screenBuffer = win32console.GetStdHandle( win32console.STD_OUT_HANDLE) self._colors = { 'normal': red | green | blue, 'red': red | bold, 'green': green | bold, 'blue': blue | bold, 'yellow': red | green | bold, 'magenta': red | blue | bold, 'cyan': green | blue | bold, 'white': red | green | blue | bold } def supported(cls, stream=sys.stdout): try: import win32console screenBuffer = win32console.GetStdHandle( win32console.STD_OUT_HANDLE) except ImportError: return False import pywintypes try: screenBuffer.SetConsoleTextAttribute( win32console.FOREGROUND_RED | win32console.FOREGROUND_GREEN | win32console.FOREGROUND_BLUE) except pywintypes.error: return False else: return True supported = classmethod(supported) def write(self, text, color): color = self._colors[color] self.screenBuffer.SetConsoleTextAttribute(color) self.stream.write(text) self.screenBuffer.SetConsoleTextAttribute(self._colors['normal']) class _NullColorizer(object): """ See _AnsiColorizer docstring. """ def __init__(self, stream): self.stream = stream def supported(cls, stream=sys.stdout): return True supported = classmethod(supported) def write(self, text, color): self.stream.write(text) def get_elapsed_time_color(elapsed_time): if elapsed_time > 1.0: return 'red' elif elapsed_time > 0.25: return 'yellow' else: return 'green' class NovaTestResult(testtools.TestResult): def __init__(self, stream, descriptions, verbosity): super(NovaTestResult, self).__init__() self.stream = stream self.showAll = verbosity > 1 self.num_slow_tests = 10 self.slow_tests = [] # this is a fixed-sized heap self.colorizer = None # NOTE(vish): reset stdout for the terminal check stdout = sys.stdout sys.stdout = sys.__stdout__ for colorizer in [_Win32Colorizer, _AnsiColorizer, _NullColorizer]: if colorizer.supported(): self.colorizer = colorizer(self.stream) break sys.stdout = stdout self.start_time = None self.last_time = {} self.results = {} self.last_written = None def _writeElapsedTime(self, elapsed): color = get_elapsed_time_color(elapsed) self.colorizer.write(" %.2f" % elapsed, color) def _addResult(self, test, *args): try: name = test.id() except AttributeError: name = 'Unknown.unknown' test_class, test_name = name.rsplit('.', 1) elapsed = (self._now() - self.start_time).total_seconds() item = (elapsed, test_class, test_name) if len(self.slow_tests) >= self.num_slow_tests: heapq.heappushpop(self.slow_tests, item) else: heapq.heappush(self.slow_tests, item) self.results.setdefault(test_class, []) self.results[test_class].append((test_name, elapsed) + args) self.last_time[test_class] = self._now() self.writeTests() def _writeResult(self, test_name, elapsed, long_result, color, short_result, success): if self.showAll: self.stream.write(' %s' % str(test_name).ljust(66)) self.colorizer.write(long_result, color) if success: self._writeElapsedTime(elapsed) self.stream.writeln() else: self.colorizer.write(short_result, color) def addSuccess(self, test): super(NovaTestResult, self).addSuccess(test) self._addResult(test, 'OK', 'green', '.', True) def addFailure(self, test, err): if test.id() == 'process-returncode': return super(NovaTestResult, self).addFailure(test, err) self._addResult(test, 'FAIL', 'red', 'F', False) def addError(self, test, err): super(NovaTestResult, self).addFailure(test, err) self._addResult(test, 'ERROR', 'red', 'E', False) def addSkip(self, test, reason=None, details=None): super(NovaTestResult, self).addSkip(test, reason, details) self._addResult(test, 'SKIP', 'blue', 'S', True) def startTest(self, test): self.start_time = self._now() super(NovaTestResult, self).startTest(test) def writeTestCase(self, cls): if not self.results.get(cls): return if cls != self.last_written: self.colorizer.write(cls, 'white') self.stream.writeln() for result in self.results[cls]: self._writeResult(*result) del self.results[cls] self.stream.flush() self.last_written = cls def writeTests(self): time = self.last_time.get(self.last_written, self._now()) if not self.last_written or (self._now() - time).total_seconds() > 2.0: diff = 3.0 while diff > 2.0: classes =list(self.results) oldest = min(classes, key=lambda x: self.last_time[x]) diff = (self._now() - self.last_time[oldest]).total_seconds() self.writeTestCase(oldest) else: self.writeTestCase(self.last_written) def done(self): self.stopTestRun() def stopTestRun(self): for cls in list(six.iterkeys(self.results)): self.writeTestCase(cls) self.stream.writeln() self.writeSlowTests() def writeSlowTests(self): # Pare out 'fast' tests slow_tests = [item for item in self.slow_tests if get_elapsed_time_color(item[0]) != 'green'] if slow_tests: slow_total_time = sum(item[0] for item in slow_tests) slow = ("Slowest %i tests took %.2f secs:" % (len(slow_tests), slow_total_time)) self.colorizer.write(slow, 'yellow') self.stream.writeln() last_cls = None # sort by name for elapsed, cls, name in sorted(slow_tests, key=lambda x: x[1] + x[2]): if cls != last_cls: self.colorizer.write(cls, 'white') self.stream.writeln() last_cls = cls self.stream.write(' %s' % str(name).ljust(68)) self._writeElapsedTime(elapsed) self.stream.writeln() def printErrors(self): if self.showAll: self.stream.writeln() self.printErrorList('ERROR', self.errors) self.printErrorList('FAIL', self.failures) def printErrorList(self, flavor, errors): for test, err in errors: self.colorizer.write("=" * 70, 'red') self.stream.writeln() self.colorizer.write(flavor, 'red') self.stream.writeln(": %s" % test.id()) self.colorizer.write("-" * 70, 'red') self.stream.writeln() self.stream.writeln("%s" % err) test = subunit.ProtocolTestCase(sys.stdin, passthrough=None) if sys.version_info[0:2] <= (2, 6): runner = unittest.TextTestRunner(verbosity=2) else: runner = unittest.TextTestRunner(verbosity=2, resultclass=NovaTestResult) if runner.run(test).wasSuccessful(): exit_code = 0 else: exit_code = 1 sys.exit(exit_code) python-cinderclient-1.0.8/tools/cinder.bash_completion0000664000175300017540000000055112274343712024341 0ustar jenkinsjenkins00000000000000_cinder() { local cur prev opts COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" opts="$(cinder bash_completion)" COMPLETION_CACHE=~/.cinderclient/*/*-cache opts+=" "$(cat $COMPLETION_CACHE 2> /dev/null | tr '\n' ' ') COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) } complete -F _cinder cinder python-cinderclient-1.0.8/tools/with_venv.sh0000775000175300017540000000012412274343712022351 0ustar jenkinsjenkins00000000000000#!/bin/bash TOOLS=`dirname $0` VENV=$TOOLS/../.venv source $VENV/bin/activate && $@ python-cinderclient-1.0.8/tools/generate_authors.sh0000775000175300017540000000005112274343712023676 0ustar jenkinsjenkins00000000000000#!/bin/bash git shortlog -se | cut -c8- python-cinderclient-1.0.8/tools/install_venv.py0000664000175300017540000000506412274343712023067 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. # # Copyright 2010 OpenStack Foundation # Copyright 2013 IBM Corp. # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import ConfigParser import os import sys import install_venv_common as install_venv # flake8: noqa def print_help(project, venv, root): help = """ %(project)s development environment setup is complete. %(project)s development uses virtualenv to track and manage Python dependencies while in development and testing. To activate the %(project)s virtualenv for the extent of your current shell session you can run: $ source %(venv)s/bin/activate Or, if you prefer, you can run commands in the virtualenv on a case by case basis by running: $ %(root)s/tools/with_venv.sh """ print help % dict(project=project, venv=venv, root=root) def main(argv): root = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) if os.environ.get('tools_path'): root = os.environ['tools_path'] venv = os.path.join(root, '.venv') if os.environ.get('venv'): venv = os.environ['venv'] pip_requires = os.path.join(root, 'requirements.txt') test_requires = os.path.join(root, 'test-requirements.txt') py_version = "python%s.%s" % (sys.version_info[0], sys.version_info[1]) setup_cfg = ConfigParser.ConfigParser() setup_cfg.read('setup.cfg') project = setup_cfg.get('metadata', 'name') install = install_venv.InstallVenv( root, venv, pip_requires, test_requires, py_version, project) options = install.parse_args(argv) install.check_python_version() install.check_dependencies() install.create_virtualenv(no_site_packages=options.no_site_packages) install.install_dependencies() install.post_process() print_help(project, venv, root) if __name__ == '__main__': main(sys.argv) python-cinderclient-1.0.8/.coveragerc0000664000175300017540000000015112274343712020762 0ustar jenkinsjenkins00000000000000[run] branch = True source = cinderclient omit = cinderclient/openstack/* [report] ignore-errors = True python-cinderclient-1.0.8/cinderclient/0000775000175300017540000000000012274343764021316 5ustar jenkinsjenkins00000000000000python-cinderclient-1.0.8/cinderclient/extension.py0000664000175300017540000000260112274343712023674 0ustar jenkinsjenkins00000000000000# 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 import base from cinderclient import utils class Extension(utils.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 "" % self.name python-cinderclient-1.0.8/cinderclient/exceptions.py0000664000175300017540000001073312274343712024046 0ustar jenkinsjenkins00000000000000# 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. """ class UnsupportedVersion(Exception): """Indicates that the user is trying to use an unsupported version of the API. """ pass class InvalidAPIVersion(Exception): pass class CommandError(Exception): pass class AuthorizationFailure(Exception): pass class NoUniqueMatch(Exception): pass 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): self.code = code self.message = message or self.__class__.message self.details = details self.request_id = request_id def __str__(self): formatted_string = "%s (HTTP %s)" % (self.message, 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 OverLimit(ClientException): """ HTTP 413 - Over limit: you're over the API limits for this time period. """ http_status = 413 message = "Over limit" # 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, OverLimit, HTTPNotImplemented]) def from_response(response, body): """ Return an instance of an ClientException or subclass based on an requests response. Usage:: resp, body = requests.request(...) if resp.status_code != 200: raise exception_from_response(resp, rest.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'): error = body[list(body)[0]] message = error.get('message', None) details = error.get('details', None) return cls(code=response.status_code, message=message, details=details, request_id=request_id) else: return cls(code=response.status_code, request_id=request_id) python-cinderclient-1.0.8/cinderclient/client.py0000664000175300017540000003445012274343712023145 0ustar jenkinsjenkins00000000000000# 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 logging try: import urlparse except ImportError: import urllib.parse as urlparse try: from eventlet import sleep except ImportError: from time import sleep try: import json except ImportError: import simplejson as json # Python 2.5 compat fix if not hasattr(urlparse, 'parse_qsl'): import cgi urlparse.parse_qsl = cgi.parse_qsl import requests from cinderclient import exceptions from cinderclient import service_catalog from cinderclient import utils class HTTPClient(object): USER_AGENT = 'python-cinderclient' def __init__(self, user, password, projectid, auth_url, 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, retries=None, http_log_debug=False, cacert=None): self.user = user self.password = password self.projectid = projectid self.tenant_id = tenant_id self.auth_url = auth_url.rstrip('/') 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.retries = int(retries or 0) self.http_log_debug = http_log_debug self.management_url = None self.auth_token = None self.proxy_token = proxy_token self.proxy_tenant_id = proxy_tenant_id self.timeout = timeout if insecure: self.verify_cert = False else: if cacert: self.verify_cert = cacert else: self.verify_cert = True self._logger = logging.getLogger(__name__) if self.http_log_debug and not self._logger.handlers: ch = logging.StreamHandler() self._logger.setLevel(logging.DEBUG) self._logger.addHandler(ch) if hasattr(requests, 'logging'): requests.logging.getLogger(requests.__name__).addHandler(ch) 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"' % (element, kwargs['headers'][element]) string_parts.append(header) if 'data' in kwargs: string_parts.append(" -d '%s'" % (kwargs['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, resp.text) 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 'body' in kwargs: kwargs['headers']['Content-Type'] = 'application/json' kwargs['data'] = json.dumps(kwargs['body']) del kwargs['body'] 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) if resp.text: try: body = json.loads(resp.text) except ValueError: pass body = None else: body = None 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: resp, body = self.request(self.management_url + 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.ClientException as e: if attempts > self.retries: raise if 500 <= e.code <= 599: pass else: raise except requests.exceptions.ConnectionError as e: # Catch a connection refused from requests.request self._logger.debug("Connection refused: %s" % e) msg = 'Unable to establish connection: %s' % e raise exceptions.ConnectionError(msg) 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 _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. """ if resp.status_code == 200: # content must always present try: self.auth_url = url self.service_catalog = \ service_catalog.ServiceCatalog(body) if extract_token: self.auth_token = self.service_catalog.get_token() management_url = self.service_catalog.url_for( attr='region', filter_value=self.region_name, endpoint_type=self.endpoint_type, service_type=self.service_type, service_name=self.service_name, volume_service_name=self.volume_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 KeyError: 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 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": while auth_url: auth_url = self._v2_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: 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_auth(auth_url) 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_auth(self, url): """Authenticate against a v2.0 auth service.""" 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.""" 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 get_volume_api_version_from_endpoint(self): magic_tuple = urlparse.urlsplit(self.management_url) scheme, netloc, path, query, frag = magic_tuple components = path.split("/") valid_versions = ['v1', 'v2'] for version in valid_versions: if version in components: return version[1:] msg = "Invalid client version '%s'. must be one of: %s" % ( (version, ', '.join(valid_versions))) raise exceptions.UnsupportedVersion(msg) def get_client_class(version): version_map = { '1': 'cinderclient.v1.client.Client', '2': 'cinderclient.v2.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 utils.import_class(client_path) def Client(version, *args, **kwargs): client_class = get_client_class(version) return client_class(*args, **kwargs) python-cinderclient-1.0.8/cinderclient/__init__.py0000664000175300017540000000172512274343712023425 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # 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. __all__ = ['__version__'] import pbr.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 python-cinderclient-1.0.8/cinderclient/openstack/0000775000175300017540000000000012274343764023305 5ustar jenkinsjenkins00000000000000python-cinderclient-1.0.8/cinderclient/openstack/__init__.py0000664000175300017540000000000012274343712025375 0ustar jenkinsjenkins00000000000000python-cinderclient-1.0.8/cinderclient/openstack/common/0000775000175300017540000000000012274343764024575 5ustar jenkinsjenkins00000000000000python-cinderclient-1.0.8/cinderclient/openstack/common/apiclient/0000775000175300017540000000000012274343764026545 5ustar jenkinsjenkins00000000000000python-cinderclient-1.0.8/cinderclient/openstack/common/apiclient/exceptions.py0000664000175300017540000002721112274343712031274 0ustar jenkinsjenkins00000000000000# 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 import six 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 six.iteritems(vars(sys.modules[__name__])) 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[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) python-cinderclient-1.0.8/cinderclient/openstack/common/apiclient/client.py0000664000175300017540000003067412274343712030400 0ustar jenkinsjenkins00000000000000# 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 requests from cinderclient.openstack.common.apiclient import exceptions from cinderclient.openstack.common import importutils _logger = logging.getLogger(__name__) 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.openstack.common.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 _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'" % (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['json']) try: del kwargs['json'] except KeyError: pass 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) python-cinderclient-1.0.8/cinderclient/openstack/common/apiclient/__init__.py0000664000175300017540000000000012274343712030635 0ustar jenkinsjenkins00000000000000python-cinderclient-1.0.8/cinderclient/openstack/common/apiclient/fake_client.py0000664000175300017540000001337512274343712031365 0ustar jenkinsjenkins00000000000000# 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. """ 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. """ # W0102: Dangerous default value %s as argument # pylint: disable=W0102 import json import requests import six from cinderclient.openstack.common.apiclient import client from cinderclient.openstack.common.py3kcompat import urlutils def assert_has_keys(dct, required=[], optional=[]): for k in required: try: assert k in dct except AssertionError: extra_keys = set(dct.keys()).difference(set(required + optional)) raise AssertionError("found unexpected keys: %s" % list(extra_keys)) class TestResponse(requests.Response): """Wrap requests.Response and provide a convenient initialization. """ def __init__(self, data): super(TestResponse, self).__init__() self._content_consumed = True if isinstance(data, dict): self.status_code = data.get('status_code', 200) # Fake the text attribute to streamline Response creation text = data.get('text', "") if isinstance(text, (dict, list)): self._content = json.dumps(text) default_headers = { "Content-Type": "application/json", } else: self._content = text default_headers = {} if six.PY3 and isinstance(self._content, six.string_types): self._content = self._content.encode('utf-8', 'strict') self.headers = data.get('headers') or default_headers else: self.status_code = data def __eq__(self, other): return (self.status_code == other.status_code and self.headers == other.headers and self._content == other._content) class FakeHTTPClient(client.HTTPClient): def __init__(self, *args, **kwargs): self.callstack = [] self.fixtures = kwargs.pop("fixtures", None) or {} if not args and not "auth_plugin" in kwargs: args = (None, ) super(FakeHTTPClient, self).__init__(*args, **kwargs) def assert_called(self, method, url, body=None, pos=-1): """Assert than an API method was just called. """ expected = (method, url) called = self.callstack[pos][0:2] assert self.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: if self.callstack[pos][3] != body: raise AssertionError('%r != %r' % (self.callstack[pos][3], body)) def assert_called_anytime(self, method, url, body=None): """Assert than an API method was called anytime in the test. """ expected = (method, url) assert self.callstack, \ "Expected %s %s but no calls were made." % expected found = False entry = None for entry in self.callstack: if expected == entry[0:2]: found = True break assert found, 'Expected %s %s; got %s' % \ (method, url, self.callstack) if body is not None: assert entry[3] == body, "%s != %s" % (entry[3], body) self.callstack = [] def clear_callstack(self): self.callstack = [] def authenticate(self): pass def client_request(self, client, method, url, **kwargs): # Check that certain things are called correctly if method in ["GET", "DELETE"]: assert "json" not in kwargs # Note the call self.callstack.append( (method, url, kwargs.get("headers") or {}, kwargs.get("json") or kwargs.get("data"))) try: fixture = self.fixtures[url][method] except KeyError: pass else: return TestResponse({"headers": fixture[0], "text": fixture[1]}) # Call the method args = urlutils.parse_qsl(urlutils.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)) resp = getattr(self, callback)(**kwargs) if len(resp) == 3: status, headers, body = resp else: status, body = resp headers = {} return TestResponse({ "status_code": status, "text": body, "headers": headers, }) python-cinderclient-1.0.8/cinderclient/openstack/common/apiclient/base.py0000664000175300017540000003713112274343712030027 0ustar jenkinsjenkins00000000000000# 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 six from cinderclient.openstack.common.apiclient import exceptions from cinderclient.openstack.common.py3kcompat import urlutils from cinderclient.openstack.common 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 six.iteritems(kwargs.copy()): 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' % urlutils.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' % urlutils.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 "" % self.name class Resource(object): """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): """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 """ self.manager = manager self._info = info self._add_details(info) self._loaded = loaded def __repr__(self): reprkeys = sorted(k for k in self.__dict__.keys() if k[0] != '_' and k != 'manager') 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 six.iteritems(info): try: setattr(self, k, v) self._info[k] = v except AttributeError: # In this case we already defined the attribute on the class pass def __getattr__(self, k): if k not in self.__dict__: #NOTE(bcwaldon): disallow lazy-loading if already loaded once if not self.is_loaded(): self.get() return self.__getattr__(k) raise AttributeError(k) else: return self.__dict__[k] 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 if hasattr(self, 'id') and hasattr(other, 'id'): return self.id == other.id return self._info == other._info def is_loaded(self): return self._loaded def set_loaded(self, val): self._loaded = val python-cinderclient-1.0.8/cinderclient/openstack/common/apiclient/auth.py0000664000175300017540000001557612274343712030067 0ustar jenkinsjenkins00000000000000# Copyright 2013 OpenStack Foundation # Copyright 2013 Spanish National Research Council. # 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. # E0202: An attribute inherited from %s hide this method # pylint: disable=E0202 import abc import argparse import os import six from stevedore import extension from cinderclient.openstack.common.apiclient import exceptions _discovered_plugins = {} def discover_auth_systems(): """Discover the available auth-systems. This won't take into account the old style auth-systems. """ global _discovered_plugins _discovered_plugins = {} def add_plugin(ext): _discovered_plugins[ext.name] = ext.plugin ep_namespace = "cinderclient.openstack.common.apiclient.auth" mgr = extension.ExtensionManager(ep_namespace) mgr.map(add_plugin) def load_auth_system_opts(parser): """Load options needed by the available auth-systems into a parser. This function will try to populate the parser with options from the available plugins. """ group = parser.add_argument_group("Common auth options") BaseAuthPlugin.add_common_opts(group) for name, auth_plugin in six.iteritems(_discovered_plugins): group = parser.add_argument_group( "Auth-system '%s' options" % name, conflict_handler="resolve") auth_plugin.add_opts(group) def load_plugin(auth_system): try: plugin_class = _discovered_plugins[auth_system] except KeyError: raise exceptions.AuthSystemNotFound(auth_system) return plugin_class(auth_system=auth_system) def load_plugin_from_args(args): """Load required plugin and populate it with options. Try to guess auth system if it is not specified. Systems are tried in alphabetical order. :type args: argparse.Namespace :raises: AuthorizationFailure """ auth_system = args.os_auth_system if auth_system: plugin = load_plugin(auth_system) plugin.parse_opts(args) plugin.sufficient_options() return plugin for plugin_auth_system in sorted(six.iterkeys(_discovered_plugins)): plugin_class = _discovered_plugins[plugin_auth_system] plugin = plugin_class() plugin.parse_opts(args) try: plugin.sufficient_options() except exceptions.AuthPluginOptionsMissing: continue return plugin raise exceptions.AuthPluginOptionsMissing(["auth_system"]) @six.add_metaclass(abc.ABCMeta) class BaseAuthPlugin(object): """Base class for authentication plugins. An authentication plugin needs to override at least the authenticate method to be a valid plugin. """ auth_system = None opt_names = [] common_opt_names = [ "auth_system", "username", "password", "tenant_name", "token", "auth_url", ] def __init__(self, auth_system=None, **kwargs): self.auth_system = auth_system or self.auth_system self.opts = dict((name, kwargs.get(name)) for name in self.opt_names) @staticmethod def _parser_add_opt(parser, opt): """Add an option to parser in two variants. :param opt: option name (with underscores) """ dashed_opt = opt.replace("_", "-") env_var = "OS_%s" % opt.upper() arg_default = os.environ.get(env_var, "") arg_help = "Defaults to env[%s]." % env_var parser.add_argument( "--os-%s" % dashed_opt, metavar="<%s>" % dashed_opt, default=arg_default, help=arg_help) parser.add_argument( "--os_%s" % opt, metavar="<%s>" % dashed_opt, help=argparse.SUPPRESS) @classmethod def add_opts(cls, parser): """Populate the parser with the options for this plugin. """ for opt in cls.opt_names: # use `BaseAuthPlugin.common_opt_names` since it is never # changed in child classes if opt not in BaseAuthPlugin.common_opt_names: cls._parser_add_opt(parser, opt) @classmethod def add_common_opts(cls, parser): """Add options that are common for several plugins. """ for opt in cls.common_opt_names: cls._parser_add_opt(parser, opt) @staticmethod def get_opt(opt_name, args): """Return option name and value. :param opt_name: name of the option, e.g., "username" :param args: parsed arguments """ return (opt_name, getattr(args, "os_%s" % opt_name, None)) def parse_opts(self, args): """Parse the actual auth-system options if any. This method is expected to populate the attribute `self.opts` with a dict containing the options and values needed to make authentication. """ self.opts.update(dict(self.get_opt(opt_name, args) for opt_name in self.opt_names)) def authenticate(self, http_client): """Authenticate using plugin defined method. The method usually analyses `self.opts` and performs a request to authentication server. :param http_client: client object that needs authentication :type http_client: HTTPClient :raises: AuthorizationFailure """ self.sufficient_options() self._do_authenticate(http_client) @abc.abstractmethod def _do_authenticate(self, http_client): """Protected method for authentication. """ def sufficient_options(self): """Check if all required options are present. :raises: AuthPluginOptionsMissing """ missing = [opt for opt in self.opt_names if not self.opts.get(opt)] if missing: raise exceptions.AuthPluginOptionsMissing(missing) @abc.abstractmethod def token_and_endpoint(self, endpoint_type, service_type): """Return token and endpoint. :param service_type: Service type of the endpoint :type service_type: string :param endpoint_type: Type of endpoint. Possible values: public or publicURL, internal or internalURL, admin or adminURL :type endpoint_type: string :returns: tuple of token and endpoint strings :raises: EndpointException """ python-cinderclient-1.0.8/cinderclient/openstack/common/importutils.py0000664000175300017540000000421512274343712027535 0ustar jenkinsjenkins00000000000000# Copyright 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 related utilities and helper functions. """ import sys import traceback def import_class(import_str): """Returns a class from a string including module and class.""" mod_str, _sep, class_str = import_str.rpartition('.') try: __import__(mod_str) return getattr(sys.modules[mod_str], class_str) except (ValueError, AttributeError): raise ImportError('Class %s cannot be found (%s)' % (class_str, traceback.format_exception(*sys.exc_info()))) def import_object(import_str, *args, **kwargs): """Import a class and return an instance of it.""" return import_class(import_str)(*args, **kwargs) def import_object_ns(name_space, import_str, *args, **kwargs): """Tries to import object from default namespace. Imports a class and return an instance of it, first by trying to find the class in a default namespace, then failing back to a full path if not found in the default namespace. """ import_value = "%s.%s" % (name_space, import_str) try: return import_class(import_value)(*args, **kwargs) except ImportError: return import_class(import_str)(*args, **kwargs) def import_module(import_str): """Import a module.""" __import__(import_str) return sys.modules[import_str] def try_import(import_str, default=None): """Try to import a module and if it fails return default.""" try: return import_module(import_str) except ImportError: return default python-cinderclient-1.0.8/cinderclient/openstack/common/__init__.py0000664000175300017540000000000012274343712026665 0ustar jenkinsjenkins00000000000000python-cinderclient-1.0.8/cinderclient/openstack/common/gettextutils.py0000664000175300017540000004221712274343712027713 0ustar jenkinsjenkins00000000000000# Copyright 2012 Red Hat, Inc. # 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. """ gettext for openstack-common modules. Usual usage in an openstack.common module: from cinderclient.openstack.common.gettextutils import _ """ import copy import gettext import locale from logging import handlers import os import re from babel import localedata import six _localedir = os.environ.get('cinderclient'.upper() + '_LOCALEDIR') _t = gettext.translation('cinderclient', localedir=_localedir, fallback=True) _AVAILABLE_LANGUAGES = {} USE_LAZY = False def enable_lazy(): """Convenience function for configuring _() to use lazy gettext Call this at the start of execution to enable the gettextutils._ function to use lazy gettext functionality. This is useful if your project is importing _ directly instead of using the gettextutils.install() way of importing the _ function. """ global USE_LAZY USE_LAZY = True def _(msg): if USE_LAZY: return Message(msg, domain='cinderclient') else: if six.PY3: return _t.gettext(msg) return _t.ugettext(msg) def install(domain, lazy=False): """Install a _() function using the given translation domain. Given a translation domain, install a _() function using gettext's install() function. The main difference from gettext.install() is that we allow overriding the default localedir (e.g. /usr/share/locale) using a translation-domain-specific environment variable (e.g. NOVA_LOCALEDIR). :param domain: the translation domain :param lazy: indicates whether or not to install the lazy _() function. The lazy _() introduces a way to do deferred translation of messages by installing a _ that builds Message objects, instead of strings, which can then be lazily translated into any available locale. """ if lazy: # NOTE(mrodden): Lazy gettext functionality. # # The following introduces a deferred way to do translations on # messages in OpenStack. We override the standard _() function # and % (format string) operation to build Message objects that can # later be translated when we have more information. def _lazy_gettext(msg): """Create and return a Message object. Lazy gettext function for a given domain, it is a factory method for a project/module to get a lazy gettext function for its own translation domain (i.e. nova, glance, cinder, etc.) Message encapsulates a string so that we can translate it later when needed. """ return Message(msg, domain=domain) from six import moves moves.builtins.__dict__['_'] = _lazy_gettext else: localedir = '%s_LOCALEDIR' % domain.upper() if six.PY3: gettext.install(domain, localedir=os.environ.get(localedir)) else: gettext.install(domain, localedir=os.environ.get(localedir), unicode=True) class Message(six.text_type): """A Message object is a unicode object that can be translated. Translation of Message is done explicitly using the translate() method. For all non-translation intents and purposes, a Message is simply unicode, and can be treated as such. """ def __new__(cls, msgid, msgtext=None, params=None, domain='cinderclient', *args): """Create a new Message object. In order for translation to work gettext requires a message ID, this msgid will be used as the base unicode text. It is also possible for the msgid and the base unicode text to be different by passing the msgtext parameter. """ # If the base msgtext is not given, we use the default translation # of the msgid (which is in English) just in case the system locale is # not English, so that the base text will be in that locale by default. if not msgtext: msgtext = Message._translate_msgid(msgid, domain) # We want to initialize the parent unicode with the actual object that # would have been plain unicode if 'Message' was not enabled. msg = super(Message, cls).__new__(cls, msgtext) msg.msgid = msgid msg.domain = domain msg.params = params return msg def translate(self, desired_locale=None): """Translate this message to the desired locale. :param desired_locale: The desired locale to translate the message to, if no locale is provided the message will be translated to the system's default locale. :returns: the translated message in unicode """ translated_message = Message._translate_msgid(self.msgid, self.domain, desired_locale) if self.params is None: # No need for more translation return translated_message # This Message object may have been formatted with one or more # Message objects as substitution arguments, given either as a single # argument, part of a tuple, or as one or more values in a dictionary. # When translating this Message we need to translate those Messages too translated_params = _translate_args(self.params, desired_locale) translated_message = translated_message % translated_params return translated_message @staticmethod def _translate_msgid(msgid, domain, desired_locale=None): if not desired_locale: system_locale = locale.getdefaultlocale() # If the system locale is not available to the runtime use English if not system_locale[0]: desired_locale = 'en_US' else: desired_locale = system_locale[0] locale_dir = os.environ.get(domain.upper() + '_LOCALEDIR') lang = gettext.translation(domain, localedir=locale_dir, languages=[desired_locale], fallback=True) if six.PY3: translator = lang.gettext else: translator = lang.ugettext translated_message = translator(msgid) return translated_message def __mod__(self, other): # When we mod a Message we want the actual operation to be performed # by the parent class (i.e. unicode()), the only thing we do here is # save the original msgid and the parameters in case of a translation params = self._sanitize_mod_params(other) unicode_mod = super(Message, self).__mod__(params) modded = Message(self.msgid, msgtext=unicode_mod, params=params, domain=self.domain) return modded def _sanitize_mod_params(self, other): """Sanitize the object being modded with this Message. - Add support for modding 'None' so translation supports it - Trim the modded object, which can be a large dictionary, to only those keys that would actually be used in a translation - Snapshot the object being modded, in case the message is translated, it will be used as it was when the Message was created """ if other is None: params = (other,) elif isinstance(other, dict): params = self._trim_dictionary_parameters(other) else: params = self._copy_param(other) return params def _trim_dictionary_parameters(self, dict_param): """Return a dict that only has matching entries in the msgid.""" # NOTE(luisg): Here we trim down the dictionary passed as parameters # to avoid carrying a lot of unnecessary weight around in the message # object, for example if someone passes in Message() % locals() but # only some params are used, and additionally we prevent errors for # non-deepcopyable objects by unicoding() them. # Look for %(param) keys in msgid; # Skip %% and deal with the case where % is first character on the line keys = re.findall('(?:[^%]|^)?%\((\w*)\)[a-z]', self.msgid) # If we don't find any %(param) keys but have a %s if not keys and re.findall('(?:[^%]|^)%[a-z]', self.msgid): # Apparently the full dictionary is the parameter params = self._copy_param(dict_param) else: params = {} # Save our existing parameters as defaults to protect # ourselves from losing values if we are called through an # (erroneous) chain that builds a valid Message with # arguments, and then does something like "msg % kwds" # where kwds is an empty dictionary. src = {} if isinstance(self.params, dict): src.update(self.params) src.update(dict_param) for key in keys: params[key] = self._copy_param(src[key]) return params def _copy_param(self, param): try: return copy.deepcopy(param) except TypeError: # Fallback to casting to unicode this will handle the # python code-like objects that can't be deep-copied return six.text_type(param) def __add__(self, other): msg = _('Message objects do not support addition.') raise TypeError(msg) def __radd__(self, other): return self.__add__(other) def __str__(self): # NOTE(luisg): Logging in python 2.6 tries to str() log records, # and it expects specifically a UnicodeError in order to proceed. msg = _('Message objects do not support str() because they may ' 'contain non-ascii characters. ' 'Please use unicode() or translate() instead.') raise UnicodeError(msg) def get_available_languages(domain): """Lists the available languages for the given translation domain. :param domain: the domain to get languages for """ if domain in _AVAILABLE_LANGUAGES: return copy.copy(_AVAILABLE_LANGUAGES[domain]) localedir = '%s_LOCALEDIR' % domain.upper() find = lambda x: gettext.find(domain, localedir=os.environ.get(localedir), languages=[x]) # NOTE(mrodden): en_US should always be available (and first in case # order matters) since our in-line message strings are en_US language_list = ['en_US'] # NOTE(luisg): Babel <1.0 used a function called list(), which was # renamed to locale_identifiers() in >=1.0, the requirements master list # requires >=0.9.6, uncapped, so defensively work with both. We can remove # this check when the master list updates to >=1.0, and update all projects list_identifiers = (getattr(localedata, 'list', None) or getattr(localedata, 'locale_identifiers')) locale_identifiers = list_identifiers() for i in locale_identifiers: if find(i) is not None: language_list.append(i) # NOTE(luisg): Babel>=1.0,<1.3 has a bug where some OpenStack supported # locales (e.g. 'zh_CN', and 'zh_TW') aren't supported even though they # are perfectly legitimate locales: # https://github.com/mitsuhiko/babel/issues/37 # In Babel 1.3 they fixed the bug and they support these locales, but # they are still not explicitly "listed" by locale_identifiers(). # That is why we add the locales here explicitly if necessary so that # they are listed as supported. aliases = {'zh': 'zh_CN', 'zh_Hant_HK': 'zh_HK', 'zh_Hant': 'zh_TW', 'fil': 'tl_PH'} for (locale, alias) in six.iteritems(aliases): if locale in language_list and alias not in language_list: language_list.append(alias) _AVAILABLE_LANGUAGES[domain] = language_list return copy.copy(language_list) def translate(obj, desired_locale=None): """Gets the translated unicode representation of the given object. If the object is not translatable it is returned as-is. If the locale is None the object is translated to the system locale. :param obj: the object to translate :param desired_locale: the locale to translate the message to, if None the default system locale will be used :returns: the translated object in unicode, or the original object if it could not be translated """ message = obj if not isinstance(message, Message): # If the object to translate is not already translatable, # let's first get its unicode representation message = six.text_type(obj) if isinstance(message, Message): # Even after unicoding() we still need to check if we are # running with translatable unicode before translating return message.translate(desired_locale) return obj def _translate_args(args, desired_locale=None): """Translates all the translatable elements of the given arguments object. This method is used for translating the translatable values in method arguments which include values of tuples or dictionaries. If the object is not a tuple or a dictionary the object itself is translated if it is translatable. If the locale is None the object is translated to the system locale. :param args: the args to translate :param desired_locale: the locale to translate the args to, if None the default system locale will be used :returns: a new args object with the translated contents of the original """ if isinstance(args, tuple): return tuple(translate(v, desired_locale) for v in args) if isinstance(args, dict): translated_dict = {} for (k, v) in six.iteritems(args): translated_v = translate(v, desired_locale) translated_dict[k] = translated_v return translated_dict return translate(args, desired_locale) class TranslationHandler(handlers.MemoryHandler): """Handler that translates records before logging them. The TranslationHandler takes a locale and a target logging.Handler object to forward LogRecord objects to after translating them. This handler depends on Message objects being logged, instead of regular strings. The handler can be configured declaratively in the logging.conf as follows: [handlers] keys = translatedlog, translator [handler_translatedlog] class = handlers.WatchedFileHandler args = ('/var/log/api-localized.log',) formatter = context [handler_translator] class = openstack.common.log.TranslationHandler target = translatedlog args = ('zh_CN',) If the specified locale is not available in the system, the handler will log in the default locale. """ def __init__(self, locale=None, target=None): """Initialize a TranslationHandler :param locale: locale to use for translating messages :param target: logging.Handler object to forward LogRecord objects to after translation """ # NOTE(luisg): In order to allow this handler to be a wrapper for # other handlers, such as a FileHandler, and still be able to # configure it using logging.conf, this handler has to extend # MemoryHandler because only the MemoryHandlers' logging.conf # parsing is implemented such that it accepts a target handler. handlers.MemoryHandler.__init__(self, capacity=0, target=target) self.locale = locale def setFormatter(self, fmt): self.target.setFormatter(fmt) def emit(self, record): # We save the message from the original record to restore it # after translation, so other handlers are not affected by this original_msg = record.msg original_args = record.args try: self._translate_and_log_record(record) finally: record.msg = original_msg record.args = original_args def _translate_and_log_record(self, record): record.msg = translate(record.msg, self.locale) # In addition to translating the message, we also need to translate # arguments that were passed to the log method that were not part # of the main message e.g., log.info(_('Some message %s'), this_one)) record.args = _translate_args(record.args, self.locale) self.target.emit(record) python-cinderclient-1.0.8/cinderclient/openstack/common/strutils.py0000664000175300017540000001660012274343712027034 0ustar jenkinsjenkins00000000000000# Copyright 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. """ System-level utilities and helper functions. """ import re import sys import unicodedata import six from cinderclient.openstack.common.gettextutils import _ # Used for looking up extensions of text # to their 'multiplied' byte amount BYTE_MULTIPLIERS = { '': 1, 't': 1024 ** 4, 'g': 1024 ** 3, 'm': 1024 ** 2, 'k': 1024, } BYTE_REGEX = re.compile(r'(^-?\d+)(\D*)') TRUE_STRINGS = ('1', 't', 'true', 'on', 'y', 'yes') FALSE_STRINGS = ('0', 'f', 'false', 'off', 'n', 'no') SLUGIFY_STRIP_RE = re.compile(r"[^\w\s-]") SLUGIFY_HYPHENATE_RE = re.compile(r"[-\s]+") def int_from_bool_as_string(subject): """Interpret a string as a boolean and return either 1 or 0. Any string value in: ('True', 'true', 'On', 'on', '1') is interpreted as a boolean True. Useful for JSON-decoded stuff and config file parsing """ return bool_from_string(subject) and 1 or 0 def bool_from_string(subject, strict=False, default=False): """Interpret a string as a boolean. A case-insensitive match is performed such that strings matching 't', 'true', 'on', 'y', 'yes', or '1' are considered True and, when `strict=False`, anything else returns the value specified by 'default'. Useful for JSON-decoded stuff and config file parsing. If `strict=True`, unrecognized values, including None, will raise a ValueError which is useful when parsing values passed in from an API call. Strings yielding False are 'f', 'false', 'off', 'n', 'no', or '0'. """ if not isinstance(subject, six.string_types): subject = str(subject) lowered = subject.strip().lower() if lowered in TRUE_STRINGS: return True elif lowered in FALSE_STRINGS: return False elif strict: acceptable = ', '.join( "'%s'" % s for s in sorted(TRUE_STRINGS + FALSE_STRINGS)) msg = _("Unrecognized value '%(val)s', acceptable values are:" " %(acceptable)s") % {'val': subject, 'acceptable': acceptable} raise ValueError(msg) else: return default def safe_decode(text, incoming=None, errors='strict'): """Decodes incoming str using `incoming` if they're not already unicode. :param incoming: Text's current encoding :param errors: Errors handling policy. See here for valid values http://docs.python.org/2/library/codecs.html :returns: text or a unicode `incoming` encoded representation of it. :raises TypeError: If text is not an instance of str """ if not isinstance(text, six.string_types): raise TypeError("%s can't be decoded" % type(text)) if isinstance(text, six.text_type): return text if not incoming: incoming = (sys.stdin.encoding or sys.getdefaultencoding()) try: return text.decode(incoming, errors) except UnicodeDecodeError: # Note(flaper87) If we get here, it means that # sys.stdin.encoding / sys.getdefaultencoding # didn't return a suitable encoding to decode # text. This happens mostly when global LANG # var is not set correctly and there's no # default encoding. In this case, most likely # python will use ASCII or ANSI encoders as # default encodings but they won't be capable # of decoding non-ASCII characters. # # Also, UTF-8 is being used since it's an ASCII # extension. return text.decode('utf-8', errors) def safe_encode(text, incoming=None, encoding='utf-8', errors='strict'): """Encodes incoming str/unicode using `encoding`. If incoming is not specified, text is expected to be encoded with current python's default encoding. (`sys.getdefaultencoding`) :param incoming: Text's current encoding :param encoding: Expected encoding for text (Default UTF-8) :param errors: Errors handling policy. See here for valid values http://docs.python.org/2/library/codecs.html :returns: text or a bytestring `encoding` encoded representation of it. :raises TypeError: If text is not an instance of str """ if not isinstance(text, six.string_types): raise TypeError("%s can't be encoded" % type(text)) if not incoming: incoming = (sys.stdin.encoding or sys.getdefaultencoding()) if isinstance(text, six.text_type): if six.PY3: return text.encode(encoding, errors).decode(incoming) else: return text.encode(encoding, errors) elif text and encoding != incoming: # Decode text before encoding it with `encoding` text = safe_decode(text, incoming, errors) if six.PY3: return text.encode(encoding, errors).decode(incoming) else: return text.encode(encoding, errors) return text def to_bytes(text, default=0): """Converts a string into an integer of bytes. Looks at the last characters of the text to determine what conversion is needed to turn the input text into a byte number. Supports "B, K(B), M(B), G(B), and T(B)". (case insensitive) :param text: String input for bytes size conversion. :param default: Default return value when text is blank. """ match = BYTE_REGEX.search(text) if match: magnitude = int(match.group(1)) mult_key_org = match.group(2) if not mult_key_org: return magnitude elif text: msg = _('Invalid string format: %s') % text raise TypeError(msg) else: return default mult_key = mult_key_org.lower().replace('b', '', 1) multiplier = BYTE_MULTIPLIERS.get(mult_key) if multiplier is None: msg = _('Unknown byte multiplier: %s') % mult_key_org raise TypeError(msg) return magnitude * multiplier def to_slug(value, incoming=None, errors="strict"): """Normalize string. Convert to lowercase, remove non-word characters, and convert spaces to hyphens. Inspired by Django's `slugify` filter. :param value: Text to slugify :param incoming: Text's current encoding :param errors: Errors handling policy. See here for valid values http://docs.python.org/2/library/codecs.html :returns: slugified unicode representation of `value` :raises TypeError: If text is not an instance of str """ value = safe_decode(value, incoming, errors) # NOTE(aababilov): no need to use safe_(encode|decode) here: # encodings are always "ascii", error handling is always "ignore" # and types are always known (first: unicode; second: str) value = unicodedata.normalize("NFKD", value).encode( "ascii", "ignore").decode("ascii") value = SLUGIFY_STRIP_RE.sub("", value).strip().lower() return SLUGIFY_HYPHENATE_RE.sub("-", value) python-cinderclient-1.0.8/cinderclient/openstack/common/py3kcompat/0000775000175300017540000000000012274343764026667 5ustar jenkinsjenkins00000000000000python-cinderclient-1.0.8/cinderclient/openstack/common/py3kcompat/urlutils.py0000664000175300017540000000356012274343712031121 0ustar jenkinsjenkins00000000000000# # Copyright 2013 Canonical Ltd. # 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. # """ Python2/Python3 compatibility layer for OpenStack """ import six if six.PY3: # python3 import urllib.error import urllib.parse import urllib.request urlencode = urllib.parse.urlencode urljoin = urllib.parse.urljoin quote = urllib.parse.quote quote_plus = urllib.parse.quote_plus parse_qsl = urllib.parse.parse_qsl unquote = urllib.parse.unquote unquote_plus = urllib.parse.unquote_plus urlparse = urllib.parse.urlparse urlsplit = urllib.parse.urlsplit urlunsplit = urllib.parse.urlunsplit SplitResult = urllib.parse.SplitResult urlopen = urllib.request.urlopen URLError = urllib.error.URLError pathname2url = urllib.request.pathname2url else: # python2 import urllib import urllib2 import urlparse urlencode = urllib.urlencode quote = urllib.quote quote_plus = urllib.quote_plus unquote = urllib.unquote unquote_plus = urllib.unquote_plus parse = urlparse parse_qsl = parse.parse_qsl urljoin = parse.urljoin urlparse = parse.urlparse urlsplit = parse.urlsplit urlunsplit = parse.urlunsplit SplitResult = parse.SplitResult urlopen = urllib2.urlopen URLError = urllib2.URLError pathname2url = urllib.pathname2url python-cinderclient-1.0.8/cinderclient/openstack/common/py3kcompat/__init__.py0000664000175300017540000000000012274343712030757 0ustar jenkinsjenkins00000000000000python-cinderclient-1.0.8/cinderclient/service_catalog.py0000664000175300017540000000646012274343712025021 0ustar jenkinsjenkins00000000000000# Copyright (c) 2011 OpenStack Foundation # 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. import cinderclient.exceptions class ServiceCatalog(object): """Helper methods for dealing with a Keystone Service Catalog.""" def __init__(self, resource_dict): self.catalog = resource_dict def get_token(self): return self.catalog['access']['token']['id'] def url_for(self, attr=None, filter_value=None, service_type=None, endpoint_type='publicURL', service_name=None, volume_service_name=None): """Fetch the public URL from the Compute service for a particular endpoint attribute. If none given, return the first. See tests for sample service catalog. """ matching_endpoints = [] if 'endpoints' in self.catalog: # We have a bastardized service catalog. Treat it special. :/ for endpoint in self.catalog['endpoints']: if not filter_value or endpoint[attr] == filter_value: matching_endpoints.append(endpoint) if not matching_endpoints: raise cinderclient.exceptions.EndpointNotFound() # We don't always get a service catalog back ... if 'serviceCatalog' not in self.catalog['access']: return None # Full catalog ... catalog = self.catalog['access']['serviceCatalog'] for service in catalog: # NOTE(thingee): For backwards compatibility, if they have v2 # enabled and the service_type is set to 'volume', go ahead and # accept that. skip_service_type_check = False if service_type == 'volumev2' and service['type'] == 'volume': version = service['endpoints'][0]['publicURL'].split('/')[3] if version == 'v2': skip_service_type_check = True if (not skip_service_type_check and service.get("type") != service_type): continue if (volume_service_name and service_type in ('volume', 'volumev2') and service.get('name') != volume_service_name): continue endpoints = service['endpoints'] for endpoint in endpoints: if not filter_value or endpoint.get(attr) == filter_value: endpoint["serviceName"] = service.get("name") matching_endpoints.append(endpoint) if not matching_endpoints: raise cinderclient.exceptions.EndpointNotFound() elif len(matching_endpoints) > 1: raise cinderclient.exceptions.AmbiguousEndpoints( endpoints=matching_endpoints) else: return matching_endpoints[0][endpoint_type] python-cinderclient-1.0.8/cinderclient/utils.py0000664000175300017540000002210212274343712023016 0ustar jenkinsjenkins00000000000000# 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 __future__ import print_function import os import re import sys import uuid import six import prettytable from cinderclient import exceptions from cinderclient.openstack.common import strutils def arg(*args, **kwargs): """Decorator for CLI args.""" def _decorator(func): add_arg(func, *args, **kwargs) return func return _decorator def env(*vars, **kwargs): """ returns the first environment variable set if none are non-empty, defaults to '' or keyword arg default """ for v in vars: value = os.environ.get(v, None) if value: return value return kwargs.get('default', '') def add_arg(f, *args, **kwargs): """Bind CLI arguments to a shell.py `do_foo` function.""" if not hasattr(f, 'arguments'): f.arguments = [] # NOTE(sirp): avoid dups that can occur when the module is shared across # tests. if (args, kwargs) not in f.arguments: # Because of the semantics of decorator composition if we just append # to the options list positional options will appear to be backwards. f.arguments.insert(0, (args, kwargs)) def add_resource_manager_extra_kwargs_hook(f, hook): """Adds hook to bind CLI arguments to ResourceManager calls. The `do_foo` calls in shell.py will receive CLI args and then in turn pass them through to the ResourceManager. Before passing through the args, the hooks registered here will be called, giving us a chance to add extra kwargs (taken from the command-line) to what's passed to the ResourceManager. """ if not hasattr(f, 'resource_manager_kwargs_hooks'): f.resource_manager_kwargs_hooks = [] names = [h.__name__ for h in f.resource_manager_kwargs_hooks] if hook.__name__ not in names: f.resource_manager_kwargs_hooks.append(hook) def get_resource_manager_extra_kwargs(f, args, allow_conflicts=False): """Return extra_kwargs by calling resource manager kwargs hooks.""" hooks = getattr(f, "resource_manager_kwargs_hooks", []) extra_kwargs = {} for hook in hooks: hook_name = hook.__name__ hook_kwargs = hook(args) conflicting_keys = set(hook_kwargs.keys()) & set(extra_kwargs.keys()) if conflicting_keys and not allow_conflicts: msg = ("Hook '%(hook_name)s' is attempting to redefine attributes " "'%(conflicting_keys)s'" % { 'hook_name': hook_name, 'conflicting_keys': conflicting_keys }) raise Exception(msg) extra_kwargs.update(hook_kwargs) return extra_kwargs def unauthenticated(f): """ Adds 'unauthenticated' attribute to decorated function. Usage: @unauthenticated def mymethod(f): ... """ f.unauthenticated = True return f def isunauthenticated(f): """ Checks to see if the function is marked as not requiring authentication with the @unauthenticated decorator. Returns True if decorator is set to True, False otherwise. """ return getattr(f, 'unauthenticated', False) def service_type(stype): """ Adds 'service_type' attribute to decorated function. Usage: @service_type('volume') def mymethod(f): ... """ def inner(f): f.service_type = stype return f return inner def get_service_type(f): """ Retrieves service type from function """ return getattr(f, 'service_type', None) def pretty_choice_list(l): return ', '.join("'%s'" % i for i in l) def _print(pt, order): if sys.version_info >= (3, 0): print(pt.get_string(sortby=order)) else: print(strutils.safe_encode(pt.get_string(sortby=order))) def print_list(objs, fields, formatters={}, order_by=None): mixed_case_fields = ['serverId'] pt = prettytable.PrettyTable([f for f in fields], caching=False) pt.aligns = ['l' for f in fields] for o in objs: row = [] for field in fields: if field in formatters: row.append(formatters[field](o)) else: if field in mixed_case_fields: field_name = field.replace(' ', '_') else: field_name = field.lower().replace(' ', '_') if type(o) == dict and field in o: data = o[field] else: data = getattr(o, field_name, '') row.append(data) pt.add_row(row) if order_by is None: order_by = fields[0] _print(pt, order_by) def print_dict(d, property="Property"): pt = prettytable.PrettyTable([property, 'Value'], caching=False) pt.aligns = ['l', 'l'] [pt.add_row(list(r)) for r in six.iteritems(d)] _print(pt, property) def find_resource(manager, name_or_id): """Helper for the _find_* methods.""" # first try to get entity as integer id try: if isinstance(name_or_id, int) or name_or_id.isdigit(): return manager.get(int(name_or_id)) except exceptions.NotFound: pass if sys.version_info <= (3, 0): name_or_id = strutils.safe_decode(name_or_id) # now try to get entity as uuid try: uuid.UUID(name_or_id) return manager.get(name_or_id) except (ValueError, exceptions.NotFound): pass try: try: return manager.find(human_id=name_or_id) except exceptions.NotFound: pass # finally try to find entity by name try: return manager.find(name=name_or_id) except exceptions.NotFound: try: return manager.find(display_name=name_or_id) except (UnicodeDecodeError, exceptions.NotFound): try: # Volumes does not have name, but display_name return manager.find(display_name=name_or_id) except exceptions.NotFound: msg = "No %s with a name or ID of '%s' exists." % \ (manager.resource_class.__name__.lower(), name_or_id) raise exceptions.CommandError(msg) except exceptions.NoUniqueMatch: msg = ("Multiple %s matches found for '%s', use an ID to be more" " specific." % (manager.resource_class.__name__.lower(), name_or_id)) raise exceptions.CommandError(msg) def find_volume(cs, volume): """Get a volume by name or ID.""" return find_resource(cs.volumes, volume) def _format_servers_list_networks(server): output = [] for (network, addresses) in list(server.networks.items()): if len(addresses) == 0: continue addresses_csv = ', '.join(addresses) group = "%s=%s" % (network, addresses_csv) output.append(group) return '; '.join(output) class HookableMixin(object): """Mixin so classes can register and run hooks.""" _hooks_map = {} @classmethod def add_hook(cls, hook_type, hook_func): 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): hook_funcs = cls._hooks_map.get(hook_type) or [] for hook_func in hook_funcs: hook_func(*args, **kwargs) def safe_issubclass(*args): """Like issubclass, but will just return False if not a class.""" try: if issubclass(*args): return True except TypeError: pass return False def import_class(import_str): """Returns a class from a string including module and class.""" mod_str, _sep, class_str = import_str.rpartition('.') __import__(mod_str) return getattr(sys.modules[mod_str], class_str) _slugify_strip_re = re.compile(r'[^\w\s-]') _slugify_hyphenate_re = re.compile(r'[-\s]+') # http://code.activestate.com/recipes/ # 577257-slugify-make-a-string-usable-in-a-url-or-filename/ def slugify(value): """ Normalizes string, converts to lowercase, removes non-alpha characters, and converts spaces to hyphens. From Django's "django/template/defaultfilters.py". """ import unicodedata if not isinstance(value, six.text_type): value = six.text_type(value) value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore') value = six.text_type(_slugify_strip_re.sub('', value).strip().lower()) return _slugify_hyphenate_re.sub('-', value) python-cinderclient-1.0.8/cinderclient/v1/0000775000175300017540000000000012274343764021644 5ustar jenkinsjenkins00000000000000python-cinderclient-1.0.8/cinderclient/v1/volume_encryption_types.py0000664000175300017540000000677112274343712027227 0ustar jenkinsjenkins00000000000000# Copyright (c) 2013 The Johns Hopkins University/Applied Physics Laboratory # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Volume Encryption Type interface """ from cinderclient import base class VolumeEncryptionType(base.Resource): """ A Volume Encryption Type is a collection of settings used to conduct encryption for a specific volume type. """ def __repr__(self): return "" % self.name class VolumeEncryptionTypeManager(base.ManagerWithFind): """ Manage :class: `VolumeEncryptionType` resources. """ resource_class = VolumeEncryptionType def list(self, search_opts=None): """ List all volume encryption types. :param volume_types: a list of volume types :return: a list of :class: VolumeEncryptionType instances """ # Since the encryption type is a volume type extension, we cannot get # all encryption types without going through all volume types. volume_types = self.api.volume_types.list() encryption_types = [] for volume_type in volume_types: encryption_type = self._get("/types/%s/encryption" % base.getid(volume_type)) if hasattr(encryption_type, 'volume_type_id'): encryption_types.append(encryption_type) return encryption_types def get(self, volume_type): """ Get the volume encryption type for the specified volume type. :param volume_type: the volume type to query :return: an instance of :class: VolumeEncryptionType """ return self._get("/types/%s/encryption" % base.getid(volume_type)) def create(self, volume_type, specs): """ Create a new encryption type for the specified volume type. :param volume_type: the volume type on which to add an encryption type :param specs: the encryption type specifications to add :return: an instance of :class: VolumeEncryptionType """ body = {'encryption': specs} return self._create("/types/%s/encryption" % base.getid(volume_type), body, "encryption") def update(self, volume_type, specs): """ Update the encryption type information for the specified volume type. :param volume_type: the volume type whose encryption type information must be updated :param specs: the encryption type specifications to update :return: an instance of :class: VolumeEncryptionType """ raise NotImplementedError() def delete(self, volume_type): """ Delete the encryption type information for the specified volume type. :param volume_type: the volume type whose encryption type information must be deleted """ return self._delete("/types/%s/encryption/provider" % base.getid(volume_type)) python-cinderclient-1.0.8/cinderclient/v1/volume_backups.py0000664000175300017540000000477412274343712025242 0ustar jenkinsjenkins00000000000000# Copyright (C) 2013 Hewlett-Packard Development Company, L.P. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Volume Backups interface (1.1 extension). """ from cinderclient import base class VolumeBackup(base.Resource): """A volume backup is a block level backup of a volume.""" def __repr__(self): return "" % self.id def delete(self): """Delete this volume backup.""" return self.manager.delete(self) class VolumeBackupManager(base.ManagerWithFind): """Manage :class:`VolumeBackup` resources.""" resource_class = VolumeBackup def create(self, volume_id, container=None, name=None, description=None): """Create a volume backup. :param volume_id: The ID of the volume to backup. :param container: The name of the backup service container. :param name: The name of the backup. :param description: The description of the backup. :rtype: :class:`VolumeBackup` """ body = {'backup': {'volume_id': volume_id, 'container': container, 'name': name, 'description': description}} return self._create('/backups', body, 'backup') def get(self, backup_id): """Show details of a volume backup. :param backup_id: The ID of the backup to display. :rtype: :class:`VolumeBackup` """ return self._get("/backups/%s" % backup_id, "backup") def list(self, detailed=True): """Get a list of all volume backups. :rtype: list of :class:`VolumeBackup` """ if detailed is True: return self._list("/backups/detail", "backups") else: return self._list("/backups", "backups") def delete(self, backup): """Delete a volume backup. :param backup: The :class:`VolumeBackup` to delete. """ self._delete("/backups/%s" % base.getid(backup)) python-cinderclient-1.0.8/cinderclient/v1/volume_transfers.py0000664000175300017540000000542512274343712025613 0ustar jenkinsjenkins00000000000000# Copyright (C) 2013 Hewlett-Packard Development Company, L.P. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Volume transfer interface (1.1 extension). """ from cinderclient import base class VolumeTransfer(base.Resource): """Transfer a volume from one tenant to another""" def __repr__(self): return "" % self.id def delete(self): """Delete this volume transfer.""" return self.manager.delete(self) class VolumeTransferManager(base.ManagerWithFind): """Manage :class:`VolumeTransfer` resources.""" resource_class = VolumeTransfer def create(self, volume_id, name=None): """Create a volume transfer. :param volume_id: The ID of the volume to transfer. :param name: The name of the transfer. :rtype: :class:`VolumeTransfer` """ body = {'transfer': {'volume_id': volume_id, 'name': name}} return self._create('/os-volume-transfer', body, 'transfer') def accept(self, transfer_id, auth_key): """Accept a volume transfer. :param transfer_id: The ID of the trasnfer to accept. :param auth_key: The auth_key of the transfer. :rtype: :class:`VolumeTransfer` """ body = {'accept': {'auth_key': auth_key}} return self._create('/os-volume-transfer/%s/accept' % transfer_id, body, 'transfer') def get(self, transfer_id): """Show details of a volume transfer. :param transfer_id: The ID of the volume transfer to display. :rtype: :class:`VolumeTransfer` """ return self._get("/os-volume-transfer/%s" % transfer_id, "transfer") def list(self, detailed=True, search_opts=None): """Get a list of all volume transfer. :rtype: list of :class:`VolumeTransfer` """ if detailed is True: return self._list("/os-volume-transfer/detail", "transfers") else: return self._list("/os-volume-transfer", "transfers") def delete(self, transfer_id): """Delete a volume transfer. :param transfer_id: The :class:`VolumeTransfer` to delete. """ self._delete("/os-volume-transfer/%s" % base.getid(transfer_id)) python-cinderclient-1.0.8/cinderclient/v1/limits.py0000664000175300017540000000534012274343712023512 0ustar jenkinsjenkins00000000000000# Copyright 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 cinderclient import base class Limits(base.Resource): """A collection of RateLimit and AbsoluteLimit objects.""" def __repr__(self): return "" @property def absolute(self): for (name, value) in list(self._info['absolute'].items()): yield AbsoluteLimit(name, value) @property def rate(self): for group in self._info['rate']: uri = group['uri'] regex = group['regex'] for rate in group['limit']: yield RateLimit(rate['verb'], uri, regex, rate['value'], rate['remaining'], rate['unit'], rate['next-available']) class RateLimit(object): """Data model that represents a flattened view of a single rate limit.""" def __init__(self, verb, uri, regex, value, remain, unit, next_available): self.verb = verb self.uri = uri self.regex = regex self.value = value self.remain = remain self.unit = unit self.next_available = next_available def __eq__(self, other): return self.uri == other.uri \ and self.regex == other.regex \ and self.value == other.value \ and self.verb == other.verb \ and self.remain == other.remain \ and self.unit == other.unit \ and self.next_available == other.next_available def __repr__(self): return "" % (self.verb, self.uri) class AbsoluteLimit(object): """Data model that represents a single absolute limit.""" def __init__(self, name, value): self.name = name self.value = value def __eq__(self, other): return self.value == other.value and self.name == other.name def __repr__(self): return "" % (self.name) class LimitsManager(base.Manager): """Manager object used to interact with limits resource.""" resource_class = Limits def get(self): """ Get a specific extension. :rtype: :class:`Limits` """ return self._get("/limits", "limits") python-cinderclient-1.0.8/cinderclient/v1/qos_specs.py0000664000175300017540000001127712274343712024216 0ustar jenkinsjenkins00000000000000# Copyright (c) 2013 eBay Inc. # Copyright (c) OpenStack LLC. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. """ QoS Specs interface. """ from cinderclient import base class QoSSpecs(base.Resource): """QoS specs entity represents quality-of-service parameters/requirements. A QoS specs is a set of parameters or requirements for quality-of-service purpose, which can be associated with volume types (for now). In future, QoS specs may be extended to be associated other entities, such as single volume. """ def __repr__(self): return "" % self.name def delete(self): return self.manager.delete(self) class QoSSpecsManager(base.ManagerWithFind): """ Manage :class:`QoSSpecs` resources. """ resource_class = QoSSpecs def list(self): """Get a list of all qos specs. :rtype: list of :class:`QoSSpecs`. """ return self._list("/qos-specs", "qos_specs") def get(self, qos_specs): """Get a specific qos specs. :param qos_specs: The ID of the :class:`QoSSpecs` to get. :rtype: :class:`QoSSpecs` """ return self._get("/qos-specs/%s" % base.getid(qos_specs), "qos_specs") def delete(self, qos_specs, force=False): """Delete a specific qos specs. :param qos_specs: The ID of the :class:`QoSSpecs` to be removed. :param force: Flag that indicates whether to delete target qos specs if it was in-use. """ self._delete("/qos-specs/%s?force=%s" % (base.getid(qos_specs), force)) def create(self, name, specs): """Create a qos specs. :param name: Descriptive name of the qos specs, must be unique :param specs: A dict of key/value pairs to be set :rtype: :class:`QoSSpecs` """ body = { "qos_specs": { "name": name, } } body["qos_specs"].update(specs) return self._create("/qos-specs", body, "qos_specs") def set_keys(self, qos_specs, specs): """Update a qos specs with new specifications. :param qos_specs: The ID of qos specs :param specs: A dict of key/value pairs to be set :rtype: :class:`QoSSpecs` """ body = { "qos_specs": {} } body["qos_specs"].update(specs) return self._update("/qos-specs/%s" % qos_specs, body) def unset_keys(self, qos_specs, specs): """Update a qos specs with new specifications. :param qos_specs: The ID of qos specs :param specs: A list of key to be unset :rtype: :class:`QoSSpecs` """ body = {'keys': specs} return self._update("/qos-specs/%s/delete_keys" % qos_specs, body) def get_associations(self, qos_specs): """Get associated entities of a qos specs. :param qos_specs: The id of the :class: `QoSSpecs` :return: a list of entities that associated with specific qos specs. """ return self._list("/qos-specs/%s/associations" % base.getid(qos_specs), "qos_associations") def associate(self, qos_specs, vol_type_id): """Associate a volume type with specific qos specs. :param qos_specs: The qos specs to be associated with :param vol_type_id: The volume type id to be associated with """ self.api.client.get("/qos-specs/%s/associate?vol_type_id=%s" % (base.getid(qos_specs), vol_type_id)) def disassociate(self, qos_specs, vol_type_id): """Disassociate qos specs from volume type. :param qos_specs: The qos specs to be associated with :param vol_type_id: The volume type id to be associated with """ self.api.client.get("/qos-specs/%s/disassociate?vol_type_id=%s" % (base.getid(qos_specs), vol_type_id)) def disassociate_all(self, qos_specs): """Disassociate all entities from specific qos specs. :param qos_specs: The qos specs to be associated with """ self.api.client.get("/qos-specs/%s/disassociate_all" % base.getid(qos_specs)) python-cinderclient-1.0.8/cinderclient/v1/services.py0000664000175300017540000000353612274343712024041 0ustar jenkinsjenkins00000000000000# Copyright (c) 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ service interface """ from cinderclient import base class Service(base.Resource): def __repr__(self): return "" % self.service class ServiceManager(base.ManagerWithFind): resource_class = Service def list(self, host=None, binary=None): """ Describes service list for host. :param host: destination host name. :param binary: service binary. """ url = "/os-services" filters = [] if host: filters.append("host=%s" % host) if binary: filters.append("binary=%s" % binary) if filters: url = "%s?%s" % (url, "&".join(filters)) return self._list(url, "services") def enable(self, host, binary): """Enable the service specified by hostname and binary.""" body = {"host": host, "binary": binary} result = self._update("/os-services/enable", body) return self.resource_class(self, result) def disable(self, host, binary): """Enable the service specified by hostname and binary.""" body = {"host": host, "binary": binary} result = self._update("/os-services/disable", body) return self.resource_class(self, result) python-cinderclient-1.0.8/cinderclient/v1/quota_classes.py0000664000175300017540000000276712274343712025071 0ustar jenkinsjenkins00000000000000# Copyright (c) 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 import base class QuotaClassSet(base.Resource): @property def id(self): """QuotaClassSet does not have a 'id' attribute but base.Resource needs it to self-refresh and QuotaSet is indexed by class_name. """ return self.class_name def update(self, *args, **kwargs): self.manager.update(self.class_name, *args, **kwargs) class QuotaClassSetManager(base.Manager): resource_class = QuotaClassSet def get(self, class_name): return self._get("/os-quota-class-sets/%s" % (class_name), "quota_class_set") def update(self, class_name, **updates): body = {'quota_class_set': {'class_name': class_name}} for update in updates: body['quota_class_set'][update] = updates[update] self._update('/os-quota-class-sets/%s' % (class_name), body) python-cinderclient-1.0.8/cinderclient/v1/client.py0000664000175300017540000001047112274343712023470 0ustar jenkinsjenkins00000000000000# 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 client from cinderclient.v1 import availability_zones from cinderclient.v1 import limits from cinderclient.v1 import qos_specs from cinderclient.v1 import quota_classes from cinderclient.v1 import quotas from cinderclient.v1 import services from cinderclient.v1 import volumes from cinderclient.v1 import volume_snapshots from cinderclient.v1 import volume_types from cinderclient.v1 import volume_encryption_types from cinderclient.v1 import volume_backups from cinderclient.v1 import volume_backups_restore from cinderclient.v1 import volume_transfers class Client(object): """ Top-level object to access the OpenStack Volume API. Create an instance with your creds:: >>> client = Client(USERNAME, PASSWORD, PROJECT_ID, AUTH_URL) Then call methods on its managers:: >>> client.volumes.list() ... """ def __init__(self, username, api_key, project_id=None, auth_url='', insecure=False, timeout=None, tenant_id=None, proxy_tenant_id=None, proxy_token=None, region_name=None, endpoint_type='publicURL', extensions=None, service_type='volume', service_name=None, volume_service_name=None, retries=None, http_log_debug=False, cacert=None): # FIXME(comstud): Rename the api_key argument above when we # know it's not being used as keyword argument password = api_key self.limits = limits.LimitsManager(self) # extensions self.volumes = volumes.VolumeManager(self) self.volume_snapshots = volume_snapshots.SnapshotManager(self) self.volume_types = volume_types.VolumeTypeManager(self) self.volume_encryption_types = \ volume_encryption_types.VolumeEncryptionTypeManager(self) self.qos_specs = qos_specs.QoSSpecsManager(self) self.quota_classes = quota_classes.QuotaClassSetManager(self) self.quotas = quotas.QuotaSetManager(self) self.backups = volume_backups.VolumeBackupManager(self) self.restores = volume_backups_restore.VolumeBackupRestoreManager(self) self.transfers = volume_transfers.VolumeTransferManager(self) self.services = services.ServiceManager(self) self.availability_zones = \ availability_zones.AvailabilityZoneManager(self) # Add in any extensions... if extensions: for extension in extensions: if extension.manager_class: setattr(self, extension.name, extension.manager_class(self)) self.client = client.HTTPClient( username, password, project_id, 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, retries=retries, http_log_debug=http_log_debug, cacert=cacert) def authenticate(self): """ Authenticate against the server. Normally this is called automatically when you first access the API, but you can call this method to force authentication right now. Returns on success; raises :exc:`exceptions.Unauthorized` if the credentials are wrong. """ self.client.authenticate() def get_volume_api_version_from_endpoint(self): return self.client.get_volume_api_version_from_endpoint() python-cinderclient-1.0.8/cinderclient/v1/volume_backups_restore.py0000664000175300017540000000304612274343712026774 0ustar jenkinsjenkins00000000000000# Copyright (C) 2013 Hewlett-Packard Development Company, L.P. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Volume Backups Restore interface (1.1 extension). This is part of the Volume Backups interface. """ from cinderclient import base class VolumeBackupsRestore(base.Resource): """A Volume Backups Restore represents a restore operation.""" def __repr__(self): return "" % self.volume_id class VolumeBackupRestoreManager(base.Manager): """Manage :class:`VolumeBackupsRestore` resources.""" resource_class = VolumeBackupsRestore def restore(self, backup_id, volume_id=None): """Restore a backup to a volume. :param backup_id: The ID of the backup to restore. :param volume_id: The ID of the volume to restore the backup to. :rtype: :class:`Restore` """ body = {'restore': {'volume_id': volume_id}} return self._create("/backups/%s/restore" % backup_id, body, "restore") python-cinderclient-1.0.8/cinderclient/v1/volume_types.py0000664000175300017540000000663412274343712024753 0ustar jenkinsjenkins00000000000000# Copyright (c) 2011 Rackspace US, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. """ Volume Type interface. """ from cinderclient import base class VolumeType(base.Resource): """ A Volume Type is the type of volume to be created """ def __repr__(self): return "" % self.name def get_keys(self): """ Get extra specs from a volume type. :param vol_type: The :class:`VolumeType` to get extra specs from """ _resp, body = self.manager.api.client.get( "/types/%s/extra_specs" % base.getid(self)) return body["extra_specs"] def set_keys(self, metadata): """ Set extra specs on a volume type. :param type : The :class:`VolumeType` to set extra spec on :param metadata: A dict of key/value pairs to be set """ body = {'extra_specs': metadata} return self.manager._create( "/types/%s/extra_specs" % base.getid(self), body, "extra_specs", return_raw=True) def unset_keys(self, keys): """ Unset extra specs on a volume type. :param type_id: The :class:`VolumeType` to unset extra spec on :param keys: A list of keys to be unset """ # NOTE(jdg): This wasn't actually doing all of the keys before # the return in the loop resulted in ony ONE key being unset. # since on success the return was NONE, we'll only interrupt the loop # and return if there's an error resp = None for k in keys: resp = self.manager._delete( "/types/%s/extra_specs/%s" % ( base.getid(self), k)) if resp is not None: return resp class VolumeTypeManager(base.ManagerWithFind): """ Manage :class:`VolumeType` resources. """ resource_class = VolumeType def list(self, search_opts=None): """ Get a list of all volume types. :rtype: list of :class:`VolumeType`. """ return self._list("/types", "volume_types") def get(self, volume_type): """ Get a specific volume type. :param volume_type: The ID of the :class:`VolumeType` to get. :rtype: :class:`VolumeType` """ return self._get("/types/%s" % base.getid(volume_type), "volume_type") def delete(self, volume_type): """ Delete a specific volume_type. :param volume_type: The ID of the :class:`VolumeType` to get. """ self._delete("/types/%s" % base.getid(volume_type)) def create(self, name): """ Create a volume type. :param name: Descriptive name of the volume type :rtype: :class:`VolumeType` """ body = { "volume_type": { "name": name, } } return self._create("/types", body, "volume_type") python-cinderclient-1.0.8/cinderclient/v1/__init__.py0000664000175300017540000000126712274343712023754 0ustar jenkinsjenkins00000000000000# Copyright (c) 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.v1.client import Client # noqa python-cinderclient-1.0.8/cinderclient/v1/availability_zones.py0000664000175300017540000000263212274343712026102 0ustar jenkinsjenkins00000000000000# Copyright 2011-2013 OpenStack Foundation # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Availability Zone interface (v1 extension)""" from cinderclient import base class AvailabilityZone(base.Resource): NAME_ATTR = 'display_name' def __repr__(self): return "" % self.zoneName class AvailabilityZoneManager(base.ManagerWithFind): """Manage :class:`AvailabilityZone` resources.""" resource_class = AvailabilityZone def list(self, detailed=False): """Get a list of all availability zones :rtype: list of :class:`AvailabilityZone` """ if detailed is True: return self._list("/os-availability-zone/detail", "availabilityZoneInfo") else: return self._list("/os-availability-zone", "availabilityZoneInfo") python-cinderclient-1.0.8/cinderclient/v1/contrib/0000775000175300017540000000000012274343764023304 5ustar jenkinsjenkins00000000000000python-cinderclient-1.0.8/cinderclient/v1/contrib/list_extensions.py0000664000175300017540000000264712274343712027112 0ustar jenkinsjenkins00000000000000# 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 import base from cinderclient import utils class ListExtResource(base.Resource): @property def summary(self): descr = self.description.strip() if not descr: return '??' lines = descr.split("\n") if len(lines) == 1: return lines[0] else: return lines[0] + "..." class ListExtManager(base.Manager): resource_class = ListExtResource def show_all(self): return self._list("/extensions", 'extensions') @utils.service_type('volume') def do_list_extensions(client, _args): """ List all the os-api extensions that are available. """ extensions = client.list_extensions.show_all() fields = ["Name", "Summary", "Alias", "Updated"] utils.print_list(extensions, fields) python-cinderclient-1.0.8/cinderclient/v1/contrib/__init__.py0000664000175300017540000000000012274343712025374 0ustar jenkinsjenkins00000000000000python-cinderclient-1.0.8/cinderclient/v1/volumes.py0000664000175300017540000003460412274343712023710 0ustar jenkinsjenkins00000000000000# Copyright 2011 Denali 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. """ Volume interface (1.1 extension). """ try: from urllib import urlencode except ImportError: from urllib.parse import urlencode import six from cinderclient import base class Volume(base.Resource): """A volume is an extra block level storage to the OpenStack instances.""" def __repr__(self): return "" % self.id def delete(self): """Delete this volume.""" self.manager.delete(self) def update(self, **kwargs): """Update the display_name or display_description for this volume.""" self.manager.update(self, **kwargs) def attach(self, instance_uuid, mountpoint, mode='rw'): """Set attachment metadata. :param instance_uuid: uuid of the attaching instance. :param mountpoint: mountpoint on the attaching instance. :param mode: the access mode """ return self.manager.attach(self, instance_uuid, mountpoint, mode) def detach(self): """Clear attachment metadata.""" return self.manager.detach(self) def reserve(self, volume): """Reserve this volume.""" return self.manager.reserve(self) def unreserve(self, volume): """Unreserve this volume.""" return self.manager.unreserve(self) def begin_detaching(self, volume): """Begin detaching volume.""" return self.manager.begin_detaching(self) def roll_detaching(self, volume): """Roll detaching volume.""" return self.manager.roll_detaching(self) def initialize_connection(self, volume, connector): """Initialize a volume connection. :param connector: connector dict from nova. """ return self.manager.initialize_connection(self, connector) def terminate_connection(self, volume, connector): """Terminate a volume connection. :param connector: connector dict from nova. """ return self.manager.terminate_connection(self, connector) def set_metadata(self, volume, metadata): """Set or Append metadata to a volume. :param volume : The :class: `Volume` to set metadata on :param metadata: A dict of key/value pairs to set """ return self.manager.set_metadata(self, metadata) def upload_to_image(self, force, image_name, container_format, disk_format): """Upload a volume to image service as an image.""" return self.manager.upload_to_image(self, force, image_name, container_format, disk_format) def force_delete(self): """Delete the specified volume ignoring its current state. :param volume: The UUID of the volume to force-delete. """ self.manager.force_delete(self) def reset_state(self, state): """Update the volume with the provided state.""" self.manager.reset_state(self, state) def extend(self, volume, new_size): """Extend the size of the specified volume. :param volume: The UUID of the volume to extend. :param new_size: The desired size to extend volume to. """ self.manager.extend(self, volume, new_size) def migrate_volume(self, host, force_host_copy): """Migrate the volume to a new host.""" self.manager.migrate_volume(self, host, force_host_copy) # def migrate_volume_completion(self, old_volume, new_volume, error): # """Complete the migration of the volume.""" # self.manager.migrate_volume_completion(self, old_volume, # new_volume, error) def update_all_metadata(self, metadata): """Update all metadata of this volume.""" return self.manager.update_all_metadata(self, metadata) def update_readonly_flag(self, volume, read_only): """Update the read-only access mode flag of the specified volume. :param volume: The UUID of the volume to update. :param read_only: The value to indicate whether to update volume to read-only access mode. """ self.manager.update_readonly_flag(self, volume, read_only) class VolumeManager(base.ManagerWithFind): """ Manage :class:`Volume` resources. """ resource_class = Volume def create(self, size, snapshot_id=None, source_volid=None, display_name=None, display_description=None, volume_type=None, user_id=None, project_id=None, availability_zone=None, metadata=None, imageRef=None): """ Create a volume. :param size: Size of volume in GB :param snapshot_id: ID of the snapshot :param display_name: Name of the volume :param display_description: Description of the volume :param volume_type: Type of volume :param user_id: User id derived from context :param project_id: Project id derived from context :param availability_zone: Availability Zone to use :param metadata: Optional metadata to set on volume creation :param imageRef: reference to an image stored in glance :param source_volid: ID of source volume to clone from :rtype: :class:`Volume` """ if metadata is None: volume_metadata = {} else: volume_metadata = metadata body = {'volume': {'size': size, 'snapshot_id': snapshot_id, 'display_name': display_name, 'display_description': display_description, 'volume_type': volume_type, 'user_id': user_id, 'project_id': project_id, 'availability_zone': availability_zone, 'status': "creating", 'attach_status': "detached", 'metadata': volume_metadata, 'imageRef': imageRef, 'source_volid': source_volid, }} return self._create('/volumes', body, 'volume') def get(self, volume_id): """ Get a volume. :param volume_id: The ID of the volume to delete. :rtype: :class:`Volume` """ return self._get("/volumes/%s" % volume_id, "volume") def list(self, detailed=True, search_opts=None): """ Get a list of all volumes. :rtype: list of :class:`Volume` """ if search_opts is None: search_opts = {} qparams = {} for opt, val in six.iteritems(search_opts): if val: qparams[opt] = val query_string = "?%s" % urlencode(qparams) if qparams else "" detail = "" if detailed: detail = "/detail" return self._list("/volumes%s%s" % (detail, query_string), "volumes") def delete(self, volume): """ Delete a volume. :param volume: The :class:`Volume` to delete. """ self._delete("/volumes/%s" % base.getid(volume)) def update(self, volume, **kwargs): """ Update the display_name or display_description for a volume. :param volume: The :class:`Volume` to update. """ if not kwargs: return body = {"volume": kwargs} self._update("/volumes/%s" % base.getid(volume), body) def _action(self, action, volume, info=None, **kwargs): """ Perform a volume "action." """ body = {action: info} self.run_hooks('modify_body_for_action', body, **kwargs) url = '/volumes/%s/action' % base.getid(volume) return self.api.client.post(url, body=body) def attach(self, volume, instance_uuid, mountpoint, mode='rw'): """ Set attachment metadata. :param volume: The :class:`Volume` (or its ID) you would like to attach. :param instance_uuid: uuid of the attaching instance. :param mountpoint: mountpoint on the attaching instance. :param mode: the access mode. """ return self._action('os-attach', volume, {'instance_uuid': instance_uuid, 'mountpoint': mountpoint, 'mode': mode}) def detach(self, volume): """ Clear attachment metadata. :param volume: The :class:`Volume` (or its ID) you would like to detach. """ return self._action('os-detach', volume) def reserve(self, volume): """ Reserve this volume. :param volume: The :class:`Volume` (or its ID) you would like to reserve. """ return self._action('os-reserve', volume) def unreserve(self, volume): """ Unreserve this volume. :param volume: The :class:`Volume` (or its ID) you would like to unreserve. """ return self._action('os-unreserve', volume) def begin_detaching(self, volume): """ Begin detaching this volume. :param volume: The :class:`Volume` (or its ID) you would like to detach. """ return self._action('os-begin_detaching', volume) def roll_detaching(self, volume): """ Roll detaching this volume. :param volume: The :class:`Volume` (or its ID) you would like to roll detaching. """ return self._action('os-roll_detaching', volume) def initialize_connection(self, volume, connector): """ Initialize a volume connection. :param volume: The :class:`Volume` (or its ID). :param connector: connector dict from nova. """ return self._action('os-initialize_connection', volume, {'connector': connector})[1]['connection_info'] def terminate_connection(self, volume, connector): """ Terminate a volume connection. :param volume: The :class:`Volume` (or its ID). :param connector: connector dict from nova. """ self._action('os-terminate_connection', volume, {'connector': connector}) def set_metadata(self, volume, metadata): """ Update/Set a volumes metadata. :param volume: The :class:`Volume`. :param metadata: A list of keys to be set. """ body = {'metadata': metadata} return self._create("/volumes/%s/metadata" % base.getid(volume), body, "metadata") def delete_metadata(self, volume, keys): """ Delete specified keys from volumes metadata. :param volume: The :class:`Volume`. :param keys: A list of keys to be removed. """ for k in keys: self._delete("/volumes/%s/metadata/%s" % (base.getid(volume), k)) def upload_to_image(self, volume, force, image_name, container_format, disk_format): """ Upload volume to image service as image. :param volume: The :class:`Volume` to upload. """ return self._action('os-volume_upload_image', volume, {'force': force, 'image_name': image_name, 'container_format': container_format, 'disk_format': disk_format}) def force_delete(self, volume): return self._action('os-force_delete', base.getid(volume)) def reset_state(self, volume, state): """Update the provided volume with the provided state.""" return self._action('os-reset_status', volume, {'status': state}) def extend(self, volume, new_size): return self._action('os-extend', base.getid(volume), {'new_size': new_size}) def get_encryption_metadata(self, volume_id): """ Retrieve the encryption metadata from the desired volume. :param volume_id: the id of the volume to query :return: a dictionary of volume encryption metadata """ return self._get("/volumes/%s/encryption" % volume_id)._info def migrate_volume(self, volume, host, force_host_copy): """Migrate volume to new host. :param volume: The :class:`Volume` to migrate :param host: The destination host :param force_host_copy: Skip driver optimizations """ return self._action('os-migrate_volume', volume, {'host': host, 'force_host_copy': force_host_copy}) def migrate_volume_completion(self, old_volume, new_volume, error): """Complete the migration from the old volume to the temp new one. :param old_volume: The original :class:`Volume` in the migration :param new_volume: The new temporary :class:`Volume` in the migration :param error: Inform of an error to cause migration cleanup """ new_volume_id = base.getid(new_volume) return self._action('os-migrate_volume_completion', old_volume, {'new_volume': new_volume_id, 'error': error})[1] def update_all_metadata(self, volume, metadata): """Update all metadata of a volume. :param volume: The :class:`Volume`. :param metadata: A list of keys to be updated. """ body = {'metadata': metadata} return self._update("/volumes/%s/metadata" % base.getid(volume), body) def update_readonly_flag(self, volume, flag): return self._action('os-update_readonly_flag', base.getid(volume), {'readonly': flag}) python-cinderclient-1.0.8/cinderclient/v1/volume_snapshots.py0000664000175300017540000001452312274343712025625 0ustar jenkinsjenkins00000000000000# Copyright 2011 Denali 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. """ Volume snapshot interface (1.1 extension). """ try: from urllib import urlencode except ImportError: from urllib.parse import urlencode from cinderclient import base import six class Snapshot(base.Resource): """ A Snapshot is a point-in-time snapshot of an openstack volume. """ def __repr__(self): return "" % self.id def delete(self): """ Delete this snapshot. """ self.manager.delete(self) def update(self, **kwargs): """ Update the display_name or display_description for this snapshot. """ self.manager.update(self, **kwargs) @property def progress(self): return self._info.get('os-extended-snapshot-attributes:progress') @property def project_id(self): return self._info.get('os-extended-snapshot-attributes:project_id') def reset_state(self, state): """Update the snapshot with the privided state.""" self.manager.reset_state(self, state) def set_metadata(self, metadata): """Set metadata of this snapshot.""" return self.manager.set_metadata(self, metadata) def delete_metadata(self, keys): """Delete metadata of this snapshot.""" return self.manager.delete_metadata(self, keys) def update_all_metadata(self, metadata): """Update_all metadata of this snapshot.""" return self.manager.update_all_metadata(self, metadata) class SnapshotManager(base.ManagerWithFind): """ Manage :class:`Snapshot` resources. """ resource_class = Snapshot def create(self, volume_id, force=False, display_name=None, display_description=None): """ Create a snapshot of the given volume. :param volume_id: The ID of the volume to snapshot. :param force: If force is True, create a snapshot even if the volume is attached to an instance. Default is False. :param display_name: Name of the snapshot :param display_description: Description of the snapshot :rtype: :class:`Snapshot` """ body = {'snapshot': {'volume_id': volume_id, 'force': force, 'display_name': display_name, 'display_description': display_description}} return self._create('/snapshots', body, 'snapshot') def get(self, snapshot_id): """ Get a snapshot. :param snapshot_id: The ID of the snapshot to get. :rtype: :class:`Snapshot` """ return self._get("/snapshots/%s" % snapshot_id, "snapshot") def list(self, detailed=True, search_opts=None): """ Get a list of all snapshots. :rtype: list of :class:`Snapshot` """ if search_opts is None: search_opts = {} qparams = {} for opt, val in six.iteritems(search_opts): if val: qparams[opt] = val # Transform the dict to a sequence of two-element tuples in fixed # order, then the encoded string will be consistent in Python 2&3. if qparams: new_qparams = sorted(qparams.items(), key=lambda x: x[0]) query_string = "?%s" % urlencode(new_qparams) else: query_string = "" detail = "" if detailed: detail = "/detail" return self._list("/snapshots%s%s" % (detail, query_string), "snapshots") def delete(self, snapshot): """ Delete a snapshot. :param snapshot: The :class:`Snapshot` to delete. """ self._delete("/snapshots/%s" % base.getid(snapshot)) def update(self, snapshot, **kwargs): """ Update the display_name or display_description for a snapshot. :param snapshot: The :class:`Snapshot` to delete. """ if not kwargs: return body = {"snapshot": kwargs} self._update("/snapshots/%s" % base.getid(snapshot), body) def reset_state(self, snapshot, state): """Update the specified volume with the provided state.""" return self._action('os-reset_status', snapshot, {'status': state}) def _action(self, action, snapshot, info=None, **kwargs): """Perform a snapshot action.""" body = {action: info} self.run_hooks('modify_body_for_action', body, **kwargs) url = '/snapshots/%s/action' % base.getid(snapshot) return self.api.client.post(url, body=body) def update_snapshot_status(self, snapshot, update_dict): return self._action('os-update_snapshot_status', base.getid(snapshot), update_dict) def set_metadata(self, snapshot, metadata): """Update/Set a snapshots metadata. :param snapshot: The :class:`Snapshot`. :param metadata: A list of keys to be set. """ body = {'metadata': metadata} return self._create("/snapshots/%s/metadata" % base.getid(snapshot), body, "metadata") def delete_metadata(self, snapshot, keys): """Delete specified keys from snapshot metadata. :param snapshot: The :class:`Snapshot`. :param keys: A list of keys to be removed. """ snapshot_id = base.getid(snapshot) for k in keys: self._delete("/snapshots/%s/metadata/%s" % (snapshot_id, k)) def update_all_metadata(self, snapshot, metadata): """Update_all snapshot metadata. :param snapshot: The :class:`Snapshot`. :param metadata: A list of keys to be updated. """ body = {'metadata': metadata} return self._update("/snapshots/%s/metadata" % base.getid(snapshot), body) python-cinderclient-1.0.8/cinderclient/v1/shell.py0000664000175300017540000013033112274343712023317 0ustar jenkinsjenkins00000000000000# 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. from __future__ import print_function import argparse import copy import os import sys import time from cinderclient import exceptions from cinderclient.openstack.common import strutils from cinderclient import utils from cinderclient.v1 import availability_zones def _poll_for_status(poll_fn, obj_id, action, final_ok_states, poll_period=5, show_progress=True): """Block while an action is being performed, periodically printing 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): """Get a volume snapshot by name or ID.""" return utils.find_resource(cs.volume_snapshots, snapshot) def _find_backup(cs, backup): """Get a backup by name or ID.""" return utils.find_resource(cs.backups, backup) def _find_transfer(cs, transfer): """Get a transfer by name or ID.""" return utils.find_resource(cs.transfers, transfer) def _find_qos_specs(cs, qos_specs): """Get a qos specs by ID.""" return utils.find_resource(cs.qos_specs, qos_specs) def _print_volume(volume): utils.print_dict(volume._info) def _print_volume_snapshot(snapshot): utils.print_dict(snapshot._info) def _print_volume_image(image): utils.print_dict(image[1]['os-volume_upload_image']) 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 = [('displayName', 'display_name'), ('volumeType', 'volume_type')] _translate_keys(collection, convert) def _translate_volume_snapshot_keys(collection): convert = [('displayName', 'display_name'), ('volumeId', 'volume_id')] _translate_keys(collection, convert) def _translate_availability_zone_keys(collection): convert = [('zoneName', 'name'), ('zoneState', 'status')] _translate_keys(collection, convert) def _extract_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 @utils.arg( '--all-tenants', dest='all_tenants', metavar='<0|1>', nargs='?', type=int, const=1, default=0, help='Display information from all tenants (Admin only).') @utils.arg( '--all_tenants', nargs='?', type=int, const=1, help=argparse.SUPPRESS) @utils.arg( '--display-name', metavar='', default=None, help='Filter results by display-name') @utils.arg( '--status', metavar='', default=None, help='Filter results by status') @utils.arg( '--metadata', type=str, nargs='*', metavar='', help='Filter results by metadata', default=None) @utils.service_type('volume') def do_list(cs, args): """List all the volumes.""" all_tenants = int(os.environ.get("ALL_TENANTS", args.all_tenants)) search_opts = { 'all_tenants': all_tenants, 'display_name': args.display_name, 'status': args.status, 'metadata': _extract_metadata(args) if args.metadata else None, } volumes = cs.volumes.list(search_opts=search_opts) _translate_volume_keys(volumes) # Create a list of servers to which the volume is attached for vol in volumes: servers = [s.get('server_id') for s in vol.attachments] setattr(vol, 'attached_to', ','.join(map(str, servers))) utils.print_list(volumes, ['ID', 'Status', 'Display Name', 'Size', 'Volume Type', 'Bootable', 'Attached to']) @utils.arg('volume', metavar='', help='Name or ID of the volume.') @utils.service_type('volume') def do_show(cs, args): """Show details about a volume.""" volume = utils.find_volume(cs, args.volume) _print_volume(volume) @utils.arg('size', metavar='', type=int, help='Size of volume in GB') @utils.arg( '--snapshot-id', metavar='', default=None, help='Create volume from snapshot id (Optional, Default=None)') @utils.arg( '--snapshot_id', help=argparse.SUPPRESS) @utils.arg( '--source-volid', metavar='', default=None, help='Create volume from volume id (Optional, Default=None)') @utils.arg( '--source_volid', help=argparse.SUPPRESS) @utils.arg( '--image-id', metavar='', default=None, help='Create volume from image id (Optional, Default=None)') @utils.arg( '--image_id', help=argparse.SUPPRESS) @utils.arg( '--display-name', metavar='', default=None, help='Volume name (Optional, Default=None)') @utils.arg( '--display_name', help=argparse.SUPPRESS) @utils.arg( '--display-description', metavar='', default=None, help='Volume description (Optional, Default=None)') @utils.arg( '--display_description', help=argparse.SUPPRESS) @utils.arg( '--volume-type', metavar='', default=None, help='Volume type (Optional, Default=None)') @utils.arg( '--volume_type', help=argparse.SUPPRESS) @utils.arg( '--availability-zone', metavar='', default=None, help='Availability zone for volume (Optional, Default=None)') @utils.arg( '--availability_zone', help=argparse.SUPPRESS) @utils.arg('--metadata', type=str, nargs='*', metavar='', help='Metadata key=value pairs (Optional, Default=None)', default=None) @utils.service_type('volume') def do_create(cs, args): """Add a new volume.""" volume_metadata = None if args.metadata is not None: volume_metadata = _extract_metadata(args) volume = cs.volumes.create(args.size, args.snapshot_id, args.source_volid, args.display_name, args.display_description, args.volume_type, availability_zone=args.availability_zone, imageRef=args.image_id, metadata=volume_metadata) _print_volume(volume) @utils.arg('volume', metavar='', nargs='+', help='Name or ID of the volume(s) to delete.') @utils.service_type('volume') def do_delete(cs, args): """Remove volume(s).""" failure_count = 0 for volume in args.volume: try: utils.find_volume(cs, volume).delete() except Exception as e: failure_count += 1 print("Delete for volume %s failed: %s" % (volume, e)) if failure_count == len(args.volume): raise exceptions.CommandError("Unable to delete any of the specified " "volumes.") @utils.arg('volume', metavar='', nargs='+', help='Name or ID of the volume(s) to delete.') @utils.service_type('volume') def do_force_delete(cs, args): """Attempt forced removal of volume(s), regardless of the state(s).""" failure_count = 0 for volume in args.volume: try: utils.find_volume(cs, volume).force_delete() except Exception as e: failure_count += 1 print("Delete for volume %s failed: %s" % (volume, e)) if failure_count == len(args.volume): raise exceptions.CommandError("Unable to force delete any of the " "specified volumes.") @utils.arg('volume', metavar='', nargs='+', help='Name or ID of the volume to modify.') @utils.arg('--state', metavar='', default='available', help=('Indicate which state to assign the volume. Options include ' 'available, error, creating, deleting, error_deleting. If no ' 'state is provided, available will be used.')) @utils.service_type('volume') def do_reset_state(cs, args): """Explicitly update the state of a volume.""" failure_count = 0 single = (len(args.volume) == 1) for volume in args.volume: try: utils.find_volume(cs, volume).reset_state(args.state) except Exception as e: failure_count += 1 msg = "Reset state for volume %s failed: %s" % (volume, e) if not single: print(msg) if failure_count == len(args.volume): if not single: msg = "Unable to reset the state for any of the specified volumes." raise exceptions.CommandError(msg) @utils.arg('volume', metavar='', help='Name or ID of the volume to rename.') @utils.arg('display_name', nargs='?', metavar='', help='New display-name for the volume.') @utils.arg('--display-description', metavar='', help='Optional volume description. (Default=None)', default=None) @utils.service_type('volume') def do_rename(cs, args): """Rename a volume.""" kwargs = {} if args.display_name is not None: kwargs['display_name'] = args.display_name if args.display_description is not None: kwargs['display_description'] = args.display_description if not any(kwargs): msg = 'Must supply either display-name or display-description.' raise exceptions.ClientException(code=1, message=msg) utils.find_volume(cs, args.volume).update(**kwargs) @utils.arg('volume', metavar='', help='Name or ID of the volume to update metadata on.') @utils.arg('action', metavar='', choices=['set', 'unset'], help="Actions: 'set' or 'unset'") @utils.arg('metadata', metavar='', nargs='+', default=[], help='Metadata to set/unset (only key is necessary on unset)') @utils.service_type('volume') def do_metadata(cs, args): """Set or Delete metadata on a volume.""" volume = utils.find_volume(cs, args.volume) metadata = _extract_metadata(args) if args.action == 'set': cs.volumes.set_metadata(volume, metadata) elif args.action == 'unset': # NOTE(zul): Make sure py2/py3 sorting is the same cs.volumes.delete_metadata(volume, sorted(metadata.keys(), reverse=True)) @utils.arg( '--all-tenants', dest='all_tenants', metavar='<0|1>', nargs='?', type=int, const=1, default=0, help='Display information from all tenants (Admin only).') @utils.arg( '--all_tenants', nargs='?', type=int, const=1, help=argparse.SUPPRESS) @utils.arg( '--display-name', metavar='', default=None, help='Filter results by display-name') @utils.arg( '--status', metavar='', default=None, help='Filter results by status') @utils.arg( '--volume-id', metavar='', default=None, help='Filter results by volume-id') @utils.service_type('volume') def do_snapshot_list(cs, args): """List all the snapshots.""" all_tenants = int(os.environ.get("ALL_TENANTS", args.all_tenants)) search_opts = { 'all_tenants': all_tenants, 'display_name': args.display_name, 'status': args.status, 'volume_id': args.volume_id, } snapshots = cs.volume_snapshots.list(search_opts=search_opts) _translate_volume_snapshot_keys(snapshots) utils.print_list(snapshots, ['ID', 'Volume ID', 'Status', 'Display Name', 'Size']) @utils.arg('snapshot', metavar='', help='Name or ID of the snapshot.') @utils.service_type('volume') def do_snapshot_show(cs, args): """Show details about a snapshot.""" snapshot = _find_volume_snapshot(cs, args.snapshot) _print_volume_snapshot(snapshot) @utils.arg('volume', metavar='', help='Name or ID of the volume to snapshot') @utils.arg('--force', metavar='', help='Optional flag to indicate whether ' 'to snapshot a volume even if it\'s ' 'attached to an instance. (Default=False)', default=False) @utils.arg( '--display-name', metavar='', default=None, help='Optional snapshot name. (Default=None)') @utils.arg( '--display_name', help=argparse.SUPPRESS) @utils.arg( '--display-description', metavar='', default=None, help='Optional snapshot description. (Default=None)') @utils.arg( '--display_description', help=argparse.SUPPRESS) @utils.service_type('volume') def do_snapshot_create(cs, args): """Add a new snapshot.""" volume = utils.find_volume(cs, args.volume) snapshot = cs.volume_snapshots.create(volume.id, args.force, args.display_name, args.display_description) _print_volume_snapshot(snapshot) @utils.arg('snapshot', metavar='', help='Name or ID of the snapshot to delete.') @utils.service_type('volume') def do_snapshot_delete(cs, args): """Remove a snapshot.""" snapshot = _find_volume_snapshot(cs, args.snapshot) snapshot.delete() @utils.arg('snapshot', metavar='', help='Name or ID of the snapshot.') @utils.arg('display_name', nargs='?', metavar='', help='New display-name for the snapshot.') @utils.arg('--display-description', metavar='', help='Optional snapshot description. (Default=None)', default=None) @utils.service_type('volume') def do_snapshot_rename(cs, args): """Rename a snapshot.""" kwargs = {} if args.display_name is not None: kwargs['display_name'] = args.display_name if args.display_description is not None: kwargs['display_description'] = args.display_description if not any(kwargs): msg = 'Must supply either display-name or display-description.' raise exceptions.ClientException(code=1, message=msg) _find_volume_snapshot(cs, args.snapshot).update(**kwargs) @utils.arg('snapshot', metavar='', nargs='+', help='Name or ID of the snapshot to modify.') @utils.arg('--state', metavar='', default='available', help=('Indicate which state to assign the snapshot. ' 'Options include available, error, creating, deleting, ' 'error_deleting. If no state is provided, ' 'available will be used.')) @utils.service_type('volume') def do_snapshot_reset_state(cs, args): """Explicitly update the state of a snapshot.""" failure_count = 0 single = (len(args.snapshot) == 1) for snapshot in args.snapshot: try: _find_volume_snapshot(cs, snapshot).reset_state(args.state) except Exception as e: failure_count += 1 msg = "Reset state for snapshot %s failed: %s" % (snapshot, e) if not single: print(msg) if failure_count == len(args.snapshot): if not single: msg = ("Unable to reset the state for any of the the specified " "snapshots.") raise exceptions.CommandError(msg) def _print_volume_type_list(vtypes): utils.print_list(vtypes, ['ID', 'Name']) def _print_type_and_extra_specs_list(vtypes): formatters = {'extra_specs': _print_type_extra_specs} utils.print_list(vtypes, ['ID', 'Name', 'extra_specs'], formatters) @utils.service_type('volume') def do_type_list(cs, args): """Print a list of available 'volume types'.""" vtypes = cs.volume_types.list() _print_volume_type_list(vtypes) @utils.service_type('volume') def do_extra_specs_list(cs, args): """Print a list of current 'volume types and extra specs' (Admin Only).""" vtypes = cs.volume_types.list() _print_type_and_extra_specs_list(vtypes) @utils.arg('name', metavar='', help="Name of the new volume type") @utils.service_type('volume') def do_type_create(cs, args): """Create a new volume type.""" vtype = cs.volume_types.create(args.name) _print_volume_type_list([vtype]) @utils.arg('id', metavar='', help="Unique ID of the volume type to delete") @utils.service_type('volume') def do_type_delete(cs, args): """Delete a specific volume type.""" cs.volume_types.delete(args.id) @utils.arg('vtype', metavar='', help="Name or ID of the volume type") @utils.arg('action', metavar='', choices=['set', 'unset'], help="Actions: 'set' or 'unset'") @utils.arg('metadata', metavar='', nargs='*', default=None, help='Extra_specs to set/unset (only key is necessary on unset)') @utils.service_type('volume') def do_type_key(cs, args): """Set or unset extra_spec for a volume type.""" vtype = _find_volume_type(cs, args.vtype) if args.metadata is not None: keypair = _extract_metadata(args) if args.action == 'set': vtype.set_keys(keypair) elif args.action == 'unset': vtype.unset_keys(list(keypair)) def do_endpoints(cs, args): """Discover endpoints that get returned from the authenticate services.""" catalog = cs.client.service_catalog.catalog for e in catalog['access']['serviceCatalog']: utils.print_dict(e['endpoints'][0], e['name']) def do_credentials(cs, args): """Show user credentials returned from auth.""" catalog = cs.client.service_catalog.catalog utils.print_dict(catalog['access']['user'], "User Credentials") utils.print_dict(catalog['access']['token'], "Token") _quota_resources = ['volumes', 'snapshots', 'gigabytes'] _quota_infos = ['Type', 'In_use', 'Reserved', 'Limit'] def _quota_show(quotas): quota_dict = {} for resource in quotas._info: 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 = [] for resource in quotas._info.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: manager.update(identifier, **updates) @utils.arg('tenant', metavar='', help='UUID of tenant to list the quotas for.') @utils.service_type('volume') def do_quota_show(cs, args): """List the quotas for a tenant.""" _quota_show(cs.quotas.get(args.tenant)) @utils.arg('tenant', metavar='', help='UUID of tenant to list the quota usage for.') @utils.service_type('volume') def do_quota_usage(cs, args): """List the quota usage for a tenant.""" _quota_usage_show(cs.quotas.get(args.tenant, usage=True)) @utils.arg('tenant', metavar='', help='UUID of tenant to list the default quotas for.') @utils.service_type('volume') def do_quota_defaults(cs, args): """List the default quotas for a tenant.""" _quota_show(cs.quotas.defaults(args.tenant)) @utils.arg('tenant', metavar='', help='UUID of tenant to set the quotas for.') @utils.arg('--volumes', metavar='', type=int, default=None, help='New value for the "volumes" quota.') @utils.arg('--snapshots', metavar='', type=int, default=None, help='New value for the "snapshots" quota.') @utils.arg('--gigabytes', metavar='', type=int, default=None, help='New value for the "gigabytes" quota.') @utils.arg('--volume-type', metavar='', default=None, help='Volume type (Optional, Default=None)') @utils.service_type('volume') def do_quota_update(cs, args): """Update the quotas for a tenant.""" _quota_update(cs.quotas, args.tenant, args) @utils.arg('class_name', metavar='', help='Name of quota class to list the quotas for.') @utils.service_type('volume') def do_quota_class_show(cs, args): """List the quotas for a quota class.""" _quota_show(cs.quota_classes.get(args.class_name)) @utils.arg('class_name', metavar='', help='Name of quota class to set the quotas for.') @utils.arg('--volumes', metavar='', type=int, default=None, help='New value for the "volumes" quota.') @utils.arg('--snapshots', metavar='', type=int, default=None, help='New value for the "snapshots" quota.') @utils.arg('--gigabytes', metavar='', type=int, default=None, help='New value for the "gigabytes" quota.') @utils.arg('--volume-type', metavar='', default=None, help='Volume type (Optional, Default=None)') @utils.service_type('volume') def do_quota_class_update(cs, args): """Update the quotas for a quota class.""" _quota_update(cs.quota_classes, args.class_name, args) @utils.service_type('volume') def do_absolute_limits(cs, args): """Print a list of absolute limits for a user""" limits = cs.limits.get().absolute columns = ['Name', 'Value'] utils.print_list(limits, columns) @utils.service_type('volume') def do_rate_limits(cs, args): """Print a list of rate limits for a user""" limits = cs.limits.get().rate columns = ['Verb', 'URI', 'Value', 'Remain', 'Unit', 'Next_Available'] utils.print_list(limits, columns) def _print_type_extra_specs(vol_type): try: return vol_type.get_keys() except exceptions.NotFound: return "N/A" def _find_volume_type(cs, vtype): """Get a volume type by name or ID.""" return utils.find_resource(cs.volume_types, vtype) @utils.arg('volume', metavar='', help='Name or ID of the volume to upload to an image') @utils.arg('--force', metavar='', help='Optional flag to indicate whether ' 'to upload a volume even if it\'s ' 'attached to an instance. (Default=False)', default=False) @utils.arg('--container-format', metavar='', help='Optional type for container format ' '(Default=bare)', default='bare') @utils.arg('--disk-format', metavar='', help='Optional type for disk format ' '(Default=raw)', default='raw') @utils.arg('image_name', metavar='', help='Name for created image') @utils.service_type('volume') def do_upload_to_image(cs, args): """Upload volume to image service as image.""" volume = utils.find_volume(cs, args.volume) _print_volume_image(volume.upload_to_image(args.force, args.image_name, args.container_format, args.disk_format)) @utils.arg('volume', metavar='', help='Name or ID of the volume to backup.') @utils.arg('--container', metavar='', help='Optional Backup container name. (Default=None)', default=None) @utils.arg('--display-name', metavar='', help='Optional backup name. (Default=None)', default=None) @utils.arg('--display-description', metavar='', help='Optional backup description. (Default=None)', default=None) @utils.service_type('volume') def do_backup_create(cs, args): """Creates a backup.""" volume = utils.find_volume(cs, args.volume) backup = cs.backups.create(volume.id, args.container, args.display_name, args.display_description) info = {"volume_id": volume.id} info.update(backup._info) if 'links' in info: info.pop('links') utils.print_dict(info) @utils.arg('backup', metavar='', help='Name or ID of the backup.') @utils.service_type('volume') def do_backup_show(cs, args): """Show details about a backup.""" backup = _find_backup(cs, args.backup) info = dict() info.update(backup._info) if 'links' in info: info.pop('links') utils.print_dict(info) @utils.service_type('volume') def do_backup_list(cs, args): """List all the backups.""" backups = cs.backups.list() columns = ['ID', 'Volume ID', 'Status', 'Name', 'Size', 'Object Count', 'Container'] utils.print_list(backups, columns) @utils.arg('backup', metavar='', help='Name or ID of the backup to delete.') @utils.service_type('volume') def do_backup_delete(cs, args): """Remove a backup.""" backup = _find_backup(cs, args.backup) backup.delete() @utils.arg('backup', metavar='', help='ID of the backup to restore.') @utils.arg('--volume-id', metavar='', help='Optional ID(or name) of the volume to restore to.', default=None) @utils.service_type('volume') def do_backup_restore(cs, args): """Restore a backup.""" if args.volume_id: volume_id = utils.find_volume(cs, args.volume_id).id else: volume_id = None cs.restores.restore(args.backup, volume_id) @utils.arg('volume', metavar='', help='Name or ID of the volume to transfer.') @utils.arg('--display-name', metavar='', help='Optional transfer name. (Default=None)', default=None) @utils.service_type('volume') def do_transfer_create(cs, args): """Creates a volume transfer.""" volume = utils.find_volume(cs, args.volume) transfer = cs.transfers.create(volume.id, args.display_name) info = dict() info.update(transfer._info) if 'links' in info: info.pop('links') utils.print_dict(info) @utils.arg('transfer', metavar='', help='Name or ID of the transfer to delete.') @utils.service_type('volume') def do_transfer_delete(cs, args): """Undo a transfer.""" transfer = _find_transfer(cs, args.transfer) transfer.delete() @utils.arg('transfer', metavar='', help='ID of the transfer to accept.') @utils.arg('auth_key', metavar='', help='Auth key of the transfer to accept.') @utils.service_type('volume') def do_transfer_accept(cs, args): """Accepts a volume transfer.""" transfer = cs.transfers.accept(args.transfer, args.auth_key) info = dict() info.update(transfer._info) if 'links' in info: info.pop('links') utils.print_dict(info) @utils.service_type('volume') def do_transfer_list(cs, args): """List all the transfers.""" transfers = cs.transfers.list() columns = ['ID', 'Volume ID', 'Name'] utils.print_list(transfers, columns) @utils.arg('transfer', metavar='', help='Name or ID of the transfer to accept.') @utils.service_type('volume') def do_transfer_show(cs, args): """Show details about a transfer.""" transfer = _find_transfer(cs, args.transfer) info = dict() info.update(transfer._info) if 'links' in info: info.pop('links') utils.print_dict(info) @utils.arg('volume', metavar='', help='Name or ID of the volume to extend.') @utils.arg('new_size', metavar='', type=int, help='New size of volume in GB') @utils.service_type('volume') def do_extend(cs, args): """Attempt to extend the size of an existing volume.""" volume = utils.find_volume(cs, args.volume) cs.volumes.extend(volume, args.new_size) @utils.arg('--host', metavar='', default=None, help='Name of host.') @utils.arg('--binary', metavar='', default=None, help='Service binary.') @utils.service_type('volume') def do_service_list(cs, args): """List all the services. Filter by host & service binary.""" result = cs.services.list(host=args.host, binary=args.binary) columns = ["Binary", "Host", "Zone", "Status", "State", "Updated_at"] utils.print_list(result, columns) @utils.arg('host', metavar='', help='Name of host.') @utils.arg('binary', metavar='', help='Service binary.') @utils.service_type('volume') def do_service_enable(cs, args): """Enable the service.""" result = cs.services.enable(args.host, args.binary) columns = ["Host", "Binary", "Status"] utils.print_list([result], columns) @utils.arg('host', metavar='', help='Name of host.') @utils.arg('binary', metavar='', help='Service binary.') @utils.service_type('volume') def do_service_disable(cs, args): """Disable the service.""" result = cs.services.disable(args.host, args.binary) columns = ["Host", "Binary", "Status"] utils.print_list([result], columns) def _treeizeAvailabilityZone(zone): """Build a tree view for availability zones.""" AvailabilityZone = availability_zones.AvailabilityZone az = AvailabilityZone(zone.manager, copy.deepcopy(zone._info), zone._loaded) result = [] # Zone tree view item az.zoneName = zone.zoneName az.zoneState = ('available' if zone.zoneState['available'] else 'not available') az._info['zoneName'] = az.zoneName az._info['zoneState'] = az.zoneState result.append(az) if getattr(zone, "hosts", None) and zone.hosts is not None: for (host, services) in zone.hosts.items(): # Host tree view item az = AvailabilityZone(zone.manager, copy.deepcopy(zone._info), zone._loaded) az.zoneName = '|- %s' % host az.zoneState = '' az._info['zoneName'] = az.zoneName az._info['zoneState'] = az.zoneState result.append(az) for (svc, state) in services.items(): # Service tree view item az = AvailabilityZone(zone.manager, copy.deepcopy(zone._info), zone._loaded) az.zoneName = '| |- %s' % svc az.zoneState = '%s %s %s' % ( 'enabled' if state['active'] else 'disabled', ':-)' if state['available'] else 'XXX', state['updated_at']) az._info['zoneName'] = az.zoneName az._info['zoneState'] = az.zoneState result.append(az) return result @utils.service_type('volume') def do_availability_zone_list(cs, _args): """List all the availability zones.""" try: availability_zones = cs.availability_zones.list() except exceptions.Forbidden as e: # policy doesn't allow probably try: availability_zones = cs.availability_zones.list(detailed=False) except Exception: raise e result = [] for zone in availability_zones: result += _treeizeAvailabilityZone(zone) _translate_availability_zone_keys(result) utils.print_list(result, ['Name', 'Status']) def _print_volume_encryption_type_list(encryption_types): """ Display a tabularized list of 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']) @utils.service_type('volume') def do_encryption_type_list(cs, args): """List encryption type information for all volume types (Admin Only).""" result = cs.volume_encryption_types.list() utils.print_list(result, ['Volume Type ID', 'Provider', 'Cipher', 'Key Size', 'Control Location']) @utils.arg('volume_type', metavar='', type=str, help="Name or ID of the volume type") @utils.service_type('volume') def do_encryption_type_show(cs, args): """Show the encryption type information for a volume type (Admin Only).""" volume_type = _find_volume_type(cs, args.volume_type) result = cs.volume_encryption_types.get(volume_type) # Display result or an empty table if no result if hasattr(result, 'volume_type_id'): _print_volume_encryption_type_list([result]) else: _print_volume_encryption_type_list([]) @utils.arg('volume_type', metavar='', type=str, help="Name or ID of the volume type") @utils.arg('provider', metavar='', type=str, help="Class providing encryption support (e.g. LuksEncryptor)") @utils.arg('--cipher', metavar='', type=str, required=False, default=None, help="Encryption algorithm/mode to use (e.g., aes-xts-plain64) " "(Optional, Default=None)") @utils.arg('--key_size', metavar='', type=int, required=False, default=None, help="Size of the encryption key, in bits (e.g., 128, 256) " "(Optional, Default=None)") @utils.arg('--control_location', metavar='', choices=['front-end', 'back-end'], type=str, required=False, default=None, help="Notional service where encryption is performed (e.g., " "front-end=Nova). Values: 'front-end', 'back-end' " "(Optional, Default=None)") @utils.service_type('volume') def do_encryption_type_create(cs, args): """Create a new encryption type for a volume type (Admin Only).""" volume_type = _find_volume_type(cs, args.volume_type) body = {} body['provider'] = args.provider body['cipher'] = args.cipher body['key_size'] = args.key_size body['control_location'] = args.control_location result = cs.volume_encryption_types.create(volume_type, body) _print_volume_encryption_type_list([result]) @utils.arg('volume_type', metavar='', type=str, help="Name or ID of the volume type") @utils.service_type('volume') def do_encryption_type_delete(cs, args): """Delete the encryption type for a volume type (Admin Only).""" volume_type = _find_volume_type(cs, args.volume_type) cs.volume_encryption_types.delete(volume_type) @utils.arg('volume', metavar='', help='ID of the volume to migrate') @utils.arg('host', metavar='', help='Destination host') @utils.arg('--force-host-copy', metavar='', choices=['True', 'False'], required=False, help='Optional flag to force the use of the generic ' 'host-based migration mechanism, bypassing driver ' 'optimizations (Default=False).', default=False) @utils.service_type('volume') def do_migrate(cs, args): """Migrate the volume to the new host.""" volume = utils.find_volume(cs, args.volume) volume.migrate_volume(args.host, args.force_host_copy) def _print_qos_specs(qos_specs): utils.print_dict(qos_specs._info) 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']) @utils.arg('name', metavar='', help="Name of the new QoS specs") @utils.arg('metadata', metavar='', nargs='+', default=[], help='Specifications for QoS') @utils.service_type('volume') def do_qos_create(cs, args): """Create a new qos specs.""" keypair = None if args.metadata is not None: keypair = _extract_metadata(args) qos_specs = cs.qos_specs.create(args.name, keypair) _print_qos_specs(qos_specs) @utils.service_type('volume') def do_qos_list(cs, args): """Get full list of qos specs.""" qos_specs = cs.qos_specs.list() _print_qos_specs_list(qos_specs) @utils.arg('qos_specs', metavar='', help='ID of the qos_specs to show.') @utils.service_type('volume') def do_qos_show(cs, args): """Get a specific qos specs.""" qos_specs = _find_qos_specs(cs, args.qos_specs) _print_qos_specs(qos_specs) @utils.arg('qos_specs', metavar='', help='ID of the qos_specs to delete.') @utils.arg('--force', metavar='', default=False, help='Optional flag that indicates whether to delete ' 'specified qos specs even if it is in-use.') @utils.service_type('volume') def do_qos_delete(cs, args): """Delete a specific qos specs.""" force = strutils.bool_from_string(args.force) qos_specs = _find_qos_specs(cs, args.qos_specs) cs.qos_specs.delete(qos_specs, force) @utils.arg('qos_specs', metavar='', help='ID of qos_specs.') @utils.arg('vol_type_id', metavar='', help='ID of volume type to be associated with.') @utils.service_type('volume') def do_qos_associate(cs, args): """Associate qos specs with specific volume type.""" cs.qos_specs.associate(args.qos_specs, args.vol_type_id) @utils.arg('qos_specs', metavar='', help='ID of qos_specs.') @utils.arg('vol_type_id', metavar='', help='ID of volume type to be associated with.') @utils.service_type('volume') def do_qos_disassociate(cs, args): """Disassociate qos specs from specific volume type.""" cs.qos_specs.disassociate(args.qos_specs, args.vol_type_id) @utils.arg('qos_specs', metavar='', help='ID of qos_specs to be operate on.') @utils.service_type('volume') def do_qos_disassociate_all(cs, args): """Disassociate qos specs from all of its associations.""" cs.qos_specs.disassociate_all(args.qos_specs) @utils.arg('qos_specs', metavar='', help='ID of qos specs') @utils.arg('action', metavar='', choices=['set', 'unset'], help="Actions: 'set' or 'unset'") @utils.arg('metadata', metavar='key=value', nargs='+', default=[], help='QoS specs to set/unset (only key is necessary on unset)') def do_qos_key(cs, args): """Set or unset specifications for a qos spec.""" keypair = _extract_metadata(args) if args.action == 'set': cs.qos_specs.set_keys(args.qos_specs, keypair) elif args.action == 'unset': cs.qos_specs.unset_keys(args.qos_specs, list(keypair)) @utils.arg('qos_specs', metavar='', help='ID of the qos_specs.') @utils.service_type('volume') def do_qos_get_association(cs, args): """Get all associations of specific qos specs.""" associations = cs.qos_specs.get_associations(args.qos_specs) _print_associations_list(associations) @utils.arg('snapshot', metavar='', help='ID of the snapshot to update metadata on.') @utils.arg('action', metavar='', choices=['set', 'unset'], help="Actions: 'set' or 'unset'") @utils.arg('metadata', metavar='', nargs='+', default=[], help='Metadata to set/unset (only key is necessary on unset)') @utils.service_type('volume') def do_snapshot_metadata(cs, args): """Set or Delete metadata of a snapshot.""" snapshot = _find_volume_snapshot(cs, args.snapshot) metadata = _extract_metadata(args) if args.action == 'set': metadata = snapshot.set_metadata(metadata) utils.print_dict(metadata._info) elif args.action == 'unset': snapshot.delete_metadata(list(metadata.keys())) @utils.arg('snapshot', metavar='', help='ID of snapshot') @utils.service_type('volume') def do_snapshot_metadata_show(cs, args): """Show metadata of given snapshot.""" snapshot = _find_volume_snapshot(cs, args.snapshot) utils.print_dict(snapshot._info['metadata'], 'Metadata-property') @utils.arg('volume', metavar='', help='ID of volume') @utils.service_type('volume') def do_metadata_show(cs, args): """Show metadata of given volume.""" volume = utils.find_volume(cs, args.volume) utils.print_dict(volume._info['metadata'], 'Metadata-property') @utils.arg('volume', metavar='', help='ID of the volume to update metadata on.') @utils.arg('metadata', metavar='', nargs='+', default=[], help='Metadata entry/entries to update.') @utils.service_type('volume') def do_metadata_update_all(cs, args): """Update all metadata of a volume.""" volume = utils.find_volume(cs, args.volume) metadata = _extract_metadata(args) metadata = volume.update_all_metadata(metadata) utils.print_dict(metadata) @utils.arg('snapshot', metavar='', help='ID of the snapshot to update metadata on.') @utils.arg('metadata', metavar='', nargs='+', default=[], help='Metadata entry/entries to update.') @utils.service_type('volume') def do_snapshot_metadata_update_all(cs, args): """Update all metadata of a snapshot.""" snapshot = _find_volume_snapshot(cs, args.snapshot) metadata = _extract_metadata(args) metadata = snapshot.update_all_metadata(metadata) utils.print_dict(metadata) @utils.arg('volume', metavar='', help='ID of the volume to update.') @utils.arg('read_only', metavar='', choices=['True', 'true', 'False', 'false'], help='Flag to indicate whether to update volume to ' 'read-only access mode.') @utils.service_type('volume') def do_readonly_mode_update(cs, args): """Update volume read-only access mode read_only.""" volume = utils.find_volume(cs, args.volume) cs.volumes.update_readonly_flag(volume, strutils.bool_from_string(args.read_only)) python-cinderclient-1.0.8/cinderclient/v1/quotas.py0000664000175300017540000000327712274343712023534 0ustar jenkinsjenkins00000000000000# 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 import base class QuotaSet(base.Resource): @property def id(self): """QuotaSet does not have a 'id' attribute but base. Resource needs it to self-refresh and QuotaSet is indexed by tenant_id. """ return self.tenant_id def update(self, *args, **kwargs): self.manager.update(self.tenant_id, *args, **kwargs) class QuotaSetManager(base.Manager): resource_class = QuotaSet def get(self, tenant_id, usage=False): if hasattr(tenant_id, 'tenant_id'): tenant_id = tenant_id.tenant_id return self._get("/os-quota-sets/%s?usage=%s" % (tenant_id, usage), "quota_set") def update(self, tenant_id, **updates): body = {'quota_set': {'tenant_id': tenant_id}} for update in updates: body['quota_set'][update] = updates[update] self._update('/os-quota-sets/%s' % (tenant_id), body) def defaults(self, tenant_id): return self._get('/os-quota-sets/%s/defaults' % tenant_id, 'quota_set') python-cinderclient-1.0.8/cinderclient/tests/0000775000175300017540000000000012274343764022460 5ustar jenkinsjenkins00000000000000python-cinderclient-1.0.8/cinderclient/tests/test_http.py0000664000175300017540000001461412274343712025047 0ustar jenkinsjenkins00000000000000# 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 import requests from cinderclient import client from cinderclient import exceptions from cinderclient.tests import utils fake_response = utils.TestResponse({ "status_code": 200, "text": '{"hi": "there"}', }) mock_request = mock.Mock(return_value=(fake_response)) bad_400_response = utils.TestResponse({ "status_code": 400, "text": '{"error": {"message": "n/a", "details": "Terrible!"}}', }) 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_500_response = utils.TestResponse({ "status_code": 500, "text": '{"error": {"message": "FAILED!", "details": "DETAILS!"}}', }) bad_500_request = mock.Mock(return_value=(bad_500_response)) def get_client(retries=0): cl = client.HTTPClient("username", "password", "project_id", "auth_test", retries=retries) return cl def get_authed_client(retries=0): cl = get_client(retries=retries) cl.management_url = "http://example.com" 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(body, {"hi": "there"}) 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_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(self.requests, [mock_request]) 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(self.requests, [mock_request]) 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_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_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() python-cinderclient-1.0.8/cinderclient/tests/__init__.py0000664000175300017540000000000012274343712024550 0ustar jenkinsjenkins00000000000000python-cinderclient-1.0.8/cinderclient/tests/test_client.py0000664000175300017540000000230412274343712025337 0ustar jenkinsjenkins00000000000000# 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 cinderclient.client import cinderclient.v1.client import cinderclient.v2.client from cinderclient.tests import utils class ClientTest(utils.TestCase): def test_get_client_class_v1(self): output = cinderclient.client.get_client_class('1') self.assertEqual(output, cinderclient.v1.client.Client) def test_get_client_class_v2(self): output = cinderclient.client.get_client_class('2') self.assertEqual(output, cinderclient.v2.client.Client) def test_get_client_class_unknown(self): self.assertRaises(cinderclient.exceptions.UnsupportedVersion, cinderclient.client.get_client_class, '0') python-cinderclient-1.0.8/cinderclient/tests/test_shell.py0000664000175300017540000000504112274343712025171 0ustar jenkinsjenkins00000000000000# 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 re import sys import fixtures from six import moves from testtools import matchers from cinderclient import exceptions import cinderclient.shell from cinderclient.tests 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', } # Patch os.environ to avoid required auth info. 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 = cinderclient.shell.OpenStackCinderShell() _shell.main(argstr.split()) except SystemExit: exc_type, exc_value, exc_traceback = sys.exc_info() self.assertEqual(exc_value.code, 0) 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+Add a new volume.', '.*?(?m)^See "cinder help COMMAND" for help on a specific command', ] 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)^List all the volumes.', ] help_text = self.shell('help list') for r in required: self.assertThat(help_text, matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE)) python-cinderclient-1.0.8/cinderclient/tests/utils.py0000664000175300017540000000372412274343712024171 0ustar jenkinsjenkins00000000000000# 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 fixtures import requests import testtools 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)) class TestResponse(requests.Response): """Class used to wrap requests.Response and provide some convenience to initialize with a dict. """ def __init__(self, data): self._text = None super(TestResponse, self) if isinstance(data, dict): self.status_code = data.get('status_code', None) self.headers = data.get('headers', None) # Fake the text attribute to streamline Response creation self._text = data.get('text', None) else: self.status_code = data def __eq__(self, other): return self.__dict__ == other.__dict__ @property def text(self): return self._text python-cinderclient-1.0.8/cinderclient/tests/test_service_catalog.py0000664000175300017540000002435412274343712027224 0ustar jenkinsjenkins00000000000000# 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 exceptions from cinderclient import service_catalog from cinderclient.tests import utils # Taken directly from keystone/content/common/samples/auth.json # Do not edit this structure. Instead, grab the latest from there. SERVICE_CATALOG = { "access": { "token": { "id": "ab48a9efdfedb23ty3494", "expires": "2010-11-01T03:32:15-05:00", "tenant": { "id": "345", "name": "My Project" } }, "user": { "id": "123", "name": "jqsmith", "roles": [ { "id": "234", "name": "compute:admin", }, { "id": "235", "name": "object-store:admin", "tenantId": "1", } ], "roles_links": [], }, "serviceCatalog": [ { "name": "Cloud Servers", "type": "compute", "endpoints": [ { "tenantId": "1", "publicURL": "https://compute1.host/v1/1234", "internalURL": "https://compute1.host/v1/1234", "region": "North", "versionId": "1.0", "versionInfo": "https://compute1.host/v1/", "versionList": "https://compute1.host/" }, { "tenantId": "2", "publicURL": "https://compute1.host/v1/3456", "internalURL": "https://compute1.host/v1/3456", "region": "North", "versionId": "1.1", "versionInfo": "https://compute1.host/v1/", "versionList": "https://compute1.host/" }, ], "endpoints_links": [], }, { "name": "Cinder Volume Service", "type": "volume", "endpoints": [ { "tenantId": "1", "publicURL": "https://volume1.host/v1/1234", "internalURL": "https://volume1.host/v1/1234", "region": "South", "versionId": "1.0", "versionInfo": "uri", "versionList": "uri" }, { "tenantId": "2", "publicURL": "https://volume1.host/v1/3456", "internalURL": "https://volume1.host/v1/3456", "region": "South", "versionId": "1.1", "versionInfo": "https://volume1.host/v1/", "versionList": "https://volume1.host/" }, ], "endpoints_links": [ { "rel": "next", "href": "https://identity1.host/v2.0/endpoints" }, ], }, { "name": "Cinder Volume Service V2", "type": "volumev2", "endpoints": [ { "tenantId": "1", "publicURL": "https://volume1.host/v2/1234", "internalURL": "https://volume1.host/v2/1234", "region": "South", "versionId": "2.0", "versionInfo": "uri", "versionList": "uri" }, { "tenantId": "2", "publicURL": "https://volume1.host/v2/3456", "internalURL": "https://volume1.host/v2/3456", "region": "South", "versionId": "1.1", "versionInfo": "https://volume1.host/v2/", "versionList": "https://volume1.host/" }, ], "endpoints_links": [ { "rel": "next", "href": "https://identity1.host/v2.0/endpoints" }, ], }, ], "serviceCatalog_links": [ { "rel": "next", "href": "https://identity.host/v2.0/endpoints?session=2hfh8Ar", }, ], }, } SERVICE_COMPATIBILITY_CATALOG = { "access": { "token": { "id": "ab48a9efdfedb23ty3494", "expires": "2010-11-01T03:32:15-05:00", "tenant": { "id": "345", "name": "My Project" } }, "user": { "id": "123", "name": "jqsmith", "roles": [ { "id": "234", "name": "compute:admin", }, { "id": "235", "name": "object-store:admin", "tenantId": "1", } ], "roles_links": [], }, "serviceCatalog": [ { "name": "Cloud Servers", "type": "compute", "endpoints": [ { "tenantId": "1", "publicURL": "https://compute1.host/v1/1234", "internalURL": "https://compute1.host/v1/1234", "region": "North", "versionId": "1.0", "versionInfo": "https://compute1.host/v1/", "versionList": "https://compute1.host/" }, { "tenantId": "2", "publicURL": "https://compute1.host/v1/3456", "internalURL": "https://compute1.host/v1/3456", "region": "North", "versionId": "1.1", "versionInfo": "https://compute1.host/v1/", "versionList": "https://compute1.host/" }, ], "endpoints_links": [], }, { "name": "Cinder Volume Service V2", "type": "volume", "endpoints": [ { "tenantId": "1", "publicURL": "https://volume1.host/v2/1234", "internalURL": "https://volume1.host/v2/1234", "region": "South", "versionId": "2.0", "versionInfo": "uri", "versionList": "uri" }, { "tenantId": "2", "publicURL": "https://volume1.host/v2/3456", "internalURL": "https://volume1.host/v2/3456", "region": "South", "versionId": "1.1", "versionInfo": "https://volume1.host/v2/", "versionList": "https://volume1.host/" }, ], "endpoints_links": [ { "rel": "next", "href": "https://identity1.host/v2.0/endpoints" }, ], }, ], "serviceCatalog_links": [ { "rel": "next", "href": "https://identity.host/v2.0/endpoints?session=2hfh8Ar", }, ], }, } class ServiceCatalogTest(utils.TestCase): def test_building_a_service_catalog(self): sc = service_catalog.ServiceCatalog(SERVICE_CATALOG) self.assertRaises(exceptions.AmbiguousEndpoints, sc.url_for, service_type='compute') self.assertEqual(sc.url_for('tenantId', '1', service_type='compute'), "https://compute1.host/v1/1234") self.assertEqual(sc.url_for('tenantId', '2', service_type='compute'), "https://compute1.host/v1/3456") self.assertRaises(exceptions.EndpointNotFound, sc.url_for, "region", "South", service_type='compute') def test_alternate_service_type(self): sc = service_catalog.ServiceCatalog(SERVICE_CATALOG) self.assertRaises(exceptions.AmbiguousEndpoints, sc.url_for, service_type='volume') self.assertEqual(sc.url_for('tenantId', '1', service_type='volume'), "https://volume1.host/v1/1234") self.assertEqual(sc.url_for('tenantId', '2', service_type='volume'), "https://volume1.host/v1/3456") self.assertEqual(sc.url_for('tenantId', '2', service_type='volumev2'), "https://volume1.host/v2/3456") self.assertEqual(sc.url_for('tenantId', '2', service_type='volumev2'), "https://volume1.host/v2/3456") self.assertRaises(exceptions.EndpointNotFound, sc.url_for, "region", "North", service_type='volume') def test_compatibility_service_type(self): sc = service_catalog.ServiceCatalog(SERVICE_COMPATIBILITY_CATALOG) self.assertEqual(sc.url_for('tenantId', '1', service_type='volume'), "https://volume1.host/v2/1234") self.assertEqual(sc.url_for('tenantId', '2', service_type='volume'), "https://volume1.host/v2/3456") python-cinderclient-1.0.8/cinderclient/tests/test_base.py0000664000175300017540000000417712274343712025005 0ustar jenkinsjenkins00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from cinderclient import base from cinderclient import exceptions from cinderclient.v1 import volumes from cinderclient.tests import utils from cinderclient.tests.v1 import fakes cs = fakes.FakeClient() class BaseTest(utils.TestCase): def test_resource_repr(self): r = base.Resource(None, dict(foo="bar", baz="spam")) self.assertEqual(repr(r), "") def test_getid(self): self.assertEqual(base.getid(4), 4) class TmpObject(object): id = 4 self.assertEqual(base.getid(TmpObject), 4) def test_eq(self): # Two resources of the same type with the same id: equal r1 = base.Resource(None, {'id': 1, 'name': 'hi'}) r2 = base.Resource(None, {'id': 1, 'name': 'hello'}) self.assertEqual(r1, r2) # Two resoruces 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') python-cinderclient-1.0.8/cinderclient/tests/v1/0000775000175300017540000000000012274343764023006 5ustar jenkinsjenkins00000000000000python-cinderclient-1.0.8/cinderclient/tests/v1/test_limits.py0000664000175300017540000001371512274343712025720 0ustar jenkinsjenkins00000000000000# 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 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("", 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.assertTrue(item in [l1, l2]) class TestRateLimit(utils.TestCase): def test_equal(self): l1 = _get_default_RateLimit() l2 = _get_default_RateLimit() self.assertTrue(l1 == l2) def test_not_equal_verbs(self): l1 = _get_default_RateLimit() l2 = _get_default_RateLimit(verb="verb2") self.assertFalse(l1 == l2) def test_not_equal_uris(self): l1 = _get_default_RateLimit() l2 = _get_default_RateLimit(uri="uri2") self.assertFalse(l1 == l2) def test_not_equal_regexps(self): l1 = _get_default_RateLimit() l2 = _get_default_RateLimit(regex="regex2") self.assertFalse(l1 == l2) def test_not_equal_values(self): l1 = _get_default_RateLimit() l2 = _get_default_RateLimit(value="value2") self.assertFalse(l1 == l2) def test_not_equal_remains(self): l1 = _get_default_RateLimit() l2 = _get_default_RateLimit(remain="remain2") self.assertFalse(l1 == l2) def test_not_equal_units(self): l1 = _get_default_RateLimit() l2 = _get_default_RateLimit(unit="unit2") self.assertFalse(l1 == l2) def test_not_equal_next_available(self): l1 = _get_default_RateLimit() l2 = _get_default_RateLimit(next_available="next2") self.assertFalse(l1 == l2) def test_repr(self): l1 = _get_default_RateLimit() self.assertEqual("", repr(l1)) class TestAbsoluteLimit(utils.TestCase): def test_equal(self): l1 = limits.AbsoluteLimit("name1", "value1") l2 = limits.AbsoluteLimit("name1", "value1") self.assertTrue(l1 == l2) def test_not_equal_values(self): l1 = limits.AbsoluteLimit("name1", "value1") l2 = limits.AbsoluteLimit("name1", "value2") self.assertFalse(l1 == l2) def test_not_equal_names(self): l1 = limits.AbsoluteLimit("name1", "value1") l2 = limits.AbsoluteLimit("name2", "value1") self.assertFalse(l1 == l2) def test_repr(self): l1 = limits.AbsoluteLimit("name1", "value1") self.assertEqual("", 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(l, l1) python-cinderclient-1.0.8/cinderclient/tests/v1/test_volume_transfers.py0000664000175300017540000000334212274343712030010 0ustar jenkinsjenkins00000000000000# 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 import utils from cinderclient.tests.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) python-cinderclient-1.0.8/cinderclient/tests/v1/test_quotas.py0000664000175300017540000000340512274343712025726 0ustar jenkinsjenkins00000000000000# 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 import utils from cinderclient.tests.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) 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) q2.volumes = 0 self.assertNotEqual(q.volumes, q2.volumes) q2.snapshots = 0 self.assertNotEqual(q.snapshots, q2.snapshots) q2.get() self.assertEqual(q.volumes, q2.volumes) self.assertEqual(q.snapshots, q2.snapshots) python-cinderclient-1.0.8/cinderclient/tests/v1/test_availability_zone.py0000664000175300017540000000607712274343712030127 0ustar jenkinsjenkins00000000000000# 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 import utils from cinderclient.tests.v1 import fakes cs = fakes.FakeClient() class AvailabilityZoneTest(utils.TestCase): def _assertZone(self, zone, name, status): self.assertEqual(zone.zoneName, name) self.assertEqual(zone.zoneState, status) def test_list_availability_zone(self): zones = cs.availability_zones.list(detailed=False) cs.assert_called('GET', '/os-availability-zone') for zone in zones: self.assertTrue(isinstance(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((len(z0), len(z1)), (1, 1)) self._assertZone(z0[0], l0[0], l0[1]) self._assertZone(z1[0], l1[0], l1[1]) def test_detail_availability_zone(self): zones = cs.availability_zones.list(detailed=True) cs.assert_called('GET', '/os-availability-zone/detail') for zone in zones: self.assertTrue(isinstance(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((len(z0), len(z1), len(z2)), (3, 3, 1)) 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]) python-cinderclient-1.0.8/cinderclient/tests/v1/test_volume_backups.py0000664000175300017540000000360312274343712027431 0ustar jenkinsjenkins00000000000000# 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 import utils from cinderclient.tests.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) python-cinderclient-1.0.8/cinderclient/tests/v1/__init__.py0000664000175300017540000000000012274343712025076 0ustar jenkinsjenkins00000000000000python-cinderclient-1.0.8/cinderclient/tests/v1/contrib/0000775000175300017540000000000012274343764024446 5ustar jenkinsjenkins00000000000000python-cinderclient-1.0.8/cinderclient/tests/v1/contrib/__init__.py0000664000175300017540000000000012274343712026536 0ustar jenkinsjenkins00000000000000python-cinderclient-1.0.8/cinderclient/tests/v1/contrib/test_list_extensions.py0000664000175300017540000000223112274343712031300 0ustar jenkinsjenkins00000000000000# 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 import utils from cinderclient.tests.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.assertTrue(len(all_exts) > 0) for r in all_exts: self.assertTrue(len(r.summary) > 0) python-cinderclient-1.0.8/cinderclient/tests/v1/test_types.py0000664000175300017540000000310412274343712025552 0ustar jenkinsjenkins00000000000000# 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 import utils from cinderclient.tests.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.assertTrue(isinstance(t, volume_types.VolumeType)) def test_create(self): t = cs.volume_types.create('test-type-3') cs.assert_called('POST', '/types') self.assertTrue(isinstance(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_unsset_keys(self): t = cs.volume_types.get(1) t.unset_keys(['k']) cs.assert_called('DELETE', '/types/1/extra_specs/k') def test_delete(self): cs.volume_types.delete(1) cs.assert_called('DELETE', '/types/1') python-cinderclient-1.0.8/cinderclient/tests/v1/test_snapshot_actions.py0000664000175300017540000000260712274343712027774 0ustar jenkinsjenkins00000000000000# 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 import utils from cinderclient.tests.v1 import fakes cs = fakes.FakeClient() class SnapshotActionsTest(utils.TestCase): def test_update_snapshot_status(self): s = cs.volume_snapshots.get('1234') cs.volume_snapshots.update_snapshot_status(s, {'status': 'available'}) cs.assert_called('POST', '/snapshots/1234/action') def test_update_snapshot_status_with_progress(self): s = cs.volume_snapshots.get('1234') cs.volume_snapshots.update_snapshot_status(s, {'status': 'available', 'progress': '73%'}) cs.assert_called('POST', '/snapshots/1234/action') python-cinderclient-1.0.8/cinderclient/tests/v1/test_volume_encryption_types.py0000664000175300017540000000776412274343712031433 0ustar jenkinsjenkins00000000000000# 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 import utils from cinderclient.tests.v1 import fakes cs = fakes.FakeClient() 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, {'encryption': {'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.assertIsNone(result, "delete result must be None") python-cinderclient-1.0.8/cinderclient/tests/v1/test_volumes.py0000664000175300017540000000713212274343712026105 0ustar jenkinsjenkins00000000000000# 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 import utils from cinderclient.tests.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_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') python-cinderclient-1.0.8/cinderclient/tests/v1/test_shell.py0000664000175300017540000003573512274343712025534 0ustar jenkinsjenkins00000000000000# 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 from cinderclient import client from cinderclient import shell from cinderclient.v1 import shell as shell_v1 from cinderclient.tests.v1 import fakes from cinderclient.tests import utils class ShellTest(utils.TestCase): FAKE_ENV = { 'CINDER_USERNAME': 'username', 'CINDER_PASSWORD': 'password', 'CINDER_PROJECT_ID': 'project_id', 'OS_VOLUME_API_VERSION': '1', 'CINDER_URL': 'http://no.where', } # 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_get_client_class = client.get_client_class client.get_client_class = lambda *_: fakes.FakeClient def tearDown(self): # For some method like test_image_meta_bad_action we are # testing a SystemExit to be thrown and object self.shell has # no time to get instantatiated which is OK in this case, so # we make sure the method is there before launching it. if hasattr(self.shell, 'cs'): self.shell.cs.clear_callstack() #HACK(bcwaldon): replace this when we start using stubs client.get_client_class = self.old_get_client_class super(ShellTest, self).tearDown() 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: def __init__(self, metadata=[]): self.metadata = metadata 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(shell_v1._extract_metadata(args), input[1]) 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_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_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') 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_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_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': None}} 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_service_disable(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') python-cinderclient-1.0.8/cinderclient/tests/v1/testfile.txt0000664000175300017540000000000512274343712025352 0ustar jenkinsjenkins00000000000000BLAH python-cinderclient-1.0.8/cinderclient/tests/v1/test_auth.py0000664000175300017540000003372412274343712025362 0ustar jenkinsjenkins00000000000000# 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 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": "12345", "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(cs.client.management_url, public_url) token_id = resp["access"]["token"]["id"] self.assertEqual(cs.client.auth_token, token_id) 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": "12345", "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(cs.client.management_url, public_url) token_id = resp["access"]["token"]["id"] self.assertEqual(cs.client.auth_token, token_id) tenant_id = resp["access"]["token"]["tenant"]["id"] self.assertEqual(cs.client.tenant_id, 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": "12345", "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(cs.client.management_url, public_url) token_id = resp["access"]["token"]["id"] self.assertEqual(cs.client.auth_token, token_id) test_auth_call() def test_ambiguous_endpoints(self): cs = client.Client("username", "password", "project_id", "http://localhost:8776/v1", service_type='volume') resp = { "access": { "token": { "expires": "12345", "id": "FAKE_ID", }, "serviceCatalog": [ { "adminURL": "http://localhost:8776/v1", "type": "volume", "name": "Cinder Volume Service", "endpoints": [ { "region": "RegionOne", "internalURL": "http://localhost:8776/v1", "publicURL": "http://localhost:8776/v1", }, ], }, { "adminURL": "http://localhost:8776/v1", "type": "volume", "name": "Cinder Volume Cloud Service", "endpoints": [ { "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(): self.assertRaises(exceptions.AmbiguousEndpoints, cs.client.authenticate) 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(cs.client.management_url, auth_response.headers['x-server-management-url']) self.assertEqual(cs.client.auth_token, auth_response.headers['x-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('/') m.assert_called() mock_request.assert_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() m.assert_called() test_auth_call() python-cinderclient-1.0.8/cinderclient/tests/v1/test_quota_classes.py0000664000175300017540000000265012274343712027261 0ustar jenkinsjenkins00000000000000# 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 import utils from cinderclient.tests.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) 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) q2.volumes = 0 self.assertNotEqual(q.volumes, q2.volumes) q2.get() self.assertEqual(q.volumes, q2.volumes) python-cinderclient-1.0.8/cinderclient/tests/v1/test_services.py0000664000175300017540000000533712274343712026243 0ustar jenkinsjenkins00000000000000# 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 import utils from cinderclient.tests.v1 import fakes from cinderclient.v1 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(len(svs), 3) [self.assertTrue(isinstance(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(len(svs), 2) [self.assertTrue(isinstance(s, services.Service)) for s in svs] [self.assertEqual(s.host, 'host2') 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(len(svs), 2) [self.assertTrue(isinstance(s, services.Service)) for s in svs] [self.assertEqual(s.binary, 'cinder-volume') 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(len(svs), 1) [self.assertTrue(isinstance(s, services.Service)) for s in svs] [self.assertEqual(s.host, 'host2') for s in svs] [self.assertEqual(s.binary, 'cinder-volume') 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.assertTrue(isinstance(s, services.Service)) self.assertEqual(s.status, 'enabled') 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.assertTrue(isinstance(s, services.Service)) self.assertEqual(s.status, 'disabled') python-cinderclient-1.0.8/cinderclient/tests/v1/test_qos.py0000664000175300017540000000563712274343712025225 0ustar jenkinsjenkins00000000000000# 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 import utils from cinderclient.tests.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) python-cinderclient-1.0.8/cinderclient/tests/v1/fakes.py0000664000175300017540000006203512274343712024450 0ustar jenkinsjenkins00000000000000# 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 try: import urlparse except ImportError: import urllib.parse as urlparse from cinderclient import client as base_client from cinderclient.tests import fakes import cinderclient.tests.utils as utils from cinderclient.v1 import client def _stub_volume(**kwargs): volume = { 'id': '1234', 'display_name': None, 'display_description': None, "attachments": [], "bootable": "false", "availability_zone": "cinder", "created_at": "2012-08-27T00:00:00.000000", "id": '00000000-0000-0000-0000-000000000000', "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, *args, **kwargs): client.Client.__init__(self, 'username', 'password', 'project_id', 'auth_url', extensions=kwargs.get('extensions')) 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 if hasattr(status, 'items'): return utils.TestResponse(status), body else: return utils.TestResponse({"status": status}), 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, {}, {}) # # 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, 'name': 'sample-volume'}, {'id': 5678, '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), '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': assert sorted(list(body[action])) == ['instance_uuid', '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'] 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}}) def get_os_quota_sets_test_defaults(self): return (200, {}, {'quota_set': { 'tenant_id': 'test', 'metadata_items': [], 'volumes': 1, 'snapshots': 1, '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}}) # # 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}}) 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}}) # # 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(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'}) def get_types_2_encryption(self, **kw): return (200, {}, {}) def post_types_2_encryption(self, body, **kw): return (200, {}, {'encryption': {}}) 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 = filter(lambda i: i['host'] == host, services) if binary: services = filter(lambda i: i['binary'] == binary, services) 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 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"}}) python-cinderclient-1.0.8/cinderclient/tests/fakes.py0000664000175300017540000000522012274343712024113 0ustar jenkinsjenkins00000000000000# 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=[], optional=[]): 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 assert_called(self, method, url, 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: assert self.client.callstack[pos][2] == body def assert_called_anytime(self, method, url, 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 def clear_callstack(self): self.client.callstack = [] def authenticate(self): pass python-cinderclient-1.0.8/cinderclient/tests/test_utils.py0000664000175300017540000000771712274343712025236 0ustar jenkinsjenkins00000000000000# 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 sys from six import moves from cinderclient import exceptions from cinderclient import utils from cinderclient import base from cinderclient.tests import utils as test_utils UUID = '8e8ec658-c7b0-4243-bdf8-6f7f2952c0d0' class FakeResource(object): def __init__(self, _id, properties): self.id = _id try: self.name = properties['name'] except KeyError: pass try: self.display_name = properties['display_name'] except KeyError: pass class FakeManager(base.ManagerWithFind): resource_class = FakeResource resources = [ FakeResource('1234', {'name': 'entity_one'}), FakeResource(UUID, {'name': 'entity_two'}), FakeResource('4242', {'display_name': 'entity_three'}), FakeResource('5678', {'name': '9876'}) ] def get(self, resource_id): for resource in self.resources: if resource.id == str(resource_id): return resource raise exceptions.NotFound(resource_id) def list(self, search_opts): return self.resources class FindResourceTestCase(test_utils.TestCase): def setUp(self): super(FindResourceTestCase, self).setUp() self.manager = FakeManager(None) def test_find_none(self): self.assertRaises(exceptions.CommandError, utils.find_resource, self.manager, 'asdf') def test_find_by_integer_id(self): output = utils.find_resource(self.manager, 1234) self.assertEqual(output, self.manager.get('1234')) def test_find_by_str_id(self): output = utils.find_resource(self.manager, '1234') self.assertEqual(output, self.manager.get('1234')) def test_find_by_uuid(self): output = utils.find_resource(self.manager, UUID) self.assertEqual(output, self.manager.get(UUID)) def test_find_by_str_name(self): output = utils.find_resource(self.manager, 'entity_one') self.assertEqual(output, self.manager.get('1234')) def test_find_by_str_displayname(self): output = utils.find_resource(self.manager, 'entity_three') self.assertEqual(output, self.manager.get('4242')) class CaptureStdout(object): """Context manager for capturing stdout from statments in its's 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 PrintListTestCase(test_utils.TestCase): def test_print_list_with_list(self): Row = collections.namedtuple('Row', ['a', 'b']) to_print = [Row(a=1, b=2), Row(a=3, b=4)] with CaptureStdout() as cso: utils.print_list(to_print, ['a', 'b']) self.assertEqual(cso.read(), """\ +---+---+ | a | b | +---+---+ | 1 | 2 | | 3 | 4 | +---+---+ """) 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(cso.read(), """\ +---+---+ | a | b | +---+---+ | 1 | 2 | | 3 | 4 | +---+---+ """) python-cinderclient-1.0.8/cinderclient/tests/v2/0000775000175300017540000000000012274343764023007 5ustar jenkinsjenkins00000000000000python-cinderclient-1.0.8/cinderclient/tests/v2/test_limits.py0000664000175300017540000001371512274343712025721 0ustar jenkinsjenkins00000000000000# 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 import utils from cinderclient.v2 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("", 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.assertTrue(item in [l1, l2]) class TestRateLimit(utils.TestCase): def test_equal(self): l1 = _get_default_RateLimit() l2 = _get_default_RateLimit() self.assertTrue(l1 == l2) def test_not_equal_verbs(self): l1 = _get_default_RateLimit() l2 = _get_default_RateLimit(verb="verb2") self.assertFalse(l1 == l2) def test_not_equal_uris(self): l1 = _get_default_RateLimit() l2 = _get_default_RateLimit(uri="uri2") self.assertFalse(l1 == l2) def test_not_equal_regexps(self): l1 = _get_default_RateLimit() l2 = _get_default_RateLimit(regex="regex2") self.assertFalse(l1 == l2) def test_not_equal_values(self): l1 = _get_default_RateLimit() l2 = _get_default_RateLimit(value="value2") self.assertFalse(l1 == l2) def test_not_equal_remains(self): l1 = _get_default_RateLimit() l2 = _get_default_RateLimit(remain="remain2") self.assertFalse(l1 == l2) def test_not_equal_units(self): l1 = _get_default_RateLimit() l2 = _get_default_RateLimit(unit="unit2") self.assertFalse(l1 == l2) def test_not_equal_next_available(self): l1 = _get_default_RateLimit() l2 = _get_default_RateLimit(next_available="next2") self.assertFalse(l1 == l2) def test_repr(self): l1 = _get_default_RateLimit() self.assertEqual("", repr(l1)) class TestAbsoluteLimit(utils.TestCase): def test_equal(self): l1 = limits.AbsoluteLimit("name1", "value1") l2 = limits.AbsoluteLimit("name1", "value1") self.assertTrue(l1 == l2) def test_not_equal_values(self): l1 = limits.AbsoluteLimit("name1", "value1") l2 = limits.AbsoluteLimit("name1", "value2") self.assertFalse(l1 == l2) def test_not_equal_names(self): l1 = limits.AbsoluteLimit("name1", "value1") l2 = limits.AbsoluteLimit("name2", "value1") self.assertFalse(l1 == l2) def test_repr(self): l1 = limits.AbsoluteLimit("name1", "value1") self.assertEqual("", 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(l, l1) python-cinderclient-1.0.8/cinderclient/tests/v2/test_volume_transfers.py0000664000175300017540000000334212274343712030011 0ustar jenkinsjenkins00000000000000# 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 import utils from cinderclient.tests.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) python-cinderclient-1.0.8/cinderclient/tests/v2/test_quotas.py0000664000175300017540000000340512274343712025727 0ustar jenkinsjenkins00000000000000# 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 import utils from cinderclient.tests.v2 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) 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) q2.volumes = 0 self.assertNotEqual(q.volumes, q2.volumes) q2.snapshots = 0 self.assertNotEqual(q.snapshots, q2.snapshots) q2.get() self.assertEqual(q.volumes, q2.volumes) self.assertEqual(q.snapshots, q2.snapshots) python-cinderclient-1.0.8/cinderclient/tests/v2/test_availability_zone.py0000664000175300017540000000607712274343712030130 0ustar jenkinsjenkins00000000000000# 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 import utils from cinderclient.tests.v1 import fakes cs = fakes.FakeClient() class AvailabilityZoneTest(utils.TestCase): def _assertZone(self, zone, name, status): self.assertEqual(zone.zoneName, name) self.assertEqual(zone.zoneState, status) def test_list_availability_zone(self): zones = cs.availability_zones.list(detailed=False) cs.assert_called('GET', '/os-availability-zone') for zone in zones: self.assertTrue(isinstance(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((len(z0), len(z1)), (1, 1)) self._assertZone(z0[0], l0[0], l0[1]) self._assertZone(z1[0], l1[0], l1[1]) def test_detail_availability_zone(self): zones = cs.availability_zones.list(detailed=True) cs.assert_called('GET', '/os-availability-zone/detail') for zone in zones: self.assertTrue(isinstance(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((len(z0), len(z1), len(z2)), (3, 3, 1)) 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]) python-cinderclient-1.0.8/cinderclient/tests/v2/test_volume_backups.py0000664000175300017540000000360312274343712027432 0ustar jenkinsjenkins00000000000000# 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 import utils from cinderclient.tests.v2 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) python-cinderclient-1.0.8/cinderclient/tests/v2/__init__.py0000664000175300017540000000000012274343712025077 0ustar jenkinsjenkins00000000000000python-cinderclient-1.0.8/cinderclient/tests/v2/contrib/0000775000175300017540000000000012274343764024447 5ustar jenkinsjenkins00000000000000python-cinderclient-1.0.8/cinderclient/tests/v2/contrib/__init__.py0000664000175300017540000000000012274343712026537 0ustar jenkinsjenkins00000000000000python-cinderclient-1.0.8/cinderclient/tests/v2/contrib/test_list_extensions.py0000664000175300017540000000237012274343712031305 0ustar jenkinsjenkins00000000000000# 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 import utils from cinderclient.tests.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.assertTrue(len(all_exts) > 0) for r in all_exts: self.assertTrue(len(r.summary) > 0) python-cinderclient-1.0.8/cinderclient/tests/v2/test_types.py0000664000175300017540000000324412274343712025560 0ustar jenkinsjenkins00000000000000# 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 import utils from cinderclient.tests.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') for t in tl: self.assertTrue(isinstance(t, volume_types.VolumeType)) def test_create(self): t = cs.volume_types.create('test-type-3') cs.assert_called('POST', '/types') self.assertTrue(isinstance(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_unsset_keys(self): t = cs.volume_types.get(1) t.unset_keys(['k']) cs.assert_called('DELETE', '/types/1/extra_specs/k') def test_delete(self): cs.volume_types.delete(1) cs.assert_called('DELETE', '/types/1') python-cinderclient-1.0.8/cinderclient/tests/v2/test_snapshot_actions.py0000664000175300017540000000260712274343712027775 0ustar jenkinsjenkins00000000000000# 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 import utils from cinderclient.tests.v2 import fakes cs = fakes.FakeClient() class SnapshotActionsTest(utils.TestCase): def test_update_snapshot_status(self): s = cs.volume_snapshots.get('1234') cs.volume_snapshots.update_snapshot_status(s, {'status': 'available'}) cs.assert_called('POST', '/snapshots/1234/action') def test_update_snapshot_status_with_progress(self): s = cs.volume_snapshots.get('1234') cs.volume_snapshots.update_snapshot_status(s, {'status': 'available', 'progress': '73%'}) cs.assert_called('POST', '/snapshots/1234/action') python-cinderclient-1.0.8/cinderclient/tests/v2/test_volume_encryption_types.py0000664000175300017540000000776412274343712031434 0ustar jenkinsjenkins00000000000000# 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 import utils from cinderclient.tests.v2 import fakes cs = fakes.FakeClient() 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, {'encryption': {'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.assertIsNone(result, "delete result must be None") python-cinderclient-1.0.8/cinderclient/tests/v2/test_volumes.py0000664000175300017540000000775112274343712026115 0ustar jenkinsjenkins00000000000000# 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 import utils from cinderclient.tests.v2 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='ro') 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': 'v2'}) cs.assert_called('POST', '/volumes/1234/metadata', {'metadata': {'k1': 'v2'}}) 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_retype(self): v = cs.volumes.get('1234') cs.volumes.retype(v, 'foo', 'on-demand') cs.assert_called('POST', '/volumes/1234/action', {'os-retype': {'new_type': 'foo', 'migration_policy': 'on-demand'}}) python-cinderclient-1.0.8/cinderclient/tests/v2/test_shell.py0000664000175300017540000003527612274343712025535 0ustar jenkinsjenkins00000000000000# 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 fixtures from cinderclient import client from cinderclient import shell from cinderclient.tests import utils from cinderclient.tests.v2 import fakes class ShellTest(utils.TestCase): FAKE_ENV = { 'CINDER_USERNAME': 'username', 'CINDER_PASSWORD': 'password', 'CINDER_PROJECT_ID': 'project_id', 'OS_VOLUME_API_VERSION': '2', 'CINDER_URL': 'http://no.where', } # 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_get_client_class = client.get_client_class client.get_client_class = lambda *_: fakes.FakeClient def tearDown(self): # For some method like test_image_meta_bad_action we are # testing a SystemExit to be thrown and object self.shell has # no time to get instantatiated which is OK in this case, so # we make sure the method is there before launching it. if hasattr(self.shell, 'cs'): self.shell.cs.clear_callstack() #HACK(bcwaldon): replace this when we start using stubs client.get_client_class = self.old_get_client_class super(ShellTest, self).tearDown() 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_list(self): self.run_command('list') # NOTE(jdg): we default to detail currently self.assert_called('GET', '/volumes/detail') def test_list_filter_status(self): self.run_command('list --status=available') self.assert_called('GET', '/volumes/detail?status=available') def test_list_filter_name(self): self.run_command('list --name=1234') self.assert_called('GET', '/volumes/detail?name=1234') def test_list_all_tenants(self): self.run_command('list --all-tenants=1') self.assert_called('GET', '/volumes/detail?all_tenants=1') def test_list_availability_zone(self): self.run_command('availability-zone-list') self.assert_called('GET', '/os-availability-zone') 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') 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': {'name': 'new-name'}} self.assert_called('PUT', '/volumes/1234', body=expected) # change description only self.run_command('rename 1234 --description=new-description') expected = {'volume': {'description': 'new-description'}} self.assert_called('PUT', '/volumes/1234', body=expected) # rename and change description self.run_command('rename 1234 new-name ' '--description=new-description') expected = {'volume': { 'name': 'new-name', 'description': 'new-description', }} self.assert_called('PUT', '/volumes/1234', body=expected) # Call rename with no arguments self.assertRaises(SystemExit, self.run_command, 'rename') def test_rename_snapshot(self): # basic rename with positional arguments self.run_command('snapshot-rename 1234 new-name') expected = {'snapshot': {'name': 'new-name'}} self.assert_called('PUT', '/snapshots/1234', body=expected) # change description only self.run_command('snapshot-rename 1234 ' '--description=new-description') expected = {'snapshot': {'description': 'new-description'}} self.assert_called('PUT', '/snapshots/1234', body=expected) # snapshot-rename and change description self.run_command('snapshot-rename 1234 new-name ' '--description=new-description') expected = {'snapshot': { 'name': 'new-name', 'description': 'new-description', }} self.assert_called('PUT', '/snapshots/1234', body=expected) # Call snapshot-rename with no arguments self.assertRaises(SystemExit, self.run_command, 'snapshot-rename') def test_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_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_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': None}} 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_service_enable(self): self.run_command('service-enable host cinder-volume') self.assert_called('PUT', '/os-services/enable', {"binary": "cinder-volume", "host": "host"}) def test_retype_with_policy(self): self.run_command('retype 1234 foo --migration-policy=on-demand') expected = {'os-retype': {'new_type': 'foo', 'migration_policy': 'on-demand'}} self.assert_called('POST', '/volumes/1234/action', body=expected) def test_retype_default_policy(self): self.run_command('retype 1234 foo') expected = {'os-retype': {'new_type': 'foo', 'migration_policy': 'never'}} self.assert_called('POST', '/volumes/1234/action', body=expected) def test_snapshot_delete(self): self.run_command('snapshot-delete 1234') self.assert_called('DELETE', '/snapshots/1234') python-cinderclient-1.0.8/cinderclient/tests/v2/test_auth.py0000664000175300017540000003407312274343712025361 0ustar jenkinsjenkins00000000000000# 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 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": "12345", "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(cs.client.management_url, public_url) token_id = resp["access"]["token"]["id"] self.assertEqual(cs.client.auth_token, token_id) 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": "12345", "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(cs.client.management_url, public_url) token_id = resp["access"]["token"]["id"] self.assertEqual(cs.client.auth_token, token_id) tenant_id = resp["access"]["token"]["tenant"]["id"] self.assertEqual(cs.client.tenant_id, 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": "12345", "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(cs.client.management_url, public_url) token_id = resp["access"]["token"]["id"] self.assertEqual(cs.client.auth_token, token_id) test_auth_call() def test_ambiguous_endpoints(self): cs = client.Client("username", "password", "project_id", "http://localhost:8776/v2", service_type='volumev2') resp = { "access": { "token": { "expires": "12345", "id": "FAKE_ID", }, "serviceCatalog": [ { "adminURL": "http://localhost:8776/v1", "type": "volumev2", "name": "Cinder Volume Service", "endpoints": [ { "region": "RegionOne", "internalURL": "http://localhost:8776/v1", "publicURL": "http://localhost:8776/v1", }, ], }, { "adminURL": "http://localhost:8776/v2", "type": "volumev2", "name": "Cinder Volume V2", "endpoints": [ { "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(): self.assertRaises(exceptions.AmbiguousEndpoints, cs.client.authenticate) 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(cs.client.management_url, auth_response.headers['x-server-management-url']) self.assertEqual(cs.client.auth_token, auth_response.headers['x-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('/') m.assert_called() mock_request.assert_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() m.assert_called() test_auth_call() python-cinderclient-1.0.8/cinderclient/tests/v2/test_quota_classes.py0000664000175300017540000000265012274343712027262 0ustar jenkinsjenkins00000000000000# 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 import utils from cinderclient.tests.v2 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) 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) q2.volumes = 0 self.assertNotEqual(q.volumes, q2.volumes) q2.get() self.assertEqual(q.volumes, q2.volumes) python-cinderclient-1.0.8/cinderclient/tests/v2/test_services.py0000664000175300017540000000533712274343712026244 0ustar jenkinsjenkins00000000000000# 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 import utils from cinderclient.tests.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(len(svs), 3) [self.assertTrue(isinstance(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(len(svs), 2) [self.assertTrue(isinstance(s, services.Service)) for s in svs] [self.assertEqual(s.host, 'host2') 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(len(svs), 2) [self.assertTrue(isinstance(s, services.Service)) for s in svs] [self.assertEqual(s.binary, 'cinder-volume') 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(len(svs), 1) [self.assertTrue(isinstance(s, services.Service)) for s in svs] [self.assertEqual(s.host, 'host2') for s in svs] [self.assertEqual(s.binary, 'cinder-volume') 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.assertTrue(isinstance(s, services.Service)) self.assertEqual(s.status, 'enabled') 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.assertTrue(isinstance(s, services.Service)) self.assertEqual(s.status, 'disabled') python-cinderclient-1.0.8/cinderclient/tests/v2/test_qos.py0000664000175300017540000000563712274343712025226 0ustar jenkinsjenkins00000000000000# 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 import utils from cinderclient.tests.v2 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) python-cinderclient-1.0.8/cinderclient/tests/v2/fakes.py0000664000175300017540000006247512274343712024461 0ustar jenkinsjenkins00000000000000# 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 try: import urlparse except ImportError: import urllib.parse as urlparse from cinderclient import client as base_client from cinderclient.tests import fakes import cinderclient.tests.utils as utils from cinderclient.v2 import client def _stub_volume(**kwargs): volume = { 'id': '1234', 'name': None, 'description': None, "attachments": [], "bootable": "false", "availability_zone": "cinder", "created_at": "2012-08-27T00:00:00.000000", "id": '00000000-0000-0000-0000-000000000000', "metadata": {}, "size": 1, "snapshot_id": None, "status": "available", "volume_type": "None", "links": [ { "href": "http://localhost/v2/fake/volumes/1234", "rel": "self" }, { "href": "http://localhost/fake/volumes/1234", "rel": "bookmark" } ], } 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/v2/%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_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_restore(): return {'volume_id': '712f4980-5ac1-41e5-9383-390aa7c9f58b'} 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, *args, **kwargs): client.Client.__init__(self, 'username', 'password', 'project_id', 'auth_url', extensions=kwargs.get('extensions')) 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/v2/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 if hasattr(status, 'items'): return utils.TestResponse(status), body else: return utils.TestResponse({"status": status}), 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, {}, {}) # # 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, 'name': 'sample-volume'}, {'id': 5678, '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), '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': assert sorted(list(body[action])) == ['instance_uuid', '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-retype': assert 'new_type' in body[action] 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}}) def get_os_quota_sets_test_defaults(self): return (200, {}, {'quota_set': { 'tenant_id': 'test', 'metadata_items': [], 'volumes': 1, 'snapshots': 1, '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}}) # # 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}}) 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}}) # # 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(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'}) def get_types_2_encryption(self, **kw): return (200, {}, {}) def post_types_2_encryption(self, body, **kw): return (200, {}, {'encryption': {}}) 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 = filter(lambda i: i['host'] == host, services) if binary: services = filter(lambda i: i['binary'] == binary, services) 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 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"}}) python-cinderclient-1.0.8/cinderclient/shell.py0000664000175300017540000005403012274343712022772 0ustar jenkinsjenkins00000000000000 # Copyright 2011 OpenStack LLC. # 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 glob import imp import itertools import os import pkgutil import sys import logging from cinderclient import client from cinderclient import exceptions as exc import cinderclient.extension from cinderclient.openstack.common import strutils from cinderclient import utils from cinderclient.v1 import shell as shell_v1 from cinderclient.v2 import shell as shell_v2 DEFAULT_OS_VOLUME_API_VERSION = "1" DEFAULT_CINDER_ENDPOINT_TYPE = 'publicURL' DEFAULT_CINDER_SERVICE_TYPE = 'volume' 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]}) class OpenStackCinderShell(object): def get_base_parser(self): parser = CinderClientArgumentParser( prog='cinder', description=__doc__.strip(), epilog='See "cinder help COMMAND" ' 'for help on a specific command.', 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('--debug', action='store_true', default=utils.env('CINDERCLIENT_DEBUG', default=False), help="Print debugging output") parser.add_argument('--os-username', metavar='', default=utils.env('OS_USERNAME', 'CINDER_USERNAME'), help='Defaults to env[OS_USERNAME].') parser.add_argument('--os_username', help=argparse.SUPPRESS) parser.add_argument('--os-password', metavar='', default=utils.env('OS_PASSWORD', 'CINDER_PASSWORD'), help='Defaults to env[OS_PASSWORD].') parser.add_argument('--os_password', help=argparse.SUPPRESS) parser.add_argument('--os-tenant-name', metavar='', default=utils.env('OS_TENANT_NAME', 'CINDER_PROJECT_ID'), help='Defaults to env[OS_TENANT_NAME].') parser.add_argument('--os_tenant_name', help=argparse.SUPPRESS) parser.add_argument('--os-tenant-id', metavar='', default=utils.env('OS_TENANT_ID', 'CINDER_TENANT_ID'), help='Defaults to env[OS_TENANT_ID].') parser.add_argument('--os_tenant_id', help=argparse.SUPPRESS) parser.add_argument('--os-auth-url', metavar='', default=utils.env('OS_AUTH_URL', 'CINDER_URL'), help='Defaults to env[OS_AUTH_URL].') parser.add_argument('--os_auth_url', help=argparse.SUPPRESS) parser.add_argument('--os-region-name', metavar='', default=utils.env('OS_REGION_NAME', 'CINDER_REGION_NAME'), help='Defaults to env[OS_REGION_NAME].') parser.add_argument('--os_region_name', help=argparse.SUPPRESS) parser.add_argument('--service-type', metavar='', help='Defaults to volume for most actions') parser.add_argument('--service_type', help=argparse.SUPPRESS) parser.add_argument('--service-name', metavar='', default=utils.env('CINDER_SERVICE_NAME'), help='Defaults to env[CINDER_SERVICE_NAME]') parser.add_argument('--service_name', help=argparse.SUPPRESS) parser.add_argument('--volume-service-name', metavar='', default=utils.env('CINDER_VOLUME_SERVICE_NAME'), help='Defaults to env[CINDER_VOLUME_SERVICE_NAME]') parser.add_argument('--volume_service_name', help=argparse.SUPPRESS) parser.add_argument('--endpoint-type', metavar='', default=utils.env('CINDER_ENDPOINT_TYPE', default=DEFAULT_CINDER_ENDPOINT_TYPE), help='Defaults to env[CINDER_ENDPOINT_TYPE] or ' + DEFAULT_CINDER_ENDPOINT_TYPE + '.') parser.add_argument('--endpoint_type', help=argparse.SUPPRESS) parser.add_argument('--os-volume-api-version', metavar='', default=utils.env('OS_VOLUME_API_VERSION', default=None), help='Accepts 1 or 2,defaults ' 'to env[OS_VOLUME_API_VERSION].') parser.add_argument('--os_volume_api_version', help=argparse.SUPPRESS) parser.add_argument('--os-cacert', metavar='', default=utils.env('OS_CACERT', default=None), help='Specify a CA bundle file to use in ' 'verifying a TLS (https) server certificate. ' 'Defaults to env[OS_CACERT]') parser.add_argument('--insecure', default=utils.env('CINDERCLIENT_INSECURE', default=False), action='store_true', help=argparse.SUPPRESS) parser.add_argument('--retries', metavar='', type=int, default=0, help='Number of retries.') # FIXME(dtroyer): The args below are here for diablo compatibility, # remove them in folsum cycle # alias for --os-username, left in for backwards compatibility parser.add_argument('--username', help=argparse.SUPPRESS) # alias for --os-region_name, left in for backwards compatibility parser.add_argument('--region_name', help=argparse.SUPPRESS) # alias for --os-password, left in for backwards compatibility parser.add_argument('--apikey', '--password', dest='apikey', default=utils.env('CINDER_API_KEY'), help=argparse.SUPPRESS) # alias for --os-tenant-name, left in for backward compatibility parser.add_argument('--projectid', '--tenant_name', dest='projectid', default=utils.env('CINDER_PROJECT_ID'), help=argparse.SUPPRESS) # alias for --os-auth-url, left in for backward compatibility parser.add_argument('--url', '--auth_url', dest='url', default=utils.env('CINDER_URL'), help=argparse.SUPPRESS) return parser def get_subcommand_parser(self, version): parser = self.get_base_parser() self.subcommands = {} subparsers = parser.add_subparsers(metavar='') try: actions_module = { '1.1': shell_v1, '2': shell_v2, }[version] except KeyError: actions_module = shell_v1 self._find_actions(subparsers, actions_module) self._find_actions(subparsers, self) for extension in self.extensions: self._find_actions(subparsers, extension.module) self._add_bash_completion_subparser(subparsers) return parser def _discover_extensions(self, version): extensions = [] for name, module in itertools.chain( self._discover_via_python_path(version), self._discover_via_contrib_path(version)): extension = cinderclient.extension.Extension(name, module) extensions.append(extension) return extensions def _discover_via_python_path(self, version): for (module_loader, name, ispkg) in pkgutil.iter_modules(): if name.endswith('python_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(self, 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 _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 _find_actions(self, subparsers, actions_module): 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 '' help = desc.strip().split('\n')[0] arguments = getattr(callback, 'arguments', []) subparser = subparsers.add_parser( command, help=help, description=desc, add_help=False, formatter_class=OpenStackHelpFormatter) subparser.add_argument('-h', '--help', action='help', help=argparse.SUPPRESS,) self.subcommands[command] = subparser for (args, kwargs) in arguments: subparser.add_argument(*args, **kwargs) 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.WARNING) logger.addHandler(streamhandler) 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 if not options.os_volume_api_version: # Environment variable OS_VOLUME_API_VERSION was # not set and '--os-volume-api-version' option doesn't # specify a value. Fall back to default. options.os_volume_api_version = DEFAULT_OS_VOLUME_API_VERSION api_version_input = False # build available subcommands based on version self.extensions = self._discover_extensions( options.os_volume_api_version) self._run_extension_hooks('__pre_parse_args__') subcommand_parser = self.get_subcommand_parser( options.os_volume_api_version) self.parser = subcommand_parser if options.help or not argv: subcommand_parser.print_help() return 0 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, insecure, service_type, service_name, volume_service_name, username, apikey, projectid, url, region_name, cacert) = ( args.os_username, args.os_password, args.os_tenant_name, args.os_auth_url, args.os_region_name, args.os_tenant_id, args.endpoint_type, args.insecure, args.service_type, args.service_name, args.volume_service_name, args.username, args.apikey, args.projectid, args.url, args.region_name, args.os_cacert) if not endpoint_type: endpoint_type = DEFAULT_CINDER_ENDPOINT_TYPE if not service_type: service_type = DEFAULT_CINDER_SERVICE_TYPE service_type = utils.get_service_type(args.func) or service_type #FIXME(usrleon): Here should be restrict for project id same as # for os_username or os_password but for compatibility it is not. if not utils.isunauthenticated(args.func): if not os_username: if not username: raise exc.CommandError( "You must provide a username " "via either --os-username or env[OS_USERNAME]") else: os_username = username if not os_password: if not apikey: raise exc.CommandError("You must provide a password " "via either --os-password or via " "env[OS_PASSWORD]") else: os_password = apikey if not (os_tenant_name or os_tenant_id): if not projectid: raise exc.CommandError("You must provide a tenant_id " "via either --os-tenant-id or " "env[OS_TENANT_ID]") else: os_tenant_name = projectid if not os_auth_url: if not url: raise exc.CommandError( "You must provide an auth url " "via either --os-auth-url or env[OS_AUTH_URL]") else: os_auth_url = url if not os_region_name and region_name: os_region_name = region_name if not (os_tenant_name or os_tenant_id): raise exc.CommandError( "You must provide a tenant_id " "via either --os-tenant-id or env[OS_TENANT_ID]") if not os_auth_url: raise exc.CommandError( "You must provide an auth url " "via either --os-auth-url or env[OS_AUTH_URL]") self.cs = client.Client(options.os_volume_api_version, os_username, os_password, os_tenant_name, os_auth_url, insecure, 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, retries=options.retries, http_log_debug=args.debug, cacert=cacert) try: if not utils.isunauthenticated(args.func): self.cs.authenticate() except exc.Unauthorized: raise exc.CommandError("Invalid OpenStack Cinder credentials.") 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() if endpoint_api_version != options.os_volume_api_version: msg = (("Volume API version is set to %s " "but you are accessing a %s endpoint. " "Change its value via either --os-volume-api-version " "or env[OS_VOLUME_API_VERSION]") % (options.os_volume_api_version, endpoint_api_version)) raise exc.InvalidAPIVersion(msg) except exc.UnsupportedVersion: endpoint_api_version = options.os_volume_api_version if api_version_input: logger.warning("Unable to determine the API version via " "endpoint URL. Falling back to user " "specified version: %s" % endpoint_api_version) else: logger.warning("Unable to determine the API version from " "endpoint URL or user input. Falling back to " "default API version: %s" % endpoint_api_version) args.func(self.cs, args) def _run_extension_hooks(self, hook_type, *args, **kwargs): """Run hooks for all registered extensions.""" for extension in self.extensions: extension.run_hooks(hook_type, *args, **kwargs) def do_bash_completion(self, args): """Print arguments for bash_completion. Prints all of the commands and options to stdout so that the cinder.bash_completion script doesn't 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='', nargs='?', help='Display help for ') def do_help(self, args): """ Display 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() # 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(map(strutils.safe_decode, 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" % strutils.six.text_type(e), file=sys.stderr) sys.exit(1) if __name__ == "__main__": main() python-cinderclient-1.0.8/cinderclient/base.py0000664000175300017540000002326612274343712022604 0ustar jenkinsjenkins00000000000000# 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 cinderclient import exceptions from cinderclient import utils # Python 2.4 compat try: all except NameError: def all(iterable): return True not in (not x for x in iterable) 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(utils.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 def _list(self, url, response_key, obj_class=None, body=None): resp = None 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"): return [obj_class(self, res, loaded=True) for res in data if res] @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() delattr(self, cache_attr) 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) else: return self.resource_class(self, body, loaded=True) 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 body[response_key] 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]) def _delete(self, url): resp, body = self.api.client.delete(url) def _update(self, url, body, **kwargs): self.run_hooks('modify_body_for_update', body, **kwargs) resp, body = self.api.client.put(url, body=body) return body 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: 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(404, 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 = list(kwargs.items()) # 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. for obj in self.list(search_opts={'all_tenants': 1}): try: if all(getattr(obj, attr) == value for (attr, value) in searches): found.append(obj) except AttributeError: continue return found class Resource(object): """ A resource represents a particular instance of an object (server, flavor, etc). This is pretty much just a bag for attributes. :param manager: Manager object :param info: dictionary representing resource attributes :param loaded: prevent lazy-loading if set to True """ HUMAN_ID = False def __init__(self, manager, info, loaded=False): self.manager = manager self._info = info self._add_details(info) self._loaded = loaded # NOTE(sirp): ensure `id` is already present because if it isn't we'll # enter an infinite loop of __getattr__ -> get -> __init__ -> # __getattr__ -> ... if 'id' in self.__dict__ and len(str(self.id)) == 36: self.manager.write_to_completion_cache('uuid', self.id) human_id = self.human_id if human_id: self.manager.write_to_completion_cache('human_id', human_id) @property def human_id(self): """Subclasses may override this provide a pretty ID which can be used for bash completion. """ if 'name' in self.__dict__ and self.HUMAN_ID: return utils.slugify(self.name) return None def _add_details(self, info): for (k, v) in six.iteritems(info): try: setattr(self, k, v) except AttributeError: # In this case we already defined the attribute on the class pass def __getattr__(self, k): if k not in self.__dict__: #NOTE(bcwaldon): disallow lazy-loading if already loaded once if not self.is_loaded(): self.get() return self.__getattr__(k) raise AttributeError(k) else: return self.__dict__[k] def __repr__(self): reprkeys = sorted(k for k in self.__dict__ if k[0] != '_' and k != 'manager') info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys) return "<%s %s>" % (self.__class__.__name__, info) 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, self.__class__): return False if hasattr(self, 'id') and hasattr(other, 'id'): return self.id == other.id return self._info == other._info def is_loaded(self): return self._loaded def set_loaded(self, val): self._loaded = val python-cinderclient-1.0.8/cinderclient/v2/0000775000175300017540000000000012274343764021645 5ustar jenkinsjenkins00000000000000python-cinderclient-1.0.8/cinderclient/v2/volume_encryption_types.py0000664000175300017540000000677112274343712027230 0ustar jenkinsjenkins00000000000000# Copyright (c) 2013 The Johns Hopkins University/Applied Physics Laboratory # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Volume Encryption Type interface """ from cinderclient import base class VolumeEncryptionType(base.Resource): """ A Volume Encryption Type is a collection of settings used to conduct encryption for a specific volume type. """ def __repr__(self): return "" % self.name class VolumeEncryptionTypeManager(base.ManagerWithFind): """ Manage :class: `VolumeEncryptionType` resources. """ resource_class = VolumeEncryptionType def list(self, search_opts=None): """ List all volume encryption types. :param volume_types: a list of volume types :return: a list of :class: VolumeEncryptionType instances """ # Since the encryption type is a volume type extension, we cannot get # all encryption types without going through all volume types. volume_types = self.api.volume_types.list() encryption_types = [] for volume_type in volume_types: encryption_type = self._get("/types/%s/encryption" % base.getid(volume_type)) if hasattr(encryption_type, 'volume_type_id'): encryption_types.append(encryption_type) return encryption_types def get(self, volume_type): """ Get the volume encryption type for the specified volume type. :param volume_type: the volume type to query :return: an instance of :class: VolumeEncryptionType """ return self._get("/types/%s/encryption" % base.getid(volume_type)) def create(self, volume_type, specs): """ Create a new encryption type for the specified volume type. :param volume_type: the volume type on which to add an encryption type :param specs: the encryption type specifications to add :return: an instance of :class: VolumeEncryptionType """ body = {'encryption': specs} return self._create("/types/%s/encryption" % base.getid(volume_type), body, "encryption") def update(self, volume_type, specs): """ Update the encryption type information for the specified volume type. :param volume_type: the volume type whose encryption type information must be updated :param specs: the encryption type specifications to update :return: an instance of :class: VolumeEncryptionType """ raise NotImplementedError() def delete(self, volume_type): """ Delete the encryption type information for the specified volume type. :param volume_type: the volume type whose encryption type information must be deleted """ return self._delete("/types/%s/encryption/provider" % base.getid(volume_type)) python-cinderclient-1.0.8/cinderclient/v2/volume_backups.py0000664000175300017540000000477412274343712025243 0ustar jenkinsjenkins00000000000000# Copyright (C) 2013 Hewlett-Packard Development Company, L.P. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Volume Backups interface (1.1 extension). """ from cinderclient import base class VolumeBackup(base.Resource): """A volume backup is a block level backup of a volume.""" def __repr__(self): return "" % self.id def delete(self): """Delete this volume backup.""" return self.manager.delete(self) class VolumeBackupManager(base.ManagerWithFind): """Manage :class:`VolumeBackup` resources.""" resource_class = VolumeBackup def create(self, volume_id, container=None, name=None, description=None): """Create a volume backup. :param volume_id: The ID of the volume to backup. :param container: The name of the backup service container. :param name: The name of the backup. :param description: The description of the backup. :rtype: :class:`VolumeBackup` """ body = {'backup': {'volume_id': volume_id, 'container': container, 'name': name, 'description': description}} return self._create('/backups', body, 'backup') def get(self, backup_id): """Show details of a volume backup. :param backup_id: The ID of the backup to display. :rtype: :class:`VolumeBackup` """ return self._get("/backups/%s" % backup_id, "backup") def list(self, detailed=True): """Get a list of all volume backups. :rtype: list of :class:`VolumeBackup` """ if detailed is True: return self._list("/backups/detail", "backups") else: return self._list("/backups", "backups") def delete(self, backup): """Delete a volume backup. :param backup: The :class:`VolumeBackup` to delete. """ self._delete("/backups/%s" % base.getid(backup)) python-cinderclient-1.0.8/cinderclient/v2/volume_transfers.py0000664000175300017540000000542512274343712025614 0ustar jenkinsjenkins00000000000000# Copyright (C) 2013 Hewlett-Packard Development Company, L.P. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Volume transfer interface (1.1 extension). """ from cinderclient import base class VolumeTransfer(base.Resource): """Transfer a volume from one tenant to another""" def __repr__(self): return "" % self.id def delete(self): """Delete this volume transfer.""" return self.manager.delete(self) class VolumeTransferManager(base.ManagerWithFind): """Manage :class:`VolumeTransfer` resources.""" resource_class = VolumeTransfer def create(self, volume_id, name=None): """Create a volume transfer. :param volume_id: The ID of the volume to transfer. :param name: The name of the transfer. :rtype: :class:`VolumeTransfer` """ body = {'transfer': {'volume_id': volume_id, 'name': name}} return self._create('/os-volume-transfer', body, 'transfer') def accept(self, transfer_id, auth_key): """Accept a volume transfer. :param transfer_id: The ID of the trasnfer to accept. :param auth_key: The auth_key of the transfer. :rtype: :class:`VolumeTransfer` """ body = {'accept': {'auth_key': auth_key}} return self._create('/os-volume-transfer/%s/accept' % transfer_id, body, 'transfer') def get(self, transfer_id): """Show details of a volume transfer. :param transfer_id: The ID of the volume transfer to display. :rtype: :class:`VolumeTransfer` """ return self._get("/os-volume-transfer/%s" % transfer_id, "transfer") def list(self, detailed=True, search_opts=None): """Get a list of all volume transfer. :rtype: list of :class:`VolumeTransfer` """ if detailed is True: return self._list("/os-volume-transfer/detail", "transfers") else: return self._list("/os-volume-transfer", "transfers") def delete(self, transfer_id): """Delete a volume transfer. :param transfer_id: The :class:`VolumeTransfer` to delete. """ self._delete("/os-volume-transfer/%s" % base.getid(transfer_id)) python-cinderclient-1.0.8/cinderclient/v2/limits.py0000664000175300017540000000532712274343712023520 0ustar jenkinsjenkins00000000000000# Copyright 2013 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from cinderclient import base class Limits(base.Resource): """A collection of RateLimit and AbsoluteLimit objects.""" def __repr__(self): return "" @property def absolute(self): for (name, value) in list(self._info['absolute'].items()): yield AbsoluteLimit(name, value) @property def rate(self): for group in self._info['rate']: uri = group['uri'] regex = group['regex'] for rate in group['limit']: yield RateLimit(rate['verb'], uri, regex, rate['value'], rate['remaining'], rate['unit'], rate['next-available']) class RateLimit(object): """Data model that represents a flattened view of a single rate limit.""" def __init__(self, verb, uri, regex, value, remain, unit, next_available): self.verb = verb self.uri = uri self.regex = regex self.value = value self.remain = remain self.unit = unit self.next_available = next_available def __eq__(self, other): return self.uri == other.uri \ and self.regex == other.regex \ and self.value == other.value \ and self.verb == other.verb \ and self.remain == other.remain \ and self.unit == other.unit \ and self.next_available == other.next_available def __repr__(self): return "" % (self.verb, self.uri) class AbsoluteLimit(object): """Data model that represents a single absolute limit.""" def __init__(self, name, value): self.name = name self.value = value def __eq__(self, other): return self.value == other.value and self.name == other.name def __repr__(self): return "" % (self.name) class LimitsManager(base.Manager): """Manager object used to interact with limits resource.""" resource_class = Limits def get(self): """Get a specific extension. :rtype: :class:`Limits` """ return self._get("/limits", "limits") python-cinderclient-1.0.8/cinderclient/v2/qos_specs.py0000664000175300017540000001127712274343712024217 0ustar jenkinsjenkins00000000000000# Copyright (c) 2013 eBay Inc. # Copyright (c) OpenStack LLC. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. """ QoS Specs interface. """ from cinderclient import base class QoSSpecs(base.Resource): """QoS specs entity represents quality-of-service parameters/requirements. A QoS specs is a set of parameters or requirements for quality-of-service purpose, which can be associated with volume types (for now). In future, QoS specs may be extended to be associated other entities, such as single volume. """ def __repr__(self): return "" % self.name def delete(self): return self.manager.delete(self) class QoSSpecsManager(base.ManagerWithFind): """ Manage :class:`QoSSpecs` resources. """ resource_class = QoSSpecs def list(self): """Get a list of all qos specs. :rtype: list of :class:`QoSSpecs`. """ return self._list("/qos-specs", "qos_specs") def get(self, qos_specs): """Get a specific qos specs. :param qos_specs: The ID of the :class:`QoSSpecs` to get. :rtype: :class:`QoSSpecs` """ return self._get("/qos-specs/%s" % base.getid(qos_specs), "qos_specs") def delete(self, qos_specs, force=False): """Delete a specific qos specs. :param qos_specs: The ID of the :class:`QoSSpecs` to be removed. :param force: Flag that indicates whether to delete target qos specs if it was in-use. """ self._delete("/qos-specs/%s?force=%s" % (base.getid(qos_specs), force)) def create(self, name, specs): """Create a qos specs. :param name: Descriptive name of the qos specs, must be unique :param specs: A dict of key/value pairs to be set :rtype: :class:`QoSSpecs` """ body = { "qos_specs": { "name": name, } } body["qos_specs"].update(specs) return self._create("/qos-specs", body, "qos_specs") def set_keys(self, qos_specs, specs): """Update a qos specs with new specifications. :param qos_specs: The ID of qos specs :param specs: A dict of key/value pairs to be set :rtype: :class:`QoSSpecs` """ body = { "qos_specs": {} } body["qos_specs"].update(specs) return self._update("/qos-specs/%s" % qos_specs, body) def unset_keys(self, qos_specs, specs): """Update a qos specs with new specifications. :param qos_specs: The ID of qos specs :param specs: A list of key to be unset :rtype: :class:`QoSSpecs` """ body = {'keys': specs} return self._update("/qos-specs/%s/delete_keys" % qos_specs, body) def get_associations(self, qos_specs): """Get associated entities of a qos specs. :param qos_specs: The id of the :class: `QoSSpecs` :return: a list of entities that associated with specific qos specs. """ return self._list("/qos-specs/%s/associations" % base.getid(qos_specs), "qos_associations") def associate(self, qos_specs, vol_type_id): """Associate a volume type with specific qos specs. :param qos_specs: The qos specs to be associated with :param vol_type_id: The volume type id to be associated with """ self.api.client.get("/qos-specs/%s/associate?vol_type_id=%s" % (base.getid(qos_specs), vol_type_id)) def disassociate(self, qos_specs, vol_type_id): """Disassociate qos specs from volume type. :param qos_specs: The qos specs to be associated with :param vol_type_id: The volume type id to be associated with """ self.api.client.get("/qos-specs/%s/disassociate?vol_type_id=%s" % (base.getid(qos_specs), vol_type_id)) def disassociate_all(self, qos_specs): """Disassociate all entities from specific qos specs. :param qos_specs: The qos specs to be associated with """ self.api.client.get("/qos-specs/%s/disassociate_all" % base.getid(qos_specs)) python-cinderclient-1.0.8/cinderclient/v2/services.py0000664000175300017540000000353612274343712024042 0ustar jenkinsjenkins00000000000000# Copyright (c) 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ service interface """ from cinderclient import base class Service(base.Resource): def __repr__(self): return "" % self.service class ServiceManager(base.ManagerWithFind): resource_class = Service def list(self, host=None, binary=None): """ Describes service list for host. :param host: destination host name. :param binary: service binary. """ url = "/os-services" filters = [] if host: filters.append("host=%s" % host) if binary: filters.append("binary=%s" % binary) if filters: url = "%s?%s" % (url, "&".join(filters)) return self._list(url, "services") def enable(self, host, binary): """Enable the service specified by hostname and binary.""" body = {"host": host, "binary": binary} result = self._update("/os-services/enable", body) return self.resource_class(self, result) def disable(self, host, binary): """Enable the service specified by hostname and binary.""" body = {"host": host, "binary": binary} result = self._update("/os-services/disable", body) return self.resource_class(self, result) python-cinderclient-1.0.8/cinderclient/v2/quota_classes.py0000664000175300017540000000263712274343712025066 0ustar jenkinsjenkins00000000000000# Copyright (c) 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from cinderclient import base class QuotaClassSet(base.Resource): @property def id(self): """Needed by base.Resource to self-refresh and be indexed.""" return self.class_name def update(self, *args, **kwargs): self.manager.update(self.class_name, *args, **kwargs) class QuotaClassSetManager(base.Manager): resource_class = QuotaClassSet def get(self, class_name): return self._get("/os-quota-class-sets/%s" % (class_name), "quota_class_set") def update(self, class_name, **updates): body = {'quota_class_set': {'class_name': class_name}} for update in updates: body['quota_class_set'][update] = updates[update] self._update('/os-quota-class-sets/%s' % (class_name), body) python-cinderclient-1.0.8/cinderclient/v2/client.py0000664000175300017540000001045412274343712023472 0ustar jenkinsjenkins00000000000000# 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 client from cinderclient.v1 import availability_zones from cinderclient.v2 import limits from cinderclient.v2 import qos_specs from cinderclient.v2 import quota_classes from cinderclient.v2 import quotas from cinderclient.v2 import services from cinderclient.v2 import volumes from cinderclient.v2 import volume_snapshots from cinderclient.v2 import volume_types from cinderclient.v2 import volume_encryption_types from cinderclient.v2 import volume_backups from cinderclient.v2 import volume_backups_restore from cinderclient.v1 import volume_transfers class Client(object): """Top-level object to access the OpenStack Volume API. Create an instance with your creds:: >>> client = Client(USERNAME, PASSWORD, PROJECT_ID, AUTH_URL) Then call methods on its managers:: >>> client.volumes.list() ... """ def __init__(self, username, api_key, project_id=None, auth_url='', insecure=False, timeout=None, tenant_id=None, proxy_tenant_id=None, proxy_token=None, region_name=None, endpoint_type='publicURL', extensions=None, service_type='volumev2', service_name=None, volume_service_name=None, retries=None, http_log_debug=False, cacert=None): # FIXME(comstud): Rename the api_key argument above when we # know it's not being used as keyword argument password = api_key self.limits = limits.LimitsManager(self) # extensions self.volumes = volumes.VolumeManager(self) self.volume_snapshots = volume_snapshots.SnapshotManager(self) self.volume_types = volume_types.VolumeTypeManager(self) self.volume_encryption_types = \ volume_encryption_types.VolumeEncryptionTypeManager(self) self.qos_specs = qos_specs.QoSSpecsManager(self) self.quota_classes = quota_classes.QuotaClassSetManager(self) self.quotas = quotas.QuotaSetManager(self) self.backups = volume_backups.VolumeBackupManager(self) self.restores = volume_backups_restore.VolumeBackupRestoreManager(self) self.transfers = volume_transfers.VolumeTransferManager(self) self.services = services.ServiceManager(self) self.availability_zones = \ availability_zones.AvailabilityZoneManager(self) # Add in any extensions... if extensions: for extension in extensions: if extension.manager_class: setattr(self, extension.name, extension.manager_class(self)) self.client = client.HTTPClient( username, password, project_id, 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, retries=retries, http_log_debug=http_log_debug, cacert=cacert) def authenticate(self): """Authenticate against the server. Normally this is called automatically when you first access the API, but you can call this method to force authentication right now. Returns on success; raises :exc:`exceptions.Unauthorized` if the credentials are wrong. """ self.client.authenticate() def get_volume_api_version_from_endpoint(self): return self.client.get_volume_api_version_from_endpoint() python-cinderclient-1.0.8/cinderclient/v2/volume_backups_restore.py0000664000175300017540000000304612274343712026775 0ustar jenkinsjenkins00000000000000# Copyright (C) 2013 Hewlett-Packard Development Company, L.P. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Volume Backups Restore interface (1.1 extension). This is part of the Volume Backups interface. """ from cinderclient import base class VolumeBackupsRestore(base.Resource): """A Volume Backups Restore represents a restore operation.""" def __repr__(self): return "" % self.volume_id class VolumeBackupRestoreManager(base.Manager): """Manage :class:`VolumeBackupsRestore` resources.""" resource_class = VolumeBackupsRestore def restore(self, backup_id, volume_id=None): """Restore a backup to a volume. :param backup_id: The ID of the backup to restore. :param volume_id: The ID of the volume to restore the backup to. :rtype: :class:`Restore` """ body = {'restore': {'volume_id': volume_id}} return self._create("/backups/%s/restore" % backup_id, body, "restore") python-cinderclient-1.0.8/cinderclient/v2/volume_types.py0000664000175300017540000000646512274343712024756 0ustar jenkinsjenkins00000000000000# Copyright (c) 2013 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. """Volume Type interface.""" from cinderclient import base class VolumeType(base.Resource): """A Volume Type is the type of volume to be created.""" def __repr__(self): return "" % self.name def get_keys(self): """Get extra specs from a volume type. :param vol_type: The :class:`VolumeType` to get extra specs from """ _resp, body = self.manager.api.client.get( "/types/%s/extra_specs" % base.getid(self)) return body["extra_specs"] def set_keys(self, metadata): """Set extra specs on a volume type. :param type : The :class:`VolumeType` to set extra spec on :param metadata: A dict of key/value pairs to be set """ body = {'extra_specs': metadata} return self.manager._create( "/types/%s/extra_specs" % base.getid(self), body, "extra_specs", return_raw=True) def unset_keys(self, keys): """Unset extra specs on a volue type. :param type_id: The :class:`VolumeType` to unset extra spec on :param keys: A list of keys to be unset """ # NOTE(jdg): This wasn't actually doing all of the keys before # the return in the loop resulted in ony ONE key being unset. # since on success the return was NONE, we'll only interrupt the loop # and return if there's an error for k in keys: resp = self.manager._delete( "/types/%s/extra_specs/%s" % ( base.getid(self), k)) if resp is not None: return resp class VolumeTypeManager(base.ManagerWithFind): """Manage :class:`VolumeType` resources.""" resource_class = VolumeType def list(self, search_opts=None): """Get a list of all volume types. :rtype: list of :class:`VolumeType`. """ return self._list("/types", "volume_types") def get(self, volume_type): """Get a specific volume type. :param volume_type: The ID of the :class:`VolumeType` to get. :rtype: :class:`VolumeType` """ return self._get("/types/%s" % base.getid(volume_type), "volume_type") def delete(self, volume_type): """Delete a specific volume_type. :param volume_type: The ID of the :class:`VolumeType` to get. """ self._delete("/types/%s" % base.getid(volume_type)) def create(self, name): """Create a volume type. :param name: Descriptive name of the volume type :rtype: :class:`VolumeType` """ body = { "volume_type": { "name": name, } } return self._create("/types", body, "volume_type") python-cinderclient-1.0.8/cinderclient/v2/__init__.py0000664000175300017540000000126612274343712023754 0ustar jenkinsjenkins00000000000000# Copyright (c) 2013 OpenStack Foundation # # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from cinderclient.v2.client import Client # noqa python-cinderclient-1.0.8/cinderclient/v2/availability_zones.py0000664000175300017540000000263212274343712026103 0ustar jenkinsjenkins00000000000000# Copyright 2011-2013 OpenStack Foundation # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Availability Zone interface (v2 extension)""" from cinderclient import base class AvailabilityZone(base.Resource): NAME_ATTR = 'display_name' def __repr__(self): return "" % self.zoneName class AvailabilityZoneManager(base.ManagerWithFind): """Manage :class:`AvailabilityZone` resources.""" resource_class = AvailabilityZone def list(self, detailed=False): """Get a list of all availability zones :rtype: list of :class:`AvailabilityZone` """ if detailed is True: return self._list("/os-availability-zone/detail", "availabilityZoneInfo") else: return self._list("/os-availability-zone", "availabilityZoneInfo") python-cinderclient-1.0.8/cinderclient/v2/contrib/0000775000175300017540000000000012274343764023305 5ustar jenkinsjenkins00000000000000python-cinderclient-1.0.8/cinderclient/v2/contrib/list_extensions.py0000664000175300017540000000264712274343712027113 0ustar jenkinsjenkins00000000000000# Copyright (c) 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from cinderclient import base from cinderclient import utils class ListExtResource(base.Resource): @property def summary(self): descr = self.description.strip() if not descr: return '??' lines = descr.split("\n") if len(lines) == 1: return lines[0] else: return lines[0] + "..." class ListExtManager(base.Manager): resource_class = ListExtResource def show_all(self): return self._list("/extensions", 'extensions') @utils.service_type('volume') def do_list_extensions(client, _args): """ List all the os-api extensions that are available. """ extensions = client.list_extensions.show_all() fields = ["Name", "Summary", "Alias", "Updated"] utils.print_list(extensions, fields) python-cinderclient-1.0.8/cinderclient/v2/contrib/__init__.py0000664000175300017540000000000012274343712025375 0ustar jenkinsjenkins00000000000000python-cinderclient-1.0.8/cinderclient/v2/volumes.py0000664000175300017540000003527012274343712023711 0ustar jenkinsjenkins00000000000000# Copyright (c) 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Volume interface (v2 extension).""" import six try: from urllib import urlencode except ImportError: from urllib.parse import urlencode from cinderclient import base class Volume(base.Resource): """A volume is an extra block level storage to the OpenStack instances.""" def __repr__(self): return "" % self.id def delete(self): """Delete this volume.""" self.manager.delete(self) def update(self, **kwargs): """Update the name or description for this volume.""" self.manager.update(self, **kwargs) def attach(self, instance_uuid, mountpoint, mode='rw'): """Set attachment metadata. :param instance_uuid: uuid of the attaching instance. :param mountpoint: mountpoint on the attaching instance. :param mode: the access mode. """ return self.manager.attach(self, instance_uuid, mountpoint, mode) def detach(self): """Clear attachment metadata.""" return self.manager.detach(self) def reserve(self, volume): """Reserve this volume.""" return self.manager.reserve(self) def unreserve(self, volume): """Unreserve this volume.""" return self.manager.unreserve(self) def begin_detaching(self, volume): """Begin detaching volume.""" return self.manager.begin_detaching(self) def roll_detaching(self, volume): """Roll detaching volume.""" return self.manager.roll_detaching(self) def initialize_connection(self, volume, connector): """Initialize a volume connection. :param connector: connector dict from nova. """ return self.manager.initialize_connection(self, connector) def terminate_connection(self, volume, connector): """Terminate a volume connection. :param connector: connector dict from nova. """ return self.manager.terminate_connection(self, connector) def set_metadata(self, volume, metadata): """Set or Append metadata to a volume. :param volume : The :class: `Volume` to set metadata on :param metadata: A dict of key/value pairs to set """ return self.manager.set_metadata(self, metadata) def upload_to_image(self, force, image_name, container_format, disk_format): """Upload a volume to image service as an image.""" return self.manager.upload_to_image(self, force, image_name, container_format, disk_format) def force_delete(self): """Delete the specified volume ignoring its current state. :param volume: The UUID of the volume to force-delete. """ self.manager.force_delete(self) def reset_state(self, state): """Update the volume with the provided state.""" self.manager.reset_state(self, state) def extend(self, volume, new_size): """Extend the size of the specified volume. :param volume: The UUID of the volume to extend :param new_size: The desired size to extend volume to. """ self.manager.extend(self, volume, new_size) def migrate_volume(self, host, force_host_copy): """Migrate the volume to a new host.""" self.manager.migrate_volume(self, host, force_host_copy) def retype(self, volume_type, policy): """Change a volume's type.""" self.manager.retype(self, volume_type, policy) def update_all_metadata(self, metadata): """Update all metadata of this volume.""" return self.manager.update_all_metadata(self, metadata) def update_readonly_flag(self, volume, read_only): """Update the read-only access mode flag of the specified volume. :param volume: The UUID of the volume to update. :param read_only: The value to indicate whether to update volume to read-only access mode. """ self.manager.update_readonly_flag(self, volume, read_only) class VolumeManager(base.ManagerWithFind): """Manage :class:`Volume` resources.""" resource_class = Volume def create(self, size, snapshot_id=None, source_volid=None, name=None, description=None, volume_type=None, user_id=None, project_id=None, availability_zone=None, metadata=None, imageRef=None, scheduler_hints=None): """Create a volume. :param size: Size of volume in GB :param snapshot_id: ID of the snapshot :param name: Name of the volume :param description: Description of the volume :param volume_type: Type of volume :param user_id: User id derived from context :param project_id: Project id derived from context :param availability_zone: Availability Zone to use :param metadata: Optional metadata to set on volume creation :param imageRef: reference to an image stored in glance :param source_volid: ID of source volume to clone from :param scheduler_hints: (optional extension) arbitrary key-value pairs specified by the client to help boot an instance :rtype: :class:`Volume` """ if metadata is None: volume_metadata = {} else: volume_metadata = metadata body = {'volume': {'size': size, 'snapshot_id': snapshot_id, 'name': name, 'description': description, 'volume_type': volume_type, 'user_id': user_id, 'project_id': project_id, 'availability_zone': availability_zone, 'status': "creating", 'attach_status': "detached", 'metadata': volume_metadata, 'imageRef': imageRef, 'source_volid': source_volid, 'scheduler_hints': scheduler_hints, }} return self._create('/volumes', body, 'volume') def get(self, volume_id): """Get a volume. :param volume_id: The ID of the volume to delete. :rtype: :class:`Volume` """ return self._get("/volumes/%s" % volume_id, "volume") def list(self, detailed=True, search_opts=None): """Get a list of all volumes. :rtype: list of :class:`Volume` """ if search_opts is None: search_opts = {} qparams = {} for opt, val in six.iteritems(search_opts): if val: qparams[opt] = val query_string = "?%s" % urlencode(qparams) if qparams else "" detail = "" if detailed: detail = "/detail" return self._list("/volumes%s%s" % (detail, query_string), "volumes") def delete(self, volume): """Delete a volume. :param volume: The :class:`Volume` to delete. """ self._delete("/volumes/%s" % base.getid(volume)) def update(self, volume, **kwargs): """Update the name or description for a volume. :param volume: The :class:`Volume` to delete. """ if not kwargs: return body = {"volume": kwargs} self._update("/volumes/%s" % base.getid(volume), body) def _action(self, action, volume, info=None, **kwargs): """Perform a volume "action." """ body = {action: info} self.run_hooks('modify_body_for_action', body, **kwargs) url = '/volumes/%s/action' % base.getid(volume) return self.api.client.post(url, body=body) def attach(self, volume, instance_uuid, mountpoint, mode='rw'): """Set attachment metadata. :param volume: The :class:`Volume` (or its ID) you would like to attach. :param instance_uuid: uuid of the attaching instance. :param mountpoint: mountpoint on the attaching instance. :param mode: the access mode. """ return self._action('os-attach', volume, {'instance_uuid': instance_uuid, 'mountpoint': mountpoint, 'mode': mode}) def detach(self, volume): """Clear attachment metadata. :param volume: The :class:`Volume` (or its ID) you would like to detach. """ return self._action('os-detach', volume) def reserve(self, volume): """Reserve this volume. :param volume: The :class:`Volume` (or its ID) you would like to reserve. """ return self._action('os-reserve', volume) def unreserve(self, volume): """Unreserve this volume. :param volume: The :class:`Volume` (or its ID) you would like to unreserve. """ return self._action('os-unreserve', volume) def begin_detaching(self, volume): """Begin detaching this volume. :param volume: The :class:`Volume` (or its ID) you would like to detach. """ return self._action('os-begin_detaching', volume) def roll_detaching(self, volume): """Roll detaching this volume. :param volume: The :class:`Volume` (or its ID) you would like to roll detaching. """ return self._action('os-roll_detaching', volume) def initialize_connection(self, volume, connector): """Initialize a volume connection. :param volume: The :class:`Volume` (or its ID). :param connector: connector dict from nova. """ return self._action('os-initialize_connection', volume, {'connector': connector})[1]['connection_info'] def terminate_connection(self, volume, connector): """Terminate a volume connection. :param volume: The :class:`Volume` (or its ID). :param connector: connector dict from nova. """ self._action('os-terminate_connection', volume, {'connector': connector}) def set_metadata(self, volume, metadata): """Update/Set a volumes metadata. :param volume: The :class:`Volume`. :param metadata: A list of keys to be set. """ body = {'metadata': metadata} return self._create("/volumes/%s/metadata" % base.getid(volume), body, "metadata") def delete_metadata(self, volume, keys): """Delete specified keys from volumes metadata. :param volume: The :class:`Volume`. :param keys: A list of keys to be removed. """ for k in keys: self._delete("/volumes/%s/metadata/%s" % (base.getid(volume), k)) def upload_to_image(self, volume, force, image_name, container_format, disk_format): """Upload volume to image service as image. :param volume: The :class:`Volume` to upload. """ return self._action('os-volume_upload_image', volume, {'force': force, 'image_name': image_name, 'container_format': container_format, 'disk_format': disk_format}) def force_delete(self, volume): return self._action('os-force_delete', base.getid(volume)) def reset_state(self, volume, state): """Update the provided volume with the provided state.""" return self._action('os-reset_status', volume, {'status': state}) def extend(self, volume, new_size): return self._action('os-extend', base.getid(volume), {'new_size': new_size}) def get_encryption_metadata(self, volume_id): """ Retrieve the encryption metadata from the desired volume. :param volume_id: the id of the volume to query :return: a dictionary of volume encryption metadata """ return self._get("/volumes/%s/encryption" % volume_id)._info def migrate_volume(self, volume, host, force_host_copy): """Migrate volume to new host. :param volume: The :class:`Volume` to migrate :param host: The destination host :param force_host_copy: Skip driver optimizations """ return self._action('os-migrate_volume', volume, {'host': host, 'force_host_copy': force_host_copy}) def migrate_volume_completion(self, old_volume, new_volume, error): """Complete the migration from the old volume to the temp new one. :param old_volume: The original :class:`Volume` in the migration :param new_volume: The new temporary :class:`Volume` in the migration :param error: Inform of an error to cause migration cleanup """ new_volume_id = base.getid(new_volume) return self._action('os-migrate_volume_completion', old_volume, {'new_volume': new_volume_id, 'error': error})[1] def update_all_metadata(self, volume, metadata): """Update all metadata of a volume. :param volume: The :class:`Volume`. :param metadata: A list of keys to be updated. """ body = {'metadata': metadata} return self._update("/volumes/%s/metadata" % base.getid(volume), body) def update_readonly_flag(self, volume, flag): return self._action('os-update_readonly_flag', base.getid(volume), {'readonly': flag}) def retype(self, volume, volume_type, policy): """Change a volume's type. :param volume: The :class:`Volume` to retype :param volume_type: New volume type :param policy: Policy for migration during the retype """ return self._action('os-retype', volume, {'new_type': volume_type, 'migration_policy': policy}) python-cinderclient-1.0.8/cinderclient/v2/volume_snapshots.py0000664000175300017540000001422212274343712025622 0ustar jenkinsjenkins00000000000000# Copyright (c) 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Volume snapshot interface (1.1 extension).""" import six try: from urllib import urlencode except ImportError: from urllib.parse import urlencode from cinderclient import base class Snapshot(base.Resource): """A Snapshot is a point-in-time snapshot of an openstack volume.""" def __repr__(self): return "" % self.id def delete(self): """Delete this snapshot.""" self.manager.delete(self) def update(self, **kwargs): """Update the name or description for this snapshot.""" self.manager.update(self, **kwargs) @property def progress(self): return self._info.get('os-extended-snapshot-attributes:progress') @property def project_id(self): return self._info.get('os-extended-snapshot-attributes:project_id') def reset_state(self, state): """Update the snapshot with the provided state.""" self.manager.reset_state(self, state) def set_metadata(self, metadata): """Set metadata of this snapshot.""" return self.manager.set_metadata(self, metadata) def delete_metadata(self, keys): """Delete metadata of this snapshot.""" return self.manager.delete_metadata(self, keys) def update_all_metadata(self, metadata): """Update_all metadata of this snapshot.""" return self.manager.update_all_metadata(self, metadata) class SnapshotManager(base.ManagerWithFind): """Manage :class:`Snapshot` resources.""" resource_class = Snapshot def create(self, volume_id, force=False, name=None, description=None): """Create a snapshot of the given volume. :param volume_id: The ID of the volume to snapshot. :param force: If force is True, create a snapshot even if the volume is attached to an instance. Default is False. :param name: Name of the snapshot :param description: Description of the snapshot :rtype: :class:`Snapshot` """ body = {'snapshot': {'volume_id': volume_id, 'force': force, 'name': name, 'description': description}} return self._create('/snapshots', body, 'snapshot') def get(self, snapshot_id): """Get a snapshot. :param snapshot_id: The ID of the snapshot to get. :rtype: :class:`Snapshot` """ return self._get("/snapshots/%s" % snapshot_id, "snapshot") def list(self, detailed=True, search_opts=None): """Get a list of all snapshots. :rtype: list of :class:`Snapshot` """ if search_opts is None: search_opts = {} qparams = {} for opt, val in six.iteritems(search_opts): if val: qparams[opt] = val # Transform the dict to a sequence of two-element tuples in fixed # order, then the encoded string will be consistent in Python 2&3. if qparams: new_qparams = sorted(qparams.items(), key=lambda x: x[0]) query_string = "?%s" % urlencode(new_qparams) else: query_string = "" detail = "" if detailed: detail = "/detail" return self._list("/snapshots%s%s" % (detail, query_string), "snapshots") def delete(self, snapshot): """Delete a snapshot. :param snapshot: The :class:`Snapshot` to delete. """ self._delete("/snapshots/%s" % base.getid(snapshot)) def update(self, snapshot, **kwargs): """Update the name or description for a snapshot. :param snapshot: The :class:`Snapshot` to delete. """ if not kwargs: return body = {"snapshot": kwargs} self._update("/snapshots/%s" % base.getid(snapshot), body) def reset_state(self, snapshot, state): """Update the specified snapshot with the provided state.""" return self._action('os-reset_status', snapshot, {'status': state}) def _action(self, action, snapshot, info=None, **kwargs): """Perform a snapshot action.""" body = {action: info} self.run_hooks('modify_body_for_action', body, **kwargs) url = '/snapshots/%s/action' % base.getid(snapshot) return self.api.client.post(url, body=body) def update_snapshot_status(self, snapshot, update_dict): return self._action('os-update_snapshot_status', base.getid(snapshot), update_dict) def set_metadata(self, snapshot, metadata): """Update/Set a snapshots metadata. :param snapshot: The :class:`Snapshot`. :param metadata: A list of keys to be set. """ body = {'metadata': metadata} return self._create("/snapshots/%s/metadata" % base.getid(snapshot), body, "metadata") def delete_metadata(self, snapshot, keys): """Delete specified keys from snapshot metadata. :param snapshot: The :class:`Snapshot`. :param keys: A list of keys to be removed. """ snapshot_id = base.getid(snapshot) for k in keys: self._delete("/snapshots/%s/metadata/%s" % (snapshot_id, k)) def update_all_metadata(self, snapshot, metadata): """Update_all snapshot metadata. :param snapshot: The :class:`Snapshot`. :param metadata: A list of keys to be updated. """ body = {'metadata': metadata} return self._update("/snapshots/%s/metadata" % base.getid(snapshot), body) python-cinderclient-1.0.8/cinderclient/v2/shell.py0000664000175300017540000014000512274343712023317 0ustar jenkinsjenkins00000000000000# 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 __future__ import print_function import argparse import copy import os import sys import time import six from cinderclient import exceptions from cinderclient import utils from cinderclient.openstack.common import strutils from cinderclient.v2 import availability_zones def _poll_for_status(poll_fn, obj_id, action, final_ok_states, poll_period=5, show_progress=True): """Block while action is performed, periodically printing 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): """Get a volume snapshot by name or ID.""" return utils.find_resource(cs.volume_snapshots, snapshot) def _find_backup(cs, backup): """Get a backup by name or ID.""" return utils.find_resource(cs.backups, backup) def _find_transfer(cs, transfer): """Get a transfer by name or ID.""" return utils.find_resource(cs.transfers, transfer) def _find_qos_specs(cs, qos_specs): """Get a qos specs by ID.""" return utils.find_resource(cs.qos_specs, qos_specs) def _print_volume_snapshot(snapshot): utils.print_dict(snapshot._info) def _print_volume_image(image): utils.print_dict(image[1]['os-volume_upload_image']) 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')] _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_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 @utils.arg('--all-tenants', dest='all_tenants', metavar='<0|1>', nargs='?', type=int, const=1, default=0, help='Display information from all tenants (Admin only).') @utils.arg('--all_tenants', nargs='?', type=int, const=1, help=argparse.SUPPRESS) @utils.arg('--name', metavar='', default=None, help='Filter results by name') @utils.arg('--display-name', help=argparse.SUPPRESS) @utils.arg('--status', metavar='', default=None, help='Filter results by status') @utils.arg('--metadata', type=str, nargs='*', metavar='', help='Filter results by metadata', default=None) @utils.service_type('volumev2') def do_list(cs, args): """List all the volumes.""" # NOTE(thingee): Backwards-compatibility with v1 args if args.display_name is not None: args.name = args.display_name all_tenants = int(os.environ.get("ALL_TENANTS", args.all_tenants)) search_opts = { 'all_tenants': all_tenants, 'name': args.name, 'status': args.status, 'metadata': _extract_metadata(args) if args.metadata else None, } volumes = cs.volumes.list(search_opts=search_opts) _translate_volume_keys(volumes) # Create a list of servers to which the volume is attached for vol in volumes: servers = [s.get('server_id') for s in vol.attachments] setattr(vol, 'attached_to', ','.join(map(str, servers))) utils.print_list(volumes, ['ID', 'Status', 'Name', 'Size', 'Volume Type', 'Bootable', 'Attached to']) @utils.arg('volume', metavar='', help='Name or ID of the volume.') @utils.service_type('volumev2') def do_show(cs, args): """Show details about a volume.""" info = dict() volume = utils.find_volume(cs, args.volume) info.update(volume._info) info.pop('links', None) utils.print_dict(info) @utils.arg('size', metavar='', type=int, help='Size of volume in GB') @utils.arg('--snapshot-id', metavar='', default=None, help='Create volume from snapshot id (Optional, Default=None)') @utils.arg('--snapshot_id', help=argparse.SUPPRESS) @utils.arg('--source-volid', metavar='', default=None, help='Create volume from volume id (Optional, Default=None)') @utils.arg('--source_volid', help=argparse.SUPPRESS) @utils.arg('--image-id', metavar='', default=None, help='Create volume from image id (Optional, Default=None)') @utils.arg('--image_id', help=argparse.SUPPRESS) @utils.arg('--name', metavar='', default=None, help='Volume name (Optional, Default=None)') @utils.arg('--display-name', help=argparse.SUPPRESS) @utils.arg('--display_name', help=argparse.SUPPRESS) @utils.arg('--description', metavar='', default=None, help='Volume description (Optional, Default=None)') @utils.arg('--display-description', help=argparse.SUPPRESS) @utils.arg('--display_description', help=argparse.SUPPRESS) @utils.arg('--volume-type', metavar='', default=None, help='Volume type (Optional, Default=None)') @utils.arg('--volume_type', help=argparse.SUPPRESS) @utils.arg('--availability-zone', metavar='', default=None, help='Availability zone for volume (Optional, Default=None)') @utils.arg('--availability_zone', help=argparse.SUPPRESS) @utils.arg('--metadata', type=str, nargs='*', metavar='', help='Metadata key=value pairs (Optional, Default=None)', default=None) @utils.arg('--hint', metavar='', dest='scheduler_hints', action='append', default=[], help='Scheduler hint like in nova') @utils.service_type('volumev2') def do_create(cs, args): """Add a new volume.""" # NOTE(thingee): Backwards-compatibility with v1 args if args.display_name is not None: args.name = args.display_name if args.display_description is not None: args.description = args.display_description volume_metadata = None if args.metadata is not None: volume_metadata = _extract_metadata(args) #NOTE(N.S.): take this piece from novaclient hints = {} if args.scheduler_hints: for hint in args.scheduler_hints: key, _sep, value = hint.partition('=') # NOTE(vish): multiple copies of the same hint will # result in a list of values if key in hints: if isinstance(hints[key], six.string_types): hints[key] = [hints[key]] hints[key] += [value] else: hints[key] = value #NOTE(N.S.): end of the taken piece volume = cs.volumes.create(args.size, args.snapshot_id, args.source_volid, args.name, args.description, args.volume_type, availability_zone=args.availability_zone, imageRef=args.image_id, metadata=volume_metadata, scheduler_hints=hints) info = dict() volume = cs.volumes.get(volume.id) info.update(volume._info) info.pop('links', None) utils.print_dict(info) @utils.arg('volume', metavar='', nargs='+', help='Name or ID of the volume(s) to delete.') @utils.service_type('volumev2') def do_delete(cs, args): """Remove a volume(s).""" failure_count = 0 for volume in args.volume: try: utils.find_volume(cs, volume).delete() except Exception as e: failure_count += 1 print("Delete for volume %s failed: %s" % (volume, e)) if failure_count == len(args.volume): raise exceptions.CommandError("Unable to delete any of the specified " "volumes.") @utils.arg('volume', metavar='', nargs='+', help='Name or ID of the volume(s) to delete.') @utils.service_type('volumev2') def do_force_delete(cs, args): """Attempt forced removal of volume(s), regardless of the state(s).""" failure_count = 0 for volume in args.volume: try: utils.find_volume(cs, volume).force_delete() except Exception as e: failure_count += 1 print("Delete for volume %s failed: %s" % (volume, e)) if failure_count == len(args.volume): raise exceptions.CommandError("Unable to force delete any of the " "specified volumes.") @utils.arg('volume', metavar='', nargs='+', help='Name or ID of the volume to modify.') @utils.arg('--state', metavar='', default='available', help=('Indicate which state to assign the volume. Options include ' 'available, error, creating, deleting, error_deleting. If no ' 'state is provided, available will be used.')) @utils.service_type('volumev2') def do_reset_state(cs, args): """Explicitly update the state of a volume.""" failure_count = 0 single = (len(args.volume) == 1) for volume in args.volume: try: utils.find_volume(cs, volume).reset_state(args.state) except Exception as e: failure_count += 1 msg = "Reset state for volume %s failed: %s" % (volume, e) if not single: print(msg) if failure_count == len(args.volume): if not single: msg = "Unable to reset the state for any of the specified volumes." raise exceptions.CommandError(msg) @utils.arg('volume', metavar='', help='Name or ID of the volume to rename.') @utils.arg('name', nargs='?', metavar='', help='New name for the volume.') @utils.arg('--description', metavar='', help='Optional volume description. (Default=None)', default=None) @utils.arg('--display-description', help=argparse.SUPPRESS) @utils.arg('--display_description', help=argparse.SUPPRESS) @utils.service_type('volumev2') def do_rename(cs, args): """Rename a volume.""" kwargs = {} if args.name is not None: kwargs['name'] = args.name if args.display_description is not None: kwargs['description'] = args.display_description elif args.description is not None: kwargs['description'] = args.description if not any(kwargs): msg = 'Must supply either name or description.' raise exceptions.ClientException(code=1, message=msg) utils.find_volume(cs, args.volume).update(**kwargs) @utils.arg('volume', metavar='', help='Name or ID of the volume to update metadata on.') @utils.arg('action', metavar='', choices=['set', 'unset'], help="Actions: 'set' or 'unset'") @utils.arg('metadata', metavar='', nargs='+', default=[], help='Metadata to set/unset (only key is necessary on unset)') @utils.service_type('volumev2') def do_metadata(cs, args): """Set or Delete metadata on a volume.""" volume = utils.find_volume(cs, args.volume) metadata = _extract_metadata(args) if args.action == 'set': cs.volumes.set_metadata(volume, metadata) elif args.action == 'unset': # NOTE(zul): Make sure py2/py3 sorting is the same cs.volumes.delete_metadata(volume, sorted(metadata.keys(), reverse=True)) @utils.arg('--all-tenants', dest='all_tenants', metavar='<0|1>', nargs='?', type=int, const=1, default=0, help='Display information from all tenants (Admin only).') @utils.arg('--all_tenants', nargs='?', type=int, const=1, help=argparse.SUPPRESS) @utils.arg('--name', metavar='', default=None, help='Filter results by name') @utils.arg('--display-name', help=argparse.SUPPRESS) @utils.arg('--display_name', help=argparse.SUPPRESS) @utils.arg('--status', metavar='', default=None, help='Filter results by status') @utils.arg('--volume-id', metavar='', default=None, help='Filter results by volume-id') @utils.arg('--volume_id', help=argparse.SUPPRESS) @utils.service_type('volumev2') def do_snapshot_list(cs, args): """List all the snapshots.""" all_tenants = int(os.environ.get("ALL_TENANTS", args.all_tenants)) if args.display_name is not None: args.name = args.display_name search_opts = { 'all_tenants': all_tenants, 'display_name': args.name, 'status': args.status, 'volume_id': args.volume_id, } snapshots = cs.volume_snapshots.list(search_opts=search_opts) _translate_volume_snapshot_keys(snapshots) utils.print_list(snapshots, ['ID', 'Volume ID', 'Status', 'Name', 'Size']) @utils.arg('snapshot', metavar='', help='Name or ID of the snapshot.') @utils.service_type('volumev2') def do_snapshot_show(cs, args): """Show details about a snapshot.""" snapshot = _find_volume_snapshot(cs, args.snapshot) _print_volume_snapshot(snapshot) @utils.arg('volume', metavar='', help='Name or ID of the volume to snapshot') @utils.arg('--force', metavar='', help='Optional flag to indicate whether ' 'to snapshot a volume even if it\'s ' 'attached to an instance. (Default=False)', default=False) @utils.arg('--name', metavar='', default=None, help='Optional snapshot name. (Default=None)') @utils.arg('--display-name', help=argparse.SUPPRESS) @utils.arg('--display_name', help=argparse.SUPPRESS) @utils.arg('--description', metavar='', default=None, help='Optional snapshot description. (Default=None)') @utils.arg('--display-description', help=argparse.SUPPRESS) @utils.arg('--display_description', help=argparse.SUPPRESS) @utils.service_type('volumev2') def do_snapshot_create(cs, args): """Add a new snapshot.""" if args.display_name is not None: args.name = args.display_name if args.display_description is not None: args.description = args.display_description volume = utils.find_volume(cs, args.volume) snapshot = cs.volume_snapshots.create(volume.id, args.force, args.name, args.description) _print_volume_snapshot(snapshot) @utils.arg('snapshot', metavar='', help='Name or ID of the snapshot to delete.') @utils.service_type('volumev2') def do_snapshot_delete(cs, args): """Remove a snapshot.""" snapshot = _find_volume_snapshot(cs, args.snapshot) snapshot.delete() @utils.arg('snapshot', metavar='', help='Name or ID of the snapshot.') @utils.arg('name', nargs='?', metavar='', help='New name for the snapshot.') @utils.arg('--description', metavar='', help='Optional snapshot description. (Default=None)', default=None) @utils.arg('--display-description', help=argparse.SUPPRESS) @utils.arg('--display_description', help=argparse.SUPPRESS) @utils.service_type('volumev2') def do_snapshot_rename(cs, args): """Rename a snapshot.""" kwargs = {} if args.name is not None: kwargs['name'] = args.name if args.description is not None: kwargs['description'] = args.description elif args.display_description is not None: kwargs['description'] = args.display_description if not any(kwargs): msg = 'Must supply either name or description.' raise exceptions.ClientException(code=1, message=msg) _find_volume_snapshot(cs, args.snapshot).update(**kwargs) @utils.arg('snapshot', metavar='', nargs='+', help='Name or ID of the snapshot to modify.') @utils.arg('--state', metavar='', default='available', help=('Indicate which state to assign the snapshot. ' 'Options include available, error, creating, ' 'deleting, error_deleting. If no state is provided, ' 'available will be used.')) @utils.service_type('volumev2') def do_snapshot_reset_state(cs, args): """Explicitly update the state of a snapshot.""" failure_count = 0 single = (len(args.snapshot) == 1) for snapshot in args.snapshot: try: _find_volume_snapshot(cs, snapshot).reset_state(args.state) except Exception as e: failure_count += 1 msg = "Reset state for snapshot %s failed: %s" % (snapshot, e) if not single: print(msg) if failure_count == len(args.snapshot): if not single: msg = ("Unable to reset the state for any of the the specified " "snapshots.") raise exceptions.CommandError(msg) def _print_volume_type_list(vtypes): utils.print_list(vtypes, ['ID', 'Name']) def _print_type_and_extra_specs_list(vtypes): formatters = {'extra_specs': _print_type_extra_specs} utils.print_list(vtypes, ['ID', 'Name', 'extra_specs'], formatters) @utils.service_type('volumev2') def do_type_list(cs, args): """Print a list of available 'volume types'.""" vtypes = cs.volume_types.list() _print_volume_type_list(vtypes) @utils.service_type('volumev2') def do_extra_specs_list(cs, args): """Print a list of current 'volume types and extra specs' (Admin Only).""" vtypes = cs.volume_types.list() _print_type_and_extra_specs_list(vtypes) @utils.arg('name', metavar='', help="Name of the new volume type") @utils.service_type('volumev2') def do_type_create(cs, args): """Create a new volume type.""" vtype = cs.volume_types.create(args.name) _print_volume_type_list([vtype]) @utils.arg('id', metavar='', help="Unique ID of the volume type to delete") @utils.service_type('volumev2') def do_type_delete(cs, args): """Delete a specific volume type.""" cs.volume_types.delete(args.id) @utils.arg('vtype', metavar='', help="Name or ID of the volume type") @utils.arg('action', metavar='', choices=['set', 'unset'], help="Actions: 'set' or 'unset'") @utils.arg('metadata', metavar='', nargs='+', default=[], help='Extra_specs to set/unset (only key is necessary on unset)') @utils.service_type('volumev2') def do_type_key(cs, args): """Set or unset extra_spec for a volume type.""" vtype = _find_volume_type(cs, args.vtype) keypair = _extract_metadata(args) if args.action == 'set': vtype.set_keys(keypair) elif args.action == 'unset': vtype.unset_keys(list(keypair)) def do_endpoints(cs, args): """Discover endpoints that get returned from the authenticate services.""" catalog = cs.client.service_catalog.catalog for e in catalog['access']['serviceCatalog']: utils.print_dict(e['endpoints'][0], e['name']) def do_credentials(cs, args): """Show user credentials returned from auth.""" catalog = cs.client.service_catalog.catalog utils.print_dict(catalog['access']['user'], "User Credentials") utils.print_dict(catalog['access']['token'], "Token") _quota_resources = ['volumes', 'snapshots', 'gigabytes'] _quota_infos = ['Type', 'In_use', 'Reserved', 'Limit'] def _quota_show(quotas): quota_dict = {} for resource in quotas._info: 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 = [] for resource in quotas._info.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: manager.update(identifier, **updates) @utils.arg('tenant', metavar='', help='UUID of tenant to list the quotas for.') @utils.service_type('volumev2') def do_quota_show(cs, args): """List the quotas for a tenant.""" _quota_show(cs.quotas.get(args.tenant)) @utils.arg('tenant', metavar='', help='UUID of tenant to list the quota usage for.') @utils.service_type('volumev2') def do_quota_usage(cs, args): """List the quota usage for a tenant.""" _quota_usage_show(cs.quotas.get(args.tenant, usage=True)) @utils.arg('tenant', metavar='', help='UUID of tenant to list the default quotas for.') @utils.service_type('volumev2') def do_quota_defaults(cs, args): """List the default quotas for a tenant.""" _quota_show(cs.quotas.defaults(args.tenant)) @utils.arg('tenant', metavar='', help='UUID of tenant to set the quotas for.') @utils.arg('--volumes', metavar='', type=int, default=None, help='New value for the "volumes" quota.') @utils.arg('--snapshots', metavar='', type=int, default=None, help='New value for the "snapshots" quota.') @utils.arg('--gigabytes', metavar='', type=int, default=None, help='New value for the "gigabytes" quota.') @utils.arg('--volume-type', metavar='', default=None, help='Volume type (Optional, Default=None)') @utils.service_type('volumev2') def do_quota_update(cs, args): """Update the quotas for a tenant.""" _quota_update(cs.quotas, args.tenant, args) @utils.arg('class_name', metavar='', help='Name of quota class to list the quotas for.') @utils.service_type('volumev2') def do_quota_class_show(cs, args): """List the quotas for a quota class.""" _quota_show(cs.quota_classes.get(args.class_name)) @utils.arg('class-name', metavar='', help='Name of quota class to set the quotas for.') @utils.arg('--volumes', metavar='', type=int, default=None, help='New value for the "volumes" quota.') @utils.arg('--snapshots', metavar='', type=int, default=None, help='New value for the "snapshots" quota.') @utils.arg('--gigabytes', metavar='', type=int, default=None, help='New value for the "gigabytes" quota.') @utils.arg('--volume-type', metavar='', default=None, help='Volume type (Optional, Default=None)') @utils.service_type('volumev2') def do_quota_class_update(cs, args): """Update the quotas for a quota class.""" _quota_update(cs.quota_classes, args.class_name, args) @utils.service_type('volumev2') def do_absolute_limits(cs, args): """Print a list of absolute limits for a user""" limits = cs.limits.get().absolute columns = ['Name', 'Value'] utils.print_list(limits, columns) @utils.service_type('volumev2') def do_rate_limits(cs, args): """Print a list of rate limits for a user""" limits = cs.limits.get().rate columns = ['Verb', 'URI', 'Value', 'Remain', 'Unit', 'Next_Available'] utils.print_list(limits, columns) def _print_type_extra_specs(vol_type): try: return vol_type.get_keys() except exceptions.NotFound: return "N/A" def _find_volume_type(cs, vtype): """Get a volume type by name or ID.""" return utils.find_resource(cs.volume_types, vtype) @utils.arg('volume', metavar='', help='Name or ID of the volume to snapshot') @utils.arg('--force', metavar='', help='Optional flag to indicate whether ' 'to upload a volume even if it\'s ' 'attached to an instance. (Default=False)', default=False) @utils.arg('--container-format', metavar='', help='Optional type for container format ' '(Default=bare)', default='bare') @utils.arg('--container_format', help=argparse.SUPPRESS) @utils.arg('--disk-format', metavar='', help='Optional type for disk format ' '(Default=raw)', default='raw') @utils.arg('--disk_format', help=argparse.SUPPRESS) @utils.arg('image_name', metavar='', help='Name for created image') @utils.arg('--image_name', help=argparse.SUPPRESS) @utils.service_type('volumev2') def do_upload_to_image(cs, args): """Upload volume to image service as image.""" volume = utils.find_volume(cs, args.volume) _print_volume_image(volume.upload_to_image(args.force, args.image_name, args.container_format, args.disk_format)) @utils.arg('volume', metavar='', help='ID of the volume to migrate') @utils.arg('host', metavar='', help='Destination host') @utils.arg('--force-host-copy', metavar='', choices=['True', 'False'], required=False, help='Optional flag to force the use of the generic ' 'host-based migration mechanism, bypassing driver ' 'optimizations (Default=False).', default=False) @utils.service_type('volumev2') def do_migrate(cs, args): """Migrate the volume to the new host.""" volume = utils.find_volume(cs, args.volume) volume.migrate_volume(args.host, args.force_host_copy) @utils.arg('volume', metavar='', help='Name or ID of the volume to retype') @utils.arg('new_type', metavar='', help='New volume type') @utils.arg('--migration-policy', metavar='', required=False, choices=['never', 'on-demand'], default='never', help='Policy on migrating the volume during the retype.') @utils.service_type('volumev2') def do_retype(cs, args): """Change the volume's type.""" volume = utils.find_volume(cs, args.volume) volume.retype(args.new_type, args.migration_policy) @utils.arg('volume', metavar='', help='Name or ID of the volume to backup.') @utils.arg('--container', metavar='', help='Optional backup container name. (Default=None)', default=None) @utils.arg('--display-name', help=argparse.SUPPRESS) @utils.arg('--name', metavar='', help='Optional backup name. (Default=None)', default=None) @utils.arg('--display-description', help=argparse.SUPPRESS) @utils.arg('--description', metavar='', default=None, help='Options backup description (Default=None)') @utils.service_type('volumev2') def do_backup_create(cs, args): """Creates a backup.""" if args.display_name is not None: args.name = args.display_name if args.display_description is not None: args.description = args.display_description volume = utils.find_volume(cs, args.volume) backup = cs.backups.create(volume.id, args.container, args.name, args.description) info = {"volume_id": volume.id} info.update(backup._info) if 'links' in info: info.pop('links') utils.print_dict(info) @utils.arg('backup', metavar='', help='Name or ID of the backup.') @utils.service_type('volumev2') def do_backup_show(cs, args): """Show details about a backup.""" backup = _find_backup(cs, args.backup) info = dict() info.update(backup._info) info.pop('links', None) utils.print_dict(info) @utils.service_type('volumev2') def do_backup_list(cs, args): """List all the backups.""" backups = cs.backups.list() columns = ['ID', 'Volume ID', 'Status', 'Name', 'Size', 'Object Count', 'Container'] utils.print_list(backups, columns) @utils.arg('backup', metavar='', help='Name or ID of the backup to delete.') @utils.service_type('volumev2') def do_backup_delete(cs, args): """Remove a backup.""" backup = _find_backup(cs, args.backup) backup.delete() @utils.arg('backup', metavar='', help='ID of the backup to restore.') @utils.arg('--volume-id', metavar='', help=argparse.SUPPRESS, default=None) @utils.arg('--volume', metavar='', help='Optional ID(or name) of the volume to restore to.', default=None) @utils.service_type('volumev2') def do_backup_restore(cs, args): """Restore a backup.""" vol = args.volume or args.volume_id if vol: volume_id = utils.find_volume(cs, vol).id else: volume_id = None cs.restores.restore(args.backup, volume_id) @utils.arg('volume', metavar='', help='Name or ID of the volume to transfer.') @utils.arg('--name', metavar='', default=None, help='Optional transfer name. (Default=None)') @utils.arg('--display-name', help=argparse.SUPPRESS) @utils.service_type('volumev2') def do_transfer_create(cs, args): """Creates a volume transfer.""" if args.display_name is not None: args.name = args.display_name volume = utils.find_volume(cs, args.volume) transfer = cs.transfers.create(volume.id, args.name) info = dict() info.update(transfer._info) info.pop('links', None) utils.print_dict(info) @utils.arg('transfer', metavar='', help='Name or ID of the transfer to delete.') @utils.service_type('volumev2') def do_transfer_delete(cs, args): """Undo a transfer.""" transfer = _find_transfer(cs, args.transfer) transfer.delete() @utils.arg('transfer', metavar='', help='ID of the transfer to accept.') @utils.arg('auth_key', metavar='', help='Auth key of the transfer to accept.') @utils.service_type('volumev2') def do_transfer_accept(cs, args): """Accepts a volume transfer.""" transfer = cs.transfers.accept(args.transfer, args.auth_key) info = dict() info.update(transfer._info) info.pop('links', None) utils.print_dict(info) @utils.service_type('volumev2') def do_transfer_list(cs, args): """List all the transfers.""" transfers = cs.transfers.list() columns = ['ID', 'Volume ID', 'Name'] utils.print_list(transfers, columns) @utils.arg('transfer', metavar='', help='Name or ID of the transfer to accept.') @utils.service_type('volumev2') def do_transfer_show(cs, args): """Show details about a transfer.""" transfer = _find_transfer(cs, args.transfer) info = dict() info.update(transfer._info) info.pop('links', None) utils.print_dict(info) @utils.arg('volume', metavar='', help='Name or ID of the volume to extend.') @utils.arg('new_size', metavar='', type=int, help='New size of volume in GB') @utils.service_type('volumev2') def do_extend(cs, args): """Attempt to extend the size of an existing volume.""" volume = utils.find_volume(cs, args.volume) cs.volumes.extend(volume, args.new_size) @utils.arg('--host', metavar='', default=None, help='Name of host.') @utils.arg('--binary', metavar='', default=None, help='Service binary.') @utils.service_type('volumev2') def do_service_list(cs, args): """List all the services. Filter by host & service binary.""" result = cs.services.list(host=args.host, binary=args.binary) columns = ["Binary", "Host", "Zone", "Status", "State", "Updated_at"] utils.print_list(result, columns) @utils.arg('host', metavar='', help='Name of host.') @utils.arg('binary', metavar='', help='Service binary.') @utils.service_type('volumev2') def do_service_enable(cs, args): """Enable the service.""" result = cs.services.enable(args.host, args.binary) columns = ["Host", "Binary", "Status"] utils.print_list([result], columns) @utils.arg('host', metavar='', help='Name of host.') @utils.arg('binary', metavar='', help='Service binary.') @utils.service_type('volumev2') def do_service_disable(cs, args): """Disable the service.""" result = cs.services.disable(args.host, args.binary) columns = ["Host", "Binary", "Status"] utils.print_list([result], columns) def _treeizeAvailabilityZone(zone): """Build a tree view for availability zones.""" AvailabilityZone = availability_zones.AvailabilityZone az = AvailabilityZone(zone.manager, copy.deepcopy(zone._info), zone._loaded) result = [] # Zone tree view item az.zoneName = zone.zoneName az.zoneState = ('available' if zone.zoneState['available'] else 'not available') az._info['zoneName'] = az.zoneName az._info['zoneState'] = az.zoneState result.append(az) if getattr(zone, "hosts", None) and zone.hosts is not None: for (host, services) in zone.hosts.items(): # Host tree view item az = AvailabilityZone(zone.manager, copy.deepcopy(zone._info), zone._loaded) az.zoneName = '|- %s' % host az.zoneState = '' az._info['zoneName'] = az.zoneName az._info['zoneState'] = az.zoneState result.append(az) for (svc, state) in services.items(): # Service tree view item az = AvailabilityZone(zone.manager, copy.deepcopy(zone._info), zone._loaded) az.zoneName = '| |- %s' % svc az.zoneState = '%s %s %s' % ( 'enabled' if state['active'] else 'disabled', ':-)' if state['available'] else 'XXX', state['updated_at']) az._info['zoneName'] = az.zoneName az._info['zoneState'] = az.zoneState result.append(az) return result @utils.service_type('volumev2') def do_availability_zone_list(cs, _args): """List all the availability zones.""" try: availability_zones = cs.availability_zones.list() except exceptions.Forbidden as e: # policy doesn't allow probably try: availability_zones = cs.availability_zones.list(detailed=False) except Exception: raise e result = [] for zone in availability_zones: result += _treeizeAvailabilityZone(zone) _translate_availability_zone_keys(result) utils.print_list(result, ['Name', 'Status']) def _print_volume_encryption_type_list(encryption_types): """ Display a tabularized list of 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']) @utils.service_type('volumev2') def do_encryption_type_list(cs, args): """List encryption type information for all volume types (Admin Only).""" result = cs.volume_encryption_types.list() utils.print_list(result, ['Volume Type ID', 'Provider', 'Cipher', 'Key Size', 'Control Location']) @utils.arg('volume_type', metavar='', type=str, help="Name or ID of the volume type") @utils.service_type('volumev2') def do_encryption_type_show(cs, args): """Show the encryption type information for a volume type (Admin Only).""" volume_type = _find_volume_type(cs, args.volume_type) result = cs.volume_encryption_types.get(volume_type) # Display result or an empty table if no result if hasattr(result, 'volume_type_id'): _print_volume_encryption_type_list([result]) else: _print_volume_encryption_type_list([]) @utils.arg('volume_type', metavar='', type=str, help="Name or ID of the volume type") @utils.arg('provider', metavar='', type=str, help="Class providing encryption support (e.g. LuksEncryptor)") @utils.arg('--cipher', metavar='', type=str, required=False, default=None, help="Encryption algorithm/mode to use (e.g., aes-xts-plain64) " "(Optional, Default=None)") @utils.arg('--key_size', metavar='', type=int, required=False, default=None, help="Size of the encryption key, in bits (e.g., 128, 256) " "(Optional, Default=None)") @utils.arg('--control_location', metavar='', choices=['front-end', 'back-end'], type=str, required=False, default=None, help="Notional service where encryption is performed (e.g., " "front-end=Nova). Values: 'front-end', 'back-end' " "(Optional, Default=None)") @utils.service_type('volumev2') def do_encryption_type_create(cs, args): """Create a new encryption type for a volume type (Admin Only).""" volume_type = _find_volume_type(cs, args.volume_type) body = {} body['provider'] = args.provider body['cipher'] = args.cipher body['key_size'] = args.key_size body['control_location'] = args.control_location result = cs.volume_encryption_types.create(volume_type, body) _print_volume_encryption_type_list([result]) @utils.arg('volume_type', metavar='', type=str, help="Name or ID of the volume type") @utils.service_type('volumev2') def do_encryption_type_delete(cs, args): """Delete the encryption type for a volume type (Admin Only).""" volume_type = _find_volume_type(cs, args.volume_type) cs.volume_encryption_types.delete(volume_type) def _print_qos_specs(qos_specs): utils.print_dict(qos_specs._info) 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']) @utils.arg('name', metavar='', help="Name of the new QoS specs") @utils.arg('metadata', metavar='', nargs='+', default=[], help='Specifications for QoS') @utils.service_type('volumev2') def do_qos_create(cs, args): """Create a new qos specs.""" keypair = None if args.metadata is not None: keypair = _extract_metadata(args) qos_specs = cs.qos_specs.create(args.name, keypair) _print_qos_specs(qos_specs) @utils.service_type('volumev2') def do_qos_list(cs, args): """Get full list of qos specs.""" qos_specs = cs.qos_specs.list() _print_qos_specs_list(qos_specs) @utils.arg('qos_specs', metavar='', help='ID of the qos_specs to show.') @utils.service_type('volumev2') def do_qos_show(cs, args): """Get a specific qos specs.""" qos_specs = _find_qos_specs(cs, args.qos_specs) _print_qos_specs(qos_specs) @utils.arg('qos_specs', metavar='', help='ID of the qos_specs to delete.') @utils.arg('--force', metavar='', default=False, help='Optional flag that indicates whether to delete ' 'specified qos specs even if it is in-use.') @utils.service_type('volumev2') def do_qos_delete(cs, args): """Delete a specific qos specs.""" force = strutils.bool_from_string(args.force) qos_specs = _find_qos_specs(cs, args.qos_specs) cs.qos_specs.delete(qos_specs, force) @utils.arg('qos_specs', metavar='', help='ID of qos_specs.') @utils.arg('vol_type_id', metavar='', help='ID of volume type to be associated with.') @utils.service_type('volumev2') def do_qos_associate(cs, args): """Associate qos specs with specific volume type.""" cs.qos_specs.associate(args.qos_specs, args.vol_type_id) @utils.arg('qos_specs', metavar='', help='ID of qos_specs.') @utils.arg('vol_type_id', metavar='', help='ID of volume type to be associated with.') @utils.service_type('volumev2') def do_qos_disassociate(cs, args): """Disassociate qos specs from specific volume type.""" cs.qos_specs.disassociate(args.qos_specs, args.vol_type_id) @utils.arg('qos_specs', metavar='', help='ID of qos_specs to be operate on.') @utils.service_type('volumev2') def do_qos_disassociate_all(cs, args): """Disassociate qos specs from all of its associations.""" cs.qos_specs.disassociate_all(args.qos_specs) @utils.arg('qos_specs', metavar='', help='ID of qos specs') @utils.arg('action', metavar='', choices=['set', 'unset'], help="Actions: 'set' or 'unset'") @utils.arg('metadata', metavar='key=value', nargs='+', default=[], help='QoS specs to set/unset (only key is necessary on unset)') def do_qos_key(cs, args): """Set or unset specifications for a qos spec.""" keypair = _extract_metadata(args) if args.action == 'set': cs.qos_specs.set_keys(args.qos_specs, keypair) elif args.action == 'unset': cs.qos_specs.unset_keys(args.qos_specs, list(keypair)) @utils.arg('qos_specs', metavar='', help='ID of the qos_specs.') @utils.service_type('volumev2') def do_qos_get_association(cs, args): """Get all associations of specific qos specs.""" associations = cs.qos_specs.get_associations(args.qos_specs) _print_associations_list(associations) @utils.arg('snapshot', metavar='', help='ID of the snapshot to update metadata on.') @utils.arg('action', metavar='', choices=['set', 'unset'], help="Actions: 'set' or 'unset'") @utils.arg('metadata', metavar='', nargs='+', default=[], help='Metadata to set/unset (only key is necessary on unset)') @utils.service_type('volumev2') def do_snapshot_metadata(cs, args): """Set or Delete metadata of a snapshot.""" snapshot = _find_volume_snapshot(cs, args.snapshot) metadata = _extract_metadata(args) if args.action == 'set': metadata = snapshot.set_metadata(metadata) utils.print_dict(metadata._info) elif args.action == 'unset': snapshot.delete_metadata(list(metadata.keys())) @utils.arg('snapshot', metavar='', help='ID of snapshot') @utils.service_type('volumev2') def do_snapshot_metadata_show(cs, args): """Show metadata of given snapshot.""" snapshot = _find_volume_snapshot(cs, args.snapshot) utils.print_dict(snapshot._info['metadata'], 'Metadata-property') @utils.arg('volume', metavar='', help='ID of volume') @utils.service_type('volumev2') def do_metadata_show(cs, args): """Show metadata of given volume.""" volume = utils.find_volume(cs, args.volume) utils.print_dict(volume._info['metadata'], 'Metadata-property') @utils.arg('volume', metavar='', help='ID of the volume to update metadata on.') @utils.arg('metadata', metavar='', nargs='+', default=[], help='Metadata entry/entries to update.') @utils.service_type('volumev2') def do_metadata_update_all(cs, args): """Update all metadata of a volume.""" volume = utils.find_volume(cs, args.volume) metadata = _extract_metadata(args) metadata = volume.update_all_metadata(metadata) utils.print_dict(metadata) @utils.arg('snapshot', metavar='', help='ID of the snapshot to update metadata on.') @utils.arg('metadata', metavar='', nargs='+', default=[], help='Metadata entry/entries to update') @utils.service_type('volumev2') def do_snapshot_metadata_update_all(cs, args): """Update all metadata of a snapshot.""" snapshot = _find_volume_snapshot(cs, args.snapshot) metadata = _extract_metadata(args) metadata = snapshot.update_all_metadata(metadata) utils.print_dict(metadata) @utils.arg('volume', metavar='', help='ID of the volume to update.') @utils.arg('read_only', metavar='', choices=['True', 'true', 'False', 'false'], help='Flag to indicate whether to update volume to ' 'read-only access mode.') @utils.service_type('volumev2') def do_readonly_mode_update(cs, args): """Update volume read-only access mode flag.""" volume = utils.find_volume(cs, args.volume) cs.volumes.update_readonly_flag(volume, strutils.bool_from_string(args.read_only)) python-cinderclient-1.0.8/cinderclient/v2/quotas.py0000664000175300017540000000315412274343712023527 0ustar jenkinsjenkins00000000000000# Copyright (c) 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from cinderclient import base class QuotaSet(base.Resource): @property def id(self): """Needed by base.Resource to self-refresh and be indexed.""" return self.tenant_id def update(self, *args, **kwargs): self.manager.update(self.tenant_id, *args, **kwargs) class QuotaSetManager(base.Manager): resource_class = QuotaSet def get(self, tenant_id, usage=False): if hasattr(tenant_id, 'tenant_id'): tenant_id = tenant_id.tenant_id return self._get("/os-quota-sets/%s?usage=%s" % (tenant_id, usage), "quota_set") def update(self, tenant_id, **updates): body = {'quota_set': {'tenant_id': tenant_id}} for update in updates: body['quota_set'][update] = updates[update] self._update('/os-quota-sets/%s' % (tenant_id), body) def defaults(self, tenant_id): return self._get('/os-quota-sets/%s/defaults' % tenant_id, 'quota_set') python-cinderclient-1.0.8/AUTHORS0000664000175300017540000000000112274343764017712 0ustar jenkinsjenkins00000000000000 python-cinderclient-1.0.8/openstack-common.conf0000664000175300017540000000030512274343712022766 0ustar jenkinsjenkins00000000000000[DEFAULT] # The list of modules to copy from openstack-common module=apiclient module=strutils module=install_venv_common # The base module to hold the copy of openstack.common base=cinderclient