python-troveclient-1.0.3/0000775000175300017540000000000012232557034016530 5ustar jenkinsjenkins00000000000000python-troveclient-1.0.3/python_troveclient.egg-info/0000775000175300017540000000000012232557034024161 5ustar jenkinsjenkins00000000000000python-troveclient-1.0.3/python_troveclient.egg-info/dependency_links.txt0000664000175300017540000000000112232557033030226 0ustar jenkinsjenkins00000000000000 python-troveclient-1.0.3/python_troveclient.egg-info/SOURCES.txt0000664000175300017540000000513512232557034026051 0ustar jenkinsjenkins00000000000000.testr.conf AUTHORS ChangeLog LICENSE MANIFEST.in README.rst openstack-common.conf requirements.txt run_local.sh setup.cfg setup.py test-requirements.txt tox.ini docs/source/conf.py docs/source/index.rst docs/source/pydocs.rst docs/source/usage.rst python_troveclient.egg-info/PKG-INFO python_troveclient.egg-info/SOURCES.txt python_troveclient.egg-info/dependency_links.txt python_troveclient.egg-info/entry_points.txt python_troveclient.egg-info/not-zip-safe python_troveclient.egg-info/requires.txt python_troveclient.egg-info/top_level.txt tools/install_venv_common.py troveclient/__init__.py troveclient/base.py troveclient/client.py troveclient/common.py troveclient/exceptions.py troveclient/service_catalog.py troveclient/shell.py troveclient/utils.py troveclient/compat/__init__.py troveclient/compat/auth.py troveclient/compat/base.py troveclient/compat/cli.py troveclient/compat/client.py troveclient/compat/common.py troveclient/compat/exceptions.py troveclient/compat/mcli.py troveclient/compat/utils.py troveclient/compat/versions.py troveclient/compat/xml.py troveclient/compat/tests/__init__.py troveclient/compat/tests/test_auth.py troveclient/compat/tests/test_common.py troveclient/compat/tests/test_xml.py troveclient/openstack/__init__.py troveclient/openstack/common/__init__.py troveclient/openstack/common/gettextutils.py troveclient/openstack/common/importutils.py troveclient/openstack/common/strutils.py troveclient/openstack/common/apiclient/__init__.py troveclient/openstack/common/apiclient/auth.py troveclient/openstack/common/apiclient/base.py troveclient/openstack/common/apiclient/client.py troveclient/openstack/common/apiclient/exceptions.py troveclient/openstack/common/apiclient/fake_client.py troveclient/openstack/common/py3kcompat/__init__.py troveclient/openstack/common/py3kcompat/urlutils.py troveclient/tests/__init__.py troveclient/tests/test_accounts.py troveclient/tests/test_base.py troveclient/tests/test_client.py troveclient/tests/test_common.py troveclient/tests/test_instances.py troveclient/tests/test_limits.py troveclient/tests/test_management.py troveclient/tests/test_secgroups.py troveclient/tests/test_users.py troveclient/tests/test_utils.py troveclient/v1/__init__.py troveclient/v1/accounts.py troveclient/v1/backups.py troveclient/v1/client.py troveclient/v1/databases.py troveclient/v1/diagnostics.py troveclient/v1/flavors.py troveclient/v1/hosts.py troveclient/v1/instances.py troveclient/v1/limits.py troveclient/v1/management.py troveclient/v1/quota.py troveclient/v1/root.py troveclient/v1/security_groups.py troveclient/v1/shell.py troveclient/v1/storage.py troveclient/v1/users.pypython-troveclient-1.0.3/python_troveclient.egg-info/top_level.txt0000664000175300017540000000001412232557033026705 0ustar jenkinsjenkins00000000000000troveclient python-troveclient-1.0.3/python_troveclient.egg-info/not-zip-safe0000664000175300017540000000000112232556656026420 0ustar jenkinsjenkins00000000000000 python-troveclient-1.0.3/python_troveclient.egg-info/PKG-INFO0000664000175300017540000002162112232557033025257 0ustar jenkinsjenkins00000000000000Metadata-Version: 1.1 Name: python-troveclient Version: 1.0.3 Summary: Client library for OpenStack DBaaS API Home-page: http://www.openstack.org/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description: Python bindings to the OpenStack Trove API =========================================== This is a client for the OpenStack Trove API. There's a Python API (the ``troveclient`` module), and a command-line script (``trove``). Each implements 100% of the OpenStack Trove API. See the `OpenStack CLI guide`_ for information on how to use the ``trove`` 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-troveclient .. _Launchpad: https://launchpad.net/python-troveclient .. _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-troveclient 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, ``trove``, 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 ``--version``. Or set them as an environment variables as well:: 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 ``trove help``:: usage: trove [--version] [--debug] [--os-username ] [--os-password ] [--os-tenant-name ] [--os-tenant-id ] [--os-auth-url ] [--os-region-name ] [--service-type ] [--service-name ] [--database-service-name ] [--endpoint-type ] [--os-database-api-version ] [--os-cacert ] [--retries ] ... Command-line interface to the OpenStack Trove API. Positional arguments: backup-create Creates a backup. backup-delete Deletes a backup. backup-list List available backups. backup-list-instance List available backups for an instance. backup-show Show details of a backup. create Creates a new instance. database-create Creates a database on an instance. database-delete Deletes a database. database-list Lists available databases on an instance. delete Deletes an instance. flavor-list Lists available flavors. flavor-show Show details of a flavor. limit-list Lists the limits for a tenant. list List all the instances. resize-flavor Resizes the flavor of an instance. resize-volume Resizes the volume size of an instance. restart Restarts the instance. root-enable Enables root for a instance. root-show Gets root enabled status for a instance. secgroup-add-rule Creates a security group rule. secgroup-delete-rule Deletes a security group rule. secgroup-list Lists all security groups. secgroup-show Shows details about a security group. show Show details of an instance. user-create Creates a user. user-delete Deletes a user from the instance. user-grant-access Grants access to a database(s) for a user. user-list Lists the users for a instance. user-revoke-access Revokes access to a database for a user. user-show Gets a user from the instance. user-show-access Gets a users access from the instance. user-update-attributes Updates a users attributes from the instance. bash-completion Print arguments for bash_completion. help Display help about this program or one of its subcommands. Optional arguments: --version show program's version number and exit --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-tenant-id Defaults to env[OS_TENANT_ID]. --os-auth-url Defaults to env[OS_AUTH_URL]. --os-region-name Defaults to env[OS_REGION_NAME]. --service-type Defaults to database for most actions --service-name Defaults to env[TROVE_SERVICE_NAME] --database-service-name Defaults to env[TROVE_DATABASE_SERVICE_NAME] --endpoint-type Defaults to env[TROVE_ENDPOINT_TYPE] or publicURL. --os-database-api-version Accepts 1,defaults to env[OS_DATABASE_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 troveclient.v1 import client >>> nt = client.Client(USER, PASS, TENANT, AUTH_URL, service_type="database") >>> nt.instances.list() [...] Platform: UNKNOWN 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-troveclient-1.0.3/python_troveclient.egg-info/entry_points.txt0000664000175300017540000000006212232557033027454 0ustar jenkinsjenkins00000000000000[console_scripts] trove = troveclient.shell:main python-troveclient-1.0.3/python_troveclient.egg-info/requires.txt0000664000175300017540000000013412232557033026556 0ustar jenkinsjenkins00000000000000pbr>=0.5.21,<1.0 PrettyTable>=0.6,<0.8 requests>=1.1 simplejson>=2.0.9 Babel>=1.3 six>=1.4.1python-troveclient-1.0.3/ChangeLog0000664000175300017540000020316212232557033020305 0ustar jenkinsjenkins00000000000000commit 00507ae54fcb17a442fcaf6908d333ef05578879 Merge: 24443e1 e9c5edd Author: Jenkins Date: Fri Oct 25 19:52:20 2013 +0000 Merge "Add --bypass-url option to trove client" commit 24443e177efa445c9dd9004d7896126531e1b238 Merge: d56ac15 1b2928c Author: Jenkins Date: Wed Oct 23 19:16:16 2013 +0000 Merge "Fixing typos that were in the readme" commit 1b2928cf49b3168501d4d93c84e8e4278399992f Author: Michael Basnight Date: Wed Oct 23 18:44:38 2013 +0000 Fixing typos that were in the readme Change-Id: I5e22e95133824967283320d5922a1e57ded7a4f3 commit d56ac15ebf86c5ef0899bd23c75aa4e0770a7f93 Author: Michael Basnight Date: Tue Oct 22 17:48:09 2013 +0000 Removing unused files with new client fixes bug 1243452 Change-Id: Ia51e6d48e486baa818ebe3108d37bb1de537702d commit 6431cc825807ddac6a4aa0dd174efe0b42e13760 Author: Michael Basnight Date: Wed Oct 23 16:13:40 2013 +0000 Adding py33 gating to the client Change-Id: I47e493bae8a00b5182608e31fcea82c3cacb152e commit ebacf913ab52e13da213de9bee01d99b2d73ba8b Merge: bdb8e0b 5aaf3d4 Author: Jenkins Date: Wed Oct 23 15:40:14 2013 +0000 Merge "Use six.iteritems() in for loop" commit bdb8e0b23703f76570f7e6aa180c65de1418004e Merge: b918934 fa3f5e9 Author: Jenkins Date: Wed Oct 23 15:40:10 2013 +0000 Merge "Resolve compatability for troveclient/compat" commit b918934aaa304929b59272cecb096a3ccd1d15c9 Merge: 5dcb22e 31a6cde Author: Jenkins Date: Wed Oct 23 15:40:07 2013 +0000 Merge "Include troveclient/compat/tests in testr" commit 5dcb22eedef735d8ce206ffc9e20aeb613c62dc6 Merge: 1fe1372 5af5d6f Author: Jenkins Date: Wed Oct 23 15:39:56 2013 +0000 Merge "Make use of strutils.to_slug in slugify()" commit 1fe1372734fd3683a60bc28f19b98b19e304781f Merge: 3d0505c 0127670 Author: Jenkins Date: Wed Oct 23 15:38:42 2013 +0000 Merge "Replace basestring with six.string_types" commit 3d0505c4b96dfb912433be9c7b9d0207f2567b55 Merge: bff7467 5de4b93 Author: Jenkins Date: Wed Oct 23 15:38:41 2013 +0000 Merge "Transform print statement to print function" commit bff74671e936227a9bfaff2609af021f626ce5c1 Merge: 8d19ac1 38a5a1d Author: Jenkins Date: Wed Oct 23 15:38:35 2013 +0000 Merge "Replace g.next() with next(g)" commit 8d19ac1b2836fb4216d6d3a2323cf22fe9aca87b Merge: 206b0f4 24a650d Author: Jenkins Date: Wed Oct 23 15:38:34 2013 +0000 Merge "Import exceptions module from openstack-common" commit 206b0f4578dac7a638f2a96ba692afdd0f40b5c9 Merge: ec6062b c07e9cf Author: Jenkins Date: Wed Oct 23 15:37:16 2013 +0000 Merge "Replace urllib.quote with urlutils.quote" commit ec6062b44d37fd6562334f56574d71bfbe9fd85a Merge: 3ad0b6d b60095f Author: Jenkins Date: Wed Oct 23 15:37:16 2013 +0000 Merge "Import urlutils module from openstack-common" commit 3ad0b6dae4e59cde00406ebd92d9985193b9779d Merge: ca4cad8 4ff4fa0 Author: Jenkins Date: Wed Oct 23 15:37:15 2013 +0000 Merge "Import exceptions module from openstack-common" commit ca4cad8a6cbf05254214a53ce3f0bb2f7965531a Merge: af4d4ed 59b0f1c Author: Jenkins Date: Wed Oct 23 15:37:15 2013 +0000 Merge "Fix unicode issue" commit 5aaf3d46ee336298cdf6a228e7a434b109c76673 Author: Kui Shi Date: Sat Oct 19 18:06:54 2013 +0800 Use six.iteritems() in for loop Use six.iteritems() in for loop. Use next() built-in function to return next item. Partial implements: blueprint py33-support Change-Id: Idff28608232ad9a41dc63d397d88e363ffde8193 commit fa3f5e94434292ade94b1f59474802759469b078 Author: Kui Shi Date: Sat Oct 19 17:50:50 2013 +0800 Resolve compatability for troveclient/compat transform print statement to print function import urlutils from openstack common Partial implements: blueprint py33-support Change-Id: I2491fb6719b21195abb6ea5602ddb8c86487d100 commit 31a6cde05289894579fad328ed104bb5bedb46c9 Author: Kui Shi Date: Sat Oct 19 15:46:20 2013 +0800 Include troveclient/compat/tests in testr All tests should be included in testr. In test_common.py, the testr command line will be read for parsing, finally emit error message: run.py: error: no such option: -t we should mock the sys.argv in setUp(). The analysis is here -------------------- troveclient/compat/common.py: CommandsBase 254 def _parse_options(self, parser): 255 opts, args = parser.parse_args() <-- 1. no arg is passed /usr/lib/python2.7/optparse.py 1361 def _get_args(self, args): 1362 if args is None: 1363 return sys.argv[1:] <-- 3. cmdline(testr command) is read 1364 else: 1365 return args[:] 1367 def parse_args(self, args=None, values=None): ... 1381 rargs = self._get_args(args) <-- 2. args is None Close-Bug #1241845 Change-Id: I092db96d1b6bb31c41e6ba4c5fc9606e3f8ac087 commit 5af5d6f30dab4fd707ed589e33c16a8965d94031 Author: Kui Shi Date: Fri Oct 18 00:03:53 2013 +0800 Make use of strutils.to_slug in slugify() Align with the change: 2070d4267c2a88b3493400b358003c8fa13778a6 Close-Bug #1241256 Change-Id: Idc16d75258baa1f5ee05ff48bfadc358c7655e55 commit 0127670f04006997bc4d22c8015588c51bd5785e Author: Kui Shi Date: Tue Oct 15 16:47:58 2013 +0800 Replace basestring with six.string_types six.string_types Possible types for text data. This is basestring() in Python 2 and str in Python 3. Partial implements: blueprint py33-support Change-Id: I25b463efdfe2e20af92dd805d4f44b40d4bd4b35 commit 5de4b935a487b50b2b19e2225150176df8d50778 Author: Kui Shi Date: Tue Oct 15 16:45:16 2013 +0800 Transform print statement to print function Partial implements: blueprint py33-support Change-Id: I029cb33b17a98da2ce225abc180f923f94ca0bf6 commit 38a5a1d5bd5bcccf52a66a84377429bdecdfa4a2 Author: Kui Shi Date: Tue Oct 15 16:34:58 2013 +0800 Replace g.next() with next(g) In Python 3, g.next() has been renamed to g.__next__(). The next() buildin function will work fine for Python 2&3. Partial implements: blueprint py33-support Change-Id: I992d019eb180f514388f21b2400b51c94c7bdad5 commit 24a650dfaac7d05a956aad144a0d4c5597f9d1d4 Author: Kui Shi Date: Tue Oct 15 16:09:39 2013 +0800 Import exceptions module from openstack-common Partial implements: blueprint py33-support Change-Id: Ie24466e8ab5f1c4474e758458ff3c892f1b0dd0d commit c07e9cf63df9863d2583ba40cfa126292b068e46 Author: Kui Shi Date: Tue Oct 15 15:50:32 2013 +0800 Replace urllib.quote with urlutils.quote quote is defined in urlutils for Python2&3 compatability. Partial implements: blueprint py33-support Change-Id: I81e667c76544b6bafd81d51d788158f6a062a90c commit b60095f92fcf2a51a2fc3592a121f511a6a38e1b Author: Kui Shi Date: Tue Oct 15 15:43:23 2013 +0800 Import urlutils module from openstack-common urlutils defines a series of urllib/urlparse modules for Python 2&3 compatability. Partial implements: blueprint py33-support Change-Id: I55c2c5d479d2f7a86d3a0c207dc40794006d951c commit 4ff4fa0661c96288a8d260a2e77eebf2f538db76 Author: Kui Shi Date: Tue Oct 15 15:31:42 2013 +0800 Import exceptions module from openstack-common Partial implements: blueprint py33-support Change-Id: I5ee3e85e1adf76779831beeb0a91591d342c798d commit af4d4ed4f75ac26339399c2c226cec69eb20caa8 Author: Michael Basnight Date: Tue Oct 22 17:46:41 2013 +0000 Fixing copyright and license headers * Added HP copyrights as appropriate Change-Id: If4f30b0caf03b16f5fb4f54185ba80d9fa41d0b0 commit e9c5eddc516594962b18cbe2f5e6e683067bd6f3 Author: daniel-a-nguyen Date: Thu Oct 17 10:46:09 2013 -0700 Add --bypass-url option to trove client Change-Id: Ie0596f2dd302eec293f3a5ab3306d56fb0fdcdb8 Closes-Bug: #1240285 commit 59b0f1c27a7da75e127be713fdc702edfa5204ed Author: Kui Shi Date: Tue Oct 15 06:50:33 2013 +0800 Fix unicode issue In Python 3, all string is unicode. Replace unicode() with six.u() Make use of strutils.to_slug() for slugify() definition Partial implements: blueprint py33-support Change-Id: I6a1e19c095a2fbafcbe47b34c7af17e1b0353b9e commit b46248c98fd3dcafb6dba24cf17103d4e644e2e4 Author: Kui Shi Date: Mon Oct 21 16:58:14 2013 +0800 Sync up exceptions.py from oslo oslo change: I169b13546bcd0d87353dbca5a9b64229b704fe66 Define _code_map in an expression. Partial implements: blueprint py33-support Change-Id: I4b9f5bc083ebe62739a33674d26728d4a8f1a2d6 commit 502f7113a832ae8f0ffcf44bf7cc8ea1fbc4877b Author: Kui Shi Date: Tue Oct 15 06:09:18 2013 +0800 change mode to octal for os.makedirs() Partial implements: blueprint py33-support Change-Id: I7a4307dfd526821e96b5c04ff636e650f20b753a commit 6a2592ac48bb86ec6778b4d12f08bf210b6aabab Author: Andrey Shestakov Date: Tue Oct 15 00:21:44 2013 +0300 Removed underscores from the end of some variables Change-Id: Iff664cef5ec580de6a33db1503a6e567701418a3 commit 2b80a03fa2577f5ca3167ada23b431fb8e1612f5 Author: Nikhil Manchanda Date: Thu Oct 10 20:00:49 2013 -0700 Fixed minor typos with trove-client help info Fixed typos and cleaned up unnecessary imports. Fixes bug 1238325 Change-Id: I7b2379cecb89e9f58cde9b2b68bddee41aefcfa3 commit 802802c02f1cee63ee4f578641a83cbd293b7be2 Author: Clare Springer Date: Thu Oct 10 13:06:13 2013 -0700 Fixes README and shell Change-Id: I2beec621fce4c8c8eb90631b2e543962f31687de Closes-Bug: #1238183 commit 3b09d769082fb288bbf8f8326d83b7db105762b8 Author: Michael Basnight Date: Thu Oct 10 19:09:47 2013 +0000 Fixing the backwards verb/noun cli names The cli names were backwards. instead of backup-list, list-backups was the name. They have been reordered to match the other openstack clients. Also added a better readme. Change-Id: I280f637bfa7bbeaf55c70115339f430dbb279152 commit fadd19e3a99510b08ca210587c907bf16dedff75 Author: Michael Basnight Date: Thu Oct 10 17:47:44 2013 +0000 Removing lxml/httplib2 depends These depends are compat only. They dont need to be listed as deps. Change-Id: Ia5718865100148d149e9c7657ace78cb08cacbac commit 0fca0a16b80f4b463c009fb7e3d4f48068fe59f9 Merge: 0036112 f7f599f Author: Jenkins Date: Thu Oct 10 06:31:22 2013 +0000 Merge "Fixed bug with showing backups" commit 0036112ad0a31fb0e2816c2e29b22ac334efa678 Merge: 9da3e2b fa48a8d Author: Jenkins Date: Thu Oct 10 06:29:41 2013 +0000 Merge "Correct handling args.backup in do_create" commit f7f599fe4f17dbeb913f1527df8f7274d7e884d3 Author: Nikhil Manchanda Date: Wed Oct 9 23:18:20 2013 -0700 Fixed bug with showing backups Pass on cs to _find_backup and use correct return variable. Change-Id: I042d161295e4e219749ebc7dd724cd8bc6ef4d10 commit fa48a8dcd46b5fd53d0907c7113d4620dcaa269f Author: Arata Notsu Date: Thu Oct 10 15:11:03 2013 +0900 Correct handling args.backup in do_create We have to use args.backup, not self.backup. Change-Id: I1f3c892cd4e9c1a42586bdd34d5239cb90c9af74 commit 9da3e2bff822ddb38f1107f8072c39421e4a165e Author: Arata Notsu Date: Thu Oct 10 14:59:32 2013 +0900 Enable shell to resize and restart By prepending "do_" to corresponding functions. Change-Id: I1a22527ab36b636eb3e6364e669a3d64154beca5 commit 06e7ba45863517fe1bd270c93508057c4a3bad1b Author: Monty Taylor Date: Wed Oct 9 23:03:44 2013 -0400 Add support for a service type env var Rackspace is currently setting their service types to weird things. Support them, because we love special snowflakes. Change-Id: I1519be00a3086945338a953bc2a1e7a4e0e65400 commit 42db0cf585504472ea440611826d62ce8a22fe81 Author: Monty Taylor Date: Wed Oct 9 22:47:23 2013 -0400 Tweak pbr conf Mainly, remove the pin in setup.py and remove the hook entry in setup.cfg. Update from requirements while we're at it. Change-Id: I34a05229b9ea0bdfd99dc16d09713c9494c1cb78 commit fd43cbd73b9de8a216039e6e66a8523e4c96543d Author: Michael Basnight Date: Thu Sep 26 22:26:02 2013 -0700 Massive refactoring to the troveclient The new client adheres to the standards of the other clients now. It prints out tables, uses ENVVAR's for auth, no longer stores pickled json in a login token, uses openstack common, and moves the cli operations into a v1 module for the future of trove when it has a v2 api. Please note for compatibility, the troveclient.compat module has the old cli. In order to deploy it, amend the setup.cfg to include the compat module. implements blueprint cli-compliance-upgrade Change-Id: Ie69d9dbc75ce90496da316244c97acca1877a327 commit 12a577f15a76694b8dc0182fe3b503a765974290 Author: Vipul Sabhaya Date: Fri Sep 20 16:28:41 2013 -0700 Remove trove as default value for Service Name * This causes service catalog lookup issues when the Catalog contains service_type = database, but service_name = somethine_else fixes bug 1228372 Change-Id: I1f7eebb06d7f57c0ecc95d8c05104f43e803c406 commit 4a82591c79b622757dc2e6f1c89a65ca4064f9d3 Merge: 30851b5 e3a301f Author: Jenkins Date: Mon Sep 23 16:24:39 2013 +0000 Merge "Replace OpenStack LLC with OpenStack Foundation" commit 30851b5b5b1004536c30f0e1ca5df50cb0de1078 Author: Michael Basnight Date: Mon Sep 16 08:56:09 2013 -0700 Removing reddwarf doc references Change-Id: I5272b50e8899cf23f5d76f5d753f0e893c04666d commit e3a301ff2805fcfdaf69422b109fc43934f8cbff Author: ZhiQiang Fan Date: Fri Sep 20 05:09:03 2013 +0800 Replace OpenStack LLC with OpenStack Foundation Some files still use trademark OpenStack LLC in header, which should be changed to OpenStack Foundation. Change-Id: Ib30cd06cdd13b9f949e028753716aa55736f4a40 Fixes-Bug: #1214176 commit deba3d7686862cfe74d5d1af6ec16fd7a6dd75af Merge: 28d5f26 cac11f2 Author: Jenkins Date: Mon Sep 16 18:10:07 2013 +0000 Merge "Added optional availability_zone flag for instance" commit 28d5f26eb7ffe7fbd9a019dbf07105fc609f5dfa Merge: 77e46b4 9445b00 Author: Jenkins Date: Fri Sep 13 01:12:21 2013 +0000 Merge "Fixed cli for root enabled" commit 77e46b4f3ec2ea61063e6ac00a0278f22a795fbe Merge: 9310edb c47d8c3 Author: Jenkins Date: Thu Sep 12 22:51:29 2013 +0000 Merge "Adds support for admin to create flavors through mgmt API" commit c47d8c36a761be5d2a98b2fda230e311e81b238b Author: Sushil Kumar Date: Mon Sep 2 20:50:36 2013 +0000 Adds support for admin to create flavors through mgmt API Extending the management API to allow for an admin to create flavors categorized on service type in trove. Change-Id: I8d5910e6dd9e497d1a8be4ffd58aa339d12db74f Implements: blueprint service-type-filter-on-flavors commit 9445b00f4e6c7ef5291a07f52d11aea39ab77d9a Author: daniel-a-nguyen Date: Mon Aug 26 18:00:37 2013 -0700 Fixed cli for root enabled Change-Id: I48e08c15709c51e8112f41cec390dd0e73864f28 Fixes: bug #1216135 commit 9310edb33d4415b9565ecfb36ad6fe45638aa4a7 Author: Alex Gaynor Date: Thu Sep 5 10:02:20 2013 -0700 Added support for running the tests under PyPy with tox This is a precursor to having them run under check and gate. Change-Id: I98692ac2591d488f0c5c6e64b1c176b21254289c commit cac11f2b1583aa193842300caa77940ea773149f Author: daniel-a-nguyen Date: Wed Sep 4 10:57:14 2013 -0700 Added optional availability_zone flag for instance Change-Id: I8ab62831481ceb27e672a265b0deaa6e3d39260a Implements: blueprint availability-zone-instance-create commit 04362650e026399893acc54f1091327a1e071105 Merge: 87329ef 812755e Author: Jenkins Date: Tue Sep 3 04:17:13 2013 +0000 Merge "PEP8 with tox -epep8 check fails" commit 812755e8a097ded6605ad9f31a64e49dfa8755d3 Author: Denis M Date: Fri Aug 30 20:32:17 2013 +0300 PEP8 with tox -epep8 check fails Change-Id: Ife06919f321ab4ad73bece2e6080fbd7d947eeef Fixes: bug #1219014 commit 87329ef2ad90c814bb869b72d3b6bc683b8fe6b1 Author: Nikhil Manchanda Date: Tue Aug 20 13:55:24 2013 -0700 Removed instance reset_password from python-troveclient The reset_password operation no longer exists and needs to be removed from python-troveclient. Change-Id: Ic162ef6deb86716fa12fa5541699f602c246563e Fixes: bug 1199507 commit aa024468a85904838e0e7d9537eeaf7801d1d5d5 Author: Monty Taylor Date: Mon Aug 5 18:04:11 2013 -0300 Sync with global requirements Change-Id: I708ffe506e1be9a72a92edfec911e3ee70481ff1 commit 8f96a61b6a42069f33d4eb0eb39b1c002ab1a0ea Author: Riddhi Shah Date: Fri Jun 28 17:48:03 2013 -0500 API for Modify User Attributes - username, host, password At present, when a Cloud Database user creates a username, password and hostname for a database user, he does not have the ability to modify these user attributes. This API enables the Cloud DB user to make changes to one or more of these user attributes (username,hostname,password). These changes are in the troveclient to enable simple command line requests Implements: blueprint modify-userattributes Change-Id: I0887f2d927eec8c77c0f562c9d855a370591231a commit b98893926d14bd0b5b2706c808688659695c1731 Author: justin-hopper Date: Wed Jul 17 18:10:33 2013 -0700 Fix Description param for Backups If description is not specified do not send as null Fixes: bug 1201990 Change-Id: Idf160cb5f8862665ded47146e96af521cf438519 commit 91595273be6aa7d8741aa38792364a411e94147f Author: Riddhi Shah Date: Tue Jul 9 14:59:32 2013 -0500 Wildcards in User Host This bug-fix, allows wildcards to be used for user host via providing ability to use '%' and '_'. Change-Id: Iebec499368b83f285283a0a8a80a2a63f0831dea Fixes: bug #1199197 commit 7c4f5cf0f184f5434fd04bb61fb8a8e99a6e34dc Merge: ebc91a9 5c56497 Author: Jenkins Date: Mon Jul 8 23:28:53 2013 +0000 Merge "Sync requirements with openstack/requirements" commit ebc91a9d42c24a7d099db61681c08ea3f843469f Merge: 0908ed6 4ddb58c Author: Jenkins Date: Mon Jul 8 23:25:37 2013 +0000 Merge "Start using pyflakes" commit 5c56497714aa52c885a1b3c98aa3aa6905655fec Author: Dirk Mueller Date: Sat Jun 29 13:08:58 2013 +0200 Sync requirements with openstack/requirements Don't require higher/different versions than openstack/requirements. It seems those higher requirements are not needed after all (I wasn't able to figure out why it was added) Change-Id: I4ea313212c0ead30e88669051f541d179eb2ecf0 commit 4ddb58ce9e609df3c0ac4be555f19643ef0511cc Author: Dirk Mueller Date: Wed Jun 26 13:47:41 2013 +0200 Start using pyflakes Instead of globally disabling pyflakes warnings, disable only those that occur frequently and fix the rest. Enable gating on those. Change-Id: I774d809ebcda2339b30c104b031211a3b2c491bd commit 0908ed6e23604207c17742477d35ad9b342e921e Author: Dirk Mueller Date: Wed Jun 26 13:36:35 2013 +0200 Add License, AUTHORS and ChangeLog to package Fixes LP Bug #1186518 Change-Id: I886504f3298d2e72834ffcc572023a779f3a6225 commit 9916c8f2733b683d859770d05dacd2c9c82912d7 Author: Michael Basnight Date: Mon Jun 17 23:34:27 2013 -0700 Rename from reddwarf to trove. Implements Blueprint reddwarf-trove-rename Change-Id: Ib2d694c7466887ca297bea4250eca17cdc06b7bf commit bc90b3e088d3d4b83b5b3de0f9f83d9b6956947d Author: Michael Basnight Date: Mon Jun 17 23:47:43 2013 -0700 Migrate to testr. Change-Id: I497fc21a0795b8921f989d7a00b6a73f7730cd22 commit 741d82c0e8546b594032695c2cc3dbd2f7473417 Author: Monty Taylor Date: Fri Jun 14 19:06:34 2013 -0400 Move to flake8/hacking. Change-Id: I57d6ddf55b032542233d30ee4c876d244802a4bf commit 257d4a8601c3848421c3f95179cf1f151e381e87 Author: Monty Taylor Date: Fri Jun 14 19:03:15 2013 -0400 Update to pbr/d2to1. Change-Id: I5f4bb2cb3d068fec24a58b7d9b395b9cc31fa613 commit 684daae39fe391f259a8768a9ae00c11f8c0b93e Author: Monty Taylor Date: Fri Jun 14 18:18:04 2013 -0400 Renamed reddwarfclient repo to troveclient. Change-Id: I5462f951afb85828c9add0ad9201213e959d7406 commit facd76606b4a3001c10ba7639209a0641ae15995 Author: Steve Leon Date: Mon Jun 3 18:04:30 2013 -0700 Renaming security group URL API - Renaming API to match openstack Fix bug # 1187181 Change-Id: I00d63c6fb40391de78d4a517fe9ff11e486f75a8 commit 61008d058d4fba59bdeb0b94c36fad96f797b276 Author: Steve Leon Date: Fri May 10 09:49:40 2013 -0700 Making the 'volume' param optional This is needed for ephemeral volume support. LP bug https://bugs.launchpad.net/reddwarf/+bug/1175719 Change-Id: I389bf6afe0b29713ff7f299c26731244045c165d commit 2305423accfbd07ea7a31143476c22a7c2a8f0d2 Author: Steve Leon Date: Thu May 2 11:32:50 2013 -0700 Making param 'size' optional for non-volume support. Param 'size' is only needed when volume-support is enabled. When volume-support is not enabled, ie using root partition or ephemeral, the size is not needed. LP bug 1175719 Change-Id: Iaf891e70fd24ad59a6a5034fd1e6fd798f0d7282 commit e63e3a69edd102741c30f21440694b078e66abbd Merge: 8547950 0f2391b Author: Jenkins Date: Tue Apr 30 00:36:27 2013 +0000 Merge "Adding GET API to backup" commit 0f2391b901a2f0abc792ccb5fd527c4911a57e13 Author: Steve Leon Date: Mon Apr 29 14:12:57 2013 -0700 Adding GET API to backup Get backup by ID. Change-Id: I0a6422ab12449212d144d9fdec1a4c7b282bed85 BP: blueprints.launchpad.net/reddwarf/+spec/consistent-snapshots">https://blueprints.launchpad.net/reddwarf/+spec/consistent-snapshots commit 85479507fbcb9c4847cf8332d945f675d4f42ef1 Merge: 430661e 0f4c1f7 Author: Jenkins Date: Wed Apr 24 20:54:03 2013 +0000 Merge "Renaming backup_id param" commit 430661e88dd7ffdb3960a5067640b1f3f29c8e40 Merge: ab4525b fb9cb1f Author: Jenkins Date: Mon Apr 22 19:53:08 2013 +0000 Merge "Stop appending default host if none is provided." commit 0f4c1f7ba346f1f71d0890be4be682899771e48c Author: Steve Leon Date: Fri Apr 12 01:03:21 2013 -0700 Renaming backup_id param Change-Id: Ice5cb9a214f2f8d8e51853c4c2d0201cb14ea6ba commit fb9cb1f2f6bd20bfad96dbe529668cd5b279b582 Author: Ed Cranford Date: Thu Apr 11 12:50:42 2013 -0500 Stop appending default host if none is provided. Appending the default hostname broke backward compatibility. This change reverses that change. Now, usernames with @ in them will need to specify their host deliberately, but that is a small edge case compared to every single user call being modified without the client user's knowing. Fixes Bug #1171559 Change-Id: I2f2e9adc252e2b1cd03b1bb0c4a53d48d5c11e53 commit ab4525b1c5cdbc4c6169fd20d54ec39fb22a30fa Merge: c20f018 cf1cc4c Author: Jenkins Date: Thu Apr 11 17:38:55 2013 +0000 Merge "adding the mgmt migrate to specific host api call" commit c20f018fd83c4440e80837212e734ab79432ea9f Merge: 6222dea daba7cb Author: Jenkins Date: Thu Apr 11 15:15:55 2013 +0000 Merge "Adding backup resource to quota command" commit daba7cba303f5ae9e6becbc3950dd2b21f621ad6 Author: Steve Leon Date: Wed Apr 10 18:13:31 2013 -0700 Adding backup resource to quota command Adding handle for error 422 Change-Id: I4c60984ec7196f346da99769f21f7372a35b3f86 commit 6222dea1404038b70ca0c18179b9cac4227bd851 Author: Tim Simpson Date: Wed Apr 10 10:39:09 2013 -0500 Use a plain dict for Quotas.show method. This also fixes the XML version translator to convert the strings to integers. implements blueprint use-plain-dict-for-quotas-show Change-Id: I4764313a863e9c1fd0ee8fa00645b57535d47c76 commit df4175ea5cd109d99c884c0bcc05671a333c58de Merge: 0456b4f 267a361 Author: Jenkins Date: Thu Apr 4 00:49:16 2013 +0000 Merge "Renaming backupRef to backupId" commit 0456b4f636e67a27f5ad0df20d5e0dddcc8c983a Merge: 50cefc7 bd9d311 Author: Jenkins Date: Wed Apr 3 23:53:56 2013 +0000 Merge "Enhancing the quota feature to remove the hard requirements for quota update" commit bd9d311b9164d6da1783295319bfa10378707ca8 Author: Steve Leon Date: Wed Apr 3 16:25:02 2013 -0700 Enhancing the quota feature to remove the hard requirements for quota update Quota update no longer requires to have all the resources to be passed. Just the resources to be updated are sufficient. This effort is needed for quota backup Change-Id: Ied8bf699db83f2322052ac29f2271a80d64f64d5 commit 267a361869a3ebebb276d5d2c7421a8b1cc060e3 Author: Steve Leon Date: Tue Apr 2 16:00:00 2013 -0700 Renaming backupRef to backupId Change-Id: I93f1132822f90e51f761db6346b5a19e2dc1a0a5 commit 50cefc7f90d79780d2bffe457784a1b7590a62dd Author: Steve Leon Date: Fri Mar 29 09:49:36 2013 -0700 Adding client commands for Backup feature - Added create, list and delete backup commands - Updated the create instance command to include optional param 'BackupRef' - Added 'backups' subcommand to 'instance' used to list all backups for that instance. Change-Id: Icc32e7097858708d0ed0f4c309d4c86f5cbd8aa6 BP: https://blueprints.launchpad.net/reddwarf/+spec/consistent-snapshots commit 0921900be47f6f5b401c15b206d7d85d065f92f2 Merge: 3dce0e8 11ba71c Author: Jenkins Date: Tue Mar 26 19:12:24 2013 +0000 Merge "Updated TYPE_MAP for security group rules" commit 3dce0e8871a6f0b48c796e859f14d2ebc3c769d4 Merge: 0585f21 645a9ad Author: Jenkins Date: Tue Mar 26 19:05:01 2013 +0000 Merge "Adds optional host parameter to users." commit 11ba71c48e0a6dfd1dda0b07f7ed5dbabd107138 Author: Nikhil Manchanda Date: Tue Mar 26 01:57:59 2013 -0700 Updated TYPE_MAP for security group rules Updated TYPE_MAP for security group rules so that from_port and to_port are correctly identified as ints for XML. Fixed bug:1160238 Change-Id: I432c7f01fc16c476d6452a4ac4e219dbfa6d0388 commit 645a9ad4178e9c554ef2faceae8fd5c3f104cfef Author: Ed Cranford Date: Mon Mar 11 10:53:36 2013 -0500 Adds optional host parameter to users. All calls that involve calling a user by name now also allow for the host to be specified optionally. Similarly, all calls that respond with a user also include the host, defaulting to '%', MySQL's shorthand for 'anywhere'. Hosts with dots in the name are escaped to avoid a problem with the way our routing parses URLs. Change-Id: Idc5d514a7d862a723469ca8b49f1c51ae07f741b commit cf1cc4c0569a07ce39e0470bf21ba5feab157f18 Author: Craig Vyvial Date: Wed Mar 13 11:57:27 2013 -0500 adding the mgmt migrate to specific host api call implements blueprint migrate-to-host Change-Id: If7c4b02a2616d9dfebdfab8af91dd999fd6f3156 commit 0585f21e46d4cc7ceb1055a2b2426edfe568d0cd Merge: 94d120d cf7774c Author: Jenkins Date: Thu Mar 21 01:29:07 2013 +0000 Merge "Client Side Changes for Security Groups Support." commit 94d120d9fec522b6cb18870ca86bf48d021594fc Author: Craig Vyvial Date: Tue Mar 19 11:25:57 2013 -0500 update dictionary with a comma (minor) BUG# 1157282 Change-Id: I61f770f7decce8c3318585db32cadd6477e6c9cd commit cf7774c28a9e84055e3d588eac58bf19361be248 Author: Nikhil Manchanda Date: Sun Mar 10 19:55:17 2013 -0700 Client Side Changes for Security Groups Support. - Added new secgroups command with the following options - list - get - add_rule - delete_rule - Added corresponding calls into the REST API Implements blueprint: security-groups Change-Id: Ie6d4bc2ce31dc18f172e92efa46ccdeea3e0a22a commit 9d190c26e378fc3b38f0db5697c6d78fcbc57dee Author: daniel-a-nguyen Date: Tue Mar 12 16:15:04 2013 -0700 Refactored client code and test to consume XML Removed unused constants Change-Id: I7a965a498ab4607de145359697b7d5d976aaa4ff Fixes: bug #1154300 commit b2cfa3d46581570815bb5723a9f1ffbfadbe4a43 Author: daniel-a-nguyen Date: Wed Mar 6 15:36:00 2013 -0800 Add absolute limits to api call for rd cli Implements: https://blueprints.launchpad.net/reddwarf/+spec/rate-limits Change-Id: I525bbdb7e58c100a3fbd29493a06e3aee9417cc9 commit 8eee9613d828d93cbc69070dc4017e1ef463dec4 Merge: 072458d 35e01f5 Author: Jenkins Date: Wed Mar 6 22:22:52 2013 +0000 Merge "Fixes data type bug in get-user call" commit 072458d3476f65c0d0e91305a4c1a0c6dbb8da1a Author: daniel-a-nguyen Date: Mon Feb 25 21:18:51 2013 -0800 Create limits api call Added negative test case for err codes Removed unused code and imports Fixed comment Implements: blueprint rate-limits Change-Id: I527903dcd12f4028fa8ec6c17ac07eb4caf89912 commit 35e01f5206bfa284b5e3add76aac90f879f42e5c Author: Ed Cranford Date: Thu Feb 28 17:07:33 2013 -0600 Fixes data type bug in get-user call Also fixes an authentication problem preventing rd-client calls from finding their service endpoint and issuing calls. Partially addresses bug #1132975 Change-Id: Iae2badda67793f30d957e414ee87d32853ef3d8e commit 16f3d12cbb5f3e8f02ac45efcdcf1cef273d99c6 Author: Tim Simpson Date: Thu Feb 21 00:45:17 2013 -0600 Updating Dbaas initializer default arguments. Specifically, the service name and type values are changed to reflect their new values in the CLI tool. Fixed bug #1131058 Change-Id: I86530e655f0bfed09956654324f29587a7df8f2c commit 8260d31799354ea0289b9dc75fb56b59ca832b68 Author: Tim Simpson Date: Tue Feb 19 17:54:10 2013 -0600 Bumping version number. Change-Id: Ieda6382ea0f1cf16c304baccb1b732aef23afcfb commit ee663ae196326bf90b3cdf678b4ec417f102d3db Merge: 0edbc6b bb6a4da Author: Jenkins Date: Tue Feb 19 23:48:17 2013 +0000 Merge "Change defaults for Service name + type" commit 0edbc6bdc195da9db51d26e5f6cd2ab1bf630cba Author: Tim Simpson Date: Fri Feb 15 18:17:57 2013 -0600 Bumping version to 0.1.1. Change-Id: Ib8835417f212ea217a529dd143918a48c7f1a795 commit 4f165388409decdc5ab3e5705965809f8ed916dd Merge: 65197ed 2a0940d Author: Jenkins Date: Sat Feb 16 00:16:53 2013 +0000 Merge "Quota commands for listing and updating quotas" commit 2a0940d3214ba3a6c3c12149908e009b5a179142 Author: Steve Leon Date: Fri Feb 15 15:53:19 2013 -0800 Quota commands for listing and updating quotas These commands are admin-command that is used to obtain quota informations and updating quotas for a particular tenant. Change-Id: I77ec538d12564b7d245fecbbc6f2698ce4b634b4 commit 65197edafb252de09fcd52194f1dabacc23aee29 Merge: 3e5cdd6 0dccd26 Author: Jenkins Date: Fri Feb 15 18:16:26 2013 +0000 Merge "Require flavor on instance-create call in the CLI." commit bb6a4da3169d63ff25c9106d4cd50d40b88e1823 Author: Vipul Sabhaya Date: Mon Feb 11 15:44:15 2013 -0800 Change defaults for Service name + type * Corresponds to change made in reddwarf-integration that changes the service name + type in keystone. fixes bug# 1122515 Change-Id: I28a366662bfe9816a926708fc376afdf29f00de1 commit 3e5cdd673e8ec0d9cd9e9a0ad25c8049a2266089 Merge: ab91599 ac380b9 Author: Jenkins Date: Wed Feb 13 21:09:39 2013 +0000 Merge "Use _pretty_list instead of _pretty_print" commit ab91599d3a99ee2eb531f15d12cc845a6e5bcd74 Author: Tim Simpson Date: Wed Feb 13 14:51:09 2013 -0600 Changed version to 0.1.0. Change-Id: Iecb6fc13d877c1b00fb1621e150141d823cdf1fe Implements: blueprint change-version-scheme commit 7244e5cc8e414dbde157312dc456f296f8fa5f15 Merge: 6a5adaa 5363e94 Author: Jenkins Date: Wed Feb 13 20:43:06 2013 +0000 Merge "Fix source code URL + Author" commit 0dccd261e50512014475a619a4f7c63cfa987ab3 Author: Ed Cranford Date: Tue Feb 12 16:40:46 2013 -0600 Require flavor on instance-create call in the CLI. Instead of defaulting to flavor 1, the create-instance call will now require that a flavorid is included with --flavor= on the command-line. Implements blueprint no-default-flavor Change-Id: I1de05bf4268d617b255e0be2fd2c999d49952c49 commit 6a5adaa2a85e7df4df26832dbd13dd58bfe50a54 Author: Ed Cranford Date: Tue Jan 22 12:58:26 2013 -0600 User modify functions. Grant, revoke, and list user access. Get user. Change user password. Partially implements blueprint modify-users Change-Id: I786d857175309d504cf1ad0a31a248d64399202c commit 5363e94d5dfd75f4ff540a6c25877f6d93acd98a Author: Vipul Sabhaya Date: Mon Feb 11 14:58:09 2013 -0800 Fix source code URL + Author fixes bug# 1083874 Change-Id: I43789923c5454cb195543d5d6b2d9ec1633bcb5a commit ac380b92b0dd90c5e396478c1961aa227ba11058 Author: daniel-a-nguyen Date: Sun Feb 10 16:38:41 2013 -0800 Use _pretty_list instead of _pretty_print Fixes bug # 1097088 Change-Id: I403b0509b608e97868160271f61706f6113ab409 commit 901d8a0423a045e4755231584b23ade83ea4a4ca Merge: 6394f37 f2b6741 Author: Jenkins Date: Sun Feb 10 23:16:23 2013 +0000 Merge "Ignore rdserver.txt." commit f2b67417cfc3eea4c79a8b4a0f7b2554d5dc1b14 Author: Tim Simpson Date: Sat Feb 9 17:30:01 2013 -0600 Ignore rdserver.txt. This file is created by run_local.sh when the server for the pydoc tests. Missing from last commit, sorry. Fixes bug #1119748 Change-Id: If6a6b91042492c477db9f5809ad8e290c7416c2b commit 6394f3747153adf1d53bc443602efa468c42dd30 Merge: 5237381 82df686 Author: Jenkins Date: Sat Feb 9 00:17:17 2013 +0000 Merge "Fixing sphinx docs." commit 82df686dfb594d65db9c76fc7018ed3177af0aa0 Author: Tim Simpson Date: Fri Feb 8 14:50:44 2013 -0600 Fixing sphinx docs. Added the docs testenv back, which I hope won't break stackforge CI. Updated the run_local.sh script since it was also broken. Change-Id: I87245ab123b03aefb283eba184304312bd18c228 Fixes: bug #1119748 commit 52373814125fb0badce12aad5bd84780b188beac Author: Tim Simpson Date: Fri Feb 8 14:26:34 2013 -0600 Bumping version number. Change-Id: I9c7fdcc706f417fbca6ef403d06bbf96724dff26 commit 36c99c3a4d3b029398e341781cd90eb0f1d31e4c Author: Joe Cruz Date: Fri Feb 1 10:34:41 2013 -0600 Adds reset task status mgmt instance action Partially implements: blueprint reset-task-status-mgmt-api Change-Id: I54fa7a91f67910a95f217ea04f240d917e3121ec commit d0a1dcc60c5623f576541d5cceff0fa4e019ce53 Merge: 7df04ca 5925e50 Author: Jenkins Date: Thu Jan 31 19:01:12 2013 +0000 Merge "Correcting the types of the values in the returned data" commit 5925e50c8c73864d5372b45b53d185a997d7dcc3 Author: DJ Johnstone Date: Wed Jan 23 11:52:36 2013 -0600 Correcting the types of the values in the returned data Partially implements: blueprint test-with-xml Change-Id: I6db4ac284b224c994ec6b674cfb8cb053e4b2734 commit 7df04ca15a553a39f405ec2fa04cdaa4a3a567e0 Author: denizdemir Date: Fri Jan 25 09:20:30 2013 -0800 adding unit tests for common.py client.py management.py users.py instances.py accounts.py implements blueprint reddwarfclient-unit-tests Change-Id: Icad276aed20795377ed43b89b963339f314bdd3e commit 28c8f2aa20536db5cd5dc8f8ff22bb6b32f352e5 Author: denizdemir Date: Tue Jan 22 14:55:51 2013 -0800 fixing delete method name for Paginated class bug 1098668 https://bugs.launchpad.net/python-reddwarfclient/+bug/1098668 Change-Id: Ib8fe4a02dcd39b5db02bb83df6cec0a3b76f753a commit 00f4cdb8dd01b7d0ce28bb36f88fc6630f3e0de9 Author: Deniz Demir Date: Thu Jan 17 15:28:19 2013 -0800 adding unit tests for base.py implements blueprint reddwarfclient-unit-tests Change-Id: Ibe97fe2ada960412b3b2024adde5ea885c909047 commit 4fb6634480294d67df9fba180eed350de4420a9b Author: Deniz Demir Date: Tue Dec 11 10:00:17 2012 -0800 Adding unit tests for auth.py implements blueprint https://blueprints.launchpad.net/python-reddwarfclient/+spec/reddwarfclient-unit-tests Also changin the unit test library from unuttest2 to testtools per request of CI Change-Id: I1b50cd4bf762c90b846ecb51db47f2afbfb6f175 commit 8c13466950643b4069f6623ccae991bc350c700d Author: Deniz Demir Date: Tue Dec 18 08:54:38 2012 -0800 Adding pep8 coverage for tests directory, and fixing existing code to comply with pep8 bug 1091757 https://bugs.launchpad.net/python-reddwarfclient/+bug/1091757 Change-Id: I821d8235095fef740a54e06334a0caa2b6ced7c2 commit 6b27c42df17d99b535a19f286e5307b9431a7847 Merge: a81092d 3cf9333 Author: Jenkins Date: Fri Dec 7 18:23:07 2012 +0000 Merge "Started work on adding unit tests - utils.py and xml.py have 100% coverage." commit 3cf9333fc0736edc7a33116faee09a71bc8af23c Author: Josh Dorothy Date: Thu Dec 6 17:29:32 2012 -0800 Started work on adding unit tests - utils.py and xml.py have 100% coverage. Blueprint: https://blueprints.launchpad.net/python-reddwarfclient/+spec/reddwarfclient-unit-tests Change-Id: I3ad18eb473eafedf2c29650bf6d732be56e8fdcc commit a81092d4bf01c7e6b02484fb58f6b2f45a3d97fa Merge: 0fd7921 7079d50 Author: Jenkins Date: Thu Nov 29 22:06:34 2012 +0000 Merge "Complying with http://wiki.openstack.org/ProjectTestingInterface" commit 0fd7921d9c540aecff928b420271d784f3d0b6cf Author: Steve Leon Date: Tue Nov 27 15:52:39 2012 -0800 Ensure create user API uses specified username. Fixed issue where using the api to create an user, it would always create an user with the name of the reddwarf admin username and not the one specified. Fixes bug 1083826 Change-Id: I2448eede88b7da8cabf824a9cf974a4ccf7ab6ec commit 7079d5054a3f0aa92bc09bb9d58437e03d92de7a Author: Josh Dorothy Date: Tue Nov 27 12:56:13 2012 -0800 Complying with http://wiki.openstack.org/ProjectTestingInterface Fixes: bug 1083835 Change-Id: I31f525c62cdb3b4c7eb695b6a431e4df6443f673 commit 5acc93d06c1d7737432f62a4c3dd50b38b26f084 Author: K Jonathan Harker Date: Mon Nov 19 16:07:27 2012 -0800 Correctly delete users. Fixes: bug #1081255 Change-Id: Idc9809d0a2b3c0ba6742aacaba58ddcfd892300e commit c8e6ed20dbb24a3df6c381e61c35576e07bfb838 Author: Vipul Sabhaya Date: Wed Nov 14 14:10:46 2012 -0800 Handle action responses that contain a response body bug 1074092 Change-Id: I31505743f54010cf5e18bffaf839751a4a29d4e6 commit 52389f90f2167701802a7eee16b9b758fa600fe6 Author: Aaron Crickenberger Date: Thu Nov 8 15:35:06 2012 -0800 Add -h/-? and print_usage to run_local.sh Change-Id: I0772db02d10804759d86bacb56a9cccb2a0a922d commit 844492a8eb044a3fda85b607424151f60e11c830 Author: Vipul Sabhaya Date: Tue Oct 30 13:47:05 2012 -0700 Only pep8 violations fix fix pep8 violations fix tox configurtion for pep8 fix pretty print for actions that return a response body Change-Id: I39a529bda8780649a5159761af7103eee8520412 commit 88c74ba80459dadf31fb8e144d94c32a8dede7e9 Author: Monty Taylor Date: Fri Oct 26 13:33:22 2012 -0400 Added gitreview file. commit 7a1077d90e315ebe518acf9002c8fbb0004d32df Merge: 498a03c 01fd119 Author: Tim Simpson Date: Mon Oct 8 08:21:16 2012 -0700 Merge pull request #37 from TimSimpsonR/load-fix Fixed bug so properties won't load from token file on login. commit 498a03cb037d55f961887b2c4d0a0f6b53a4d75b Merge: 3c2a954 79fa1fb Author: Tim Simpson Date: Thu Sep 27 13:07:44 2012 -0700 Merge pull request #38 from sacharya/mgmt-migrate Updaing the client for mgmt api migrate instance call. commit 79fa1fbd148ce614eed70784b7dc6d5e4638617f Author: Sudarshan Acharya Date: Mon Sep 24 12:31:00 2012 -0500 Updaing the client for mgmt api migrate instance call. commit 01fd11998734a84bfbe0dcca2e501d4d16190a91 Author: Tim Simpson Date: Sat Sep 15 22:39:53 2012 -0500 Fixed bug so properties won't load from token file on login. commit 3c2a9544c2981db40198e3dee84fc5cdbb611350 Merge: fa92440 fd35b0f Author: Tim Simpson Date: Tue Sep 4 11:06:11 2012 -0700 Merge pull request #36 from TimSimpsonR/ci_runner Adding a script to run the client in CI easier. commit fd35b0f5f2cc3725ffb3df5219ecfe382c6b9a8b Author: Tim Simpson Date: Tue Sep 4 13:14:55 2012 -0500 Adding a script to run the client in CI easier. commit fa924402f4612d822b7310c5a086500ee34548a9 Author: Tim Simpson Date: Wed Aug 29 09:10:23 2012 -0500 Update docs/source/usage.rst Fixed inconsequential pydoc test. commit 5cb027f1cb2b7a4f5789060020c45f1870149323 Merge: 681ee5e 9cee37a Author: Tim Simpson Date: Tue Aug 28 16:59:16 2012 -0700 Merge pull request #35 from TimSimpsonR/reauth_correct No longer load the token file on auth login. commit 9cee37a6d91c66200025577a31f912b3194bf326 Author: Tim Simpson Date: Tue Aug 28 12:38:37 2012 -0500 No longer load the token file on auth login. * Print a pretty message about saving the token when we get one. commit 681ee5ea9a13f5cbd09b01cabfd929dfc8483de7 Merge: ef67fe3 3f171b8 Author: Nirmal Ranganathan Date: Wed Aug 22 15:01:37 2012 -0700 Merge pull request #32 from ed-/hide-get-client Moves get_client to _get_client and fixes calls to it in mcli. commit 3f171b854d4e60d8cddb3a537515770590ff814a Author: Ed Cranford Date: Thu Aug 16 11:25:42 2012 -0500 Moves get_client to _get_client and fixes calls to it in mcli. commit ef67fe3b4d1e3bbbb9adc3df8f257134bed00a18 Author: Paul Marshall Date: Thu Jun 28 10:59:47 2012 -0500 add host action for updating all instances commit 9249ac8890eb4c1eb17315693ae7249d5f09fb05 Author: Tim Simpson Date: Wed Jun 27 11:06:22 2012 -0500 Adding stop command. commit 49a5489822a1f77ca6651b1aabc6b365d29f1dc1 Merge: 966b64b 9be0075 Author: Tim Simpson Date: Fri Aug 17 10:09:10 2012 -0700 Merge pull request #33 from TimSimpsonR/reauth_correct Fixed re-authentication logic to actually use the fresher X-Auth-Token. commit 966b64bbf8b30a18bbbcb8e83d9546b686ce8e8f Merge: 51a93fa 662214a Author: Tim Simpson Date: Thu Aug 16 13:06:44 2012 -0700 Merge pull request #30 from pdmars/add_hwinfo adding hardware info call to management client commit 51a93faf66d42bf4a4fb4c4860297472017b95bf Merge: 2cfa902 4fd5a89 Author: Tim Simpson Date: Thu Aug 16 13:06:08 2012 -0700 Merge pull request #31 from ed-/fix-reboot Removes the id from reboot since params and require take care of that now. commit 9be007515ad7ffce7a0841a8250233657b538c4d Author: Tim Simpson Date: Thu Aug 16 13:49:05 2012 -0500 Fixed re-authentication logic to actually use the fresher X-Auth-Token. commit 4fd5a896decc07fc06d7ac01d8c77e97878ce3ba Author: Ed Cranford Date: Wed Aug 15 16:42:37 2012 -0500 Removes the id from reboot since params and require take care of that now. commit 662214abf6576d6bd42bce9f3d8b5002f27696e4 Author: Paul Marshall Date: Wed Aug 15 10:58:59 2012 -0500 adding hardware info call to management client commit 2cfa902d886f02d7900c82505386bb2b7627522c Merge: 3e2106b 62e603a Author: Tim Simpson Date: Wed Aug 15 08:19:52 2012 -0700 Merge pull request #29 from TimSimpsonR/minor_fixes Minor fixes and tweaks. commit 62e603a3b757f7c405ce065401ca8a4920670601 Author: Tim Simpson Date: Tue Aug 14 15:29:38 2012 -0500 Minor fixes and tweaks. * insecure no longer needs a string value added to it. * Print "ok" in situations where the client would otherwise give no output. * Fixed a bug in non-verbose output. commit 3e2106b858dccb509a99e828fee903a9f2368a90 Author: Tim Simpson Date: Tue Aug 14 13:09:38 2012 -0500 Update setup.py Adding lxml to the requirements in setup.py. commit 4187d16cd9333f227490bc3cf6ededd25f5a9ce1 Merge: d256568 88f9530 Author: Tim Simpson Date: Tue Aug 14 10:47:07 2012 -0700 Merge pull request #27 from TimSimpsonR/better_cli-2 Added a ton of CLI options, plus fixed a CI bug. commit 88f9530151b5252487b260cec8e4cdb61f07acec Author: Tim Simpson Date: Thu Aug 9 11:04:28 2012 -0500 Added a ton of CLI options, plus fixed a CI bug. * Renamed the auth_type "basic" to the more apt "auth1.1". * Made it possible to pass an "token" and "service_url" argument alone to the client. It wouldn't work with just this before. * The client now saves all arguments you give it to the pickled file, including the auth strategy, and preserves the token and service_url (which it didn't before) which makes exotic auth types such as "fake" easier to work with. * Not raising an error for a lack of an auth_url until auth occurs (which is usually right after creation of the client anyway for most auth types). * Moved oparser code into CliOption class. This is where the options live plus is the name of that pickled file that gets stored on login. * Added a "debug" option which avoids swallowing stack traces if something goes wrong with the CLI. Should make client work much easier. * Added a "verbose" option which changes the output to instead show the simulated CURL statement plus the request and response headers and bodies, which is useful because I... * Added an "xml" option which does all the communication in XML. * Fixed a bug which was affecting the CI tests where the client would fail if the response body could not be parsed. * Added all of Ed's work to update the mgmt CLI module with his newer named parameters. commit d2565688f6887400ba28597d9d5852b2dc02a5ef Merge: 48e58d2 611fd13 Author: Tim Simpson Date: Tue Aug 14 10:27:41 2012 -0700 Merge pull request #28 from ed-/fix-flavor-id Fixes reference to flavor_id that should be flavor instead commit 48e58d265fa10ca2388593c4732d1f3d1074c1c7 Merge: e0b9db2 26b058a Author: Nirmal Ranganathan Date: Tue Aug 14 08:52:07 2012 -0700 Merge pull request #25 from vipulsabhaya/reset-password Support for root user reset-password action commit 611fd13894f4208bdc4be8838a1e2b32396c24c5 Author: Ed Cranford Date: Tue Aug 14 09:58:06 2012 -0500 Fixes reference to flavor_id that should be flavor instead commit 26b058acc9f70949be86db0f654350d6c0ce713a Merge: 248f879 65d7071 Author: Vipul Sabhaya Date: Sun Aug 12 21:18:21 2012 -0700 Merge branch 'master' into reset-password Conflicts: reddwarfclient/cli.py reddwarfclient/instances.py commit e0b9db2aee786e443603ad7569c201147b2e9348 Merge: 65d7071 e9318ec Author: Nirmal Ranganathan Date: Fri Aug 10 14:43:12 2012 -0700 Merge pull request #26 from ed-/fix-volume-size Fixed volume_size for size commit e9318ec03cdeda7a5bd0a8d7a08f8adf3f2d5909 Author: Ed Cranford Date: Thu Aug 9 14:15:08 2012 -0500 Fixed volume_size for size commit 65d707135474baa9c26ebed493a01019fa54c2f8 Merge: 454ecf2 fc5d8bd Author: Tim Simpson Date: Thu Aug 9 08:20:43 2012 -0700 Merge pull request #23 from TimSimpsonR/xml Made the client send and receive XML. commit 454ecf2ba4355047d6e752b0753d768bac6a19c8 Merge: 65a0ba5 0fad705 Author: Tim Simpson Date: Thu Aug 9 08:20:23 2012 -0700 Merge pull request #24 from ed-/double-dash Built common CommandsBase so we can share double-dashed options from the... commit 0fad705f91396a7b3b3cd6a8153c3db0cd9a7961 Author: Ed Cranford Date: Thu Jul 26 14:35:40 2012 -0500 Built common CommandsBase so we can share double-dashed options from the parser between command classes. commit 248f87906c27276b3eedd42b64809bbae2daa8e9 Author: Vipul Sabhaya Date: Fri Jul 27 11:00:00 2012 -0700 Support for root user reset-password action commit 65a0ba568c0d59dda8013bce7eb5e619c763fda0 Merge: f82a580 6a02267 Author: Tim Simpson Date: Thu Jul 26 08:57:25 2012 -0700 Merge pull request #22 from TimSimpsonR/doc_it_up Adding tox to Python-RDC to make documenting the client very easy. commit fc5d8bd49056226e763c9fa4e11489ffa968a2d1 Author: Tim Simpson Date: Mon Jul 23 17:28:04 2012 -0500 Made the client send and receive XML. * Added a FakeAuth class, since it was so useful for hitting fake mode. * Added a morph_request and morph_response function to the ReddwarfHTTPClient class, to make it possible to use different content types (XML). * Added an XML module with a version of the client class that reads XML. * Added a "mgmt" attribute to the Dbaas class, so that management classes can be hit in a way that closer approximates their URLs. * Added a "resize_flavor" method, which is a clone of the "resize_instance" method, since that name makes a lot more sense. commit 6a02267f9e22464bb342313e1babfb3620bc0472 Author: Tim Simpson Date: Mon Jul 16 15:16:27 2012 -0500 Adding tox to Python-RDC to make documenting the client very easy. * Added some Sphinx docs. commit f82a5800532a84a60c6fc40dd4f1e78042877331 Merge: 89b0080 3d7037e Author: Tim Simpson Date: Mon Jul 16 13:05:21 2012 -0700 Merge pull request #21 from TimSimpsonR/fake_mode Changed client to allow custom Authenticator classes. commit 89b00801541335972d1fd251684bd2b3cc68ac07 Merge: 0450ba4 d202079 Author: Nirmal Ranganathan Date: Thu Jul 12 12:21:46 2012 -0700 Merge pull request #19 from TimSimpsonR/pretty_print Added a "pretty_print" option which makes captured output look even better. commit 0450ba4e5dcd244cc36e1f810641d45e2760c543 Merge: 4b73dbc 20f1611 Author: Michael Basnight Date: Thu Jul 12 12:02:21 2012 -0700 Merge pull request #20 from pdmars/mgmt_list_accounts list all accounts with non-deleted instances commit 3d7037e231bbf70999370f42214ba2183d956be0 Author: Tim Simpson Date: Thu Jul 12 13:47:59 2012 -0500 Changed client to allow custom Authenticator classes. * This allows a user to specify a custom strategy. The motivation is to locally run fake mode without having to fake keystone. * Updated the setup.py with the project's requirements (swiped from Nova Client's). commit 20f1611fa7b57cdc503aa96f587ee98b5db58ec6 Author: Paul Marshall Date: Wed Jul 11 10:24:57 2012 -0500 list all accounts with non-deleted instances commit d202079a8b07b7d6d6ea66792808f4efbed1da6e Author: Tim Simpson Date: Tue Jul 10 11:08:17 2012 -0500 Added a "pretty_print" option which makes captured output look even better. * Shows a possibly usable curl statement which includes the request body. * Also prints the request and response body as well formated, easy to read JSON. * Specify RDC_PP while running tests to see the http communication in this format in any logged test failures. commit 4b73dbccf4bcf12c57832673544aeb6a548ade2b Merge: d744ccd 5fa0c6f Author: Nirmal Ranganathan Date: Mon Jul 9 11:42:03 2012 -0700 Merge pull request #18 from pdmars/mgmt_reboot_instance adding mysql stop and instance reboot commands for management client commit 5fa0c6f799b77d8e4d6b85c72cd32eaa73867c5b Author: Paul Marshall Date: Thu Jul 5 14:52:44 2012 -0500 adding mysql stop and instance reboot commands for management client commit d744ccd51d4127d0be7e950eca1e0cd48d4787dc Merge: 5241529 199ded7 Author: Nirmal Ranganathan Date: Fri Jul 6 13:50:18 2012 -0700 Merge pull request #17 from rnirmal/splitup Moving away from novaclient and adding all the missing pieces into reddw... commit 199ded7eb54a539b3036d8a5a7056c55b24ff9f0 Author: Nirmal Ranganathan Date: Tue Jul 3 10:21:43 2012 -0500 Moving away from novaclient and adding all the missing pieces into reddwarfclient. - Added parameters for the authentication instead of the arguments. - Cleaned out the HttpClient and Authentication pieces. commit 5241529fa2ab2f59ba0f0be195727619af5f6561 Merge: d79605b b153461 Author: Nirmal Ranganathan Date: Tue Jul 3 09:25:29 2012 -0700 Merge pull request #16 from rnirmal/mgmt Adding back the mgmt client commands commit b15346162255c8f74485051a32df9164dcecf252 Author: Sudarshan Acharya Date: Thu Jun 21 14:06:11 2012 -0500 Mgmt Storage device details commit 773590df6afa4dea5b85f8fa9138f4d7697f617e Author: Sudarshan Acharya Date: Mon Jun 18 12:26:34 2012 -0500 Mgmt Instances. commit 79ecd5c077c858dc6d5eb8e2b6413aaf5e066331 Author: Tim Simpson Date: Fri Jun 15 10:13:31 2012 -0500 Adding hosts to management CLI. commit 5521b2de4fd7e51a07015f8a22a65f374665fd37 Author: Nirmal Ranganathan Date: Thu Jun 7 22:27:24 2012 -0500 Adding back accounts to the mgmt cli commit d79605b37a73ae3b47d71814e0049ed40d8fe1d6 Merge: 44c9523 6fd2d54 Author: Tim Simpson Date: Fri Jun 22 13:14:16 2012 -0700 Merge pull request #15 from TimSimpsonR/import_exceptions2 Import nova exceptions as our own, so they can be used by importing from our client directly. commit 6fd2d54d554596f6a70b37ef76fa0085cb27f91e Author: Tim Simpson Date: Thu Jun 21 15:05:13 2012 -0500 Import nova exceptions as our own, so they can be used by importing from our client directly. commit 44c952347aa15bbc2722a0bd881d5ad3f34d4221 Author: Tim Simpson Date: Mon Jun 18 15:01:24 2012 -0500 Changing the default auth_strategy from "basic" to None. commit 220b8daa3dc577935f84e724735dec1574ffc937 Merge: 2167dab 290d515 Author: Nirmal Ranganathan Date: Mon Jun 18 12:00:16 2012 -0700 Merge pull request #14 from rnirmal/raxauth Adding rax auth commit 290d515c7672e4e3bab49e4b52a5d2ffaaddb85c Author: Nirmal Ranganathan Date: Sun Jun 17 22:53:55 2012 -0500 Adding rax auth commit 2167dab9872ee4f448efc0d4646a61ae7e4f9959 Merge: 9e860e0 9aaf84c Author: Nirmal Ranganathan Date: Thu Jun 14 09:12:16 2012 -0700 Merge pull request #13 from rnirmal/farvel_flavor_details Removing flavor detail/index and just sticking with flavor list which is... commit 9aaf84cc9910389e666a5577f5f8d155d2c1636b Author: Nirmal Ranganathan Date: Wed Jun 13 15:32:11 2012 -0500 Removing flavor detail/index and just sticking with flavor list which is the default commit 9e860e0c3489ab5bacd5ac5d84db651712fe8f0f Merge: 63a8acb b5bc8b7 Author: Nirmal Ranganathan Date: Thu Jun 14 08:49:25 2012 -0700 Merge pull request #12 from rnirmal/au_revoir_details Au revoir instances/detail. Changing the instances list to just use inde... commit b5bc8b7c55c7a108f2f524a93c2b3c0af38984d4 Author: Nirmal Ranganathan Date: Wed Jun 13 11:47:47 2012 -0500 Au revoir instances/detail. Changing the instances list to just use index. commit 63a8acb92070baf77c31de1985fa24ae8fdd0b84 Merge: 5f63500 0b21c5d Author: Tim Simpson Date: Wed Jun 6 06:47:47 2012 -0700 Merge pull request #11 from ed-/users-page-cli-fix Small bugfix, now iterates over users.links like dbs and instances. commit 0b21c5d6b7df63aa138bea9fe9b3591f546fd4bc Author: Ed Cranford Date: Mon Jun 4 10:39:48 2012 -0500 Small bugfix, now iterates over users.links like dbs and instances. commit 5f63500482645115258f0102ffb32d5687273c58 Merge: 83e74a3 389bdf8 Author: Tim Simpson Date: Fri Jun 1 12:40:41 2012 -0700 Merge pull request #10 from ed-/pagination Pagination commit 389bdf81aa6d18c015de5495640b33404d8e8a47 Author: Ed Cranford Date: Tue May 22 16:35:47 2012 -0500 Adds pagination limit and marker to instances, databases, and users indices. commit 83e74a3dcf6b80a2c2aae9dab58e9a514f53f7d9 Merge: bf1ff22 bbda631 Author: Tim Simpson Date: Mon May 21 15:33:57 2012 -0700 Merge pull request #9 from TimSimpsonR/qe_merge Saving the last response. commit bbda631e8abc4a7ec3e7c99480676dbe810842b4 Author: Tim Simpson Date: Fri May 18 14:58:54 2012 -0500 Saving the last response. commit bf1ff22e4b8d3dc280e94be4000fc94cbb1870f3 Merge: 795fe36 68d036d Author: Michael Basnight Date: Thu May 17 10:45:55 2012 -0700 Merge pull request #8 from TimSimpsonR/qe_merge Qe merge commit 68d036debc14d1accf2ce7875b2c1146f0640379 Author: Tim Simpson Date: Thu May 17 12:52:20 2012 -0500 Fixed error where self was passed unnecessarily to super class __init__, messing up other arguments. commit f1bafd3d3c4d8bb96511d52221070c9de8f21a9b Author: Tim Simpson Date: Tue May 15 08:27:26 2012 -0500 Added the ability to handle "basic auth" authentication. commit 795fe363280158a22705619a9dec53d83f5f921e Merge: ca9f35c ecdb185 Author: Michael Basnight Date: Mon May 14 08:56:21 2012 -0700 Merge pull request #7 from ed-/root-to-mgmt root_enabled_history shortened to root_history. commit ecdb185461620aa621f30b4b7564166578e5dcd9 Author: Ed Cranford Date: Thu May 10 11:49:26 2012 -0500 root_enabled_history shortened to root_history. commit ca9f35c8926799d27ba98b2c7d79602b82b6e0fc Merge: 9fa1947 01313e5 Author: Tim Simpson Date: Thu May 3 14:21:52 2012 -0700 Merge pull request #5 from ed-/root Root enabled history commit 01313e56fe446ea3eaa459b0cc9dcf89c043de7d Author: Ed Cranford Date: Mon Apr 23 12:58:21 2012 -0500 PEP8 fixes commit 3e92682e3a8a7a1cdeee838dfa5f82a5539beb88 Author: Ed Cranford Date: Thu Apr 19 17:39:59 2012 -0500 Updated the root history property names. commit 9fa1947adb70390c52fbd40e66d28e9cfe13302a Merge: 2dc5d02 b1eeaa4 Author: Michael Basnight Date: Wed May 2 13:11:47 2012 -0700 Merge pull request #6 from TimSimpsonR/unproc-fix Changing Reddwarf client to use its own request function. commit b1eeaa4b9550388a3d289cb09a695a67845c1fcd Author: Tim Simpson Date: Wed May 2 07:40:13 2012 -0500 Changing Reddwarf client to use its own request function. * Nova client changed in a way that broke our client, so copying the code from there is necessary. * Adding InstanceStatus class with the status strings. * Moved the Dbaas and ReddwarfHTTPClient into their own module. * Changed exceptions module to check Nova's exception map after first looking in Reddwarf's. commit 2dc5d02d7080e7125e4b9d901b513f44b625360c Merge: 4487402 ece8b9e Author: Michael Basnight Date: Tue May 1 08:16:25 2012 -0700 Merge pull request #4 from ed-/haz_flavors Flavors commit ece8b9e2d11a6c29c115c5e18fcb86de557eafaf Author: Ed Cranford Date: Thu Mar 29 16:43:26 2012 -0500 Fixed a 1/N problem with the client commit 3ec9bc32cf861c19dd276f49ffbe301f7655d43c Author: Ed Cranford Date: Wed Mar 21 17:53:24 2012 -0500 Adding Flavors to the client commit 4487402a3099e6d0ef716a79a49fe5879e00f7fe Merge: db440d9 88704a5 Author: Tim Simpson Date: Thu Mar 15 09:13:32 2012 -0700 Merge pull request #3 from TimSimpsonR/keystone-fix Updated python-reddwarfclient to work with the newer version of Keystone... commit 88704a541599da2106e43d37318ed288b5c1fa3b Author: Tim Simpson Date: Thu Mar 15 11:18:35 2012 -0500 Updated python-reddwarfclient to work with the newer version of Keystone. * Changed the authenticate function. commit db440d9667ec9d41ca94c41565e81069ad60595e Merge: 49412e5 3590d98 Author: Tim Simpson Date: Mon Mar 12 09:07:57 2012 -0700 Merge pull request #2 from TimSimpsonR/keystone-fix Updated client to work with newer keystone version. commit 3590d985536200a8f2623a3dbb2bb0ea37ec186d Author: Tim Simpson Date: Fri Mar 9 16:54:05 2012 -0600 Updated client to work with newer keystone version. commit 49412e5546267e2b7a92565125c2ff498350b222 Merge: 424c649 1faea31 Author: Michael Basnight Date: Fri Mar 9 12:58:36 2012 -0800 Merge pull request #1 from TimSimpsonR/setup-py Added the code. Also added a setup.py. commit 1faea31c95da39c7df2769815eb8ff37648d371a Author: Tim Simpson Date: Wed Mar 7 15:14:22 2012 -0600 Added setup.py. * Added the CLI tool from reddwarf to this repo, made it available as a script. * Added a gitignore. commit 17911cb64f1c23479511e4936c72b4e336873271 Author: Tim Simpson Date: Wed Mar 7 08:04:53 2012 -0600 Initial copy of reddwarfclient from reddwarf project. commit 424c649ce3c95fa3d421342e2c4e8c0965334126 Author: Michael Basnight Date: Wed Mar 7 08:48:47 2012 -0600 first commitpython-troveclient-1.0.3/.testr.conf0000664000175300017540000000050112232556651020616 0ustar jenkinsjenkins00000000000000[DEFAULT] test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \ ${PYTHON:-python} -m subunit.run discover -t ./ ./ $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--list python-troveclient-1.0.3/test-requirements.txt0000664000175300017540000000016112232556651022773 0ustar jenkinsjenkins00000000000000hacking>=0.5.6,<0.8 discover sphinx>=1.1.2 testrepository>=0.0.17 testtools>=0.9.32 mock>=1.0 httplib2 lxml>=2.3 python-troveclient-1.0.3/setup.py0000775000175300017540000000141512232556651020252 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-troveclient-1.0.3/docs/0000775000175300017540000000000012232557034017460 5ustar jenkinsjenkins00000000000000python-troveclient-1.0.3/docs/source/0000775000175300017540000000000012232557034020760 5ustar jenkinsjenkins00000000000000python-troveclient-1.0.3/docs/source/conf.py0000664000175300017540000000104512232556651022263 0ustar jenkinsjenkins00000000000000# -*- coding: utf-8 -*- extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.coverage' ] templates_path = ['_templates'] source_suffix = '.rst' master_doc = 'index' project = u'python-troveclient' copyright = u'2012, OpenStack Foundation' exclude_trees = [] pygments_style = 'sphinx' html_theme = 'default' html_static_path = ['_static'] htmlhelp_basename = 'python-troveclientdoc' latex_documents = [ ('index', 'python-troveclient.tex', u'python-troveclient Documentation', u'OpenStack', 'manual'), ] python-troveclient-1.0.3/docs/source/index.rst0000664000175300017540000000147412232556651022633 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. .. include:: ../../README.rst :start-line: 0 :end-line: 22 .. contents:: Contents :local: .. include:: ./usage.rst .. include:: ./pydocs.rst Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` python-troveclient-1.0.3/docs/source/pydocs.rst0000664000175300017540000000035612232556651023023 0ustar jenkinsjenkins00000000000000PyDocs ================= troveclient -------------- .. automodule:: troveclient :members: :undoc-members: :show-inheritance: .. autoclass:: troveclient.client.Dbaas :members: :undoc-members: :show-inheritance: python-troveclient-1.0.3/docs/source/usage.rst0000664000175300017540000001245212232556651022626 0ustar jenkinsjenkins00000000000000Using the Client Programmatically ================================= .. testsetup:: # Creates some vars we don't show in the docs. AUTH_URL="http://localhost:8779/v1.0/auth" from troveclient import Dbaas from troveclient import auth class FakeAuth(auth.Authenticator): def authenticate(self): class FakeCatalog(object): def __init__(self, auth): self.auth = auth def get_public_url(self): return "%s/%s" % ('http://localhost:8779/v1.0', self.auth.tenant) def get_token(self): return self.auth.tenant return FakeCatalog(self) from troveclient import Dbaas OLD_INIT = Dbaas.__init__ def new_init(*args, **kwargs): kwargs['auth_strategy'] = FakeAuth OLD_INIT(*args, **kwargs) # Monkey patch init so it'll work with fake auth. Dbaas.__init__ = new_init client = Dbaas("jsmith", "abcdef", tenant="12345", auth_url=AUTH_URL) client.authenticate() # Delete all instances. instances = [1] while len(instances) > 0: instances = client.instances.list() for instance in instances: try: instance.delete() except: pass flavor_id = "1" for i in range(30): name = "Instance #%d" % i client.instances.create(name, flavor_id, None) Authentication -------------- Authenticating is necessary to use every feature of the client (except to discover available versions). To create the client, create an instance of the Dbaas (Database as a Service) class. The auth url, auth user, key, and tenant ID must be specified in the call to the constructor. .. testcode:: from troveclient import Dbaas global AUTH_URL client = Dbaas("jsmith", "abcdef", tenant="12345", auth_url=AUTH_URL) client.authenticate() The default authentication strategy assumes a Keystone complaint auth system. For Rackspace auth, use the keyword argument "auth_strategy='rax'". Versions -------- You can discover the available versions by querying the versions property as follows: .. testcode:: versions = client.versions.index("http://localhost:8779") The "index" method returns a list of Version objects which have the ID as well as a list of links, each with a URL to use to reach that particular version. .. testcode:: for version in versions: print(version.id) for link in version.links: if link['rel'] == 'self': print(" %s" % link['href']) .. testoutput:: v1.0 http://localhost:8779/v1.0/ Instances --------- The following example creates a 512 MB instance with a 1 GB volume: .. testcode:: client.authenticate() flavor_id = "1" volume = {'size':1} databases = [{"name": "my_db", "character_set": "latin2", # This and the next field are "collate": "latin2_general_ci"}] # optional. users = [{"name": "jsmith", "password": "12345", "databases": [{"name": "my_db"}] }] instance = client.instances.create("My Instance", flavor_id, volume, databases, users) To retrieve the instance, use the "get" method of "instances": .. testcode:: updated_instance = client.instances.get(instance.id) print(updated_instance.name) print(" Status=%s Flavor=%s" % (updated_instance.status, updated_instance.flavor['id'])) .. testoutput:: My Instance Status=BUILD Flavor=1 You can delete an instance by calling "delete" on the instance object itself, or by using the delete method on "instances." .. testcode:: # Wait for the instance to be ready before we delete it. import time from troveclient.exceptions import NotFound while instance.status == "BUILD": instance.get() time.sleep(1) print("Ready in an %s state." % instance.status) instance.delete() # Delete and wait for the instance to go away. while True: try: instance = client.instances.get(instance.id) assert instance.status == "SHUTDOWN" except NotFound: break .. testoutput:: Ready in an ACTIVE state. Listing instances and Pagination -------------------------------- To list all instances, use the list method of "instances": .. testcode:: instances = client.instances.list() Lists paginate after twenty items, meaning you'll only get twenty items back even if there are more. To see the next set of items, send a marker. The marker is a key value (in the case of instances, the ID) which is the non-inclusive starting point for all returned items. The lists returned by the client always include a "next" property. This can be used as the "marker" argument to get the next section of the list back from the server. If no more items are available, then the next property is None. .. testcode:: # There are currently 30 instances. instances = client.instances.list() print(len(instances)) print(instances.next is None) instances2 = client.instances.list(marker=instances.next) print(len(instances2)) print(instances2.next is None) .. testoutput:: 20 False 10 True python-troveclient-1.0.3/README.rst0000664000175300017540000001575112232556651020234 0ustar jenkinsjenkins00000000000000Python bindings to the OpenStack Trove API =========================================== This is a client for the OpenStack Trove API. There's a Python API (the ``troveclient`` module), and a command-line script (``trove``). Each implements 100% of the OpenStack Trove API. See the `OpenStack CLI guide`_ for information on how to use the ``trove`` 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-troveclient .. _Launchpad: https://launchpad.net/python-troveclient .. _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-troveclient 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, ``trove``, 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 ``--version``. Or set them as an environment variables as well:: 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 ``trove help``:: usage: trove [--version] [--debug] [--os-username ] [--os-password ] [--os-tenant-name ] [--os-tenant-id ] [--os-auth-url ] [--os-region-name ] [--service-type ] [--service-name ] [--database-service-name ] [--endpoint-type ] [--os-database-api-version ] [--os-cacert ] [--retries ] ... Command-line interface to the OpenStack Trove API. Positional arguments: backup-create Creates a backup. backup-delete Deletes a backup. backup-list List available backups. backup-list-instance List available backups for an instance. backup-show Show details of a backup. create Creates a new instance. database-create Creates a database on an instance. database-delete Deletes a database. database-list Lists available databases on an instance. delete Deletes an instance. flavor-list Lists available flavors. flavor-show Show details of a flavor. limit-list Lists the limits for a tenant. list List all the instances. resize-flavor Resizes the flavor of an instance. resize-volume Resizes the volume size of an instance. restart Restarts the instance. root-enable Enables root for a instance. root-show Gets root enabled status for a instance. secgroup-add-rule Creates a security group rule. secgroup-delete-rule Deletes a security group rule. secgroup-list Lists all security groups. secgroup-show Shows details about a security group. show Show details of an instance. user-create Creates a user. user-delete Deletes a user from the instance. user-grant-access Grants access to a database(s) for a user. user-list Lists the users for a instance. user-revoke-access Revokes access to a database for a user. user-show Gets a user from the instance. user-show-access Gets a users access from the instance. user-update-attributes Updates a users attributes from the instance. bash-completion Print arguments for bash_completion. help Display help about this program or one of its subcommands. Optional arguments: --version show program's version number and exit --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-tenant-id Defaults to env[OS_TENANT_ID]. --os-auth-url Defaults to env[OS_AUTH_URL]. --os-region-name Defaults to env[OS_REGION_NAME]. --service-type Defaults to database for most actions --service-name Defaults to env[TROVE_SERVICE_NAME] --database-service-name Defaults to env[TROVE_DATABASE_SERVICE_NAME] --endpoint-type Defaults to env[TROVE_ENDPOINT_TYPE] or publicURL. --os-database-api-version Accepts 1,defaults to env[OS_DATABASE_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 troveclient.v1 import client >>> nt = client.Client(USER, PASS, TENANT, AUTH_URL, service_type="database") >>> nt.instances.list() [...] python-troveclient-1.0.3/requirements.txt0000664000175300017540000000014612232556651022021 0ustar jenkinsjenkins00000000000000pbr>=0.5.21,<1.0 argparse PrettyTable>=0.6,<0.8 requests>=1.1 simplejson>=2.0.9 Babel>=1.3 six>=1.4.1 python-troveclient-1.0.3/tox.ini0000664000175300017540000000160512232556651020051 0ustar jenkinsjenkins00000000000000# Python Trove Client [tox] envlist = py26,py27,py33,pypy,pep8 [testenv] setenv = VIRTUAL_ENV={envdir} NOSE_WITH_OPENSTACK=1 NOSE_OPENSTACK_COLOR=1 NOSE_OPENSTACK_RED=0.05 NOSE_OPENSTACK_YELLOW=0.025 NOSE_OPENSTACK_SHOW_ELAPSED=1 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}' [testenv:docs] -deps = coverage httplib2 sphinx -commands = sphinx-build -b html {toxinidir}/docs/source {envtmpdir}/html [flake8] ignore = E125,F401,F403,F811,F821,F841,H102,H103,H201,H202,H23,H301,H306,H401,H402,H403,H404,H702,H703 show-source = True exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build python-troveclient-1.0.3/PKG-INFO0000664000175300017540000002162112232557034017627 0ustar jenkinsjenkins00000000000000Metadata-Version: 1.1 Name: python-troveclient Version: 1.0.3 Summary: Client library for OpenStack DBaaS API Home-page: http://www.openstack.org/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description: Python bindings to the OpenStack Trove API =========================================== This is a client for the OpenStack Trove API. There's a Python API (the ``troveclient`` module), and a command-line script (``trove``). Each implements 100% of the OpenStack Trove API. See the `OpenStack CLI guide`_ for information on how to use the ``trove`` 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-troveclient .. _Launchpad: https://launchpad.net/python-troveclient .. _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-troveclient 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, ``trove``, 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 ``--version``. Or set them as an environment variables as well:: 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 ``trove help``:: usage: trove [--version] [--debug] [--os-username ] [--os-password ] [--os-tenant-name ] [--os-tenant-id ] [--os-auth-url ] [--os-region-name ] [--service-type ] [--service-name ] [--database-service-name ] [--endpoint-type ] [--os-database-api-version ] [--os-cacert ] [--retries ] ... Command-line interface to the OpenStack Trove API. Positional arguments: backup-create Creates a backup. backup-delete Deletes a backup. backup-list List available backups. backup-list-instance List available backups for an instance. backup-show Show details of a backup. create Creates a new instance. database-create Creates a database on an instance. database-delete Deletes a database. database-list Lists available databases on an instance. delete Deletes an instance. flavor-list Lists available flavors. flavor-show Show details of a flavor. limit-list Lists the limits for a tenant. list List all the instances. resize-flavor Resizes the flavor of an instance. resize-volume Resizes the volume size of an instance. restart Restarts the instance. root-enable Enables root for a instance. root-show Gets root enabled status for a instance. secgroup-add-rule Creates a security group rule. secgroup-delete-rule Deletes a security group rule. secgroup-list Lists all security groups. secgroup-show Shows details about a security group. show Show details of an instance. user-create Creates a user. user-delete Deletes a user from the instance. user-grant-access Grants access to a database(s) for a user. user-list Lists the users for a instance. user-revoke-access Revokes access to a database for a user. user-show Gets a user from the instance. user-show-access Gets a users access from the instance. user-update-attributes Updates a users attributes from the instance. bash-completion Print arguments for bash_completion. help Display help about this program or one of its subcommands. Optional arguments: --version show program's version number and exit --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-tenant-id Defaults to env[OS_TENANT_ID]. --os-auth-url Defaults to env[OS_AUTH_URL]. --os-region-name Defaults to env[OS_REGION_NAME]. --service-type Defaults to database for most actions --service-name Defaults to env[TROVE_SERVICE_NAME] --database-service-name Defaults to env[TROVE_DATABASE_SERVICE_NAME] --endpoint-type Defaults to env[TROVE_ENDPOINT_TYPE] or publicURL. --os-database-api-version Accepts 1,defaults to env[OS_DATABASE_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 troveclient.v1 import client >>> nt = client.Client(USER, PASS, TENANT, AUTH_URL, service_type="database") >>> nt.instances.list() [...] Platform: UNKNOWN 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-troveclient-1.0.3/MANIFEST.in0000664000175300017540000000013612232556651020272 0ustar jenkinsjenkins00000000000000include AUTHORS include ChangeLog exclude .gitignore exclude .gitreview global-exclude *.pyc python-troveclient-1.0.3/troveclient/0000775000175300017540000000000012232557034021066 5ustar jenkinsjenkins00000000000000python-troveclient-1.0.3/troveclient/common.py0000664000175300017540000000441712232556651022742 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack Foundation # Copyright 2013 Rackspace Hosting # 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 copy import json import optparse import os import pickle import sys from troveclient import client from troveclient import exceptions from troveclient.openstack.common.py3kcompat import urlutils def check_for_exceptions(resp, body): if resp.status_code in (400, 422, 500): raise exceptions.from_response(resp, body) def limit_url(url, limit=None, marker=None): if not limit and not marker: return url query = [] if marker: query.append("marker=%s" % marker) if limit: query.append("limit=%s" % limit) query = '?' + '&'.join(query) return url + query def quote_user_host(user, host): quoted = '' if host: quoted = urlutils.quote("%s@%s" % (user, host)) else: quoted = urlutils.quote("%s" % user) return quoted.replace('.', '%2e') class Paginated(object): """ Pretends to be a list if you iterate over it, but also keeps a next property you can use to get the next page of data. """ def __init__(self, items=[], next_marker=None, links=[]): self.items = items self.next = next_marker self.links = links def __len__(self): return len(self.items) def __iter__(self): return self.items.__iter__() def __getitem__(self, key): return self.items[key] def __setitem__(self, key, value): self.items[key] = value def __delitem__(self, key): del self.items[key] def __reversed__(self): return reversed(self.items) def __contains__(self, needle): return needle in self.items python-troveclient-1.0.3/troveclient/exceptions.py0000664000175300017540000001155412232556651023633 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack Foundation # Copyright 2013 Rackspace Hosting # Copyright 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. class UnsupportedVersion(Exception): """Indicates that the user is trying to use an unsupported version of the API""" 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 AuthUrlNotGiven(EndpointNotFound): """The auth url was not given.""" pass class ServiceUrlNotGiven(EndpointNotFound): """The service url was not given.""" pass class ResponseFormatError(Exception): """Could not parse the response format.""" 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" class UnprocessableEntity(ClientException): """ HTTP 422 - Unprocessable Entity: The request cannot be processed. """ http_status = 422 message = "Unprocessable Entity" # 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, UnprocessableEntity]) def from_response(response, body): """ Return an instance of an ClientException or subclass based on an request's response. Usage:: resp, body = http.request(...) if resp.status != 200: raise exception_from_response(resp, body) """ cls = _code_map.get(response.status, ClientException) if body: message = "n/a" details = "n/a" if hasattr(body, 'keys'): error = body[body.keys()[0]] message = error.get('message', None) details = error.get('details', None) return cls(code=response.status, message=message, details=details) else: request_id = response.get('x-compute-request-id') return cls(code=response.status, request_id=request_id) python-troveclient-1.0.3/troveclient/client.py0000664000175300017540000003557112232556654022740 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack Foundation # Copyright 2013 Rackspace Hosting # Copyright 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. """ OpenStack Client interface. Handles the REST calls and responses. """ from __future__ import print_function import logging import os 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 troveclient.openstack.common.apiclient import exceptions from troveclient import service_catalog from troveclient import utils from troveclient.openstack.common.apiclient import client class HTTPClient(object): USER_AGENT = 'python-troveclient' 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, database_service_name=None, retries=None, http_log_debug=False, cacert=None, bypass_url=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.database_service_name = database_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 self.bypass_url = bypass_url 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, url) 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, database_service_name=self.database_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, url) 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: if "TROVE_RAX_AUTH" in os.environ: auth_url = self._rax_auth(auth_url) else: 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 trove 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) # Allows for setting an endpoint not defined in the catalog if self.bypass_url is not None and self.bypass_url != '': self.management_url = self.bypass_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, url) 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 _rax_auth(self, url): """Authenticate against the Rackspace auth service.""" body = {"auth": { "RAX-KSKEY:apiKeyCredentials": { "username": self.user, "apiKey": self.password, "tenantName": self.projectid}}} 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_database_api_version_from_endpoint(self): magic_tuple = urlparse.urlsplit(self.management_url) scheme, netloc, path, query, frag = magic_tuple v = path.split("/")[1] valid_versions = ['v1.0'] if v not in valid_versions: msg = "Invalid client version '%s'. must be one of: %s" % ( (v, ', '.join(valid_versions))) raise exceptions.UnsupportedVersion(msg) return v[1:] def get_version_map(): return { '1.0': 'troveclient.v1.client.Client', } def Client(version, *args, **kwargs): version_map = get_version_map() client_class = client.BaseClient.get_class('database', version, version_map) return client_class(*args, **kwargs) python-troveclient-1.0.3/troveclient/__init__.py0000664000175300017540000000200712232556651023202 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack Foundation # Copyright 2013 Rackspace Hosting # 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. __all__ = ['__version__'] import pbr.version version_info = pbr.version.VersionInfo('python-troveclient') # 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-troveclient-1.0.3/troveclient/openstack/0000775000175300017540000000000012232557034023055 5ustar jenkinsjenkins00000000000000python-troveclient-1.0.3/troveclient/openstack/__init__.py0000664000175300017540000000000012232556651025160 0ustar jenkinsjenkins00000000000000python-troveclient-1.0.3/troveclient/openstack/common/0000775000175300017540000000000012232557034024345 5ustar jenkinsjenkins00000000000000python-troveclient-1.0.3/troveclient/openstack/common/apiclient/0000775000175300017540000000000012232557034026315 5ustar jenkinsjenkins00000000000000python-troveclient-1.0.3/troveclient/openstack/common/apiclient/exceptions.py0000664000175300017540000002713012232556651031057 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # 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 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-troveclient-1.0.3/troveclient/openstack/common/apiclient/client.py0000664000175300017540000003074512232556651030162 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # 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 troveclient.openstack.common.apiclient import exceptions from troveclient.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 exeptions 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 = "troveclient.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-troveclient-1.0.3/troveclient/openstack/common/apiclient/__init__.py0000664000175300017540000000125012232556651030430 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # 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. python-troveclient-1.0.3/troveclient/openstack/common/apiclient/fake_client.py0000664000175300017540000001314012232556651031136 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # 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 urlparse import requests from troveclient.openstack.common.apiclient import client 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 = {} 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 = 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)) 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-troveclient-1.0.3/troveclient/openstack/common/apiclient/base.py0000664000175300017540000003707712232556651027623 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # 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 urllib from troveclient.openstack.common.apiclient import exceptions from troveclient.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) class ManagerWithFind(BaseManager): """Manager with additional `find()`/`findall()` methods.""" __metaclass__ = abc.ABCMeta @abc.abstractmethod def list(self): pass def find(self, **kwargs): """Find a single item with attributes matching ``**kwargs``. This isn't very efficient: it loads the entire list then filters on the Python side. """ matches = self.findall(**kwargs) num_matches = len(matches) if num_matches == 0: msg = "No %s matching %s." % (self.resource_class.__name__, kwargs) raise exceptions.NotFound(msg) elif num_matches > 1: raise exceptions.NoUniqueMatch() else: return matches[0] def findall(self, **kwargs): """Find all items with attributes matching ``**kwargs``. This isn't very efficient: it loads the entire list then filters on the Python side. """ found = [] searches = kwargs.items() for obj in self.list(): try: if all(getattr(obj, attr) == value for (attr, value) in searches): found.append(obj) except AttributeError: continue return found class CrudManager(BaseManager): """Base manager class for manipulating entities. Children of this class are expected to define a `collection_key` and `key`. - `collection_key`: Usually a plural noun by convention (e.g. `entities`); used to refer collections in both URL's (e.g. `/v3/entities`) and JSON objects containing a list of member resources (e.g. `{'entities': [{}, {}, {}]}`). - `key`: Usually a singular noun by convention (e.g. `entity`); used to refer to an individual member of the collection. """ collection_key = None key = None def build_url(self, base_url=None, **kwargs): """Builds a resource URL for the given kwargs. Given an example collection where `collection_key = 'entities'` and `key = 'entity'`, the following URL's could be generated. By default, the URL will represent a collection of entities, e.g.:: /entities If kwargs contains an `entity_id`, then the URL will represent a specific member, e.g.:: /entities/{entity_id} :param base_url: if provided, the generated URL will be appended to it """ url = base_url if base_url is not None else '' url += '/%s' % self.collection_key # do we have a specific entity? entity_id = kwargs.get('%s_id' % self.key) if entity_id is not None: url += '/%s' % entity_id return url def _filter_kwargs(self, kwargs): """Drop null values and handle ids.""" for key, ref in kwargs.copy().iteritems(): 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' % urllib.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' % urllib.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 info.iteritems(): 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-troveclient-1.0.3/troveclient/openstack/common/apiclient/auth.py0000664000175300017540000001571612232556651027646 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # 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 logging import os from stevedore import extension from troveclient.openstack.common.apiclient import exceptions logger = logging.getLogger(__name__) _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 = "troveclient.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 _discovered_plugins.iteritems(): 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 requred 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(_discovered_plugins.iterkeys()): 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"]) class BaseAuthPlugin(object): """Base class for authentication plugins. An authentication plugin needs to override at least the authenticate method to be a valid plugin. """ __metaclass__ = abc.ABCMeta 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-troveclient-1.0.3/troveclient/openstack/common/importutils.py0000664000175300017540000000427212232556651027323 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # 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-troveclient-1.0.3/troveclient/openstack/common/__init__.py0000664000175300017540000000000012232556651026450 0ustar jenkinsjenkins00000000000000python-troveclient-1.0.3/troveclient/openstack/common/gettextutils.py0000664000175300017540000003075512232556651027502 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # 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 troveclient.openstack.common.gettextutils import _ """ import copy import gettext import logging import os import re try: import UserString as _userString except ImportError: import collections as _userString from babel import localedata import six _localedir = os.environ.get('troveclient'.upper() + '_LOCALEDIR') _t = gettext.translation('troveclient', 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, 'troveclient') 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. # # Also included below is an example LocaleHandler that translates # Messages to an associated locale, effectively allowing many logs, # each with their own locale. 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) 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(_userString.UserString, object): """Class used to encapsulate translatable messages.""" def __init__(self, msg, domain): # _msg is the gettext msgid and should never change self._msg = msg self._left_extra_msg = '' self._right_extra_msg = '' self._locale = None self.params = None self.domain = domain @property def data(self): # NOTE(mrodden): this should always resolve to a unicode string # that best represents the state of the message currently localedir = os.environ.get(self.domain.upper() + '_LOCALEDIR') if self.locale: lang = gettext.translation(self.domain, localedir=localedir, languages=[self.locale], fallback=True) else: # use system locale for translations lang = gettext.translation(self.domain, localedir=localedir, fallback=True) if six.PY3: ugettext = lang.gettext else: ugettext = lang.ugettext full_msg = (self._left_extra_msg + ugettext(self._msg) + self._right_extra_msg) if self.params is not None: full_msg = full_msg % self.params return six.text_type(full_msg) @property def locale(self): return self._locale @locale.setter def locale(self, value): self._locale = value if not self.params: return # This Message object may have been constructed with one or more # Message objects as substitution parameters, given as a single # Message, or a tuple or Map containing some, so when setting the # locale for this Message we need to set it for those Messages too. if isinstance(self.params, Message): self.params.locale = value return if isinstance(self.params, tuple): for param in self.params: if isinstance(param, Message): param.locale = value return for param in self.params.values(): if isinstance(param, Message): param.locale = value def _save_dictionary_parameter(self, dict_param): full_msg = self.data # look for %(blah) fields in string; # ignore %% and deal with the # case where % is first character on the line keys = re.findall('(?:[^%]|^)?%\((\w*)\)[a-z]', full_msg) # if we don't find any %(blah) blocks but have a %s if not keys and re.findall('(?:[^%]|^)%[a-z]', full_msg): # apparently the full dictionary is the parameter params = copy.deepcopy(dict_param) else: params = {} for key in keys: try: params[key] = copy.deepcopy(dict_param[key]) except TypeError: # cast uncopyable thing to unicode string params[key] = six.text_type(dict_param[key]) return params def _save_parameters(self, other): # we check for None later to see if # we actually have parameters to inject, # so encapsulate if our parameter is actually None if other is None: self.params = (other, ) elif isinstance(other, dict): self.params = self._save_dictionary_parameter(other) else: # fallback to casting to unicode, # this will handle the problematic python code-like # objects that cannot be deep-copied try: self.params = copy.deepcopy(other) except TypeError: self.params = six.text_type(other) return self # overrides to be more string-like def __unicode__(self): return self.data def __str__(self): if six.PY3: return self.__unicode__() return self.data.encode('utf-8') def __getstate__(self): to_copy = ['_msg', '_right_extra_msg', '_left_extra_msg', 'domain', 'params', '_locale'] new_dict = self.__dict__.fromkeys(to_copy) for attr in to_copy: new_dict[attr] = copy.deepcopy(self.__dict__[attr]) return new_dict def __setstate__(self, state): for (k, v) in state.items(): setattr(self, k, v) # operator overloads def __add__(self, other): copied = copy.deepcopy(self) copied._right_extra_msg += other.__str__() return copied def __radd__(self, other): copied = copy.deepcopy(self) copied._left_extra_msg += other.__str__() return copied def __mod__(self, other): # do a format string to catch and raise # any possible KeyErrors from missing parameters self.data % other copied = copy.deepcopy(self) return copied._save_parameters(other) def __mul__(self, other): return self.data * other def __rmul__(self, other): return other * self.data def __getitem__(self, key): return self.data[key] def __getslice__(self, start, end): return self.data.__getslice__(start, end) def __getattribute__(self, name): # NOTE(mrodden): handle lossy operations that we can't deal with yet # These override the UserString implementation, since UserString # uses our __class__ attribute to try and build a new message # after running the inner data string through the operation. # At that point, we have lost the gettext message id and can just # safely resolve to a string instead. ops = ['capitalize', 'center', 'decode', 'encode', 'expandtabs', 'ljust', 'lstrip', 'replace', 'rjust', 'rstrip', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill'] if name in ops: return getattr(self.data, name) else: return _userString.UserString.__getattribute__(self, name) 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 all projects udpate 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) _AVAILABLE_LANGUAGES[domain] = language_list return copy.copy(language_list) def get_localized_message(message, user_locale): """Gets a localized version of the given message in the given locale.""" if isinstance(message, Message): if user_locale: message.locale = user_locale return six.text_type(message) else: return message class LocaleHandler(logging.Handler): """Handler that can have a locale associated to translate Messages. A quick example of how to utilize the Message class above. LocaleHandler takes a locale and a target logging.Handler object to forward LogRecord objects to after translating the internal Message. """ def __init__(self, locale, target): """Initialize a LocaleHandler :param locale: locale to use for translating messages :param target: logging.Handler object to forward LogRecord objects to after translation """ logging.Handler.__init__(self) self.locale = locale self.target = target def emit(self, record): if isinstance(record.msg, Message): # set the locale and resolve to a string record.msg.locale = self.locale self.target.emit(record) python-troveclient-1.0.3/troveclient/openstack/common/strutils.py0000664000175300017540000001627612232556651026630 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # 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 troveclient.openstack.common.gettextutils import _ # noqa # 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): """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 is considered False. 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 False 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 isntance 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 isntance 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): return text.encode(encoding, errors) elif text and encoding != incoming: # Decode text before encoding it with `encoding` text = safe_decode(text, incoming, errors) 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-troveclient-1.0.3/troveclient/openstack/common/py3kcompat/0000775000175300017540000000000012232557034026437 5ustar jenkinsjenkins00000000000000python-troveclient-1.0.3/troveclient/openstack/common/py3kcompat/urlutils.py0000664000175300017540000000261012232556651030677 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # # 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.parse urlencode = urllib.parse.urlencode urljoin = urllib.parse.urljoin quote = urllib.parse.quote parse_qsl = urllib.parse.parse_qsl unquote = urllib.parse.unquote urlparse = urllib.parse.urlparse urlsplit = urllib.parse.urlsplit urlunsplit = urllib.parse.urlunsplit else: # python2 import urllib import urlparse urlencode = urllib.urlencode quote = urllib.quote unquote = urllib.unquote parse = urlparse parse_qsl = parse.parse_qsl urljoin = parse.urljoin urlparse = parse.urlparse urlsplit = parse.urlsplit urlunsplit = parse.urlunsplit python-troveclient-1.0.3/troveclient/openstack/common/py3kcompat/__init__.py0000664000175300017540000000124512232556651030556 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # # 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. # python-troveclient-1.0.3/troveclient/service_catalog.py0000664000175300017540000000666712232556651024615 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack LLC. # Copyright 2011, Piston Cloud Computing, Inc. # Copyright 2013 Rackspace Hosting # 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 troveclient.openstack.common.apiclient import 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, database_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 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 'database', go ahead and # accept that. skip_service_type_check = False if service_type == 'databasev2' and service['type'] == 'database': 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 (database_service_name and service_type in ('database', 'databasev2') and service.get('name') != database_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 exceptions.EndpointNotFound() elif len(matching_endpoints) > 1: raise exceptions.AmbiguousEndpoints( endpoints=matching_endpoints) else: return matching_endpoints[0][endpoint_type] python-troveclient-1.0.3/troveclient/compat/0000775000175300017540000000000012232557034022351 5ustar jenkinsjenkins00000000000000python-troveclient-1.0.3/troveclient/compat/common.py0000664000175300017540000003371012232556651024223 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. import copy import json import optparse import os import pickle import six import sys from troveclient.compat import client from troveclient.compat.xml import TroveXmlClient from troveclient.compat import exceptions from troveclient.openstack.common.py3kcompat import urlutils def methods_of(obj): """Get all callable methods of an object that don't start with underscore returns a list of tuples of the form (method_name, method)""" result = {} for i in dir(obj): if callable(getattr(obj, i)) and not i.startswith('_'): result[i] = getattr(obj, i) return result def check_for_exceptions(resp, body): if resp.status in (400, 422, 500): raise exceptions.from_response(resp, body) def print_actions(cmd, actions): """Print help for the command with list of options and description""" print(("Available actions for '%s' cmd:") % cmd) for k, v in six.iteritems(actions): print("\t%-20s%s" % (k, v.__doc__)) sys.exit(2) def print_commands(commands): """Print the list of available commands and description""" print("Available commands") for k, v in six.iteritems(commands): print("\t%-20s%s" % (k, v.__doc__)) sys.exit(2) def limit_url(url, limit=None, marker=None): if not limit and not marker: return url query = [] if marker: query.append("marker=%s" % marker) if limit: query.append("limit=%s" % limit) query = '?' + '&'.join(query) return url + query def quote_user_host(user, host): quoted = '' if host: quoted = urlutils.quote("%s@%s" % (user, host)) else: quoted = urlutils.quote("%s" % user) return quoted.replace('.', '%2e') class CliOptions(object): """A token object containing the user, apikey and token which is pickleable.""" APITOKEN = os.path.expanduser("~/.apitoken") DEFAULT_VALUES = { 'username': None, 'apikey': None, 'tenant_id': None, 'auth_url': None, 'auth_type': 'keystone', 'service_type': 'database', 'service_name': '', 'region': 'RegionOne', 'service_url': None, 'insecure': False, 'verbose': False, 'debug': False, 'token': None, 'xml': None, } def __init__(self, **kwargs): for key, value in self.DEFAULT_VALUES.items(): setattr(self, key, value) @classmethod def default(cls): kwargs = copy.deepcopy(cls.DEFAULT_VALUES) return cls(**kwargs) @classmethod def load_from_file(cls): try: with open(cls.APITOKEN, 'rb') as token: return pickle.load(token) except IOError: pass # File probably not found. except: print("ERROR: Token file found at %s was corrupt." % cls.APITOKEN) return cls.default() @classmethod def save_from_instance_fields(cls, instance): apitoken = cls.default() for key, default_value in cls.DEFAULT_VALUES.items(): final_value = getattr(instance, key, default_value) setattr(apitoken, key, final_value) with open(cls.APITOKEN, 'wb') as token: pickle.dump(apitoken, token, protocol=2) @classmethod def create_optparser(cls, load_file): oparser = optparse.OptionParser( usage="%prog [options] ", version='1.0', conflict_handler='resolve') if load_file: file = cls.load_from_file() else: file = cls.default() def add_option(*args, **kwargs): if len(args) == 1: name = args[0] else: name = args[1] kwargs['default'] = getattr(file, name, cls.DEFAULT_VALUES[name]) oparser.add_option("--%s" % name, **kwargs) add_option("verbose", action="store_true", help="Show equivalent curl statement along " "with actual HTTP communication.") add_option("debug", action="store_true", help="Show the stack trace on errors.") add_option("auth_url", help="Auth API endpoint URL with port and " "version. Default: http://localhost:5000/v2.0") add_option("username", help="Login username") add_option("apikey", help="Api key") add_option("tenant_id", help="Tenant Id associated with the account") add_option("auth_type", help="Auth type to support different auth environments, \ Supported values are 'keystone', 'rax'.") add_option("service_type", help="Service type is a name associated for the catalog") add_option("service_name", help="Service name as provided in the service catalog") add_option("service_url", help="Service endpoint to use " "if the catalog doesn't have one.") add_option("region", help="Region the service is located in") add_option("insecure", action="store_true", help="Run in insecure mode for https endpoints.") add_option("token", help="Token from a prior login.") add_option("xml", action="store_true", help="Changes format to XML.") oparser.add_option("--secure", action="store_false", dest="insecure", help="Run in insecure mode for https endpoints.") oparser.add_option("--json", action="store_false", dest="xml", help="Changes format to JSON.") oparser.add_option("--terse", action="store_false", dest="verbose", help="Toggles verbose mode off.") oparser.add_option("--hide-debug", action="store_false", dest="debug", help="Toggles debug mode off.") return oparser class ArgumentRequired(Exception): def __init__(self, param): self.param = param def __str__(self): return 'Argument "--%s" required.' % self.param class ArgumentsRequired(ArgumentRequired): def __init__(self, *params): self.params = params def __str__(self): returnstring = 'Specify at least one of these arguments: ' for param in self.params: returnstring = returnstring + '"--%s" ' % param return returnstring class CommandsBase(object): params = [] def __init__(self, parser): self._parse_options(parser) def _get_client(self): """Creates the all important client object.""" try: if self.xml: client_cls = TroveXmlClient else: client_cls = client.TroveHTTPClient if self.verbose: client.log_to_streamhandler(sys.stdout) client.RDC_PP = True return client.Dbaas(self.username, self.apikey, self.tenant_id, auth_url=self.auth_url, auth_strategy=self.auth_type, service_type=self.service_type, service_name=self.service_name, region_name=self.region, service_url=self.service_url, insecure=self.insecure, client_cls=client_cls) except: if self.debug: raise print(sys.exc_info()[1]) def _safe_exec(self, func, *args, **kwargs): if not self.debug: try: return func(*args, **kwargs) except: print(sys.exc_info()[1]) return None else: return func(*args, **kwargs) @classmethod def _prepare_parser(cls, parser): for param in cls.params: parser.add_option("--%s" % param) def _parse_options(self, parser): opts, args = parser.parse_args() for param in opts.__dict__: value = getattr(opts, param) setattr(self, param, value) def _require(self, *params): for param in params: if not hasattr(self, param): raise ArgumentRequired(param) if not getattr(self, param): raise ArgumentRequired(param) def _require_at_least_one_of(self, *params): # One or more of params is required to be present. argument_present = False for param in params: if hasattr(self, param): if getattr(self, param): argument_present = True if argument_present is False: raise ArgumentsRequired(*params) def _make_list(self, *params): # Convert the listed params to lists. for param in params: raw = getattr(self, param) if isinstance(raw, list): return raw = [item.strip() for item in raw.split(',')] setattr(self, param, raw) def _pretty_print(self, func, *args, **kwargs): if self.verbose: self._safe_exec(func, *args, **kwargs) return # Skip this, since the verbose stuff will show up anyway. def wrapped_func(): result = func(*args, **kwargs) if result: print(json.dumps(result._info, sort_keys=True, indent=4)) else: print("OK") self._safe_exec(wrapped_func) def _dumps(self, item): return json.dumps(item, sort_keys=True, indent=4) def _pretty_list(self, func, *args, **kwargs): result = self._safe_exec(func, *args, **kwargs) if self.verbose: return if result and len(result) > 0: for item in result: print(self._dumps(item._info)) else: print("OK") def _pretty_paged(self, func, *args, **kwargs): try: limit = self.limit if limit: limit = int(limit, 10) result = func(*args, limit=limit, marker=self.marker, **kwargs) if self.verbose: return # Verbose already shows the output, so skip this. if result and len(result) > 0: for item in result: print(self._dumps(item._info)) if result.links: print("Links:") for link in result.links: print(self._dumps((link))) else: print("OK") except: if self.debug: raise print(sys.exc_info()[1]) class Auth(CommandsBase): """Authenticate with your username and api key""" params = [ 'apikey', 'auth_strategy', 'auth_type', 'auth_url', 'options', 'region', 'service_name', 'service_type', 'service_url', 'tenant_id', 'username', ] def __init__(self, parser): super(Auth, self).__init__(parser) self.dbaas = None def login(self): """Login to retrieve an auth token to use for other api calls""" self._require('username', 'apikey', 'tenant_id', 'auth_url') try: self.dbaas = self._get_client() self.dbaas.authenticate() self.token = self.dbaas.client.auth_token self.service_url = self.dbaas.client.service_url CliOptions.save_from_instance_fields(self) print("Token aquired! Saving to %s..." % CliOptions.APITOKEN) print(" service_url = %s" % self.service_url) print(" token = %s" % self.token) except: if self.debug: raise print(sys.exc_info()[1]) class AuthedCommandsBase(CommandsBase): """Commands that work only with an authicated client.""" def __init__(self, parser): """Makes sure a token is available somehow and logs in.""" super(AuthedCommandsBase, self).__init__(parser) try: self._require('token') except ArgumentRequired: if self.debug: raise print('No token argument supplied. Use the "auth login" command ' 'to log in and get a token.\n') sys.exit(1) try: self._require('service_url') except ArgumentRequired: if self.debug: raise print('No service_url given.\n') sys.exit(1) self.dbaas = self._get_client() # Actually set the token to avoid a re-auth. self.dbaas.client.auth_token = self.token self.dbaas.client.authenticate_with_token(self.token, self.service_url) class Paginated(object): """ Pretends to be a list if you iterate over it, but also keeps a next property you can use to get the next page of data. """ def __init__(self, items=[], next_marker=None, links=[]): self.items = items self.next = next_marker self.links = links def __len__(self): return len(self.items) def __iter__(self): return self.items.__iter__() def __getitem__(self, key): return self.items[key] def __setitem__(self, key, value): self.items[key] = value def __delitem__(self, key): del self.items[key] def __reversed__(self): return reversed(self.items) def __contains__(self, needle): return needle in self.items python-troveclient-1.0.3/troveclient/compat/xml.py0000664000175300017540000002106612232556651023534 0ustar jenkinsjenkins00000000000000from lxml import etree from numbers import Number from troveclient.compat import exceptions from troveclient.compat.client import TroveHTTPClient XML_NS = {None: "http://docs.openstack.org/database/api/v1.0"} # If XML element is listed here then this searches through the ancestors. LISTIFY = { "accounts": [[]], "databases": [[]], "flavors": [[]], "instances": [[]], "links": [[]], "hosts": [[]], "devices": [[]], "users": [[]], "versions": [[]], "attachments": [[]], "limits": [[]], "security_groups": [[]], "backups": [[]] } class IntDict(object): pass TYPE_MAP = { "instance": { "volume": { "used": float, "size": int, }, "deleted": bool, "server": { "local_id": int, "deleted": bool, }, }, "instances": { "deleted": bool, }, "deleted": bool, "flavor": { "ram": int, }, "diagnostics": { "vmHwm": int, "vmPeak": int, "vmSize": int, "threads": int, "vmRss": int, "fdSize": int, }, "security_group_rule": { "from_port": int, "to_port": int, }, "quotas": IntDict, } TYPE_MAP["flavors"] = TYPE_MAP["flavor"] REQUEST_AS_LIST = set(['databases', 'users']) def element_ancestors_match_list(element, list): """ For element root at matches against list ["blah", "foo"]. """ itr_elem = element.getparent() for name in list: if itr_elem is None: break if name != normalize_tag(itr_elem): return False itr_elem = itr_elem.getparent() return True def element_must_be_list(parent_element, name): """Determines if an element to be created should be a dict or list.""" if name in LISTIFY: list_of_lists = LISTIFY[name] for tag_list in list_of_lists: if element_ancestors_match_list(parent_element, tag_list): return True return False def element_to_json(name, element): if element_must_be_list(element, name): return element_to_list(element) else: return element_to_dict(element) def root_element_to_json(name, element): """Returns a tuple of the root JSON value, plus the links if found.""" if name == "rootEnabled": # Why oh why were we inconsistent here? :'( if element.text.strip() == "False": return False, None elif element.text.strip() == "True": return True, None if element_must_be_list(element, name): return element_to_list(element, True) else: return element_to_dict(element), None def element_to_list(element, check_for_links=False): """ For element "foo" in Returns [{}, {}] """ links = None result = [] for child_element in element: # The "links" element gets jammed into the root element. if check_for_links and normalize_tag(child_element) == "links": links = element_to_list(child_element) else: result.append(element_to_dict(child_element)) if check_for_links: return result, links else: return result def element_to_dict(element): result = {} for name, value in element.items(): result[name] = value for child_element in element: name = normalize_tag(child_element) result[name] = element_to_json(name, child_element) if len(result) == 0 and element.text: string_value = element.text.strip() if len(string_value): if string_value == 'None': return None return string_value return result def standardize_json_lists(json_dict): """ In XML, we might see something like {'instances':{'instances':[...]}}, which we must change to just {'instances':[...]} to be compatable with the true JSON format. If any items are dictionaries with only one item which is a list, simply remove the dictionary and insert its list directly. """ found_items = [] for key, value in json_dict.items(): value = json_dict[key] if isinstance(value, dict): if len(value) == 1 and isinstance(value.values()[0], list): found_items.append(key) else: standardize_json_lists(value) for key in found_items: json_dict[key] = json_dict[key].values()[0] def normalize_tag(elem): """Given an element, returns the tag minus the XMLNS junk. IOW, .tag may sometimes return the XML namespace at the start of the string. This gets rids of that. """ try: prefix = "{" + elem.nsmap[None] + "}" if elem.tag.startswith(prefix): return elem.tag[len(prefix):] except KeyError: pass return elem.tag def create_root_xml_element(name, value): """Create the first element using a name and a dictionary.""" element = etree.Element(name, nsmap=XML_NS) if name in REQUEST_AS_LIST: add_subelements_from_list(element, name, value) else: populate_element_from_dict(element, value) return element def create_subelement(parent_element, name, value): """Attaches a new element onto the parent element.""" if isinstance(value, dict): create_subelement_from_dict(parent_element, name, value) elif isinstance(value, list): create_subelement_from_list(parent_element, name, value) else: raise TypeError("Can't handle type %s." % type(value)) def create_subelement_from_dict(parent_element, name, dict): element = etree.SubElement(parent_element, name) populate_element_from_dict(element, dict) def create_subelement_from_list(parent_element, name, list): element = etree.SubElement(parent_element, name) add_subelements_from_list(element, name, list) def add_subelements_from_list(element, name, list): if name.endswith("s"): item_name = name[:len(name) - 1] else: item_name = name for item in list: create_subelement(element, item_name, item) def populate_element_from_dict(element, dict): for key, value in dict.items(): if isinstance(value, basestring): element.set(key, value) elif isinstance(value, Number): element.set(key, str(value)) elif isinstance(value, None.__class__): element.set(key, '') else: create_subelement(element, key, value) def modify_response_types(value, type_translator): """ This will convert some string in response dictionary to ints or bool so that our respose is compatiable with code expecting JSON style responses """ if isinstance(value, str): if value == 'True': return True elif value == 'False': return False else: return type_translator(value) elif isinstance(value, dict): for k, v in value.iteritems(): if type_translator is not IntDict: if v.__class__ is dict and v.__len__() == 0: value[k] = None elif k in type_translator: value[k] = modify_response_types(value[k], type_translator[k]) else: value[k] = int(value[k]) return value elif isinstance(value, list): return [modify_response_types(element, type_translator) for element in value] class TroveXmlClient(TroveHTTPClient): @classmethod def morph_request(self, kwargs): kwargs['headers']['Accept'] = 'application/xml' kwargs['headers']['Content-Type'] = 'application/xml' if 'body' in kwargs: body = kwargs['body'] root_name = body.keys()[0] xml = create_root_xml_element(root_name, body[root_name]) xml_string = etree.tostring(xml, pretty_print=True) kwargs['body'] = xml_string @classmethod def morph_response_body(self, body_string): # The root XML element always becomes a dictionary with a single # field, which has the same key as the elements name. result = {} try: root_element = etree.XML(body_string) except etree.XMLSyntaxError: raise exceptions.ResponseFormatError() root_name = normalize_tag(root_element) root_value, links = root_element_to_json(root_name, root_element) result = {root_name: root_value} if links: result['links'] = links modify_response_types(result, TYPE_MAP) return result python-troveclient-1.0.3/troveclient/compat/exceptions.py0000664000175300017540000001131412232556651025110 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. class UnsupportedVersion(Exception): """Indicates that the user is trying to use an unsupported version of the API""" 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 AuthUrlNotGiven(EndpointNotFound): """The auth url was not given.""" pass class ServiceUrlNotGiven(EndpointNotFound): """The service url was not given.""" pass class ResponseFormatError(Exception): """Could not parse the response format.""" 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" class UnprocessableEntity(ClientException): """ HTTP 422 - Unprocessable Entity: The request cannot be processed. """ http_status = 422 message = "Unprocessable Entity" # 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, UnprocessableEntity]) def from_response(response, body): """ Return an instance of an ClientException or subclass based on an httplib2 response. Usage:: resp, body = http.request(...) if resp.status != 200: raise exception_from_response(resp, body) """ cls = _code_map.get(response.status, ClientException) if body: message = "n/a" details = "n/a" if hasattr(body, 'keys'): error = body[body.keys()[0]] message = error.get('message', None) details = error.get('details', None) return cls(code=response.status, message=message, details=details) else: request_id = response.get('x-compute-request-id') return cls(code=response.status, request_id=request_id) python-troveclient-1.0.3/troveclient/compat/client.py0000664000175300017540000003141712232556651024213 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. import httplib2 import logging import os import time import sys try: import json except ImportError: import simplejson as json from troveclient.compat import auth from troveclient.compat import exceptions _logger = logging.getLogger(__name__) RDC_PP = os.environ.get("RDC_PP", "False") == "True" expected_errors = (400, 401, 403, 404, 408, 409, 413, 422, 500, 501) def log_to_streamhandler(stream=None): stream = stream or sys.stderr ch = logging.StreamHandler(stream) _logger.setLevel(logging.DEBUG) _logger.addHandler(ch) if 'REDDWARFCLIENT_DEBUG' in os.environ and os.environ['REDDWARFCLIENT_DEBUG']: log_to_streamhandler() class TroveHTTPClient(httplib2.Http): USER_AGENT = 'python-troveclient' def __init__(self, user, password, tenant, auth_url, service_name, service_url=None, auth_strategy=None, insecure=False, timeout=None, proxy_tenant_id=None, proxy_token=None, region_name=None, endpoint_type='publicURL', service_type=None, timings=False): super(TroveHTTPClient, self).__init__(timeout=timeout) self.username = user self.password = password self.tenant = tenant if auth_url: self.auth_url = auth_url.rstrip('/') else: self.auth_url = None self.region_name = region_name self.endpoint_type = endpoint_type self.service_url = service_url self.service_type = service_type self.service_name = service_name self.timings = timings self.times = [] # [("item", starttime, endtime), ...] self.auth_token = None self.proxy_token = proxy_token self.proxy_tenant_id = proxy_tenant_id # httplib2 overrides self.force_exception_to_status_code = True self.disable_ssl_certificate_validation = insecure auth_cls = auth.get_authenticator_cls(auth_strategy) self.authenticator = auth_cls(self, auth_strategy, self.auth_url, self.username, self.password, self.tenant, region=region_name, service_type=service_type, service_name=service_name, service_url=service_url) def get_timings(self): return self.times def http_log(self, args, kwargs, resp, body): if not RDC_PP: self.simple_log(args, kwargs, resp, body) else: self.pretty_log(args, kwargs, resp, body) def simple_log(self, args, kwargs, resp, body): if not _logger.isEnabledFor(logging.DEBUG): return string_parts = ['curl -i'] for element in args: if element in ('GET', 'POST'): 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) _logger.debug("REQ: %s\n" % "".join(string_parts)) if 'body' in kwargs: _logger.debug("REQ BODY: %s\n" % (kwargs['body'])) _logger.debug("RESP:%s %s\n", resp, body) def pretty_log(self, args, kwargs, resp, body): if not _logger.isEnabledFor(logging.DEBUG): return string_parts = ['curl -i'] for element in args: if element in ('GET', 'POST'): 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) curl_cmd = "".join(string_parts) _logger.debug("REQUEST:") if 'body' in kwargs: _logger.debug("%s -d '%s'" % (curl_cmd, kwargs['body'])) try: req_body = json.dumps(json.loads(kwargs['body']), sort_keys=True, indent=4) except: req_body = kwargs['body'] _logger.debug("BODY: %s\n" % (req_body)) else: _logger.debug(curl_cmd) try: resp_body = json.dumps(json.loads(body), sort_keys=True, indent=4) except: resp_body = body _logger.debug("RESPONSE HEADERS: %s" % resp) _logger.debug("RESPONSE BODY : %s" % resp_body) def request(self, *args, **kwargs): kwargs.setdefault('headers', kwargs.get('headers', {})) kwargs['headers']['User-Agent'] = self.USER_AGENT self.morph_request(kwargs) resp, body = super(TroveHTTPClient, self).request(*args, **kwargs) # compat between requests and httplib2 resp.status_code = resp.status # Save this in case anyone wants it. self.last_response = (resp, body) self.http_log(args, kwargs, resp, body) if body: try: body = self.morph_response_body(body) except exceptions.ResponseFormatError: # Acceptable only if the response status is an error code. # Otherwise its the API or client misbehaving. self.raise_error_from_status(resp, None) raise # Not accepted! else: body = None if resp.status in expected_errors: raise exceptions.from_response(resp, body) return resp, body def raise_error_from_status(self, resp, body): if resp.status in expected_errors: raise exceptions.from_response(resp, body) def morph_request(self, kwargs): kwargs['headers']['Accept'] = 'application/json' kwargs['headers']['Content-Type'] = 'application/json' if 'body' in kwargs: kwargs['body'] = json.dumps(kwargs['body']) def morph_response_body(self, body_string): try: return json.loads(body_string) except ValueError: raise exceptions.ResponseFormatError() def _time_request(self, url, method, **kwargs): start_time = time.time() resp, body = self.request(url, method, **kwargs) self.times.append(("%s %s" % (method, url), start_time, time.time())) return resp, body def _cs_request(self, url, method, **kwargs): def request(): kwargs.setdefault('headers', {})['X-Auth-Token'] = self.auth_token if self.tenant: kwargs['headers']['X-Auth-Project-Id'] = self.tenant resp, body = self._time_request(self.service_url + url, method, **kwargs) return resp, body if not self.auth_token or not self.service_url: self.authenticate() # Perform the request once. If we get a 401 back then it # might be because the auth token expired, so try to # re-authenticate and try again. If it still fails, bail. try: return request() except exceptions.Unauthorized as ex: self.authenticate() return request() 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 authenticate(self): """Auths the client and gets a token. May optionally set a service url. The client will get auth errors until the authentication step occurs. Additionally, if a service_url was not explicitly given in the clients __init__ method, one will be obtained from the auth service. """ catalog = self.authenticator.authenticate() if self.service_url: possible_service_url = None else: if self.endpoint_type == "publicURL": possible_service_url = catalog.get_public_url() elif self.endpoint_type == "adminURL": possible_service_url = catalog.get_management_url() self.authenticate_with_token(catalog.get_token(), possible_service_url) def authenticate_with_token(self, token, service_url=None): self.auth_token = token if not self.service_url: if not service_url: raise exceptions.ServiceUrlNotGiven() else: self.service_url = service_url class Dbaas(object): """ Top-level object to access the Rackspace Database as a Service API. Create an instance with your creds:: >>> red = Dbaas(USERNAME, API_KEY, TENANT, AUTH_URL, SERVICE_NAME, \ SERVICE_URL) Then call methods on its managers:: >>> red.instances.list() ... >>> red.flavors.list() ... &c. """ def __init__(self, username, api_key, tenant=None, auth_url=None, service_type='database', service_name=None, service_url=None, insecure=False, auth_strategy='keystone', region_name=None, client_cls=TroveHTTPClient): from troveclient.compat.versions import Versions from troveclient.v1.databases import Databases from troveclient.v1.flavors import Flavors from troveclient.v1.instances import Instances from troveclient.v1.limits import Limits from troveclient.v1.users import Users from troveclient.v1.root import Root from troveclient.v1.hosts import Hosts from troveclient.v1.quota import Quotas from troveclient.v1.backups import Backups from troveclient.v1.security_groups import SecurityGroups from troveclient.v1.security_groups import SecurityGroupRules from troveclient.v1.storage import StorageInfo from troveclient.v1.management import Management from troveclient.v1.management import MgmtFlavors from troveclient.v1.accounts import Accounts from troveclient.v1.diagnostics import DiagnosticsInterrogator from troveclient.v1.diagnostics import HwInfoInterrogator self.client = client_cls(username, api_key, tenant, auth_url, service_type=service_type, service_name=service_name, service_url=service_url, insecure=insecure, auth_strategy=auth_strategy, region_name=region_name) self.versions = Versions(self) self.databases = Databases(self) self.flavors = Flavors(self) self.instances = Instances(self) self.limits = Limits(self) self.users = Users(self) self.root = Root(self) self.hosts = Hosts(self) self.quota = Quotas(self) self.backups = Backups(self) self.security_groups = SecurityGroups(self) self.security_group_rules = SecurityGroupRules(self) self.storage = StorageInfo(self) self.management = Management(self) self.mgmt_flavor = MgmtFlavors(self) self.accounts = Accounts(self) self.diagnostics = DiagnosticsInterrogator(self) self.hwinfo = HwInfoInterrogator(self) class Mgmt(object): def __init__(self, dbaas): self.instances = dbaas.management self.hosts = dbaas.hosts self.accounts = dbaas.accounts self.storage = dbaas.storage self.mgmt = Mgmt(self) def set_management_url(self, url): self.client.management_url = url def get_timings(self): return self.client.get_timings() def authenticate(self): """ Authenticate against the server. This is called to perform an authentication to retrieve a token. Returns on success; raises :exc:`exceptions.Unauthorized` if the credentials are wrong. """ self.client.authenticate() python-troveclient-1.0.3/troveclient/compat/__init__.py0000664000175300017540000000301412232556651024464 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 troveclient.v1.accounts import Accounts # noqa from troveclient.v1.databases import Databases # noqa from troveclient.v1.flavors import Flavors # noqa from troveclient.v1.instances import Instances # noqa from troveclient.v1.hosts import Hosts # noqa from troveclient.v1.management import Management # noqa from troveclient.v1.management import RootHistory # noqa from troveclient.v1.management import MgmtFlavors # noqa from troveclient.v1.root import Root # noqa from troveclient.v1.storage import StorageInfo # noqa from troveclient.v1.users import Users # noqa from troveclient.compat.versions import Versions # noqa from troveclient.v1.diagnostics import DiagnosticsInterrogator # noqa from troveclient.v1.diagnostics import HwInfoInterrogator # noqa from troveclient.compat.client import Dbaas # noqa from troveclient.compat.client import TroveHTTPClient # noqa python-troveclient-1.0.3/troveclient/compat/cli.py0000664000175300017540000003010112232556651023471 0ustar jenkinsjenkins00000000000000#!/usr/bin/env python # 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. """ Trove Command line tool """ import os import sys # If ../trove/__init__.py exists, add ../ to Python search path, so that # it will override what happens to be installed in /usr/(local/)lib/python... possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), os.pardir, os.pardir)) if os.path.exists(os.path.join(possible_topdir, 'troveclient', '__init__.py')): sys.path.insert(0, possible_topdir) from troveclient.compat import common class InstanceCommands(common.AuthedCommandsBase): """Commands to perform various instances operations and actions""" params = [ 'flavor', 'id', 'limit', 'marker', 'name', 'size', 'backup', 'availability_zone' ] def create(self): """Create a new instance""" self._require('name', 'flavor') volume = None if self.size: volume = {"size": self.size} restorePoint = None if self.backup: restorePoint = {"backupRef": self.backup} self._pretty_print(self.dbaas.instances.create, self.name, self.flavor, volume, restorePoint=restorePoint, availability_zone=self.availability_zone) def delete(self): """Delete the specified instance""" self._require('id') print self.dbaas.instances.delete(self.id) def get(self): """Get details for the specified instance""" self._require('id') self._pretty_print(self.dbaas.instances.get, self.id) def backups(self): """Get a list of backups for the specified instance""" self._require('id') self._pretty_list(self.dbaas.instances.backups, self.id) def list(self): """List all instances for account""" # limit and marker are not required. limit = self.limit or None if limit: limit = int(limit, 10) self._pretty_paged(self.dbaas.instances.list) def resize_volume(self): """Resize an instance volume""" self._require('id', 'size') self._pretty_print(self.dbaas.instances.resize_volume, self.id, self.size) def resize_instance(self): """Resize an instance flavor""" self._require('id', 'flavor') self._pretty_print(self.dbaas.instances.resize_instance, self.id, self.flavor) def restart(self): """Restart the database""" self._require('id') self._pretty_print(self.dbaas.instances.restart, self.id) class FlavorsCommands(common.AuthedCommandsBase): """Commands for listing Flavors""" params = [] def list(self): """List the available flavors""" self._pretty_list(self.dbaas.flavors.list) class DatabaseCommands(common.AuthedCommandsBase): """Database CRUD operations on an instance""" params = [ 'name', 'id', 'limit', 'marker', ] def create(self): """Create a database""" self._require('id', 'name') databases = [{'name': self.name}] print self.dbaas.databases.create(self.id, databases) def delete(self): """Delete a database""" self._require('id', 'name') print self.dbaas.databases.delete(self.id, self.name) def list(self): """List the databases""" self._require('id') self._pretty_paged(self.dbaas.databases.list, self.id) class UserCommands(common.AuthedCommandsBase): """User CRUD operations on an instance""" params = [ 'id', 'database', 'databases', 'hostname', 'name', 'password', 'new_name', 'new_host', 'new_password', ] def create(self): """Create a user in instance, with access to one or more databases""" self._require('id', 'name', 'password', 'databases') self._make_list('databases') databases = [{'name': dbname} for dbname in self.databases] users = [{'name': self.name, 'password': self.password, 'databases': databases}] if self.hostname: users[0]['host'] = self.hostname self.dbaas.users.create(self.id, users) def delete(self): """Delete the specified user""" self._require('id', 'name') self.dbaas.users.delete(self.id, self.name, self.hostname) def get(self): """Get a single user.""" self._require('id', 'name') self._pretty_print(self.dbaas.users.get, self.id, self.name, self.hostname) def update_attributes(self): """Update attributes of a single user.""" self._require('id', 'name') self._require_at_least_one_of('new_name', 'new_host', 'new_password') user_new = {} if self.new_name: user_new['name'] = self.new_name if self.new_host: user_new['host'] = self.new_host if self.new_password: user_new['password'] = self.new_password self.dbaas.users.update_attributes(self.id, self.name, user_new, self.hostname) def list(self): """List all the users for an instance""" self._require('id') self._pretty_paged(self.dbaas.users.list, self.id) def access(self): """Show all databases the user has access to.""" self._require('id', 'name') self._pretty_list(self.dbaas.users.list_access, self.id, self.name, self.hostname) def grant(self): """Allow an existing user permissions to access one or more databases.""" self._require('id', 'name', 'databases') self._make_list('databases') self.dbaas.users.grant(self.id, self.name, self.databases, self.hostname) def revoke(self): """Revoke from an existing user access permissions to a database.""" self._require('id', 'name', 'database') self.dbaas.users.revoke(self.id, self.name, self.database, self.hostname) def change_password(self): """Change the password of a single user.""" self._require('id', 'name', 'password') users = [{'name': self.name, 'host': self.hostname, 'password': self.password}] self.dbaas.users.change_passwords(self.id, users) class RootCommands(common.AuthedCommandsBase): """Root user related operations on an instance""" params = [ 'id', ] def create(self): """Enable the instance's root user.""" self._require('id') try: user, password = self.dbaas.root.create(self.id) print "User:\t\t%s\nPassword:\t%s" % (user, password) except: print sys.exc_info()[1] def enabled(self): """Check the instance for root access""" self._require('id') self._pretty_print(self.dbaas.root.is_root_enabled, self.id) class VersionCommands(common.AuthedCommandsBase): """List available versions""" params = [ 'url', ] def list(self): """List all the supported versions""" self._require('url') self._pretty_list(self.dbaas.versions.index, self.url) class LimitsCommands(common.AuthedCommandsBase): """Show the rate limits and absolute limits""" def list(self): """List the rate limits and absolute limits""" self._pretty_list(self.dbaas.limits.list) class BackupsCommands(common.AuthedCommandsBase): """Command to manage and show backups""" params = ['name', 'instance', 'description'] def get(self): """Get details for the specified backup""" self._require('id') self._pretty_print(self.dbaas.backups.get, self.id) def list(self): """List backups""" self._pretty_list(self.dbaas.backups.list) def create(self): """Create a new backup""" self._require('name', 'instance') self._pretty_print(self.dbaas.backups.create, self.name, self.instance, self.description) def delete(self): """Delete a backup""" self._require('id') self._pretty_print(self.dbaas.backups.delete, self.id) class SecurityGroupCommands(common.AuthedCommandsBase): """Commands to list and show Security Groups For an Instance and """ """create and delete security group rules for them. """ params = [ 'id', 'secgroup_id', 'protocol', 'from_port', 'to_port', 'cidr' ] def get(self): """Get a security group associated with an instance.""" self._require('id') self._pretty_print(self.dbaas.security_groups.get, self.id) def list(self): """List all the Security Groups and the rules""" self._pretty_paged(self.dbaas.security_groups.list) def add_rule(self): """Add a security group rule""" self._require('secgroup_id', 'protocol', 'from_port', 'to_port', 'cidr') self.dbaas.security_group_rules.create(self.secgroup_id, self.protocol, self.from_port, self.to_port, self.cidr) def delete_rule(self): """Delete a security group rule""" self._require('id') self.dbaas.security_group_rules.delete(self.id) COMMANDS = { 'auth': common.Auth, 'instance': InstanceCommands, 'flavor': FlavorsCommands, 'database': DatabaseCommands, 'limit': LimitsCommands, 'backup': BackupsCommands, 'user': UserCommands, 'root': RootCommands, 'version': VersionCommands, 'secgroup': SecurityGroupCommands, } def main(): # Parse arguments import pdb load_file = True for index, arg in enumerate(sys.argv): if (arg == "auth" and len(sys.argv) > (index + 1) and sys.argv[index + 1] == "login"): load_file = False oparser = common.CliOptions.create_optparser(load_file) for k, v in COMMANDS.items(): v._prepare_parser(oparser) (options, args) = oparser.parse_args() if not args: common.print_commands(COMMANDS) if options.verbose: os.environ['RDC_PP'] = "True" os.environ['REDDWARFCLIENT_DEBUG'] = "True" # Pop the command and check if it's in the known commands cmd = args.pop(0) if cmd in COMMANDS: fn = COMMANDS.get(cmd) command_object = None try: command_object = fn(oparser) except Exception as ex: if options.debug: raise print(ex) # Get a list of supported actions for the command actions = common.methods_of(command_object) if len(args) < 1: common.print_actions(cmd, actions) # Check for a valid action and perform that action action = args.pop(0) if action in actions: if not options.debug: try: getattr(command_object, action)() except Exception as ex: if options.debug: raise print ex else: getattr(command_object, action)() else: common.print_actions(cmd, actions) else: common.print_commands(COMMANDS) if __name__ == '__main__': main() python-troveclient-1.0.3/troveclient/compat/utils.py0000664000175300017540000000360412232556651024072 0ustar jenkinsjenkins00000000000000# Copyright 2012 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import os import re from troveclient.openstack.common import strutils 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 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', '') # 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". Make use of strutils.to_slug from openstack common """ return strutils.to_slug(value, incoming=None, errors="strict") python-troveclient-1.0.3/troveclient/compat/tests/0000775000175300017540000000000012232557034023513 5ustar jenkinsjenkins00000000000000python-troveclient-1.0.3/troveclient/compat/tests/test_xml.py0000664000175300017540000002300412232556651025727 0ustar jenkinsjenkins00000000000000from testtools import TestCase from lxml import etree #from troveclient import xml # Killing this until xml support is brought back. #class XmlTest(TestCase): class XmlTest(object): ELEMENT = ''' ''' ROOT = etree.fromstring(ELEMENT) JSON = {'instances': { 'instances': ['1', '2', '3']}, 'dummy': {'dict': True} } def test_element_ancestors_match_list(self): # Test normal operation: self.assertTrue(xml.element_ancestors_match_list(self.ROOT[0][0], ['instance', 'instances'])) # Test itr_elem is None: self.assertTrue(xml.element_ancestors_match_list(self.ROOT, ['instances'])) # Test that the first parent element does not match the first list # element: self.assertFalse(xml.element_ancestors_match_list(self.ROOT[0][0], ['instances', 'instance'])) def test_populate_element_from_dict(self): # Test populate_element_from_dict with a None in the data ele = ''' ''' rt = etree.fromstring(ele) self.assertEqual(None, xml.populate_element_from_dict(rt, {'size': None})) def test_element_must_be_list(self): # Test for when name isn't in the dictionary self.assertFalse(xml.element_must_be_list(self.ROOT, "not_in_list")) # Test when name is in the dictionary but list is empty self.assertTrue(xml.element_must_be_list(self.ROOT, "accounts")) # Test when name is in the dictionary but list is not empty self.assertTrue(xml.element_must_be_list(self.ROOT[0][0][0], "links")) def test_element_to_json(self): # Test when element must be list: self.assertEqual([{'flavor': {'links': [], 'value': {'value': '5'}}}], xml.element_to_json("accounts", self.ROOT)) # Test when element must not be list: exp = {'instance': {'flavor': {'links': [], 'value': {'value': '5'}}}} self.assertEqual(exp, xml.element_to_json("not_in_list", self.ROOT)) def test_root_element_to_json(self): # Test when element must be list: exp = ([{'flavor': {'links': [], 'value': {'value': '5'}}}], None) self.assertEqual(exp, xml.root_element_to_json("accounts", self.ROOT)) # Test when element must not be list: exp = {'instance': {'flavor': {'links': [], 'value': {'value': '5'}}}} self.assertEqual((exp, None), xml.root_element_to_json("not_in_list", self.ROOT)) # Test rootEnabled True: t_element = etree.fromstring(''' True ''') self.assertEqual((True, None), xml.root_element_to_json("rootEnabled", t_element)) # Test rootEnabled False: f_element = etree.fromstring(''' False ''') self.assertEqual((False, None), xml.root_element_to_json("rootEnabled", f_element)) def test_element_to_list(self): # Test w/ no child elements self.assertEqual([], xml.element_to_list(self.ROOT[0][0][0])) # Test w/ no child elements and check_for_links = True self.assertEqual(([], None), xml.element_to_list(self.ROOT[0][0][0], check_for_links=True)) # Test w/ child elements self.assertEqual([{}, {'value': '5'}], xml.element_to_list(self.ROOT[0][0])) # Test w/ child elements and check_for_links = True self.assertEqual(([{'value': '5'}], []), xml.element_to_list(self.ROOT[0][0], check_for_links=True)) def test_element_to_dict(self): # Test when there is not a None exp = {'instance': {'flavor': {'links': [], 'value': {'value': '5'}}}} self.assertEqual(exp, xml.element_to_dict(self.ROOT)) # Test when there is a None element = ''' None ''' rt = etree.fromstring(element) self.assertEqual(None, xml.element_to_dict(rt)) def test_standarize_json(self): xml.standardize_json_lists(self.JSON) self.assertEqual({'instances': ['1', '2', '3'], 'dummy': {'dict': True}}, self.JSON) def test_normalize_tag(self): ELEMENT_NS = ''' ''' ROOT_NS = etree.fromstring(ELEMENT_NS) # Test normalizing without namespace info self.assertEqual('instances', xml.normalize_tag(self.ROOT)) # Test normalizing with namespace info self.assertEqual('instances', xml.normalize_tag(ROOT_NS)) def test_create_root_xml_element(self): # Test creating when name is not in REQUEST_AS_LIST element = xml.create_root_xml_element("root", {"root": "value"}) exp = '' self.assertEqual(exp, etree.tostring(element)) # Test creating when name is in REQUEST_AS_LIST element = xml.create_root_xml_element("users", []) exp = '' self.assertEqual(exp, etree.tostring(element)) def test_creating_subelements(self): # Test creating a subelement as a dictionary element = xml.create_root_xml_element("root", {"root": 5}) xml.create_subelement(element, "subelement", {"subelement": "value"}) exp = '' self.assertEqual(exp, etree.tostring(element)) # Test creating a subelement as a list element = xml.create_root_xml_element("root", {"root": {"value": "nested"}}) xml.create_subelement(element, "subelement", [{"subelement": "value"}]) exp = '' \ '' self.assertEqual(exp, etree.tostring(element)) # Test creating a subelement as a string (should raise TypeError) element = xml.create_root_xml_element("root", {"root": "value"}) try: xml.create_subelement(element, "subelement", ["value"]) self.fail("TypeError exception expected") except TypeError: pass def test_modify_response_types(self): TYPE_MAP = { "Int": int, "Bool": bool } #Is a string True self.assertEqual(True, xml.modify_response_types('True', TYPE_MAP)) #Is a string False self.assertEqual(False, xml.modify_response_types('False', TYPE_MAP)) #Is a dict test_dict = {"Int": "5"} test_dict = xml.modify_response_types(test_dict, TYPE_MAP) self.assertEqual(int, test_dict["Int"].__class__) #Is a list test_list = {"a_list": [{"Int": "5"}, {"Str": "A"}]} test_list = xml.modify_response_types(test_list["a_list"], TYPE_MAP) self.assertEqual([{'Int': 5}, {'Str': 'A'}], test_list) def test_trovexmlclient(self): from troveclient import exceptions client = xml.TroveXmlClient("user", "password", "tenant", "auth_url", "service_name", auth_strategy="fake") request = {'headers': {}} # Test morph_request, no body client.morph_request(request) self.assertEqual('application/xml', request['headers']['Accept']) self.assertEqual('application/xml', request['headers']['Content-Type']) # Test morph_request, with body request['body'] = {'root': {'test': 'test'}} client.morph_request(request) body = '\n' exp = {'body': body, 'headers': {'Content-Type': 'application/xml', 'Accept': 'application/xml'}} self.assertEqual(exp, request) # Test morph_response_body request = "" result = client.morph_response_body(request) self.assertEqual({'users': [], 'links': [{'href': 'value'}]}, result) # Test morph_response_body with improper input try: client.morph_response_body("value") self.fail("ResponseFormatError exception expected") except exceptions.ResponseFormatError: pass python-troveclient-1.0.3/troveclient/compat/tests/__init__.py0000664000175300017540000000000012232556651025616 0ustar jenkinsjenkins00000000000000python-troveclient-1.0.3/troveclient/compat/tests/test_auth.py0000664000175300017540000003670212232556651026101 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack Foundation # Copyright 2013 Rackspace Hosting # Copyright 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 testtools import TestCase from troveclient.compat import auth from mock import Mock from troveclient.compat import exceptions """ Unit tests for the classes and functions in auth.py. """ def check_url_none(test_case, auth_class): # url is None, it must throw exception authObj = auth_class(url=None, type=auth_class, client=None, username=None, password=None, tenant=None) try: authObj.authenticate() test_case.fail("AuthUrlNotGiven exception expected") except exceptions.AuthUrlNotGiven: pass class AuthenticatorTest(TestCase): def setUp(self): super(AuthenticatorTest, self).setUp() self.orig_load = auth.ServiceCatalog._load self.orig__init = auth.ServiceCatalog.__init__ def tearDown(self): super(AuthenticatorTest, self).tearDown() auth.ServiceCatalog._load = self.orig_load auth.ServiceCatalog.__init__ = self.orig__init def test_get_authenticator_cls(self): class_list = (auth.KeyStoneV2Authenticator, auth.RaxAuthenticator, auth.Auth1_1, auth.FakeAuth) for c in class_list: self.assertEqual(c, auth.get_authenticator_cls(c)) class_names = {"keystone": auth.KeyStoneV2Authenticator, "rax": auth.RaxAuthenticator, "auth1.1": auth.Auth1_1, "fake": auth.FakeAuth} for cn in class_names.keys(): self.assertEqual(class_names[cn], auth.get_authenticator_cls(cn)) cls_or_name = "_unknown_" self.assertRaises(ValueError, auth.get_authenticator_cls, cls_or_name) def test__authenticate(self): authObj = auth.Authenticator(Mock(), auth.KeyStoneV2Authenticator, Mock(), Mock(), Mock(), Mock()) # test response code 200 resp = Mock() resp.status = 200 body = "test_body" auth.ServiceCatalog._load = Mock(return_value=1) authObj.client._time_request = Mock(return_value=(resp, body)) sc = authObj._authenticate(Mock(), Mock()) self.assertEqual(body, sc.catalog) # test AmbiguousEndpoints exception auth.ServiceCatalog.__init__ = \ Mock(side_effect=exceptions.AmbiguousEndpoints) self.assertRaises(exceptions.AmbiguousEndpoints, authObj._authenticate, Mock(), Mock()) # test handling KeyError and raising AuthorizationFailure exception auth.ServiceCatalog.__init__ = Mock(side_effect=KeyError) self.assertRaises(exceptions.AuthorizationFailure, authObj._authenticate, Mock(), Mock()) # test EndpointNotFound exception mock = Mock(side_effect=exceptions.EndpointNotFound) auth.ServiceCatalog.__init__ = mock self.assertRaises(exceptions.EndpointNotFound, authObj._authenticate, Mock(), Mock()) mock.side_effect = None # test response code 305 resp.__getitem__ = Mock(return_value='loc') resp.status = 305 body = "test_body" authObj.client._time_request = Mock(return_value=(resp, body)) l = authObj._authenticate(Mock(), Mock()) self.assertEqual('loc', l) # test any response code other than 200 and 305 resp.status = 404 exceptions.from_response = Mock(side_effect=ValueError) self.assertRaises(ValueError, authObj._authenticate, Mock(), Mock()) def test_authenticate(self): authObj = auth.Authenticator(Mock(), auth.KeyStoneV2Authenticator, Mock(), Mock(), Mock(), Mock()) self.assertRaises(NotImplementedError, authObj.authenticate) class KeyStoneV2AuthenticatorTest(TestCase): def test_authenticate(self): # url is None check_url_none(self, auth.KeyStoneV2Authenticator) # url is not None, so it must not throw exception url = "test_url" cls_type = auth.KeyStoneV2Authenticator authObj = auth.KeyStoneV2Authenticator(url=url, type=cls_type, client=None, username=None, password=None, tenant=None) def side_effect_func(url): return url mock = Mock() mock.side_effect = side_effect_func authObj._v2_auth = mock r = authObj.authenticate() self.assertEqual(url, r) def test__v2_auth(self): username = "trove_user" password = "trove_password" tenant = "tenant" cls_type = auth.KeyStoneV2Authenticator authObj = auth.KeyStoneV2Authenticator(url=None, type=cls_type, client=None, username=username, password=password, tenant=tenant) def side_effect_func(url, body): return body mock = Mock() mock.side_effect = side_effect_func authObj._authenticate = mock body = authObj._v2_auth(Mock()) self.assertEqual(username, body['auth']['passwordCredentials']['username']) self.assertEqual(password, body['auth']['passwordCredentials']['password']) self.assertEqual(tenant, body['auth']['tenantName']) class Auth1_1Test(TestCase): def test_authenticate(self): # handle when url is None check_url_none(self, auth.Auth1_1) # url is not none username = "trove_user" password = "trove_password" url = "test_url" authObj = auth.Auth1_1(url=url, type=auth.Auth1_1, client=None, username=username, password=password, tenant=None) def side_effect_func(auth_url, body, root_key): return auth_url, body, root_key mock = Mock() mock.side_effect = side_effect_func authObj._authenticate = mock auth_url, body, root_key = authObj.authenticate() self.assertEqual(username, body['credentials']['username']) self.assertEqual(password, body['credentials']['key']) self.assertEqual(auth_url, url) self.assertEqual('auth', root_key) class RaxAuthenticatorTest(TestCase): def test_authenticate(self): # url is None check_url_none(self, auth.RaxAuthenticator) # url is not None, so it must not throw exception url = "test_url" authObj = auth.RaxAuthenticator(url=url, type=auth.RaxAuthenticator, client=None, username=None, password=None, tenant=None) def side_effect_func(url): return url mock = Mock() mock.side_effect = side_effect_func authObj._rax_auth = mock r = authObj.authenticate() self.assertEqual(url, r) def test__rax_auth(self): username = "trove_user" password = "trove_password" tenant = "tenant" authObj = auth.RaxAuthenticator(url=None, type=auth.RaxAuthenticator, client=None, username=username, password=password, tenant=tenant) def side_effect_func(url, body): return body mock = Mock() mock.side_effect = side_effect_func authObj._authenticate = mock body = authObj._rax_auth(Mock()) v = body['auth']['RAX-KSKEY:apiKeyCredentials']['username'] self.assertEqual(username, v) v = body['auth']['RAX-KSKEY:apiKeyCredentials']['apiKey'] self.assertEqual(password, v) v = body['auth']['RAX-KSKEY:apiKeyCredentials']['tenantName'] self.assertEqual(tenant, v) class FakeAuthTest(TestCase): def test_authenticate(self): tenant = "tenant" authObj = auth.FakeAuth(url=None, type=auth.FakeAuth, client=None, username=None, password=None, tenant=tenant) fc = authObj.authenticate() public_url = "%s/%s" % ('http://localhost:8779/v1.0', tenant) self.assertEqual(public_url, fc.get_public_url()) self.assertEqual(tenant, fc.get_token()) class ServiceCatalogTest(TestCase): def setUp(self): super(ServiceCatalogTest, self).setUp() self.orig_url_for = auth.ServiceCatalog._url_for self.orig__init__ = auth.ServiceCatalog.__init__ auth.ServiceCatalog.__init__ = Mock(return_value=None) self.test_url = "http://localhost:1234/test" def tearDown(self): super(ServiceCatalogTest, self).tearDown() auth.ServiceCatalog._url_for = self.orig_url_for auth.ServiceCatalog.__init__ = self.orig__init__ def test__load(self): url = "random_url" auth.ServiceCatalog._url_for = Mock(return_value=url) # when service_url is None scObj = auth.ServiceCatalog() scObj.region = None scObj.service_url = None scObj._load() self.assertEqual(url, scObj.public_url) self.assertEqual(url, scObj.management_url) # service url is not None service_url = "service_url" scObj = auth.ServiceCatalog() scObj.region = None scObj.service_url = service_url scObj._load() self.assertEqual(service_url, scObj.public_url) self.assertEqual(service_url, scObj.management_url) def test_get_token(self): test_id = "test_id" scObj = auth.ServiceCatalog() scObj.root_key = "root_key" scObj.catalog = dict() scObj.catalog[scObj.root_key] = dict() scObj.catalog[scObj.root_key]['token'] = dict() scObj.catalog[scObj.root_key]['token']['id'] = test_id self.assertEqual(test_id, scObj.get_token()) def test_get_management_url(self): test_mng_url = "test_management_url" scObj = auth.ServiceCatalog() scObj.management_url = test_mng_url self.assertEqual(test_mng_url, scObj.get_management_url()) def test_get_public_url(self): test_public_url = "test_public_url" scObj = auth.ServiceCatalog() scObj.public_url = test_public_url self.assertEqual(test_public_url, scObj.get_public_url()) def test__url_for(self): scObj = auth.ServiceCatalog() # case for no endpoint found self.case_no_endpoint_match(scObj) # case for empty service catalog self.case_endpoing_with_empty_catalog(scObj) # more than one matching endpoints self.case_ambiguous_endpoint(scObj) # happy case self.case_unique_endpoint(scObj) # testing if-statements in for-loop to iterate services in catalog self.case_iterating_services_in_catalog(scObj) def case_no_endpoint_match(self, scObj): # empty endpoint list scObj.catalog = dict() scObj.catalog['endpoints'] = list() self.assertRaises(exceptions.EndpointNotFound, scObj._url_for) def side_effect_func_ep(attr): return "test_attr_value" # simulating dict endpoint = Mock() mock = Mock() mock.side_effect = side_effect_func_ep endpoint.__getitem__ = mock scObj.catalog['endpoints'].append(endpoint) # not-empty list but not matching endpoint filter_value = "not_matching_value" self.assertRaises(exceptions.EndpointNotFound, scObj._url_for, attr="test_attr", filter_value=filter_value) filter_value = "test_attr_value" # so that we have an endpoint match scObj.root_key = "access" scObj.catalog[scObj.root_key] = dict() self.assertRaises(exceptions.EndpointNotFound, scObj._url_for, attr="test_attr", filter_value=filter_value) def case_endpoing_with_empty_catalog(self, scObj): # first, test with empty catalog, this should pass since # there is already enpoint added scObj.catalog[scObj.root_key]['serviceCatalog'] = list() endpoint = scObj.catalog['endpoints'][0] endpoint.get = Mock(return_value=self.test_url) r_url = scObj._url_for(attr="test_attr", filter_value="test_attr_value") self.assertEqual(self.test_url, r_url) def case_ambiguous_endpoint(self, scObj): scObj.service_type = "trove" scObj.service_name = "test_service_name" def side_effect_func_service(key): if key == "type": return "trove" elif key == "name": return "test_service_name" return None mock1 = Mock() mock1.side_effect = side_effect_func_service service1 = Mock() service1.get = mock1 endpoint2 = {"test_attr": "test_attr_value"} service1.__getitem__ = Mock(return_value=[endpoint2]) scObj.catalog[scObj.root_key]['serviceCatalog'] = [service1] self.assertRaises(exceptions.AmbiguousEndpoints, scObj._url_for, attr="test_attr", filter_value="test_attr_value") def case_unique_endpoint(self, scObj): # changing the endpoint2 attribute to pass the filter service1 = scObj.catalog[scObj.root_key]['serviceCatalog'][0] endpoint2 = service1[0][0] endpoint2["test_attr"] = "new value not matching filter" r_url = scObj._url_for(attr="test_attr", filter_value="test_attr_value") self.assertEqual(self.test_url, r_url) def case_iterating_services_in_catalog(self, scObj): service1 = scObj.catalog[scObj.root_key]['serviceCatalog'][0] scObj.catalog = dict() scObj.root_key = "access" scObj.catalog[scObj.root_key] = dict() scObj.service_type = "no_match" scObj.catalog[scObj.root_key]['serviceCatalog'] = [service1] self.assertRaises(exceptions.EndpointNotFound, scObj._url_for) scObj.service_type = "database" scObj.service_name = "no_match" self.assertRaises(exceptions.EndpointNotFound, scObj._url_for) # no endpoints and no 'serviceCatalog' in catalog => raise exception scObj = auth.ServiceCatalog() scObj.catalog = dict() scObj.root_key = "access" scObj.catalog[scObj.root_key] = dict() scObj.catalog[scObj.root_key]['serviceCatalog'] = [] self.assertRaises(exceptions.EndpointNotFound, scObj._url_for, attr="test_attr", filter_value="test_attr_value") python-troveclient-1.0.3/troveclient/compat/tests/test_common.py0000664000175300017540000003104512232556651026423 0ustar jenkinsjenkins00000000000000import sys import optparse import json import collections from testtools import TestCase from mock import Mock from troveclient.compat import common from troveclient import client """ unit tests for common.py """ class CommonTest(TestCase): def setUp(self): super(CommonTest, self).setUp() self.orig_sys_exit = sys.exit sys.exit = Mock(return_value=None) def tearDown(self): super(CommonTest, self).tearDown() sys.exit = self.orig_sys_exit def test_methods_of(self): class DummyClass: def dummyMethod(self): print("just for test") obj = DummyClass() result = common.methods_of(obj) self.assertEqual(1, len(result)) method = result['dummyMethod'] self.assertIsNotNone(method) def test_check_for_exceptions(self): status = [400, 422, 500] for s in status: resp = Mock() #compat still uses status resp.status = s self.assertRaises(Exception, common.check_for_exceptions, resp, "body") # a no-exception case resp = Mock() resp.status_code = 200 common.check_for_exceptions(resp, "body") def test_print_actions(self): cmd = "test-cmd" actions = {"test": "test action", "help": "help action"} common.print_actions(cmd, actions) pass def test_print_commands(self): commands = {"cmd-1": "cmd 1", "cmd-2": "cmd 2"} common.print_commands(commands) pass def test_limit_url(self): url = "test-url" limit = None marker = None self.assertEqual(url, common.limit_url(url)) limit = "test-limit" marker = "test-marker" expected = "test-url?marker=test-marker&limit=test-limit" self.assertEqual(expected, common.limit_url(url, limit=limit, marker=marker)) class CliOptionsTest(TestCase): def check_default_options(self, co): self.assertEqual(None, co.username) self.assertEqual(None, co.apikey) self.assertEqual(None, co.tenant_id) self.assertEqual(None, co.auth_url) self.assertEqual('keystone', co.auth_type) self.assertEqual('database', co.service_type) self.assertEqual('RegionOne', co.region) self.assertEqual(None, co.service_url) self.assertFalse(co.insecure) self.assertFalse(co.verbose) self.assertFalse(co.debug) self.assertEqual(None, co.token) self.assertEqual(None, co.xml) def check_option(self, oparser, option_name): option = oparser.get_option("--%s" % option_name) self.assertNotEqual(None, option) if option_name in common.CliOptions.DEFAULT_VALUES: self.assertEqual(common.CliOptions.DEFAULT_VALUES[option_name], option.default) def test___init__(self): co = common.CliOptions() self.check_default_options(co) def test_deafult(self): co = common.CliOptions.default() self.check_default_options(co) def test_load_from_file(self): co = common.CliOptions.load_from_file() self.check_default_options(co) def test_create_optparser(self): option_names = ["verbose", "debug", "auth_url", "username", "apikey", "tenant_id", "auth_type", "service_type", "service_name", "service_type", "service_name", "service_url", "region", "insecure", "token", "xml", "secure", "json", "terse", "hide-debug"] oparser = common.CliOptions.create_optparser(True) for option_name in option_names: self.check_option(oparser, option_name) oparser = common.CliOptions.create_optparser(False) for option_name in option_names: self.check_option(oparser, option_name) class ArgumentRequiredTest(TestCase): def setUp(self): super(ArgumentRequiredTest, self).setUp() self.param = "test-param" self.arg_req = common.ArgumentRequired(self.param) def test___init__(self): self.assertEqual(self.param, self.arg_req.param) def test___str__(self): expected = 'Argument "--%s" required.' % self.param self.assertEqual(expected, self.arg_req.__str__()) class CommandsBaseTest(TestCase): def setUp(self): super(CommandsBaseTest, self).setUp() self.orig_sys_exit = sys.exit sys.exit = Mock(return_value=None) self.orig_sys_argv = sys.argv sys.argv = ['fakecmd'] parser = common.CliOptions().create_optparser(False) self.cmd_base = common.CommandsBase(parser) def tearDown(self): super(CommandsBaseTest, self).tearDown() sys.exit = self.orig_sys_exit sys.argv = self.orig_sys_argv def test___init__(self): self.assertNotEqual(None, self.cmd_base) def test__safe_exec(self): func = Mock(return_value="test") self.cmd_base.debug = True r = self.cmd_base._safe_exec(func) self.assertEqual("test", r) self.cmd_base.debug = False r = self.cmd_base._safe_exec(func) self.assertEqual("test", r) func = Mock(side_effect=ValueError) # an arbitrary exception r = self.cmd_base._safe_exec(func) self.assertEqual(None, r) def test__prepare_parser(self): parser = optparse.OptionParser() common.CommandsBase.params = ["test_1", "test_2"] self.cmd_base._prepare_parser(parser) option = parser.get_option("--%s" % "test_1") self.assertNotEqual(None, option) option = parser.get_option("--%s" % "test_2") self.assertNotEqual(None, option) def test__parse_options(self): parser = optparse.OptionParser() parser.add_option("--%s" % "test_1", default="test_1v") parser.add_option("--%s" % "test_2", default="test_2v") self.cmd_base._parse_options(parser) self.assertEqual("test_1v", self.cmd_base.test_1) self.assertEqual("test_2v", self.cmd_base.test_2) def test__require(self): self.assertRaises(common.ArgumentRequired, self.cmd_base._require, "attr_1") self.cmd_base.attr_1 = None self.assertRaises(common.ArgumentRequired, self.cmd_base._require, "attr_1") self.cmd_base.attr_1 = "attr_v1" self.cmd_base._require("attr_1") def test__make_list(self): self.assertRaises(AttributeError, self.cmd_base._make_list, "attr1") self.cmd_base.attr1 = "v1,v2" self.cmd_base._make_list("attr1") self.assertEqual(["v1", "v2"], self.cmd_base.attr1) self.cmd_base.attr1 = ["v3"] self.cmd_base._make_list("attr1") self.assertEqual(["v3"], self.cmd_base.attr1) def test__pretty_print(self): func = Mock(return_value=None) self.cmd_base.verbose = True self.assertEqual(None, self.cmd_base._pretty_print(func)) self.cmd_base.verbose = False self.assertEqual(None, self.cmd_base._pretty_print(func)) def test__dumps(self): json.dumps = Mock(return_value="test-dump") self.assertEqual("test-dump", self.cmd_base._dumps("item")) def test__pretty_list(self): func = Mock(return_value=None) self.cmd_base.verbose = True self.assertEqual(None, self.cmd_base._pretty_list(func)) self.cmd_base.verbose = False self.assertEqual(None, self.cmd_base._pretty_list(func)) item = Mock(return_value="test") item._info = "info" func = Mock(return_value=[item]) self.assertEqual(None, self.cmd_base._pretty_list(func)) def test__pretty_paged(self): self.cmd_base.limit = "5" func = Mock(return_value=None) self.cmd_base.verbose = True self.assertEqual(None, self.cmd_base._pretty_paged(func)) self.cmd_base.verbose = False class MockIterable(collections.Iterable): links = ["item"] count = 1 def __iter__(self): return ["item1"] def __len__(self): return count ret = MockIterable() func = Mock(return_value=ret) self.assertEqual(None, self.cmd_base._pretty_paged(func)) ret.count = 0 self.assertEqual(None, self.cmd_base._pretty_paged(func)) func = Mock(side_effect=ValueError) self.assertEqual(None, self.cmd_base._pretty_paged(func)) self.cmd_base.debug = True self.cmd_base.marker = Mock() self.assertRaises(ValueError, self.cmd_base._pretty_paged, func) class AuthTest(TestCase): def setUp(self): super(AuthTest, self).setUp() self.orig_sys_exit = sys.exit sys.exit = Mock(return_value=None) self.orig_sys_argv = sys.argv sys.argv = ['fakecmd'] self.parser = common.CliOptions().create_optparser(False) self.auth = common.Auth(self.parser) def tearDown(self): super(AuthTest, self).tearDown() sys.exit = self.orig_sys_exit sys.argv = self.orig_sys_argv def test___init__(self): self.assertEqual(None, self.auth.dbaas) self.assertEqual(None, self.auth.apikey) def test_login(self): self.auth.username = "username" self.auth.apikey = "apikey" self.auth.tenant_id = "tenant_id" self.auth.auth_url = "auth_url" dbaas = Mock() dbaas.authenticate = Mock(return_value=None) dbaas.client = Mock() dbaas.client.auth_token = Mock() dbaas.client.service_url = Mock() self.auth._get_client = Mock(return_value=dbaas) self.auth.login() self.auth.debug = True self.auth._get_client = Mock(side_effect=ValueError) self.assertRaises(ValueError, self.auth.login) self.auth.debug = False self.auth.login() class AuthedCommandsBaseTest(TestCase): def setUp(self): super(AuthedCommandsBaseTest, self).setUp() self.orig_sys_exit = sys.exit sys.exit = Mock(return_value=None) self.orig_sys_argv = sys.argv sys.argv = ['fakecmd'] def tearDown(self): super(AuthedCommandsBaseTest, self).tearDown() sys.exit = self.orig_sys_exit self.orig_sys_argv = sys.argv def test___init__(self): parser = common.CliOptions().create_optparser(False) common.AuthedCommandsBase.debug = True dbaas = Mock() dbaas.authenticate = Mock(return_value=None) dbaas.client = Mock() dbaas.client.auth_token = Mock() dbaas.client.service_url = Mock() dbaas.client.authenticate_with_token = Mock() common.AuthedCommandsBase._get_client = Mock(return_value=dbaas) authed_cmd = common.AuthedCommandsBase(parser) class PaginatedTest(TestCase): def setUp(self): super(PaginatedTest, self).setUp() self.items_ = ["item1", "item2"] self.next_marker_ = "next-marker" self.links_ = ["link1", "link2"] self.pgn = common.Paginated(self.items_, self.next_marker_, self.links_) def tearDown(self): super(PaginatedTest, self).tearDown() def test___init__(self): self.assertEqual(self.items_, self.pgn.items) self.assertEqual(self.next_marker_, self.pgn.next) self.assertEqual(self.links_, self.pgn.links) def test___len__(self): self.assertEqual(len(self.items_), self.pgn.__len__()) def test___iter__(self): itr_expected = self.items_.__iter__() itr = self.pgn.__iter__() self.assertEqual(next(itr_expected), next(itr)) self.assertEqual(next(itr_expected), next(itr)) self.assertRaises(StopIteration, next, itr_expected) self.assertRaises(StopIteration, next, itr) def test___getitem__(self): self.assertEqual(self.items_[0], self.pgn.__getitem__(0)) def test___setitem__(self): self.pgn.__setitem__(0, "new-item") self.assertEqual("new-item", self.pgn.items[0]) def test___delitem(self): del self.pgn[0] self.assertEqual(1, self.pgn.__len__()) def test___reversed__(self): itr = self.pgn.__reversed__() expected = ["item2", "item1"] self.assertEqual("item2", next(itr)) self.assertEqual("item1", next(itr)) self.assertRaises(StopIteration, next, itr) def test___contains__(self): self.assertTrue(self.pgn.__contains__("item1")) self.assertTrue(self.pgn.__contains__("item2")) self.assertFalse(self.pgn.__contains__("item3")) python-troveclient-1.0.3/troveclient/compat/base.py0000664000175300017540000002252212232556651023644 0ustar jenkinsjenkins00000000000000# Copyright 2010 Jacob Kaplan-Moss # Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Base utilities to build API operation managers and objects on top of. """ import contextlib import hashlib import os from troveclient.compat import exceptions from troveclient.compat 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('REDDWARFCLIENT_ID_CACHE_DIR', default="~/.troveclient") # NOTE(sirp): Keep separate UUID caches for each username + endpoint # pair username = utils.env('OS_USERNAME', 'USERNAME') url = utils.env('OS_URL', 'SERVICE_URL') uniqifier = hashlib.md5(username + url).hexdigest() cache_dir = os.path.expanduser(os.path.join(base_dir, uniqifier)) try: os.makedirs(cache_dir, 0o755) except OSError: # NOTE(kiall): This is typicaly 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 typicaly 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(Manager): """ Like a `Manager`, but with additional `find()`/`findall()` methods. """ 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 = 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 def list(self): raise NotImplementedError 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 info.iteritems(): 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__.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) 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-troveclient-1.0.3/troveclient/compat/auth.py0000664000175300017540000002130312232556651023667 0ustar jenkinsjenkins00000000000000# Copyright 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. from __future__ import print_function from six import string_types from troveclient.compat import exceptions def get_authenticator_cls(cls_or_name): """Factory method to retrieve Authenticator class.""" if isinstance(cls_or_name, type): return cls_or_name elif isinstance(cls_or_name, string_types): if cls_or_name == "keystone": return KeyStoneV2Authenticator elif cls_or_name == "rax": return RaxAuthenticator elif cls_or_name == "auth1.1": return Auth1_1 elif cls_or_name == "fake": return FakeAuth raise ValueError("Could not determine authenticator class from the given " "value %r." % cls_or_name) class Authenticator(object): """ Helper class to perform Keystone or other miscellaneous authentication. The "authenticate" method returns a ServiceCatalog, which can be used to obtain a token. """ URL_REQUIRED = True def __init__(self, client, type, url, username, password, tenant, region=None, service_type=None, service_name=None, service_url=None): self.client = client self.type = type self.url = url self.username = username self.password = password self.tenant = tenant self.region = region self.service_type = service_type self.service_name = service_name self.service_url = service_url def _authenticate(self, url, body, root_key='access'): """Authenticate and extract the service catalog.""" # Make sure we follow redirects when trying to reach Keystone tmp_follow_all_redirects = self.client.follow_all_redirects self.client.follow_all_redirects = True try: resp, body = self.client._time_request(url, "POST", body=body) finally: self.client.follow_all_redirects = tmp_follow_all_redirects if resp.status == 200: # content must always present try: return ServiceCatalog(body, region=self.region, service_type=self.service_type, service_name=self.service_name, service_url=self.service_url, root_key=root_key) 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 == 305: return resp['location'] else: raise exceptions.from_response(resp, body) def authenticate(self): raise NotImplementedError("Missing authenticate method.") class KeyStoneV2Authenticator(Authenticator): def authenticate(self): if self.url is None: raise exceptions.AuthUrlNotGiven() return self._v2_auth(self.url) def _v2_auth(self, url): """Authenticate against a v2.0 auth service.""" body = {"auth": { "passwordCredentials": { "username": self.username, "password": self.password} } } if self.tenant: body['auth']['tenantName'] = self.tenant return self._authenticate(url, body) class Auth1_1(Authenticator): def authenticate(self): """Authenticate against a v2.0 auth service.""" if self.url is None: raise exceptions.AuthUrlNotGiven() auth_url = self.url body = { "credentials": { "username": self.username, "key": self.password }} return self._authenticate(auth_url, body, root_key='auth') class RaxAuthenticator(Authenticator): def authenticate(self): if self.url is None: raise exceptions.AuthUrlNotGiven() return self._rax_auth(self.url) def _rax_auth(self, url): """Authenticate against the Rackspace auth service.""" body = {'auth': { 'RAX-KSKEY:apiKeyCredentials': { 'username': self.username, 'apiKey': self.password, 'tenantName': self.tenant} } } return self._authenticate(self.url, body) class FakeAuth(Authenticator): """Useful for faking auth.""" def authenticate(self): class FakeCatalog(object): def __init__(self, auth): self.auth = auth def get_public_url(self): return "%s/%s" % ('http://localhost:8779/v1.0', self.auth.tenant) def get_token(self): return self.auth.tenant return FakeCatalog(self) class ServiceCatalog(object): """Represents a Keystone Service Catalog which describes a service. This class has methods to obtain a valid token as well as a public service url and a management url. """ def __init__(self, resource_dict, region=None, service_type=None, service_name=None, service_url=None, root_key='access'): self.catalog = resource_dict self.region = region self.service_type = service_type self.service_name = service_name self.service_url = service_url self.management_url = None self.public_url = None self.root_key = root_key self._load() def _load(self): if not self.service_url: self.public_url = self._url_for(attr='region', filter_value=self.region, endpoint_type="publicURL") self.management_url = self._url_for(attr='region', filter_value=self.region, endpoint_type="adminURL") else: self.public_url = self.service_url self.management_url = self.service_url def get_token(self): return self.catalog[self.root_key]['token']['id'] def get_management_url(self): return self.management_url def get_public_url(self): return self.public_url def _url_for(self, attr=None, filter_value=None, endpoint_type='publicURL'): """ Fetch the public URL from the Trove service for a particular endpoint attribute. If none given, return the first. """ 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 exceptions.EndpointNotFound() # We don't always get a service catalog back ... if 'serviceCatalog' not in self.catalog[self.root_key]: raise exceptions.EndpointNotFound() # Full catalog ... catalog = self.catalog[self.root_key]['serviceCatalog'] for service in catalog: if service.get("type") != self.service_type: continue if (self.service_name and self.service_type == 'database' and service.get('name') != self.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 exceptions.EndpointNotFound() elif len(matching_endpoints) > 1: raise exceptions.AmbiguousEndpoints(endpoints=matching_endpoints) else: return matching_endpoints[0].get(endpoint_type, None) python-troveclient-1.0.3/troveclient/compat/versions.py0000664000175300017540000000234112232556651024577 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 troveclient.compat import base class Version(base.Resource): """ Version is an opaque instance used to hold version information. """ def __repr__(self): return "" % self.id class Versions(base.ManagerWithFind): """ Manage :class:`Versions` information. """ resource_class = Version def index(self, url): """ Get a list of all versions. :rtype: list of :class:`Versions`. """ resp, body = self.api.client.request(url, "GET") return [self.resource_class(self, res) for res in body['versions']] python-troveclient-1.0.3/troveclient/compat/mcli.py0000664000175300017540000001702312232556651023656 0ustar jenkinsjenkins00000000000000#!/usr/bin/env python # 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. """ Trove Management Command line tool """ import json import os import sys # If ../trove/__init__.py exists, add ../ to Python search path, so that # it will override what happens to be installed in /usr/(local/)lib/python... possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), os.pardir, os.pardir)) if os.path.exists(os.path.join(possible_topdir, 'troveclient.compat', '__init__.py')): sys.path.insert(0, possible_topdir) from troveclient.compat import common oparser = None def _pretty_print(info): print json.dumps(info, sort_keys=True, indent=4) class HostCommands(common.AuthedCommandsBase): """Commands to list info on hosts""" params = [ 'name', ] def update_all(self): """Update all instances on a host""" self._require('name') self.dbaas.hosts.update_all(self.name) def get(self): """List details for the specified host""" self._require('name') self._pretty_print(self.dbaas.hosts.get, self.name) def list(self): """List all compute hosts""" self._pretty_list(self.dbaas.hosts.index) class QuotaCommands(common.AuthedCommandsBase): """List and update quota limits for a tenant.""" params = ['id', 'instances', 'volumes', 'backups'] def list(self): """List all quotas for a tenant""" self._require('id') self._pretty_print(self.dbaas.quota.show, self.id) def update(self): """Update quota limits for a tenant""" self._require('id') self._pretty_print(self.dbaas.quota.update, self.id, dict((param, getattr(self, param)) for param in self.params if param != 'id')) class RootCommands(common.AuthedCommandsBase): """List details about the root info for an instance.""" params = [ 'id', ] def history(self): """List root history for the instance.""" self._require('id') self._pretty_print(self.dbaas.management.root_enabled_history, self.id) class AccountCommands(common.AuthedCommandsBase): """Commands to list account info""" params = [ 'id', ] def list(self): """List all accounts with non-deleted instances""" self._pretty_print(self.dbaas.accounts.index) def get(self): """List details for the account provided""" self._require('id') self._pretty_print(self.dbaas.accounts.show, self.id) class InstanceCommands(common.AuthedCommandsBase): """List details about an instance.""" params = [ 'deleted', 'id', 'limit', 'marker', 'host', ] def get(self): """List details for the instance.""" self._require('id') self._pretty_print(self.dbaas.management.show, self.id) def list(self): """List all instances for account""" deleted = None if self.deleted is not None: if self.deleted.lower() in ['true']: deleted = True elif self.deleted.lower() in ['false']: deleted = False self._pretty_paged(self.dbaas.management.index, deleted=deleted) def hwinfo(self): """Show hardware information details about an instance.""" self._require('id') self._pretty_print(self.dbaas.hwinfo.get, self.id) def diagnostic(self): """List diagnostic details about an instance.""" self._require('id') self._pretty_print(self.dbaas.diagnostics.get, self.id) def stop(self): """Stop MySQL on the given instance.""" self._require('id') self._pretty_print(self.dbaas.management.stop, self.id) def reboot(self): """Reboot the instance.""" self._require('id') self._pretty_print(self.dbaas.management.reboot, self.id) def migrate(self): """Migrate the instance.""" self._require('id') self._pretty_print(self.dbaas.management.migrate, self.id, self.host) def reset_task_status(self): """Set the instance's task status to NONE.""" self._require('id') self._pretty_print(self.dbaas.management.reset_task_status, self.id) class StorageCommands(common.AuthedCommandsBase): """Commands to list devices info""" params = [] def list(self): """List details for the storage device""" self._pretty_list(self.dbaas.storage.index) class FlavorsCommands(common.AuthedCommandsBase): """Commands for managing Flavors""" params = [ 'name', 'ram', 'disk', 'vcpus', 'flavor_id', 'ephemeral', 'swap', 'rxtx_factor', 'service_type' ] def create(self): """Create a new flavor""" self._require('name', 'ram', 'disk', 'vcpus', 'flavor_id', 'service_type') self._pretty_print(self.dbaas.mgmt_flavor.create, self.name, self.ram, self.disk, self.vcpus, self.flavor_id, self.ephemeral, self.swap, self.rxtx_factor, self.service_type) def config_options(oparser): oparser.add_option("-u", "--url", default="http://localhost:5000/v1.1", help="Auth API endpoint URL with port and version. \ Default: http://localhost:5000/v1.1") COMMANDS = { 'account': AccountCommands, 'host': HostCommands, 'instance': InstanceCommands, 'root': RootCommands, 'storage': StorageCommands, 'quota': QuotaCommands, 'flavor': FlavorsCommands, } def main(): # Parse arguments oparser = common.CliOptions.create_optparser(True) for k, v in COMMANDS.items(): v._prepare_parser(oparser) (options, args) = oparser.parse_args() if not args: common.print_commands(COMMANDS) # Pop the command and check if it's in the known commands cmd = args.pop(0) if cmd in COMMANDS: fn = COMMANDS.get(cmd) command_object = None try: command_object = fn(oparser) except Exception as ex: if options.debug: raise print(ex) # Get a list of supported actions for the command actions = common.methods_of(command_object) if len(args) < 1: common.print_actions(cmd, actions) # Check for a valid action and perform that action action = args.pop(0) if action in actions: try: getattr(command_object, action)() except Exception as ex: if options.debug: raise print ex else: common.print_actions(cmd, actions) else: common.print_commands(COMMANDS) if __name__ == '__main__': main() python-troveclient-1.0.3/troveclient/utils.py0000664000175300017540000001517312232556651022613 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack Foundation # Copyright 2013 Rackspace Hosting # 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 troveclient.openstack.common.apiclient import exceptions from troveclient.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 sematics 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 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('database') 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 translate_keys(collection, convert): for item in collection: keys = list(item.__dict__.keys()) 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 _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 = [] 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(' ', '_') 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: # Instances 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) 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) # 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". Make use strutils.to_slug from openstack common """ return strutils.to_slug(value, incoming=None, errors="strict") python-troveclient-1.0.3/troveclient/v1/0000775000175300017540000000000012232557034021414 5ustar jenkinsjenkins00000000000000python-troveclient-1.0.3/troveclient/v1/databases.py0000664000175300017540000000702112232556651023721 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack Foundation # Copyright 2013 Rackspace Hosting # 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 troveclient import base from troveclient.common import check_for_exceptions from troveclient.common import limit_url from troveclient.common import Paginated from troveclient.openstack.common.py3kcompat import urlutils class Database(base.Resource): """ According to Wikipedia, "A database is a system intended to organize, store, and retrieve large amounts of data easily." """ def __repr__(self): return "" % self.name class Databases(base.ManagerWithFind): """ Manage :class:`Databases` resources. """ resource_class = Database def create(self, instance_id, databases): """ Create new databases within the specified instance """ body = {"databases": databases} url = "/instances/%s/databases" % instance_id resp, body = self.api.client.post(url, body=body) check_for_exceptions(resp, body) def delete(self, instance_id, dbname): """Delete an existing database in the specified instance""" url = "/instances/%s/databases/%s" % (instance_id, dbname) resp, body = self.api.client.delete(url) check_for_exceptions(resp, body) def _list(self, url, response_key, limit=None, marker=None): resp, body = self.api.client.get(limit_url(url, limit, marker)) check_for_exceptions(resp, body) if not body: raise Exception("Call to " + url + " did not return a body.") links = body.get('links', []) next_links = [link['href'] for link in links if link['rel'] == 'next'] next_marker = None for link in next_links: # Extract the marker from the url. parsed_url = urlutils.urlparse(link) query_dict = dict(urlutils.parse_qsl(parsed_url.query)) next_marker = query_dict.get('marker', None) databases = body[response_key] databases = [self.resource_class(self, res) for res in databases] return Paginated(databases, next_marker=next_marker, links=links) def list(self, instance, limit=None, marker=None): """ Get a list of all Databases from the instance. :rtype: list of :class:`Database`. """ return self._list("/instances/%s/databases" % base.getid(instance), "databases", limit, marker) # def get(self, instance, database): # """ # Get a specific instances. # # :param flavor: The ID of the :class:`Database` to get. # :rtype: :class:`Database` # """ # assert isinstance(instance, Instance) # assert isinstance(database, (Database, int)) # instance_id = base.getid(instance) # db_id = base.getid(database) # url = "/instances/%s/databases/%s" % (instance_id, db_id) # return self._get(url, "database") python-troveclient-1.0.3/troveclient/v1/security_groups.py0000664000175300017540000001022012232556651025233 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack Foundation # Copyright 2013 Hewlett-Packard Development Company, L.P. # Copyright 2013 Rackspace Hosting # 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 troveclient import base from troveclient.common import limit_url from troveclient.common import Paginated from troveclient.openstack.common.apiclient import exceptions from troveclient.openstack.common.py3kcompat import urlutils class SecurityGroup(base.Resource): """ Security Group is a resource used to hold security group information. """ def __repr__(self): return "" % self.name class SecurityGroups(base.ManagerWithFind): """ Manage :class:`SecurityGroup` resources. """ resource_class = SecurityGroup def _list(self, url, response_key, limit=None, marker=None): resp, body = self.api.client.get(limit_url(url, limit, marker)) if not body: raise Exception("Call to " + url + " did not return a body.") links = body.get('links', []) next_links = [link['href'] for link in links if link['rel'] == 'next'] next_marker = None for link in next_links: # Extract the marker from the url. parsed_url = urlutils.urlparse(link) query_dict = dict(urlutils.parse_qsl(parsed_url.query)) next_marker = query_dict.get('marker', None) instances = body[response_key] instances = [self.resource_class(self, res) for res in instances] return Paginated(instances, next_marker=next_marker, links=links) def list(self, limit=None, marker=None): """ Get a list of all security groups. :rtype: list of :class:`SecurityGroup`. """ return self._list("/security-groups", "security_groups", limit, marker) def get(self, security_group): """ Get a specific security group. :rtype: :class:`SecurityGroup` """ return self._get("/security-groups/%s" % base.getid(security_group), "security_group") class SecurityGroupRule(base.Resource): """ Security Group Rule is a resource used to hold security group rule related information. """ def __repr__(self): return \ "" % (self.group_id, self.protocol, self.from_port, self.to_port, self.cidr) class SecurityGroupRules(base.ManagerWithFind): """ Manage :class:`SecurityGroupRules` resources. """ resource_class = SecurityGroupRule def create(self, group_id, protocol, from_port, to_port, cidr): """ Create a new security group rule. """ body = {"security_group_rule": { "group_id": group_id, "protocol": protocol, "from_port": from_port, "to_port": to_port, "cidr": cidr }} return self._create("/security-group-rules", body, "security_group_rule") def delete(self, security_group_rule): """ Delete the specified security group rule. :param security_group_rule: The security group rule to delete """ resp, body = self.api.client.delete("/security-group-rules/%s" % base.getid(security_group_rule)) if resp.status_code in (422, 500): raise exceptions.from_response(resp, body) # Appease the abc gods def list(self): pass python-troveclient-1.0.3/troveclient/v1/hosts.py0000664000175300017540000000450212232556651023133 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack Foundation # Copyright 2013 Rackspace Hosting # 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 troveclient import base from troveclient.common import check_for_exceptions class Host(base.Resource): """ A Hosts is an opaque instance used to store Host instances. """ def __repr__(self): return "" % self.name class Hosts(base.ManagerWithFind): """ Manage :class:`Host` resources. """ resource_class = Host def _list(self, url, response_key): resp, body = self.api.client.get(url) if not body: raise Exception("Call to " + url + " did not return a body.") return [self.resource_class(self, res) for res in body[response_key]] def _action(self, host_id, body): """ Perform a host "action" -- update """ url = "/mgmt/hosts/%s/instances/action" % host_id resp, body = self.api.client.post(url, body=body) check_for_exceptions(resp, body) def update_all(self, host_id): """ Update all instances on a host. """ body = {'update': ''} self._action(host_id, body) def index(self): """ Get a list of all hosts. :rtype: list of :class:`Hosts`. """ return self._list("/mgmt/hosts", "hosts") def get(self, host): """ Get a specific host. :rtype: :class:`host` """ return self._get("/mgmt/hosts/%s" % self._get_host_name(host), "host") @staticmethod def _get_host_name(host): try: if host.name: return host.name except AttributeError: return host # Appease the abc gods def list(self): pass python-troveclient-1.0.3/troveclient/v1/limits.py0000664000175300017540000000315512232556651023277 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack Foundation # Copyright 2013 Rackspace Hosting # Copyright 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 troveclient import base from troveclient.openstack.common.apiclient import exceptions class Limit(base.Resource): def __repr__(self): return "" % self.verb class Limits(base.ManagerWithFind): """ Manages :class `Limit` resources """ resource_class = Limit def __repr__(self): return "" % id(self) def _list(self, url, response_key): resp, body = self.api.client.get(url) if resp is None or resp.status_code != 200: raise exceptions.from_response(resp, body) if not body: raise Exception("Call to " + url + " did not return a body.") return [self.resource_class(self, res) for res in body[response_key]] def list(self): """ Retrieve the limits """ return self._list("/limits", "limits") python-troveclient-1.0.3/troveclient/v1/backups.py0000664000175300017540000000440012232556651023420 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack Foundation # Copyright 2013 Rackspace Hosting # Copyright 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 troveclient import base from troveclient.openstack.common.apiclient import exceptions class Backup(base.Resource): """ Backup is a resource used to hold backup information. """ def __repr__(self): return "" % self.name class Backups(base.ManagerWithFind): """ Manage :class:`Backups` information. """ resource_class = Backup def get(self, backup): """ Get a specific backup. :rtype: :class:`Backups` """ return self._get("/backups/%s" % base.getid(backup), "backup") def list(self, limit=None, marker=None): """ Get a list of all backups. :rtype: list of :class:`Backups`. """ return self._list("/backups", "backups", limit, marker) def create(self, name, instance, description=None): """ Create a new backup from the given instance. """ body = { "backup": { "name": name, "instance": instance } } if description: body['backup']['description'] = description return self._create("/backups", body, "backup") def delete(self, backup_id): """ Delete the specified backup. :param backup_id: The backup id to delete """ resp, body = self.api.client.delete("/backups/%s" % backup_id) if resp.status_code in (422, 500): raise exceptions.from_response(resp, body) python-troveclient-1.0.3/troveclient/v1/storage.py0000664000175300017540000000302112232556651023432 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack Foundation # Copyright 2013 Rackspace Hosting # 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 troveclient import base class Device(base.Resource): """ Storage is an opaque instance used to hold storage information. """ def __repr__(self): return "" % self.name class StorageInfo(base.ManagerWithFind): """ Manage :class:`Storage` resources. """ resource_class = Device def _list(self, url, response_key): resp, body = self.api.client.get(url) if not body: raise Exception("Call to " + url + " did not return a body.") return [self.resource_class(self, res) for res in body[response_key]] def index(self): """ Get a list of all storages. :rtype: list of :class:`Storages`. """ return self._list("/mgmt/storage", "devices") # Appease the abc gods def list(self): pass python-troveclient-1.0.3/troveclient/v1/management.py0000664000175300017540000001322412232556651024110 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack Foundation # Copyright 2013 Rackspace Hosting # 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 troveclient import base from troveclient.common import check_for_exceptions from troveclient.common import limit_url from troveclient.common import Paginated from troveclient.openstack.common.py3kcompat import urlutils from troveclient.v1.instances import Instance from troveclient.v1.flavors import Flavor class RootHistory(base.Resource): def __repr__(self): return ("" % (self.id, self.created, self.user)) class Management(base.ManagerWithFind): """ Manage :class:`Instances` resources. """ resource_class = Instance # Appease the abc gods def list(self): pass def _list(self, url, response_key, limit=None, marker=None): resp, body = self.api.client.get(limit_url(url, limit, marker)) if not body: raise Exception("Call to " + url + " did not return a body.") links = body.get('links', []) next_links = [link['href'] for link in links if link['rel'] == 'next'] next_marker = None for link in next_links: # Extract the marker from the url. parsed_url = urlutils.urlparse(link) query_dict = dict(urlutils.parse_qsl(parsed_url.query)) next_marker = query_dict.get('marker', None) instances = body[response_key] instances = [self.resource_class(self, res) for res in instances] return Paginated(instances, next_marker=next_marker, links=links) def show(self, instance): """ Get details of one instance. :rtype: :class:`Instance`. """ return self._get("/mgmt/instances/%s" % base.getid(instance), 'instance') def index(self, deleted=None, limit=None, marker=None): """ Show an overview of all local instances. Optionally, filter by deleted status. :rtype: list of :class:`Instance`. """ form = '' if deleted is not None: if deleted: form = "?deleted=true" else: form = "?deleted=false" url = "/mgmt/instances%s" % form return self._list(url, "instances", limit, marker) def root_enabled_history(self, instance): """ Get root access history of one instance. """ url = "/mgmt/instances/%s/root" % base.getid(instance) resp, body = self.api.client.get(url) if not body: raise Exception("Call to " + url + " did not return a body.") return RootHistory(self, body['root_history']) def _action(self, instance_id, body): """ Perform a server "action" -- reboot/rebuild/resize/etc. """ url = "/mgmt/instances/%s/action" % instance_id resp, body = self.api.client.post(url, body=body) check_for_exceptions(resp, body) def stop(self, instance_id): body = {'stop': {}} self._action(instance_id, body) def reboot(self, instance_id): """ Reboot the underlying OS. :param instance_id: The :class:`Instance` (or its ID) to share onto. """ body = {'reboot': {}} self._action(instance_id, body) def migrate(self, instance_id, host=None): """ Migrate the instance. :param instance_id: The :class:`Instance` (or its ID) to share onto. """ if host: body = {'migrate': {'host': host}} else: body = {'migrate': {}} self._action(instance_id, body) def update(self, instance_id): """ Update the guest agent via apt-get. """ body = {'update': {}} self._action(instance_id, body) def reset_task_status(self, instance_id): """ Set the task status to NONE. """ body = {'reset-task-status': {}} self._action(instance_id, body) class MgmtFlavors(base.ManagerWithFind): """ Manage :class:`Flavor` resources. """ resource_class = Flavor def __repr__(self): return "" % id(self) # Appease the abc gods def list(self): pass def create(self, name, ram, disk, vcpus, flavorid="auto", ephemeral=None, swap=None, rxtx_factor=None, service_type=None): """ Create a new flavor. """ body = {"flavor": { "flavor_id": flavorid, "name": name, "ram": ram, "disk": disk, "vcpu": vcpus, "ephemeral": 0, "swap": 0, "rxtx_factor": "1.0", "is_public": "True" }} if ephemeral: body["flavor"]["ephemeral"] = ephemeral if swap: body["flavor"]["swap"] = swap if rxtx_factor: body["flavor"]["rxtx_factor"] = rxtx_factor if service_type: body["flavor"]["service_type"] = service_type return self._create("/mgmt/flavors", body, "flavor") python-troveclient-1.0.3/troveclient/v1/client.py0000664000175300017540000001067512232556654023264 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack Foundation # Copyright 2013 Rackspace Hosting # 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 troveclient import client from troveclient.v1.databases import Databases from troveclient.v1.flavors import Flavors from troveclient.v1.instances import Instances from troveclient.v1.limits import Limits from troveclient.v1.users import Users from troveclient.v1.root import Root from troveclient.v1.hosts import Hosts from troveclient.v1.quota import Quotas from troveclient.v1.backups import Backups from troveclient.v1.security_groups import SecurityGroups from troveclient.v1.security_groups import SecurityGroupRules from troveclient.v1.storage import StorageInfo from troveclient.v1.management import Management from troveclient.v1.management import MgmtFlavors from troveclient.v1.accounts import Accounts from troveclient.v1.diagnostics import DiagnosticsInterrogator from troveclient.v1.diagnostics import HwInfoInterrogator class Client(object): """ Top-level object to access the OpenStack Database API. Create an instance with your creds:: >>> client = Client(USERNAME, PASSWORD, PROJECT_ID, AUTH_URL) Then call methods on its managers:: >>> client.instances.list() ... """ def __init__(self, username, password, 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='database', service_name=None, database_service_name=None, retries=None, http_log_debug=False, cacert=None, bypass_url=None): # self.limits = limits.LimitsManager(self) # extensions self.flavors = Flavors(self) self.users = Users(self) self.databases = Databases(self) self.backups = Backups(self) self.instances = Instances(self) self.limits = Limits(self) self.root = Root(self) self.security_group_rules = SecurityGroupRules(self) self.security_groups = SecurityGroups(self) #self.hosts = Hosts(self) #self.quota = Quotas(self) #self.storage = StorageInfo(self) #self.management = Management(self) #self.mgmt_flavor = MgmtFlavors(self) #self.accounts = Accounts(self) #self.diagnostics = DiagnosticsInterrogator(self) #self.hwinfo = HwInfoInterrogator(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, database_service_name=database_service_name, retries=retries, http_log_debug=http_log_debug, cacert=cacert, bypass_url=bypass_url) 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_database_api_version_from_endpoint(self): return self.client.get_database_api_version_from_endpoint() python-troveclient-1.0.3/troveclient/v1/__init__.py0000664000175300017540000000131312232556651023527 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack Foundation # Copyright 2013 Rackspace Hosting # 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. python-troveclient-1.0.3/troveclient/v1/instances.py0000664000175300017540000001314212232556651023762 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack Foundation # Copyright 2013 Rackspace Hosting # Copyright 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 troveclient import base from troveclient.common import check_for_exceptions from troveclient.common import limit_url from troveclient.common import Paginated from troveclient.openstack.common.apiclient import exceptions from troveclient.openstack.common.py3kcompat import urlutils REBOOT_SOFT, REBOOT_HARD = 'SOFT', 'HARD' class Instance(base.Resource): """ An Instance is an opaque instance used to store Database instances. """ def __repr__(self): return "" % self.name def list_databases(self): return self.manager.databases.list(self) def delete(self): """ Delete the instance. """ self.manager.delete(self) def restart(self): """ Restart the database instance """ self.manager.restart(self.id) class Instances(base.ManagerWithFind): """ Manage :class:`Instance` resources. """ resource_class = Instance def create(self, name, flavor_id, volume=None, databases=None, users=None, restorePoint=None, availability_zone=None): """ Create (boot) a new instance. """ body = {"instance": { "name": name, "flavorRef": flavor_id }} if volume: body["instance"]["volume"] = volume if databases: body["instance"]["databases"] = databases if users: body["instance"]["users"] = users if restorePoint: body["instance"]["restorePoint"] = restorePoint if availability_zone: body["instance"]["availability_zone"] = availability_zone return self._create("/instances", body, "instance") def _list(self, url, response_key, limit=None, marker=None): resp, body = self.api.client.get(limit_url(url, limit, marker)) if not body: raise Exception("Call to " + url + " did not return a body.") links = body.get('links', []) next_links = [link['href'] for link in links if link['rel'] == 'next'] next_marker = None for link in next_links: # Extract the marker from the url. parsed_url = urlutils.urlparse(link) query_dict = dict(urlutils.parse_qsl(parsed_url.query)) next_marker = query_dict.get('marker', None) instances = body[response_key] instances = [self.resource_class(self, res) for res in instances] return Paginated(instances, next_marker=next_marker, links=links) def list(self, limit=None, marker=None): """ Get a list of all instances. :rtype: list of :class:`Instance`. """ return self._list("/instances", "instances", limit, marker) def get(self, instance): """ Get a specific instances. :rtype: :class:`Instance` """ return self._get("/instances/%s" % base.getid(instance), "instance") def backups(self, instance): """ Get the list of backups for a specific instance. :rtype: list of :class:`Backups`. """ return self._list("/instances/%s/backups" % base.getid(instance), "backups") def delete(self, instance): """ Delete the specified instance. :param instance_id: The instance id to delete """ resp, body = self.api.client.delete("/instances/%s" % base.getid(instance)) if resp.status_code in (422, 500): raise exceptions.from_response(resp, body) def _action(self, instance_id, body): """ Perform a server "action" -- reboot/rebuild/resize/etc. """ url = "/instances/%s/action" % instance_id resp, body = self.api.client.post(url, body=body) check_for_exceptions(resp, body) if body: return self.resource_class(self, body, loaded=True) return body def resize_volume(self, instance_id, volume_size): """ Resize the volume on an existing instances """ body = {"resize": {"volume": {"size": volume_size}}} self._action(instance_id, body) def resize_instance(self, instance_id, flavor_id): """ Resize the volume on an existing instances """ body = {"resize": {"flavorRef": flavor_id}} self._action(instance_id, body) def restart(self, instance_id): """ Restart the database instance. :param instance_id: The :class:`Instance` (or its ID) to share onto. """ body = {'restart': {}} self._action(instance_id, body) Instances.resize_flavor = Instances.resize_instance class InstanceStatus(object): ACTIVE = "ACTIVE" BLOCKED = "BLOCKED" BUILD = "BUILD" FAILED = "FAILED" REBOOT = "REBOOT" RESIZE = "RESIZE" SHUTDOWN = "SHUTDOWN" python-troveclient-1.0.3/troveclient/v1/quota.py0000664000175300017540000000370712232556651023132 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack Foundation # Copyright 2013 Rackspace Hosting # Copyright 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 troveclient import base from troveclient.common import check_for_exceptions class Quotas(base.ManagerWithFind): """ Manage :class:`Quota` information. """ resource_class = base.Resource def show(self, tenant_id): """Get a list of all quotas for a tenant id""" url = "/mgmt/quotas/%s" % tenant_id resp, body = self.api.client.get(url) check_for_exceptions(resp, body) if not body: raise Exception("Call to " + url + " did not return a body.") if 'quotas' not in body: raise Exception("Missing key value 'quotas' in response body.") return body['quotas'] def update(self, id, quotas): """ Set limits for quotas """ url = "/mgmt/quotas/%s" % id body = {"quotas": quotas} resp, body = self.api.client.put(url, body=body) check_for_exceptions(resp, body) if not body: raise Exception("Call to " + url + " did not return a body.") if 'quotas' not in body: raise Exception("Missing key value 'quotas' in response body.") return body['quotas'] # Appease the abc gods def list(self): pass python-troveclient-1.0.3/troveclient/v1/flavors.py0000664000175300017540000000263612232556651023455 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack Foundation # Copyright 2013 Rackspace Hosting # 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 troveclient import base class Flavor(base.Resource): """ A Flavor is an Instance type, specifying among other things, RAM size. """ def __repr__(self): return "" % self.name class Flavors(base.ManagerWithFind): """ Manage :class:`Flavor` resources. """ resource_class = Flavor def list(self): """ Get a list of all flavors. :rtype: list of :class:`Flavor`. """ return self._list("/flavors", "flavors") def get(self, flavor): """ Get a specific flavor. :rtype: :class:`Flavor` """ return self._get("/flavors/%s" % base.getid(flavor), "flavor") python-troveclient-1.0.3/troveclient/v1/diagnostics.py0000664000175300017540000000347412232556651024311 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack Foundation # Copyright 2013 Rackspace Hosting # 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 troveclient import base class Diagnostics(base.Resource): """ Account is an opaque instance used to hold account information. """ def __repr__(self): return "" % self.version class DiagnosticsInterrogator(base.ManagerWithFind): """ Manager class for Interrogator resource """ resource_class = Diagnostics def get(self, instance): """ Get the diagnostics of the guest on the instance. """ return self._get("/mgmt/instances/%s/diagnostics" % base.getid(instance), "diagnostics") # Appease the abc gods def list(self): pass class HwInfo(base.Resource): def __repr__(self): return "" % self.version class HwInfoInterrogator(base.ManagerWithFind): """ Manager class for HwInfo """ resource_class = HwInfo def get(self, instance): """ Get the hardware information of the instance. """ return self._get("/mgmt/instances/%s/hwinfo" % base.getid(instance)) # Appease the abc gods def list(self): pass python-troveclient-1.0.3/troveclient/v1/root.py0000664000175300017540000000320312232556651022753 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack Foundation # Copyright 2013 Rackspace Hosting # 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 troveclient import base from troveclient.v1 import users from troveclient.common import check_for_exceptions class Root(base.ManagerWithFind): """ Manager class for Root resource """ resource_class = users.User url = "/instances/%s/root" def create(self, instance_id): """ Enable the root user and return the root password for the sepcified db instance """ resp, body = self.api.client.post(self.url % instance_id) check_for_exceptions(resp, body) return body['user']['name'], body['user']['password'] def is_root_enabled(self, instance_id): """ Return True if root is enabled for the instance; False otherwise""" resp, body = self.api.client.get(self.url % instance_id) check_for_exceptions(resp, body) return self.resource_class(self, body, loaded=True) # Appease the abc gods def list(self): pass python-troveclient-1.0.3/troveclient/v1/shell.py0000664000175300017540000004227112232556651023107 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack Foundation # Copyright 2013 Rackspace Hosting # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from __future__ import print_function import sys import time from troveclient import utils 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 _print_instance(instance): # Get rid of those ugly links if instance._info.get('links'): del(instance._info['links']) utils.print_dict(instance._info) def _find_instance(cs, instance): """Get a instance by ID.""" return utils.find_resource(cs.instances, instance) def _find_flavor(cs, flavor): """Get a flavor by ID.""" return utils.find_resource(cs.flavors, flavor) def _find_backup(cs, backup): """Gets a backup by ID.""" return utils.find_resource(cs.backups, backup) # Flavor related calls @utils.service_type('database') def do_flavor_list(cs, args): """Lists available flavors.""" flavors = cs.flavors.list() utils.print_list(flavors, ['id', 'name', 'ram']) @utils.arg('flavor', metavar='', help='ID of the flavor.') @utils.service_type('database') def do_flavor_show(cs, args): """Show details of a flavor.""" flavor = _find_flavor(cs, args.flavor) _print_instance(flavor) # Instance related calls @utils.service_type('database') def do_list(cs, args): """List all the instances.""" instances = cs.instances.list() for instance in instances: setattr(instance, 'flavor_id', instance.flavor['id']) if hasattr(instance, 'volume'): setattr(instance, 'size', instance.volume['size']) utils.print_list(instances, ['id', 'name', 'status', 'flavor_id', 'size']) @utils.arg('instance', metavar='', help='ID of the instance.') @utils.service_type('database') def do_show(cs, args): """Show details of an instance.""" instance = _find_instance(cs, args.instance) instance._info['flavor'] = instance.flavor['id'] if hasattr(instance, 'volume'): instance._info['volume'] = instance.volume['size'] _print_instance(instance) @utils.arg('instance', metavar='', help='ID of the instance.') @utils.service_type('database') def do_delete(cs, args): """Deletes an instance.""" cs.instances.delete(args.instance) @utils.arg('name', metavar='', type=str, help='Name of the instance') @utils.arg('--size', metavar='', type=int, default=None, help='Size of the instance disk in GB') @utils.arg('flavor_id', metavar='', help='Flavor of the instance') @utils.arg('--databases', metavar='', help='Optional list of databases.', nargs="+", default=[]) @utils.arg('--users', metavar='', help='Optional list of users in the form user:password.', nargs="+", default=[]) @utils.arg('--backup', metavar='', default=None, help='A backup UUID') @utils.arg('--availability_zone', metavar='', default=None, help='The Zone hint to give to nova') @utils.service_type('database') def do_create(cs, args): """Creates a new instance.""" volume = None if args.size: volume = {"size": args.size} restore_point = None if args.backup: restore_point = {"backupRef": args.backup} databases = [{'name': value} for value in args.databases] users = [{'name': n, 'password': p} for (n, p) in [z.split(':')[:2] for z in args.users]] instance = cs.instances.create(args.name, args.flavor_id, volume=volume, databases=databases, users=users, restorePoint=restore_point, availability_zone=args.availability_zone) instance._info['flavor'] = instance.flavor['id'] if hasattr(instance, 'volume'): instance._info['volume'] = instance.volume['size'] del(instance._info['links']) _print_instance(instance) @utils.arg('instance', metavar='', type=str, help='UUID of the instance') @utils.arg('flavor_id', metavar='', help='Flavor of the instance') @utils.service_type('database') def do_resize_flavor(cs, args): """Resizes the flavor of an instance.""" cs.instances.resize_flavor(args.instance, args.flavor_id) @utils.arg('instance', metavar='', type=str, help='UUID of the instance') @utils.arg('size', metavar='', type=int, default=None, help='Size of the instance disk in GB') @utils.service_type('database') def do_resize_volume(cs, args): """Resizes the volume size of an instance.""" cs.instances.resize_volume(args.instance, args.size) @utils.arg('instance', metavar='', type=str, help='UUID of the instance') @utils.service_type('database') def do_restart(cs, args): """Restarts the instance.""" cs.instances.restart(args.instance) # Backup related commands @utils.arg('backup', metavar='', help='ID of the backup.') @utils.service_type('database') def do_backup_show(cs, args): """Show details of a backup.""" backup = _find_backup(cs, args.backup) _print_instance(backup) @utils.arg('instance', metavar='', help='ID of the instance.') @utils.service_type('database') def do_backup_list_instance(cs, args): """List available backups for an instance.""" backups = cs.instances.backups(args.instance) utils.print_list(backups, ['id', 'instance_id', 'name', 'description', 'status']) @utils.service_type('database') def do_backup_list(cs, args): """List available backups.""" backups = cs.backups.list() utils.print_list(backups, ['id', 'instance_id', 'name', 'description', 'status']) @utils.arg('backup', metavar='', help='ID of the backup.') @utils.service_type('database') def do_backup_delete(cs, args): """Deletes a backup.""" cs.backups.delete(args.backup) @utils.arg('name', metavar='', help='Name of the backup.') @utils.arg('instance', metavar='', help='UUID of the instance.') @utils.arg('--description', metavar='', default=None, help='An optional description for the backup.') @utils.service_type('database') def do_backup_create(cs, args): """Creates a backup.""" backup = cs.backups.create(args.name, args.instance, description=args.description) _print_instance(backup) # Database related actions @utils.arg('instance', metavar='', help='UUID of the instance.') @utils.arg('name', metavar='', help='Name of the backup.') @utils.arg('--character_set', metavar='', default=None, help='Optional character set for database') @utils.arg('--collate', metavar='', default=None, help='Optional collation type for database') @utils.service_type('database') def do_database_create(cs, args): """Creates a database on an instance.""" database_dict = {'name': args.name} if args.collate: database_dict['collate'] = args.collate if args.character_set: database_dict['character_set'] = args.character_set cs.databases.create(args.instance, [database_dict]) @utils.arg('instance', metavar='', help='UUID of the instance.') @utils.service_type('database') def do_database_list(cs, args): """Lists available databases on an instance.""" wrapper = cs.databases.list(args.instance) databases = wrapper.items while (wrapper.next): wrapper = cs.databases.list(args.instance, marker=wrapper.next) databases = wrapper.items utils.print_list(databases, ['name']) @utils.arg('instance', metavar='', help='UUID of the instance.') @utils.arg('database', metavar='', help='Name of the database.') @utils.service_type('database') def do_database_delete(cs, args): """Deletes a database.""" cs.databases.delete(args.instance, args.database) # User related actions @utils.arg('instance', metavar='', help='UUID of the instance.') @utils.arg('name', metavar='', help='Name of user') @utils.arg('password', metavar='', help='Password of user') @utils.arg('--host', metavar='', default=None, help='Optional host of user') @utils.arg('--databases', metavar='', help='Optional list of databases.', nargs="+", default=[]) @utils.service_type('database') def do_user_create(cs, args): """Creates a user.""" databases = [{'name': value} for value in args.databases] user = {'name': args.name, 'password': args.password, 'databases': databases} if args.host: user['host'] = args.host cs.users.create(args.instance, [user]) @utils.arg('instance', metavar='', help='UUID of the instance.') @utils.service_type('database') def do_user_list(cs, args): """Lists the users for a instance.""" wrapper = cs.users.list(args.instance) users = wrapper.items while (wrapper.next): wrapper = cs.users.list(args.instance, marker=wrapper.next) users += wrapper.items utils.print_list(users, ['name', 'host', 'databases']) @utils.arg('instance', metavar='', help='UUID of the instance.') @utils.arg('name', metavar='', help='Name of user') @utils.arg('--host', metavar='', default=None, help='Optional host of user') @utils.service_type('database') def do_user_delete(cs, args): """Deletes a user from the instance.""" cs.users.delete(args.instance, args.name, hostname=args.host) @utils.arg('instance', metavar='', help='UUID of the instance.') @utils.arg('name', metavar='', help='Name of user') @utils.arg('--host', metavar='', default=None, help='Optional host of user') @utils.service_type('database') # Quoting is not working now that we arent using httplib2 # anymore and instead are using requests def do_user_show(cs, args): """Gets a user from the instance.""" user = cs.users.get(args.instance, args.name, hostname=args.host) _print_instance(user) @utils.arg('instance', metavar='', help='UUID of the instance.') @utils.arg('name', metavar='', help='Name of user') @utils.arg('--host', metavar='', default=None, help='Optional host of user') @utils.service_type('database') # Quoting is not working now that we arent using httplib2 # anymore and instead are using requests def do_user_show_access(cs, args): """Gets a users access from the instance.""" access = cs.users.list_access(args.instance, args.name, hostname=args.host) utils.print_list(access, ['name']) @utils.arg('instance', metavar='', help='UUID of the instance.') @utils.arg('name', metavar='', help='Name of user') @utils.arg('--host', metavar='', default=None, help='Optional host of user') @utils.arg('--new_name', metavar='', default=None, help='Optional new name of user') @utils.arg('--new_password', metavar='', default=None, help='Optional new password of user') @utils.arg('--new_host', metavar='', default=None, help='Optional new host of user') @utils.service_type('database') # Quoting is not working now that we arent using httplib2 # anymore and instead are using requests def do_user_update_attributes(cs, args): """Updates a users attributes from the instance.""" new_attrs = {} if args.new_name: new_attrs['name'] = args.new_name if args.new_password: new_attrs['password'] = args.new_password if args.new_host: new_attrs['host'] = args.new_host cs.users.update_attributes(args.instance, args.name, newuserattr=new_attrs, hostname=args.host) @utils.arg('instance', metavar='', help='UUID of the instance.') @utils.arg('name', metavar='', help='Name of user') @utils.arg('--host', metavar='', default=None, help='Optional host of user') @utils.arg('databases', metavar='', help='List of databases.', nargs="+", default=[]) @utils.service_type('database') def do_user_grant_access(cs, args): """Grants access to a database(s) for a user.""" cs.users.grant(args.instance, args.name, args.databases, hostname=args.host) @utils.arg('instance', metavar='', help='UUID of the instance.') @utils.arg('name', metavar='', help='Name of user') @utils.arg('database', metavar='', help='A single database.') @utils.arg('--host', metavar='', default=None, help='Optional host of user') @utils.service_type('database') def do_user_revoke_access(cs, args): """Revokes access to a database for a user.""" cs.users.revoke(args.instance, args.name, args.database, hostname=args.host) # Limits related commands @utils.service_type('database') def do_limit_list(cs, args): """Lists the limits for a tenant.""" limits = cs.limits.list() # Pop the first one, its absolute limits absolute = limits.pop(0) _print_instance(absolute) utils.print_list(limits, ['value', 'verb', 'remaining', 'unit']) # Root related commands @utils.arg('instance', metavar='', help='UUID of the instance.') @utils.service_type('database') def do_root_enable(cs, args): """Enables root for a instance.""" root = cs.root.create(args.instance) utils.print_dict({'name': root[0], 'password': root[1]}) @utils.arg('instance', metavar='', help='UUID of the instance.') @utils.service_type('database') def do_root_show(cs, args): """Gets root enabled status for a instance.""" root = cs.root.is_root_enabled(args.instance) utils.print_dict({'is_root_enabled': root.rootEnabled}) # security group related functions @utils.service_type('database') def do_secgroup_list(cs, args): """Lists all security groups.""" wrapper = cs.security_groups.list() sec_grps = wrapper.items while (wrapper.next): wrapper = cs.security_groups.list() sec_grps += wrapper.items utils.print_list(sec_grps, ['id', 'name', 'rules', 'instance_id']) @utils.arg('security_group', metavar='', help='ID of the security group.') @utils.service_type('database') def do_secgroup_show(cs, args): """Shows details about a security group.""" sec_grp = cs.security_groups.get(args.security_group) _print_instance(sec_grp) @utils.arg('security_group', metavar='', help='Security group name') @utils.arg('protocol', metavar='', help='Protocol') @utils.arg('from_port', metavar='', help='from port') @utils.arg('to_port', metavar='', help='to port') @utils.arg('cidr', metavar='', help='CIDR address') @utils.service_type('database') def do_secgroup_add_rule(cs, args): """Creates a security group rule.""" rule = cs.security_group_rules.create(args.security_group, args.protocol, args.from_port, args.to_port, args.cidr) _print_instance(rule) @utils.arg('security_group_rule', metavar='', help='Security group rule') @utils.service_type('database') def do_secgroup_delete_rule(cs, args): """Deletes a security group rule.""" cs.security_group_rules.delete(args.security_group_rule) python-troveclient-1.0.3/troveclient/v1/users.py0000664000175300017540000001342612232556651023141 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack Foundation # Copyright 2013 Rackspace Hosting # 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 troveclient import base from troveclient.v1 import databases from troveclient.common import check_for_exceptions from troveclient.common import limit_url from troveclient.common import Paginated from troveclient.common import quote_user_host from troveclient.openstack.common.py3kcompat import urlutils class User(base.Resource): """ A database user """ def __repr__(self): return "" % self.name class Users(base.ManagerWithFind): """ Manage :class:`Users` resources. """ resource_class = User def create(self, instance_id, users): """ Create users with permissions to the specified databases """ body = {"users": users} url = "/instances/%s/users" % instance_id resp, body = self.api.client.post(url, body=body) check_for_exceptions(resp, body) def delete(self, instance_id, username, hostname=None): """Delete an existing user in the specified instance""" user = quote_user_host(username, hostname) url = "/instances/%s/users/%s" % (instance_id, user) resp, body = self.api.client.delete(url) check_for_exceptions(resp, body) def _list(self, url, response_key, limit=None, marker=None): resp, body = self.api.client.get(limit_url(url, limit, marker)) check_for_exceptions(resp, body) if not body: raise Exception("Call to " + url + " did not return a body.") links = body.get('links', []) next_links = [link['href'] for link in links if link['rel'] == 'next'] next_marker = None for link in next_links: # Extract the marker from the url. parsed_url = urlutils.urlparse(link) query_dict = dict(urlutils.parse_qsl(parsed_url.query)) next_marker = query_dict.get('marker', None) users = [self.resource_class(self, res) for res in body[response_key]] return Paginated(users, next_marker=next_marker, links=links) def list(self, instance, limit=None, marker=None): """ Get a list of all Users from the instance's Database. :rtype: list of :class:`User`. """ return self._list("/instances/%s/users" % base.getid(instance), "users", limit, marker) def get(self, instance_id, username, hostname=None): """ Get a single User from the instance's Database. :rtype: :class:`User`. """ user = quote_user_host(username, hostname) url = "/instances/%s/users/%s" % (instance_id, user) return self._get(url, "user") def update_attributes(self, instance, username, newuserattr=None, hostname=None): """ Update attributes of a single User in an instance. :rtype: :class:`User`. """ instance_id = base.getid(instance) user = quote_user_host(username, hostname) user_dict = {} if not newuserattr: newuserattr = {} else: user_dict['user'] = newuserattr url = "/instances/%s/users/%s" % (instance_id, user) resp, body = self.api.client.put(url, body=user_dict) check_for_exceptions(resp, body) def list_access(self, instance, username, hostname=None): """Show all databases the given user has access to. """ instance_id = base.getid(instance) user = quote_user_host(username, hostname) url = "/instances/%(instance_id)s/users/%(user)s/databases" local_vars = locals() resp, body = self.api.client.get(url % local_vars) check_for_exceptions(resp, body) if not body: raise Exception("Call to %s did not return to a body" % url) return [databases.Database(self, db) for db in body['databases']] def grant(self, instance, username, databases, hostname=None): """Allow an existing user permissions to access a database.""" instance_id = base.getid(instance) user = quote_user_host(username, hostname) url = "/instances/%(instance_id)s/users/%(user)s/databases" dbs = {'databases': [{'name': db} for db in databases]} local_vars = locals() resp, body = self.api.client.put(url % local_vars, body=dbs) check_for_exceptions(resp, body) def revoke(self, instance, username, database, hostname=None): """Revoke from an existing user access permissions to a database.""" instance_id = base.getid(instance) user = quote_user_host(username, hostname) url = ("/instances/%(instance_id)s/users/%(user)s/" "databases/%(database)s") local_vars = locals() resp, body = self.api.client.delete(url % local_vars) check_for_exceptions(resp, body) def change_passwords(self, instance, users): """Change the password for one or more users.""" instance_id = base.getid(instance) user_dict = {"users": users} url = "/instances/%s/users" % instance_id resp, body = self.api.client.put(url, body=user_dict) check_for_exceptions(resp, body) python-troveclient-1.0.3/troveclient/v1/accounts.py0000664000175300017540000000422312232556651023612 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack Foundation # Copyright 2013 Rackspace Hosting # 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 troveclient import base from troveclient.common import check_for_exceptions class Account(base.Resource): """ Account is an opaque instance used to hold account information. """ def __repr__(self): return "" % self.name class Accounts(base.ManagerWithFind): """ Manage :class:`Account` information. """ resource_class = Account def _list(self, url, response_key): resp, body = self.api.client.get(url) if not body: raise Exception("Call to " + url + " did not return a body.") return self.resource_class(self, body[response_key]) def index(self): """Get a list of all accounts with non-deleted instances""" url = "/mgmt/accounts" resp, body = self.api.client.get(url) check_for_exceptions(resp, body) if not body: raise Exception("Call to " + url + " did not return a body.") return base.Resource(self, body) def show(self, account): """ Get details of one account. :rtype: :class:`Account`. """ acct_name = self._get_account_name(account) return self._list("/mgmt/accounts/%s" % acct_name, 'account') # Appease the abc gods def list(self): pass @staticmethod def _get_account_name(account): try: if account.name: return account.name except AttributeError: return account python-troveclient-1.0.3/troveclient/tests/0000775000175300017540000000000012232557034022230 5ustar jenkinsjenkins00000000000000python-troveclient-1.0.3/troveclient/tests/test_limits.py0000664000175300017540000000646512232556651025161 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack Foundation # Copyright 2013 Rackspace Hosting # Copyright 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 testtools import TestCase from mock import Mock from troveclient.v1 import limits class LimitsTest(TestCase): """ This class tests the calling code for the Limits API """ def setUp(self): super(LimitsTest, self).setUp() self.limits = limits.Limits(Mock()) self.limits.api.client = Mock() def tearDown(self): super(LimitsTest, self).tearDown() def test_list(self): resp = Mock() resp.status_code = 200 body = {"limits": [ {'maxTotalInstances': 55, 'verb': 'ABSOLUTE', 'maxTotalVolumes': 100}, {'regex': '.*', 'nextAvailable': '2011-07-21T18:17:06Z', 'uri': '*', 'value': 10, 'verb': 'POST', 'remaining': 2, 'unit': 'MINUTE'}, {'regex': '.*', 'nextAvailable': '2011-07-21T18:17:06Z', 'uri': '*', 'value': 10, 'verb': 'PUT', 'remaining': 2, 'unit': 'MINUTE'}, {'regex': '.*', 'nextAvailable': '2011-07-21T18:17:06Z', 'uri': '*', 'value': 10, 'verb': 'DELETE', 'remaining': 2, 'unit': 'MINUTE'}, {'regex': '.*', 'nextAvailable': '2011-07-21T18:17:06Z', 'uri': '*', 'value': 10, 'verb': 'GET', 'remaining': 2, 'unit': 'MINUTE'}]} response = (resp, body) mock_get = Mock(return_value=response) self.limits.api.client.get = mock_get self.assertIsNotNone(self.limits.list()) mock_get.assert_called_once_with("/limits") def test_list_errors(self): status_list = [400, 401, 403, 404, 408, 409, 413, 500, 501] for status_code in status_list: self._check_error_response(status_code) def _check_error_response(self, status_code): RESPONSE_KEY = "limits" resp = Mock() resp.status_code = status_code body = {RESPONSE_KEY: { 'absolute': {}, 'rate': [ {'limit': [] }]}} response = (resp, body) mock_get = Mock(return_value=response) self.limits.api.client.get = mock_get self.assertRaises(Exception, self.limits.list) python-troveclient-1.0.3/troveclient/tests/test_instances.py0000664000175300017540000001604612232556651025643 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack Foundation # Copyright 2013 Rackspace Hosting # Copyright 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 testtools import TestCase from mock import Mock from troveclient.v1 import instances from troveclient import base """ Unit tests for instances.py """ class InstanceTest(TestCase): def setUp(self): super(InstanceTest, self).setUp() self.orig__init = instances.Instance.__init__ instances.Instance.__init__ = Mock(return_value=None) self.instance = instances.Instance() self.instance.manager = Mock() def tearDown(self): super(InstanceTest, self).tearDown() instances.Instance.__init__ = self.orig__init def test___repr__(self): self.instance.name = "instance-1" self.assertEqual('', self.instance.__repr__()) def test_list_databases(self): db_list = ['database1', 'database2'] self.instance.manager.databases = Mock() self.instance.manager.databases.list = Mock(return_value=db_list) self.assertEqual(db_list, self.instance.list_databases()) def test_delete(self): db_delete_mock = Mock(return_value=None) self.instance.manager.delete = db_delete_mock self.instance.delete() self.assertEqual(1, db_delete_mock.call_count) def test_restart(self): db_restart_mock = Mock(return_value=None) self.instance.manager.restart = db_restart_mock self.instance.id = 1 self.instance.restart() self.assertEqual(1, db_restart_mock.call_count) class InstancesTest(TestCase): def setUp(self): super(InstancesTest, self).setUp() self.orig__init = instances.Instances.__init__ instances.Instances.__init__ = Mock(return_value=None) self.instances = instances.Instances() self.instances.api = Mock() self.instances.api.client = Mock() self.instances.resource_class = Mock(return_value="instance-1") self.orig_base_getid = base.getid base.getid = Mock(return_value="instance1") def tearDown(self): super(InstancesTest, self).tearDown() instances.Instances.__init__ = self.orig__init base.getid = self.orig_base_getid def test_create(self): def side_effect_func(path, body, inst): return path, body, inst self.instances._create = Mock(side_effect=side_effect_func) p, b, i = self.instances.create("test-name", 103, "test-volume", ['db1', 'db2'], ['u1', 'u2']) self.assertEqual("/instances", p) self.assertEqual("instance", i) self.assertEqual(['db1', 'db2'], b["instance"]["databases"]) self.assertEqual(['u1', 'u2'], b["instance"]["users"]) self.assertEqual("test-name", b["instance"]["name"]) self.assertEqual("test-volume", b["instance"]["volume"]) self.assertEqual(103, b["instance"]["flavorRef"]) def test__list(self): self.instances.api.client.get = Mock(return_value=('resp', None)) self.assertRaises(Exception, self.instances._list, "url", None) body = Mock() body.get = Mock(return_value=[{'href': 'http://test.net/test_file', 'rel': 'next'}]) body.__getitem__ = Mock(return_value='instance1') #self.instances.resource_class = Mock(return_value="instance-1") self.instances.api.client.get = Mock(return_value=('resp', body)) _expected = [{'href': 'http://test.net/test_file', 'rel': 'next'}] self.assertEqual(_expected, self.instances._list("url", None).links) def test_list(self): def side_effect_func(path, inst, limit, marker): return path, inst, limit, marker self.instances._list = Mock(side_effect=side_effect_func) limit = "test-limit" marker = "test-marker" expected = ("/instances", "instances", limit, marker) self.assertEqual(expected, self.instances.list(limit, marker)) def test_get(self): def side_effect_func(path, inst): return path, inst self.instances._get = Mock(side_effect=side_effect_func) self.assertEqual(('/instances/instance1', 'instance'), self.instances.get(1)) def test_delete(self): resp = Mock() resp.status_code = 200 body = None self.instances.api.client.delete = Mock(return_value=(resp, body)) self.instances.delete('instance1') resp.status_code = 500 self.assertRaises(Exception, self.instances.delete, 'instance1') def test__action(self): body = Mock() resp = Mock() resp.status_code = 200 self.instances.api.client.post = Mock(return_value=(resp, body)) self.assertEqual('instance-1', self.instances._action(1, body)) self.instances.api.client.post = Mock(return_value=(resp, None)) self.assertEqual(None, self.instances._action(1, body)) def _set_action_mock(self): def side_effect_func(instance_id, body): self._instance_id = instance_id self._body = body self._instance_id = None self._body = None self.instances._action = Mock(side_effect=side_effect_func) def test_resize_volume(self): self._set_action_mock() self.instances.resize_volume(152, 512) self.assertEqual(152, self._instance_id) self.assertEqual({"resize": {"volume": {"size": 512}}}, self._body) def test_resize_instance(self): self._set_action_mock() self.instances.resize_instance(4725, 103) self.assertEqual(4725, self._instance_id) self.assertEqual({"resize": {"flavorRef": 103}}, self._body) def test_restart(self): self._set_action_mock() self.instances.restart(253) self.assertEqual(253, self._instance_id) self.assertEqual({'restart': {}}, self._body) class InstanceStatusTest(TestCase): def test_constants(self): self.assertEqual("ACTIVE", instances.InstanceStatus.ACTIVE) self.assertEqual("BLOCKED", instances.InstanceStatus.BLOCKED) self.assertEqual("BUILD", instances.InstanceStatus.BUILD) self.assertEqual("FAILED", instances.InstanceStatus.FAILED) self.assertEqual("REBOOT", instances.InstanceStatus.REBOOT) self.assertEqual("RESIZE", instances.InstanceStatus.RESIZE) self.assertEqual("SHUTDOWN", instances.InstanceStatus.SHUTDOWN) python-troveclient-1.0.3/troveclient/tests/test_users.py0000664000175300017540000001142112232556651025005 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack Foundation # Copyright 2013 Rackspace Hosting # Copyright 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 testtools import TestCase from mock import Mock from troveclient.v1 import users from troveclient import base """ Unit tests for users.py """ class UserTest(TestCase): def setUp(self): super(UserTest, self).setUp() self.orig__init = users.User.__init__ users.User.__init__ = Mock(return_value=None) self.user = users.User() def tearDown(self): super(UserTest, self).tearDown() users.User.__init__ = self.orig__init def test___repr__(self): self.user.name = "user-1" self.assertEqual('', self.user.__repr__()) class UsersTest(TestCase): def setUp(self): super(UsersTest, self).setUp() self.orig__init = users.Users.__init__ users.Users.__init__ = Mock(return_value=None) self.users = users.Users() self.users.api = Mock() self.users.api.client = Mock() self.orig_base_getid = base.getid base.getid = Mock(return_value="instance1") def tearDown(self): super(UsersTest, self).tearDown() users.Users.__init__ = self.orig__init base.getid = self.orig_base_getid def _get_mock_method(self): self._resp = Mock() self._body = None self._url = None def side_effect_func(url, body=None): self._body = body self._url = url return (self._resp, body) return Mock(side_effect=side_effect_func) def _build_fake_user(self, name, hostname=None, password=None, databases=None): return { 'name': name, 'password': password if password else 'password', 'host': hostname, 'databases': databases if databases else [], } def test_create(self): self.users.api.client.post = self._get_mock_method() self._resp.status_code = 200 user = self._build_fake_user('user1') self.users.create(23, [user]) self.assertEqual('/instances/23/users', self._url) self.assertEqual({"users": [user]}, self._body) # Even if host isn't supplied originally, # the default is supplied. del user['host'] self.users.create(23, [user]) self.assertEqual('/instances/23/users', self._url) user['host'] = '%' self.assertEqual({"users": [user]}, self._body) # If host is supplied, of course it's put into the body. user['host'] = '127.0.0.1' self.users.create(23, [user]) self.assertEqual({"users": [user]}, self._body) # Make sure that response of 400 is recognized as an error. user['host'] = '%' self._resp.status_code = 400 self.assertRaises(Exception, self.users.create, 12, [user]) def test_delete(self): self.users.api.client.delete = self._get_mock_method() self._resp.status_code = 200 self.users.delete(27, 'user1') self.assertEqual('/instances/27/users/user1', self._url) self._resp.status_code = 400 self.assertRaises(Exception, self.users.delete, 34, 'user1') def test__list(self): def side_effect_func(self, val): return val key = 'key' body = Mock() body.get = Mock(return_value=[{'href': 'http://test.net/test_file', 'rel': 'next'}]) body.__getitem__ = Mock(return_value=["test-value"]) resp = Mock() resp.status_code = 200 self.users.resource_class = Mock(side_effect=side_effect_func) self.users.api.client.get = Mock(return_value=(resp, body)) self.assertEqual(["test-value"], self.users._list('url', key).items) self.users.api.client.get = Mock(return_value=(resp, None)) self.assertRaises(Exception, self.users._list, 'url', None) def test_list(self): def side_effect_func(path, user, limit, marker): return path self.users._list = Mock(side_effect=side_effect_func) self.assertEqual('/instances/instance1/users', self.users.list(1)) python-troveclient-1.0.3/troveclient/tests/test_management.py0000664000175300017540000001647312232556651025774 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack Foundation # Copyright 2013 Rackspace Hosting # Copyright 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 testtools import TestCase from mock import Mock from troveclient.v1 import management from troveclient import base """ Unit tests for management.py """ class RootHistoryTest(TestCase): def setUp(self): super(RootHistoryTest, self).setUp() self.orig__init = management.RootHistory.__init__ management.RootHistory.__init__ = Mock(return_value=None) def tearDown(self): super(RootHistoryTest, self).tearDown() management.RootHistory.__init__ = self.orig__init def test___repr__(self): root_history = management.RootHistory() root_history.id = "1" root_history.created = "ct" root_history.user = "tu" self.assertEqual('', root_history.__repr__()) class ManagementTest(TestCase): def setUp(self): super(ManagementTest, self).setUp() self.orig__init = management.Management.__init__ management.Management.__init__ = Mock(return_value=None) self.management = management.Management() self.management.api = Mock() self.management.api.client = Mock() self.orig_hist__init = management.RootHistory.__init__ self.orig_base_getid = base.getid base.getid = Mock(return_value="instance1") def tearDown(self): super(ManagementTest, self).tearDown() management.Management.__init__ = self.orig__init management.RootHistory.__init__ = self.orig_hist__init base.getid = self.orig_base_getid def test__list(self): self.management.api.client.get = Mock(return_value=('resp', None)) self.assertRaises(Exception, self.management._list, "url", None) body = Mock() body.get = Mock(return_value=[{'href': 'http://test.net/test_file', 'rel': 'next'}]) body.__getitem__ = Mock(return_value='instance1') self.management.resource_class = Mock(return_value="instance-1") self.management.api.client.get = Mock(return_value=('resp', body)) _expected = [{'href': 'http://test.net/test_file', 'rel': 'next'}] self.assertEqual(_expected, self.management._list("url", None).links) def test_show(self): def side_effect_func(path, instance): return path, instance self.management._get = Mock(side_effect=side_effect_func) p, i = self.management.show(1) self.assertEqual(('/mgmt/instances/instance1', 'instance'), (p, i)) def test_index(self): def side_effect_func(url, name, limit, marker): return url self.management._list = Mock(side_effect=side_effect_func) self.assertEqual('/mgmt/instances?deleted=true', self.management.index(deleted=True)) self.assertEqual('/mgmt/instances?deleted=false', self.management.index(deleted=False)) def test_root_enabled_history(self): self.management.api.client.get = Mock(return_value=('resp', None)) self.assertRaises(Exception, self.management.root_enabled_history, "instance") body = {'root_history': 'rh'} self.management.api.client.get = Mock(return_value=('resp', body)) management.RootHistory.__init__ = Mock(return_value=None) rh = self.management.root_enabled_history("instance") self.assertTrue(isinstance(rh, management.RootHistory)) def test__action(self): resp = Mock() self.management.api.client.post = Mock(return_value=(resp, 'body')) resp.status_code = 200 self.management._action(1, 'body') self.assertEqual(1, self.management.api.client.post.call_count) resp.status_code = 400 self.assertRaises(Exception, self.management._action, 1, 'body') self.assertEqual(2, self.management.api.client.post.call_count) def _mock_action(self): self.body_ = "" def side_effect_func(instance_id, body): self.body_ = body self.management._action = Mock(side_effect=side_effect_func) def test_stop(self): self._mock_action() self.management.stop(1) self.assertEqual(1, self.management._action.call_count) self.assertEqual({'stop': {}}, self.body_) def test_reboot(self): self._mock_action() self.management.reboot(1) self.assertEqual(1, self.management._action.call_count) self.assertEqual({'reboot': {}}, self.body_) def test_migrate(self): self._mock_action() self.management.migrate(1) self.assertEqual(1, self.management._action.call_count) self.assertEqual({'migrate': {}}, self.body_) def test_migrate_to_host(self): hostname = 'hostname2' self._mock_action() self.management.migrate(1, host=hostname) self.assertEqual(1, self.management._action.call_count) self.assertEqual({'migrate': {'host': hostname}}, self.body_) def test_update(self): self._mock_action() self.management.update(1) self.assertEqual(1, self.management._action.call_count) self.assertEqual({'update': {}}, self.body_) def test_reset_task_status(self): self._mock_action() self.management.reset_task_status(1) self.assertEqual(1, self.management._action.call_count) self.assertEqual({'reset-task-status': {}}, self.body_) class MgmtFlavorsTest(TestCase): def setUp(self): super(MgmtFlavorsTest, self).setUp() self.orig__init = management.MgmtFlavors.__init__ management.MgmtFlavors.__init__ = Mock(return_value=None) self.flavors = management.MgmtFlavors() self.flavors.api = Mock() self.flavors.api.client = Mock() self.flavors.resource_class = Mock(return_value="flavor-1") self.orig_base_getid = base.getid base.getid = Mock(return_value="flavor1") def tearDown(self): super(MgmtFlavorsTest, self).tearDown() management.MgmtFlavors.__init__ = self.orig__init base.getid = self.orig_base_getid def test_create(self): def side_effect_func(path, body, inst): return path, body, inst self.flavors._create = Mock(side_effect=side_effect_func) p, b, i = self.flavors.create("test-name", 1024, 30, 2, 1) self.assertEqual("/mgmt/flavors", p) self.assertEqual("flavor", i) self.assertEqual("test-name", b["flavor"]["name"]) self.assertEqual(1024, b["flavor"]["ram"]) self.assertEqual(2, b["flavor"]["vcpu"]) self.assertEqual(1, b["flavor"]["flavor_id"]) python-troveclient-1.0.3/troveclient/tests/__init__.py0000664000175300017540000000134012232556651024343 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 Rackspace Hosting # Copyright 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. python-troveclient-1.0.3/troveclient/tests/test_accounts.py0000664000175300017540000000704112232556651025466 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack Foundation # Copyright 2013 Rackspace Hosting # Copyright 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 testtools import TestCase from mock import Mock from troveclient.v1 import accounts from troveclient import base """ Unit tests for accounts.py """ class AccountTest(TestCase): def setUp(self): super(AccountTest, self).setUp() self.orig__init = accounts.Account.__init__ accounts.Account.__init__ = Mock(return_value=None) self.account = accounts.Account() def tearDown(self): super(AccountTest, self).tearDown() accounts.Account.__init__ = self.orig__init def test___repr__(self): self.account.name = "account-1" self.assertEqual('', self.account.__repr__()) class AccountsTest(TestCase): def setUp(self): super(AccountsTest, self).setUp() self.orig__init = accounts.Accounts.__init__ accounts.Accounts.__init__ = Mock(return_value=None) self.accounts = accounts.Accounts() self.accounts.api = Mock() self.accounts.api.client = Mock() def tearDown(self): super(AccountsTest, self).tearDown() accounts.Accounts.__init__ = self.orig__init def test__list(self): def side_effect_func(self, val): return val self.accounts.resource_class = Mock(side_effect=side_effect_func) key_ = 'key' body_ = {key_: "test-value"} self.accounts.api.client.get = Mock(return_value=('resp', body_)) self.assertEqual("test-value", self.accounts._list('url', key_)) self.accounts.api.client.get = Mock(return_value=('resp', None)) self.assertRaises(Exception, self.accounts._list, 'url', None) def test_index(self): resp = Mock() resp.status_code = 400 body = {"Accounts": {}} self.accounts.api.client.get = Mock(return_value=(resp, body)) self.assertRaises(Exception, self.accounts.index) resp.status_code = 200 self.assertTrue(isinstance(self.accounts.index(), base.Resource)) self.accounts.api.client.get = Mock(return_value=(resp, None)) self.assertRaises(Exception, self.accounts.index) def test_show(self): def side_effect_func(acct_name, acct): return acct_name, acct account_ = Mock() account_.name = "test-account" self.accounts._list = Mock(side_effect=side_effect_func) self.assertEqual(('/mgmt/accounts/test-account', 'account'), self.accounts.show(account_)) def test__get_account_name(self): account_ = 'account with no name' self.assertEqual(account_, accounts.Accounts._get_account_name(account_)) account_ = Mock() account_.name = "account-name" self.assertEqual("account-name", accounts.Accounts._get_account_name(account_)) python-troveclient-1.0.3/troveclient/tests/test_client.py0000664000175300017540000000302112232556651025117 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack Foundation # Copyright 2013 Rackspace Hosting # Copyright 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 testtools import TestCase import troveclient.v1.client from troveclient.client import get_version_map from troveclient.openstack.common.apiclient import client from troveclient.openstack.common.apiclient import exceptions class ClientTest(TestCase): def test_get_client_class_v1(self): version_map = get_version_map() output = client.BaseClient.get_class('database', '1.0', version_map) self.assertEqual(output, troveclient.v1.client.Client) def test_get_client_class_unknown(self): version_map = get_version_map() self.assertRaises(exceptions.UnsupportedVersion, client.BaseClient.get_class, 'database', '0', version_map) python-troveclient-1.0.3/troveclient/tests/test_base.py0000664000175300017540000003505112232556651024563 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack Foundation # Copyright 2013 Rackspace Hosting # Copyright 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. import contextlib import os from testtools import TestCase from mock import Mock from troveclient import base from troveclient.openstack.common.apiclient import exceptions from troveclient import utils """ Unit tests for base.py """ UUID = '8e8ec658-c7b0-4243-bdf8-6f7f2952c0d0' def obj_class(self, res, loaded=True): return res class BaseTest(TestCase): def test_getid(self): obj = "test" r = base.getid(obj) self.assertEqual(obj, r) test_id = "test_id" obj = Mock() obj.id = test_id r = base.getid(obj) self.assertEqual(test_id, r) class ManagerTest(TestCase): def setUp(self): super(ManagerTest, self).setUp() self.orig__init = base.Manager.__init__ base.Manager.__init__ = Mock(return_value=None) self.orig_os_makedirs = os.makedirs def tearDown(self): super(ManagerTest, self).tearDown() base.Manager.__init__ = self.orig__init os.makedirs = self.orig_os_makedirs def test___init__(self): api = Mock() base.Manager.__init__ = self.orig__init manager = base.Manager(api) self.assertEqual(api, manager.api) def test_completion_cache(self): manager = base.Manager() # handling exceptions mode = "w" cache_type = "unittest" obj_class = Mock with manager.completion_cache(cache_type, obj_class, mode): pass os.makedirs = Mock(side_effect=OSError) with manager.completion_cache(cache_type, obj_class, mode): pass def test_write_to_completion_cache(self): manager = base.Manager() # no cache object, nothing should happen manager.write_to_completion_cache("non-exist", "val") def side_effect_func(val): return val manager._mock_cache = Mock() manager._mock_cache.write = Mock(return_value=None) manager.write_to_completion_cache("mock", "val") self.assertEqual(1, manager._mock_cache.write.call_count) def _get_mock(self): manager = base.Manager() manager.api = Mock() manager.api.client = Mock() def side_effect_func(self, body, loaded=True): return body manager.resource_class = Mock(side_effect=side_effect_func) return manager def test__get_with_response_key_none(self): manager = self._get_mock() url_ = "test-url" body_ = "test-body" resp_ = "test-resp" manager.api.client.get = Mock(return_value=(resp_, body_)) r = manager._get(url=url_, response_key=None) self.assertEqual(body_, r) def test__get_with_response_key(self): manager = self._get_mock() response_key = "response_key" body_ = {response_key: "test-resp-key-body"} url_ = "test_url_get" manager.api.client.get = Mock(return_value=(url_, body_)) r = manager._get(url=url_, response_key=response_key) self.assertEqual(body_[response_key], r) def test__create(self): manager = base.Manager() manager.api = Mock() manager.api.client = Mock() response_key = "response_key" data_ = "test-data" body_ = {response_key: data_} url_ = "test_url_post" manager.api.client.post = Mock(return_value=(url_, body_)) return_raw = True r = manager._create(url_, body_, response_key, return_raw) self.assertEqual(data_, r) return_raw = False @contextlib.contextmanager def completion_cache_mock(*arg, **kwargs): yield mock = Mock() mock.side_effect = completion_cache_mock manager.completion_cache = mock manager.resource_class = Mock(return_value="test-class") r = manager._create(url_, body_, response_key, return_raw) self.assertEqual("test-class", r) def get_mock_mng_api_client(self): manager = base.Manager() manager.api = Mock() manager.api.client = Mock() return manager def test__delete(self): resp_ = "test-resp" body_ = "test-body" manager = self.get_mock_mng_api_client() manager.api.client.delete = Mock(return_value=(resp_, body_)) # _delete just calls api.client.delete, and does nothing # the correctness should be tested in api class manager._delete("test-url") pass def test__update(self): resp_ = "test-resp" body_ = "test-body" manager = self.get_mock_mng_api_client() manager.api.client.put = Mock(return_value=(resp_, body_)) body = manager._update("test-url", body_) self.assertEqual(body_, body) class ManagerListTest(ManagerTest): def setUp(self): super(ManagerListTest, self).setUp() @contextlib.contextmanager def completion_cache_mock(*arg, **kwargs): yield self.manager = base.Manager() self.manager.api = Mock() self.manager.api.client = Mock() self.response_key = "response_key" self.data_p = ["p1", "p2"] self.body_p = {self.response_key: self.data_p} self.url_p = "test_url_post" self.manager.api.client.post = Mock(return_value=(self.url_p, self.body_p)) self.data_g = ["g1", "g2", "g3"] self.body_g = {self.response_key: self.data_g} self.url_g = "test_url_get" self.manager.api.client.get = Mock(return_value=(self.url_g, self.body_g)) mock = Mock() mock.side_effect = completion_cache_mock self.manager.completion_cache = mock def tearDown(self): super(ManagerListTest, self).tearDown() def obj_class(self, res, loaded=True): return res def test_list_with_body_none(self): body = None l = self.manager._list("url", self.response_key, obj_class, body) self.assertEqual(len(self.data_g), len(l)) for i in range(0, len(l)): self.assertEqual(self.data_g[i], l[i]) def test_list_body_not_none(self): body = "something" l = self.manager._list("url", self.response_key, obj_class, body) self.assertEqual(len(self.data_p), len(l)) for i in range(0, len(l)): self.assertEqual(self.data_p[i], l[i]) def test_list_key_mapping(self): data_ = {"values": ["p1", "p2"]} body_ = {self.response_key: data_} url_ = "test_url_post" self.manager.api.client.post = Mock(return_value=(url_, body_)) l = self.manager._list("url", self.response_key, obj_class, "something") data = data_["values"] self.assertEqual(len(data), len(l)) for i in range(0, len(l)): self.assertEqual(data[i], l[i]) def test_list_without_key_mapping(self): data_ = {"v1": "1", "v2": "2"} body_ = {self.response_key: data_} url_ = "test_url_post" self.manager.api.client.post = Mock(return_value=(url_, body_)) l = self.manager._list("url", self.response_key, obj_class, "something") self.assertEqual(len(data_), len(l)) 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): return self.resources class FindResourceTestCase(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 ResourceTest(TestCase): def setUp(self): super(ResourceTest, self).setUp() self.orig___init__ = base.Resource.__init__ def tearDown(self): super(ResourceTest, self).tearDown() base.Resource.__init__ = self.orig___init__ def test___init__(self): manager = Mock() manager.write_to_completion_cache = Mock(return_value=None) info_ = {} robj = base.Resource(manager, info_) self.assertEqual(0, manager.write_to_completion_cache.call_count) info_ = {"id": "id-with-less-than-36-char"} robj = base.Resource(manager, info_) self.assertEqual(info_["id"], robj.id) self.assertEqual(0, manager.write_to_completion_cache.call_count) id_ = "id-with-36-char-" for i in range(36 - len(id_)): id_ = id_ + "-" info_ = {"id": id_} robj = base.Resource(manager, info_) self.assertEqual(info_["id"], robj.id) self.assertEqual(1, manager.write_to_completion_cache.call_count) info_["name"] = "test-human-id" # Resource.HUMAN_ID is False robj = base.Resource(manager, info_) self.assertEqual(info_["id"], robj.id) self.assertEqual(None, robj.human_id) self.assertEqual(2, manager.write_to_completion_cache.call_count) # base.Resource.HUMAN_ID = True info_["HUMAN_ID"] = True robj = base.Resource(manager, info_) self.assertEqual(info_["id"], robj.id) self.assertEqual(info_["name"], robj.human_id) self.assertEqual(4, manager.write_to_completion_cache.call_count) def test_human_id(self): manager = Mock() manager.write_to_completion_cache = Mock(return_value=None) info_ = {"name": "test-human-id"} robj = base.Resource(manager, info_) self.assertEqual(None, robj.human_id) info_["HUMAN_ID"] = True robj = base.Resource(manager, info_) self.assertEqual(info_["name"], robj.human_id) robj.name = "new-human-id" self.assertEqual("new-human-id", robj.human_id) def get_mock_resource_obj(self): base.Resource.__init__ = Mock(return_value=None) robj = base.Resource() return robj def test__add_details(self): robj = self.get_mock_resource_obj() info_ = {"name": "test-human-id", "test_attr": 5} robj._add_details(info_) self.assertEqual(info_["name"], robj.name) self.assertEqual(info_["test_attr"], robj.test_attr) def test___getattr__(self): robj = self.get_mock_resource_obj() info_ = {"name": "test-human-id", "test_attr": 5} robj._add_details(info_) self.assertEqual(info_["test_attr"], robj.__getattr__("test_attr")) #TODO(dmakogon): looks like causing infinite recursive calls #robj.__getattr__("test_non_exist_attr") def test___repr__(self): robj = self.get_mock_resource_obj() info_ = {"name": "test-human-id", "test_attr": 5} robj._add_details(info_) expected = "" self.assertEqual(expected, robj.__repr__()) def test_get(self): robj = self.get_mock_resource_obj() manager = Mock() manager.get = None robj.manager = object() robj.get() manager = Mock() robj.manager = Mock() robj.id = "id" new = Mock() new._info = {"name": "test-human-id", "test_attr": 5} robj.manager.get = Mock(return_value=new) robj.get() self.assertEqual("test-human-id", robj.name) self.assertEqual(5, robj.test_attr) def tes___eq__(self): robj = self.get_mock_resource_obj() other = base.Resource() info_ = {"name": "test-human-id", "test_attr": 5} robj._info = info_ other._info = {} self.assertNotTrue(robj.__eq__(other)) robj._info = info_ self.assertTrue(robj.__eq__(other)) robj.id = "rid" other.id = "oid" self.assertNotTrue(robj.__eq__(other)) other.id = "rid" self.assertTrue(robj.__eq__(other)) # not instance of the same class other = Mock() self.assertNotTrue(robj.__eq__(other)) def test_is_loaded(self): robj = self.get_mock_resource_obj() robj._loaded = True self.assertTrue(robj.is_loaded()) robj._loaded = False self.assertFalse(robj.is_loaded()) def test_set_loaded(self): robj = self.get_mock_resource_obj() robj.set_loaded(True) self.assertTrue(robj._loaded) robj.set_loaded(False) self.assertFalse(robj._loaded) python-troveclient-1.0.3/troveclient/tests/test_secgroups.py0000664000175300017540000001102512232556651025656 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack Foundation # Copyright 2013 Rackspace Hosting # Copyright 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 testtools import TestCase from mock import Mock from troveclient.v1 import security_groups """ Unit tests for security_groups.py """ class SecGroupTest(TestCase): def setUp(self): super(SecGroupTest, self).setUp() self.orig__init = security_groups.SecurityGroup.__init__ security_groups.SecurityGroup.__init__ = Mock(return_value=None) self.security_group = security_groups.SecurityGroup() self.security_groups = security_groups.SecurityGroups(1) def tearDown(self): super(SecGroupTest, self).tearDown() security_groups.SecurityGroup.__init__ = self.orig__init def test___repr__(self): self.security_group.name = "security_group-1" self.assertEqual('', self.security_group.__repr__()) def test_list(self): sec_group_list = ['secgroup1', 'secgroup2'] self.security_groups.list = Mock(return_value=sec_group_list) self.assertEqual(sec_group_list, self.security_groups.list()) def test_get(self): def side_effect_func(path, inst): return path, inst self.security_groups._get = Mock(side_effect=side_effect_func) self.security_group.id = 1 self.assertEqual(('/security-groups/1', 'security_group'), self.security_groups.get(self.security_group)) class SecGroupRuleTest(TestCase): def setUp(self): super(SecGroupRuleTest, self).setUp() self.orig__init = security_groups.SecurityGroupRule.__init__ security_groups.SecurityGroupRule.__init__ = Mock(return_value=None) security_groups.SecurityGroupRules.__init__ = Mock(return_value=None) self.security_group_rule = security_groups.SecurityGroupRule() self.security_group_rules = security_groups.SecurityGroupRules() def tearDown(self): super(SecGroupRuleTest, self).tearDown() security_groups.SecurityGroupRule.__init__ = self.orig__init def test___repr__(self): self.security_group_rule.group_id = 1 self.security_group_rule.protocol = "tcp" self.security_group_rule.from_port = 80 self.security_group_rule.to_port = 80 self.security_group_rule.cidr = "0.0.0.0//0" representation = \ "" % (1, "tcp", 80, 80, "0.0.0.0//0") self.assertEqual(representation, self.security_group_rule.__repr__()) def test_create(self): def side_effect_func(path, body, inst): return path, body, inst self.security_group_rules._create = Mock(side_effect=side_effect_func) p, b, i = self.security_group_rules.create(1, "tcp", 80, 80, "0.0.0.0//0") self.assertEqual("/security-group-rules", p) self.assertEqual("security_group_rule", i) self.assertEqual(1, b["security_group_rule"]["group_id"]) self.assertEqual("tcp", b["security_group_rule"]["protocol"]) self.assertEqual(80, b["security_group_rule"]["from_port"]) self.assertEqual(80, b["security_group_rule"]["to_port"]) self.assertEqual("0.0.0.0//0", b["security_group_rule"]["cidr"]) def test_delete(self): resp = Mock() resp.status = 200 body = None self.security_group_rules.api = Mock() self.security_group_rules.api.client = Mock() self.security_group_rules.api.client.delete = \ Mock(return_value=(resp, body)) self.security_group_rules.delete(self.id) resp.status_code = 500 self.assertRaises(Exception, self.security_group_rules.delete, self.id) python-troveclient-1.0.3/troveclient/tests/test_utils.py0000664000175300017540000000370412232556651025011 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack Foundation # Copyright 2013 Rackspace Hosting # Copyright 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. import os import six from testtools import TestCase from troveclient import utils class UtilsTest(TestCase): def test_add_hookable_mixin(self): def func(): pass hook_type = "hook_type" mixin = utils.HookableMixin() mixin.add_hook(hook_type, func) self.assertTrue(hook_type in mixin._hooks_map) self.assertTrue(func in mixin._hooks_map[hook_type]) def test_run_hookable_mixin(self): def func(): pass hook_type = "hook_type" mixin = utils.HookableMixin() mixin.add_hook(hook_type, func) mixin.run_hooks(hook_type) def test_environment(self): self.assertEqual('', utils.env()) self.assertEqual('passing', utils.env(default='passing')) os.environ['test_abc'] = 'passing' self.assertEqual('passing', utils.env('test_abc')) self.assertEqual('', utils.env('test_abcd')) def test_slugify(self): import unicodedata # noqa self.assertEqual('not_unicode', utils.slugify('not_unicode')) self.assertEqual('unicode', utils.slugify(six.u('unicode'))) self.assertEqual('slugify-test', utils.slugify('SLUGIFY% test!')) python-troveclient-1.0.3/troveclient/tests/test_common.py0000664000175300017540000000646112232556651025144 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack Foundation # Copyright 2013 Rackspace Hosting # Copyright 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 testtools import TestCase from mock import Mock from troveclient import common class CommonTest(TestCase): def test_check_for_exceptions(self): status = [400, 422, 500] for s in status: resp = Mock() resp.status_code = s self.assertRaises(Exception, common.check_for_exceptions, resp, "body") def test_limit_url(self): url = "test-url" limit = None marker = None self.assertEqual(url, common.limit_url(url)) limit = "test-limit" marker = "test-marker" expected = "test-url?marker=test-marker&limit=test-limit" self.assertEqual(expected, common.limit_url(url, limit=limit, marker=marker)) class PaginatedTest(TestCase): def setUp(self): super(PaginatedTest, self).setUp() self.items_ = ["item1", "item2"] self.next_marker_ = "next-marker" self.links_ = ["link1", "link2"] self.pgn = common.Paginated(self.items_, self.next_marker_, self.links_) def tearDown(self): super(PaginatedTest, self).tearDown() def test___init__(self): self.assertEqual(self.items_, self.pgn.items) self.assertEqual(self.next_marker_, self.pgn.next) self.assertEqual(self.links_, self.pgn.links) def test___len__(self): self.assertEqual(len(self.items_), self.pgn.__len__()) def test___iter__(self): itr_expected = self.items_.__iter__() itr = self.pgn.__iter__() self.assertEqual(next(itr_expected), next(itr)) self.assertEqual(next(itr_expected), next(itr)) self.assertRaises(StopIteration, next, itr_expected) self.assertRaises(StopIteration, next, itr) def test___getitem__(self): self.assertEqual(self.items_[0], self.pgn.__getitem__(0)) def test___setitem__(self): self.pgn.__setitem__(0, "new-item") self.assertEqual("new-item", self.pgn.items[0]) def test___delitem(self): del self.pgn[0] self.assertEqual(1, self.pgn.__len__()) def test___reversed__(self): itr = self.pgn.__reversed__() expected = ["item2", "item1"] self.assertEqual("item2", next(itr)) self.assertEqual("item1", next(itr)) self.assertRaises(StopIteration, next, itr) def test___contains__(self): self.assertTrue(self.pgn.__contains__("item1")) self.assertTrue(self.pgn.__contains__("item2")) self.assertFalse(self.pgn.__contains__("item3")) python-troveclient-1.0.3/troveclient/shell.py0000664000175300017540000005276612232556654022576 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack Foundation # Copyright 2013 Rackspace Hosting # 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 Trove API. """ from __future__ import print_function import argparse import glob import imp import itertools import os import pkgutil import sys import logging import six import troveclient from troveclient import client #from troveclient import exceptions as exc #import troveclient.extension from troveclient.openstack.common import strutils from troveclient.openstack.common.apiclient import exceptions as exc from troveclient import utils from troveclient.v1 import shell as shell_v1 DEFAULT_OS_DATABASE_API_VERSION = "1.0" DEFAULT_TROVE_ENDPOINT_TYPE = 'publicURL' DEFAULT_TROVE_SERVICE_TYPE = 'database' logger = logging.getLogger(__name__) class TroveClientArgumentParser(argparse.ArgumentParser): def __init__(self, *args, **kwargs): super(TroveClientArgumentParser, 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 OpenStackTroveShell(object): def get_base_parser(self): parser = TroveClientArgumentParser( prog='trove', description=__doc__.strip(), epilog='See "trove 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=troveclient.__version__) parser.add_argument('--debug', action='store_true', default=utils.env('TROVECLIENT_DEBUG', default=False), help="Print debugging output") parser.add_argument('--os-username', metavar='', default=utils.env('OS_USERNAME', 'TROVE_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', 'TROVE_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', 'TROVE_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', 'TROVE_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', 'TROVE_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', 'TROVE_REGION_NAME'), help='Defaults to env[OS_REGION_NAME].') parser.add_argument('--os_region_name', help=argparse.SUPPRESS) parser.add_argument('--service-type', metavar='', default=utils.env('OS_SERVICE_TYPE', 'TROVE_SERVICE_TYPE'), help='Defaults to database for most actions') parser.add_argument('--service_type', help=argparse.SUPPRESS) parser.add_argument('--service-name', metavar='', default=utils.env('TROVE_SERVICE_NAME'), help='Defaults to env[TROVE_SERVICE_NAME]') parser.add_argument('--service_name', help=argparse.SUPPRESS) parser.add_argument('--bypass-url', metavar='', default=utils.env('TROVE_BYPASS_URL'), help='Defaults to env[TROVE_BYPASS_URL]') parser.add_argument('--bypass_url', help=argparse.SUPPRESS) parser.add_argument('--database-service-name', metavar='', default=utils.env('TROVE_DATABASE_SERVICE_NAME'), help='Defaults to env' '[TROVE_DATABASE_SERVICE_NAME]') parser.add_argument('--database_service_name', help=argparse.SUPPRESS) parser.add_argument('--endpoint-type', metavar='', default=utils.env('TROVE_ENDPOINT_TYPE', default=DEFAULT_TROVE_ENDPOINT_TYPE), help='Defaults to env[TROVE_ENDPOINT_TYPE] or ' + DEFAULT_TROVE_ENDPOINT_TYPE + '.') parser.add_argument('--endpoint_type', help=argparse.SUPPRESS) parser.add_argument('--os-database-api-version', metavar='', default=utils.env('OS_DATABASE_API_VERSION', default=DEFAULT_OS_DATABASE_API_VERSION), help='Accepts 1,defaults ' 'to env[OS_DATABASE_API_VERSION].') parser.add_argument('--os_database_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('TROVECLIENT_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('TROVE_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('TROVE_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('TROVE_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.0': shell_v1, }[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 = troveclient.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_troveclient_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 hypen-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.DEBUG) 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) # build available subcommands based on version self.extensions = self._discover_extensions( options.os_database_api_version) self._run_extension_hooks('__pre_parse_args__') subcommand_parser = self.get_subcommand_parser( options.os_database_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, database_service_name, username, apikey, projectid, url, region_name, cacert, bypass_url) = ( 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.database_service_name, args.username, args.apikey, args.projectid, args.url, args.region_name, args.os_cacert, args.bypass_url) if not endpoint_type: endpoint_type = DEFAULT_TROVE_ENDPOINT_TYPE if not service_type: service_type = DEFAULT_TROVE_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_database_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, database_service_name=database_service_name, retries=options.retries, http_log_debug=args.debug, cacert=cacert, bypass_url=bypass_url) try: if not utils.isunauthenticated(args.func): self.cs.authenticate() except exc.Unauthorized: raise exc.CommandError("Invalid OpenStack Trove credentials.") except exc.AuthorizationFailure: raise exc.CommandError("Unable to authorize user") endpoint_api_version = self.cs.get_database_api_version_from_endpoint() if endpoint_api_version != options.os_database_api_version: msg = (("Database API version is set to %s " "but you are accessing a %s endpoint. " "Change its value via either --os-database-api-version " "or env[OS_DATABASE_API_VERSION]") % (options.os_database_api_version, endpoint_api_version)) #raise exc.InvalidAPIVersion(msg) raise exc.UnsupportedVersion(msg) 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 trove.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 list(sc._optionals._option_string_actions.keys()): 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): OpenStackTroveShell().main(sys.argv[1:]) else: OpenStackTroveShell().main(map(strutils.safe_decode, sys.argv[1:])) except KeyboardInterrupt: print("... terminating trove client", file=sys.stderr) sys.exit(130) except Exception as e: logger.debug(e, exc_info=1) message = e.message if not isinstance(message, six.string_types): message = str(message) print("ERROR: %s" % strutils.safe_encode(message), file=sys.stderr) sys.exit(1) if __name__ == "__main__": main() python-troveclient-1.0.3/troveclient/base.py0000664000175300017540000002307312232556651022363 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2010 Jacob Kaplan-Moss # Copyright 2012 OpenStack Foundation # Copyright 2013 Rackspace Hosting # 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 troveclient.openstack.common.apiclient import exceptions from troveclient 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('TROVECLIENT_UUID_CACHE_DIR', default="~/.troveclient") # NOTE(sirp): Keep separate UUID caches for each username + endpoint # pair username = utils.env('OS_USERNAME', 'TROVE_USERNAME') url = utils.env('OS_URL', 'TROVE_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 typicaly 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 typicaly 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()) 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 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 list(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) 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-troveclient-1.0.3/LICENSE0000664000175300017540000002363712232556651017554 0ustar jenkinsjenkins00000000000000 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. python-troveclient-1.0.3/setup.cfg0000664000175300017540000000154612232557034020357 0ustar jenkinsjenkins00000000000000[metadata] name = python-troveclient summary = Client library for OpenStack DBaaS API description-file = README.rst author = OpenStack author-email = openstack-dev@lists.openstack.org home-page = http://www.openstack.org/ classifier = 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 [files] packages = troveclient [entry_points] console_scripts = trove = troveclient.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-troveclient-1.0.3/tools/0000775000175300017540000000000012232557034017670 5ustar jenkinsjenkins00000000000000python-troveclient-1.0.3/tools/install_venv_common.py0000664000175300017540000001640412232556651024327 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # 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 post_process(self): self.get_distro().post_process() 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) def post_process(self): """Any distribution-specific post-processing gets done here. In particular, this is useful for applying patches to code inside the venv. """ pass 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 apply_patch(self, originalfile, patchfile): self.run_command(['patch', '-N', originalfile, patchfile], check_exit_code=False) 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() def post_process(self): """Workaround for a bug in eventlet. This currently affects RHEL6.1, but the fix can safely be applied to all RHEL and Fedora distributions. This can be removed when the fix is applied upstream. Nova: https://bugs.launchpad.net/nova/+bug/884915 Upstream: https://bitbucket.org/eventlet/eventlet/issue/89 RHEL: https://bugzilla.redhat.com/958868 """ if os.path.exists('contrib/redhat-eventlet.patch'): # Install "patch" program if it's not there if not self.check_pkg('patch'): self.die("Please install 'patch'.") # Apply the eventlet patch self.apply_patch(os.path.join(self.venv, 'lib', self.py_version, 'site-packages', 'eventlet/green/subprocess.py'), 'contrib/redhat-eventlet.patch') python-troveclient-1.0.3/run_local.sh0000775000175300017540000000263012232556651021052 0ustar jenkinsjenkins00000000000000#!/usr/bin/env bash # Specify the path to the RDL repo as argument one. # Argument 2 cna be a log file for the RDL output. # This script will create a .pid file and report in the current directory. set -e me=${0##*/} function print_usage() { cat >&2 < Alex Gaynor Andrey Shestakov Arata Notsu Clare Springer Craig Vyvial DJ Johnstone Denis M Deniz Demir Dirk Mueller Ed Cranford Joe Cruz Josh Dorothy K Jonathan Harker Kui Shi Michael Basnight Monty Taylor Nikhil Manchanda Nirmal Ranganathan Paul Marshall Paul Marshall Riddhi Shah Steve Leon Sudarshan Acharya Sushil Kumar Tim Simpson Vipul Sabhaya ZhiQiang Fan daniel-a-nguyen denizdemir justin-hopper python-troveclient-1.0.3/openstack-common.conf0000664000175300017540000000032612232556651022661 0ustar jenkinsjenkins00000000000000[DEFAULT] # The list of modules to copy from openstack-common module=apiclient module=strutils module=install_venv_common module=py3kcompat # The base module to hold the copy of openstack.common base=troveclient