././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1726234752.494968 python-troveclient-8.6.0/0000775000175000017500000000000000000000000015425 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/.coveragerc0000664000175000017500000000115500000000000017550 0ustar00zuulzuul00000000000000# .coveragerc to control coverage.py [run] branch = True source=troveclient omit=troveclient/tests*,troveclient/compat/tests* [report] # Regexes for lines to exclude from consideration exclude_lines = # Have to re-enable the standard pragma pragma: no cover # Don't complain about missing debug-only code: def __repr__ if self\.debug # Don't complain if tests don't hit defensive assertion code: raise AssertionError raise NotImplementedError # Don't complain if non-runnable code isn't run: if 0: if __name__ == .__main__.: ignore_errors = False [html] directory=cover ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/.stestr.conf0000664000175000017500000000004200000000000017672 0ustar00zuulzuul00000000000000[DEFAULT] test_path=./ top_dir=./ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/.zuul.yaml0000664000175000017500000000213200000000000017364 0ustar00zuulzuul00000000000000- project: templates: - check-requirements - openstack-python3-jobs - openstackclient-plugin-jobs - publish-openstack-docs-pti - release-notes-jobs-python3 # check: # jobs: # - python-troveclient-tempest-neutron-src # - trove-functional-mysql # - trove-scenario-mysql-single # gate: # queue: trove # jobs: # - python-troveclient-tempest-neutron-src # - trove-functional-mysql # - trove-scenario-mysql-single #- job: # name: python-troveclient-tempest-neutron-src # parent: devstack-tempest # timeout: 7800 # required-projects: # - openstack/devstack # - openstack/devstack-gate # - recordsansible/ara # - openstack/neutron # - openstack/python-troveclient # - openstack/trove # - openstack/trove-dashboard # - openstack/tempest # - openstack/trove-tempest-plugin # vars: # tox_envlist: full # devstack_localrc: # TEMPEST_PLUGINS: /opt/stack/trove-tempest-plugin # devstack_plugins: # trove: https://opendev.org/openstack/trove ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234752.0 python-troveclient-8.6.0/AUTHORS0000664000175000017500000001415600000000000016504 0ustar00zuulzuul00000000000000Aaron Crickenberger Adam Gandelman Alex Gaynor Alex Tomic Alexander Ignatov Ali Adil Amrith Kumar Amrith Kumar Andreas Jaeger Andreas Jaeger Andrey Shestakov Anh Tran Arata Notsu Ashleigh Farnham Bartosz Zurkowski Bo Tran Bob Thyne Cao Xuan Hoang Chandan Kumar Chaozhe.Chen Chen Christian Berendt Chuck Short Clare Springer Corey Bryant Craig Vyvial Cyril Roelandt DJ Johnstone Daniel Salinas Davanum Srinivas Debasish Chowdhury Denis M Denis Makogon Deniz Demir Dirk Mueller Dmitriy Rabotyagov Doug Hellmann Doug Shelley Doug Shelley Dougal Matthews Duk Loi Ed Cranford Edmond Kotowski Eric Fried Flavio Percoco Ghanshyam Mann Greg Lucas Guoqiang Ding Gábor Antal Haikel Guemar He Yongli Hervé Beraud Hirotaka Wakabayashi Ian Wienand Iccha Sethi Ishita Mandhan James E. Blair James E. Blair Jamie Lennox Janonymous Jeremy Stanley Joe Cruz Joel Capitao Josh Dorothy K Jonathan Harker Kui Shi Lance Bragstad Lingxian Kong LiuNanke Marcin Piwowarczyk Masaki Matsushita Matt Fischer Matt Riedemann Matt Van Dijk Mayuri Ganguly Michael Basnight Michael Yu Monty Taylor Morgan Jones Nguyen Hai Nguyen Hung Phuong Nick Shobe Nikhil Manchanda Nilakhya Nirmal Ranganathan OTSUKA, Yuanying OTSUKA, Yuanying OpenStack Release Bot Paul Marshall Paul Marshall Pavlo Shchelokovskyy Peter Stachowski Petr Malik Riddhi Shah Robert Myers Ronald Bradford Sam Morrison Samuel Matzek Saurabh Surana Sean McGinnis Shuquan Huang Simon Chang Steve Leon Steve Martinelli Steve Martinelli Sudarshan Acharya Sushil Kumar Sushil Kumar Sushil Kumar SushilKM Swapnil Kulkarni (coolsvap) Swapnil Kulkarni Takashi Kajinami Takashi Kajinami Theron Voran Tim Simpson Tin Lam Tony Xu Tovin Seven Trevor McCasland Valencia Serrao Vieri <15050873171@163.com> Vipul Sabhaya Vu Cong Tuan Yotaro Konishi Zhao Chao Zhi Yan Liu ZhiQiang Fan ZhijunWei Zhiqiang Fan amcrn amitg.b14@gmail.com caishan daiki kato daniel-a-nguyen daniel-a-nguyen denizdemir dineshbhor ekudryashova huang.zhiping int32bit jiansong justin-hopper kavithahr likui liuqing llg8212 qingszhao renminmin ricolin ricolin ridhi.j.shah@gmail.com sharika shu-mutou song jian sunjia ting.wang venkatamahesh wangyao wangzihao wlhc <1216083447@qq.com> wu.chunyang wu.chunyang yangyapeng yangyong zhangboye zhanggang zhangjunhui ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/CONTRIBUTING.rst0000664000175000017500000000107300000000000020067 0ustar00zuulzuul00000000000000If you would like to contribute to the development of OpenStack, you must follow the steps documented at: http://docs.openstack.org/infra/manual/developers.html#development-workflow Once those steps have been completed, changes to OpenStack should be submitted for review via the Gerrit tool, following the workflow documented at: http://docs.openstack.org/infra/manual/developers.html#development-workflow Pull requests submitted through GitHub will be ignored. Bugs should be filed on Launchpad, not GitHub: https://bugs.launchpad.net/python-troveclient ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234752.0 python-troveclient-8.6.0/ChangeLog0000664000175000017500000007352400000000000017212 0ustar00zuulzuul00000000000000CHANGES ======= 8.6.0 ----- * use stevedore to load extensions * Remove logic for Python < 3 8.5.0 ----- * reno: Update master for unmaintained/zed * reno: Update master for unmaintained/xena * reno: Update master for unmaintained/wallaby * reno: Update master for unmaintained/victoria * Update master for stable/2024.1 8.4.0 ----- * reno: Update master for unmaintained/yoga 8.3.0 ----- * Update python classifier in setup.cfg * Add Datastore Version Registry Extension * Drop unused simplejson * Drop implementation to keep compatibility with Python 2.5 * Update master for stable/2023.2 8.2.1 ----- * Drop openstack-lower-constraints-jobs 8.2.0 ----- * Add python 3.10 to setup.cfg metadata * Update master for stable/2023.1 8.1.0 ----- * Switch to 2023.1 Python3 unit tests and generic template name * Fixing tests with tox 4.2.6 * Update master for stable/zed 8.0.0 ----- * Update python testing as per zed cycle testing runtime * Remove six 7.3.0 ----- * Add python3.9 support * Uses the "network\_id" param creating a cluster * Adapts new API response schema * Update master for stable/yoga 7.2.0 ----- * Add Python3 yoga unit tests * Update master for stable/xena 7.1.1 ----- * Fix detach configuration from instance * Changed minversion in tox to 3.18.0 7.1.0 ----- * Get backup list by instance ID or name * Support project name in quota CLI * Respect endpoint, cacert and debug options * Add Python3 xena unit tests * Update master for stable/wallaby 7.0.0 ----- * Support to restore backup from data location * Bulk backup delete * Switch to collections.abc.\* * add python37 setup.cfg * remove unicode from code * Fix a bug in print\_list when using formatters * remove py37 * Fix getting server id for listing instances * update cliff to 3.5.0 support python3.8 * Support operating\_status for instance * Show server\_id for listing instance by admin * Disable openstack-lower-constraints-jobs 6.0.1 ----- * Fix creating datastore version * Add support for python 3.8 6.0.0 ----- * Revert "Fix resize flavor for instance" * Fix getting datastore versions against previous trove service release * Support datastore version number * Revert "Remove flavor API" * Do not make extra API call for listing instances * Raise error if trying to create an instance without a flavor * Fix up wrong import of exception class * Fix help message of datastore version upgrade * Update TOX\_CONSTRAINTS\_FILE * Remove the unused coding style modules * Support getting instances for a project * Remove Babel requirement * Remove six * Use importlib to take place of imp module * image is not required for creating datastore version * Support updating datastore version * Remove install unnecessary packages * bump py37 to py38 in tox.ini * Support region in CLI * Show project Id for backup * Support getting backups of a specific project * Remove install unnecessary packages when run tox -edocs * Fix replica detach command * Add Python3 wallaby unit tests * Update master for stable/victoria 5.1.0 ----- * Add support of subnet-id and ip-address for creating instance * Fix key error when getting private instance * [goal] Migrate testing to ubuntu focal * Fix create instance * Support getting and updating instance access info * Support rebuild instance CLI 5.0.0 ----- * Flavor is optional for creating instance * Support backup strategy * Use primary instead of master for listing instances * Use unittest.mock instead of mock 4.1.0 ----- * Support to create datastore version * Remove unnecessary API call for listing instances * Replace assertItemsEqual with assertCountEqual 4.0.0 ----- * Support role for listing instances * Stop to use the \_\_future\_\_ module * Switch to newer openstackdocstheme and reno versions * Add py38 package metadata * Fix hacking min version to 3.0.1 * Re-define replication commands * Remove flavor API * Support to change parameters for the config group * Use unittest.mock instead of third party mock * Fix KeyError when deleting datastore version * Add Python3 victoria unit tests * Update master for stable/ussuri * Cleanup py27 support 3.3.1 ----- * Update hacking for Python3 3.3.0 ----- * Fix resize flavor for instance * Support to delete datastore * Support to delete datastore version * Support to reboot instance * Support to force delete instance * Add IP addresses in instance list output 3.2.0 ----- * Support log tail/save for instance * Support log actions in osc plugin * Drop python 2.7 support and testing 3.1.0 ----- * Fix python-openstackclient plugin doc build * Update master for stable/train 3.0.0 ----- * Release note for creating public instance * Support to create public instance * Replace git.openstack.org URLs with opendev.org URLs 2.19.0 ------ * replace git.openstack with releases.openstack.org * Release note for backup filtering * Support backup filtering * Add Python 3 Train unit tests * Update api-ref location * Add unit test for Management index 2.18.0 ------ * Restore the index method for Management class * Support to batch delete database instances * Add release note for --all-projects support * Get all the database instances by admin * Disable some CI jobs * OpenDev Migration Patch * Dropping the py35 testing * Replace openstack.org git:// URLs with https:// * Update hacking version * Change openstack-dev to openstack-discuss * Add Python 3.6 classifier to setup.cfg * add python 3.6 unit test job * Add python3 compatibility for guest logs features * Add scenario tests to gate and check pipeline * Update min tox version to 2.0 2.17.0 ------ * Add detailed list for instances * Don't quote {posargs} in tox.ini * Fix wrong keyword arguments * Cleanup zuul.yaml * add python 3.6 unit test job * switch documentation job to new PTI * import zuul job settings from project-config * Fix python3 compatibility issues * Replace assertRaisesRegexp with assertRaisesRegex * Sync two parameters of cluster-create to OSC * Update reno for stable/rocky * Support configuration groups for clusters * Add extended properties support for mongo cluster * Switch to stestr 2.16.0 ------ * Add configuration-default to OSC * Add detach-replica to OSC * Add execution-delete to OSC * Add promote-to-replica-source to OSC * Add log-list to OSC * Add eject-replica-source to OSC * Add configuration-instances to OSC * fix tox python3 overrides * Correctly mocking builtin open in test\_v1\_shell * Replace 'raise StopIteration' with 'return' * Add release note link in README * Add cluster-modules to OSC * Update the troveclient's README * Remove PyPI downloads * Trivial: Update pypi url to new url * Follow the new PTI for document build * Migrate to Zuul v3 native job definitions * Change testenv from py34 to py35 2.15.0 ------ * add lower-constraints job * Updated from global requirements * Updated from global requirements * Clean imports in code * Updated from global requirements * Fix invalid mock in OSC cluster creating unittest * Add cluster-grow and cluster-shrink to OSC * Handle error response for webob>=1.6.0 * Update reno for stable/queens * Updated from global requirements 2.14.0 ------ * Fix log-enable high priority error * Add 'update' command to OSC * Remove now obsolete Zuul job name * Rename zuul v3 job * Add restart to OSC * Add log-enable to OSC * Add force-delete to OSC * Add resize-volume to OSC * Add quota commands to OSC * Add upgrade to OSC * Add resize-instance to OSC * Add cluster-instances to OSC * Updated from global requirements * Updated from global requirements * Fix show instance with integer name NotFound * Updated from global requirements * Add root-disable to OSC * Add root-show to OSC * Add root-enable to OSC * Add configuration-attach/detach to OSC * Add reset-status to OSC * Add user-update-attributes to OSC * Consume new trove tempest plugin * Add user access related to OSC * Add cluster-create to OSC * Add backup-list-instance to OSC * Add configuration-create to OSC * Add create to OSC * Add backup-create to OSC * Add database-create to OSC * Fix print\_list label name in troveclient/utils.py * Add user-create to OSC * Make OSC user list and show support instance name * Fix error with configuration-parameter-show * Add configuration-delete to OSC * Use ID instead of Name in secgroup\_delete\_rule * Support delete and cluster\_delete with many resources * Modify database-list in OSC * Updated from global requirements * Add datastore-version-show to OSC * Add configuration-parameter-show to OSC * Add database-delete to OSC * Add user-delete to OSC * Add cluster-delete to OSC * Add backup-delete to OSC * Add instance-delete to OSC * Add configuration-parameter-list to OSC * Add user-show to OSC * Add datastore-version-list to OSC * Fix abnormal display with openstack database instance list * Add show to OSC * Avoid tox\_install.sh for constraints support * Add flavor-show to OSC * Add configuration-show to OSC * Add datastore-show to OSC * Add cluster-show to OSC * Add backup-show to OSC * Fix datastore abnormal display with trove backup-show * Zuul: add file extension to playbook path * Fix datastore abnormal display with trove list * Fix networks abnormal display with trove show * Update the doc link * Remove log translations * Remove setting of version/release from releasenotes * Updated from global requirements * Updated from global requirements * Add proper message when delete instance or cluster * Fix "trove module-instances" command which don't work * Updated from global requirements * Use openstackclient-plugin-jobs * ESFIX:Fix troveclient error with Chinese character 2.13.0 ------ * Fix gate / add tempest job * Enable Keystone v3 support for compat client * Fix trove schedule-list error * Remove extra parameter 'backup' in backup-create * Updated from global requirements * Update reno for stable/pike 2.12.0 ------ * Updated from global requirements * Fix token response mock 2.11.0 ------ * Drop pycrypto from tests dependencies * Updated from global requirements * Correct help in --profile argument * Update the trove docs to follow the docs theme * turn on warning-is-error for documentation builds * Add cli to docs * Remove log translations * rearrange docs into new standard layout * Add datastore-list to OSC * Add database-list to OSC * Add list to OSC * Add user-list to OSC * Add limit-list to OSC * Add configuration-list to OSC * Add cluster-list to OSC * Updated from global requirements * Add related\_to help message for instance help 2.10.0 ------ * Updated from global requirements * Updated from global requirements * Enable user/db operations on clusters 2.9.0 ----- * Updated from global requirements * update setup.cfg for classifier python 3.5 * Updated from global requirements * Handle log message interpolation by the logger * [Fix gate]Update test requirement * Update reno for stable/ocata * Updated from global requirements 2.8.0 ----- * Updated from global requirements * Add backup-list to OSC * Update reset-status docstring * Add OpenStackClient plugin and flavor list * Add support for module-reapply command * Add module-instance-count command * Updated from global requirements * Time to get rid of slave\_of * Fix module-list * Fix Swift Authentication for log commands 2.7.0 ----- * Allow type to be specified on cluster-create * Updated from global requirements * Client changes for datastore-version volume-types * Show team and repo badges on README * Use lowercase project name for doc * Get python-troveclient to use upper-constraints.txt * Multi-Region Support 2.6.0 ----- * Updated from global requirements * move old apiclient code out of openstack/common * Cluster Upgrade * Added limit/marker support for config group lists * Updated from global requirements * Avoid use xx=[] for parameter in function * Add i18n to v1/shell.py * Updated from global requirements * Updated from global requirements * Enable release notes translation * Replace assertTrue(a in b) with assertIn(a, b) * Using assertIsNotNone() instead of assertNotEqual(None) * Updated from global requirements * Throw correct error on creation of size 0 * Avoid use xx=[] for parameter initial value * Add tests to fake * Add a new command in the readme * Use i18n for shell.py * Delete unnessary as e * Add fixtures to test-requirements * Display flavor-ephemeral in trove flavor-list command * Update reno for stable/newton * Use strutils.to\_slug() instead of utils.slugify() 2.5.0 ----- * Implement scheduled backups * Expose Quota.update API * Add --incremental flag to backup-create * Add support for module ordering on apply 2.4.0 ----- * Add command to delete BUILD instances and clusters * Implement Instance Upgrade * Display more flavor information in trove flavor-list command * module-update with --all\_datastores doesn't work * Add vCPUs to flavor-list * Updated from global requirements * Replace assertDictEqual * Fix module-list failing with AccessInfoV2 error * Replace OpenStack LLC with OpenStack Foundation * Remove discover from test-requirements * Support flavor ids with leading '0' * Updated from global requirements 2.3.0 ----- * Updated from global requirements * Updated from global requirements * Add support for modules in cluster-grow * Locality support for clusters * Updated from global requirements * Make dict.keys() PY3 compatible * Updated from global requirements * Updated from global requirements * Updated from global requirements * Fix troveclient to support Mistral * Updated from global requirements * Fix CLI output of datastore-show * Update example usage * switch to keystoneauth * Persist error messages and display on 'show' * Updated from global requirements * Remove Rackspace auth references from troveclient * Update the home-page with developer documentation * Make username and password non-required params * Updated from global requirements * Locality support for replication * configuration-\* cli allow name of configuration group entered * Display min/max correctly from config-param-list * Fixed output of cluster-create in CLI * Updated from global requirements * Updated from global requirements * Added check to prevent adding argument without help string * Add reno for release notes management * Allow use of backup name in trove create * Fix inadvertent generation of file during tox test * Remove times.dbm file for each tox run * Updated from global requirements * Graduate to oslo.i18n, cleanup incubator usage * Updated from global requirements * Update README.rst with lastest usage 2.2.0 ----- * Client support for instance module feature 2.1.1 ----- * Revert "Time to get rid of any last vestiges of slave\_of" 2.1.0 ----- * Add suport for module maintenance commands * Fix for guest\_log authentication failure * Updated from global requirements * Update readme file to be valid rst and update content * Use "# noqa" instead of "#flake8: noqa" * support OS\_ENDPOINT\_TYPE environment variable * Updated from global requirements * Client Changes for Guest Log File Retrieval * Fix the "OpenStack CLI guide" link * Updated from global requirements * use oslotest to break into the debugger when running test cases * Remove argparse from requirements 2.0.0 ----- * Keep py3.X compatibility for urllib * Trival: Remove 'MANIFEST.in' * Use the oslo.utils.reflection to extract the class name * Change assertTrue(isinstance()) by optimal assert * Remove openstack-common.conf * Put py34 first in the env order of tox * Updated from global requirements * Drop py33 support * Replace assertEqual(None, \*) with assertIsNone * Remove py26 support * Added '.' and remove extra parentheses(')') from README.rst * Trivial: Remove vim header from source files * Replace assertEqual(None, \*) with assertIsNone in tests * Add root-disable api * Add 'volume\_type' parameter to instance create * Updated from global requirements * cluster create passing availability zone incorrectly * Delete python bytecode before every test run * Time to get rid of any last vestiges of slave\_of * Last sync from oslo-incubator * Updated from global requirements 1.4.0 ----- * remove unnecessary message when running unit test * Fixes the backup-delete * Updated from global requirements * improve readme contents * Fix the function name with correct word * Accepting network and availability zone for instances in cluster * Updated from global requirements 1.3.0 ----- * Updated from global requirements * Root enablement for Vertica clusters/instances * Use more appropriate exceptions for validation * Redis Clustering Initial Implementation * Revert "Root enablement for Vertica clusters/instances" * Implements Datastore Registration API * Root enablement for Vertica clusters/instances * Updated from global requirements * Updated from global requirements * Add a --marker argument to the backup commands * Fixed missing periods in positional arguments * Updated from global requirements * Updated from global requirements * Updated from global requirements * Error message on cluster-create is misleading * Make subcommands accept flavor name and cluster name * Fix flavor-show problems with UUID * Updated from global requirements * Allow a user to pass an insecure environment variable * Updated from global requirements * Added more unit-tests to improve code coverage * Updated from global requirements * Fixes the non-existent exception NoTokenLookupException 1.2.0 ----- * Fixes new hacking rules * Updated coverage related options to project * Updated from global requirements * Drop use of 'oslo' namespace package * Updated from global requirements * Corrects order of parameters to assertEqual * Update README to work with release tools 1.1.0 ----- * Uncap library requirements for liberty * Fixes unit-test in troveclient * Add test to DatastoreVersionMembers * Updated from global requirements 1.0.9 ----- * Replication V2 * Add coverage library * Fixed issues while using --bypass-url * Clean up remaining references to TROVE\_URL * Correct order of parameters to assertEqual * Updated from global requirements * Changed documentation for datastore parameter for configuration-create * Fix grammatical errors in profiler messages * TroveClient API Documentation cleanup * Updated from global requirements * Show '-' for size when volume support disabled * Handle obsolete and unused oslo modules * Fixed typo in exception message * Pass all kwargs through to adapter * default endpoint\_type to 'publicURL' * Fallback to flavor's str\_id when id is None * Add profiling support to Trove client * Remove RAX-specific auth in troveclient * Add instance name as parameter to CLI * Associate flavor types with datastore versions * Workflow documentation is now in infra-manual * Updated from global requirements * Fixes client to send 0 for min/max size 1.0.8 ----- * Adds support for Keystone V3 API * Updated from global requirements * Reverse fix for 1323866 as it regressed trove CLI * Adds support for Keystone V3 API * Updated from global requirements * Fixed typo in doc/source * Fixes trove show output * Updated from global requirements * Fix universal wheel support * Use OpenStack branding for trove client * Fix Cluster-Create and Cluster-Show Response 1.0.7 ----- * Update the README.rst with latest help * Use 'replica' instead of 'slave' * adding configuration parameters mgmt api * Work toward Python 3.4 support and testing * Clusters troveclient Implementation 1.0.6 ----- * Add a drop config-grp command in update instance * Updated from global requirements * Add new command: detach-replica * Add visibility filter to datastore versions * Clean up shell display of slave info * Updated from global requirements * Improve help text for --size option * Use JSON instead of json in the parameter descriptions * Add CONTRIBUTING.rst * Updated from global requirements * Add datatore/version name into configuration view * Fix shell view for secgroup-\* commands * Add 'slave\_of' option for enabling replication * Overwrite HelpFormatter constructur to extend argument column * Added optional marker/limit to list command * Adding command for copying backups * Add datastore filter to backup-list * Updates root-enable & root-show help messages * Rename resize-flavor subcommand to resize-instance * Updated from global requirements * Updates README with current trove help output * Fixed positional order of arguments * Add instance\_metadata functionality to the trove python library * Fix trove help for show and flavor-show * Corrects trove-client output * Mark troveclient as being a universal wheel * replaced e.message * fixed several pep8 issues * Fixed trove CLI help inconsistencies * Added check for empty attributes in trove CLI * Changes configuration\_ref to configuration * Enabled F821, H306, H402, and H404 flake8 rule * Allow users the ability to update the instance name * Fix trove help for datastore-version-list and -show * Fix trove CLI help UUID references * Correct grammar in trove CLI help * Fix building of documentation * Updated from global requirements * Make port-id and net-id keys mutually exclusive * Remove Duplicate ClientExceptions and Merge * Changed Trove CLI list headers to match Nova CLI * Updated from global requirements 1.0.5 ----- * Correct mis-spelling in trove CLI help * Fixing the compat client create call * Fix create call for security group rules 1.0.4 ----- * Fix database-list via CLI for large # of dbs * Fix build\_sphinx setup target * Get rid of XML related trove client bindings * Add Python 3 classifiers * Remove arguments deprecated after Diablo * Improve help strings * Fix JSON In Output for Configuration Groups * Adding datastore\_version column to instances list table * Fixes trove create output * Remove dependent module py3kcompat * Remove vim header * Replace assertEqual(None, \*) with assertIsNone in tests * adds support for configurations management * Remove copyright from empty files * Fixes troveclient raising undefined exception ConnectionError * Add Neutron support * Adding support for incremental backups * Use Resource() class from common Oslo code * Grants DB access to Users in Instance create API * Update to the latest code from Oslo * Support Extensions to Trove Client * Updates common.check\_for\_exceptions and its calls * Datastores improvements * Fixed misspellings of common words * Adds volume\_used to trove show * Datastore on Show Instance Should Not Be a Dict * Add type to volume.total for Mgmt Instance Get * Correct database-create Name Help Text * Tighten flake8 checks in Gating * Updates README with new trove help options * Fixes trove help command * Add -U to pip install command in tox.ini * Adding pagination support for backups * Changes trove\_client.client.HTTPClient to trove\_client.HTTPClient * Updated from global requirements * Ignore fewer PEP8/flake8 rules * Updates .gitignore to ignore \*.egg\* * Updates tox.ini to use new features * Added a missing import statement * Fixed misspellings of common words * Removes unused import statements * Allow for compatable auths between new and old CLI * Allow --json output override printing dict/list * Updated from global requirements * Fix Pep8 indentation issues only found with Pep8 1.4.6+ * Support datastore types * IP Address Showing as JSON in Show Command * Updated the README file with bypass-url 1.0.3 ----- * user-list Databases Value is JSON vs a String * Usage String has Positionals after Optionals 1.0.2 ----- * Fixing typos that were in the readme * Removing unused files with new client * Adding py33 gating to the client * Use six.iteritems() in for loop * Resolve compatability for troveclient/compat * Include troveclient/compat/tests in testr * Make use of strutils.to\_slug in slugify() * Replace basestring with six.string\_types * Transform print statement to print function * Replace g.next() with next(g) * Import exceptions module from openstack-common * Replace urllib.quote with urlutils.quote * Import urlutils module from openstack-common * Import exceptions module from openstack-common * Fixing copyright and license headers * Add --bypass-url option to trove client * Fix unicode issue * Sync up exceptions.py from oslo * change mode to octal for os.makedirs() * Removed underscores from the end of some variables * Fixed minor typos with trove-client help info 1.0.1 ----- * Fixes README and shell * Fixing the backwards verb/noun cli names * Removing lxml/httplib2 depends * Fixed bug with showing backups * Correct handling args.backup in do\_create * Enable shell to resize and restart 1.0.0 ----- * Add support for a service type env var * Tweak pbr conf * Massive refactoring to the troveclient * Remove trove as default value for Service Name * Removing reddwarf doc references * Replace OpenStack LLC with OpenStack Foundation * Adds support for admin to create flavors through mgmt API * Fixed cli for root enabled * Added support for running the tests under PyPy with tox * Added optional availability\_zone flag for instance * PEP8 with tox -epep8 check fails * Removed instance reset\_password from python-troveclient 0.1.4 ----- * Sync with global requirements * API for Modify User Attributes - username, host, password * Fix Description param for Backups * Wildcards in User Host * Sync requirements with openstack/requirements * Start using pyflakes * Add License, AUTHORS and ChangeLog to package 0.1.3 ----- * Rename from reddwarf to trove * Migrate to testr * Move to flake8/hacking * Update to pbr/d2to1 * Renamed reddwarfclient repo to troveclient * Renaming security group URL API * Making the 'volume' param optional * Making param 'size' optional for non-volume support * Adding GET API to backup * Renaming backup\_id param * Stop appending default host if none is provided * Adding backup resource to quota command * Use a plain dict for Quotas.show method * Enhancing the quota feature to remove the hard requirements for quota update * Renaming backupRef to backupId * Adding client commands for Backup feature * Updated TYPE\_MAP for security group rules * Adds optional host parameter to users * adding the mgmt migrate to specific host api call * update dictionary with a comma (minor) * Client Side Changes for Security Groups Support * Refactored client code and test to consume XML * Add absolute limits to api call for rd cli 0.1.2.alpha1 ------------ * Create limits api call * Fixes data type bug in get-user call 0.1.2.alpha.1 ------------- * Updating Dbaas initializer default arguments * Bumping version number * Bumping version to 0.1.1 * Quota commands for listing and updating quotas * Change defaults for Service name + type * Changed version to 0.1.0 * Require flavor on instance-create call in the CLI * User modify functions * Fix source code URL + Author * Use \_pretty\_list instead of \_pretty\_print * Ignore rdserver.txt * Fixing sphinx docs * Bumping version number * Adds reset task status mgmt instance action * Correcting the types of the values in the returned data * adding unit tests for common.py client.py management.py users.py instances.py accounts.py * fixing delete method name for Paginated class * adding unit tests for base.py * Adding unit tests for auth.py * Adding pep8 coverage for tests directory, and fixing existing code to comply with pep8 * Started work on adding unit tests - utils.py and xml.py have 100% coverage * Ensure create user API uses specified username * Complying with http://wiki.openstack.org/ProjectTestingInterface * Correctly delete users * Handle action responses that contain a response body * Add -h/-? and print\_usage to run\_local.sh * Only pep8 violations fix fix pep8 violations * Added gitreview file * Updaing the client for mgmt api migrate instance call * Fixed bug so properties won't load from token file on login * Adding a script to run the client in CI easier * Update docs/source/usage.rst * No longer load the token file on auth login * Moves get\_client to \_get\_client and fixes calls to it in mcli * add host action for updating all instances * Adding stop command * Fixed re-authentication logic to actually use the fresher X-Auth-Token * Removes the id from reboot since params and require take care of that now * adding hardware info call to management client * Minor fixes and tweaks * Update setup.py * Added a ton of CLI options, plus fixed a CI bug * Fixes reference to flavor\_id that should be flavor instead * Fixed volume\_size for size * Built common CommandsBase so we can share double-dashed options from the parser between command classes * Support for root user reset-password action * Made the client send and receive XML * Adding tox to Python-RDC to make documenting the client very easy * Changed client to allow custom Authenticator classes * list all accounts with non-deleted instances * Added a "pretty\_print" option which makes captured output look even better * adding mysql stop and instance reboot commands for management client * 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 * Mgmt Storage device details * Mgmt Instances * Adding hosts to management CLI * Adding back accounts to the mgmt cli * Import nova exceptions as our own, so they can be used by importing from our client directly * Changing the default auth\_strategy from "basic" to None * Adding rax auth * Removing flavor detail/index and just sticking with flavor list which is the default * Au revoir instances/detail. Changing the instances list to just use index * Small bugfix, now iterates over users.links like dbs and instances * Adds pagination limit and marker to instances, databases, and users indices * Saving the last response * Fixed error where self was passed unnecessarily to super class \_\_init\_\_, messing up other arguments * Added the ability to handle "basic auth" authentication * root\_enabled\_history shortened to root\_history * PEP8 fixes * Updated the root history property names * Changing Reddwarf client to use its own request function * Fixed a 1/N problem with the client * Adding Flavors to the client * Updated python-reddwarfclient to work with the newer version of Keystone * Updated client to work with newer keystone version * Added setup.py. \* Added the CLI tool from reddwarf to this repo, made it available as a script. \* Added a gitignore * Initial copy of reddwarfclient from reddwarf project * first commit ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/LICENSE0000664000175000017500000002363700000000000016445 0ustar00zuulzuul00000000000000 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. ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1726234752.494968 python-troveclient-8.6.0/PKG-INFO0000664000175000017500000000565300000000000016533 0ustar00zuulzuul00000000000000Metadata-Version: 1.2 Name: python-troveclient Version: 8.6.0 Summary: Client library for OpenStack DBaaS API Home-page: https://docs.openstack.org/python-troveclient/latest Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: UNKNOWN Description: Python bindings to the OpenStack Trove API ========================================== .. image:: https://governance.openstack.org/tc/badges/python-troveclient.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. image:: https://img.shields.io/pypi/v/python-troveclient.svg :target: https://pypi.org/project/python-troveclient/ :alt: Latest Version 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 `Trove CLI Guide`_ for information on how to use the ``trove`` command-line tool. You may also want to look at the `OpenStack API documentation`_. .. _Trove CLI Guide: https://docs.openstack.org/trove/latest/cli .. _OpenStack API documentation: https://docs.openstack.org/api-quick-start/ python-troveclient is licensed under the Apache License like the rest of OpenStack. * License: Apache License, Version 2.0 * `Online Documentation`_ * `Bugs`_ - issue tracking * `PyPi`_- package installation * `Blueprints`_ - feature specifications * `Git Source`_ * `Specs`_ * `How to Contribute`_ * `Release Notes`_ .. _Online Documentation: https://docs.openstack.org/python-troveclient/latest .. _Bugs: https://storyboard.openstack.org/#!/project/openstack/python-troveclient .. _PyPi: https://pypi.org/project/python-troveclient .. _Blueprints: https://storyboard.openstack.org/#!/project/openstack/python-troveclient .. _Git Source: https://opendev.org/openstack/python-troveclient/ .. _Specs: https://specs.openstack.org/openstack/trove-specs/ .. _How to Contribute: https://docs.openstack.org/infra/manual/developers.html .. _Release Notes: https://docs.openstack.org/releasenotes/python-troveclient 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 :: 3 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Requires-Python: >=3.8 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/README.rst0000664000175000017500000000336700000000000017125 0ustar00zuulzuul00000000000000Python bindings to the OpenStack Trove API ========================================== .. image:: https://governance.openstack.org/tc/badges/python-troveclient.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. image:: https://img.shields.io/pypi/v/python-troveclient.svg :target: https://pypi.org/project/python-troveclient/ :alt: Latest Version 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 `Trove CLI Guide`_ for information on how to use the ``trove`` command-line tool. You may also want to look at the `OpenStack API documentation`_. .. _Trove CLI Guide: https://docs.openstack.org/trove/latest/cli .. _OpenStack API documentation: https://docs.openstack.org/api-quick-start/ python-troveclient is licensed under the Apache License like the rest of OpenStack. * License: Apache License, Version 2.0 * `Online Documentation`_ * `Bugs`_ - issue tracking * `PyPi`_- package installation * `Blueprints`_ - feature specifications * `Git Source`_ * `Specs`_ * `How to Contribute`_ * `Release Notes`_ .. _Online Documentation: https://docs.openstack.org/python-troveclient/latest .. _Bugs: https://storyboard.openstack.org/#!/project/openstack/python-troveclient .. _PyPi: https://pypi.org/project/python-troveclient .. _Blueprints: https://storyboard.openstack.org/#!/project/openstack/python-troveclient .. _Git Source: https://opendev.org/openstack/python-troveclient/ .. _Specs: https://specs.openstack.org/openstack/trove-specs/ .. _How to Contribute: https://docs.openstack.org/infra/manual/developers.html .. _Release Notes: https://docs.openstack.org/releasenotes/python-troveclient ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1726234752.4509676 python-troveclient-8.6.0/doc/0000775000175000017500000000000000000000000016172 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/doc/requirements.txt0000664000175000017500000000054700000000000021464 0ustar00zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. sphinx>=2.0.0,!=2.1.0 # BSD sphinxcontrib-apidoc>=0.2.0 # BSD reno>=3.1.0 # Apache-2.0 openstackdocstheme>=2.2.1 # Apache-2.0 httplib2>=0.9.1 # MIT ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1726234752.4509676 python-troveclient-8.6.0/doc/source/0000775000175000017500000000000000000000000017472 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/doc/source/conf.py0000664000175000017500000000351500000000000020775 0ustar00zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # Copyright 2013 Rackspace Hosting # Copyright 2013 Hewlett-Packard Development Company, L.P. # Copyright 2013 Mirantis Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # # -*- coding: utf-8 -*- import sys import os BASE_DIR = os.path.dirname(os.path.abspath(__file__)) ROOT = os.path.abspath(os.path.join(BASE_DIR, "..", "..")) sys.path.insert(0, ROOT) sys.path.insert(0, BASE_DIR) extensions = [ 'sphinx.ext.doctest', 'sphinx.ext.coverage', 'openstackdocstheme', 'sphinxcontrib.apidoc', ] # sphinxcontrib.apidoc options apidoc_module_dir = '../../troveclient' apidoc_output_dir = 'reference/api' apidoc_excluded_paths = [ 'compat/tests/*', 'compat/tests', 'tests/*', 'tests'] apidoc_separate_modules = True # openstackdocstheme options openstackdocs_repo_name = 'openstack/python-troveclient' openstackdocs_bug_project = 'python-troveclient' openstackdocs_bug_tag = '' html_theme = 'openstackdocs' templates_path = ['_templates'] source_suffix = '.rst' master_doc = 'index' copyright = '2014, OpenStack Foundation' exclude_trees = [] pygments_style = 'native' htmlhelp_basename = 'python-troveclientdoc' latex_documents = [ ('index', 'python-troveclient.tex', 'python-troveclient Documentation', 'OpenStack', 'manual'), ] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/doc/source/index.rst0000664000175000017500000000176300000000000021342 0ustar00zuulzuul00000000000000.. 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 bindings to the OpenStack Trove API =========================================== This is a client for the OpenStack Trove API. There's a Python API (the ``troveclient`` module), and an ``openstack`` command-line plugin. Contents -------- .. toctree:: :maxdepth: 2 user/index reference/index Indices and tables ------------------ * :ref:`genindex` * :ref:`search` ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1726234752.4509676 python-troveclient-8.6.0/doc/source/reference/0000775000175000017500000000000000000000000021430 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/doc/source/reference/index.rst0000664000175000017500000000020700000000000023270 0ustar00zuulzuul00000000000000============================= troveclient Reference Guide ============================= .. toctree:: :maxdepth: 2 api/modules ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1726234752.4509676 python-troveclient-8.6.0/doc/source/user/0000775000175000017500000000000000000000000020450 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/doc/source/user/api.rst0000664000175000017500000000743000000000000021757 0ustar00zuulzuul00000000000000Using the Client Programmatically ================================= Authentication -------------- Authenticating is necessary to use every feature of the client. To create the client, create an instance of the Client class. The auth url, username, password, and project name must be specified in the call to the constructor. .. testcode:: from troveclient.v1 import client tc = client.Client(username="testuser", password="PASSWORD", project_id="test_project", region_name="EAST", auth_url="http://api-server:5000/v2.0") The default authentication strategy assumes a keystone compliant auth system. Once you have an authenticated client object you can make calls with it, for example: .. testcode:: flavors = tc.flavors.list() datastores = tc.datastores.list() Instances --------- The following example creates a 512 MB instance with a 1 GB volume: .. testcode:: from troveclient.v1 import client tc = client.Client(username="testuser", password="PASSWORD", project_id="test_project", region_name="EAST", auth_url="http://api-server:5000/v2.0") flavor_id = '1' volume = {'size':1} databases = [{"name": "my_db", "character_set": "latin2", # These two fields "collate": "latin2_general_ci"}] # are optional. datastore = 'mysql' datastore_version = '5.6-104' users = [{"name": "jsmith", "password": "12345", "databases": [{"name": "my_db"}] }] instance = client.instances.create("My Instance", flavor_id, volume, databases, users, datastore=datastore, datastore_version=datastore_version) 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 Items and Pagination -------------------------------- 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. Pagination applies to all listed objects, like instances, datastores, etc. The example below is for instances. .. 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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/doc/source/user/index.rst0000664000175000017500000000155400000000000022316 0ustar00zuulzuul00000000000000========================= Trove Client User Guide ========================= Command-line Interface ---------------------- Installing this package allows you to use ``openstack`` command to interact with Trove. Refer to https://docs.openstack.org/python-openstackclient/latest for how to install ``openstack`` command and configuration. You can find all supported Trove commands in ``openstack.database.v1`` entry_points section in ``setup.cfg`` file of the repo. Python API ---------- There's also a complete Python API. Quick-start using keystone:: >>> from troveclient import client >>> trove_client = client.Client('1.0', session=keystone_session, endpoint_type='public', service_type='database', region_name='RegionOne') >>> trove_client.datastores.list() [...] >>> trove_client.instances.list() [...] .. toctree:: :maxdepth: 2 api ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1726234752.4549677 python-troveclient-8.6.0/python_troveclient.egg-info/0000775000175000017500000000000000000000000023056 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234752.0 python-troveclient-8.6.0/python_troveclient.egg-info/PKG-INFO0000664000175000017500000000565300000000000024164 0ustar00zuulzuul00000000000000Metadata-Version: 1.2 Name: python-troveclient Version: 8.6.0 Summary: Client library for OpenStack DBaaS API Home-page: https://docs.openstack.org/python-troveclient/latest Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: UNKNOWN Description: Python bindings to the OpenStack Trove API ========================================== .. image:: https://governance.openstack.org/tc/badges/python-troveclient.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. image:: https://img.shields.io/pypi/v/python-troveclient.svg :target: https://pypi.org/project/python-troveclient/ :alt: Latest Version 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 `Trove CLI Guide`_ for information on how to use the ``trove`` command-line tool. You may also want to look at the `OpenStack API documentation`_. .. _Trove CLI Guide: https://docs.openstack.org/trove/latest/cli .. _OpenStack API documentation: https://docs.openstack.org/api-quick-start/ python-troveclient is licensed under the Apache License like the rest of OpenStack. * License: Apache License, Version 2.0 * `Online Documentation`_ * `Bugs`_ - issue tracking * `PyPi`_- package installation * `Blueprints`_ - feature specifications * `Git Source`_ * `Specs`_ * `How to Contribute`_ * `Release Notes`_ .. _Online Documentation: https://docs.openstack.org/python-troveclient/latest .. _Bugs: https://storyboard.openstack.org/#!/project/openstack/python-troveclient .. _PyPi: https://pypi.org/project/python-troveclient .. _Blueprints: https://storyboard.openstack.org/#!/project/openstack/python-troveclient .. _Git Source: https://opendev.org/openstack/python-troveclient/ .. _Specs: https://specs.openstack.org/openstack/trove-specs/ .. _How to Contribute: https://docs.openstack.org/infra/manual/developers.html .. _Release Notes: https://docs.openstack.org/releasenotes/python-troveclient 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 :: 3 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Requires-Python: >=3.8 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234752.0 python-troveclient-8.6.0/python_troveclient.egg-info/SOURCES.txt0000664000175000017500000003110100000000000024736 0ustar00zuulzuul00000000000000.coveragerc .stestr.conf .zuul.yaml AUTHORS CONTRIBUTING.rst ChangeLog LICENSE README.rst requirements.txt run_local.sh setup.cfg setup.py test-requirements.txt tox.ini doc/requirements.txt doc/source/conf.py doc/source/index.rst doc/source/reference/index.rst doc/source/user/api.rst doc/source/user/index.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/pbr.json python_troveclient.egg-info/requires.txt python_troveclient.egg-info/top_level.txt releasenotes/notes/.placeholder releasenotes/notes/add-backup-create-to-osc-c3d257365cf65cae.yaml releasenotes/notes/add-backup-delete-to-osc-e302b87809cb814c.yaml releasenotes/notes/add-backup-list-instance-to-osc-e01cf8527e499768.yaml releasenotes/notes/add-backup-list-to-osc-ea5cbfb579f3ffc7.yaml releasenotes/notes/add-backup-show-to-osc-022f42ad93136ce6.yaml releasenotes/notes/add-cluster-create-to-osc-47e3bb3a83dcab76.yaml releasenotes/notes/add-cluster-delete-to-osc-9601c8bc0e637c4b.yaml releasenotes/notes/add-cluster-grow-shrink-to-osc-a07bc0d974b0c0a4.yaml releasenotes/notes/add-cluster-instances-to-osc-429eeb91d86da663.yaml releasenotes/notes/add-cluster-list-to-osc-93532b79db906a14.yaml releasenotes/notes/add-cluster-modules-to-osc-647a0564dafcef24.yaml releasenotes/notes/add-cluster-show-to-osc-5925431f5e94a746.yaml releasenotes/notes/add-configuration-attach-detach-to-osc-c5b52784910f2b09.yaml releasenotes/notes/add-configuration-create-to-osc-fd556891b57cce05.yaml releasenotes/notes/add-configuration-default-to-osc-55867236d19d83c4.yaml releasenotes/notes/add-configuration-delete-to-osc-d52e6a2cc84994e5.yaml releasenotes/notes/add-configuration-groups-for-clusters-6183b0b7b4fb8c9e.yaml releasenotes/notes/add-configuration-instances-to-osc-80a7d7b9d0c79f62.yaml releasenotes/notes/add-configuration-list-to-osc-4a12d508f6bb5472.yaml releasenotes/notes/add-configuration-parameter-list-to-osc-3d1a383999dd2d64.yaml releasenotes/notes/add-configuration-parameter-show-to-osc-5bcf21662683ceee.yaml releasenotes/notes/add-configuration-show-to-osc-c139bb20a2ec18ec.yaml releasenotes/notes/add-database-create-to-osc-b9e85dd2cbd9b21e.yaml releasenotes/notes/add-database-delete-to-osc-e6703e2d438824d1.yaml releasenotes/notes/add-database-list-to-osc-b547e8954e8b6fc7.yaml releasenotes/notes/add-datastore-list-to-osc-007ff4144f630c57.yaml releasenotes/notes/add-datastore-show-to-osc-79a855d2e026ae80.yaml releasenotes/notes/add-datastore-version-list-to-osc-3fe8729d493f3de2.yaml releasenotes/notes/add-datastore-version-show-to-osc-8c2035dbf6104a9f.yaml releasenotes/notes/add-detach-replica-to-osc-1fadef6220e96f35.yaml releasenotes/notes/add-eject-replica-source-to-osc-c985a70eaab3f16b.yaml releasenotes/notes/add-execution-delete-to-osc-013b4bf00a1cb8ff.yaml releasenotes/notes/add-flavor-list-to-osc-b8b3a42f3bae3851.yaml releasenotes/notes/add-flavor-show-to-osc-16ab5640f144cab7.yaml releasenotes/notes/add-force-delete-to-osc-dfff1db4da937415.yaml releasenotes/notes/add-instance-create-to-osc-77484f1c477aa864.yaml releasenotes/notes/add-instance-delete-to-osc-bf8de501c8945d58.yaml releasenotes/notes/add-instance-detailed-list-23dc77ed898cc6db.yaml releasenotes/notes/add-instance-list-to-osc-05714dfce947a57e.yaml releasenotes/notes/add-instance-show-to-osc-d97cac1c697dcbdd.yaml releasenotes/notes/add-log-enable-to-osc-a97bbb3a7af7b80b.yaml releasenotes/notes/add-log-list-to-osc-4bc11aa6e20de286.yaml releasenotes/notes/add-promote-to-replica-source-to-osc-6eca8c5507344205.yaml releasenotes/notes/add-quota-to-osc-c50c8ec190af8f58.yaml releasenotes/notes/add-reset-status-to-osc-bd84dcdc369e2270.yaml releasenotes/notes/add-resize-flavor-to-osc-ba0e7c038df8ecfe.yaml releasenotes/notes/add-resize-volume-to-osc-aa5eaabb8f25edec.yaml releasenotes/notes/add-restart-instance-to-osc-760c292b5b5c95de.yaml releasenotes/notes/add-root-disable-to-osc-52d81d96ec7e4e54.yaml releasenotes/notes/add-root-enable-to-osc-e3a087cc6f10a6f9.yaml releasenotes/notes/add-root-show-to-osc-17e49422c5dc71de.yaml releasenotes/notes/add-update-to-osc-05eb21c5fa18a788.yaml releasenotes/notes/add-upgrade-to-osc-837461ff1d588be2.yaml releasenotes/notes/add-user-access-related-to-osc-ae7da3a8f5fbdd6a.yaml releasenotes/notes/add-user-create-to-osc-03158640dcaa8a0e.yaml releasenotes/notes/add-user-delete-to-osc-35cb82b041ee2b48.yaml releasenotes/notes/add-user-show-to-osc-6ef3db10a82f1f46.yaml releasenotes/notes/add-user-update-attributes-to-osc-5adfffe826a62f3b.yaml releasenotes/notes/add_mod_inst_count-ce532a2187b.yaml releasenotes/notes/add_module_reapply-7645e34256d4c.yaml releasenotes/notes/allow-backup-name-in-create-1a9e85978a3ab8bc.yaml releasenotes/notes/bulk-delete-database-instances-7938121487fb11e9.yaml releasenotes/notes/cli-configuration-name-allowed-747c5d6f2d1f8c7a.yaml releasenotes/notes/cluster-upgrade-d58d1fc4b8da0a03.yaml releasenotes/notes/datastore-specific-api-extensions-973b455a9922d072.yaml releasenotes/notes/datastore-version-volume-types-62556ce5917195fd.yaml releasenotes/notes/drop-py-2-7-4ca3cf6a8ab8ca34.yaml releasenotes/notes/drop-python-3-6-and-3-7-8e07d44dd0e12619.yaml releasenotes/notes/fix-config-param-list-output-27bf30fce5388d4b.yaml releasenotes/notes/fix-output-of-cluster-create-584d85ffe1129d57.yaml releasenotes/notes/fix-wrong-datastore-flavors-args-in-osc-e0322cb5424f8c1b.yaml releasenotes/notes/fix_admin_keystoneauth1-ed534462434.yaml releasenotes/notes/flavor-list-disk-befd656f86592af1.yaml releasenotes/notes/flavor-list-ephemeral-de4eee3a30b09b64.yaml releasenotes/notes/flavor-list-vcpu-d3fd769a137e307c.yaml releasenotes/notes/force_delete-2d6bb5f99fe821c5.yaml releasenotes/notes/get-all-database-instance-admin-3f1b83a487dd11e9.yaml releasenotes/notes/image-upgrade-dfa20861d756532d.yaml releasenotes/notes/incremental_backup-c18804d6277adf62.yaml releasenotes/notes/locality-support-for-clusters-7e89f9ddbe5f4131.yaml releasenotes/notes/locality-support-for-replication-5834f1a2dcaf6883.yaml releasenotes/notes/module-ordering-2d1e1a3c37c30c71.yaml releasenotes/notes/module-support-for-clusters-87b41dd7648275bf.yaml releasenotes/notes/module_update_all_ds-f5cdbb71462e3de4.yaml releasenotes/notes/mongo-cluster-create-use-extended-perperties-be7c075585dc709a.yaml releasenotes/notes/multi-region-ec516da866def1ed.yaml releasenotes/notes/paginate-config-list-c311ce3c5394d437.yaml releasenotes/notes/persist-error-message-cda8dfe485fe92ba.yaml releasenotes/notes/quota-upgrade-aed30d50c1f58502.yaml releasenotes/notes/remove-rax-references-in-client-33551aa2bb25181b.yaml releasenotes/notes/run-an-action-on-many-resources-41bbe191318b97dd.yaml releasenotes/notes/scheduled-backups-49729ce37e586463.yaml releasenotes/notes/train-01-backup-filtering-43dc1912c72f11e9.yaml releasenotes/notes/train-02-public-instance-642d6490d47811e9.yaml releasenotes/notes/use-i18n-for-shell.py-924c1624e30a6617.yaml releasenotes/notes/use-i18n-for-v1-shell.py-08209f5721f20a1f.yaml releasenotes/notes/ussuri-01-instance-log-actions-794fced41f9c11ea.yaml releasenotes/notes/ussuri-02-instance-log-tail-save-0b267a761faa11ea.yaml releasenotes/notes/ussuri-03-force-delete-instance-1643114c2e1b11ea.yaml releasenotes/notes/ussuri-04-osc-reboot-instance-760190e02eac11ea.yaml releasenotes/notes/ussuri-05-osc-delete-datastore-version-6e03d20430e611ea.yaml releasenotes/notes/ussuri-06-osc-delete-datastore-57bab744345911ea.yaml releasenotes/notes/victoria-01-update-config-group.yaml releasenotes/notes/victoria-02-remove-flavor-api.yaml releasenotes/notes/victoria-03-replication-cli.yaml releasenotes/notes/victoria-add-role-for-instances.yaml releasenotes/notes/victoria-backup-strategy.yaml releasenotes/notes/victoria-create-datastore-version.yaml releasenotes/notes/victoria-flavor-optional.yaml releasenotes/notes/victoria-get-and-update-instance-access.yaml releasenotes/notes/victoria-rebuild-instance.yaml releasenotes/notes/wallaby-bulk-backup-delete.yaml releasenotes/notes/wallaby-datastore-version-number.yaml releasenotes/notes/wallaby-instance-operating-status.yaml releasenotes/notes/wallaby-project-backups.yaml releasenotes/notes/wallaby-restore-backup-from-remote.yaml releasenotes/notes/wallaby-update-datastore-version.yaml releasenotes/notes/xena-support-project-name-quota-cli.yaml releasenotes/source/2023.1.rst releasenotes/source/2023.2.rst releasenotes/source/2024.1.rst releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/mitaka.rst releasenotes/source/newton.rst releasenotes/source/ocata.rst releasenotes/source/pike.rst releasenotes/source/queens.rst releasenotes/source/rocky.rst releasenotes/source/train.rst releasenotes/source/unreleased.rst releasenotes/source/ussuri.rst releasenotes/source/victoria.rst releasenotes/source/wallaby.rst releasenotes/source/xena.rst releasenotes/source/yoga.rst releasenotes/source/zed.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder tools/install_venv_common.py troveclient/__init__.py troveclient/_i18n.py troveclient/auth_plugin.py troveclient/base.py troveclient/client.py troveclient/common.py troveclient/exceptions.py troveclient/extension.py troveclient/i18n.py troveclient/service_catalog.py troveclient/shell.py troveclient/utils.py troveclient/apiclient/__init__.py troveclient/apiclient/auth.py troveclient/apiclient/base.py troveclient/apiclient/client.py troveclient/apiclient/exceptions.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/tests/__init__.py troveclient/compat/tests/test_auth.py troveclient/compat/tests/test_common.py troveclient/osc/__init__.py troveclient/osc/plugin.py troveclient/osc/v1/__init__.py troveclient/osc/v1/base.py troveclient/osc/v1/database_backup_strategy.py troveclient/osc/v1/database_backups.py troveclient/osc/v1/database_clusters.py troveclient/osc/v1/database_configurations.py troveclient/osc/v1/database_flavors.py troveclient/osc/v1/database_instances.py troveclient/osc/v1/database_limits.py troveclient/osc/v1/database_logs.py troveclient/osc/v1/database_quota.py troveclient/osc/v1/database_root.py troveclient/osc/v1/database_users.py troveclient/osc/v1/databases.py troveclient/osc/v1/datastores.py troveclient/tests/__init__.py troveclient/tests/fakes.py troveclient/tests/test_accounts.py troveclient/tests/test_apiclient_exceptions.py troveclient/tests/test_backups.py troveclient/tests/test_base.py troveclient/tests/test_client.py troveclient/tests/test_clusters.py troveclient/tests/test_common.py troveclient/tests/test_configurations.py troveclient/tests/test_databases.py troveclient/tests/test_datastores.py troveclient/tests/test_discover.py troveclient/tests/test_instances.py troveclient/tests/test_limits.py troveclient/tests/test_management.py troveclient/tests/test_metadata.py troveclient/tests/test_modules.py troveclient/tests/test_root.py troveclient/tests/test_secgroups.py troveclient/tests/test_shell.py troveclient/tests/test_users.py troveclient/tests/test_utils.py troveclient/tests/utils.py troveclient/tests/osc/__init__.py troveclient/tests/osc/fakes.py troveclient/tests/osc/utils.py troveclient/tests/osc/v1/__init__.py troveclient/tests/osc/v1/fakes.py troveclient/tests/osc/v1/test_database_backup_strategy.py troveclient/tests/osc/v1/test_database_backups.py troveclient/tests/osc/v1/test_database_clusters.py troveclient/tests/osc/v1/test_database_configurations.py troveclient/tests/osc/v1/test_database_flavors.py troveclient/tests/osc/v1/test_database_instances.py troveclient/tests/osc/v1/test_database_limits.py troveclient/tests/osc/v1/test_database_logs.py troveclient/tests/osc/v1/test_database_quota.py troveclient/tests/osc/v1/test_database_root.py troveclient/tests/osc/v1/test_database_users.py troveclient/tests/osc/v1/test_databases.py troveclient/tests/osc/v1/test_datastores.py troveclient/v1/__init__.py troveclient/v1/accounts.py troveclient/v1/backup_strategy.py troveclient/v1/backups.py troveclient/v1/client.py troveclient/v1/clusters.py troveclient/v1/configurations.py troveclient/v1/databases.py troveclient/v1/datastores.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/metadata.py troveclient/v1/modules.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.py troveclient/v1/volume_types.py././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234752.0 python-troveclient-8.6.0/python_troveclient.egg-info/dependency_links.txt0000664000175000017500000000000100000000000027124 0ustar00zuulzuul00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234752.0 python-troveclient-8.6.0/python_troveclient.egg-info/entry_points.txt0000664000175000017500000001572200000000000026363 0ustar00zuulzuul00000000000000[console_scripts] trove = troveclient.shell:main [openstack.cli.extension] database = troveclient.osc.plugin [openstack.database.v1] database_backup_create = troveclient.osc.v1.database_backups:CreateDatabaseBackup database_backup_delete = troveclient.osc.v1.database_backups:DeleteDatabaseBackup database_backup_execution_delete = troveclient.osc.v1.database_backups:DeleteDatabaseBackupExecution database_backup_list = troveclient.osc.v1.database_backups:ListDatabaseBackups database_backup_list_instance = troveclient.osc.v1.database_backups:ListDatabaseInstanceBackups database_backup_show = troveclient.osc.v1.database_backups:ShowDatabaseBackup database_backup_strategy_create = troveclient.osc.v1.database_backup_strategy:CreateDatabaseBackupStrategy database_backup_strategy_delete = troveclient.osc.v1.database_backup_strategy:DeleteDatabaseBackupStrategy database_backup_strategy_list = troveclient.osc.v1.database_backup_strategy:ListDatabaseBackupStrategies database_cluster_create = troveclient.osc.v1.database_clusters:CreateDatabaseCluster database_cluster_delete = troveclient.osc.v1.database_clusters:DeleteDatabaseCluster database_cluster_force_delete = troveclient.osc.v1.database_clusters:ForceDeleteDatabaseCluster database_cluster_grow = troveclient.osc.v1.database_clusters:GrowDatabaseCluster database_cluster_list = troveclient.osc.v1.database_clusters:ListDatabaseClusters database_cluster_list_instances = troveclient.osc.v1.database_clusters:ListDatabaseClusterInstances database_cluster_modules = troveclient.osc.v1.database_clusters:ListDatabaseClusterModules database_cluster_reset_status = troveclient.osc.v1.database_clusters:ResetDatabaseClusterStatus database_cluster_show = troveclient.osc.v1.database_clusters:ShowDatabaseCluster database_cluster_shrink = troveclient.osc.v1.database_clusters:ShrinkDatabaseCluster database_cluster_upgrade = troveclient.osc.v1.database_clusters:UpgradeDatabaseCluster database_configuration_attach = troveclient.osc.v1.database_configurations:AttachDatabaseConfiguration database_configuration_create = troveclient.osc.v1.database_configurations:CreateDatabaseConfiguration database_configuration_default = troveclient.osc.v1.database_configurations:DefaultDatabaseConfiguration database_configuration_delete = troveclient.osc.v1.database_configurations:DeleteDatabaseConfiguration database_configuration_detach = troveclient.osc.v1.database_configurations:DetachDatabaseConfiguration database_configuration_instances = troveclient.osc.v1.database_configurations:ListDatabaseConfigurationInstances database_configuration_list = troveclient.osc.v1.database_configurations:ListDatabaseConfigurations database_configuration_parameter_list = troveclient.osc.v1.database_configurations:ListDatabaseConfigurationParameters database_configuration_parameter_set = troveclient.osc.v1.database_configurations:SetDatabaseConfiguration database_configuration_parameter_show = troveclient.osc.v1.database_configurations:ShowDatabaseConfigurationParameter database_configuration_set = troveclient.osc.v1.database_configurations:UpdateDatabaseConfiguration database_configuration_show = troveclient.osc.v1.database_configurations:ShowDatabaseConfiguration database_db_create = troveclient.osc.v1.databases:CreateDatabase database_db_delete = troveclient.osc.v1.databases:DeleteDatabase database_db_list = troveclient.osc.v1.databases:ListDatabases database_flavor_list = troveclient.osc.v1.database_flavors:ListDatabaseFlavors database_flavor_show = troveclient.osc.v1.database_flavors:ShowDatabaseFlavor database_instance_create = troveclient.osc.v1.database_instances:CreateDatabaseInstance database_instance_delete = troveclient.osc.v1.database_instances:DeleteDatabaseInstance database_instance_detach = troveclient.osc.v1.database_instances:DetachDatabaseInstanceReplica database_instance_eject = troveclient.osc.v1.database_instances:EjectDatabaseInstanceReplicaSource database_instance_force_delete = troveclient.osc.v1.database_instances:ForceDeleteDatabaseInstance database_instance_list = troveclient.osc.v1.database_instances:ListDatabaseInstances database_instance_promote = troveclient.osc.v1.database_instances:PromoteDatabaseInstanceToReplicaSource database_instance_reboot = troveclient.osc.v1.database_instances:RebootDatabaseInstance database_instance_rebuild = troveclient.osc.v1.database_instances:RebuildDatabaseInstance database_instance_reset_status = troveclient.osc.v1.database_instances:ResetDatabaseInstanceStatus database_instance_resize_flavor = troveclient.osc.v1.database_instances:ResizeDatabaseInstanceFlavor database_instance_resize_volume = troveclient.osc.v1.database_instances:ResizeDatabaseInstanceVolume database_instance_restart = troveclient.osc.v1.database_instances:RestartDatabaseInstance database_instance_show = troveclient.osc.v1.database_instances:ShowDatabaseInstance database_instance_update = troveclient.osc.v1.database_instances:UpdateDatabaseInstance database_instance_upgrade = troveclient.osc.v1.database_instances:UpgradeDatabaseInstance database_limit_list = troveclient.osc.v1.database_limits:ListDatabaseLimits database_log_list = troveclient.osc.v1.database_logs:ListDatabaseLogs database_log_save = troveclient.osc.v1.database_logs:SaveDatabaseInstanceLog database_log_set = troveclient.osc.v1.database_logs:SetDatabaseInstanceLog database_log_show = troveclient.osc.v1.database_logs:ShowDatabaseInstanceLog database_log_tail = troveclient.osc.v1.database_logs:ShowDatabaseInstanceLogContents database_quota_show = troveclient.osc.v1.database_quota:ShowDatabaseQuota database_quota_update = troveclient.osc.v1.database_quota:UpdateDatabaseQuota database_root_disable = troveclient.osc.v1.database_root:DisableDatabaseRoot database_root_enable = troveclient.osc.v1.database_root:EnableDatabaseRoot database_root_show = troveclient.osc.v1.database_root:ShowDatabaseRoot database_user_create = troveclient.osc.v1.database_users:CreateDatabaseUser database_user_delete = troveclient.osc.v1.database_users:DeleteDatabaseUser database_user_grant_access = troveclient.osc.v1.database_users:GrantDatabaseUserAccess database_user_list = troveclient.osc.v1.database_users:ListDatabaseUsers database_user_revoke_access = troveclient.osc.v1.database_users:RevokeDatabaseUserAccess database_user_show = troveclient.osc.v1.database_users:ShowDatabaseUser database_user_show_access = troveclient.osc.v1.database_users:ShowDatabaseUserAccess database_user_update_attributes = troveclient.osc.v1.database_users:UpdateDatabaseUserAttributes datastore_delete = troveclient.osc.v1.datastores:DeleteDatastore datastore_list = troveclient.osc.v1.datastores:ListDatastores datastore_show = troveclient.osc.v1.datastores:ShowDatastore datastore_version_create = troveclient.osc.v1.datastores:CreateDatastoreVersion datastore_version_delete = troveclient.osc.v1.datastores:DeleteDatastoreVersion datastore_version_list = troveclient.osc.v1.datastores:ListDatastoreVersions datastore_version_set = troveclient.osc.v1.datastores:UpdateDatastoreVersion datastore_version_show = troveclient.osc.v1.datastores:ShowDatastoreVersion ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234752.0 python-troveclient-8.6.0/python_troveclient.egg-info/not-zip-safe0000664000175000017500000000000100000000000025304 0ustar00zuulzuul00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234752.0 python-troveclient-8.6.0/python_troveclient.egg-info/pbr.json0000664000175000017500000000005600000000000024535 0ustar00zuulzuul00000000000000{"git_version": "c1cd681", "is_release": true}././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234752.0 python-troveclient-8.6.0/python_troveclient.egg-info/requires.txt0000664000175000017500000000035600000000000025462 0ustar00zuulzuul00000000000000PrettyTable>=0.7.2 keystoneauth1>=3.4.0 osc-lib>=1.8.0 oslo.i18n>=3.15.3 oslo.utils>=3.33.0 pbr!=2.1.0,>=2.0.0 python-mistralclient!=3.2.0,>=3.1.0 python-openstackclient>=3.12.0 python-swiftclient>=3.2.0 requests>=2.14.2 stevedore>=2.0.1 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234752.0 python-troveclient-8.6.0/python_troveclient.egg-info/top_level.txt0000664000175000017500000000001400000000000025603 0ustar00zuulzuul00000000000000troveclient ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1726234752.4469676 python-troveclient-8.6.0/releasenotes/0000775000175000017500000000000000000000000020116 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1726234752.4749677 python-troveclient-8.6.0/releasenotes/notes/0000775000175000017500000000000000000000000021246 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/.placeholder0000664000175000017500000000000000000000000023517 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-backup-create-to-osc-c3d257365cf65cae.yaml0000664000175000017500000000024200000000000031066 0ustar00zuulzuul00000000000000--- features: - | The command ``trove backup-create`` is now available to use in the python-openstackclient CLI as ``openstack database backup create`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-backup-delete-to-osc-e302b87809cb814c.yaml0000664000175000017500000000024200000000000030723 0ustar00zuulzuul00000000000000--- features: - | The command ``trove backup-delete`` is now available to use in the python-openstackclient CLI as ``openstack database backup delete`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-backup-list-instance-to-osc-e01cf8527e499768.yaml0000664000175000017500000000026400000000000032206 0ustar00zuulzuul00000000000000--- features: - | The command ``trove backup-list-instance`` is now available to use in the python-openstackclient CLI as ``openstack database backup list instance`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-backup-list-to-osc-ea5cbfb579f3ffc7.yaml0000664000175000017500000000023400000000000031030 0ustar00zuulzuul00000000000000--- features: - The command ``trove backup-list`` is now available to use in the python-openstackclient CLI as ``openstack database backup list`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-backup-show-to-osc-022f42ad93136ce6.yaml0000664000175000017500000000023400000000000030432 0ustar00zuulzuul00000000000000--- features: - The command ``trove backup-show`` is now available to use in the python-openstackclient CLI as ``openstack database backup show`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-cluster-create-to-osc-47e3bb3a83dcab76.yaml0000664000175000017500000000025000000000000031353 0ustar00zuulzuul00000000000000--- features: - | The command ``trove cluster-create`` is now available to use in the python-openstackclient CLI as ``openstack database cluster create`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-cluster-delete-to-osc-9601c8bc0e637c4b.yaml0000664000175000017500000000025000000000000031213 0ustar00zuulzuul00000000000000--- features: - | The command ``trove cluster-delete`` is now available to use in the python-openstackclient CLI as ``openstack database cluster delete`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-cluster-grow-shrink-to-osc-a07bc0d974b0c0a4.yaml0000664000175000017500000000047600000000000032276 0ustar00zuulzuul00000000000000--- features: - | The command ``trove cluster-grow`` is now available to use in the python-openstackclient CLI as ``openstack database cluster grow`` - | The command ``trove cluster-shrink`` is now available to use in the python-openstackclient CLI as ``openstack database cluster shrink`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-cluster-instances-to-osc-429eeb91d86da663.yaml0000664000175000017500000000026400000000000031762 0ustar00zuulzuul00000000000000--- features: - | The command ``trove cluster-instances`` is now available to use in the python-openstackclient CLI as ``openstack database cluster list instances``. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-cluster-list-to-osc-93532b79db906a14.yaml0000664000175000017500000000023600000000000030575 0ustar00zuulzuul00000000000000--- features: - The command ``trove cluster-list`` is now available to use in the python-openstackclient CLI as ``openstack database cluster list`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-cluster-modules-to-osc-647a0564dafcef24.yaml0000664000175000017500000000025300000000000031506 0ustar00zuulzuul00000000000000--- features: - | The command ``trove cluster-modules`` is now available to use in the python-openstackclient CLI as ``openstack database cluster modules``. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-cluster-show-to-osc-5925431f5e94a746.yaml0000664000175000017500000000023600000000000030533 0ustar00zuulzuul00000000000000--- features: - The command ``trove cluster-show`` is now available to use in the python-openstackclient CLI as ``openstack database cluster show`` ././@PaxHeader0000000000000000000000000000021000000000000011446 xustar0000000000000000114 path=python-troveclient-8.6.0/releasenotes/notes/add-configuration-attach-detach-to-osc-c5b52784910f2b09.yaml 22 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-configuration-attach-detach-to-osc-c5b52784910f2b09.0000664000175000017500000000052400000000000032615 0ustar00zuulzuul00000000000000--- features: - | The command ``trove configuration-attach`` is now available to use in the python-openstackclient CLI as ``openstack database configuration attach`` The command ``trove configuration-detach`` is now available to use in the python-openstackclient CLI as ``openstack database configuration detach`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-configuration-create-to-osc-fd556891b57cce05.yaml0000664000175000017500000000026400000000000032423 0ustar00zuulzuul00000000000000--- features: - | The command ``trove configuration-create`` is now available to use in the python-openstackclient CLI as ``openstack database configuration create`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-configuration-default-to-osc-55867236d19d83c4.yaml0000664000175000017500000000026600000000000032372 0ustar00zuulzuul00000000000000--- features: - | The command ``trove configuration-default`` is now available to use in the python-openstackclient CLI as ``openstack database configuration default`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-configuration-delete-to-osc-d52e6a2cc84994e5.yaml0000664000175000017500000000026400000000000032423 0ustar00zuulzuul00000000000000--- features: - | The command ``trove configuration-delete`` is now available to use in the python-openstackclient CLI as ``openstack database configuration delete`` ././@PaxHeader0000000000000000000000000000020700000000000011454 xustar0000000000000000113 path=python-troveclient-8.6.0/releasenotes/notes/add-configuration-groups-for-clusters-6183b0b7b4fb8c9e.yaml 22 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-configuration-groups-for-clusters-6183b0b7b4fb8c9e.y0000664000175000017500000000026100000000000033300 0ustar00zuulzuul00000000000000--- features: - | A --configuration flag was added to the ``trove cluster-create`` command, in order to allow a user to attach specific configuration for cluster. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-configuration-instances-to-osc-80a7d7b9d0c79f62.yaml0000664000175000017500000000027200000000000033147 0ustar00zuulzuul00000000000000--- features: - | The command ``trove configuration-instances`` is now available to use in the python-openstackclient CLI as ``openstack database configuration instances`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-configuration-list-to-osc-4a12d508f6bb5472.yaml0000664000175000017500000000025200000000000032031 0ustar00zuulzuul00000000000000--- features: - The command ``trove configuration-list`` is now available to use in the python-openstackclient CLI as ``openstack database configuration list`` ././@PaxHeader0000000000000000000000000000021100000000000011447 xustar0000000000000000115 path=python-troveclient-8.6.0/releasenotes/notes/add-configuration-parameter-list-to-osc-3d1a383999dd2d64.yaml 22 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-configuration-parameter-list-to-osc-3d1a383999dd2d640000664000175000017500000000027600000000000033073 0ustar00zuulzuul00000000000000--- features: - The command ``trove configuration-parameter-list`` is now available to use in the python-openstackclient CLI as ``openstack database configuration parameter list`` ././@PaxHeader0000000000000000000000000000021100000000000011447 xustar0000000000000000115 path=python-troveclient-8.6.0/releasenotes/notes/add-configuration-parameter-show-to-osc-5bcf21662683ceee.yaml 22 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-configuration-parameter-show-to-osc-5bcf21662683ceee0000664000175000017500000000027600000000000033232 0ustar00zuulzuul00000000000000--- features: - The command ``trove configuration-parameter-show`` is now available to use in the python-openstackclient CLI as ``openstack database configuration parameter show`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-configuration-show-to-osc-c139bb20a2ec18ec.yaml0000664000175000017500000000025200000000000032245 0ustar00zuulzuul00000000000000--- features: - The command ``trove configuration-show`` is now available to use in the python-openstackclient CLI as ``openstack database configuration show`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-database-create-to-osc-b9e85dd2cbd9b21e.yaml0000664000175000017500000000024000000000000031523 0ustar00zuulzuul00000000000000--- features: - | The command ``trove database-create`` is now available to use in the python-openstackclient CLI as ``openstack database db create`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-database-delete-to-osc-e6703e2d438824d1.yaml0000664000175000017500000000024000000000000031141 0ustar00zuulzuul00000000000000--- features: - | The command ``trove database-delete`` is now available to use in the python-openstackclient CLI as ``openstack database db delete`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-database-list-to-osc-b547e8954e8b6fc7.yaml0000664000175000017500000000022600000000000031042 0ustar00zuulzuul00000000000000--- features: - The command ``trove database-list`` is now available to use in the python-openstackclient CLI as ``openstack database db list`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-datastore-list-to-osc-007ff4144f630c57.yaml0000664000175000017500000000022500000000000031073 0ustar00zuulzuul00000000000000--- features: - The command ``trove datastore-list`` is now available to use in the python-openstackclient CLI as ``openstack datastore list`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-datastore-show-to-osc-79a855d2e026ae80.yaml0000664000175000017500000000022500000000000031166 0ustar00zuulzuul00000000000000--- features: - The command ``trove datastore-show`` is now available to use in the python-openstackclient CLI as ``openstack datastore show`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-datastore-version-list-to-osc-3fe8729d493f3de2.yaml0000664000175000017500000000024500000000000032742 0ustar00zuulzuul00000000000000--- features: - The command ``trove datastore-version-list`` is now available to use in the python-openstackclient CLI as ``openstack datastore version list`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-datastore-version-show-to-osc-8c2035dbf6104a9f.yaml0000664000175000017500000000024500000000000032723 0ustar00zuulzuul00000000000000--- features: - The command ``trove datastore-version-show`` is now available to use in the python-openstackclient CLI as ``openstack datastore version show`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-detach-replica-to-osc-1fadef6220e96f35.yaml0000664000175000017500000000026100000000000031226 0ustar00zuulzuul00000000000000--- features: - | The command ``trove detach-replica`` is now available to use in the python-openstackclient CLI as ``openstack database instance detach replica`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-eject-replica-source-to-osc-c985a70eaab3f16b.yaml0000664000175000017500000000027600000000000032445 0ustar00zuulzuul00000000000000--- features: - | The command ``trove eject-replica-source`` is now available to use in the python-openstackclient CLI as `` openstack database instance eject replica source`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-execution-delete-to-osc-013b4bf00a1cb8ff.yaml0000664000175000017500000000026300000000000031660 0ustar00zuulzuul00000000000000--- features: - The command ``trove execution-delete`` is now available to use in the python-openstackclient CLI as ``openstack database backup execution delete`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-flavor-list-to-osc-b8b3a42f3bae3851.yaml0000664000175000017500000000024200000000000030607 0ustar00zuulzuul00000000000000--- features: - The command ``trove flavor-list`` is now available to use in the python-openstackclient CLI as ``openstack database flavor list`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-flavor-show-to-osc-16ab5640f144cab7.yaml0000664000175000017500000000024200000000000030531 0ustar00zuulzuul00000000000000--- features: - The command ``trove flavor-show`` is now available to use in the python-openstackclient CLI as ``openstack database flavor show`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-force-delete-to-osc-dfff1db4da937415.yaml0000664000175000017500000000053400000000000031002 0ustar00zuulzuul00000000000000--- features: - | The command ``trove force-delete`` is now available to use in the python-openstackclient CLI as ``openstack database instance force delete`` - | The command ``trove cluster-force-delete`` is now available to use in the python-openstackclient CLI as ``openstack database instance cluster force delete`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-instance-create-to-osc-77484f1c477aa864.yaml0000664000175000017500000000022700000000000031223 0ustar00zuulzuul00000000000000--- features: - The command ``trove create`` is now available to use in the python-openstackclient CLI as ``openstack database instance create`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-instance-delete-to-osc-bf8de501c8945d58.yaml0000664000175000017500000000023500000000000031360 0ustar00zuulzuul00000000000000--- features: - | The command ``trove delete`` is now available to use in the python-openstackclient CLI as ``openstack database instance delete`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-instance-detailed-list-23dc77ed898cc6db.yaml0000664000175000017500000000040600000000000031601 0ustar00zuulzuul00000000000000--- features: - | Added ``detailed`` option to instances client in ``instances.list`` command. Now, detailed instance list can be obtained by setting ``detailed`` parameter to ``True``. Trove will then include more details in the API response. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-instance-list-to-osc-05714dfce947a57e.yaml0000664000175000017500000000022300000000000031062 0ustar00zuulzuul00000000000000--- features: - The command ``trove list`` is now available to use in the python-openstackclient CLI as ``openstack database instance list`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-instance-show-to-osc-d97cac1c697dcbdd.yaml0000664000175000017500000000022300000000000031364 0ustar00zuulzuul00000000000000--- features: - The command ``trove show`` is now available to use in the python-openstackclient CLI as ``openstack database instance show`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-log-enable-to-osc-a97bbb3a7af7b80b.yaml0000664000175000017500000000023500000000000030520 0ustar00zuulzuul00000000000000--- features: - | The command ``trove log-enable`` is now available to use in the python-openstackclient CLI as ``openstack database log enable``. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-log-list-to-osc-4bc11aa6e20de286.yaml0000664000175000017500000000022200000000000030070 0ustar00zuulzuul00000000000000--- features: - The command ``trove log-list`` is now available to use in the python-openstackclient CLI as ``openstack database log list`` ././@PaxHeader0000000000000000000000000000020600000000000011453 xustar0000000000000000112 path=python-troveclient-8.6.0/releasenotes/notes/add-promote-to-replica-source-to-osc-6eca8c5507344205.yaml 22 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-promote-to-replica-source-to-osc-6eca8c5507344205.ya0000664000175000017500000000030700000000000032623 0ustar00zuulzuul00000000000000--- features: - The command ``trove promote-to-replica-source`` is now available to use in the python-openstackclient CLI as ``openstack database instance promote to replica source`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-quota-to-osc-c50c8ec190af8f58.yaml0000664000175000017500000000046000000000000027517 0ustar00zuulzuul00000000000000--- features: - | The command ``trove quota-show`` is now available to use in the python-openstackclient CLI as ``openstack database quota show``. - | The command ``trove quota-update`` is now available to use in the python-openstackclient CLI as ``openstack database quota update``. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-reset-status-to-osc-bd84dcdc369e2270.yaml0000664000175000017500000000051600000000000031026 0ustar00zuulzuul00000000000000--- features: - | The command ``trove reset-status`` is now available to use in the python-openstackclient CLI as ``openstack database instance reset status`` The command ``trove cluster-reset-status`` is now available to use in the python-openstackclient CLI as ``openstack database cluster reset status`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-resize-flavor-to-osc-ba0e7c038df8ecfe.yaml0000664000175000017500000000025500000000000031373 0ustar00zuulzuul00000000000000--- features: - | The command ``trove resize-instance`` is now available to use in the python-openstackclient CLI as ``openstack database instance resize flavor`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-resize-volume-to-osc-aa5eaabb8f25edec.yaml0000664000175000017500000000025300000000000031535 0ustar00zuulzuul00000000000000--- features: - | The command ``trove resize-volume`` is now available to use in the python-openstackclient CLI as ``openstack database instance resize volume`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-restart-instance-to-osc-760c292b5b5c95de.yaml0000664000175000017500000000023700000000000031574 0ustar00zuulzuul00000000000000--- features: - | The command ``trove restart`` is now available to use in the python-openstackclient CLI as ``openstack database instance restart`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-root-disable-to-osc-52d81d96ec7e4e54.yaml0000664000175000017500000000024100000000000030675 0ustar00zuulzuul00000000000000--- features: - | The command ``trove root-disable`` is now available to use in the python-openstackclient CLI as ``openstack database root disable``. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-root-enable-to-osc-e3a087cc6f10a6f9.yaml0000664000175000017500000000023700000000000030571 0ustar00zuulzuul00000000000000--- features: - | The command ``trove root-enable`` is now available to use in the python-openstackclient CLI as ``openstack database root enable``. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-root-show-to-osc-17e49422c5dc71de.yaml0000664000175000017500000000023300000000000030240 0ustar00zuulzuul00000000000000--- features: - | The command ``trove root-show`` is now available to use in the python-openstackclient CLI as ``openstack database root show``. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-update-to-osc-05eb21c5fa18a788.yaml0000664000175000017500000000023600000000000027561 0ustar00zuulzuul00000000000000--- features: - | The command ``trove update`` is now available to use in the python-openstackclient CLI as ``openstack database instance update``. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-upgrade-to-osc-837461ff1d588be2.yaml0000664000175000017500000000050100000000000027656 0ustar00zuulzuul00000000000000--- features: - | The command ``trove upgrade`` is now available to use in the python-openstackclient CLI as ``openstack database instance upgrade``. - | The command ``trove cluster-upgrade`` is now available to use in the python-openstackclient CLI as ``openstack database cluster upgrade``. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-user-access-related-to-osc-ae7da3a8f5fbdd6a.yaml0000664000175000017500000000051400000000000032512 0ustar00zuulzuul00000000000000--- features: - The command ``trove user-grant-access`` and ``trove user-revoke-access`` and ``trove user-show-access`` are now available to use in the python-openstackclient CLI as ``openstack database user grant access`` and ``openstack database user revoke access`` and ``openstack database user show access`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-user-create-to-osc-03158640dcaa8a0e.yaml0000664000175000017500000000023600000000000030501 0ustar00zuulzuul00000000000000--- features: - | The command ``trove user-create`` is now available to use in the python-openstackclient CLI as ``openstack database user create`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-user-delete-to-osc-35cb82b041ee2b48.yaml0000664000175000017500000000023600000000000030506 0ustar00zuulzuul00000000000000--- features: - | The command ``trove user-delete`` is now available to use in the python-openstackclient CLI as ``openstack database user delete`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-user-show-to-osc-6ef3db10a82f1f46.yaml0000664000175000017500000000023000000000000030303 0ustar00zuulzuul00000000000000--- features: - The command ``trove user-show`` is now available to use in the python-openstackclient CLI as ``openstack database user show`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add-user-update-attributes-to-osc-5adfffe826a62f3b.yaml0000664000175000017500000000027000000000000033145 0ustar00zuulzuul00000000000000--- features: - | The command ``trove user-update-attributes`` is now available to use in the python-openstackclient CLI as ``openstack database user update attributes`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add_mod_inst_count-ce532a2187b.yaml0000664000175000017500000000022000000000000027424 0ustar00zuulzuul00000000000000--- fixes: - Add module-instance-count support to list a count of all instances having a specific module applied. Bug 1554900 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/add_module_reapply-7645e34256d4c.yaml0000664000175000017500000000023600000000000027533 0ustar00zuulzuul00000000000000--- fixes: - Add module-reapply command to facilitate applying a module again to all instances where it was previously applied. Bug 1554903 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/allow-backup-name-in-create-1a9e85978a3ab8bc.yaml0000664000175000017500000000012500000000000031602 0ustar00zuulzuul00000000000000--- fixes: - Allow use of backup name in trove create when restoring a backup. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/bulk-delete-database-instances-7938121487fb11e9.yaml0000664000175000017500000000010100000000000032055 0ustar00zuulzuul00000000000000features: - Allow users to delete database instances in batch. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/cli-configuration-name-allowed-747c5d6f2d1f8c7a.yaml0000664000175000017500000000044200000000000032420 0ustar00zuulzuul00000000000000--- fixes: - configuration-* cli commands now allow name of configuration group entered instead of just the configuration id. This will allow a user to specify the configuration group name or the id to use for all the cli commands related to configuration groups. Bug 1505529 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/cluster-upgrade-d58d1fc4b8da0a03.yaml0000664000175000017500000000016200000000000027602 0ustar00zuulzuul00000000000000features: - Added cluster-upgrade command to upgrade all instances in a cluster to a new datastore version. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/datastore-specific-api-extensions-973b455a9922d072.yaml0000664000175000017500000000012000000000000032644 0ustar00zuulzuul00000000000000--- features: - Support was added to manage users and databases for clusters. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/datastore-version-volume-types-62556ce5917195fd.yaml0000664000175000017500000000013000000000000032340 0ustar00zuulzuul00000000000000--- features: - Added support for listing volume types for a given datastore version. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/drop-py-2-7-4ca3cf6a8ab8ca34.yaml0000664000175000017500000000033500000000000026456 0ustar00zuulzuul00000000000000--- upgrade: - | Python 2.7 support has been dropped. Last release of python-troveclient to support py2.7 is OpenStack Train. The minimum version of Python now supported by python-troveclient is Python 3.6. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/drop-python-3-6-and-3-7-8e07d44dd0e12619.yaml0000664000175000017500000000020100000000000030112 0ustar00zuulzuul00000000000000--- upgrade: - | Python 3.6 & 3.7 support has been dropped. The minimum version of Python now supported is Python 3.8. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/fix-config-param-list-output-27bf30fce5388d4b.yaml0000664000175000017500000000023600000000000032072 0ustar00zuulzuul00000000000000--- fixes: - The CLI output from configuration-parameter-list was fixed to properly display the 'Min Size' and 'Max Size' values. Bug 1572272 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/fix-output-of-cluster-create-584d85ffe1129d57.yaml0000664000175000017500000000022100000000000031750 0ustar00zuulzuul00000000000000--- fixes: - Fixed CLI output of cluster-create to only print pertinent information so it is consistent with cluster-show. Bug 1563504 ././@PaxHeader0000000000000000000000000000021100000000000011447 xustar0000000000000000115 path=python-troveclient-8.6.0/releasenotes/notes/fix-wrong-datastore-flavors-args-in-osc-e0322cb5424f8c1b.yaml 22 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/fix-wrong-datastore-flavors-args-in-osc-e0322cb5424f8c1b0000664000175000017500000000042000000000000033064 0ustar00zuulzuul00000000000000--- fixes: - | [`bug 1794663 `_] Fixed wrong keyword arguments to make ``openstack database flavor list`` command work properly with ``--datastore-type`` and ``--datastore-version-id`` options. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/fix_admin_keystoneauth1-ed534462434.yaml0000664000175000017500000000027500000000000030271 0ustar00zuulzuul00000000000000--- fixes: - Having the CLI command display non-redundant information for module-list when invoked with admin privileges now works with both keystone V2 and V3. Bug 1622019 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/flavor-list-disk-befd656f86592af1.yaml0000664000175000017500000000006000000000000027633 0ustar00zuulzuul00000000000000--- other: - Add disk column in flavor-list. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/flavor-list-ephemeral-de4eee3a30b09b64.yaml0000664000175000017500000000010100000000000030673 0ustar00zuulzuul00000000000000--- other: - Add ephemeral column in flavor-list Bug 1617980. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/flavor-list-vcpu-d3fd769a137e307c.yaml0000664000175000017500000000007400000000000027572 0ustar00zuulzuul00000000000000--- other: - Add vCPUs column in flavor-list Bug 1261876. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/force_delete-2d6bb5f99fe821c5.yaml0000664000175000017500000000037100000000000027063 0ustar00zuulzuul00000000000000features: - The reset-status command will set the task and status of an instance to ERROR after which it can be deleted. - The force-delete command will allow the deletion of an instance even if the instance is stuck in BUILD state. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/get-all-database-instance-admin-3f1b83a487dd11e9.yaml0000664000175000017500000000021000000000000032322 0ustar00zuulzuul00000000000000features: - Admin user can use ``openstack database instance list --all-projects`` to get database instances of all the projects. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/image-upgrade-dfa20861d756532d.yaml0000664000175000017500000000023300000000000026774 0ustar00zuulzuul00000000000000--- features: - Add a new trove upgrade CLI command and a new Instances.upgrade python API method to implement the new Instance Upgrade feature. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/incremental_backup-c18804d6277adf62.yaml0000664000175000017500000000035700000000000030127 0ustar00zuulzuul00000000000000features: - The --incremental flag for backup-create will add the abiility to create incremental backup based on last full or incremental backup. If no full or incremental backup exists a new full backup will be created. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/locality-support-for-clusters-7e89f9ddbe5f4131.yaml0000664000175000017500000000037100000000000032427 0ustar00zuulzuul00000000000000--- features: - A --locality flag was added to the trove cluster-create command to allow a user to specify whether instances in a cluster should be on the same hypervisor (affinity) or on different hypervisors (anti-affinity). ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/locality-support-for-replication-5834f1a2dcaf6883.yaml0000664000175000017500000000034700000000000033005 0ustar00zuulzuul00000000000000--- features: - A --locality flag was added to the trove create command to allow a user to specify whether new replicas should be on the same hypervisor (affinity) or on different hypervisors (anti-affinity). ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/module-ordering-2d1e1a3c37c30c71.yaml0000664000175000017500000000034600000000000027425 0ustar00zuulzuul00000000000000--- features: - Modules can now be applied in a consistent order, based on the new 'priority_apply' and 'apply_order' attributes available to module-create and module-update. Blueprint module-management-ordering ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/module-support-for-clusters-87b41dd7648275bf.yaml0000664000175000017500000000063300000000000031733 0ustar00zuulzuul00000000000000--- features: - Support was added for modules in cluster-grow and. the CLI consolidated to look more like cluster-create. This means that not including --instance on cluster-grow now raises a MissingArgs exception. Not including a required option in the --instance argument also raises MissingArgs now (instead of the previously raised CommandError). Bug 15778917 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/module_update_all_ds-f5cdbb71462e3de4.yaml0000664000175000017500000000017700000000000030660 0ustar00zuulzuul00000000000000--- fixes: - Updating a module with all_datastores and all_datastore_versions now works correctly. Bug 1612430 ././@PaxHeader0000000000000000000000000000021600000000000011454 xustar0000000000000000120 path=python-troveclient-8.6.0/releasenotes/notes/mongo-cluster-create-use-extended-perperties-be7c075585dc709a.yaml 22 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/mongo-cluster-create-use-extended-perperties-be7c075585d0000664000175000017500000000050000000000000033453 0ustar00zuulzuul00000000000000--- features: - | User can specify the number and volume of mongos/configserver by using the extends argument when creating mongodb cluster. Currently, the supported parameters are, num_configsvr, num_mongos, configsvr_volume_size, configsvr_volume_type, mongos_volume_size and mongos_volume_type. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/multi-region-ec516da866def1ed.yaml0000664000175000017500000000027200000000000027211 0ustar00zuulzuul00000000000000features: - Adds --region option to create and cluster-create APIs. For now, these options are excluded from CLI help strings. This is the first step in multiregion support. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/paginate-config-list-c311ce3c5394d437.yaml0000664000175000017500000000017500000000000030273 0ustar00zuulzuul00000000000000features: - Added pagination support (limit and marker) to the CLI for configuration-list and configuration-instances. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/persist-error-message-cda8dfe485fe92ba.yaml0000664000175000017500000000013400000000000031120 0ustar00zuulzuul00000000000000--- features: - Support added for error messages when running the Trove show command. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/quota-upgrade-aed30d50c1f58502.yaml0000664000175000017500000000027600000000000027116 0ustar00zuulzuul00000000000000features: - Adds quota-show and quota-update commands to show the limits for all resources and to change the limit for a single resource. These commands require admin privileges. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/remove-rax-references-in-client-33551aa2bb25181b.yaml0000664000175000017500000000023400000000000032326 0ustar00zuulzuul00000000000000--- fixes: - Remove all the rax references in the client. Use the rackspace plugin for auth if you want to use rax auth with troveclient. Bug 1401804 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/run-an-action-on-many-resources-41bbe191318b97dd.yaml0000664000175000017500000000041400000000000032404 0ustar00zuulzuul00000000000000--- features: - This change defines do_action_on_many function as a helper to run an action on many resources (like trove delete). Use command like ``trove delete instance1 instance2 ...`` to delete many instances and the same as cluster-delete. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/scheduled-backups-49729ce37e586463.yaml0000664000175000017500000000014500000000000027543 0ustar00zuulzuul00000000000000features: - Implements trove schedule-* and execution-* commands to support scheduled backups. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/train-01-backup-filtering-43dc1912c72f11e9.yaml0000664000175000017500000000012700000000000031043 0ustar00zuulzuul00000000000000--- features: - Support ``--instance-id`` and ``--all-projects`` for getting backups.././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/train-02-public-instance-642d6490d47811e9.yaml0000664000175000017500000000012700000000000030553 0ustar00zuulzuul00000000000000--- features: - Support ``--is-public`` and ``--allowed-cidr`` for creating instance.././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/use-i18n-for-shell.py-924c1624e30a6617.yaml0000664000175000017500000000007200000000000030013 0ustar00zuulzuul00000000000000--- other: - Use i18n for shell.py Partial-Bug 1379001. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/use-i18n-for-v1-shell.py-08209f5721f20a1f.yaml0000664000175000017500000000005000000000000030407 0ustar00zuulzuul00000000000000--- other: - Use i18n for v1/shell.py ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/ussuri-01-instance-log-actions-794fced41f9c11ea.yaml0000664000175000017500000000034100000000000032301 0ustar00zuulzuul00000000000000--- features: - | Support following instance log actions: .. code-block:: console openstack database log show openstack database log set [OPTIONS] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/ussuri-02-instance-log-tail-save-0b267a761faa11ea.yaml0000664000175000017500000000040100000000000032417 0ustar00zuulzuul00000000000000--- features: - | Support to show log content and save instance log files: .. code-block:: console openstack database log tail [--lines LINES] openstack database log save [--file FILE] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/ussuri-03-force-delete-instance-1643114c2e1b11ea.yaml0000664000175000017500000000027600000000000032155 0ustar00zuulzuul00000000000000--- features: - | Support to force delete instance. By default, only allowed by admin user. .. code-block:: console openstack database instance delete --force ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/ussuri-04-osc-reboot-instance-760190e02eac11ea.yaml0000664000175000017500000000021000000000000031746 0ustar00zuulzuul00000000000000--- features: - | Support to reboot instance. .. code-block:: console openstack database instance reboot ././@PaxHeader0000000000000000000000000000021000000000000011446 xustar0000000000000000114 path=python-troveclient-8.6.0/releasenotes/notes/ussuri-05-osc-delete-datastore-version-6e03d20430e611ea.yaml 22 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/ussuri-05-osc-delete-datastore-version-6e03d20430e611ea.0000664000175000017500000000024300000000000032626 0ustar00zuulzuul00000000000000--- features: - | Support to delete datastore version by ID. .. code-block:: console openstack datastore version delete ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/ussuri-06-osc-delete-datastore-57bab744345911ea.yaml0000664000175000017500000000026100000000000032046 0ustar00zuulzuul00000000000000--- features: - | Support to delete datastore by ID or name, this is admin action by default. .. code-block:: console openstack datastore delete ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/victoria-01-update-config-group.yaml0000664000175000017500000000043100000000000030043 0ustar00zuulzuul00000000000000--- features: - | Support following commands of updating configuration: .. code-block:: console openstack database configuration parameter set openstack database configuration set --name NAME --description DESC ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/victoria-02-remove-flavor-api.yaml0000664000175000017500000000022600000000000027522 0ustar00zuulzuul00000000000000--- deprecations: - Trove CLI doesn't support to get flavor information more. User needs to provide Nova flavor ID when creating trove instance.././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/victoria-03-replication-cli.yaml0000664000175000017500000000074400000000000027253 0ustar00zuulzuul00000000000000--- features: - | Support following replication commands: .. code-block:: console openstack database instance detach openstack database instance promote openstack database instance eject deprecations: - | The following commands are removed: .. code-block:: console openstack database instance detach replica openstack database instance promote to replica source openstack database instance eject replica source ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/victoria-add-role-for-instances.yaml0000664000175000017500000000017100000000000030207 0ustar00zuulzuul00000000000000--- features: - Add replication 'Role' for listing instances to indicate if the instance is a master or a replica. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/victoria-backup-strategy.yaml0000664000175000017500000000005700000000000027057 0ustar00zuulzuul00000000000000--- features: - Support backup strategy CLI. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/victoria-create-datastore-version.yaml0000664000175000017500000000007600000000000030665 0ustar00zuulzuul00000000000000--- features: - Support to create datastore version in CLI. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/victoria-flavor-optional.yaml0000664000175000017500000000022300000000000027061 0ustar00zuulzuul00000000000000--- features: - Change flavor as an optional parameter (``--flavor``) for creating instance. When creating replicas, flavor is not requried. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/victoria-get-and-update-instance-access.yaml0000664000175000017500000000012100000000000031602 0ustar00zuulzuul00000000000000--- features: - Added support to show and update access settings for instance. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/victoria-rebuild-instance.yaml0000664000175000017500000000023100000000000027174 0ustar00zuulzuul00000000000000--- features: - | Support to rebuild instance. .. code-block:: console openstack database instance rebuild ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/wallaby-bulk-backup-delete.yaml0000664000175000017500000000006500000000000027224 0ustar00zuulzuul00000000000000--- features: - Support deleting backups in batch. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/wallaby-datastore-version-number.yaml0000664000175000017500000000023300000000000030520 0ustar00zuulzuul00000000000000--- features: - Support specifying and showing datastore version number for several resources such as datastore version, instance and configuration. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/wallaby-instance-operating-status.yaml0000664000175000017500000000027600000000000030703 0ustar00zuulzuul00000000000000--- features: - Support ``operating_status`` for listing and showing instance. When instance is created successfully, the ``status`` is ACTIVE and ``operating_status`` is HEALTHY. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/wallaby-project-backups.yaml0000664000175000017500000000014300000000000026655 0ustar00zuulzuul00000000000000--- features: - Support to get backups of a specific project by ``--project-id PROJECT_ID``. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/wallaby-restore-backup-from-remote.yaml0000664000175000017500000000042400000000000030743 0ustar00zuulzuul00000000000000--- features: - In multi-region deployment with geo-replicated Swift, the user can restore a backup in one region by manually specifying the original backup data location created in another region. Instance ID or name is not needed anymore for creating backups. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/wallaby-update-datastore-version.yaml0000664000175000017500000000006600000000000030516 0ustar00zuulzuul00000000000000--- features: - Support updating datastore version. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/notes/xena-support-project-name-quota-cli.yaml0000664000175000017500000000010100000000000031045 0ustar00zuulzuul00000000000000--- features: - Support both project name and ID in quota CLI. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1726234752.4749677 python-troveclient-8.6.0/releasenotes/source/0000775000175000017500000000000000000000000021416 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/source/2023.1.rst0000664000175000017500000000020200000000000022667 0ustar00zuulzuul00000000000000=========================== 2023.1 Series Release Notes =========================== .. release-notes:: :branch: stable/2023.1 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/source/2023.2.rst0000664000175000017500000000020200000000000022670 0ustar00zuulzuul00000000000000=========================== 2023.2 Series Release Notes =========================== .. release-notes:: :branch: stable/2023.2 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/source/2024.1.rst0000664000175000017500000000020200000000000022670 0ustar00zuulzuul00000000000000=========================== 2024.1 Series Release Notes =========================== .. release-notes:: :branch: stable/2024.1 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1726234752.4749677 python-troveclient-8.6.0/releasenotes/source/_static/0000775000175000017500000000000000000000000023044 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/source/_static/.placeholder0000664000175000017500000000000000000000000025315 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1726234752.4749677 python-troveclient-8.6.0/releasenotes/source/_templates/0000775000175000017500000000000000000000000023553 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/source/_templates/.placeholder0000664000175000017500000000000000000000000026024 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/source/conf.py0000664000175000017500000002053700000000000022724 0ustar00zuulzuul00000000000000# -*- coding: utf-8 -*- # # Trove Client Release Notes documentation build configuration file, created by # sphinx-quickstart on Tue Apr 5 19:49:56 2016. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys import os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [] extensions = [ 'openstackdocstheme', 'reno.sphinxext', ] # openstackdocstheme options openstackdocs_repo_name = 'openstack/python-troveclient' openstackdocs_bug_project = 'python-troveclient' openstackdocs_bug_tag = '' openstackdocs_auto_name = False # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = 'Trove Client Release Notes' copyright = '2016, Trove developers' # Release notes are version independent. # The short X.Y version. version = '' # The full version, including alpha/beta/rc tags. release = '' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'native' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'openstackdocs' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. #html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'TroveClientReleaseNotesdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'TroveClientReleaseNotes.tex', 'Trove Client Release Notes Documentation', 'Trove developers', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'troveclientreleasenotes', 'Trove Client Release Notes Documentation', ['Trove developers'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'TroveClientReleaseNotes', 'Trove Client Release Notes Documentation', 'Trove developers', 'TroveClientReleaseNotes', 'OpenStack Database as a Service.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False # -- Options for Internationalization output ------------------------------ locale_dirs = ['locale/'] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/source/index.rst0000664000175000017500000000055200000000000023261 0ustar00zuulzuul00000000000000========================== Trove Client Release Notes ========================== Contents: .. toctree:: :maxdepth: 2 unreleased 2024.1 2023.2 2023.1 zed yoga xena wallaby victoria ussuri train rocky queens pike ocata newton mitaka Indices and tables ================== * :ref:`genindex` * :ref:`search` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/source/mitaka.rst0000664000175000017500000000021100000000000023410 0ustar00zuulzuul00000000000000=========================== Mitaka Series Release Notes =========================== .. release-notes:: :branch: origin/stable/mitaka ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/source/newton.rst0000664000175000017500000000023200000000000023457 0ustar00zuulzuul00000000000000=================================== Newton Series Release Notes =================================== .. release-notes:: :branch: origin/stable/newton ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/source/ocata.rst0000664000175000017500000000023000000000000023232 0ustar00zuulzuul00000000000000=================================== Ocata Series Release Notes =================================== .. release-notes:: :branch: origin/stable/ocata ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/source/pike.rst0000664000175000017500000000021700000000000023100 0ustar00zuulzuul00000000000000=================================== Pike Series Release Notes =================================== .. release-notes:: :branch: stable/pike ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/source/queens.rst0000664000175000017500000000022300000000000023445 0ustar00zuulzuul00000000000000=================================== Queens Series Release Notes =================================== .. release-notes:: :branch: stable/queens ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/source/rocky.rst0000664000175000017500000000022100000000000023272 0ustar00zuulzuul00000000000000=================================== Rocky Series Release Notes =================================== .. release-notes:: :branch: stable/rocky ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/source/train.rst0000664000175000017500000000017600000000000023271 0ustar00zuulzuul00000000000000========================== Train Series Release Notes ========================== .. release-notes:: :branch: stable/train ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/source/unreleased.rst0000664000175000017500000000015300000000000024276 0ustar00zuulzuul00000000000000============================ Current Series Release Notes ============================ .. release-notes:: ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/source/ussuri.rst0000664000175000017500000000020200000000000023474 0ustar00zuulzuul00000000000000=========================== Ussuri Series Release Notes =========================== .. release-notes:: :branch: stable/ussuri ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/source/victoria.rst0000664000175000017500000000022000000000000023762 0ustar00zuulzuul00000000000000============================= Victoria Series Release Notes ============================= .. release-notes:: :branch: unmaintained/victoria ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/source/wallaby.rst0000664000175000017500000000021400000000000023600 0ustar00zuulzuul00000000000000============================ Wallaby Series Release Notes ============================ .. release-notes:: :branch: unmaintained/wallaby ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/source/xena.rst0000664000175000017500000000020000000000000023073 0ustar00zuulzuul00000000000000========================= Xena Series Release Notes ========================= .. release-notes:: :branch: unmaintained/xena ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/source/yoga.rst0000664000175000017500000000020000000000000023077 0ustar00zuulzuul00000000000000========================= Yoga Series Release Notes ========================= .. release-notes:: :branch: unmaintained/yoga ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/releasenotes/source/zed.rst0000664000175000017500000000017400000000000022734 0ustar00zuulzuul00000000000000======================== Zed Series Release Notes ======================== .. release-notes:: :branch: unmaintained/zed ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/requirements.txt0000664000175000017500000000111000000000000020702 0ustar00zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. pbr!=2.1.0,>=2.0.0 # Apache-2.0 PrettyTable>=0.7.2 # BSD requests>=2.14.2 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 keystoneauth1>=3.4.0 # Apache-2.0 python-swiftclient>=3.2.0 # Apache-2.0 python-mistralclient!=3.2.0,>=3.1.0 # Apache-2.0 osc-lib>=1.8.0 # Apache-2.0 python-openstackclient>=3.12.0 # Apache-2.0 stevedore>=2.0.1 # Apache-2.0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/run_local.sh0000775000175000017500000000263000000000000017743 0ustar00zuulzuul00000000000000#!/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 <=3.8 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 :: 3 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 [files] packages = troveclient [entry_points] console_scripts = trove = troveclient.shell:main openstack.cli.extension = database = troveclient.osc.plugin openstack.database.v1 = database_backup_create= troveclient.osc.v1.database_backups:CreateDatabaseBackup database_backup_delete = troveclient.osc.v1.database_backups:DeleteDatabaseBackup database_backup_execution_delete = troveclient.osc.v1.database_backups:DeleteDatabaseBackupExecution database_backup_list = troveclient.osc.v1.database_backups:ListDatabaseBackups database_backup_list_instance = troveclient.osc.v1.database_backups:ListDatabaseInstanceBackups database_backup_show = troveclient.osc.v1.database_backups:ShowDatabaseBackup database_cluster_create = troveclient.osc.v1.database_clusters:CreateDatabaseCluster database_cluster_delete = troveclient.osc.v1.database_clusters:DeleteDatabaseCluster database_cluster_force_delete = troveclient.osc.v1.database_clusters:ForceDeleteDatabaseCluster database_cluster_grow = troveclient.osc.v1.database_clusters:GrowDatabaseCluster database_cluster_list = troveclient.osc.v1.database_clusters:ListDatabaseClusters database_cluster_list_instances = troveclient.osc.v1.database_clusters:ListDatabaseClusterInstances database_cluster_modules = troveclient.osc.v1.database_clusters:ListDatabaseClusterModules database_cluster_reset_status = troveclient.osc.v1.database_clusters:ResetDatabaseClusterStatus database_cluster_show = troveclient.osc.v1.database_clusters:ShowDatabaseCluster database_cluster_shrink = troveclient.osc.v1.database_clusters:ShrinkDatabaseCluster database_cluster_upgrade = troveclient.osc.v1.database_clusters:UpgradeDatabaseCluster database_configuration_attach = troveclient.osc.v1.database_configurations:AttachDatabaseConfiguration database_configuration_create = troveclient.osc.v1.database_configurations:CreateDatabaseConfiguration database_configuration_default = troveclient.osc.v1.database_configurations:DefaultDatabaseConfiguration database_configuration_delete = troveclient.osc.v1.database_configurations:DeleteDatabaseConfiguration database_configuration_detach = troveclient.osc.v1.database_configurations:DetachDatabaseConfiguration database_configuration_instances = troveclient.osc.v1.database_configurations:ListDatabaseConfigurationInstances database_configuration_list = troveclient.osc.v1.database_configurations:ListDatabaseConfigurations database_configuration_set = troveclient.osc.v1.database_configurations:UpdateDatabaseConfiguration database_configuration_parameter_set = troveclient.osc.v1.database_configurations:SetDatabaseConfiguration database_configuration_parameter_list = troveclient.osc.v1.database_configurations:ListDatabaseConfigurationParameters database_configuration_parameter_show = troveclient.osc.v1.database_configurations:ShowDatabaseConfigurationParameter database_configuration_show = troveclient.osc.v1.database_configurations:ShowDatabaseConfiguration database_db_create = troveclient.osc.v1.databases:CreateDatabase database_db_delete = troveclient.osc.v1.databases:DeleteDatabase database_db_list = troveclient.osc.v1.databases:ListDatabases database_flavor_list = troveclient.osc.v1.database_flavors:ListDatabaseFlavors database_flavor_show = troveclient.osc.v1.database_flavors:ShowDatabaseFlavor database_instance_create = troveclient.osc.v1.database_instances:CreateDatabaseInstance database_instance_delete = troveclient.osc.v1.database_instances:DeleteDatabaseInstance database_instance_force_delete = troveclient.osc.v1.database_instances:ForceDeleteDatabaseInstance database_instance_list = troveclient.osc.v1.database_instances:ListDatabaseInstances database_instance_reset_status = troveclient.osc.v1.database_instances:ResetDatabaseInstanceStatus database_instance_resize_flavor = troveclient.osc.v1.database_instances:ResizeDatabaseInstanceFlavor database_instance_resize_volume = troveclient.osc.v1.database_instances:ResizeDatabaseInstanceVolume database_instance_restart = troveclient.osc.v1.database_instances:RestartDatabaseInstance database_instance_show = troveclient.osc.v1.database_instances:ShowDatabaseInstance database_instance_update = troveclient.osc.v1.database_instances:UpdateDatabaseInstance database_instance_upgrade = troveclient.osc.v1.database_instances:UpgradeDatabaseInstance database_instance_reboot = troveclient.osc.v1.database_instances:RebootDatabaseInstance database_instance_rebuild = troveclient.osc.v1.database_instances:RebuildDatabaseInstance database_instance_detach = troveclient.osc.v1.database_instances:DetachDatabaseInstanceReplica database_instance_eject = troveclient.osc.v1.database_instances:EjectDatabaseInstanceReplicaSource database_instance_promote = troveclient.osc.v1.database_instances:PromoteDatabaseInstanceToReplicaSource database_limit_list = troveclient.osc.v1.database_limits:ListDatabaseLimits database_log_list = troveclient.osc.v1.database_logs:ListDatabaseLogs database_log_set = troveclient.osc.v1.database_logs:SetDatabaseInstanceLog database_log_show = troveclient.osc.v1.database_logs:ShowDatabaseInstanceLog database_log_tail = troveclient.osc.v1.database_logs:ShowDatabaseInstanceLogContents database_log_save = troveclient.osc.v1.database_logs:SaveDatabaseInstanceLog database_quota_show = troveclient.osc.v1.database_quota:ShowDatabaseQuota database_quota_update = troveclient.osc.v1.database_quota:UpdateDatabaseQuota database_root_disable = troveclient.osc.v1.database_root:DisableDatabaseRoot database_root_enable = troveclient.osc.v1.database_root:EnableDatabaseRoot database_root_show = troveclient.osc.v1.database_root:ShowDatabaseRoot database_user_create = troveclient.osc.v1.database_users:CreateDatabaseUser database_user_delete = troveclient.osc.v1.database_users:DeleteDatabaseUser database_user_grant_access = troveclient.osc.v1.database_users:GrantDatabaseUserAccess database_user_list = troveclient.osc.v1.database_users:ListDatabaseUsers database_user_revoke_access = troveclient.osc.v1.database_users:RevokeDatabaseUserAccess database_user_show = troveclient.osc.v1.database_users:ShowDatabaseUser database_user_show_access = troveclient.osc.v1.database_users:ShowDatabaseUserAccess database_user_update_attributes = troveclient.osc.v1.database_users:UpdateDatabaseUserAttributes datastore_list = troveclient.osc.v1.datastores:ListDatastores datastore_show = troveclient.osc.v1.datastores:ShowDatastore datastore_delete = troveclient.osc.v1.datastores:DeleteDatastore datastore_version_create = troveclient.osc.v1.datastores:CreateDatastoreVersion datastore_version_list = troveclient.osc.v1.datastores:ListDatastoreVersions datastore_version_show = troveclient.osc.v1.datastores:ShowDatastoreVersion datastore_version_delete = troveclient.osc.v1.datastores:DeleteDatastoreVersion datastore_version_set = troveclient.osc.v1.datastores:UpdateDatastoreVersion database_backup_strategy_list = troveclient.osc.v1.database_backup_strategy:ListDatabaseBackupStrategies database_backup_strategy_create = troveclient.osc.v1.database_backup_strategy:CreateDatabaseBackupStrategy database_backup_strategy_delete = troveclient.osc.v1.database_backup_strategy:DeleteDatabaseBackupStrategy [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/setup.py0000664000175000017500000000127100000000000017140 0ustar00zuulzuul00000000000000# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import setuptools setuptools.setup( setup_requires=['pbr>=2.0.0'], pbr=True) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/test-requirements.txt0000664000175000017500000000101600000000000021664 0ustar00zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. hacking>=3.0.1,<3.1.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD oslotest>=3.2.0 # Apache-2.0 python-openstackclient>=3.12.0 # Apache-2.0 requests-mock>=1.2.0 # Apache-2.0 stestr>=2.0.0 # Apache-2.0 testscenarios>=0.4 # Apache-2.0/BSD testtools>=2.2.0 # MIT httplib2>=0.9.1 # MIT ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1726234752.4749677 python-troveclient-8.6.0/tools/0000775000175000017500000000000000000000000016565 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/tools/install_venv_common.py0000664000175000017500000001344000000000000023215 0ustar00zuulzuul00000000000000# 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 """ import optparse import os import subprocess import sys class InstallVenv(object): def __init__(self, root, venv, requirements, test_requirements, py_version, project): self.root = root self.venv = venv self.requirements = requirements self.test_requirements = test_requirements self.py_version = py_version self.project = project def die(self, message, *args): print(message % args, file=sys.stderr) sys.exit(1) def check_python_version(self): if sys.version_info < (2, 6): self.die("Need Python Version >= 2.6") def run_command_with_code(self, cmd, redirect_output=True, check_exit_code=True): """Runs a command in an out-of-process shell. Returns the output of that command. Working directory is self.root. """ if redirect_output: stdout = subprocess.PIPE else: stdout = None proc = subprocess.Popen(cmd, cwd=self.root, stdout=stdout) output = proc.communicate()[0] if check_exit_code and proc.returncode != 0: self.die('Command "%s" failed.\n%s', ' '.join(cmd), output) return (output, proc.returncode) def run_command(self, cmd, redirect_output=True, check_exit_code=True): return self.run_command_with_code(cmd, redirect_output, check_exit_code)[0] def get_distro(self): if (os.path.exists('/etc/fedora-release') or os.path.exists('/etc/redhat-release')): return Fedora( self.root, self.venv, self.requirements, self.test_requirements, self.py_version, self.project) else: return Distro( self.root, self.venv, self.requirements, self.test_requirements, self.py_version, self.project) def check_dependencies(self): self.get_distro().install_virtualenv() def create_virtualenv(self, no_site_packages=True): """Creates the virtual environment and installs PIP. Creates the virtual environment and installs PIP only into the virtual environment. """ if not os.path.isdir(self.venv): print('Creating venv...', end=' ') if no_site_packages: self.run_command(['virtualenv', '-q', '--no-site-packages', self.venv]) else: self.run_command(['virtualenv', '-q', self.venv]) print('done.') else: print("venv already exists...") pass def pip_install(self, *args): self.run_command(['tools/with_venv.sh', 'pip', 'install', '--upgrade'] + list(args), redirect_output=False) def install_dependencies(self): print('Installing dependencies with pip (this can take a while)...') # First things first, make sure our venv has the latest pip and # setuptools and pbr self.pip_install('pip>=1.4') self.pip_install('setuptools') self.pip_install('pbr') self.pip_install('-r', self.requirements, '-r', self.test_requirements) def parse_args(self, argv): """Parses command-line arguments.""" parser = optparse.OptionParser() parser.add_option('-n', '--no-site-packages', action='store_true', help="Do not inherit packages from global Python " "install.") return parser.parse_args(argv[1:])[0] class Distro(InstallVenv): def check_cmd(self, cmd): return bool(self.run_command(['which', cmd], check_exit_code=False).strip()) def install_virtualenv(self): if self.check_cmd('virtualenv'): return if self.check_cmd('easy_install'): print('Installing virtualenv via easy_install...', end=' ') if self.run_command(['easy_install', 'virtualenv']): print('Succeeded') return else: print('Failed') self.die('ERROR: virtualenv not found.\n\n%s development' ' requires virtualenv, please install it using your' ' favorite package management tool' % self.project) class Fedora(Distro): """This covers all Fedora-based distributions. Includes: Fedora, RHEL, CentOS, Scientific Linux """ def check_pkg(self, pkg): return self.run_command_with_code(['rpm', '-q', pkg], check_exit_code=False)[1] == 0 def install_virtualenv(self): if self.check_cmd('virtualenv'): return if not self.check_pkg('python-virtualenv'): self.die("Please install 'python-virtualenv'.") super(Fedora, self).install_virtualenv() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/tox.ini0000664000175000017500000000346300000000000016746 0ustar00zuulzuul00000000000000# Python Trove Client [tox] envlist = py39,pep8 minversion = 3.18.0 skipsdist = True ignore_basepython_conflict = True [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 usedevelop = True install_command = pip install {opts} {packages} deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = find . -type f -name "*.pyc" -delete stestr run {posargs} allowlist_externals = find rm [testenv:debug] commands = oslo_debug_helper -t troveclient/tests {posargs} [testenv:pep8] commands = flake8 [testenv:venv] commands = {posargs} [testenv:cover] setenv = PYTHON=coverage run --source troveclient --parallel-mode commands = coverage erase stestr run {posargs} coverage combine coverage html -d cover coverage xml -o cover/coverage.xml coverage report [testenv:docs] deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = rm -rf doc/html doc/build sphinx-build -W -b html doc/source doc/build/html [testenv:releasenotes] deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/doc/requirements.txt commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [flake8] enable-extensions = H106,H203,H904 ignore = H202,H405,H501,W504,H306 show-source = True exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,releasenotes ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1726234752.4789677 python-troveclient-8.6.0/troveclient/0000775000175000017500000000000000000000000017763 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/__init__.py0000664000175000017500000000173200000000000022077 0ustar00zuulzuul00000000000000# 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. import pbr.version __all__ = ['__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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/_i18n.py0000664000175000017500000000226500000000000021260 0ustar00zuulzuul00000000000000# Copyright 2016 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """oslo.i18n integration module. See https://docs.openstack.org/oslo.i18n/latest/user/index.html . """ import oslo_i18n DOMAIN = "troveclient" _translators = oslo_i18n.TranslatorFactory(domain=DOMAIN) # The primary translation function using the well-known name "_" _ = _translators.primary # The contextual translation function using the name "_C" # requires oslo.i18n >=2.1.0 _C = _translators.contextual_form # The plural translation function using the name "_P" # requires oslo.i18n >=2.1.0 _P = _translators.plural_form def get_available_languages(): return oslo_i18n.get_available_languages(DOMAIN) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1726234752.4789677 python-troveclient-8.6.0/troveclient/apiclient/0000775000175000017500000000000000000000000021733 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/apiclient/__init__.py0000664000175000017500000000000000000000000024032 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/apiclient/auth.py0000664000175000017500000001547700000000000023264 0ustar00zuulzuul00000000000000# Copyright 2013 OpenStack Foundation # Copyright 2013 Spanish National Research Council. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # E0202: An attribute inherited from %s hide this method # pylint: disable=E0202 import abc import argparse import os from stevedore import extension from troveclient.apiclient import exceptions _discovered_plugins = {} def discover_auth_systems(): """Discover the available auth-systems. This won't take into account the old style auth-systems. """ global _discovered_plugins _discovered_plugins = {} def add_plugin(ext): _discovered_plugins[ext.name] = ext.plugin ep_namespace = "troveclient.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.items(): group = parser.add_argument_group( "Auth-system '%s' options" % name, conflict_handler="resolve") auth_plugin.add_opts(group) def load_plugin(auth_system): try: plugin_class = _discovered_plugins[auth_system] except KeyError: raise exceptions.AuthSystemNotFound(auth_system) return plugin_class(auth_system=auth_system) def load_plugin_from_args(args): """Load required plugin and populate it with options. Try to guess auth system if it is not specified. Systems are tried in alphabetical order. :type args: argparse.Namespace :raises: AuthPluginOptionsMissing """ 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.keys()): 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(metaclass=abc.ABCMeta): """Base class for authentication plugins. An authentication plugin needs to override at least the authenticate method to be a valid plugin. """ auth_system = None opt_names = [] common_opt_names = [ "auth_system", "username", "password", "tenant_name", "token", "auth_url", ] def __init__(self, auth_system=None, **kwargs): self.auth_system = auth_system or self.auth_system self.opts = dict((name, kwargs.get(name)) for name in self.opt_names) @staticmethod def _parser_add_opt(parser, opt): """Add an option to parser in two variants. :param opt: option name (with underscores) """ dashed_opt = opt.replace("_", "-") env_var = "OS_%s" % opt.upper() arg_default = os.environ.get(env_var, "") arg_help = "Defaults to env[%s]." % env_var parser.add_argument( "--os-%s" % dashed_opt, metavar="<%s>" % dashed_opt, default=arg_default, help=arg_help) parser.add_argument( "--os_%s" % opt, metavar="<%s>" % dashed_opt, help=argparse.SUPPRESS) @classmethod def add_opts(cls, parser): """Populate the parser with the options for this plugin. """ for opt in cls.opt_names: # use `BaseAuthPlugin.common_opt_names` since it is never # changed in child classes if opt not in BaseAuthPlugin.common_opt_names: cls._parser_add_opt(parser, opt) @classmethod def add_common_opts(cls, parser): """Add options that are common for several plugins. """ for opt in cls.common_opt_names: cls._parser_add_opt(parser, opt) @staticmethod def get_opt(opt_name, args): """Return option name and value. :param opt_name: name of the option, e.g., "username" :param args: parsed arguments """ return (opt_name, getattr(args, "os_%s" % opt_name, None)) def parse_opts(self, args): """Parse the actual auth-system options if any. This method is expected to populate the attribute `self.opts` with a dict containing the options and values needed to make authentication. """ self.opts.update(dict(self.get_opt(opt_name, args) for opt_name in self.opt_names)) def authenticate(self, http_client): """Authenticate using plugin defined method. The method usually analyses `self.opts` and performs a request to authentication server. :param http_client: client object that needs authentication :type http_client: troveclient.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 """ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/apiclient/base.py0000664000175000017500000003733600000000000023233 0ustar00zuulzuul00000000000000# Copyright 2010 Jacob Kaplan-Moss # Copyright 2011 OpenStack Foundation # Copyright 2012 Grid Dynamics # Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Base utilities to build API operation managers and objects on top of. """ # E1102: %s is not callable # pylint: disable=E1102 import abc import copy from oslo_utils import reflection from oslo_utils import strutils from urllib import parse from troveclient.apiclient import exceptions 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, metaclass=abc.ABCMeta): """Manager with additional `find()`/`findall()` methods.""" @abc.abstractmethod def list(self): pass def find(self, **kwargs): """Find a single item with attributes matching ``**kwargs``. This isn't very efficient: it loads the entire list then filters on the Python side. """ matches = self.findall(**kwargs) num_matches = len(matches) if num_matches == 0: msg = "No %s matching %s." % (self.resource_class.__name__, kwargs) raise exceptions.NotFound(msg) elif num_matches > 1: raise exceptions.NoUniqueMatch() else: return matches[0] def findall(self, **kwargs): """Find all items with attributes matching ``**kwargs``. This isn't very efficient: it loads the entire list then filters on the Python side. """ found = [] searches = kwargs.items() for obj in self.list(): try: if all(getattr(obj, attr) == value for (attr, value) in searches): found.append(obj) except AttributeError: continue return found class CrudManager(BaseManager): """Base manager class for manipulating entities. Children of this class are expected to define a `collection_key` and `key`. - `collection_key`: Usually a plural noun by convention (e.g. `entities`); used to refer collections in both URL's (e.g. `/v3/entities`) and JSON objects containing a list of member resources (e.g. `{'entities': [{}, {}, {}]}`). - `key`: Usually a singular noun by convention (e.g. `entity`); used to refer to an individual member of the collection. """ collection_key = None key = None def build_url(self, base_url=None, **kwargs): """Builds a resource URL for the given kwargs. Given an example collection where `collection_key = 'entities'` and `key = 'entity'`, the following URL's could be generated. By default, the URL will represent a collection of entities, e.g.:: /entities If kwargs contains an `entity_id`, then the URL will represent a specific member, e.g.:: /entities/{entity_id} :param base_url: if provided, the generated URL will be appended to it """ url = base_url if base_url is not None else '' url += '/%s' % self.collection_key # do we have a specific entity? entity_id = kwargs.get('%s_id' % self.key) if entity_id is not None: url += '/%s' % entity_id return url def _filter_kwargs(self, kwargs): """Drop null values and handle ids.""" for key, ref in kwargs.copy().items(): if ref is None: kwargs.pop(key) else: if isinstance(ref, Resource): kwargs.pop(key) kwargs['%s_id' % key] = getid(ref) return kwargs def create(self, **kwargs): kwargs = self._filter_kwargs(kwargs) return self._post( self.build_url(**kwargs), {self.key: kwargs}, self.key) def get(self, **kwargs): kwargs = self._filter_kwargs(kwargs) return self._get( self.build_url(**kwargs), self.key) def head(self, **kwargs): kwargs = self._filter_kwargs(kwargs) return self._head(self.build_url(**kwargs)) def list(self, base_url=None, **kwargs): """List the collection. :param base_url: if provided, the generated URL will be appended to it """ kwargs = self._filter_kwargs(kwargs) return self._list( '%(base_url)s%(query)s' % { 'base_url': self.build_url(base_url=base_url, **kwargs), 'query': '?%s' % parse.urlencode(kwargs) if kwargs else '', }, self.collection_key) def put(self, base_url=None, **kwargs): """Update an element. :param base_url: if provided, the generated URL will be appended to it """ kwargs = self._filter_kwargs(kwargs) return self._put(self.build_url(base_url=base_url, **kwargs)) def update(self, **kwargs): kwargs = self._filter_kwargs(kwargs) params = kwargs.copy() params.pop('%s_id' % self.key) return self._patch( self.build_url(**kwargs), {self.key: params}, self.key) def delete(self, **kwargs): kwargs = self._filter_kwargs(kwargs) return self._delete( self.build_url(**kwargs)) def find(self, base_url=None, **kwargs): """Find a single item with attributes matching ``**kwargs``. :param base_url: if provided, the generated URL will be appended to it """ kwargs = self._filter_kwargs(kwargs) rl = self._list( '%(base_url)s%(query)s' % { 'base_url': self.build_url(base_url=base_url, **kwargs), 'query': '?%s' % parse.urlencode(kwargs) if kwargs else '', }, self.collection_key) num = len(rl) if num == 0: msg = "No %s matching %s." % (self.resource_class.__name__, kwargs) raise exceptions.NotFound(404, msg) elif num > 1: raise exceptions.NoUniqueMatch else: return rl[0] class Extension(HookableMixin): """Extension descriptor.""" SUPPORTED_HOOKS = ('__pre_parse_args__', '__post_parse_args__') manager_class = None def __init__(self, name, module): super(Extension, self).__init__() self.name = name self.module = module self._parse_extension_module() def _parse_extension_module(self): self.manager_class = None for attr_name, attr_value in self.module.__dict__.items(): if attr_name in self.SUPPORTED_HOOKS: self.add_hook(attr_name, attr_value) else: try: if issubclass(attr_value, BaseManager): self.manager_class = attr_value except TypeError: pass def __repr__(self): return "" % 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) self_cls_name = reflection.get_class_name(self, fully_qualified=False) return "<%s %s>" % (self_cls_name, info) @property def human_id(self): """Human-readable ID which can be used for bash completion. """ if self.NAME_ATTR in self.__dict__ and self.HUMAN_ID: return strutils.to_slug(getattr(self, self.NAME_ATTR)) return None def _add_details(self, info): for (k, v) in info.items(): try: setattr(self, k, v) self._info[k] = v except AttributeError: # In this case we already defined the attribute on the class pass def __getattr__(self, k): if k == "__setstate__": raise AttributeError(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._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 @property def is_loaded(self): return self._loaded def to_dict(self): return copy.deepcopy(self._info) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/apiclient/client.py0000664000175000017500000003047200000000000023571 0ustar00zuulzuul00000000000000# 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 json import logging import time import requests from oslo_utils import importutils from troveclient.apiclient import exceptions LOG = logging.getLogger(__name__) class HTTPClient(object): """This client handles sending HTTP requests to OpenStack servers. Features: - share authentication information between several clients to different services (e.g., for compute and image clients); - reissue authentication request for expired tokens; - encode/decode JSON bodies; - raise exceptions on HTTP errors; - pluggable authentication; - store authentication information in a keyring; - store time spent for requests; - register clients for particular services, so one can use `http_client.identity` or `http_client.compute`; - log requests and responses in a format that is easy to copy-and-paste into terminal and send the same request with curl. """ user_agent = "troveclient.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) LOG.debug("REQ: %s", " ".join(string_parts)) if 'data' in kwargs: LOG.debug("REQ BODY: %s\n", kwargs['data']) def _http_log_resp(self, resp): if not self.debug: return LOG.debug( "RESP: [%s] %s\n", resp.status_code, resp.headers) if resp._content_consumed: LOG.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: LOG.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) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/apiclient/exceptions.py0000664000175000017500000003077400000000000024501 0ustar00zuulzuul00000000000000# Copyright 2010 Jacob Kaplan-Moss # Copyright 2011 Nebula, Inc. # Copyright 2013 Alessio Ababilov # Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Exception definitions. """ import inspect import sys class ClientException(Exception): """The base exception class for all exceptions this library raises. """ pass class MissingArgs(ClientException): """Supplied arguments are not sufficient for calling a function.""" def __init__(self, missing, message=None): self.missing = missing self.message = message or "Missing argument(s): %s" self.message %= ", ".join(missing) super(MissingArgs, self).__init__(self.message) class ValidationError(ClientException): """Error in validation on API client side.""" pass class UnsupportedVersion(ClientException): """User is trying to use an unsupported version of the API.""" pass class CommandError(ClientException): """Error in CLI tool.""" pass class AuthorizationFailure(ClientException): """Cannot authorize API client.""" pass class ConnectionRefused(ClientException): """Cannot connect to API service.""" pass class AuthPluginOptionsMissing(AuthorizationFailure): """Auth plugin misses some options.""" def __init__(self, opt_names): super(AuthPluginOptionsMissing, self).__init__( "Authentication failed. Missing options: %s" % ", ".join(opt_names)) self.opt_names = opt_names class AuthSystemNotFound(AuthorizationFailure): """User has specified a AuthSystem that is not installed.""" def __init__(self, auth_system): super(AuthSystemNotFound, self).__init__( "AuthSystemNotFound: %s" % repr(auth_system)) self.auth_system = auth_system class NoUniqueMatch(ClientException): """Multiple entities found instead of one.""" pass class EndpointException(ClientException): """Something is rotten in Service Catalog.""" pass class EndpointNotFound(EndpointException): """Could not find requested endpoint in Service Catalog.""" pass class AmbiguousEndpoints(EndpointException): """Found more than one matching endpoint in Service Catalog.""" def __init__(self, endpoints=None): super(AmbiguousEndpoints, self).__init__( "AmbiguousEndpoints: %s" % repr(endpoints)) self.endpoints = endpoints class HttpError(ClientException): """The base exception class for all HTTP exceptions. """ http_status = 0 message = "HTTP Error" def __init__(self, message=None, details=None, response=None, request_id=None, url=None, method=None, http_status=None): self.http_status = http_status or self.http_status self.message = message or self.message self.details = details self.request_id = request_id self.response = response self.url = url self.method = method formatted_string = "%s (HTTP %s)" % (self.message, self.http_status) if request_id: formatted_string += " (Request-ID: %s)" % request_id super(HttpError, self).__init__(formatted_string) class HTTPClientError(HttpError): """Client-side HTTP error. Exception for cases in which the client seems to have erred. """ message = "HTTP Client Error" class HttpServerError(HttpError): """Server-side HTTP error. Exception for cases in which the server is aware that it has erred or is incapable of performing the request. """ message = "HTTP Server Error" class BadRequest(HTTPClientError): """HTTP 400 - Bad Request. The request cannot be fulfilled due to bad syntax. """ http_status = 400 message = "Bad Request" class Unauthorized(HTTPClientError): """HTTP 401 - Unauthorized. Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet been provided. """ http_status = 401 message = "Unauthorized" class PaymentRequired(HTTPClientError): """HTTP 402 - Payment Required. Reserved for future use. """ http_status = 402 message = "Payment Required" class Forbidden(HTTPClientError): """HTTP 403 - Forbidden. The request was a valid request, but the server is refusing to respond to it. """ http_status = 403 message = "Forbidden" class NotFound(HTTPClientError): """HTTP 404 - Not Found. The requested resource could not be found but may be available again in the future. """ http_status = 404 message = "Not Found" class MethodNotAllowed(HTTPClientError): """HTTP 405 - Method Not Allowed. A request was made of a resource using a request method not supported by that resource. """ http_status = 405 message = "Method Not Allowed" class NotAcceptable(HTTPClientError): """HTTP 406 - Not Acceptable. The requested resource is only capable of generating content not acceptable according to the Accept headers sent in the request. """ http_status = 406 message = "Not Acceptable" class ProxyAuthenticationRequired(HTTPClientError): """HTTP 407 - Proxy Authentication Required. The client must first authenticate itself with the proxy. """ http_status = 407 message = "Proxy Authentication Required" class RequestTimeout(HTTPClientError): """HTTP 408 - Request Timeout. The server timed out waiting for the request. """ http_status = 408 message = "Request Timeout" class Conflict(HTTPClientError): """HTTP 409 - Conflict. Indicates that the request could not be processed because of conflict in the request, such as an edit conflict. """ http_status = 409 message = "Conflict" class Gone(HTTPClientError): """HTTP 410 - Gone. Indicates that the resource requested is no longer available and will not be available again. """ http_status = 410 message = "Gone" class LengthRequired(HTTPClientError): """HTTP 411 - Length Required. The request did not specify the length of its content, which is required by the requested resource. """ http_status = 411 message = "Length Required" class PreconditionFailed(HTTPClientError): """HTTP 412 - Precondition Failed. The server does not meet one of the preconditions that the requester put on the request. """ http_status = 412 message = "Precondition Failed" class RequestEntityTooLarge(HTTPClientError): """HTTP 413 - Request Entity Too Large. The request is larger than the server is willing or able to process. """ http_status = 413 message = "Request Entity Too Large" def __init__(self, *args, **kwargs): try: self.retry_after = int(kwargs.pop('retry_after')) except (KeyError, ValueError): self.retry_after = 0 super(RequestEntityTooLarge, self).__init__(*args, **kwargs) class RequestUriTooLong(HTTPClientError): """HTTP 414 - Request-URI Too Long. The URI provided was too long for the server to process. """ http_status = 414 message = "Request-URI Too Long" class UnsupportedMediaType(HTTPClientError): """HTTP 415 - Unsupported Media Type. The request entity has a media type which the server or resource does not support. """ http_status = 415 message = "Unsupported Media Type" class RequestedRangeNotSatisfiable(HTTPClientError): """HTTP 416 - Requested Range Not Satisfiable. The client has asked for a portion of the file, but the server cannot supply that portion. """ http_status = 416 message = "Requested Range Not Satisfiable" class ExpectationFailed(HTTPClientError): """HTTP 417 - Expectation Failed. The server cannot meet the requirements of the Expect request-header field. """ http_status = 417 message = "Expectation Failed" class UnprocessableEntity(HTTPClientError): """HTTP 422 - Unprocessable Entity. The request was well-formed but was unable to be followed due to semantic errors. """ http_status = 422 message = "Unprocessable Entity" class InternalServerError(HttpServerError): """HTTP 500 - Internal Server Error. A generic error message, given when no more specific message is suitable. """ http_status = 500 message = "Internal Server Error" # NotImplemented is a python keyword. class HttpNotImplemented(HttpServerError): """HTTP 501 - Not Implemented. The server either does not recognize the request method, or it lacks the ability to fulfill the request. """ http_status = 501 message = "Not Implemented" class BadGateway(HttpServerError): """HTTP 502 - Bad Gateway. The server was acting as a gateway or proxy and received an invalid response from the upstream server. """ http_status = 502 message = "Bad Gateway" class ServiceUnavailable(HttpServerError): """HTTP 503 - Service Unavailable. The server is currently unavailable. """ http_status = 503 message = "Service Unavailable" class GatewayTimeout(HttpServerError): """HTTP 504 - Gateway Timeout. The server was acting as a gateway or proxy and did not receive a timely response from the upstream server. """ http_status = 504 message = "Gateway Timeout" class HttpVersionNotSupported(HttpServerError): """HTTP 505 - HttpVersion Not Supported. The server does not support the HTTP protocol version used in the request. """ http_status = 505 message = "HTTP Version Not Supported" # _code_map contains all the classes that have http_status attribute. _code_map = dict( (getattr(obj, 'http_status', None), obj) for name, obj in vars(sys.modules[__name__]).items() if inspect.isclass(obj) and getattr(obj, 'http_status', False) ) def from_response(response, method, url): """Returns an instance of :class:`HttpError` or subclass based on response. :param response: instance of `requests.Response` class :param method: HTTP method used for request :param url: URL used for request """ kwargs = { "http_status": response.status_code, "response": response, "method": method, "url": url, "request_id": response.headers.get("x-compute-request-id"), } if "retry-after" in response.headers: kwargs["retry_after"] = response.headers["retry-after"] content_type = response.headers.get("Content-Type", "") if content_type.startswith("application/json"): try: body = response.json() except ValueError: pass else: if hasattr(body, 'keys'): # NOTE(mriedem): WebOb<1.6.0 will return a nested dict # structure where the error keys to the # message/details/code. WebOb>=1.6.0 returns just a response # body as a single dict, not nested, so we have to handle # both cases (since we can't trust what we're given with # content_type: application/json either way. if 'message' in body: # WebOb 1.6.0 case message = body.get('message') details = body.get('details') else: # WebOb<1.6.0 where we assume there is a single error # message key to the body that has the message and details. error = list(body.values())[0] message = error.get("message") details = error.get("details") kwargs["message"] = message kwargs["details"] = details 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) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/auth_plugin.py0000664000175000017500000000611600000000000022660 0ustar00zuulzuul00000000000000# Copyright 2014 Rackspace # 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. import logging import stevedore from troveclient import exceptions LOG = 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 mgr = stevedore.ExtensionManager( namespace='openstack.client.auth_plugin', invoke_on_load=True, ) _discovered_plugins = {ext.name: ext.obj for ext in mgr} 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. """ for name, auth_plugin in _discovered_plugins.items(): add_opts_fn = getattr(auth_plugin, "add_opts", None) if add_opts_fn: group = parser.add_argument_group("Auth-system '%s' options" % name) add_opts_fn(group) def load_plugin(auth_system): if auth_system in _discovered_plugins: return _discovered_plugins[auth_system] raise exceptions.AuthSystemNotFound(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. """ def __init__(self): self.opts = {} def get_auth_url(self): """Return the auth url for the plugin (if any).""" return None @staticmethod def add_opts(parser): """Populate and return the parser with the options for this plugin. If the plugin does not need any options, it should return the same parser untouched. """ return parser 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. If the dict is empty, the client should assume that it needs the same options as the 'keystone' auth system (i.e. os_username and os_password). Returns the self.opts dict. """ return self.opts def authenticate(self, cls, auth_url): """Authenticate using plugin defined method.""" raise exceptions.AuthSystemNotFound(self.auth_system) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/base.py0000664000175000017500000002204100000000000021246 0ustar00zuulzuul00000000000000# 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 from urllib import parse from troveclient.apiclient import base from troveclient.apiclient import exceptions from troveclient import common 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): """Retrieves an id from object or integer. 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): """Manager defining CRUD operations for API. 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 _paginated(self, url, response_key, limit=None, marker=None, query_strings=None): query_strings = query_strings or {} url = common.append_query_strings(url, limit=limit, marker=marker, **query_strings) resp, body = self.api.client.get(url) 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 = parse.urlparse(link) query_dict = dict(parse.parse_qsl(parsed_url.query)) next_marker = query_dict.get('marker') data = [self.resource_class(self, res) for res in body[response_key]] return common.Paginated(data, next_marker=next_marker, links=links) 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): """Bash-completion cache. 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', 'NOVA_URL') uniqifier = hashlib.md5(username.encode('utf-8') + url.encode('utf-8')).hexdigest() cache_dir = os.path.expanduser(os.path.join(base_dir, uniqifier)) try: os.makedirs(cache_dir, 0o755) except OSError: # NOTE(kiall): This is typically either permission denied while # attempting to create the directory, or the directory # already exists. Either way, don't fail. pass resource = obj_class.__name__.lower() filename = "%s-%s-cache" % (resource, cache_type.replace('_', '-')) path = os.path.join(cache_dir, filename) cache_attr = "_%s_cache" % cache_type try: setattr(self, cache_attr, open(path, mode)) except IOError: # NOTE(kiall): This is typically a permission denied while # attempting to write the cache file. pass try: yield finally: cache = getattr(self, cache_attr, None) if cache: cache.close() delattr(self, cache_attr) def write_to_completion_cache(self, cache_type, val): cache = getattr(self, "_%s_cache" % cache_type, None) if cache: cache.write("%s\n" % val) def _get(self, url, response_key=None): resp, body = self.api.client.get(url) if response_key: return self.resource_class(self, body[response_key], loaded=True) else: return self.resource_class(self, body, loaded=True) def _create(self, url, body, response_key, return_raw=False, **kwargs): self.run_hooks('modify_body_for_create', body, **kwargs) resp, body = self.api.client.post(url, body=body) if 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) return resp, body 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 def _edit(self, url, body): resp, body = self.api.client.patch(url, body=body) return body class ManagerWithFind(Manager, metaclass=abc.ABCMeta): """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(self.get(obj.id)) except AttributeError: continue return found class Resource(base.Resource): """A resource represents a particular instance of an object like server. 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): super(Resource, self).__init__(manager, info, 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) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/client.py0000664000175000017500000004503400000000000021621 0ustar00zuulzuul00000000000000# 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. """ import json import logging from keystoneauth1 import adapter from oslo_utils import importutils import requests from urllib import parse as urlparse from troveclient.apiclient import client from troveclient import exceptions from troveclient import service_catalog try: import eventlet as sleep_lib except ImportError: import time as sleep_lib osprofiler_web = importutils.try_import("osprofiler.web") class TroveClientMixin(object): 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:] class HTTPClient(TroveClientMixin): 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, auth_system='keystone', auth_plugin=None): if auth_system and auth_system != 'keystone' and not auth_plugin: raise exceptions.AuthSystemNotFound(auth_system) if not auth_url and auth_system and auth_system != 'keystone': auth_url = auth_plugin.get_auth_url() if not auth_url: raise exceptions.EndpointNotFound() self.user = user self.password = password self.projectid = projectid self.tenant_id = tenant_id self.auth_url = auth_url.rstrip('/') if auth_url else auth_url 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 self.auth_system = auth_system self.auth_plugin = auth_plugin if insecure: self.verify_cert = False else: if cacert: self.verify_cert = cacert else: self.verify_cert = True self.auth_system = auth_system self.auth_plugin = auth_plugin self.LOG = logging.getLogger(__name__) if self.http_log_debug and not self.LOG.handlers: ch = logging.StreamHandler() self.LOG.setLevel(logging.DEBUG) self.LOG.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.LOG.debug("\nREQ: %s\n", "".join(string_parts)) def http_log_resp(self, resp): if not self.http_log_debug: return self.LOG.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 osprofiler_web: kwargs['headers'].update(osprofiler_web.get_trace_id_headers()) if 'body' in kwargs: kwargs['headers']['Content-Type'] = 'application/json' kwargs['data'] = json.dumps(kwargs['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: if attempts > self.retries: raise except exceptions.Unauthorized: if auth_attempts > 0: raise self.LOG.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.LOG.debug("Connection refused: %s", e) msg = 'Unable to establish connection: %s' % e raise exceptions.ConnectionRefused(msg) self.LOG.debug( "Failed attempt(%s of %s), retrying in %s seconds", attempts, self.retries, backoff) sleep_lib.sleep(backoff) backoff *= 2 def get(self, url, **kwargs): return self._cs_request(url, 'GET', **kwargs) def patch(self, url, **kwargs): return self._cs_request(url, 'PATCH', **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.LOG.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 not self.auth_system or self.auth_system == 'keystone': auth_url = self._v2_auth(auth_url) else: auth_url = self._plugin_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 _plugin_auth(self, auth_url): return self.auth_plugin.authenticate(self, auth_url) def _v1_auth(self, url): if self.proxy_token: raise exceptions.NoTokenLookupException() headers = {'X-Auth-User': self.user, 'X-Auth-Key': self.password} if self.projectid: headers['X-Auth-Project-Id'] = self.projectid resp, body = self.request(url, 'GET', headers=headers) if resp.status_code in (200, 204): # in some cases we get No Content try: mgmt_header = 'x-server-management-url' self.management_url = resp.headers[mgmt_header].rstrip('/') self.auth_token = resp.headers['x-auth-token'] self.auth_url = url except (KeyError, TypeError): raise exceptions.AuthorizationFailure() elif resp.status_code == 305: return resp.headers['location'] else: raise exceptions.from_response(resp, body, 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 _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) class SessionClient(adapter.LegacyJsonAdapter, TroveClientMixin): def __init__(self, session, auth, **kwargs): self.database_service_name = kwargs.pop('database_service_name', None) super(SessionClient, self).__init__(session=session, auth=auth, **kwargs) # FIXME(jamielennox): this is going to cause an authentication request # on client init. This is different to how the other clients work. endpoint = self.get_endpoint() if not endpoint: raise exceptions.EndpointNotFound() self.management_url = endpoint.rstrip('/') def request(self, url, method, **kwargs): raise_exc = kwargs.pop('raise_exc', True) resp, body = super(SessionClient, self).request(url, method, raise_exc=False, **kwargs) if raise_exc and resp.status_code >= 400: raise exceptions.from_response(resp, body, url) return resp, body def _construct_http_client(username=None, password=None, project_id=None, auth_url=None, insecure=False, timeout=None, proxy_tenant_id=None, proxy_token=None, region_name=None, endpoint_type='publicURL', service_type='database', service_name=None, database_service_name=None, retries=None, http_log_debug=False, auth_system='keystone', auth_plugin=None, cacert=None, bypass_url=None, tenant_id=None, session=None, **kwargs): if session: try: kwargs.setdefault('interface', endpoint_type) except KeyError: pass return SessionClient(session=session, service_type=service_type, service_name=service_name, region_name=region_name, database_service_name=database_service_name, connect_retries=retries, **kwargs) else: return HTTPClient(username, password, projectid=project_id, auth_url=auth_url, insecure=insecure, timeout=timeout, tenant_id=tenant_id, proxy_token=proxy_token, proxy_tenant_id=proxy_tenant_id, region_name=region_name, endpoint_type=endpoint_type, service_type=service_type, service_name=service_name, database_service_name=database_service_name, retries=retries, http_log_debug=http_log_debug, cacert=cacert, bypass_url=bypass_url, auth_system=auth_system, auth_plugin=auth_plugin, ) 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) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/common.py0000664000175000017500000000312200000000000021623 0ustar00zuulzuul00000000000000# 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 urllib import parse from troveclient.apiclient import exceptions def check_for_exceptions(resp, body, url): if resp.status_code in (400, 422, 500): raise exceptions.from_response(resp, body, url) def append_query_strings(url, **query_strings): if not query_strings: return url query = '&'.join('{0}={1}'.format(key, val) for key, val in query_strings.items() if val is not None) return url + ('?' + query if query else "") def quote_user_host(user, host): quoted = '' if host: quoted = parse.quote("%s@%s" % (user, host)) else: quoted = parse.quote("%s" % user) return quoted.replace('.', '%2e') class Paginated(list): def __init__(self, items=None, next_marker=None, links=None): items = items or [] links = links or [] super(Paginated, self).__init__(items) self.next = next_marker self.links = links ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1726234752.4829679 python-troveclient-8.6.0/troveclient/compat/0000775000175000017500000000000000000000000021246 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/compat/__init__.py0000664000175000017500000000301400000000000023355 0ustar00zuulzuul00000000000000# 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.client import Dbaas # noqa from troveclient.compat.client import TroveHTTPClient # noqa from troveclient.compat.versions import Versions # noqa from troveclient.v1.accounts import Accounts # noqa from troveclient.v1.databases import Databases # noqa from troveclient.v1.diagnostics import DiagnosticsInterrogator # noqa from troveclient.v1.diagnostics import HwInfoInterrogator # noqa from troveclient.v1.flavors import Flavors # noqa from troveclient.v1.hosts import Hosts # noqa from troveclient.v1.instances import Instances # noqa from troveclient.v1.management import Management # noqa from troveclient.v1.management import MgmtFlavors # noqa from troveclient.v1.management import RootHistory # noqa from troveclient.v1.root import Root # noqa from troveclient.v1.storage import StorageInfo # noqa from troveclient.v1.users import Users # noqa ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/compat/auth.py0000664000175000017500000003302000000000000022557 0ustar00zuulzuul00000000000000# 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 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, str): if cls_or_name == "keystone": return KeyStoneV3Authenticator 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 == 201: # Keystone v3 try: token = resp.get('x-subject-token') return ServiceCatalog3(body, region=self.region, service_type=self.service_type, service_name=self.service_name, service_url=self.service_url, token=token) 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 == 200: # Keystone pre-v3 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 KeyStoneV3Authenticator(Authenticator): def __init__(self, client, type, url, username, password, tenant, region=None, service_type=None, service_name=None, service_url=None): super(KeyStoneV3Authenticator, self).\ __init__(client, type, url, username, password, tenant, region=region, service_type=service_type, service_name=service_name, service_url=service_url) # The Auth obect is needed because # troveclient.v1.instances.Instances._get_swift_client assumes # its v3 auth path is using a client which inherits from # keystone1.adapter.Adapter and then further goes after that # adapter's internal attributes to fetch them and send them # to the Swift client it creates. class Auth(object): def __init__(self, auth_url, username, password, project_name): token_str = "/auth/tokens" if auth_url.endswith(token_str): auth_url = auth_url[:-len(token_str)] self.auth_url = auth_url self._username = username self._password = password self._project_name = project_name self.auth = Auth(url, username, password, tenant) def authenticate(self): if self.url is None: raise exceptions.AuthUrlNotGiven() return self._v3_auth(self.url) def _v3_auth(self, url): """Authenticate against a v3.0 auth service.""" body = {'auth': { 'identity': { 'methods': ['password'], 'password': { 'user': { 'domain': {'name': 'Default'}, 'name': self.username, 'password': self.password } } }}} if self.tenant: body['auth']['scope'] = {'project': { 'domain': {'name': 'Default'}, 'name': self.tenant}} return self._authenticate(url, body) 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 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 requested URL. 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) class ServiceCatalog3(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, token=None): self.body = 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.token = token self._load() def _load(self): if not self.service_url: self.public_url = self._url_for(attr='region', filter_value=self.region, endpoint_type="public") self.management_url = self._url_for(attr='region', filter_value=self.region, endpoint_type="admin") else: self.public_url = self.service_url self.management_url = self.service_url def get_token(self): return self.token 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='public'): """Fetch requested URL. Fetch the public URL from the Trove service for a particular endpoint attribute. If none given, return the first. """ """Fetch the requested end point URL. """ matching_endpoints = [] catalog = self.body['token']['catalog'] 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 endpoint.get('interface') == endpoint_type and \ (not filter_value or endpoint.get(attr) == filter_value): matching_endpoints.append(endpoint) if not matching_endpoints: raise exceptions.EndpointNotFound() else: return matching_endpoints[0].get('url') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/compat/base.py0000664000175000017500000002303700000000000022537 0ustar00zuulzuul00000000000000# 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 oslo_utils import reflection from oslo_utils import strutils 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): """Retrives an id from object or integer. 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): """Manager defining CRUD operations for API. 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): """Bash-completion cache. 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 typically either permission denied while # attempting to create the directory, or the directory # already exists. Either way, don't fail. pass resource = obj_class.__name__.lower() filename = "%s-%s-cache" % (resource, cache_type.replace('_', '-')) path = os.path.join(cache_dir, filename) cache_attr = "_%s_cache" % cache_type try: setattr(self, cache_attr, open(path, mode)) except IOError: # NOTE(kiall): This is typically a permission denied while # attempting to write the cache file. pass try: yield finally: cache = getattr(self, cache_attr, None) if cache: cache.close() delattr(self, cache_attr) def write_to_completion_cache(self, cache_type, val): cache = getattr(self, "_%s_cache" % cache_type, None) if cache: cache.write("%s\n" % val) def _get(self, url, response_key=None): resp, body = self.api.client.get(url) if response_key: return self.resource_class(self, body[response_key], loaded=True) else: return self.resource_class(self, body, loaded=True) def _create(self, url, body, response_key, return_raw=False, **kwargs): self.run_hooks('modify_body_for_create', body, **kwargs) resp, body = self.api.client.post(url, body=body) if return_raw: return body[response_key] with self.completion_cache('human_id', self.resource_class, mode="a"): with self.completion_cache('uuid', self.resource_class, mode="a"): return self.resource_class(self, body[response_key]) def _delete(self, url): resp, body = self.api.client.delete(url) def _update(self, url, body, **kwargs): self.run_hooks('modify_body_for_update', body, **kwargs) resp, body = self.api.client.put(url, body=body) return body class ManagerWithFind(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 like server. 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): """Provides a pretty ID which can be used for bash completion.""" if 'name' in self.__dict__ and self.HUMAN_ID: return strutils.to_slug(self.name) return None def _add_details(self, info): for (k, v) in info.items(): try: setattr(self, k, v) except AttributeError: # In this case we already defined the attribute on the class 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) self_cls_name = reflection.get_class_name(self, fully_qualified=False) return "<%s %s>" % (self_cls_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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/compat/cli.py0000664000175000017500000003754700000000000022407 0ustar00zuulzuul00000000000000#!/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 from troveclient.compat import common # 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) class InstanceCommands(common.AuthedCommandsBase): """Commands to perform various instance operations and actions.""" params = [ 'flavor', 'id', 'limit', 'marker', 'name', 'size', 'backup', 'availability_zone', 'configuration_id', ] def _get_configuration_ref(self): configuration_ref = None if self.configuration_id is not None: if self.configuration_id == "": configuration_ref = self.configuration_id else: configuration_ref = "/".join( [ self.dbaas.client.service_url, self.configuration_id, ] ) return configuration_ref 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, configuration=self._get_configuration_ref()) # TODO(pdmars): is this actually what this should be named? def modify(self): """Modify an instance.""" self._require('id') self._pretty_print(self.dbaas.instances.modify, self.id, configuration=self._get_configuration_ref()) 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) def configuration(self): """Get configuration for the specified instance.""" self._require('id') self._pretty_print(self.dbaas.instances.configuration, self.id) class FlavorsCommands(common.AuthedCommandsBase): """Command 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 Exception: print(sys.exc_info()[1]) def delete(self): """Disable the instance's root user.""" self._require('id') print(self.dbaas.root.delete(self.id)) 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 DatastoreConfigurationParameters(common.AuthedCommandsBase): """Command to show configuration parameters for a datastore.""" params = ['datastore', 'parameter'] def parameters(self): """List parameters that can be set.""" self._require('datastore') self._pretty_print(self.dbaas.configuration_parameters.parameters, self.datastore) def get_parameter(self): """List parameters that can be set.""" self._require('datastore', 'parameter') self._pretty_print(self.dbaas.configuration_parameters.get_parameter, self.datastore, self.parameter) class ConfigurationsCommands(common.AuthedCommandsBase): """Command to manage and show configurations.""" params = ['name', 'instances', 'values', 'description', 'parameter'] def get(self): """Get details for the specified configuration.""" self._require('id') self._pretty_print(self.dbaas.configurations.get, self.id) def list_instances(self): """Get details for the specified configuration.""" self._require('id') self._pretty_list(self.dbaas.configurations.instances, self.id) def list(self): """List configurations.""" self._pretty_list(self.dbaas.configurations.list) def create(self): """Create a new configuration.""" self._require('name', 'values') self._pretty_print(self.dbaas.configurations.create, self.name, self.values, self.description) def update(self): """Update an existing configuration.""" self._require('id', 'values') self._pretty_print(self.dbaas.configurations.update, self.id, self.values, self.name, self.description) def edit(self): """Edit an existing configuration values.""" self._require('id', 'values') self._pretty_print(self.dbaas.configurations.edit, self.id, self.values) def delete(self): """Delete a configuration.""" self._require('id') self._pretty_print(self.dbaas.configurations.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) class MetadataCommands(common.AuthedCommandsBase): """Commands to create/update/replace/delete/show metadata for an instance """ params = [ 'instance_id', 'metadata' ] def show(self): """Show instance metadata.""" self._require('instance_id') self._pretty_print(self.dbaas.metadata.show(self.instance_id)) COMMANDS = { 'auth': common.Auth, 'instance': InstanceCommands, 'flavor': FlavorsCommands, 'database': DatabaseCommands, 'limit': LimitsCommands, 'backup': BackupsCommands, 'configuration': ConfigurationsCommands, 'user': UserCommands, 'root': RootCommands, 'version': VersionCommands, 'secgroup': SecurityGroupCommands, 'metadata': MetadataCommands, } def main(): # Parse arguments 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() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/compat/client.py0000664000175000017500000003335000000000000023102 0ustar00zuulzuul00000000000000# 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 json import logging import os import sys import time from troveclient.compat import auth from troveclient.compat import exceptions LOG = 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) LOG.setLevel(logging.DEBUG) LOG.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) if hasattr(self.authenticator, 'auth'): self.auth = self.authenticator.auth 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 LOG.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) LOG.debug("REQ: %s\n", "".join(string_parts)) if 'body' in kwargs: LOG.debug("REQ BODY: %s\n", kwargs['body']) LOG.debug("RESP:%s %s\n", resp, body) def pretty_log(self, args, kwargs, resp, body): if not LOG.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) LOG.debug("REQUEST:") if 'body' in kwargs: LOG.debug("%s -d '%s'", curl_cmd, kwargs['body']) try: req_body = json.dumps(json.loads(kwargs['body']), sort_keys=True, indent=4) except Exception: req_body = kwargs['body'] LOG.debug("BODY: %s\n", req_body) else: LOG.debug(curl_cmd) try: resp_body = json.dumps(json.loads(body), sort_keys=True, indent=4) except Exception: resp_body = body LOG.debug("RESPONSE HEADERS: %s", resp) LOG.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, raw_body): try: return json.loads(raw_body.decode()) 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: self.authenticate() return request() def get(self, url, **kwargs): return self._cs_request(url, 'GET', **kwargs) def patch(self, url, **kwargs): return self._cs_request(url, 'PATCH', **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 import versions from troveclient.v1 import accounts from troveclient.v1 import backups from troveclient.v1 import clusters from troveclient.v1 import configurations from troveclient.v1 import databases from troveclient.v1 import datastores from troveclient.v1 import diagnostics from troveclient.v1 import flavors from troveclient.v1 import hosts from troveclient.v1 import instances from troveclient.v1 import limits from troveclient.v1 import management from troveclient.v1 import metadata from troveclient.v1 import modules from troveclient.v1 import quota from troveclient.v1 import root from troveclient.v1 import security_groups from troveclient.v1 import storage from troveclient.v1 import users 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.Versions(self) self.databases = databases.Databases(self) self.flavors = flavors.Flavors(self) self.instances = instances.Instances(self) self.limits = limits.Limits(self) self.users = users.Users(self) self.root = root.Root(self) self.hosts = hosts.Hosts(self) self.quota = quota.Quotas(self) self.backups = backups.Backups(self) self.clusters = clusters.Clusters(self) self.security_groups = security_groups.SecurityGroups(self) self.security_group_rules = security_groups.SecurityGroupRules(self) self.datastores = datastores.Datastores(self) self.datastore_versions = datastores.DatastoreVersions(self) self.datastore_version_members = (datastores. DatastoreVersionMembers(self)) self.storage = storage.StorageInfo(self) self.management = management.Management(self) self.mgmt_cluster = management.MgmtClusters(self) self.mgmt_flavor = management.MgmtFlavors(self) self.accounts = accounts.Accounts(self) self.diagnostics = diagnostics.DiagnosticsInterrogator(self) self.hwinfo = diagnostics.HwInfoInterrogator(self) self.configurations = configurations.Configurations(self) config_parameters = configurations.ConfigurationParameters(self) self.configuration_parameters = config_parameters self.metadata = metadata.Metadata(self) self.modules = modules.Modules(self) self.mgmt_configs = management.MgmtConfigurationParameters(self) self.mgmt_datastore_versions = management.MgmtDatastoreVersions(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.datastore_version = dbaas.mgmt_datastore_versions 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() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/compat/common.py0000664000175000017500000003334000000000000023113 0ustar00zuulzuul00000000000000# 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 sys from urllib import parse from troveclient.compat import client from troveclient.compat import exceptions 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 actions.items(): 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 commands.items(): 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 = parse.quote("%s@%s" % (user, host)) else: quoted = parse.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, } 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 Exception: 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 value are 'keystone'.") 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.") oparser.add_option("--json", action="store_false", dest="xml", help="Changes format to JSON.") oparser.add_option("--secure", action="store_false", dest="insecure", help="Run in insecure mode for https endpoints.") 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: 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 Exception: 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 Exception: 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 Exception: 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 acquired! Saving to %s..." % CliOptions.APITOKEN) print(" service_url = %s" % self.service_url) print(" token = %s" % self.token) except Exception: if self.debug: raise print(sys.exc_info()[1]) class AuthedCommandsBase(CommandsBase): """Commands that work only with an authenticated 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=None, next_marker=None, links=None): self.items = items or [] self.next = next_marker self.links = links or [] 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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/compat/exceptions.py0000664000175000017500000001136200000000000024004 0ustar00zuulzuul00000000000000# 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 don't have access to this resource.""" http_status = 403 message = "Forbidden" class NotFound(ClientException): """HTTP 404 - Not found.""" http_status = 404 message = "Not found" class Conflict(ClientException): """HTTP 409 - Conflict.""" http_status = 409 message = "Conflict" 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, Conflict, OverLimit, HTTPNotImplemented, UnprocessableEntity]) def from_response(response, body): """Return an instance of an ClientException based on a 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[list(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) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/compat/mcli.py0000664000175000017500000001704200000000000022550 0ustar00zuulzuul00000000000000#!/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 from troveclient.compat import common # 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) 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() ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1726234752.4829679 python-troveclient-8.6.0/troveclient/compat/tests/0000775000175000017500000000000000000000000022410 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/compat/tests/__init__.py0000664000175000017500000000000000000000000024507 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/compat/tests/test_auth.py0000664000175000017500000003420500000000000024766 0ustar00zuulzuul00000000000000# 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 testtools from unittest import mock from troveclient.compat import auth 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(testtools.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.Auth1_1, auth.FakeAuth) for c in class_list: self.assertEqual(c, auth.get_authenticator_cls(c)) class_names = {"keystone": auth.KeyStoneV3Authenticator, "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.Mock(), auth.KeyStoneV2Authenticator, mock.Mock(), mock.Mock(), mock.Mock(), mock.Mock()) # test response code 200 resp = mock.Mock() resp.status = 200 body = "test_body" auth.ServiceCatalog._load = mock.Mock(return_value=1) authObj.client._time_request = mock.Mock(return_value=(resp, body)) sc = authObj._authenticate(mock.Mock(), mock.Mock()) self.assertEqual(body, sc.catalog) # test AmbiguousEndpoints exception auth.ServiceCatalog.__init__ = mock.Mock( side_effect=exceptions.AmbiguousEndpoints ) self.assertRaises(exceptions.AmbiguousEndpoints, authObj._authenticate, mock.Mock(), mock.Mock()) # test handling KeyError and raising AuthorizationFailure exception auth.ServiceCatalog.__init__ = mock.Mock(side_effect=KeyError) self.assertRaises(exceptions.AuthorizationFailure, authObj._authenticate, mock.Mock(), mock.Mock()) # test EndpointNotFound exception mock_obj = mock.Mock(side_effect=exceptions.EndpointNotFound) auth.ServiceCatalog.__init__ = mock_obj self.assertRaises(exceptions.EndpointNotFound, authObj._authenticate, mock.Mock(), mock.Mock()) mock_obj.side_effect = None # test response code 305 resp.__getitem__ = mock.Mock(return_value='loc') resp.status = 305 body = "test_body" authObj.client._time_request = mock.Mock(return_value=(resp, body)) lo = authObj._authenticate(mock.Mock(), mock.Mock()) self.assertEqual('loc', lo) # test any response code other than 200 and 305 resp.status = 404 exceptions.from_response = mock.Mock(side_effect=ValueError) self.assertRaises(ValueError, authObj._authenticate, mock.Mock(), mock.Mock()) def test_authenticate(self): authObj = auth.Authenticator(mock.Mock(), auth.KeyStoneV2Authenticator, mock.Mock(), mock.Mock(), mock.Mock(), mock.Mock()) self.assertRaises(NotImplementedError, authObj.authenticate) class KeyStoneV2AuthenticatorTest(testtools.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_obj = mock.Mock() mock_obj.side_effect = side_effect_func authObj._v2_auth = mock_obj 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_obj = mock.Mock() mock_obj.side_effect = side_effect_func authObj._authenticate = mock_obj body = authObj._v2_auth(mock.Mock()) self.assertEqual(username, body['auth']['passwordCredentials']['username']) self.assertEqual(password, body['auth']['passwordCredentials']['password']) self.assertEqual(tenant, body['auth']['tenantName']) class Auth1_1Test(testtools.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_obj = mock.Mock() mock_obj.side_effect = side_effect_func authObj._authenticate = mock_obj 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 FakeAuthTest(testtools.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(testtools.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.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.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_endpoint_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_obj = mock.Mock() mock_obj.side_effect = side_effect_func_ep endpoint.__getitem__ = mock_obj 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_endpoint_with_empty_catalog(self, scObj): # First, test with an empty catalog. This should pass since # there is already an endpoint added. scObj.catalog[scObj.root_key]['serviceCatalog'] = list() endpoint = scObj.catalog['endpoints'][0] endpoint.get = mock.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.Mock() mock1.side_effect = side_effect_func_service service1 = mock.Mock() service1.get = mock1 endpoint2 = {"test_attr": "test_attr_value"} service1.__getitem__ = mock.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") ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/compat/tests/test_common.py0000664000175000017500000003234000000000000025313 0ustar00zuulzuul00000000000000# 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 collections import json import optparse import sys from unittest import mock import testtools from troveclient.compat import common """ unit tests for common.py """ class CommonTest(testtools.TestCase): def setUp(self): super(CommonTest, self).setUp() self.orig_sys_exit = sys.exit sys.exit = mock.Mock(return_value=None) def tearDown(self): super(CommonTest, self).tearDown() sys.exit = self.orig_sys_exit def test_methods_of(self): class DummyClass(object): 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.Mock() # compat still uses status resp.status = s self.assertRaises(Exception, common.check_for_exceptions, resp, "body") # a no-exception case resp = mock.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(testtools.TestCase): def check_default_options(self, co): self.assertIsNone(co.username) self.assertIsNone(co.apikey) self.assertIsNone(co.tenant_id) self.assertIsNone(co.auth_url) self.assertEqual('keystone', co.auth_type) self.assertEqual('database', co.service_type) self.assertEqual('RegionOne', co.region) self.assertIsNone(co.service_url) self.assertFalse(co.insecure) self.assertFalse(co.verbose) self.assertFalse(co.debug) self.assertIsNone(co.token) def check_option(self, oparser, option_name): option = oparser.get_option("--%s" % option_name) self.assertIsNotNone(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_default(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", "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(testtools.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(testtools.TestCase): def setUp(self): super(CommandsBaseTest, self).setUp() self.orig_sys_exit = sys.exit sys.exit = mock.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.assertIsNotNone(self.cmd_base) def test__safe_exec(self): func = mock.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.Mock(side_effect=ValueError) # an arbitrary exception r = self.cmd_base._safe_exec(func) self.assertIsNone(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.assertIsNotNone(option) option = parser.get_option("--%s" % "test_2") self.assertIsNotNone(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.Mock(return_value=None) self.cmd_base.verbose = True self.assertIsNone(self.cmd_base._pretty_print(func)) self.cmd_base.verbose = False self.assertIsNone(self.cmd_base._pretty_print(func)) def test__dumps(self): orig_dumps = json.dumps json.dumps = mock.Mock(return_value="test-dump") self.assertEqual("test-dump", self.cmd_base._dumps("item")) json.dumps = orig_dumps def test__pretty_list(self): func = mock.Mock(return_value=None) self.cmd_base.verbose = True self.assertIsNone(self.cmd_base._pretty_list(func)) self.cmd_base.verbose = False self.assertIsNone(self.cmd_base._pretty_list(func)) item = mock.Mock(return_value="test") item._info = "info" func = mock.Mock(return_value=[item]) self.assertIsNone(self.cmd_base._pretty_list(func)) def test__pretty_paged(self): self.cmd_base.limit = "5" func = mock.Mock(return_value=None) self.cmd_base.verbose = True self.assertIsNone(self.cmd_base._pretty_paged(func)) self.cmd_base.verbose = False class MockIterable(collections.abc.Iterable): links = ["item"] count = 1 def __iter__(self): return ["item1"] def __len__(self): return self.count ret = MockIterable() func = mock.Mock(return_value=ret) self.assertIsNone(self.cmd_base._pretty_paged(func)) ret.count = 0 self.assertIsNone(self.cmd_base._pretty_paged(func)) func = mock.Mock(side_effect=ValueError) self.assertIsNone(self.cmd_base._pretty_paged(func)) self.cmd_base.debug = True self.cmd_base.marker = mock.Mock() self.assertRaises(ValueError, self.cmd_base._pretty_paged, func) class AuthTest(testtools.TestCase): def setUp(self): super(AuthTest, self).setUp() self.orig_sys_exit = sys.exit sys.exit = mock.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.assertIsNone(self.auth.dbaas) self.assertIsNone(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.Mock() dbaas.authenticate = mock.Mock(return_value=None) dbaas.client = mock.Mock() dbaas.client.auth_token = mock.Mock() dbaas.client.service_url = mock.Mock() self.auth._get_client = mock.Mock(return_value=dbaas) self.auth.login() self.auth.debug = True self.auth._get_client = mock.Mock(side_effect=ValueError) self.assertRaises(ValueError, self.auth.login) self.auth.debug = False self.auth.login() class AuthedCommandsBaseTest(testtools.TestCase): def setUp(self): super(AuthedCommandsBaseTest, self).setUp() self.orig_sys_exit = sys.exit sys.exit = mock.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.Mock() dbaas.authenticate = mock.Mock(return_value=None) dbaas.client = mock.Mock() dbaas.client.auth_token = mock.Mock() dbaas.client.service_url = mock.Mock() dbaas.client.authenticate_with_token = mock.Mock() common.AuthedCommandsBase._get_client = mock.Mock(return_value=dbaas) common.AuthedCommandsBase(parser) class PaginatedTest(testtools.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__() 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")) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/compat/utils.py0000664000175000017500000000267100000000000022766 0ustar00zuulzuul00000000000000# 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 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 environment variables. 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', '') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/compat/versions.py0000664000175000017500000000230400000000000023467 0ustar00zuulzuul00000000000000# 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']] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/exceptions.py0000664000175000017500000000232400000000000022517 0ustar00zuulzuul00000000000000# 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. """ Exception definitions """ # alias exceptions from apiclient for users of this module from troveclient.apiclient.exceptions import * # noqa class NoTokenLookupException(Exception): """This form of authentication does not support looking up endpoints from an existing token. """ pass class ResponseFormatError(Exception): """Could not parse the response format.""" pass class GuestLogNotFoundError(Exception): """The specified guest log does not exist.""" pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/extension.py0000664000175000017500000000256500000000000022361 0ustar00zuulzuul00000000000000# 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. from troveclient import base from troveclient import utils class Extension(utils.HookableMixin): """Extension descriptor.""" SUPPORTED_HOOKS = ('__pre_parse_args__', '__post_parse_args__') def __init__(self, name, module): self.name = name self.module = module self._parse_extension_module() def _parse_extension_module(self): self.manager_class = None for attr_name, attr_value in self.module.__dict__.items(): if attr_name in self.SUPPORTED_HOOKS: self.add_hook(attr_name, attr_value) elif utils.safe_issubclass(attr_value, base.Manager): self.manager_class = attr_value def __repr__(self): return "" % self.name ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/i18n.py0000664000175000017500000000222400000000000021114 0ustar00zuulzuul00000000000000# Copyright 2014 Tesora, Inc. # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """oslo.i18n integration module. See https://docs.openstack.org/oslo.i18n/latest/user/index.html . """ import oslo_i18n # NOTE(dhellmann): This reference to o-s-l-o will be replaced by the # application name when this module is synced into the separate # repository. It is OK to have more than one translation function # using the same domain, since there will still only be one message # catalog. _translators = oslo_i18n.TranslatorFactory(domain='python-troveclient') # The primary translation function using the well-known name "_" _ = _translators.primary ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1726234752.4829679 python-troveclient-8.6.0/troveclient/osc/0000775000175000017500000000000000000000000020547 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/osc/__init__.py0000664000175000017500000000000000000000000022646 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/osc/plugin.py0000664000175000017500000000341100000000000022416 0ustar00zuulzuul00000000000000# 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 logging from osc_lib import utils LOG = logging.getLogger(__name__) DEFAULT_DATABASE_API_VERSION = '1' API_VERSION_OPTION = 'os_database_api_version' API_NAME = 'database' API_VERSIONS = { '1': 'troveclient.v1.client.Client', } def make_client(instance): """Returns a database service client""" trove_client = utils.get_client_class( API_NAME, instance._api_version[API_NAME], API_VERSIONS) LOG.debug('Instantiating database client: %s', trove_client) client = trove_client( auth=instance.auth, session=instance.session, region_name=instance._region_name, endpoint_type=instance.interface, cacert=instance.cert, http_log_debug=instance._cli_options.debug ) return client def build_option_parser(parser): """Hook to add global options""" parser.add_argument( '--os-database-api-version', metavar='', default=utils.env( 'OS_DATABASE_API_VERSION', default=DEFAULT_DATABASE_API_VERSION), help='Database API version, default=' + DEFAULT_DATABASE_API_VERSION + ' (Env: OS_DATABASE_API_VERSION)') return parser ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1726234752.4829679 python-troveclient-8.6.0/troveclient/osc/v1/0000775000175000017500000000000000000000000021075 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/osc/v1/__init__.py0000664000175000017500000000000000000000000023174 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/osc/v1/base.py0000664000175000017500000000237700000000000022372 0ustar00zuulzuul00000000000000# Copyright 2019 Catalyst Cloud Ltd. # # 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 osc_lib.command import command from osc_lib import exceptions class TroveDeleter(command.Command): def delete_resources(self, ids): """Delete one or more resources.""" failure_flag = False success_msg = "Request to delete %s %s has been accepted." error_msg = "Unable to delete the specified %s(s)." for id in ids: try: self.delete_func(id) print(success_msg % (self.resource, id)) except Exception as e: failure_flag = True print(e) if failure_flag: raise exceptions.CommandError(error_msg % self.resource) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/osc/v1/database_backup_strategy.py0000664000175000017500000000725700000000000026475 0ustar00zuulzuul00000000000000# Copyright 2020 Catalyst Cloud # # 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 osc_lib.command import command from osc_lib import utils as osc_utils from troveclient.i18n import _ class ListDatabaseBackupStrategies(command.Lister): _description = _("List backup strategies") columns = ['Project ID', 'Instance ID', 'Swift Container'] def get_parser(self, prog_name): parser = super(ListDatabaseBackupStrategies, self).get_parser( prog_name) parser.add_argument( '--instance-id', help=_('Filter results by database instance ID.') ) parser.add_argument( '--project-id', help=_('Project ID in Keystone. Only admin user is allowed to ' 'list backup strategy for other projects.') ) return parser def take_action(self, parsed_args): manager = self.app.client_manager.database.backup_strategies result = manager.list(instance_id=parsed_args.instance_id, project_id=parsed_args.project_id) backup_strategies = [osc_utils.get_item_properties(item, self.columns) for item in result] return self.columns, backup_strategies class CreateDatabaseBackupStrategy(command.ShowOne): _description = _("Creates backup strategy for the project or a particular " "instance.") def get_parser(self, prog_name): parser = super(CreateDatabaseBackupStrategy, self).get_parser( prog_name) parser.add_argument( '--project-id', help=_('Project ID in Keystone. Only admin user is allowed to ' 'create backup strategy for other projects.') ) parser.add_argument( '--instance-id', help=_('Database instance ID.') ) parser.add_argument( '--swift-container', help=_('The container name for storing the backup data when Swift ' 'is used as backup storage backend.') ) return parser def take_action(self, parsed_args): manager = self.app.client_manager.database.backup_strategies result = manager.create( instance_id=parsed_args.instance_id, swift_container=parsed_args.swift_container ) return zip(*sorted(result.to_dict().items())) class DeleteDatabaseBackupStrategy(command.Command): _description = _("Deletes backup strategy.") def get_parser(self, prog_name): parser = super(DeleteDatabaseBackupStrategy, self).get_parser( prog_name) parser.add_argument( '--project-id', help=_('Project ID in Keystone. Only admin user is allowed to ' 'delete backup strategy for other projects.') ) parser.add_argument( '--instance-id', help=_('Database instance ID.') ) return parser def take_action(self, parsed_args): manager = self.app.client_manager.database.backup_strategies manager.delete(instance_id=parsed_args.instance_id, project_id=parsed_args.project_id) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/osc/v1/database_backups.py0000664000175000017500000003021700000000000024726 0ustar00zuulzuul00000000000000# 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. """Database v1 Backups action implementations""" from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils as osc_utils from oslo_utils import uuidutils from troveclient.i18n import _ from troveclient.osc.v1 import base from troveclient import utils as trove_utils def set_attributes_for_print_detail(backup): info = backup._info.copy() if hasattr(backup, 'datastore'): info['datastore'] = backup.datastore['type'] info['datastore_version'] = backup.datastore['version'] info['datastore_version_id'] = backup.datastore['version_id'] return info class ListDatabaseBackups(command.Lister): _description = _("List database backups") columns = ['ID', 'Instance ID', 'Name', 'Status', 'Parent ID', 'Updated', 'Project ID'] def get_parser(self, prog_name): parser = super(ListDatabaseBackups, self).get_parser(prog_name) parser.add_argument( '--limit', dest='limit', metavar='', default=None, help=_('Return up to N number of the most recent bcakups.') ) parser.add_argument( '--marker', dest='marker', metavar='', type=str, default=None, help=_('Begin displaying the results for IDs greater than the' 'specified marker. When used with ``--limit``, set ' 'this to the last ID displayed in the previous run.') ) parser.add_argument( '--datastore', dest='datastore', metavar='', default=None, help=_('ID or name of the datastore (to filter backups by).') ) parser.add_argument( '--instance-id', default=None, help=_('Filter backups by database instance ID. Deprecated since ' 'Xena. Use -i/--instance instead.') ) parser.add_argument( '-i', '--instance', default=None, help=_('Filter backups by database instance(ID or name).') ) parser.add_argument( '--all-projects', action='store_true', help=_('Get all the backups of all the projects(Admin only).') ) parser.add_argument( '--project-id', default=None, help=_('Filter backups by project ID.') ) return parser def take_action(self, parsed_args): database_backups = self.app.client_manager.database.backups instance_id = parsed_args.instance or parsed_args.instance_id if instance_id: instance_mgr = self.app.client_manager.database.instances instance_id = trove_utils.get_resource_id(instance_mgr, instance_id) items = database_backups.list(limit=parsed_args.limit, datastore=parsed_args.datastore, marker=parsed_args.marker, instance_id=instance_id, all_projects=parsed_args.all_projects, project_id=parsed_args.project_id) backups = items while items.next and not parsed_args.limit: items = database_backups.list( marker=items.next, datastore=parsed_args.datastore, instance_id=parsed_args.instance_id, all_projects=parsed_args.all_projects, project_id=parsed_args.project_id ) backups += items backups = [osc_utils.get_item_properties(b, self.columns) for b in backups] return self.columns, backups class ListDatabaseInstanceBackups(command.Lister): _description = _("Lists available backups for an instance.") columns = ['ID', 'Instance ID', 'Name', 'Status', 'Parent ID', 'Updated'] def get_parser(self, prog_name): parser = super(ListDatabaseInstanceBackups, self).get_parser(prog_name) parser.add_argument( 'instance', metavar='', help=_('ID or name of the instance.') ) parser.add_argument( '--limit', dest='limit', metavar='', default=None, help=_('Return up to N number of the most recent bcakups.') ) parser.add_argument( '--marker', dest='marker', metavar='', type=str, default=None, help=_('Begin displaying the results for IDs greater than the' 'specified marker. When used with ``--limit``, set ' 'this to the last ID displayed in the previous run.') ) return parser def take_action(self, parsed_args): database_instances = self.app.client_manager.database.instances instance = osc_utils.find_resource(database_instances, parsed_args.instance) items = database_instances.backups(instance, limit=parsed_args.limit, marker=parsed_args.marker) backups = items while items.next and not parsed_args.limit: items = database_instances.backups(instance, marker=items.next) backups += items backups = [osc_utils.get_item_properties(b, self.columns) for b in backups] return self.columns, backups class ShowDatabaseBackup(command.ShowOne): _description = _("Shows details of a database backup") def get_parser(self, prog_name): parser = super(ShowDatabaseBackup, self).get_parser(prog_name) parser.add_argument( 'backup', metavar='', help=_('ID or name of the backup'), ) return parser def take_action(self, parsed_args): database_backups = self.app.client_manager.database.backups backup = osc_utils.find_resource(database_backups, parsed_args.backup) backup = set_attributes_for_print_detail(backup) return zip(*sorted(backup.items())) class DeleteDatabaseBackup(base.TroveDeleter): _description = _("Deletes a backup.") def get_parser(self, prog_name): parser = super(DeleteDatabaseBackup, self).get_parser(prog_name) parser.add_argument( 'backup', nargs='+', metavar='backup', help='Id or name of backup(s).' ) return parser def take_action(self, parsed_args): db_backups = self.app.client_manager.database.backups # Used for batch deletion self.delete_func = db_backups.delete self.resource = 'database backup' ids = [] for backup_id in parsed_args.backup: if not uuidutils.is_uuid_like(backup_id): try: backup_id = trove_utils.get_resource_id_by_name( db_backups, backup_id ) except Exception as e: msg = ("Failed to get database backup %s, error: %s" % (backup_id, str(e))) raise exceptions.CommandError(msg) ids.append(backup_id) self.delete_resources(ids) class CreateDatabaseBackup(command.ShowOne): _description = _("Creates a backup of an instance.") def get_parser(self, prog_name): parser = super(CreateDatabaseBackup, self).get_parser(prog_name) parser.add_argument( 'name', metavar='', help=_('Name of the backup.') ) parser.add_argument( '-i', '--instance', metavar='', help=_('ID or name of the instance. This is not required if ' 'restoring a backup from the data location.') ) parser.add_argument( '--description', metavar='', default=None, help=_('An optional description for the backup.') ) parser.add_argument( '--parent', metavar='', default=None, help=_('Optional ID of the parent backup to perform an' ' incremental backup from.') ) parser.add_argument( '--incremental', action='store_true', default=False, help=_('Create an incremental backup based on the last' ' full or incremental backup. It will create a' ' full backup if no existing backup found.') ) parser.add_argument( '--swift-container', help=_('The container name for storing the backup data when Swift ' 'is used as backup storage backend. If not specified, will ' 'use the container name configured in the backup strategy, ' 'otherwise, the default value configured by the cloud ' 'operator. Non-existent container is created ' 'automatically.') ) parser.add_argument( '--restore-from', help=_('The original backup data location, typically this is a ' 'Swift object URL.') ) parser.add_argument( '--restore-datastore-version', help=_('ID of the local datastore version corresponding to the ' 'original backup') ) parser.add_argument( '--restore-size', type=float, help=_('The original backup size.') ) return parser def take_action(self, parsed_args): manager = self.app.client_manager.database database_backups = manager.backups params = {} instance_id = None if parsed_args.restore_from: # Leave the input validation to Trove server. params.update({ 'restore_from': parsed_args.restore_from, 'restore_ds_version': parsed_args.restore_datastore_version, 'restore_size': parsed_args.restore_size, }) elif not parsed_args.instance: raise exceptions.CommandError('Instance ID or name is required if ' 'not restoring a backup.') else: instance_id = trove_utils.get_resource_id(manager.instances, parsed_args.instance) params.update({ 'description': parsed_args.description, 'parent_id': parsed_args.parent, 'incremental': parsed_args.incremental, 'swift_container': parsed_args.swift_container }) backup = database_backups.create(parsed_args.name, instance_id, **params) backup = set_attributes_for_print_detail(backup) return zip(*sorted(backup.items())) class DeleteDatabaseBackupExecution(command.Command): _description = _("Deletes an execution.") def get_parser(self, prog_name): parser = super(DeleteDatabaseBackupExecution, self).get_parser( prog_name) parser.add_argument( 'execution', metavar='', help=_('ID of the execution to delete.') ) return parser def take_action(self, parsed_args): database_backups = self.app.client_manager.database.backups database_backups.execution_delete(parsed_args.execution) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/osc/v1/database_clusters.py0000664000175000017500000003456100000000000025150 0ustar00zuulzuul00000000000000# 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. """Database v1 Clusters action implementations""" from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils from troveclient.i18n import _ from troveclient.v1.shell import _parse_extended_properties from troveclient.v1.shell import _parse_instance_options from troveclient.v1.shell import EXT_PROPS_HELP from troveclient.v1.shell import EXT_PROPS_METAVAR from troveclient.v1.shell import INSTANCE_HELP from troveclient.v1.shell import INSTANCE_METAVAR def set_attributes_for_print_detail(cluster): info = cluster._info.copy() if hasattr(cluster, 'datastore'): info['datastore'] = cluster.datastore['type'] info['datastore_version'] = cluster.datastore['version'] if hasattr(cluster, 'task'): info['task_description'] = cluster.task['description'] info['task_name'] = cluster.task['name'] info.pop('task', None) if hasattr(cluster, 'ip'): ip = [] for addr in cluster.ip: if isinstance(addr, dict): ip.append(addr['address']) else: ip.append(addr) info['ip'] = ', '.join(ip) instances = info.pop('instances', None) if instances: info['instance_count'] = len(instances) info.pop('links', None) return info class ListDatabaseClusters(command.Lister): _description = _("List database clusters") columns = ['ID', 'Name', 'Datastore', 'Datastore Version', 'Task Name'] def get_parser(self, prog_name): parser = super(ListDatabaseClusters, self).get_parser(prog_name) parser.add_argument( '--limit', dest='limit', metavar='', type=int, default=None, help=_('Limit the number of results displayed.') ) parser.add_argument( '--marker', dest='marker', metavar='', type=str, default=None, help=_('Begin displaying the results for IDs greater than the' ' specified marker. When used with ``--limit``, set ' ' this to the last ID displayed in the previous run.') ) return parser def take_action(self, parsed_args): database_clusters = self.app.client_manager.database.clusters clusters = database_clusters.list(limit=parsed_args.limit, marker=parsed_args.marker) for cluster in clusters: setattr(cluster, 'datastore_version', cluster.datastore['version']) setattr(cluster, 'datastore', cluster.datastore['type']) setattr(cluster, 'task_name', cluster.task['name']) clusters = [utils.get_item_properties(c, self.columns) for c in clusters] return self.columns, clusters class ShowDatabaseCluster(command.ShowOne): _description = _("Shows details of a database cluster") def get_parser(self, prog_name): parser = super(ShowDatabaseCluster, self).get_parser(prog_name) parser.add_argument( 'cluster', metavar='', help=_('ID or name of the cluster'), ) return parser def take_action(self, parsed_args): database_clusters = self.app.client_manager.database.clusters cluster = utils.find_resource(database_clusters, parsed_args.cluster) cluster = set_attributes_for_print_detail(cluster) return zip(*sorted(cluster.items())) class DeleteDatabaseCluster(command.Command): _description = _("Deletes a cluster.") def get_parser(self, prog_name): parser = super(DeleteDatabaseCluster, self).get_parser(prog_name) parser.add_argument( 'cluster', metavar='', help=_('ID or name of the cluster.'), ) return parser def take_action(self, parsed_args): database_clusters = self.app.client_manager.database.clusters try: cluster = utils.find_resource(database_clusters, parsed_args.cluster) database_clusters.delete(cluster) except Exception as e: msg = (_("Failed to delete cluster %(cluster)s: %(e)s") % {'cluster': parsed_args.cluster, 'e': e}) raise exceptions.CommandError(msg) class CreateDatabaseCluster(command.ShowOne): _description = _("Creates a new database cluster.") def get_parser(self, prog_name): parser = super(CreateDatabaseCluster, self).get_parser(prog_name) parser.add_argument( 'name', metavar='', type=str, help=_('Name of the cluster.'), ) parser.add_argument( 'datastore', metavar='', help=_('A datastore name or ID.'), ) parser.add_argument( 'datastore_version', metavar='', help=_('A datastore version name or ID.'), ) parser.add_argument( '--instance', metavar=INSTANCE_METAVAR, action='append', dest='instances', default=[], help=INSTANCE_HELP, ) parser.add_argument( '--locality', metavar='', default=None, choices=['affinity', 'anti-affinity'], help=_('Locality policy to use when creating cluster. ' 'Choose one of %(choices)s.'), ) parser.add_argument( '--extended-properties', dest='extended_properties', metavar=EXT_PROPS_METAVAR, default=None, help=EXT_PROPS_HELP, ) parser.add_argument( '--configuration', metavar='', type=str, default=None, help=_('ID of the configuration group to attach to the cluster.'), ) return parser def take_action(self, parsed_args): database = self.app.client_manager.database instances = _parse_instance_options(database, parsed_args.instances) extended_properties = {} if parsed_args.extended_properties: extended_properties = _parse_extended_properties( parsed_args.extended_properties) cluster = database.clusters.create( parsed_args.name, parsed_args.datastore, parsed_args.datastore_version, instances=instances, locality=parsed_args.locality, extended_properties=extended_properties, configuration=parsed_args.configuration) cluster = set_attributes_for_print_detail(cluster) return zip(*sorted(cluster.items())) class ResetDatabaseClusterStatus(command.Command): _description = _("Set the cluster task to NONE.") def get_parser(self, prog_name): parser = super(ResetDatabaseClusterStatus, self).get_parser(prog_name) parser.add_argument( 'cluster', metavar='', help=_('ID or name of the cluster.'), ) return parser def take_action(self, parsed_args): database_clusters = self.app.client_manager.database.clusters cluster = utils.find_resource(database_clusters, parsed_args.cluster) database_clusters.reset_status(cluster) class ListDatabaseClusterInstances(command.Lister): _description = _("Lists all instances of a cluster.") columns = ['ID', 'Name', 'Flavor ID', 'Size', 'Status'] def get_parser(self, prog_name): parser = (super(ListDatabaseClusterInstances, self) .get_parser(prog_name)) parser.add_argument( 'cluster', metavar='', help=_('ID or name of the cluster.')) return parser def take_action(self, parsed_args): database_clusters = self.app.client_manager.database.clusters cluster = utils.find_resource(database_clusters, parsed_args.cluster) instances = cluster._info['instances'] for instance in instances: instance['flavor_id'] = instance['flavor']['id'] if instance.get('volume'): instance['size'] = instance['volume']['size'] instances = [utils.get_dict_properties(inst, self.columns) for inst in instances] return self.columns, instances class UpgradeDatabaseCluster(command.Command): _description = _("Upgrades a cluster to a new datastore version.") def get_parser(self, prog_name): parser = super(UpgradeDatabaseCluster, self).get_parser(prog_name) parser.add_argument( 'cluster', metavar='', help=_('ID or name of the cluster.'), ) parser.add_argument( 'datastore_version', metavar='', help=_('A datastore version name or ID.'), ) return parser def take_action(self, parsed_args): database_clusters = self.app.client_manager.database.clusters cluster = utils.find_resource(database_clusters, parsed_args.cluster) database_clusters.upgrade(cluster, parsed_args.datastore_version) class ForceDeleteDatabaseCluster(command.Command): _description = _("Force delete a cluster.") def get_parser(self, prog_name): parser = super(ForceDeleteDatabaseCluster, self).get_parser(prog_name) parser.add_argument( 'cluster', metavar='', help=_('ID or name of the cluster.'), ) return parser def take_action(self, parsed_args): database_clusters = self.app.client_manager.database.clusters cluster = utils.find_resource(database_clusters, parsed_args.cluster) database_clusters.reset_status(cluster) try: database_clusters.delete(cluster) except Exception as e: msg = (_("Failed to delete cluster %(cluster)s: %(e)s") % {'cluster': parsed_args.cluster, 'e': e}) raise exceptions.CommandError(msg) class GrowDatabaseCluster(command.Command): _description = _("Adds more instances to a cluster.") def get_parser(self, prog_name): parser = super(GrowDatabaseCluster, self).get_parser(prog_name) parser.add_argument( '--instance', metavar=INSTANCE_METAVAR, action='append', dest='instances', default=[], help=INSTANCE_HELP ) parser.add_argument( 'cluster', metavar='', help=_('ID or name of the cluster.') ) return parser def take_action(self, parsed_args): database_client_manager = self.app.client_manager.database db_clusters = database_client_manager.clusters cluster = utils.find_resource(db_clusters, parsed_args.cluster) instances = _parse_instance_options(database_client_manager, parsed_args.instances, for_grow=True) db_clusters.grow(cluster, instances=instances) class ShrinkDatabaseCluster(command.Command): _description = _("Drops instances from a cluster.") def get_parser(self, prog_name): parser = super(ShrinkDatabaseCluster, self).get_parser(prog_name) parser.add_argument( 'cluster', metavar='', help=_('ID or name of the cluster.') ) parser.add_argument( 'instances', metavar='', nargs='+', default=[], help=_("Drop instance(s) from the cluster. Specify " "multiple ids to drop multiple instances.") ) return parser def take_action(self, parsed_args): database_client_manager = self.app.client_manager.database db_clusters = database_client_manager.clusters cluster = utils.find_resource(db_clusters, parsed_args.cluster) db_instances = database_client_manager.instances instances = [ {'id': utils.find_resource(db_instances, instance).id} for instance in parsed_args.instances ] db_clusters.shrink(cluster, instances) class ListDatabaseClusterModules(command.Lister): _description = _("Lists all modules for each instance of a cluster.") columns = ['instance_name', 'Module Name', 'Module Type', 'md5', 'created', 'updated'] def get_parser(self, prog_name): parser = (super(ListDatabaseClusterModules, self) .get_parser(prog_name)) parser.add_argument( 'cluster', metavar='', help=_('ID or name of the cluster.')) return parser def take_action(self, parsed_args): database_clusters = self.app.client_manager.database.clusters database_instances = self.app.client_manager.database.instances cluster = utils.find_resource(database_clusters, parsed_args.cluster) instances = cluster._info['instances'] modules = [] for instance in instances: new_list = database_instances.modules(instance['id']) for item in new_list: item.instance_id = instance['id'] item.instance_name = instance['name'] item.module_name = item.name item.module_type = item.type modules += new_list modules = [utils.get_item_properties(module, self.columns) for module in modules] return self.columns, modules ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/osc/v1/database_configurations.py0000664000175000017500000004054100000000000026331 0ustar00zuulzuul00000000000000# 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. """Database v1 Configurations action implementations""" import json from osc_lib.command import command from osc_lib import utils as osc_utils from oslo_utils import uuidutils from troveclient import exceptions from troveclient.i18n import _ def set_attributes_for_print_detail(configuration): info = configuration._info.copy() info['values'] = json.dumps(configuration.values) del info['datastore_version_id'] return info class ListDatabaseConfigurations(command.Lister): _description = _("List database configurations") columns = ['ID', 'Name', 'Description', 'Datastore Name', 'Datastore Version Name', 'Datastore Version Number'] def get_parser(self, prog_name): parser = super(ListDatabaseConfigurations, self).get_parser(prog_name) parser.add_argument( '--limit', dest='limit', metavar='', type=int, default=None, help=_('Limit the number of results displayed.') ) parser.add_argument( '--marker', dest='marker', metavar='', help=_('Begin displaying the results for IDs greater than the ' 'specified marker. When used with --limit, set this to ' 'the last ID displayed in the previous run.') ) return parser def take_action(self, parsed_args): db_configurations = self.app.client_manager.database.configurations config = db_configurations.list(limit=parsed_args.limit, marker=parsed_args.marker) config = [osc_utils.get_item_properties(c, self.columns) for c in config] return self.columns, config class ShowDatabaseConfiguration(command.ShowOne): _description = _("Shows details of a database configuration group.") def get_parser(self, prog_name): parser = super(ShowDatabaseConfiguration, self).get_parser(prog_name) parser.add_argument( 'configuration_group', metavar='', help=_('ID or name of the configuration group'), ) return parser def take_action(self, parsed_args): db_configurations = self.app.client_manager.database.configurations configuration = osc_utils.find_resource( db_configurations, parsed_args.configuration_group) configuration = set_attributes_for_print_detail(configuration) return zip(*sorted(configuration.items())) class ListDatabaseConfigurationParameters(command.Lister): _description = _("Lists available parameters for a configuration group.") columns = ['Name', 'Type', 'Min Size', 'Max Size', 'Restart Required'] def get_parser(self, prog_name): parser = super(ListDatabaseConfigurationParameters, self).\ get_parser(prog_name) parser.add_argument( 'datastore_version', metavar='', help=_('Datastore version name or ID assigned to the ' 'configuration group. ID is preferred if more than one ' 'datastore versions have the same name.') ) parser.add_argument( '--datastore', metavar='', default=None, help=_('ID or name of the datastore to list configuration' 'parameters for. Optional if the ID of the' 'datastore_version is provided.') ) return parser def take_action(self, parsed_args): db_configuration_parameters = self.app.client_manager.\ database.configuration_parameters if uuidutils.is_uuid_like(parsed_args.datastore_version): params = db_configuration_parameters.parameters_by_version( parsed_args.datastore_version) elif parsed_args.datastore: params = db_configuration_parameters.parameters( parsed_args.datastore, parsed_args.datastore_version) else: raise exceptions.NoUniqueMatch(_('Either datastore version ID or ' 'datastore name needs to be ' 'specified.')) for param in params: setattr(param, 'min_size', getattr(param, 'min', '-')) setattr(param, 'max_size', getattr(param, 'max', '-')) params = [osc_utils.get_item_properties(p, self.columns) for p in params] return self.columns, params class ShowDatabaseConfigurationParameter(command.ShowOne): _description = _("Shows details of a database configuration parameter.") def get_parser(self, prog_name): parser = super(ShowDatabaseConfigurationParameter, self).\ get_parser(prog_name) parser.add_argument( 'datastore_version', metavar='', help=_('Datastore version name or ID assigned to the ' 'configuration group. ID is preferred if more than one ' 'datastore versions have the same name.') ) parser.add_argument( 'parameter', metavar='', help=_('Name of the configuration parameter.'), ) parser.add_argument( '--datastore', metavar='', default=None, help=_('ID or name of the datastore to list configuration' ' parameters for. Optional if the ID of the' ' datastore_version is provided.'), ) return parser def take_action(self, parsed_args): db_configuration_parameters = self.app.client_manager.database. \ configuration_parameters if uuidutils.is_uuid_like(parsed_args.datastore_version): param = db_configuration_parameters.get_parameter_by_version( parsed_args.datastore_version, parsed_args.parameter) elif parsed_args.datastore: param = db_configuration_parameters.get_parameter( parsed_args.datastore, parsed_args.datastore_version, parsed_args.parameter) else: raise exceptions.NoUniqueMatch(_('Either datastore version ID or ' 'datastore name needs to be ' 'specified.')) return zip(*sorted(param._info.items())) class DeleteDatabaseConfiguration(command.Command): _description = _("Deletes a configuration group.") def get_parser(self, prog_name): parser = super(DeleteDatabaseConfiguration, self).get_parser(prog_name) parser.add_argument( 'configuration_group', metavar='', help=_('ID or name of the configuration group'), ) return parser def take_action(self, parsed_args): db_configurations = self.app.client_manager.database.configurations try: configuration = osc_utils.find_resource( db_configurations, parsed_args.configuration_group) db_configurations.delete(configuration) except Exception as e: msg = (_("Failed to delete configuration %(c_group)s: %(e)s") % {'c_group': parsed_args.configuration_group, 'e': e}) raise exceptions.CommandError(msg) class CreateDatabaseConfiguration(command.ShowOne): _description = _("Creates a configuration group.") def get_parser(self, prog_name): parser = super(CreateDatabaseConfiguration, self).get_parser(prog_name) parser.add_argument( 'name', metavar='', help=_('Name of the configuration group.'), ) parser.add_argument( 'values', metavar='', help=_('Dictionary of the values to set.'), ) parser.add_argument( '--datastore', metavar='', default=None, help=_('Datastore assigned to the configuration group. Required ' 'if default datastore is not configured.'), ) parser.add_argument( '--datastore-version', metavar='', default=None, help=_('Datastore version ID assigned to the ' 'configuration group.'), ) parser.add_argument( '--datastore-version-number', default=None, help=_('The version number for the database. The version number ' 'is needed for the datastore versions with the same name.'), ) parser.add_argument( '--description', metavar='', default=None, help=_('An optional description for the configuration group.'), ) return parser def take_action(self, parsed_args): db_configurations = self.app.client_manager.database.configurations config_grp = db_configurations.create( parsed_args.name, parsed_args.values, description=parsed_args.description, datastore=parsed_args.datastore, datastore_version=parsed_args.datastore_version, datastore_version_number=parsed_args.datastore_version_number) config_grp = set_attributes_for_print_detail(config_grp) return zip(*sorted(config_grp.items())) class AttachDatabaseConfiguration(command.Command): _description = _("Attaches a configuration group to an instance.") def get_parser(self, prog_name): parser = super(AttachDatabaseConfiguration, self).get_parser(prog_name) parser.add_argument( 'instance', metavar='', type=str, help=_('ID or name of the instance'), ) parser.add_argument( 'configuration', metavar='', type=str, help=_('ID or name of the configuration group to attach to the ' 'instance.'), ) return parser def take_action(self, parsed_args): manager = self.app.client_manager.database db_instances = manager.instances db_configurations = manager.configurations instance_id = parsed_args.instance config_id = parsed_args.configuration if not uuidutils.is_uuid_like(instance_id): instance_id = osc_utils.find_resource(db_instances, instance_id) if not uuidutils.is_uuid_like(config_id): config_id = osc_utils.find_resource(db_configurations, config_id) db_instances.update(instance_id, configuration=config_id) class DetachDatabaseConfiguration(command.Command): _description = _("Detaches a configuration group from an instance.") def get_parser(self, prog_name): parser = super(DetachDatabaseConfiguration, self).get_parser(prog_name) parser.add_argument( 'instance', metavar='', type=str, help=_('ID or name of the instance.'), ) return parser def take_action(self, parsed_args): db_instances = self.app.client_manager.database.instances instance_id = parsed_args.instance if not uuidutils.is_uuid_like(instance_id): instance_id = osc_utils.find_resource(db_instances, instance_id) db_instances.update(instance_id, remove_configuration=True) class ListDatabaseConfigurationInstances(command.Lister): _description = _("Lists all instances associated " "with a configuration group.") columns = ['ID', 'Name'] def get_parser(self, prog_name): parser = super(ListDatabaseConfigurationInstances, self).\ get_parser(prog_name) parser.add_argument( 'configuration_group', metavar='', help=_('ID or name of the configuration group.') ) parser.add_argument( '--limit', metavar='', default=None, type=int, help=_('Limit the number of results displayed.') ) parser.add_argument( '--marker', metavar='', default=None, type=str, help=_('Begin displaying the results for IDs greater than the ' 'specified marker. When used with --limit, set this to ' 'the last ID displayed in the previous run.') ) return parser def take_action(self, parsed_args): db_configurations = self.app.client_manager.database.configurations configuration = osc_utils.find_resource( db_configurations, parsed_args.configuration_group) params = db_configurations.instances(configuration, limit=parsed_args.limit, marker=parsed_args.marker) instance = [osc_utils.get_item_properties(p, self.columns) for p in params] return self.columns, instance class DefaultDatabaseConfiguration(command.ShowOne): _description = _("Shows the default configuration of an instance.") def get_parser(self, prog_name): parser = super(DefaultDatabaseConfiguration, self).get_parser( prog_name) parser.add_argument( 'instance', metavar='', type=str, help=_('ID or name of the instance.'), ) return parser def take_action(self, parsed_args): db_instances = self.app.client_manager.database.instances instance = osc_utils.find_resource(db_instances, parsed_args.instance) configs = db_instances.configuration(instance) return zip(*sorted(configs._info['configuration'].items())) class SetDatabaseConfiguration(command.Command): _description = _("Change parameters for a configuration group.") def get_parser(self, prog_name): parser = super(SetDatabaseConfiguration, self).get_parser(prog_name) parser.add_argument( 'configuration_group_id', help=_('Configuration group ID.'), ) parser.add_argument( 'values', metavar='', help=_('Dictionary of the new values to set.'), ) return parser def take_action(self, parsed_args): db_configurations = self.app.client_manager.database.configurations db_configurations.edit( parsed_args.configuration_group_id, parsed_args.values ) class UpdateDatabaseConfiguration(command.Command): _description = _("Update a configuration group.") def get_parser(self, prog_name): parser = super(UpdateDatabaseConfiguration, self).get_parser(prog_name) parser.add_argument( 'configuration_group_id', help=_('Configuration group ID.'), ) parser.add_argument( 'values', metavar='', help=_('Dictionary of the values to set.'), ) parser.add_argument( '--name', metavar='', help=_('New name of the configuration group.'), ) parser.add_argument( '--description', metavar='', default=None, help=_('An optional description for the configuration group.'), ) return parser def take_action(self, parsed_args): db_configurations = self.app.client_manager.database.configurations db_configurations.update( parsed_args.configuration_group_id, parsed_args.values, name=parsed_args.name, description=parsed_args.description ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/osc/v1/database_flavors.py0000664000175000017500000000654100000000000024755 0ustar00zuulzuul00000000000000# 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. """Database v1 Flavors action implementations""" from osc_lib.command import command from osc_lib import utils from troveclient import exceptions from troveclient.i18n import _ def set_attributes_for_print_detail(flavor): info = flavor._info.copy() # Get rid of those ugly links if info.get('links'): del(info['links']) # Fallback to str_id for flavors, where necessary if hasattr(flavor, 'str_id'): info['id'] = flavor.id del(info['str_id']) return info class ListDatabaseFlavors(command.Lister): _description = _("List database flavors") columns = ['ID', 'Name', 'RAM', 'vCPUs', 'Disk', 'Ephemeral'] def get_parser(self, prog_name): parser = super(ListDatabaseFlavors, self).get_parser(prog_name) parser.add_argument( '--datastore-type', dest='datastore_type', metavar='', help=_('Type of the datastore. For eg: mysql.') ) parser.add_argument( '--datastore-version-id', dest='datastore_version_id', metavar='', help=_('ID of the datastore version.') ) return parser def take_action(self, parsed_args): db_flavors = self.app.client_manager.database.flavors if parsed_args.datastore_type and parsed_args.datastore_version_id: flavors = db_flavors.list_datastore_version_associated_flavors( datastore=parsed_args.datastore_type, version_id=parsed_args.datastore_version_id) elif (not parsed_args.datastore_type and not parsed_args.datastore_version_id): flavors = db_flavors.list() else: raise exceptions.MissingArgs(['datastore-type', 'datastore-version-id']) # Fallback to str_id where necessary. _flavors = [] for f in flavors: if not f.id and hasattr(f, 'str_id'): f.id = f.str_id _flavors.append(utils.get_item_properties(f, self.columns)) return self.columns, _flavors class ShowDatabaseFlavor(command.ShowOne): _description = _("Shows details of a database flavor") def get_parser(self, prog_name): parser = super(ShowDatabaseFlavor, self).get_parser(prog_name) parser.add_argument( 'flavor', metavar='', help=_('ID or name of the flavor'), ) return parser def take_action(self, parsed_args): db_flavors = self.app.client_manager.database.flavors flavor = utils.find_resource(db_flavors, parsed_args.flavor) flavor = set_attributes_for_print_detail(flavor) return zip(*sorted(list(flavor.items()))) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/osc/v1/database_instances.py0000664000175000017500000007330300000000000025270 0ustar00zuulzuul00000000000000# 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. """Database v1 Instances action implementations""" import argparse from osc_lib.command import command from osc_lib import utils as osc_utils from oslo_utils import uuidutils from troveclient import exceptions from troveclient.i18n import _ from troveclient.osc.v1 import base from troveclient import utils as trove_utils def get_instances_info(instances): instances_info = [] for instance in instances: # To avoid invoking GET request to trove. instance_info = instance.to_dict() instance_info['flavor_id'] = instance.flavor['id'] instance_info['size'] = '-' if 'volume' in instance_info: instance_info['size'] = instance_info['volume']['size'] instance_info['role'] = '' if 'replica_of' in instance_info: instance_info['role'] = 'replica' if 'replicas' in instance_info: instance_info['role'] = 'primary' if 'datastore' in instance_info: if instance.datastore.get('version'): instance_info['datastore_version'] = instance.\ datastore['version'] instance_info['datastore'] = instance.datastore['type'] if 'access' in instance_info: instance_info['public'] = instance_info["access"].get( "is_public", False) if 'addresses' not in instance_info: instance_info['addresses'] = '' if 'operating_status' not in instance_info: # In case newer version python-troveclient is talking to older # version trove. instance_info['operating_status'] = '' instances_info.append(instance_info) return instances_info def set_attributes_for_print_detail(instance): info = instance.to_dict() info['flavor'] = instance.flavor['id'] if hasattr(instance, 'volume'): info['volume'] = instance.volume['size'] if 'used' in instance.volume: info['volume_used'] = instance.volume['used'] if hasattr(instance, 'ip'): info['ip'] = ', '.join(instance.ip) if hasattr(instance, 'datastore'): info['datastore'] = instance.datastore['type'] info['datastore_version'] = instance.datastore['version'] info['datastore_version_number'] = instance.datastore.get( 'version_number') if hasattr(instance, 'configuration'): info['configuration'] = instance.configuration['id'] if hasattr(instance, 'replica_of'): info['replica_of'] = instance.replica_of['id'] if hasattr(instance, 'replicas'): replicas = [replica['id'] for replica in instance.replicas] info['replicas'] = ', '.join(replicas) if hasattr(instance, 'networks'): info['networks'] = instance.networks['name'] info['networks_id'] = instance.networks['id'] if hasattr(instance, 'fault'): info.pop('fault', None) info['fault'] = instance.fault['message'] info['fault_date'] = instance.fault['created'] if 'details' in instance.fault and instance.fault['details']: info['fault_details'] = instance.fault['details'] if hasattr(instance, 'access'): info['public'] = instance.access.get("is_public", False) info['allowed_cidrs'] = instance.access.get('allowed_cidrs', []) info.pop("access", None) info.pop('links', None) return info class ListDatabaseInstances(command.Lister): _description = _("List database instances") columns = ['ID', 'Name', 'Datastore', 'Datastore Version', 'Status', 'Operating Status', 'Public', 'Addresses', 'Flavor ID', 'Size', 'Role'] admin_columns = columns + ["Server ID", "Tenant ID"] def get_parser(self, prog_name): parser = super(ListDatabaseInstances, self).get_parser(prog_name) parser.add_argument( '--limit', dest='limit', metavar='', default=None, help=_('Limit the number of results displayed.') ) parser.add_argument( '--marker', dest='marker', metavar='', type=str, default=None, help=_('Begin displaying the results for IDs greater than the' 'specified marker. When used with ``--limit``, set ' 'this to the last ID displayed in the previous run.') ) parser.add_argument( '--include_clustered', '--include-clustered', dest='include_clustered', action="store_true", default=False, help=_("Include instances that are part of a cluster " "(default %(default)s). --include-clustered may be " "deprecated in the future, retaining just " "--include_clustered.") ) parser.add_argument( '--all-projects', dest='all_projects', action="store_true", default=False, help=_("Include database instances of all projects (admin only)") ) parser.add_argument( '--project-id', help=_("Include database instances of a specific project " "(admin only)") ) return parser def take_action(self, parsed_args): extra_params = {} if parsed_args.all_projects or parsed_args.project_id: db_instances = self.app.client_manager.database.mgmt_instances cols = self.admin_columns if parsed_args.project_id: extra_params['project_id'] = parsed_args.project_id else: db_instances = self.app.client_manager.database.instances cols = self.columns instances = db_instances.list( limit=parsed_args.limit, marker=parsed_args.marker, include_clustered=parsed_args.include_clustered, **extra_params ) if instances: instances_info = get_instances_info(instances) instances = [osc_utils.get_dict_properties(info, cols) for info in instances_info] return cols, instances class ShowDatabaseInstance(command.ShowOne): _description = _("Show instance details") def get_parser(self, prog_name): parser = super(ShowDatabaseInstance, self).get_parser(prog_name) parser.add_argument( 'instance', metavar='', help=_('Instance (name or ID)'), ) return parser def take_action(self, parsed_args): db_instances = self.app.client_manager.database.instances instance = osc_utils.find_resource(db_instances, parsed_args.instance) instance = set_attributes_for_print_detail(instance) return zip(*sorted(instance.items())) class DeleteDatabaseInstance(base.TroveDeleter): _description = _("Deletes an instance.") def get_parser(self, prog_name): parser = super(DeleteDatabaseInstance, self).get_parser(prog_name) parser.add_argument( 'instance', nargs='+', metavar='instance', help='Id or name of instance(s).' ) parser.add_argument( '--force', action="store_true", default=False, help=_('Force delete the instance, will reset the instance status ' 'before deleting.'), ) return parser def take_action(self, parsed_args): db_instances = self.app.client_manager.database.instances # Used for batch deletion self.delete_func = (db_instances.force_delete if parsed_args.force else db_instances.delete) self.resource = 'database instance' ids = [] for instance_id in parsed_args.instance: if not uuidutils.is_uuid_like(instance_id): try: instance_id = trove_utils.get_resource_id_by_name( db_instances, instance_id ) except Exception as e: msg = ("Failed to get database instance %s, error: %s" % (instance_id, str(e))) raise exceptions.CommandError(msg) ids.append(instance_id) self.delete_resources(ids) class CreateDatabaseInstance(command.ShowOne): _description = _("Creates a new database instance.") def get_parser(self, prog_name): parser = super(CreateDatabaseInstance, self).get_parser(prog_name) parser.add_argument( 'name', metavar='', help=_("Name of the instance."), ) parser.add_argument( '--flavor', metavar='', type=str, help=_("Flavor to create the instance (name or ID). Flavor is not " "required when creating replica instances."), ) parser.add_argument( '--size', metavar='', type=int, default=None, help=_("Size of the instance disk volume in GB. " "Required when volume support is enabled."), ) parser.add_argument( '--volume-type', metavar='', type=str, default=None, help=_("Volume type. Optional when volume support is enabled."), ) parser.add_argument( '--databases', metavar='', nargs="+", default=[], help=_("Optional list of databases."), ) parser.add_argument( '--users', metavar='', nargs="+", default=[], help=_("Optional list of users."), ) parser.add_argument( '--backup', metavar='', default=None, help=_("A backup name or ID."), ) parser.add_argument( '--availability-zone', metavar='', default=None, help=_("The Zone hint to give to Nova."), ) parser.add_argument( '--datastore', metavar='', default=None, help=_("A datastore name or ID."), ) parser.add_argument( '--datastore-version', metavar='', default=None, help=_("A datastore version name or ID."), ) parser.add_argument( '--datastore-version-number', default=None, help=_('The version number for the database. The version number ' 'is needed for the datastore versions with the same name.'), ) parser.add_argument( '--nic', metavar=(',subnet-id=,' 'ip-address=>'), dest='nics', help=_("Create instance in the given Neutron network. This " "information is used for creating user-facing port for the " "instance. Either network ID or subnet ID (or both) should " "be specified, IP address is optional"), ) parser.add_argument( '--configuration', metavar='', default=None, help=_("ID of the configuration group to attach to the instance."), ) parser.add_argument( '--replica-of', metavar='', default=None, help=_("ID or name of an existing instance to replicate from."), ) parser.add_argument( '--replica-count', metavar='', type=int, default=None, help=_("Number of replicas to create (defaults to 1 if " "replica_of specified)."), ) parser.add_argument( '--module', metavar='', type=str, dest='modules', action='append', default=[], help=_("ID or name of the module to apply. Specify multiple " "times to apply multiple modules."), ) parser.add_argument( '--locality', metavar='', default=None, choices=['affinity', 'anti-affinity'], help=_("Locality policy to use when creating replicas. Choose " "one of %(choices)s."), ) parser.add_argument( '--region', metavar='', type=str, default=None, help=argparse.SUPPRESS, ) parser.add_argument( '--is-public', action='store_true', help="Whether or not to make the instance public.", ) parser.add_argument( '--allowed-cidr', action='append', dest='allowed_cidrs', help="The IP CIDRs that are allowed to access the database " "instance. Repeat for multiple values", ) return parser def take_action(self, parsed_args): database = self.app.client_manager.database db_instances = database.instances if not parsed_args.replica_of and not parsed_args.flavor: raise exceptions.CommandError(_("Please specify a flavor")) if parsed_args.replica_of and parsed_args.flavor: print("Warning: Flavor is ignored for creating replica.") if not parsed_args.replica_of: flavor_id = osc_utils.find_resource( database.flavors, parsed_args.flavor).id else: flavor_id = None volume = None if parsed_args.size is not None and parsed_args.size <= 0: raise exceptions.ValidationError( _("Volume size '%s' must be an integer and greater than 0.") % parsed_args.size) elif parsed_args.size: volume = {"size": parsed_args.size, "type": parsed_args.volume_type} restore_point = None if parsed_args.backup: restore_point = {"backupRef": osc_utils.find_resource( database.backups, parsed_args.backup).id} replica_of = None replica_count = parsed_args.replica_count if parsed_args.replica_of: replica_of = osc_utils.find_resource( db_instances, parsed_args.replica_of) replica_count = replica_count or 1 locality = None if parsed_args.locality: locality = parsed_args.locality if replica_of: raise exceptions.ValidationError( _('Cannot specify locality when adding replicas ' 'to existing master.')) databases = [{'name': value} for value in parsed_args.databases] users = [{'name': n, 'password': p, 'databases': databases} for (n, p) in [z.split(':')[:2] for z in parsed_args.users]] nics = [] if parsed_args.nics: nic_info = {} allowed_keys = { 'net-id': 'network_id', 'subnet-id': 'subnet_id', 'ip-address': 'ip_address' } fields = parsed_args.nics.split(',') for field in fields: field = field.strip() k, v = field.split('=', 1) k = k.strip() v = v.strip() if k not in allowed_keys.keys(): raise exceptions.ValidationError( f"{k} is not allowed." ) if v: nic_info[allowed_keys[k]] = v nics.append(nic_info) modules = [] for module in parsed_args.modules: modules.append(osc_utils.find_resource(database.modules, module).id) access = {'is_public': False} if parsed_args.is_public: access['is_public'] = True if parsed_args.allowed_cidrs: access['allowed_cidrs'] = parsed_args.allowed_cidrs instance = db_instances.create( parsed_args.name, flavor_id=flavor_id, volume=volume, databases=databases, users=users, restorePoint=restore_point, availability_zone=(parsed_args.availability_zone), datastore=parsed_args.datastore, datastore_version=(parsed_args.datastore_version), datastore_version_number=(parsed_args.datastore_version_number), nics=nics, configuration=parsed_args.configuration, replica_of=replica_of, replica_count=replica_count, modules=modules, locality=locality, region_name=parsed_args.region, access=access ) instance = set_attributes_for_print_detail(instance) return zip(*sorted(instance.items())) class ResetDatabaseInstanceStatus(command.Command): _description = _("Set instance service status to ERROR and clear the " "current task status. Mark any running backup operations " "as FAILED.") def get_parser(self, prog_name): parser = super(ResetDatabaseInstanceStatus, self).get_parser(prog_name) parser.add_argument( 'instance', metavar='', help=_('ID or name of the instance'), ) return parser def take_action(self, parsed_args): db_instances = self.app.client_manager.database.instances instance = osc_utils.find_resource(db_instances, parsed_args.instance) db_instances.reset_status(instance) class ResizeDatabaseInstanceFlavor(command.Command): _description = _("Resize an instance with a new flavor") def get_parser(self, prog_name): parser = super(ResizeDatabaseInstanceFlavor, self).get_parser( prog_name ) parser.add_argument( 'instance', metavar='', type=str, help=_('ID or name of the instance') ) parser.add_argument( 'flavor', type=str, help=_('ID or name of the new flavor.') ) return parser def take_action(self, parsed_args): instance_mgr = self.app.client_manager.database.instances flavor_mgr = self.app.client_manager.database.flavors instance_id = parsed_args.instance if not uuidutils.is_uuid_like(instance_id): instance = osc_utils.find_resource(instance_mgr, instance_id) instance_id = instance.id flavor = osc_utils.find_resource(flavor_mgr, parsed_args.flavor) instance_mgr.resize_instance(instance_id, flavor.id) class UpgradeDatabaseInstance(command.Command): _description = _("Upgrades an instance to a new datastore version.") def get_parser(self, prog_name): parser = super(UpgradeDatabaseInstance, self).get_parser(prog_name) parser.add_argument( 'instance', metavar='', type=str, help=_('ID or name of the instance.'), ) parser.add_argument( 'datastore_version', metavar='', help=_('ID or name of the datastore version.'), ) return parser def take_action(self, parsed_args): db_instances = self.app.client_manager.database.instances instance = osc_utils.find_resource(db_instances, parsed_args.instance) db_instances.upgrade(instance, parsed_args.datastore_version) class ResizeDatabaseInstanceVolume(command.Command): _description = _("Resizes the volume size of an instance.") def get_parser(self, prog_name): parser = super(ResizeDatabaseInstanceVolume, self).get_parser( prog_name ) parser.add_argument( 'instance', metavar='', type=str, help=_('ID or name of the instance.') ) parser.add_argument( 'size', metavar='', type=int, default=None, help=_('New size of the instance disk volume in GB.') ) return parser def take_action(self, parsed_args): db_instances = self.app.client_manager.database.instances instance = osc_utils.find_resource(db_instances, parsed_args.instance) db_instances.resize_volume(instance, parsed_args.size) class ForceDeleteDatabaseInstance(command.Command): _description = _("Force delete an instance.") def get_parser(self, prog_name): parser = ( super(ForceDeleteDatabaseInstance, self).get_parser(prog_name)) parser.add_argument( 'instance', metavar='', help=_('ID or name of the instance'), ) return parser def take_action(self, parsed_args): db_instances = self.app.client_manager.database.instances instance = osc_utils.find_resource(db_instances, parsed_args.instance) db_instances.reset_status(instance) try: db_instances.delete(instance) except Exception as e: msg = (_("Failed to delete instance %(instance)s: %(e)s") % {'instance': parsed_args.instance, 'e': e}) raise exceptions.CommandError(msg) class PromoteDatabaseInstanceToReplicaSource(command.Command): _description = _( "Promotes a replica to be the new replica source of its set.") def get_parser(self, prog_name): parser = super(PromoteDatabaseInstanceToReplicaSource, self).get_parser(prog_name) parser.add_argument( 'instance', metavar='', type=str, help=_('ID or name of the instance.'), ) return parser def take_action(self, parsed_args): db_instances = self.app.client_manager.database.instances instance = osc_utils.find_resource(db_instances, parsed_args.instance) db_instances.promote_to_replica_source(instance) class RestartDatabaseInstance(command.Command): _description = _("Restarts an instance.") def get_parser(self, prog_name): parser = super(RestartDatabaseInstance, self).get_parser( prog_name ) parser.add_argument( 'instance', metavar='', type=str, help=_('ID or name of the instance.') ) return parser def take_action(self, parsed_args): db_instances = self.app.client_manager.database.instances instance = osc_utils.find_resource(db_instances, parsed_args.instance) db_instances.restart(instance) class EjectDatabaseInstanceReplicaSource(command.Command): _description = _("Ejects a replica source from its set.") def get_parser(self, prog_name): parser = super(EjectDatabaseInstanceReplicaSource, self).get_parser( prog_name) parser.add_argument( 'instance', metavar='', type=str, help=_('ID or name of the instance.'), ) return parser def take_action(self, parsed_args): db_instances = self.app.client_manager.database.instances instance = osc_utils.find_resource(db_instances, parsed_args.instance) db_instances.eject_replica_source(instance) class UpdateDatabaseInstance(command.Command): _description = _("Updates an instance: Edits name, " "configuration, or replica source.") def get_parser(self, prog_name): parser = super(UpdateDatabaseInstance, self).get_parser(prog_name) parser.add_argument( 'instance', metavar='', type=str, help=_('ID or name of the instance.'), ) parser.add_argument( '--name', metavar='', type=str, default=None, help=_('ID or name of the instance.'), ) parser.add_argument( '--configuration', metavar='', type=str, default=None, help=_('ID of the configuration reference to attach.'), ) parser.add_argument( '--detach-replica-source', '--detach_replica_source', dest='detach_replica_source', action="store_true", default=False, help=_('Detach the replica instance from its replication source. ' '--detach-replica-source may be deprecated in the future ' 'in favor of just --detach_replica_source'), ) parser.add_argument( '--remove-configuration', '--remove_configuration', dest='remove_configuration', action="store_true", default=False, help=_('Drops the current configuration reference.'), ) public_group = parser.add_mutually_exclusive_group() public_group.add_argument( '--is-public', dest='public', default=None, action='store_true', help="Make the database instance accessible to public.", ) public_group.add_argument( '--is-private', dest='public', default=None, action='store_false', help="Make the database instance inaccessible to public.", ) parser.add_argument( '--allowed-cidr', action='append', dest='allowed_cidrs', help="The IP CIDRs that are allowed to access the database " "instance. Repeat for multiple values", ) return parser def take_action(self, parsed_args): instance_mgr = self.app.client_manager.database.instances instance_id = parsed_args.instance if not uuidutils.is_uuid_like(instance_id): instance_id = osc_utils.find_resource(instance_mgr, instance_id) instance_mgr.update(instance_id, parsed_args.configuration, parsed_args.name, parsed_args.detach_replica_source, parsed_args.remove_configuration, is_public=parsed_args.public, allowed_cidrs=parsed_args.allowed_cidrs) class DetachDatabaseInstanceReplica(command.Command): _description = _("Detaches a replica instance " "from its replication source.") def get_parser(self, prog_name): parser = super(DetachDatabaseInstanceReplica, self).get_parser( prog_name) parser.add_argument( 'instance', metavar='', type=str, help=_('ID or name of the instance.'), ) return parser def take_action(self, parsed_args): db_instances = self.app.client_manager.database.instances instance = osc_utils.find_resource(db_instances, parsed_args.instance) db_instances.update(instance, detach_replica_source=True) class RebootDatabaseInstance(command.Command): _description = _("Reboots an instance(the Nova server).") def get_parser(self, prog_name): parser = super(RebootDatabaseInstance, self).get_parser(prog_name) parser.add_argument( 'instance', metavar='', type=str, help=_('ID or name of the instance.')) return parser def take_action(self, parsed_args): instance_id = parsed_args.instance if not uuidutils.is_uuid_like(instance_id): instance_mgr = self.app.client_manager.database.instances instance_id = osc_utils.find_resource(instance_mgr, instance_id) mgmt_instance_mgr = self.app.client_manager.database.mgmt_instances mgmt_instance_mgr.reboot(instance_id) class RebuildDatabaseInstance(command.Command): _description = _("Rebuilds an instance(the Nova server).") def get_parser(self, prog_name): parser = super(RebuildDatabaseInstance, self).get_parser(prog_name) parser.add_argument( 'instance', metavar='', type=str, help=_('ID or name of the instance.')) parser.add_argument( 'image', metavar='', help=_('ID of the new guest image.')) return parser def take_action(self, parsed_args): instance_id = parsed_args.instance if not uuidutils.is_uuid_like(instance_id): instance_mgr = self.app.client_manager.database.instances instance_id = osc_utils.find_resource(instance_mgr, instance_id) mgmt_instance_mgr = self.app.client_manager.database.mgmt_instances mgmt_instance_mgr.rebuild(instance_id, parsed_args.image) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/osc/v1/database_limits.py0000664000175000017500000000240000000000000024570 0ustar00zuulzuul00000000000000# 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. """Database v1 Limits action implementations""" from osc_lib.command import command from osc_lib import utils as osc_utils from troveclient.i18n import _ from troveclient import utils class ListDatabaseLimits(command.Lister): _description = _("List database limits") columns = ['Value', 'Verb', 'Remaining', 'Unit'] def take_action(self, parsed_args): database_limits = self.app.client_manager.database.limits limits = database_limits.list() # Pop the first one, its absolute limits utils.print_dict(limits.pop(0)._info) limits = [osc_utils.get_item_properties(i, self.columns) for i in limits] return self.columns, limits ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/osc/v1/database_logs.py0000664000175000017500000001726700000000000024254 0ustar00zuulzuul00000000000000# 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 osc_lib.command import command from osc_lib import utils as osc_utils from troveclient import exceptions from troveclient.i18n import _ class ListDatabaseLogs(command.Lister): _description = _("Lists the log files available for instance.") columns = ['Name', 'Type', 'Status', 'Published', 'Pending', 'Container', 'Prefix'] def get_parser(self, prog_name): parser = super(ListDatabaseLogs, self).get_parser(prog_name) parser.add_argument( 'instance', metavar='', help=_('ID or name of the instance.') ) return parser def take_action(self, parsed_args): database_instances = self.app.client_manager.database.instances instance = osc_utils.find_resource(database_instances, parsed_args.instance) log_list = database_instances.log_list(instance) logs = [osc_utils.get_item_properties(l, self.columns) for l in log_list] return self.columns, logs class SetDatabaseInstanceLog(command.ShowOne): _description = _("Instructs Trove guest to operate logs.") def get_parser(self, prog_name): parser = super(SetDatabaseInstanceLog, self).get_parser(prog_name) parser.add_argument( 'instance', metavar='', type=str, help=_('Id or Name of the instance.') ) parser.add_argument( 'log_name', metavar='', type=str, help=_('Name of log to operate.') ) parser.add_argument( '--enable', action='store_true', help="Whether or not to enable log collection.", ) parser.add_argument( '--disable', action='store_true', help="Whether or not to disable log collection.", ) parser.add_argument( '--publish', action='store_true', help="Whether or not to publish log files to the backend storage " "for logs(Swift by default).", ) parser.add_argument( '--discard', action='store_true', help="Whether or not to discard the existing logs before publish.", ) return parser def take_action(self, parsed_args): db_instances = self.app.client_manager.database.instances instance = osc_utils.find_resource(db_instances, parsed_args.instance) log_info = db_instances.log_action( instance, parsed_args.log_name, enable=parsed_args.enable, disable=parsed_args.disable, discard=parsed_args.discard, publish=parsed_args.publish ) result = log_info._info return zip(*sorted(result.items())) class ShowDatabaseInstanceLog(command.ShowOne): _description = _("Show information of given log name for the database " "instance.") def get_parser(self, prog_name): parser = super(ShowDatabaseInstanceLog, self).get_parser(prog_name) parser.add_argument( 'instance', metavar='', type=str, help=_('Id or Name of the instance.') ) parser.add_argument( 'log_name', metavar='', type=str, help=_('Name of log to operate.') ) return parser def take_action(self, parsed_args): db_instances = self.app.client_manager.database.instances instance = osc_utils.find_resource(db_instances, parsed_args.instance) log_info = db_instances.log_show(instance, parsed_args.log_name) result = log_info._info return zip(*sorted(result.items())) class ShowDatabaseInstanceLogContents(command.Command): _description = _("Show the content of log file.") def get_parser(self, prog_name): parser = super(ShowDatabaseInstanceLogContents, self).get_parser( prog_name) parser.add_argument( 'instance', metavar='', type=str, help=_('Id or Name of the instance.') ) parser.add_argument( 'log_name', metavar='', type=str, help=_('Name of log to operate.') ) parser.add_argument( '--lines', default=50, type=int, help="The number of log lines can be shown in batch.", ) return parser def take_action(self, parsed_args): db_instances = self.app.client_manager.database.instances instance = osc_utils.find_resource(db_instances, parsed_args.instance) try: log_gen = db_instances.log_generator(instance, parsed_args.log_name, lines=parsed_args.lines) for log_part in log_gen(): print(log_part, end="") except exceptions.GuestLogNotFoundError: print( "ERROR: No published '%(log_name)s' log was found for " "%(instance)s" % {'log_name': parsed_args.log_name, 'instance': instance} ) except Exception as ex: error_msg = ex.message.split('\n') print(error_msg[0]) class SaveDatabaseInstanceLog(command.Command): _description = _("Save the log file.") def get_parser(self, prog_name): parser = super(SaveDatabaseInstanceLog, self).get_parser(prog_name) parser.add_argument( 'instance', metavar='', type=str, help=_('Id or Name of the instance.') ) parser.add_argument( 'log_name', metavar='', type=str, help=_('Name of log to operate.') ) parser.add_argument( '--file', help="Path of file to save log to for instance.", ) return parser def take_action(self, parsed_args): db_instances = self.app.client_manager.database.instances instance = osc_utils.find_resource(db_instances, parsed_args.instance) try: filepath = db_instances.log_save(instance, parsed_args.log_name, filename=parsed_args.file) print(_('Log "%(log_name)s" written to %(file_name)s') % {'log_name': parsed_args.log_name, 'file_name': filepath}) except exceptions.GuestLogNotFoundError: print( "ERROR: No published '%(log_name)s' log was found for " "%(instance)s" % {'log_name': parsed_args.log_name, 'instance': instance} ) except Exception as ex: error_msg = ex.message.split('\n') print(error_msg[0]) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/osc/v1/database_quota.py0000664000175000017500000000510100000000000024421 0ustar00zuulzuul00000000000000# 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. """Database v1 Quota action implementations""" from osc_lib import utils as osc_utils from osc_lib.command import command from troveclient.i18n import _ from troveclient import utils class ShowDatabaseQuota(command.Lister): _description = _("Show quotas for a project.") columns = ['Resource', 'In Use', 'Reserved', 'Limit'] def get_parser(self, prog_name): parser = super(ShowDatabaseQuota, self).get_parser(prog_name) parser.add_argument( 'project', help=_('Id or name of the project.'), ) return parser def take_action(self, parsed_args): db_quota = self.app.client_manager.database.quota project_id = utils.get_project_id( self.app.client_manager.identity, parsed_args.project ) quota = [osc_utils.get_item_properties(q, self.columns) for q in db_quota.show(project_id)] return self.columns, quota class UpdateDatabaseQuota(command.ShowOne): _description = _("Update quotas for a project.") def get_parser(self, prog_name): parser = super(UpdateDatabaseQuota, self).get_parser(prog_name) parser.add_argument( 'project', help=_('Id or name of the project.'), ) parser.add_argument( 'resource', metavar='', help=_('Resource name.'), ) parser.add_argument( 'limit', metavar='', type=int, help=_('New limit to set for the named resource.'), ) return parser def take_action(self, parsed_args): db_quota = self.app.client_manager.database.quota project_id = utils.get_project_id( self.app.client_manager.identity, parsed_args.project ) update_params = { parsed_args.resource: parsed_args.limit } updated_quota = db_quota.update(project_id, update_params) return zip(*sorted(updated_quota.items())) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/osc/v1/database_root.py0000664000175000017500000001152100000000000024256 0ustar00zuulzuul00000000000000# 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. """Database v1 Root action implementations""" from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils as osc_utils from troveclient.i18n import _ def find_instance_or_cluster(database_client_manager, instance_or_cluster): """Returns an instance or cluster, found by ID or name, along with the type of resource, instance or cluster. Raises CommandError if none is found. """ db_instances = database_client_manager.instances try: return (osc_utils.find_resource(db_instances, instance_or_cluster), 'instance') except exceptions.CommandError: db_clusters = database_client_manager.clusters try: return (osc_utils.find_resource(db_clusters, instance_or_cluster), 'cluster') except exceptions.CommandError: raise exceptions.CommandError( _("No instance or cluster with a name or ID of '%s' exists.") % instance_or_cluster) class EnableDatabaseRoot(command.ShowOne): _description = _("Enables root for an instance and resets " "if already exists.") def get_parser(self, prog_name): parser = super(EnableDatabaseRoot, self).get_parser(prog_name) parser.add_argument( 'instance_or_cluster', metavar='', help=_('ID or name of the instance or cluster.'), ) parser.add_argument( '--root_password', metavar='', default=None, help=_('Root password to set.')) return parser def take_action(self, parsed_args): database_client_manager = self.app.client_manager.database instance_or_cluster, resource_type = find_instance_or_cluster( database_client_manager, parsed_args.instance_or_cluster) db_root = database_client_manager.root if resource_type == 'instance': root = db_root.create_instance_root(instance_or_cluster, parsed_args.root_password) else: root = db_root.create_cluster_root(instance_or_cluster, parsed_args.root_password) result = {'name': root[0], 'password': root[1]} return zip(*sorted(result.items())) class DisableDatabaseRoot(command.Command): _description = _("Disables root for an instance.") def get_parser(self, prog_name): parser = super(DisableDatabaseRoot, self).get_parser(prog_name) parser.add_argument( 'instance', metavar='', help=_('ID or name of the instance.'), ) return parser def take_action(self, parsed_args): database_client_manager = self.app.client_manager.database db_instances = database_client_manager.instances instance = osc_utils.find_resource(db_instances, parsed_args.instance) db_root = database_client_manager.root db_root.disable_instance_root(instance) class ShowDatabaseRoot(command.ShowOne): _description = _("Gets status if root was ever enabled for " "an instance or cluster.") def get_parser(self, prog_name): parser = super(ShowDatabaseRoot, self).get_parser(prog_name) parser.add_argument( 'instance_or_cluster', metavar='', help=_('ID or name of the instance or cluster.'), ) return parser def take_action(self, parsed_args): database_client_manager = self.app.client_manager.database instance_or_cluster, resource_type = find_instance_or_cluster( database_client_manager, parsed_args.instance_or_cluster) db_root = database_client_manager.root if resource_type == 'instance': root = db_root.is_instance_root_enabled(instance_or_cluster) else: root = db_root.is_cluster_root_enabled(instance_or_cluster) result = {'is_root_enabled': root.rootEnabled} return zip(*sorted(result.items())) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/osc/v1/database_users.py0000664000175000017500000002576600000000000024454 0ustar00zuulzuul00000000000000# 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. """Database v1 Users action implementations""" from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils from troveclient.i18n import _ class CreateDatabaseUser(command.Command): _description = _("Creates a user on an instance.") def get_parser(self, prog_name): parser = super(CreateDatabaseUser, self).get_parser(prog_name) parser.add_argument( 'instance', metavar='', help=_('ID or name of the instance.') ) parser.add_argument( 'name', metavar='', help=_('Name of user.') ) parser.add_argument( 'password', metavar='', help=_('Password of user.') ) parser.add_argument( '--host', metavar='', help=_('Optional host of user.') ) parser.add_argument( '--databases', metavar='', nargs='+', default=[], help=_('Optional list of databases.') ) return parser def take_action(self, parsed_args): manager = self.app.client_manager.database users = manager.users instance = utils.find_resource(manager.instances, parsed_args.instance) databases = [{'name': value} for value in parsed_args.databases] user = {'name': parsed_args.name, 'password': parsed_args.password, 'databases': databases} if parsed_args.host: user['host'] = parsed_args.host users.create(instance, [user]) class ListDatabaseUsers(command.Lister): _description = _("Lists the users for an instance.") columns = ['Name', 'Host', 'Databases'] def get_parser(self, prog_name): parser = super(ListDatabaseUsers, self).get_parser(prog_name) parser.add_argument( dest='instance', metavar='', help=_('ID or name of the instance.') ) return parser def take_action(self, parsed_args): manager = self.app.client_manager.database db_users = manager.users instance = utils.find_resource(manager.instances, parsed_args.instance) items = db_users.list(instance) users = items while (items.next): items = db_users.list(parsed_args.instance, marker=items.next) users += items for user in users: db_names = [db['name'] for db in user.databases] user.databases = ', '.join(db_names) users = [utils.get_item_properties(u, self.columns) for u in users] return self.columns, users class ShowDatabaseUser(command.ShowOne): _description = _("Shows details of a database user of an instance.") def get_parser(self, prog_name): parser = super(ShowDatabaseUser, self).get_parser(prog_name) parser.add_argument( 'instance', metavar='', help=_('ID or name of the instance.'), ) parser.add_argument( 'name', metavar='', help=_('Name of user.'), ) parser.add_argument( "--host", metavar="", help=_("Optional host of user."), ) return parser def take_action(self, parsed_args): manager = self.app.client_manager.database db_users = manager.users instance = utils.find_resource(manager.instances, parsed_args.instance) user = db_users.get(instance, parsed_args.name, hostname=parsed_args.host) return zip(*sorted(user._info.items())) class DeleteDatabaseUser(command.Command): _description = _("Deletes a user from an instance.") def get_parser(self, prog_name): parser = super(DeleteDatabaseUser, self).get_parser(prog_name) parser.add_argument( 'instance', metavar='', help=_('ID or name of the instance.') ) parser.add_argument( 'name', metavar='', help=_('Name of user.') ) parser.add_argument( '--host', metavar='', help=_('Optional host of user.') ) return parser def take_action(self, parsed_args): manager = self.app.client_manager.database users = manager.users try: instance = utils.find_resource(manager.instances, parsed_args.instance) users.delete(instance, parsed_args.name, parsed_args.host) except Exception as e: msg = (_("Failed to delete user %(user)s: %(e)s") % {'user': parsed_args.name, 'e': e}) raise exceptions.CommandError(msg) class GrantDatabaseUserAccess(command.Command): _description = _("Grants access to a database(s) for a user.") def get_parser(self, prog_name): parser = super(GrantDatabaseUserAccess, self).get_parser(prog_name) parser.add_argument( 'instance', metavar='', help=_('ID or name of the instance.') ) parser.add_argument( 'name', metavar='', help=_('Name of user.') ) parser.add_argument( '--host', metavar='', default=None, help=_('Optional host of user.') ) parser.add_argument( 'databases', metavar='', nargs="+", default=[], help=_('List of databases.') ) return parser def take_action(self, parsed_args): manager = self.app.client_manager.database users = manager.users instance = utils.find_resource(manager.instances, parsed_args.instance) users.grant(instance, parsed_args.name, parsed_args.databases, hostname=parsed_args.host) class RevokeDatabaseUserAccess(command.Command): _description = _("Revokes access to a database for a user.") def get_parser(self, prog_name): parser = super(RevokeDatabaseUserAccess, self).get_parser(prog_name) parser.add_argument( 'instance', metavar='', help=_('ID or name of the instance.') ) parser.add_argument( 'name', metavar='', help=_('Name of user.') ) parser.add_argument( '--host', metavar='', default=None, help=_('Optional host of user.') ) parser.add_argument( 'databases', metavar='', help=_('A single database.') ) return parser def take_action(self, parsed_args): manager = self.app.client_manager.database users = manager.users instance = utils.find_resource(manager.instances, parsed_args.instance) users.revoke(instance, parsed_args.name, parsed_args.databases, hostname=parsed_args.host) class ShowDatabaseUserAccess(command.Lister): _description = _("Shows access details of a user of an instance.") columns = ['Name'] def get_parser(self, prog_name): parser = super(ShowDatabaseUserAccess, self).get_parser(prog_name) parser.add_argument( 'instance', metavar='', help=_('ID or name of the instance.') ) parser.add_argument( 'name', metavar='', help=_('Name of user.') ) parser.add_argument( '--host', metavar='', default=None, help=_('Optional host of user.') ) return parser def take_action(self, parsed_args): manager = self.app.client_manager.database users = manager.users instance = utils.find_resource(manager.instances, parsed_args.instance) names = users.list_access(instance, parsed_args.name, hostname=parsed_args.host) access = [utils.get_item_properties(n, self.columns) for n in names] return self.columns, access class UpdateDatabaseUserAttributes(command.Command): _description = _("Updates a user's attributes on an instance." "At least one optional argument must be provided.") def get_parser(self, prog_name): parser = super(UpdateDatabaseUserAttributes, self).get_parser(prog_name) parser.add_argument( 'instance', metavar='', help=_('ID or name of the instance.') ) parser.add_argument( 'name', metavar='', help=_('Name of user.') ) parser.add_argument( '--host', metavar='', default=None, help=_('Optional host of user.') ) parser.add_argument( '--new_name', metavar='', default=None, help=_('Optional new name of user.') ) parser.add_argument( '--new_password', metavar='', default=None, help=_('Optional new password of user.') ) parser.add_argument( '--new_host', metavar='', default=None, help=_('Optional new host of user.') ) return parser def take_action(self, parsed_args): manager = self.app.client_manager.database users = manager.users instance = utils.find_resource(manager.instances, parsed_args.instance) new_attrs = {} if parsed_args.new_name: new_attrs['name'] = parsed_args.new_name if parsed_args.new_password: new_attrs['password'] = parsed_args.new_password if parsed_args.new_host: new_attrs['host'] = parsed_args.new_host users.update_attributes(instance, parsed_args.name, newuserattr=new_attrs, hostname=parsed_args.host) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/osc/v1/databases.py0000664000175000017500000000775500000000000023414 0ustar00zuulzuul00000000000000# 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. """Database v1 Databases action implementations""" from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils from troveclient.i18n import _ class CreateDatabase(command.Command): _description = _("Creates a database on an instance.") def get_parser(self, prog_name): parser = super(CreateDatabase, self).get_parser(prog_name) parser.add_argument( "instance", metavar="", help=_("ID or name of the instance."), ) parser.add_argument( "name", metavar="", help=_("Name of the database."), ) parser.add_argument( "--character_set", metavar="", help=_("Optional character set for database."), ) parser.add_argument( "--collate", metavar="", help=_("Optional collation type for database."), ) return parser def take_action(self, parsed_args): manager = self.app.client_manager.database databases = manager.databases instance = utils.find_resource(manager.instances, parsed_args.instance) database_dict = {'name': parsed_args.name} if parsed_args.collate: database_dict['collate'] = parsed_args.collate if parsed_args.character_set: database_dict['character_set'] = parsed_args.character_set databases.create(instance, [database_dict]) class ListDatabases(command.Lister): _description = _("Get a list of all Databases from the instance.") columns = ['Name'] def get_parser(self, prog_name): parser = super(ListDatabases, self).get_parser(prog_name) parser.add_argument( dest='instance', metavar='', help=_('ID or name of the instance.') ) return parser def take_action(self, parsed_args): manager = self.app.client_manager.database databases = manager.databases instance = utils.find_resource(manager.instances, parsed_args.instance) items = databases.list(instance) dbs = items while items.next: items = databases.list(instance, marker=items.next) dbs += items dbs = [utils.get_item_properties(db, self.columns) for db in dbs] return self.columns, dbs class DeleteDatabase(command.Command): _description = _("Deletes a database from an instance.") def get_parser(self, prog_name): parser = super(DeleteDatabase, self).get_parser(prog_name) parser.add_argument( dest='instance', metavar='', help=_('ID or name of the instance.') ) parser.add_argument( dest='database', metavar='', help=_('Name of the database.') ) return parser def take_action(self, parsed_args): manager = self.app.client_manager.database databases = manager.databases try: instance = utils.find_resource(manager.instances, parsed_args.instance) databases.delete(instance, parsed_args.database) except Exception as e: msg = (_("Failed to delete database %(database)s: %(e)s") % {'database': parsed_args.database, 'e': e}) raise exceptions.CommandError(msg) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/osc/v1/datastores.py0000664000175000017500000003141300000000000023622 0ustar00zuulzuul00000000000000# 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. """Database v1 Datastores action implementations""" from osc_lib.command import command from osc_lib import utils from troveclient import exceptions from troveclient.i18n import _ from troveclient import utils as tc_utils def set_attributes_for_print_detail(datastore): info = datastore._info.copy() versions = info.get('versions', []) versions_str = "\n".join( [ver['name'] + " (" + ver['id'] + ")" for ver in versions]) info['versions (id)'] = versions_str info.pop('versions', None) info.pop('links', None) if hasattr(datastore, 'default_version'): def_ver_id = getattr(datastore, 'default_version') info['default_version'] = [ ver['name'] for ver in versions if ver['id'] == def_ver_id][0] return info class ListDatastores(command.Lister): _description = _("List available datastores") columns = ['ID', 'Name'] def take_action(self, parsed_args): datastore_client = self.app.client_manager.database.datastores datastores = datastore_client.list() ds = [utils.get_item_properties(d, self.columns) for d in datastores] return self.columns, ds class ShowDatastore(command.ShowOne): _description = _("Shows details of a datastore") def get_parser(self, prog_name): parser = super(ShowDatastore, self).get_parser(prog_name) parser.add_argument( 'datastore', metavar='', help=_('ID of the datastore'), ) return parser def take_action(self, parsed_args): datastore_client = self.app.client_manager.database.datastores datastore = utils.find_resource(datastore_client, parsed_args.datastore) datastore = set_attributes_for_print_detail(datastore) return zip(*sorted(datastore.items())) class DeleteDatastore(command.Command): _description = _("Deletes a datastore") def get_parser(self, prog_name): parser = super(DeleteDatastore, self).get_parser(prog_name) parser.add_argument( 'datastore', metavar='', help=_('ID or name of the datastore'), ) return parser def take_action(self, parsed_args): datastore_client = self.app.client_manager.database.datastores try: datastore_client.delete(parsed_args.datastore) except Exception as e: msg = (_("Failed to delete datastore %(datastore)s: %(e)s") % {'datastore': parsed_args.datastore, 'e': e}) raise exceptions.CommandError(msg) class ListDatastoreVersions(command.Lister): _description = _("Lists available versions for a datastore") columns = ['ID', 'Name', "Version"] def get_parser(self, prog_name): parser = super(ListDatastoreVersions, self).get_parser(prog_name) parser.add_argument( 'datastore', metavar='', help=_('ID or name of the datastore'), ) return parser def take_action(self, parsed_args): datastore_version_client = \ self.app.client_manager.database.datastore_versions versions = datastore_version_client.list(parsed_args.datastore) ds = [utils.get_dict_properties(d.to_dict(), self.columns) for d in versions] return self.columns, ds class ShowDatastoreVersion(command.ShowOne): _description = _("Shows details of a datastore version.") def get_parser(self, prog_name): parser = super(ShowDatastoreVersion, self).get_parser(prog_name) parser.add_argument( 'datastore_version', metavar='', help=_('ID or name of the datastore version.'), ) parser.add_argument( '--datastore', metavar='', default=None, help=_('ID or name of the datastore. Optional if the ID of' 'the datastore_version is provided.'), ) return parser def take_action(self, parsed_args): datastore_version_client =\ self.app.client_manager.database.datastore_versions if parsed_args.datastore: datastore_version = datastore_version_client.\ get(parsed_args.datastore, parsed_args.datastore_version) elif tc_utils.is_uuid_like(parsed_args.datastore_version): datastore_version = datastore_version_client.\ get_by_uuid(parsed_args.datastore_version) else: raise exceptions.NoUniqueMatch(_('The datastore name or id is' ' required to retrieve a' ' datastore version by name.')) if datastore_version._info.get('links'): del (datastore_version._info['links']) return zip(*sorted(datastore_version._info.items())) class DeleteDatastoreVersion(command.Command): _description = _("Deletes a datastore version.") def get_parser(self, prog_name): parser = super(DeleteDatastoreVersion, self).get_parser(prog_name) parser.add_argument( 'datastore_version', metavar='', help=_('ID of the datastore version.'), ) return parser def take_action(self, parsed_args): client = self.app.client_manager.database.mgmt_ds_versions try: client.delete(parsed_args.datastore_version) except Exception as e: msg = (_("Failed to delete datastore version %(version)s: %(e)s") % {'version': parsed_args.datastore_version, 'e': e}) raise exceptions.CommandError(msg) class CreateDatastoreVersion(command.Command): _description = _("Creates a datastore version.") def get_parser(self, prog_name): parser = super(CreateDatastoreVersion, self).get_parser(prog_name) parser.add_argument( 'version_name', help=_('Datastore version name.'), ) parser.add_argument( 'datastore_name', help=_("Datastore name. The datastore is created automatically " "if does not exist."), ) parser.add_argument( 'datastore_manager', help=_('Datastore manager, e.g. mysql'), ) parser.add_argument( 'image_id', help=_('ID of the datastore image in Glance. This can be empty ' 'string if --image-tags is specified.'), ) parser.add_argument( '--registry-ext', help=_('Extension for default datastore managers. ' 'Allows the use of custom managers for each of ' 'the datastores supported by Trove.' 'This can be empty string.'), ) parser.add_argument( '--repl-strategy', help=_('Extension for default strategy for replication. ' 'Allows the use of custom replication strategy ' 'for each of the datastores supported by Trove.' 'This can be empty string.'), ) parser.add_argument( '--active', action='store_true', help=_('Enable the datastore version.'), ) parser.add_argument( '--image-tags', help=_('List of image tags separated by comma, e.g. trove,mysql'), ) parser.add_argument( '--default', action='store_true', help=_('If set the datastore version as default.'), ) parser.add_argument( '--version-number', help=_("The version number for the database. If not specified, " "use the version name as the default value."), ) return parser def take_action(self, parsed_args): client = self.app.client_manager.database.mgmt_ds_versions image_tags = [] if parsed_args.image_tags: image_tags = parsed_args.image_tags.split(',') try: client.create( parsed_args.version_name, parsed_args.datastore_name, parsed_args.datastore_manager, parsed_args.image_id, image_tags=image_tags, registry_ext=parsed_args.registry_ext, repl_strategy=parsed_args.repl_strategy, active='true' if parsed_args.active else 'false', default='true' if parsed_args.default else 'false', version=parsed_args.version_number ) except Exception as e: msg = (_("Failed to create datastore version %(version)s: %(e)s") % {'version': parsed_args.version_name, 'e': e}) raise exceptions.CommandError(msg) class UpdateDatastoreVersion(command.Command): _description = _("Updates a datastore version.") def get_parser(self, prog_name): parser = super(UpdateDatastoreVersion, self).get_parser(prog_name) parser.add_argument( 'datastore_version_id', help=_('Datastore version ID.'), ) parser.add_argument( '--datastore-manager', default=None, help=_("Datastore manager name."), ) parser.add_argument( '--image', default=None, help=_('ID of the datastore image in Glance.'), ) parser.add_argument( '--image-tags', default=None, help=_('List of image tags separated by comma, e.g. trove,mysql'), ) parser.add_argument( '--registry-ext', help=_('Extension for default datastore managers. ' 'Allows the use of custom managers for each of ' 'the datastores supported by Trove.' 'This can be empty string.'), ) parser.add_argument( '--repl-strategy', help=_('Extension for default strategy for replication. ' 'Allows the use of custom replication strategy ' 'for each of the datastores supported by Trove.' 'This can be empty string.'), ) parser.add_argument( '--version-name', help=_('New datastore version name.'), ) enable_group = parser.add_mutually_exclusive_group() enable_group.add_argument('--enable', dest='enable', default=None, action='store_const', const='true') enable_group.add_argument('--disable', dest='enable', default=None, action='store_const', const='false') default_group = parser.add_mutually_exclusive_group() default_group.add_argument('--default', dest='default', default=None, action='store_const', const='true') default_group.add_argument('--non-default', dest='default', default=None, action='store_const', const='false') return parser def take_action(self, parsed_args): client = self.app.client_manager.database.mgmt_ds_versions image_tags = None if parsed_args.image_tags is not None: image_tags = parsed_args.image_tags.split(',') try: client.edit( parsed_args.datastore_version_id, datastore_manager=parsed_args.datastore_manager, image=parsed_args.image, image_tags=image_tags, registry_ext=parsed_args.registry_ext, repl_strategy=parsed_args.repl_strategy, active=parsed_args.enable, default=parsed_args.default, name=parsed_args.version_name ) except Exception as e: msg = (_("Failed to update datastore version %(version)s: %(e)s") % {'version': parsed_args.datastore_version_id, 'e': e}) raise exceptions.CommandError(msg) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/service_catalog.py0000664000175000017500000000657700000000000023506 0ustar00zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # 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.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] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/shell.py0000664000175000017500000007453700000000000021464 0ustar00zuulzuul00000000000000# 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. """ import argparse import getpass import glob import importlib import itertools import logging import os import pkgutil import sys from keystoneauth1.identity.generic import password from keystoneauth1.identity.generic import token from keystoneauth1 import loading from oslo_utils import encodeutils from oslo_utils import importutils import stevedore from troveclient.apiclient import exceptions as exc import troveclient.auth_plugin from troveclient import client import troveclient.extension from troveclient.i18n import _ # noqa 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' LOG = logging.getLogger(__name__) osprofiler_profiler = importutils.try_import("osprofiler.profiler") class TroveClientArgumentParser(argparse.ArgumentParser): def __init__(self, *args, **kwargs): super(TroveClientArgumentParser, self).__init__(*args, **kwargs) def add_argument(self, *args, **kwargs): if kwargs.get('help') is None: raise Exception(_("An argument '%s' was specified without help.") % args[0]) super(TroveClientArgumentParser, self).add_argument(*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, argv): 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__, help=_("Show program's version number and exit.")) parser.add_argument('--debug', action='store_true', default=utils.env('TROVECLIENT_DEBUG', default=False), help=_("Print debugging output.")) parser.add_argument('--os-auth-system', metavar='', default=utils.env('OS_AUTH_SYSTEM'), help=argparse.SUPPRESS) parser.add_argument('--os_auth_system', 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) default_trove_endpoint_type = utils.env( 'OS_ENDPOINT_TYPE', default=DEFAULT_TROVE_ENDPOINT_TYPE) 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 ' 'env[OS_ENDPOINT_TYPE] or ' '%(DEFAULT_TROVE_ENDPOINT_TYPE)s.') % {'DEFAULT_TROVE_ENDPOINT_TYPE': 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('--retries', metavar='', type=int, default=0, help=_('Number of retries.')) parser.add_argument('--json', '--os-json-output', dest='json', action='store_true', default=utils.env('OS_JSON_OUTPUT', default=False), help=_('Output JSON instead of prettyprint. ' 'Defaults to env[OS_JSON_OUTPUT].')) if osprofiler_profiler: parser.add_argument('--profile', metavar='HMAC_KEY', default=utils.env('OS_PROFILE_HMACKEY', default=None), help=_('HMAC key used to encrypt context ' 'data when profiling the performance ' 'of an operation. This key should be ' 'set to one of the HMAC keys ' 'configured in Trove (they are found ' 'in configuration files, typically in ' '/etc/trove). Without the key, ' 'profiling will not be triggered even ' 'if it is enabled on the server side. ' 'Defaults to env[OS_PROFILE_HMACKEY].' )) self._append_global_identity_args(parser, argv) # The auth-system-plugins might require some extra options troveclient.auth_plugin.load_auth_system_opts(parser) return parser def _append_global_identity_args(self, parser, argv): # Register CLI identity related arguments # Use keystoneauth to register common CLI arguments loading.register_session_argparse_arguments(parser) default_auth_plugin = 'password' if 'os-token' in argv: default_auth_plugin = 'token' loading.register_auth_argparse_arguments(parser, argv, default=default_auth_plugin) parser.set_defaults(insecure=utils.env('TROVECLIENT_INSECURE', default=False)) parser.set_defaults(os_auth_url=utils.env('OS_AUTH_URL')) parser.set_defaults(os_project_name=utils.env( 'OS_PROJECT_NAME', 'OS_TENANT_NAME')) parser.set_defaults(os_project_id=utils.env( 'OS_PROJECT_ID', 'OS_TENANT_ID')) parser.add_argument('--os_tenant_name', help=argparse.SUPPRESS) parser.add_argument('--os_tenant_id', help=argparse.SUPPRESS) parser.add_argument('--os-auth-token', default=utils.env('OS_AUTH_TOKEN'), help=argparse.SUPPRESS) parser.add_argument('--os-region-name', metavar='', default=utils.env('OS_REGION_NAME'), help=_('Specify the region to use. ' 'Defaults to env[OS_REGION_NAME].')) parser.add_argument('--os_region_name', help=argparse.SUPPRESS) def get_subcommand_parser(self, version, argv): parser = self.get_base_parser(argv) 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(), self._discover_via_contrib_path(version), self._discover_via_entry_points()): extension = troveclient.extension.Extension(name, module) extensions.append(extension) return extensions def _discover_via_python_path(self): 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) if hasattr(module, 'extension_name'): name = module.extension_name yield name, module def _load_module(self, name, path): module_spec = importlib.spec_from_file_location( name, path ) module = importlib.module_from_spec(module_spec) module_spec.loader.exec_module(module) return module def _discover_via_contrib_path(self, version): module_path = os.path.dirname(os.path.abspath(__file__)) version_str = "v%s" % version.replace('.', '_') version_pkg = 'v1' if version_str == 'v1_0' else version_str ext_path = os.path.join(module_path, version_pkg, '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 = self._load_module(name, ext_path) yield name, module def _discover_via_entry_points(self): mgr = stevedore.ExtensionManager(namespace='troveclient.extension', invoke_on_load=True) for ext in mgr: yield ext.name, ext.plugin def _add_bash_completion_subparser(self, subparsers): subparser = subparsers.add_parser( 'bash_completion', add_help=False, formatter_class=OpenStackHelpFormatter) self.subcommands['bash_completion'] = subparser subparser.set_defaults(func=self.do_bash_completion) def _find_actions(self, subparsers, actions_module): for attr in (a for a in dir(actions_module) if a.startswith('do_')): # I prefer to be hyphen-separated instead of underscores. command = attr[3:].replace('_', '-') callback = getattr(actions_module, attr) desc = callback.__doc__ or '' help = desc.strip().split('\n')[0] arguments = getattr(callback, 'arguments', []) subparser = subparsers.add_parser( command, help=help, description=desc, add_help=False, formatter_class=OpenStackHelpFormatter) subparser.add_argument('-h', '--help', action='help', help=argparse.SUPPRESS,) self.subcommands[command] = subparser for (args, kwargs) in arguments: subparser.add_argument(*args, **kwargs) subparser.set_defaults(func=callback) def setup_debugging(self, debug): if not debug: return streamformat = "%(levelname)s (%(module)s:%(lineno)d) %(message)s" logging.basicConfig(level=logging.DEBUG, format=streamformat) def main(self, argv): # Parse args once to find version and debug settings parser = self.get_base_parser(argv) (options, args) = parser.parse_known_args(argv) self.setup_debugging(options.debug) self.options = options # Discover available auth plugins troveclient.auth_plugin.discover_auth_systems() # 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, argv) 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 = args.os_username os_password = args.os_password os_project_name = getattr(args, 'os_project_name', getattr(args, 'os_tenant_name', None)) os_auth_url = args.os_auth_url os_region_name = args.os_region_name os_project_id = getattr( args, 'os_project_id', getattr(args, 'os_tenant_id', None)) os_auth_system = args.os_auth_system if "v2.0" not in os_auth_url: if (not args.os_project_domain_id and not args.os_project_domain_name): setattr(args, "os_project_domain_id", "default") if not args.os_user_domain_id and not args.os_user_domain_name: setattr(args, "os_user_domain_id", "default") endpoint_type = args.endpoint_type insecure = args.insecure service_type = args.service_type service_name = args.service_name database_service_name = args.database_service_name cacert = args.os_cacert bypass_url = args.bypass_url if os_auth_system and os_auth_system != "keystone": auth_plugin = troveclient.auth_plugin.load_plugin(os_auth_system) else: auth_plugin = None 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 auth_plugin: auth_plugin.parse_opts(args) if not auth_plugin or not auth_plugin.opts: if not os_username: raise exc.CommandError(_( "You must provide a username " "via either --os-username or env[OS_USERNAME]")) if not os_password: os_password = getpass.getpass() if not os_auth_url: if os_auth_system and os_auth_system != 'keystone': os_auth_url = auth_plugin.get_auth_url() # V3 stuff project_info_provided = (self.options.os_project_name or self.options.os_project_id) if (not project_info_provided): raise exc.CommandError( _("You must provide a " "project_id or project_name (with " "project_domain_name or project_domain_id) via " " --os-project-id (env[OS_PROJECT_ID])" " --os-project-name (env[OS_PROJECT_NAME])," " --os-project-domain-id " "(env[OS_PROJECT_DOMAIN_ID])" " --os-project-domain-name " "(env[OS_PROJECT_DOMAIN_NAME])")) if not os_auth_url: raise exc.CommandError(_("You must provide an auth url " "via either --os-auth-url or " "env[OS_AUTH_URL] or specify an " "auth_system which defines a default " "url with --os-auth-system or " "env[OS_AUTH_SYSTEM]")) use_session = True if auth_plugin or bypass_url: use_session = False ks_session = None keystone_auth = None if use_session: project_id = args.os_project_id or args.os_tenant_id project_name = args.os_project_name or args.os_tenant_name ks_session = loading.load_session_from_argparse_arguments(args) keystone_auth = self._get_keystone_auth( ks_session, args.os_auth_url, username=args.os_username, user_id=args.os_user_id, user_domain_id=args.os_user_domain_id, user_domain_name=args.os_user_domain_name, password=args.os_password, auth_token=args.os_auth_token, project_id=project_id, project_name=project_name, project_domain_id=args.os_project_domain_id, project_domain_name=args.os_project_domain_name) profile = osprofiler_profiler and options.profile if profile: osprofiler_profiler.init(options.profile) self.cs = client.Client(options.os_database_api_version, os_username, os_password, os_project_name, os_auth_url, insecure, region_name=os_region_name, tenant_id=os_project_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, auth_system=os_auth_system, auth_plugin=auth_plugin, session=ks_session, auth=keystone_auth) try: if not utils.isunauthenticated(args.func): # If Keystone is used, authentication is handled as # part of session. if not use_session: 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 %(db_ver)s " "but you are accessing a %(ep_ver)s endpoint. " "Change its value via either --os-database-api-version " "or env[OS_DATABASE_API_VERSION]") % {'db_ver': options.os_database_api_version, 'ep_ver': endpoint_api_version}) # raise exc.InvalidAPIVersion(msg) raise exc.UnsupportedVersion(msg) # Override printing to json output if args.json: utils.json_output = True else: utils.json_output = False try: args.func(self.cs, args) finally: if profile: trace_id = osprofiler_profiler.get().get_base_id() print(_("Trace ID: %(trace_id)s") % {'trace_id': trace_id}) print(_("To display the trace, use the following command:\n" "osprofiler trace show --html %(trace_id)s") % {'trace_id': trace_id}) 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): """Prints 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): """Displays help about this program or one of its subcommands.""" if args.command: if args.command in self.subcommands: self.subcommands[args.command].print_help() else: raise exc.CommandError(_("'%s' is not a valid subcommand") % args.command) else: self.parser.print_help() def _get_keystone_auth(self, session, auth_url, **kwargs): auth_token = kwargs.pop('auth_token', None) if auth_token: return token.Token(auth_url, auth_token, **kwargs) else: return password.Password( auth_url, username=kwargs.pop('username'), user_id=kwargs.pop('user_id'), password=kwargs.pop('password'), user_domain_id=kwargs.pop('user_domain_id'), user_domain_name=kwargs.pop('user_domain_name'), **kwargs) # I'm picky about my shell help. class OpenStackHelpFormatter(argparse.HelpFormatter): def __init__(self, prog, indent_increment=2, max_help_position=34, width=None): super(OpenStackHelpFormatter, self).__init__(prog, indent_increment, max_help_position, width) def start_section(self, heading): # Title-case the headings heading = '%s%s' % (heading[0].upper(), heading[1:]) super(OpenStackHelpFormatter, self).start_section(heading) def _format_usage(self, usage, actions, groups, prefix): """Formats the argument list to correct argument positions. Print positionals before optionals in the usage string to help users avoid argparse nargs='*' problems. ex: 'trove create --databases ' fails with 'error: too few arguments', but this succeeds: 'trove create --databases ' """ if prefix is None: prefix = _('usage: ') # if usage is specified, use that if usage is not None: usage = usage % dict(prog=self._prog) # if no optionals or positionals are available, usage is just prog elif usage is None and not actions: usage = '%(prog)s' % dict(prog=self._prog) # if optionals and positionals are available, calculate usage elif usage is None: prog = '%(prog)s' % dict(prog=self._prog) # split optionals from positionals optionals = [] positionals = [] for action in actions: if action.option_strings: optionals.append(action) else: positionals.append(action) # build full usage string format = self._format_actions_usage action_usage = format(optionals + positionals, groups) usage = ' '.join([s for s in [prog, action_usage] if s]) # wrap the usage parts if it's too long text_width = self._width - self._current_indent if len(prefix) + len(usage) > text_width: # break usage into wrappable parts part_regexp = r'\(.*?\)+|\[.*?\]+|\S+' opt_usage = format(optionals, groups) pos_usage = format(positionals, groups) opt_parts = argparse._re.findall(part_regexp, opt_usage) pos_parts = argparse._re.findall(part_regexp, pos_usage) assert ' '.join(opt_parts) == opt_usage assert ' '.join(pos_parts) == pos_usage # helper for wrapping lines def get_lines(parts, indent, prefix=None): lines = [] line = [] if prefix is not None: line_len = len(prefix) - 1 else: line_len = len(indent) - 1 for part in parts: if line_len + 1 + len(part) > text_width: lines.append(indent + ' '.join(line)) line = [] line_len = len(indent) - 1 line.append(part) line_len += len(part) + 1 if line: lines.append(indent + ' '.join(line)) if prefix is not None: lines[0] = lines[0][len(indent):] return lines # if prog is short, follow it with optionals or positionals if len(prefix) + len(prog) <= 0.75 * text_width: indent = ' ' * (len(prefix) + len(prog) + 1) if pos_parts: if prog == 'trove': # "trove help" called without any subcommand lines = get_lines([prog] + opt_parts, indent, prefix) lines.extend(get_lines(pos_parts, indent)) else: lines = get_lines([prog] + pos_parts, indent, prefix) lines.extend(get_lines(opt_parts, indent)) elif opt_parts: lines = get_lines([prog] + opt_parts, indent, prefix) else: lines = [prog] # if prog is long, put it on its own line else: indent = ' ' * len(prefix) parts = pos_parts + opt_parts lines = get_lines(parts, indent) if len(lines) > 1: lines = [] lines.extend(get_lines(pos_parts, indent)) lines.extend(get_lines(opt_parts, indent)) lines = [prog] + lines # join lines into usage usage = '\n'.join(lines) # prefix with 'usage:' return '%s%s\n\n' % (prefix, usage) def main(): try: OpenStackTroveShell().main(sys.argv[1:]) except KeyboardInterrupt: print(_("... terminating trove client"), file=sys.stderr) sys.exit(130) except Exception as e: LOG.debug(e, exc_info=1) message = str(e) if not isinstance(message, str): message = str(message) print(_("ERROR: %s") % encodeutils.safe_encode(message), file=sys.stderr) sys.exit(1) if __name__ == "__main__": main() ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1726234752.4869678 python-troveclient-8.6.0/troveclient/tests/0000775000175000017500000000000000000000000021125 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/__init__.py0000664000175000017500000000000000000000000023224 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/fakes.py0000664000175000017500000007515300000000000022603 0ustar00zuulzuul00000000000000# Copyright [2015] 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. from troveclient import client as base_client from troveclient.tests import utils from troveclient.v1 import client from urllib import parse def get_version_map(): return { '1.0': 'troveclient.tests.fakes.FakeClient', } def assert_has_keys(dict, required=None, optional=None): required = required or [] optional = optional or [] keys = dict.keys() for k in required: try: assert k in keys except AssertionError: raise AssertionError("key: %s not found." % k) class FakeClient(client.Client): URL_QUERY_SEPARATOR = '&' URL_SEPARATOR = '?' def __init__(self, *args, **kwargs): client.Client.__init__(self, 'username', 'password', 'project_id', 'auth_url', extensions=kwargs.get('extensions')) self.client = FakeHTTPClient(**kwargs) def _order_url_query_str(self, url): """Returns the url with the query strings ordered, if they exist and there's more than one. Otherwise the url is returned unaltered. """ if self.URL_QUERY_SEPARATOR in url: parts = url.split(self.URL_SEPARATOR) if len(parts) == 2: queries = sorted(parts[1].split(self.URL_QUERY_SEPARATOR)) url = self.URL_SEPARATOR.join( [parts[0], self.URL_QUERY_SEPARATOR.join(queries)]) return url def assert_called(self, method, url, body=None, pos=-1): """Assert than an API method was just called.""" expected = (method, utils.order_url(url)) called = (self.client.callstack[pos][0], utils.order_url(self.client.callstack[pos][1])) assert self.client.callstack, \ "Expected %s %s but no calls were made." % expected assert expected == called, \ 'Expected %s %s; got %s %s' % (expected + called) if body is not None: if self.client.callstack[pos][2] != body: raise AssertionError('%r != %r' % (self.client.callstack[pos][2], body)) def assert_called_anytime(self, method, url, body=None): """Assert than an API method was called anytime in the test.""" expected = (method, utils.order_url(url)) assert self.client.callstack, \ "Expected %s %s but no calls were made." % expected found = False for entry in self.client.callstack: if expected == (entry[0], utils.order_url(entry[1])): found = True break assert found, 'Expected %s; got %s' % (expected, self.client.callstack) if body is not None: try: assert entry[2] == body except AssertionError: print(entry[2]) print("!=") print(body) raise self.client.callstack = [] class FakeHTTPClient(base_client.HTTPClient): def __init__(self, **kwargs): self.username = 'username' self.password = 'password' self.auth_url = 'auth_url' self.management_url = ( 'http://trove-api:8779/v1.0/14630bc0e9ef4e248c9753eaf57b0f6e') self.tenant_id = 'tenant_id' self.callstack = [] self.projectid = 'projectid' self.user = 'user' self.region_name = 'region_name' self.endpoint_type = 'endpoint_type' self.service_type = 'service_type' self.service_name = 'service_name' self.volume_service_name = 'volume_service_name' self.timings = 'timings' self.bypass_url = 'bypass_url' self.os_cache = 'os_cache' self.http_log_debug = 'http_log_debug' def _cs_request(self, url, method, **kwargs): # Check that certain things are called correctly if method in ['GET', 'DELETE']: assert 'body' not in kwargs elif method == 'PUT': assert 'body' in kwargs if url is not None: # Call the method args = parse.parse_qsl(parse.urlparse(url)[4]) kwargs.update(args) munged_url = url.rsplit('?', 1)[0] munged_url = munged_url.strip('/').replace('/', '_') munged_url = munged_url.replace('.', '_') munged_url = munged_url.replace('-', '_') munged_url = munged_url.replace(' ', '_') callback = "%s_%s" % (method.lower(), munged_url) if not hasattr(self, callback): raise AssertionError('Called unknown API method: %s %s, ' 'expected fakes method name: %s' % (method, url, callback)) # Note the call self.callstack.append((method, url, kwargs.get('body'))) status, headers, body = getattr(self, callback)(**kwargs) r = utils.TestResponse({ "status_code": status, "text": body, "headers": headers, }) return r, body def get_instances(self, **kw): return ( 200, {}, { "instances": [ { "id": "1234", "name": "test-member-1", "status": "ACTIVE", "addresses": [ {"type": "private", "address": "10.0.0.13"} ], "volume": {"size": 2}, "flavor": {"id": "02"}, "region": "regionOne", "datastore": { "version": "5.6", "type": "mysql", "version_number": "5.7.29" }, "tenant_id": "fake_tenant_id", "replica_of": {"id": "fake_master_id"}, "access": {"is_public": False, "allowed_cidrs": []} }, { "id": "5678", "name": "test-member-2", "status": "ACTIVE", "addresses": [ {"type": "private", "address": "10.0.0.14"} ], "volume": {"size": 2}, "flavor": {"id": "2"}, "region": "regionOne", "datastore": { "version": "5.6", "type": "mysql", "version_number": "5.7.29" }, "tenant_id": "fake_tenant_id", "access": {"is_public": False, "allowed_cidrs": []} } ] } ) def get_instance_counts(self, **kw): return (200, {}, {"instances": [ { "module_id": "4321", "module_name": "mod1", "min_date": "2015-05-02T11:06:16", "max_date": "2015-05-02T11:06:19", "module_md5": "9db783b92a9355f70c41806659fcb77d", "current": True, "count": 1}]}) def get_instances_1234(self, **kw): r = {'instance': self.get_instances()[2]['instances'][0]} return (200, {}, r) def get_instance_create(self, **kw): return (200, {}, {"instance": { "status": "BUILD", "updated": "2017-12-22T20:02:32", "name": "test", "created": "2017-12-22T20:02:32", "networks": { "name": "test-net", "id": "net-id" }, "id": "2468", "volume": { "size": 1 }, "flavor": { "id": "310" }, "datastore": { "version": "5.6", "type": "mysql", "version_number": "5.7.29" }}}) def post_instances(self, body, **kw): assert_has_keys( body['instance'], required=['name', 'flavorRef'], optional=['volume']) if 'volume' in body['instance']: assert_has_keys(body['instance']['volume'], required=['size']) return (202, {}, self.get_instances_1234()[2]) def get_flavors(self, **kw): return (200, {}, {"flavors": [ { "str_id": "1", "ram": 512, "id": 1, "name": "m1.tiny"}, { "str_id": "10", "ram": 768, "id": 10, "name": "eph.rd-smaller"}, { "str_id": "2", "ram": 2048, "id": 2, "name": "m1.small"}, { "str_id": "3", "ram": 4096, "id": 3, "name": "m1.medium"}, { "str_id": "7d0d16e5-875f-4198-b6da-90ab2d3e899e", "ram": 8192, "id": None, "name": "m1.uuid"}, { "str_id": "02", "ram": 1024, "id": None, "name": "m1.leading-zero"}]}) def get_datastores_mysql_versions_some_version_id_flavors(self, **kw): return self.get_flavors() def get_flavors_1(self, **kw): r = {'flavor': self.get_flavors()[2]['flavors'][0]} return (200, {}, r) def get_flavors_2(self, **kw): r = {'flavor': self.get_flavors()[2]['flavors'][2]} return (200, {}, r) def get_flavors_m1_tiny(self, **kw): r = {'flavor': self.get_flavors()[2]['flavors'][0]} return (200, {}, r) def get_flavors_eph_rd_smaller(self, **kw): r = {'flavor': self.get_flavors()[2]['flavors'][1]} return (200, {}, r) def get_flavors_m1_small(self, **kw): r = {'flavor': self.get_flavors()[2]['flavors'][2]} return (200, {}, r) def get_flavors_m1_medium(self, **kw): r = {'flavor': self.get_flavors()[2]['flavors'][3]} return (200, {}, r) def get_flavors_m1_uuid(self, **kw): r = {'flavor': self.get_flavors()[2]['flavors'][4]} return (200, {}, r) def get_flavors_02(self, **kw): r = {'flavor': self.get_flavors()[2]['flavors'][5]} return (200, {}, r) def get_flavors_m1_leading_zero(self, **kw): r = {'flavor': self.get_flavors()[2]['flavors'][5]} return (200, {}, r) def get_volume_types(self, **kw): return (200, {}, {"volume_types": [ { "id": "1", "name": "vt_1", "description": "Volume type #1", "is_public": False}, { "id": "10", "name": "volume_type_2", "description": "Volume type #2", "is_public": True}]}) def get_datastores_mysql_versions_some_version_id_volume_types(self, **kw): return self.get_volume_types() def get_volume_types_1(self, **kw): r = {'volume_type': self.get_volume_types()[2]['volume_types'][0]} return (200, {}, r) def get_volume_types_2(self, **kw): r = {'volume_type': self.get_volume_types()[2]['volume_types'][2]} return (200, {}, r) def get_clusters(self, **kw): return (200, {}, {"clusters": [ { "instances": [ { "type": "member", "id": "member-1", "ip": ["10.0.0.3"], "flavor": {"id": "02"}, "name": "test-clstr-member-1" }, { "type": "member", "id": "member-2", "ip": ["10.0.0.4"], "flavor": {"id": "2"}, "name": "test-clstr-member-2" }], "updated": "2015-05-02T11:06:19", "task": {"description": "No tasks for the cluster.", "id": 1, "name": "NONE"}, "name": "test-clstr", "created": "2015-05-02T10:37:04", "datastore": {"version": "7.1", "type": "vertica"}, "id": "cls-1234"}]}) def get_clusters_cls_1234(self, **kw): # NOTE(zhaochao): getting a cluster will load instances # informations while getting the list of clusters won't, # so we cannot just reuse the reponse of getting clusters # for getting a cluster. # The following response body is almost identical to the # result of get_clusters, except the additional 'status' # and 'volume' items, so all existing unitests accessing # this piece of fake response will continue to work # without any modification. return (200, {}, {'cluster': { "instances": [ { "type": "member", "id": "member-1", "ip": ["10.0.0.3"], "flavor": {"id": "02"}, "name": "test-clstr-member-1", "status": "ACTIVE", "volume": {"size": 2} }, { "type": "member", "id": "member-2", "ip": ["10.0.0.4"], "flavor": {"id": "2"}, "name": "test-clstr-member-2", "status": "ACTIVE", "volume": {"size": 2} }], "updated": "2015-05-02T11:06:19", "task": {"description": "No tasks for the cluster.", "id": 1, "name": "NONE"}, "name": "test-clstr", "created": "2015-05-02T10:37:04", "datastore": {"version": "7.1", "type": "vertica"}, "id": "cls-1234"}}) def get_cluster_instance_modules(self, **kw): return (200, {}, { "modules": [ { "auto_apply": False, "contents": None, "created": "2018-04-17 05:34:02.84", "datastore": "mariadb", "datastore_version": "all", "id": "module-1", "is_admin": False, "md5": "md5-1", "message": "Module.V1", "name": "mymod1", "removed": None, "status": "OK", "tenant": "7f1f041fc291455b83a0b3eb98140808", "type": "ping", "updated": "2018-04-17 05:34:02.84", "visible": True}, { "auto_apply": False, "contents": None, "created": "2018-04-17 05:34:02.84", "datastore": "mariadb", "datastore_version": "all", "id": "module-2", "is_admin": False, "md5": "md5-2", "message": "Module.V1", "name": "mymod2", "removed": None, "status": "OK", "tenant": "7f1f041fc291455b83a0b3eb98140808", "type": "ping", "updated": "2018-04-17 05:34:02.84", "visible": True}]}) def delete_instances_1234(self, **kw): return (202, {}, None) def delete_clusters_cls_1234(self, **kw): return (202, {}, None) def patch_instances_1234(self, **kw): return (202, {}, None) def post_clusters(self, body, **kw): assert_has_keys( body['cluster'], required=['instances', 'datastore', 'name']) if 'instances' in body['cluster']: for instance in body['cluster']['instances']: assert_has_keys(instance, required=['volume', 'flavorRef']) return (202, {}, self.get_clusters_cls_1234()[2]) def post_clusters_cls_1234(self, body, **kw): return (202, {}, None) def post_instances_1234_action(self, **kw): return (202, {}, None) def get_datastores(self, **kw): return (200, {}, {"datastores": [ { "default_version": "v-56", "versions": [{"id": "v-56", "name": "5.6"}], "id": "d-123", "name": "mysql"}, { "default_version": "v-71", "versions": [{"id": "v-71", "name": "7.1"}], "id": "d-456", "name": "vertica" }]}) def get_datastores_d_123(self, **kw): r = {'datastore': self.get_datastores()[2]['datastores'][0]} return (200, {}, r) def get_datastores_d_123_versions(self, **kw): return (200, {}, {"versions": [ { "datastore": "d-123", "id": "v-56", "name": "5.6"}]}) def get_datastores_d_123_versions_v_56(self, **kw): r = {'version': self.get_datastores_d_123_versions()[2]['versions'][0]} return (200, {}, r) def get_configurations(self, **kw): return (200, {}, {"configurations": [ { "datastore_name": "mysql", "updated": "2015-05-16T10:24:29", "name": "test_config", "created": "2015-05-16T10:24:28", "datastore_version_name": "5.6", "datastore_version_number": "5.7.29", "id": "c-123", "values": {"max_connections": 5}, "datastore_version_id": "d-123", "description": ''}]}) def get_configurations_c_123(self, **kw): r = {'configuration': self.get_configurations()[2]['configurations'][0] } return (200, {}, r) def get_datastores_d_123_versions_v_156_parameters(self, **kw): return (200, {}, {"configuration-parameters": [ { "type": "string", "name": "character_set_results", "datastore_version_id": "d-123", "restart_required": "false"}, { "name": "connect_timeout", "min": 2, "max": 31536000, "restart_required": "false", "type": "integer", "datastore_version_id": "d-123"}, { "type": "string", "name": "character_set_client", "datastore_version_id": "d-123", "restart_required": "false"}, { "name": "max_connections", "min": 1, "max": 100000, "restart_required": "false", "type": "integer", "datastore_version_id": "d-123"}]}) def get_datastores_d_123_versions_v_56_parameters_max_connections(self, **kw): r = self.get_datastores_d_123_versions_v_156_parameters()[ 2]['configuration-parameters'][3] return (200, {}, r) def get_configurations_c_123_instances(self, **kw): return (200, {}, {"instances": [ { "id": "1", "name": "instance-1"}, { "id": "2", "name": "instance-2"}]}) def delete_configurations_c_123(self, **kw): return (202, {}, None) def get_instances_1234_configuration(self, **kw): return (200, {}, {"instance": {"configuration": { "tmp_table_size": "15M", "innodb_log_files_in_group": "2", "skip-external-locking": "1", "max_user_connections": "98"}}}) def put_instances_1234(self, **kw): return (202, {}, None) def patch_instances_1234_metadata_key_123(self, **kw): return (202, {}, None) def put_instances_1234_metadata_key_123(self, **kw): return (202, {}, None) def delete_instances_1234_metadata_key_123(self, **kw): return (202, {}, None) def post_instances_1234_metadata_key123(self, body, **kw): return (202, {}, {'metadata': {}}) def get_instances_1234_metadata(self, **kw): return (200, {}, {"metadata": {}}) def get_instances_1234_metadata_key123(self, **kw): return (200, {}, {"metadata": {}}) def get_modules(self, **kw): return (200, {}, {"modules": [ { "id": "4321", "name": "mod1", "type": "ping", "datastore": 'all', "datastore_version": 'all', "tenant": 'all', "auto_apply": 0, "visible": 1, "priority_apply": 0, "apply_order": 5, "is_admin": 0}, { "id": "8765", "name": "mod2", "type": "ping", "datastore": 'all', "datastore_version": 'all', "tenant": 'all', "auto_apply": 0, "visible": 0, "priority_apply": 0, "apply_order": 5, "is_admin": 1}]}) def get_modules_4321(self, **kw): r = {'module': self.get_modules()[2]['modules'][0]} return (200, {}, r) def get_modules_8765(self, **kw): r = {'module': self.get_modules()[2]['modules'][1]} return (200, {}, r) def post_modules(self, **kw): r = {'module': self.get_modules()[2]['modules'][0]} return (200, {}, r) def put_modules_4321(self, **kw): return (200, {}, {"module": {'name': 'mod3'}}) def delete_modules_4321(self, **kw): return (200, {}, None) def get_instances_1234_modules(self, **kw): return (200, {}, {"modules": [{"module": {}}]}) def get_modules_4321_instances(self, **kw): if kw.get('count_only', False): return self.get_instance_counts() return self.get_instances() def put_modules_4321_instances(self, **kw): return (202, {}, None) def get_instances_modules(self, **kw): return (200, {}, None) def get_instances_member_1_modules(self, **kw): return self.get_modules() def get_instances_member_2_modules(self, **kw): return self.get_modules() def post_instances_1234_modules(self, **kw): r = {'modules': [self.get_modules()[2]['modules'][0]]} return (200, {}, r) def delete_instances_1234_modules_4321(self, **kw): return (200, {}, None) def get_limits(self, **kw): return (200, {}, {"limits": [ { "max_backups": 50, "verb": "ABSOLUTE", "max_volumes": 20, "max_instances": 5}]}) def get_backups(self, **kw): return (200, {}, {"backups": [ { "status": "COMPLETED", "updated": "2015-05-16T14:23:08", "description": None, "datastore": {"version": "5.6", "type": "mysql", "version_id": "v-56"}, "id": "bk-1234", "size": 0.11, "name": "bkp_1", "created": "2015-05-16T14:22:28", "instance_id": "1234", "parent_id": None, "locationRef": ("http://backup_srvr/database_backups/" "bk-1234.xbstream.gz.enc"), "project_id": "262db161-d3e4-4218-8bde-5bd879fc3e61" }, { "status": "COMPLETED", "updated": "2015-05-16T14:22:12", "description": None, "datastore": {"version": "5.6", "type": "mysql", "version_id": "v-56"}, "id": "bk-5678", "size": 0.11, "name": "test_bkp", "created": "2015-05-16T14:21:27", "instance_id": "5678", "parent_id": None, "locationRef": ("http://backup_srvr/database_backups/" "bk-5678.xbstream.gz.enc"), "project_id": "262db161-d3e4-4218-8bde-5bd879fc3e61" } ]}) def get_backups_bk_1234(self, **kw): r = {'backup': self.get_backups()[2]['backups'][0]} return (200, {}, r) def get_backups_bkp_1(self, **kw): r = {'backup': self.get_backups()[2]['backups'][0]} return (200, {}, r) def get_instances_1234_backups(self, **kw): r = {'backups': [self.get_backups()[2]['backups'][0]]} return (200, {}, r) def delete_backups_bk_1234(self, **kw): return (202, {}, None) def post_backups(self, body, **kw): assert_has_keys( body['backup'], required=['name'], optional=['description', 'parent']) return (202, {}, self.get_backups_bk_1234()[2]) def get_instances_1234_databases(self, **kw): return (200, {}, {"databases": [ {"name": "db_1"}, {"name": "db_2"}, {"name": "performance_schema"}]}) def delete_instances_1234_databases_db_1(self, **kw): return (202, {}, None) def post_instances_1234_databases(self, body, **kw): assert_has_keys( body, required=['databases']) for database in body['databases']: assert_has_keys(database, required=['name'], optional=['character_set', 'collate']) return (202, {}, self.get_instances_1234_databases()[2]['databases'][0]) def get_instances_1234_users(self, **kw): return (200, {}, {"users": [ {"host": "%", "name": "jacob", "databases": []}, {"host": "%", "name": "rocky", "databases": []}, {"host": "%", "name": "harry", "databases": [{"name": "db1"}]}]}) def get_instances_1234_users_jacob(self, **kw): r = {'user': self.get_instances_1234_users()[2]['users'][0]} return (200, {}, r) def delete_instances_1234_users_jacob(self, **kw): return (202, {}, None) def post_instances_1234_users(self, body, **kw): assert_has_keys( body, required=['users']) for database in body['users']: assert_has_keys(database, required=['name', 'password'], optional=['databases']) return (202, {}, self.get_instances_1234_users()[2]['users'][0]) def get_instances_1234_users_jacob_databases(self, **kw): r = {'databases': [ self.get_instances_1234_databases()[2]['databases'][0], self.get_instances_1234_databases()[2]['databases'][1]]} return (200, {}, r) def put_instances_1234_users_jacob(self, **kw): return (202, {}, None) def put_instances_1234_users_jacob_databases(self, **kw): return (202, {}, None) def delete_instances_1234_users_jacob_databases_db1(self, **kw): return (202, {}, None) def post_instances_1234_root(self, **kw): return (202, {}, {"user": {"password": "password", "name": "root"}}) def post_clusters_cls_1234_root(self, **kw): return (202, {}, {"user": {"password": "password", "name": "root"}}) def delete_instances_1234_root(self, **kw): return (202, {}, None) def get_instances_1234_root(self, **kw): return (200, {}, {"rootEnabled": 'True'}) def get_instances_master_1(self, **kw): return (200, {}, {"instance": {"id": 'myid'}}) def get_clusters_cls_1234_root(self, **kw): return (200, {}, {"rootEnabled": 'True'}) def get_security_groups(self, **kw): return (200, {}, {"security_groups": [ { "instance_id": "1234", "updated": "2015-05-16T17:29:45", "name": "SecGroup_1234", "created": "2015-05-16T17:29:45", "rules": [{"to_port": 3306, "cidr": "0.0.0.0/0", "from_port": 3306, "protocol": "tcp", "id": "1"}], "id": "2", "description": "Security Group for 1234"}]}) def get_security_groups_2(self, **kw): r = {'security_group': self.get_security_groups()[ 2]['security_groups'][0]} return (200, {}, r) def delete_security_group_rules_2(self, **kw): return (202, {}, None) def post_security_group_rules(self, body, **kw): assert_has_keys(body['security_group_rule'], required=['cidr', 'cidr']) return (202, {}, {"security_group_rule": [ { "from_port": 3306, "protocol": "tcp", "created": "2015-05-16T17:55:05", "to_port": 3306, "security_group_id": "2", "cidr": "15.0.0.0/24", "id": 3}]}) def get_quotas(self, **kw): return (200, {}, {"quotas": [ { "reserved": 1, "resource": "instances", "limit": 10, "in_use": 2 }, { "reserved": 3, "resource": "backups", "limit": 50, "in_use": 4 }, { "reserved": 5, "resource": "volumes", "limit": 40, "in_use": 6 }]}) def update_instances_quota(self, **kw): return (200, {}, {"quotas": {"instances": 51}}) def get_logs(self, **kw): return (200, {}, {"logs": [ { "name": "general", "type": "USER", "status": "Partial", "published": "128", "pending": "4096", "container": "data_logs", "prefix": "mysql-general", "metafile": "mysql-general_metafile" }, { "name": "slow_query", "type": "USER", "status": "Ready", "published": "0", "pending": "128", "container": "None", "prefix": "None", "metafile": "mysql-slow_query_metafile"}]}) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1726234752.4909678 python-troveclient-8.6.0/troveclient/tests/osc/0000775000175000017500000000000000000000000021711 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/osc/__init__.py0000664000175000017500000000000000000000000024010 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/osc/fakes.py0000664000175000017500000000151400000000000023355 0ustar00zuulzuul00000000000000# 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 FakeStdout(object): def __init__(self): self.content = [] def write(self, text): self.content.append(text) def make_string(self): result = '' for line in self.content: result = result + line return result ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/osc/utils.py0000664000175000017500000000560500000000000023431 0ustar00zuulzuul00000000000000# 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 random import sys from unittest import mock import uuid import fixtures import testtools from troveclient.tests.osc import fakes class TestCase(testtools.TestCase): def setUp(self): testtools.TestCase.setUp(self) if (os.environ.get("OS_STDOUT_CAPTURE") == "True" or os.environ.get("OS_STDOUT_CAPTURE") == "1"): stdout = self.useFixture(fixtures.StringStream("stdout")).stream self.useFixture(fixtures.MonkeyPatch("sys.stdout", stdout)) if (os.environ.get("OS_STDERR_CAPTURE") == "True" or os.environ.get("OS_STDERR_CAPTURE") == "1"): stderr = self.useFixture(fixtures.StringStream("stderr")).stream self.useFixture(fixtures.MonkeyPatch("sys.stderr", stderr)) @classmethod def random_name(cls, name='', prefix=None): """Generate a random name that inclues a random number. :param str name: The name that you want to include :param str prefix: The prefix that you want to include :return: a random name. The format is '--'. (e.g. 'prefixfoo-namebar-154876201') :rtype: string """ randbits = str(random.randint(1, 0x7fffffff)) rand_name = randbits if name: rand_name = name + '-' + rand_name if prefix: rand_name = prefix + '-' + rand_name return rand_name @classmethod def random_uuid(cls): return str(uuid.uuid4()) class TestCommand(TestCase): """Test cliff command classes""" def setUp(self): super(TestCommand, self).setUp() # Build up a fake app self.fake_stdout = fakes.FakeStdout() self.app = mock.MagicMock() self.app.stdout = self.fake_stdout self.app.stdin = sys.stdin self.app.stderr = sys.stderr def check_parser(self, cmd, args, verify_args): cmd_parser = cmd.get_parser('check_parser') try: parsed_args = cmd_parser.parse_args(args) except SystemExit: raise Exception("Argument parse failed") for av in verify_args: attr, value = av if attr: self.assertIn(attr, parsed_args) self.assertEqual(getattr(parsed_args, attr), value) return parsed_args ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1726234752.4909678 python-troveclient-8.6.0/troveclient/tests/osc/v1/0000775000175000017500000000000000000000000022237 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/osc/v1/__init__.py0000664000175000017500000000000000000000000024336 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/osc/v1/fakes.py0000664000175000017500000001562000000000000023706 0ustar00zuulzuul00000000000000# 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 unittest import mock from troveclient.tests import fakes from troveclient.tests.osc import utils from troveclient.v1 import backups from troveclient.v1 import clusters from troveclient.v1 import configurations from troveclient.v1 import databases from troveclient.v1 import datastores from troveclient.v1 import flavors from troveclient.v1 import instances from troveclient.v1 import limits from troveclient.v1 import modules from troveclient.v1 import quota from troveclient.v1 import users class TestDatabasev1(utils.TestCommand): def setUp(self): super(TestDatabasev1, self).setUp() self.app.client_manager.database = mock.MagicMock() class FakeFlavors(object): fake_flavors = fakes.FakeHTTPClient().get_flavors()[2]['flavors'] def get_flavors_1(self): return flavors.Flavor(None, self.fake_flavors[0]) class FakeBackups(object): fake_backups = fakes.FakeHTTPClient().get_backups()[2]['backups'] def get_backup_bk_1234(self): return backups.Backup(None, self.fake_backups[0]) class FakeClusters(object): fake_clusters = fakes.FakeHTTPClient().get_clusters()[2]['clusters'] fake_cluster = (fakes.FakeHTTPClient() .get_clusters_cls_1234()[2]['cluster']) fake_cluster_member = fake_cluster['instances'][1] fake_cluster_instance_modules = (fakes.FakeHTTPClient(). get_cluster_instance_modules()[2] ['modules']) def get_clusters_cls_1234(self): return clusters.Cluster(None, self.fake_cluster) def get_clusters_member_2(self): return instances.Instance(None, self.fake_cluster_member) def cluster_instance_modules(self): return [[modules.Module(None, mod)] for mod in self.fake_cluster_instance_modules] class FakeConfigurations(object): fake_config = (fakes.FakeHTTPClient().get_configurations() [2]['configurations']) fake_config_instances = (fakes.FakeHTTPClient(). get_configurations_c_123_instances()[2]) fake_default_config = ( fakes.FakeHTTPClient().get_instances_1234_configuration() [2]['instance']) def get_configurations_c_123(self): return configurations.Configuration(None, self.fake_config[0]) def get_configuration_instances(self): return [instances.Instance(None, fake_instance) for fake_instance in self.fake_config_instances['instances']] def get_default_configuration(self): return instances.Instance(None, self.fake_default_config) class FakeConfigurationParameters(object): fake_config_param = (fakes.FakeHTTPClient(). get_datastores_d_123_versions_v_156_parameters() [2]['configuration-parameters']) def get_params_connect_timeout(self): return configurations.\ ConfigurationParameter(None, self.fake_config_param[1]) class FakeLimits(object): fake_limits = fakes.FakeHTTPClient().get_limits()[2]['limits'] def get_absolute_limits(self): return limits.Limit(None, self.fake_limits[0]) def get_non_absolute_limits(self): return limits.Limit(None, {'value': 200, 'verb': 'DELETE', 'remaining': 200, 'unit': 'MINUTE'}) class FakeUsers(object): fake_users = fakes.FakeHTTPClient().get_instances_1234_users()[2]['users'] fake_user_access = fakes.FakeHTTPClient().\ get_instances_1234_users_jacob_databases()[2] def get_instances_1234_users_harry(self): return users.User(None, self.fake_users[2]) def get_instances_1234_users_access(self): return [databases.Database(self, db) for db in self.fake_user_access['databases']] class FakeInstances(object): fake_instances = (fakes.FakeHTTPClient().get_instances()[2]['instances']) fake_instance = fakes.FakeHTTPClient().get_instance_create()[2] def get_instances_1234(self): return instances.Instance(None, self.fake_instances[0]) def get_instances(self): return [instances.Instance(None, fake_instance) for fake_instance in self.fake_instances] def get_instance_create(self): return instances.Instance(None, self.fake_instance['instance']) class FakeDatabases(object): fake_databases = [{'name': 'fakedb1'}] def get_databases_1(self): return databases.Database(None, self.fake_databases[0]) class FakeDatastores(object): fake_datastores = fakes.FakeHTTPClient().get_datastores()[2]['datastores'] fake_datastore_versions = fake_datastores[0]['versions'] def get_datastores_d_123(self): return datastores.Datastore(None, self.fake_datastores[0]) def get_datastores_d_123_versions(self): return datastores.Datastore(None, self.fake_datastore_versions[0]) class FakeRoot(object): fake_instance_1234_root = (fakes.FakeHTTPClient() .get_instances_1234_root()[2]) fake_cls_1234_root = (fakes.FakeHTTPClient() .get_clusters_cls_1234_root()[2]) def get_instance_1234_root(self): return users.User(None, self.fake_instance_1234_root, loaded=True) def get_cls_1234_root(self): return users.User(None, self.fake_cls_1234_root, loaded=True) def post_instance_1234_root(self): root = fakes.FakeHTTPClient().post_instances_1234_root()[2]['user'] return root['name'], root['password'] def post_cls_1234_root(self): root = fakes.FakeHTTPClient().post_instances_1234_root()[2]['user'] return root['name'], root['password'] def delete_instance_1234_root(self): return fakes.FakeHTTPClient().delete_instances_1234_root()[2] class FakeQuota(object): fake_quotas = fakes.FakeHTTPClient().get_quotas()[2]['quotas'] fake_instances_quota = (fakes.FakeHTTPClient() .update_instances_quota()[2]['quotas']) def get_quotas(self): return [quota.Quotas.resource_class(None, q) for q in self.fake_quotas] class FakeLogs(object): fake_logs = fakes.FakeHTTPClient().get_logs()[2]['logs'] def get_logs(self): return [instances.DatastoreLog(None, fake_log) for fake_log in self.fake_logs] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/osc/v1/test_database_backup_strategy.py0000664000175000017500000000622400000000000030667 0ustar00zuulzuul00000000000000# Copyright 2020 Catalyst Cloud # # 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.osc.v1 import database_backup_strategy from troveclient.tests.osc.v1 import fakes from troveclient.v1 import backup_strategy class TestBackupStrategy(fakes.TestDatabasev1): def setUp(self): super(TestBackupStrategy, self).setUp() self.manager = self.app.client_manager.database.backup_strategies class TestBackupStrategyList(TestBackupStrategy): def setUp(self): super(TestBackupStrategyList, self).setUp() self.cmd = database_backup_strategy.ListDatabaseBackupStrategies( self.app, None) def test_list(self): item = backup_strategy.BackupStrategy( None, { 'project_id': 'fake_project_id', 'instance_id': 'fake_instance_id', 'swift_container': 'fake_container' } ) self.manager.list.return_value = [item] parsed_args = self.check_parser(self.cmd, [], []) columns, data = self.cmd.take_action(parsed_args) self.manager.list.assert_called_once_with(instance_id=None, project_id=None) self.assertEqual( database_backup_strategy.ListDatabaseBackupStrategies.columns, columns) self.assertEqual( [('fake_project_id', 'fake_instance_id', 'fake_container')], data) class TestBackupStrategyCreate(TestBackupStrategy): def setUp(self): super(TestBackupStrategyCreate, self).setUp() self.cmd = database_backup_strategy.CreateDatabaseBackupStrategy( self.app, None) def test_create(self): args = ['--instance-id', 'fake_instance_id', '--swift-container', 'fake_container'] parsed_args = self.check_parser(self.cmd, args, []) self.cmd.take_action(parsed_args) self.manager.create.assert_called_once_with( instance_id='fake_instance_id', swift_container='fake_container' ) class TestBackupStrategyDelete(TestBackupStrategy): def setUp(self): super(TestBackupStrategyDelete, self).setUp() self.cmd = database_backup_strategy.DeleteDatabaseBackupStrategy( self.app, None) def test_delete(self): args = ['--instance-id', 'fake_instance_id', '--project-id', 'fake_project'] parsed_args = self.check_parser(self.cmd, args, []) self.cmd.take_action(parsed_args) self.manager.delete.assert_called_once_with( project_id='fake_project', instance_id='fake_instance_id', ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/osc/v1/test_database_backups.py0000664000175000017500000003070000000000000027124 0ustar00zuulzuul00000000000000# 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 unittest import mock from osc_lib import exceptions from osc_lib import utils from oslo_utils import uuidutils from troveclient import common from troveclient.osc.v1 import database_backups from troveclient.tests.osc.v1 import fakes class TestBackups(fakes.TestDatabasev1): fake_backups = fakes.FakeBackups() def setUp(self): super(TestBackups, self).setUp() self.mock_client = self.app.client_manager.database self.backup_client = self.app.client_manager.database.backups self.instance_client = self.app.client_manager.database.instances class TestBackupList(TestBackups): columns = database_backups.ListDatabaseBackups.columns values = ('bk-1234', '1234', 'bkp_1', 'COMPLETED', None, '2015-05-16T14:23:08', '262db161-d3e4-4218-8bde-5bd879fc3e61') def setUp(self): super(TestBackupList, self).setUp() self.cmd = database_backups.ListDatabaseBackups(self.app, None) data = [self.fake_backups.get_backup_bk_1234()] self.backup_client.list.return_value = common.Paginated(data) def test_backup_list_defaults(self): parsed_args = self.check_parser(self.cmd, [], []) columns, data = self.cmd.take_action(parsed_args) params = { 'datastore': None, 'limit': None, 'marker': None, 'instance_id': None, 'all_projects': False, 'project_id': None } self.backup_client.list.assert_called_once_with(**params) self.assertEqual(self.columns, columns) self.assertEqual([self.values], data) @mock.patch('troveclient.utils.get_resource_id') def test_backup_list_by_instance_id(self, get_resource_id_mock): get_resource_id_mock.return_value = 'fake_uuid' parsed_args = self.check_parser(self.cmd, ["--instance-id", "fake_id"], []) self.cmd.take_action(parsed_args) params = { 'datastore': None, 'limit': None, 'marker': None, 'instance_id': 'fake_uuid', 'all_projects': False, 'project_id': None } self.backup_client.list.assert_called_once_with(**params) @mock.patch('troveclient.utils.get_resource_id') def test_backup_list_by_instance_name(self, get_resource_id_mock): get_resource_id_mock.return_value = 'fake_uuid' parsed_args = self.check_parser(self.cmd, ["--instance", "fake_name"], []) self.cmd.take_action(parsed_args) params = { 'datastore': None, 'limit': None, 'marker': None, 'instance_id': 'fake_uuid', 'all_projects': False, 'project_id': None } self.backup_client.list.assert_called_once_with(**params) get_resource_id_mock.assert_called_once_with(self.instance_client, 'fake_name') def test_backup_list_all_projects(self): parsed_args = self.check_parser(self.cmd, ["--all-projects"], []) self.cmd.take_action(parsed_args) params = { 'datastore': None, 'limit': None, 'marker': None, 'instance_id': None, 'all_projects': True, 'project_id': None } self.backup_client.list.assert_called_once_with(**params) def test_backup_list_by_project(self): parsed_args = self.check_parser(self.cmd, ["--project-id", "fake_id"], []) self.cmd.take_action(parsed_args) params = { 'datastore': None, 'limit': None, 'marker': None, 'instance_id': None, 'all_projects': False, 'project_id': 'fake_id' } self.backup_client.list.assert_called_once_with(**params) class TestBackupListInstance(TestBackups): defaults = { 'limit': None, 'marker': None } columns = database_backups.ListDatabaseInstanceBackups.columns values = ('bk-1234', '1234', 'bkp_1', 'COMPLETED', None, '2015-05-16T14:23:08') def setUp(self): super(TestBackupListInstance, self).setUp() self.cmd = database_backups.ListDatabaseInstanceBackups(self.app, None) data = [self.fake_backups.get_backup_bk_1234()] self.instance_client.backups.return_value = common.Paginated(data) @mock.patch.object(utils, 'find_resource') def test_backup_list_defaults(self, mock_find): args = ['1234'] mock_find.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, []) columns, data = self.cmd.take_action(parsed_args) self.instance_client.backups.assert_called_once_with('1234', **self.defaults) self.assertEqual(self.columns, columns) self.assertEqual([self.values], data) class TestBackupShow(TestBackups): values = ('2015-05-16T14:22:28', 'mysql', '5.6', 'v-56', None, 'bk-1234', '1234', 'http://backup_srvr/database_backups/bk-1234.xbstream.gz.enc', 'bkp_1', None, '262db161-d3e4-4218-8bde-5bd879fc3e61', 0.11, 'COMPLETED', '2015-05-16T14:23:08') def setUp(self): super(TestBackupShow, self).setUp() self.cmd = database_backups.ShowDatabaseBackup(self.app, None) self.data = self.fake_backups.get_backup_bk_1234() self.backup_client.get.return_value = self.data self.columns = ( 'created', 'datastore', 'datastore_version', 'datastore_version_id', 'description', 'id', 'instance_id', 'locationRef', 'name', 'parent_id', 'project_id', 'size', 'status', 'updated', ) def test_show(self): args = ['bkp_1'] parsed_args = self.check_parser(self.cmd, args, []) columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) self.assertEqual(self.values, data) class TestDatabaseBackupDelete(TestBackups): def setUp(self): super(TestDatabaseBackupDelete, self).setUp() self.cmd = database_backups.DeleteDatabaseBackup(self.app, None) @mock.patch("troveclient.utils.get_resource_id_by_name") def test_backup_delete(self, mock_getid): args = ['backup1'] mock_getid.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, []) self.cmd.take_action(parsed_args) self.backup_client.delete.assert_called_with('backup1') @mock.patch("troveclient.utils.get_resource_id_by_name") def test_backup_delete_with_exception(self, mock_getid): args = ['fakebackup'] parsed_args = self.check_parser(self.cmd, args, []) mock_getid.side_effect = exceptions.CommandError self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) @mock.patch("troveclient.utils.get_resource_id_by_name") def test_backup_bulk_delete(self, mock_getid): backup_1 = uuidutils.generate_uuid() backup_2 = uuidutils.generate_uuid() mock_getid.return_value = backup_1 args = ["fake_backup", backup_2] parsed_args = self.check_parser(self.cmd, args, []) self.cmd.take_action(parsed_args) mock_getid.assert_called_once_with(self.backup_client, "fake_backup") calls = [mock.call(backup_1), mock.call(backup_2)] self.backup_client.delete.assert_has_calls(calls) class TestBackupCreate(TestBackups): values = ('2015-05-16T14:22:28', 'mysql', '5.6', 'v-56', None, 'bk-1234', '1234', 'http://backup_srvr/database_backups/bk-1234.xbstream.gz.enc', 'bkp_1', None, '262db161-d3e4-4218-8bde-5bd879fc3e61', 0.11, 'COMPLETED', '2015-05-16T14:23:08') def setUp(self): super(TestBackupCreate, self).setUp() self.cmd = database_backups.CreateDatabaseBackup(self.app, None) self.data = self.fake_backups.get_backup_bk_1234() self.backup_client.create.return_value = self.data self.columns = ( 'created', 'datastore', 'datastore_version', 'datastore_version_id', 'description', 'id', 'instance_id', 'locationRef', 'name', 'parent_id', 'project_id', 'size', 'status', 'updated', ) def test_backup_create_return_value(self): args = ['bk-1234', '--instance', self.random_uuid()] parsed_args = self.check_parser(self.cmd, args, []) columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) self.assertEqual(self.values, data) @mock.patch('troveclient.utils.get_resource_id_by_name') def test_backup_create(self, mock_find): args = ['bk-1234-1', '--instance', '1234'] mock_find.return_value = 'fake-instance-id' parsed_args = self.check_parser(self.cmd, args, []) self.cmd.take_action(parsed_args) self.backup_client.create.assert_called_with('bk-1234-1', 'fake-instance-id', description=None, parent_id=None, incremental=False, swift_container=None) @mock.patch('troveclient.utils.get_resource_id_by_name') def test_incremental_backup_create(self, mock_find): args = ['bk-1234-2', '--instance', '1234', '--description', 'backup 1234', '--parent', '1234-1', '--incremental'] mock_find.return_value = 'fake-instance-id' parsed_args = self.check_parser(self.cmd, args, []) self.cmd.take_action(parsed_args) self.backup_client.create.assert_called_with('bk-1234-2', 'fake-instance-id', description='backup 1234', parent_id='1234-1', incremental=True, swift_container=None) def test_create_from_data_location(self): name = self.random_name('backup') ds_version = self.random_uuid() args = [name, '--restore-from', 'fake-remote-location', '--restore-datastore-version', ds_version, '--restore-size', '3'] parsed_args = self.check_parser(self.cmd, args, []) self.cmd.take_action(parsed_args) self.backup_client.create.assert_called_with( name, None, restore_from='fake-remote-location', restore_ds_version=ds_version, restore_size=3, ) def test_required_params_missing(self): args = [self.random_name('backup')] parsed_args = self.check_parser(self.cmd, args, []) self.assertRaises( exceptions.CommandError, self.cmd.take_action, parsed_args) class TestDatabaseBackupExecutionDelete(TestBackups): def setUp(self): super(TestDatabaseBackupExecutionDelete, self).setUp() self.cmd = database_backups.DeleteDatabaseBackupExecution( self.app, None) def test_execution_delete(self): args = ['execution'] parsed_args = self.check_parser(self.cmd, args, []) result = self.cmd.take_action(parsed_args) self.backup_client.execution_delete.assert_called_with('execution') self.assertIsNone(result) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/osc/v1/test_database_clusters.py0000664000175000017500000003035000000000000027341 0ustar00zuulzuul00000000000000# 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 unittest import mock from osc_lib import exceptions from osc_lib import utils from troveclient import common from troveclient.osc.v1 import database_clusters from troveclient.tests.osc.v1 import fakes class TestClusters(fakes.TestDatabasev1): fake_clusters = fakes.FakeClusters() def setUp(self): super(TestClusters, self).setUp() self.mock_client = self.app.client_manager.database self.cluster_client = self.app.client_manager.database.clusters self.instance_client = self.app.client_manager.database.instances class TestClusterList(TestClusters): defaults = { 'limit': None, 'marker': None } columns = database_clusters.ListDatabaseClusters.columns values = ('cls-1234', 'test-clstr', 'vertica', '7.1', 'NONE') def setUp(self): super(TestClusterList, self).setUp() self.cmd = database_clusters.ListDatabaseClusters(self.app, None) data = [self.fake_clusters.get_clusters_cls_1234()] self.cluster_client.list.return_value = common.Paginated(data) def test_cluster_list_defaults(self): parsed_args = self.check_parser(self.cmd, [], []) columns, data = self.cmd.take_action(parsed_args) self.cluster_client.list.assert_called_once_with(**self.defaults) self.assertEqual(self.columns, columns) self.assertEqual([self.values], data) class TestClusterShow(TestClusters): values = ('2015-05-02T10:37:04', 'vertica', '7.1', 'cls-1234', 2, 'test-clstr', 'No tasks for the cluster.', 'NONE', '2015-05-02T11:06:19') def setUp(self): super(TestClusterShow, self).setUp() self.cmd = database_clusters.ShowDatabaseCluster(self.app, None) self.data = self.fake_clusters.get_clusters_cls_1234() self.cluster_client.get.return_value = self.data self.columns = ( 'created', 'datastore', 'datastore_version', 'id', 'instance_count', 'name', 'task_description', 'task_name', 'updated', ) def test_show(self): args = ['cls-1234'] parsed_args = self.check_parser(self.cmd, args, []) columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) self.assertEqual(self.values, data) class TestDatabaseClusterDelete(TestClusters): def setUp(self): super(TestDatabaseClusterDelete, self).setUp() self.cmd = database_clusters.DeleteDatabaseCluster(self.app, None) @mock.patch.object(utils, 'find_resource') def test_cluster_delete(self, mock_find): args = ['cluster1'] mock_find.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, []) result = self.cmd.take_action(parsed_args) self.cluster_client.delete.assert_called_with('cluster1') self.assertIsNone(result) @mock.patch.object(utils, 'find_resource') def test_cluster_delete_with_exception(self, mock_find): args = ['fakecluster'] parsed_args = self.check_parser(self.cmd, args, []) mock_find.side_effect = exceptions.CommandError self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) class TestDatabaseClusterCreate(TestClusters): values = ('2015-05-02T10:37:04', 'vertica', '7.1', 'cls-1234', 2, 'test-clstr', 'No tasks for the cluster.', 'NONE', '2015-05-02T11:06:19') columns = ( 'created', 'datastore', 'datastore_version', 'id', 'instance_count', 'name', 'task_description', 'task_name', 'updated', ) def setUp(self): super(TestDatabaseClusterCreate, self).setUp() self.cmd = database_clusters.CreateDatabaseCluster(self.app, None) self.data = self.fake_clusters.get_clusters_cls_1234() self.cluster_client.create.return_value = self.data @mock.patch.object(database_clusters, '_parse_instance_options') def test_cluster_create(self, mock_parse_instance_opts): instances = ['flavor="02",volume=2', 'flavor="03",volume=3'] parsed_instances = [{'flavor': '02', 'volume': 2}, {'flavor': '03', 'volume': 3}] extended_properties = "foo_properties=foo_value" parsed_extended_properties = {'foo_properties': 'foo_value'} mock_parse_instance_opts.return_value = parsed_instances args = ['test-name', 'vertica', '7.1', '--instance', instances[0], '--instance', instances[1], '--extended-properties', extended_properties, '--configuration', 'config01'] verifylist = [ ('name', 'test-name'), ('datastore', 'vertica'), ('datastore_version', '7.1'), ('instances', instances), ('extended_properties', extended_properties), ('configuration', 'config01'), ] parsed_args = self.check_parser(self.cmd, args, verifylist) columns, data = self.cmd.take_action(parsed_args) self.cluster_client.create.assert_called_with( parsed_args.name, parsed_args.datastore, parsed_args.datastore_version, instances=parsed_instances, locality=parsed_args.locality, extended_properties=parsed_extended_properties, configuration=parsed_args.configuration) self.assertEqual(self.columns, columns) self.assertEqual(self.values, data) class TestDatabaseClusterResetStatus(TestClusters): def setUp(self): super(TestDatabaseClusterResetStatus, self).setUp() self.cmd = database_clusters.ResetDatabaseClusterStatus(self.app, None) @mock.patch.object(utils, 'find_resource') def test_cluster_reset_status(self, mock_find): args = ['cluster1'] mock_find.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, []) result = self.cmd.take_action(parsed_args) self.cluster_client.reset_status.assert_called_with('cluster1') self.assertIsNone(result) class TestClusterListInstances(TestClusters): columns = (database_clusters .ListDatabaseClusterInstances.columns) values = [('member-1', 'test-clstr-member-1', '02', 2, 'ACTIVE'), ('member-2', 'test-clstr-member-2', '2', 2, 'ACTIVE')] def setUp(self): super(TestClusterListInstances, self).setUp() self.cmd = (database_clusters .ListDatabaseClusterInstances(self.app, None)) self.data = self.fake_clusters.get_clusters_cls_1234() self.cluster_client.get.return_value = self.data def test_cluster_list_instances(self): args = ['cls-1234'] parsed_args = self.check_parser(self.cmd, args, []) columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) self.assertEqual(self.values, data) class TestDatabaseClusterUpgrade(TestClusters): def setUp(self): super(TestDatabaseClusterUpgrade, self).setUp() self.cmd = database_clusters.UpgradeDatabaseCluster(self.app, None) @mock.patch.object(utils, 'find_resource') def test_cluster_upgrade(self, mock_find): args = ['cluster1', 'datastore_version1'] mock_find.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, []) result = self.cmd.take_action(parsed_args) self.cluster_client.upgrade.assert_called_with('cluster1', 'datastore_version1') self.assertIsNone(result) class TestDatabaseClusterForceDelete(TestClusters): def setUp(self): super(TestDatabaseClusterForceDelete, self).setUp() self.cmd = database_clusters.ForceDeleteDatabaseCluster(self.app, None) @mock.patch.object(utils, 'find_resource') def test_cluster_force_delete(self, mock_find): args = ['cluster1'] mock_find.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, []) result = self.cmd.take_action(parsed_args) self.cluster_client.reset_status.assert_called_with('cluster1') self.cluster_client.delete.assert_called_with('cluster1') self.assertIsNone(result) @mock.patch.object(utils, 'find_resource') def test_cluster_force_delete_with_exception(self, mock_find): args = ['fakecluster'] parsed_args = self.check_parser(self.cmd, args, []) mock_find.return_value = args[0] self.cluster_client.delete.side_effect = exceptions.CommandError self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) class TestDatabaseClusterGrow(TestClusters): def setUp(self): super(TestDatabaseClusterGrow, self).setUp() self.cmd = database_clusters.GrowDatabaseCluster(self.app, None) @mock.patch.object(utils, 'find_resource') @mock.patch.object(database_clusters, '_parse_instance_options') def test_cluster_grow(self, mock_parse_instance_opts, mock_find_resource): args = ['test-clstr', '--instance', 'name=test-clstr-member-3,flavor=3'] parsed_instance_opts = [ {'name': 'test-clstr-member-3', 'flavor': 3} ] mock_parse_instance_opts.return_value = parsed_instance_opts mock_find_resource.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, []) result = self.cmd.take_action(parsed_args) self.cluster_client.grow.assert_called_with( 'test-clstr', instances=parsed_instance_opts) self.assertIsNone(result) class TestDatabaseClusterShrink(TestClusters): def setUp(self): super(TestDatabaseClusterShrink, self).setUp() self.cmd = database_clusters.ShrinkDatabaseCluster(self.app, None) self.cluster_member = self.fake_clusters.get_clusters_member_2() @mock.patch.object(utils, 'find_resource') def test_cluster_grow(self, mock_find_resource): args = ['test-clstr', 'test-clstr-member-2'] mock_find_resource.side_effect = [ args[0], self.cluster_member] parsed_args = self.check_parser(self.cmd, args, []) result = self.cmd.take_action(parsed_args) self.cluster_client.shrink.assert_called_with('test-clstr', [{'id': 'member-2'}]) self.assertIsNone(result) class TestClusterListModules(TestClusters): columns = (database_clusters .ListDatabaseClusterModules.columns) values = [('test-clstr-member-1', 'mymod1', 'ping', 'md5-1', '2018-04-17 05:34:02.84', '2018-04-17 05:34:02.84'), ('test-clstr-member-2', 'mymod2', 'ping', 'md5-2', '2018-04-17 05:34:02.84', '2018-04-17 05:34:02.84')] def setUp(self): super(TestClusterListModules, self).setUp() self.cmd = (database_clusters .ListDatabaseClusterModules(self.app, None)) self.data = self.fake_clusters.get_clusters_cls_1234() self.instance_client.modules.side_effect = ( self.fake_clusters.cluster_instance_modules()) @mock.patch.object(utils, 'find_resource') def test_cluster_list_modules(self, mock_find_resource): mock_find_resource.return_value = self.data args = ['cls-1234'] parsed_args = self.check_parser(self.cmd, args, []) columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) self.assertEqual(self.values, data) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/osc/v1/test_database_configurations.py0000664000175000017500000003503300000000000030532 0ustar00zuulzuul00000000000000# 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 unittest import mock from osc_lib import utils from troveclient import common from troveclient import exceptions from troveclient.osc.v1 import database_configurations from troveclient.tests.osc.v1 import fakes class TestConfigurations(fakes.TestDatabasev1): fake_configurations = fakes.FakeConfigurations() fake_configuration_params = fakes.FakeConfigurationParameters() def setUp(self): super(TestConfigurations, self).setUp() self.mock_client = self.app.client_manager.database self.configuration_client = (self.app.client_manager.database. configurations) self.instance_client = self.app.client_manager.database.instances self.configuration_params_client = (self.app.client_manager. database.configuration_parameters) class TestConfigurationList(TestConfigurations): defaults = { 'limit': None, 'marker': None } columns = database_configurations.ListDatabaseConfigurations.columns values = ('c-123', 'test_config', '', 'mysql', '5.6', "5.7.29") def setUp(self): super(TestConfigurationList, self).setUp() self.cmd = database_configurations.ListDatabaseConfigurations(self.app, None) data = [self.fake_configurations.get_configurations_c_123()] self.configuration_client.list.return_value = common.Paginated(data) def test_configuration_list_defaults(self): parsed_args = self.check_parser(self.cmd, [], []) columns, data = self.cmd.take_action(parsed_args) self.configuration_client.list.assert_called_once_with(**self.defaults) self.assertEqual(self.columns, columns) self.assertEqual([tuple(self.values)], data) class TestConfigurationShow(TestConfigurations): values = ('2015-05-16T10:24:28', 'mysql', '5.6', '5.7.29', '', 'c-123', 'test_config', '2015-05-16T10:24:29', '{"max_connections": 5}') def setUp(self): super(TestConfigurationShow, self).setUp() self.cmd = database_configurations.ShowDatabaseConfiguration(self.app, None) self.data = self.fake_configurations.get_configurations_c_123() self.configuration_client.get.return_value = self.data self.columns = ( 'created', 'datastore_name', 'datastore_version_name', 'datastore_version_number', 'description', 'id', 'name', 'updated', 'values', ) def test_show(self): args = ['c-123'] parsed_args = self.check_parser(self.cmd, args, []) columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) self.assertEqual(self.values, data) class TestConfigurationParameterList(TestConfigurations): columns = database_configurations.\ ListDatabaseConfigurationParameters.columns values = ('connect_timeout', 'integer', 2, 31536000, 'false') def setUp(self): super(TestConfigurationParameterList, self).setUp() self.cmd = database_configurations.\ ListDatabaseConfigurationParameters(self.app, None) data = [self.fake_configuration_params.get_params_connect_timeout()] self.configuration_params_client.parameters.return_value =\ common.Paginated(data) self.configuration_params_client.parameters_by_version.return_value =\ common.Paginated(data) def test_configuration_parameters_list_defaults(self): args = ['d-123', '--datastore', 'mysql'] verifylist = [ ('datastore_version', 'd-123'), ('datastore', 'mysql'), ] parsed_args = self.check_parser(self.cmd, args, verifylist) columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) self.assertEqual([tuple(self.values)], data) def test_configuration_parameters_list_with_version_id_exception(self): args = [ 'd-123', ] verifylist = [ ('datastore_version', 'd-123'), ] parsed_args = self.check_parser(self.cmd, args, verifylist) self.assertRaises(exceptions.NoUniqueMatch, self.cmd.take_action, parsed_args) class TestConfigurationParameterShow(TestConfigurations): values = ('d-123', 31536000, 2, 'connect_timeout', 'false', 'integer') def setUp(self): super(TestConfigurationParameterShow, self).setUp() self.cmd = database_configurations. \ ShowDatabaseConfigurationParameter(self.app, None) data = self.fake_configuration_params.get_params_connect_timeout() self.configuration_params_client.get_parameter.return_value = data self.configuration_params_client.\ get_parameter_by_version.return_value = data self.columns = ( 'datastore_version_id', 'max', 'min', 'name', 'restart_required', 'type', ) def test_configuration_parameter_show_defaults(self): args = ['d-123', 'connect_timeout', '--datastore', 'mysql'] verifylist = [ ('datastore_version', 'd-123'), ('parameter', 'connect_timeout'), ('datastore', 'mysql'), ] parsed_args = self.check_parser(self.cmd, args, verifylist) columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) self.assertEqual(self.values, data) def test_configuration_parameter_show_with_version_id_exception(self): args = [ 'd-123', 'connect_timeout', ] verifylist = [ ('datastore_version', 'd-123'), ('parameter', 'connect_timeout'), ] parsed_args = self.check_parser(self.cmd, args, verifylist) self.assertRaises(exceptions.NoUniqueMatch, self.cmd.take_action, parsed_args) class TestDatabaseConfigurationDelete(TestConfigurations): def setUp(self): super(TestDatabaseConfigurationDelete, self).setUp() self.cmd = database_configurations.\ DeleteDatabaseConfiguration(self.app, None) @mock.patch.object(utils, 'find_resource') def test_configuration_delete(self, mock_find): args = ['config1'] mock_find.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, []) result = self.cmd.take_action(parsed_args) self.configuration_client.delete.assert_called_with('config1') self.assertIsNone(result) @mock.patch.object(utils, 'find_resource') def test_configuration_delete_with_exception(self, mock_find): args = ['fakeconfig'] parsed_args = self.check_parser(self.cmd, args, []) mock_find.side_effect = exceptions.CommandError self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) class TestConfigurationCreate(TestConfigurations): values = ('2015-05-16T10:24:28', 'mysql', '5.6', '5.7.29', '', 'c-123', 'test_config', '2015-05-16T10:24:29', '{"max_connections": 5}') def setUp(self): super(TestConfigurationCreate, self).setUp() self.cmd = database_configurations.\ CreateDatabaseConfiguration(self.app, None) self.data = self.fake_configurations.get_configurations_c_123() self.configuration_client.create.return_value = self.data self.columns = ( 'created', 'datastore_name', 'datastore_version_name', 'datastore_version_number', 'description', 'id', 'name', 'updated', 'values', ) def test_configuration_create_return_value(self): args = ['c-123', '{"max_connections": 5}', '--description', 'test_config', '--datastore', 'mysql', '--datastore-version', '5.6'] parsed_args = self.check_parser(self.cmd, args, []) columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) self.assertEqual(self.values, data) def test_configuration_create(self): args = ['cgroup1', '{"param1": 1, "param2": 2}'] parsed_args = self.check_parser(self.cmd, args, []) self.cmd.take_action(parsed_args) self.configuration_client.create.assert_called_with( 'cgroup1', '{"param1": 1, "param2": 2}', description=None, datastore=None, datastore_version=None, datastore_version_number=None) def test_configuration_create_with_optional_args(self): args = ['cgroup2', '{"param3": 3, "param4": 4}', '--description', 'cgroup 2', '--datastore', 'mysql', '--datastore-version', '5.6'] parsed_args = self.check_parser(self.cmd, args, []) self.cmd.take_action(parsed_args) self.configuration_client.create.assert_called_with( 'cgroup2', '{"param3": 3, "param4": 4}', description='cgroup 2', datastore='mysql', datastore_version='5.6', datastore_version_number=None) class TestConfigurationAttach(TestConfigurations): def setUp(self): super(TestConfigurationAttach, self).setUp() self.cmd = database_configurations.\ AttachDatabaseConfiguration(self.app, None) @mock.patch.object(utils, 'find_resource') def test_configuration_attach(self, mock_find): args = ['instance1', 'config1'] mock_find.side_effect = ['instance1', 'config1'] parsed_args = self.check_parser(self.cmd, args, []) result = self.cmd.take_action(parsed_args) self.instance_client.update.assert_called_with( 'instance1', configuration='config1') self.assertIsNone(result) class TestConfigurationDetach(TestConfigurations): def setUp(self): super(TestConfigurationDetach, self).setUp() self.cmd = database_configurations. \ DetachDatabaseConfiguration(self.app, None) @mock.patch.object(utils, 'find_resource') def test_configuration_detach(self, mock_find): args = ['instance2'] mock_find.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, []) result = self.cmd.take_action(parsed_args) self.instance_client.update.assert_called_with( 'instance2', remove_configuration=True) self.assertIsNone(result) class TestConfigurationInstancesList(TestConfigurations): defaults = { 'limit': None, 'marker': None } columns = ( database_configurations.ListDatabaseConfigurationInstances.columns) values = [('1', 'instance-1'), ('2', 'instance-2')] def setUp(self): super(TestConfigurationInstancesList, self).setUp() self.cmd = database_configurations.ListDatabaseConfigurationInstances( self.app, None) data = ( self.fake_configurations.get_configuration_instances()) self.configuration_client.instances.return_value = common.Paginated( data) @mock.patch.object(utils, 'find_resource') def test_configuration_instances_list(self, mock_find): args = ['c-123'] mock_find.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, []) columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) self.assertEqual(self.values, data) class TestConfigurationDefault(TestConfigurations): values = ('2', '98', '1', '15M') def setUp(self): super(TestConfigurationDefault, self).setUp() self.cmd = database_configurations.DefaultDatabaseConfiguration( self.app, None) self.data = ( self.fake_configurations.get_default_configuration()) self.instance_client.configuration.return_value = self.data self.columns = ( 'innodb_log_files_in_group', 'max_user_connections', 'skip-external-locking', 'tmp_table_size', ) @mock.patch.object(utils, 'find_resource') def test_default_database_configuration(self, mock_find): args = ['1234'] mock_find.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, []) columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) self.assertEqual(self.values, data) class TestSetDatabaseConfiguration(TestConfigurations): def setUp(self): super(TestSetDatabaseConfiguration, self).setUp() self.cmd = database_configurations.SetDatabaseConfiguration( self.app, None) def test_set_database_configuration_parameter(self): args = ['config_group_id', '{"param1": 1, "param2": 2}'] parsed_args = self.check_parser(self.cmd, args, []) self.cmd.take_action(parsed_args) self.configuration_client.edit.assert_called_once_with( 'config_group_id', '{"param1": 1, "param2": 2}' ) class TestUpdateDatabaseConfiguration(TestConfigurations): def setUp(self): super(TestUpdateDatabaseConfiguration, self).setUp() self.cmd = database_configurations.UpdateDatabaseConfiguration( self.app, None) def test_set_database_configuration_parameter(self): args = ['config_group_id', '{"param1": 1, "param2": 2}', '--name', 'new_name'] parsed_args = self.check_parser(self.cmd, args, []) self.cmd.take_action(parsed_args) self.configuration_client.update.assert_called_once_with( 'config_group_id', '{"param1": 1, "param2": 2}', name='new_name', description=None ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/osc/v1/test_database_flavors.py0000664000175000017500000000571700000000000027162 0ustar00zuulzuul00000000000000# 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.osc.v1 import database_flavors from troveclient.tests.osc.v1 import fakes class TestFlavors(fakes.TestDatabasev1): fake_flavors = fakes.FakeFlavors() def setUp(self): super(TestFlavors, self).setUp() self.mock_client = self.app.client_manager.database self.flavor_client = self.app.client_manager.database.flavors class TestFlavorList(TestFlavors): columns = database_flavors.ListDatabaseFlavors.columns values = (1, 'm1.tiny', 512, '', '', '') def setUp(self): super(TestFlavorList, self).setUp() self.cmd = database_flavors.ListDatabaseFlavors(self.app, None) self.data = [self.fake_flavors.get_flavors_1()] self.flavor_client.list.return_value = self.data self.flavor_client.list_datastore_version_associated_flavors. \ return_value = self.data def test_flavor_list_defaults(self): parsed_args = self.check_parser(self.cmd, [], []) columns, values = self.cmd.take_action(parsed_args) self.flavor_client.list.assert_called_once_with() self.assertEqual(self.columns, columns) self.assertEqual([self.values], values) def test_flavor_list_with_optional_args(self): args = ['--datastore-type', 'mysql', '--datastore-version-id', '5.6'] parsed_args = self.check_parser(self.cmd, args, []) list_flavor_dict = {'datastore': 'mysql', 'version_id': '5.6'} columns, values = self.cmd.take_action(parsed_args) self.flavor_client.list_datastore_version_associated_flavors. \ assert_called_once_with(**list_flavor_dict) self.assertEqual(self.columns, columns) self.assertEqual([self.values], values) class TestFlavorShow(TestFlavors): values = (1, 'm1.tiny', 512) def setUp(self): super(TestFlavorShow, self).setUp() self.cmd = database_flavors.ShowDatabaseFlavor(self.app, None) self.data = self.fake_flavors.get_flavors_1() self.flavor_client.get.return_value = self.data self.columns = ( 'id', 'name', 'ram', ) def test_flavor_show_defaults(self): args = ['m1.tiny'] parsed_args = self.check_parser(self.cmd, args, []) columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) self.assertEqual(self.values, data) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/osc/v1/test_database_instances.py0000664000175000017500000006406300000000000027474 0ustar00zuulzuul00000000000000# 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 unittest import mock from osc_lib import utils from oslo_utils import uuidutils from troveclient import common from troveclient import exceptions from troveclient.osc.v1 import database_instances from troveclient.tests.osc.v1 import fakes from troveclient.v1 import instances class TestInstances(fakes.TestDatabasev1): def setUp(self): super(TestInstances, self).setUp() self.fake_instances = fakes.FakeInstances() self.instance_client = self.app.client_manager.database.instances self.mgmt_client = self.app.client_manager.database.mgmt_instances class TestInstanceList(TestInstances): defaults = { 'include_clustered': False, 'limit': None, 'marker': None } def setUp(self): super(TestInstanceList, self).setUp() self.cmd = database_instances.ListDatabaseInstances(self.app, None) def test_instance_list_defaults(self): instance_id = self.random_uuid() name = self.random_name('test-list') tenant_id = self.random_uuid() insts = [ { "id": instance_id, "name": name, "status": "ACTIVE", "operating_status": "HEALTHY", "addresses": [ {"type": "private", "address": "10.0.0.13"} ], "volume": {"size": 2}, "flavor": {"id": "02"}, "region": "regionOne", "datastore": { "version": "5.6", "type": "mysql", "version_number": "5.7.29" }, "tenant_id": tenant_id, "replica_of": self.random_uuid(), "access": {"is_public": False, "allowed_cidrs": []}, } ] self.instance_client.list.return_value = common.Paginated( [instances.Instance(mock.MagicMock(), inst) for inst in insts]) parsed_args = self.check_parser(self.cmd, [], []) columns, data = self.cmd.take_action(parsed_args) self.instance_client.list.assert_called_once_with(**self.defaults) self.assertEqual( database_instances.ListDatabaseInstances.columns, columns ) values = [ (instance_id, name, 'mysql', '5.6', 'ACTIVE', 'HEALTHY', False, [{"type": "private", "address": "10.0.0.13"}], '02', 2, 'replica'), ] self.assertEqual(values, data) def test_instance_list_all_projects(self): instance_id = self.random_uuid() name = self.random_name('test-list') tenant_id = self.random_uuid() server_id = self.random_uuid() insts = [ { "id": instance_id, "name": name, "status": "ACTIVE", "operating_status": "HEALTHY", "addresses": [ {"type": "private", "address": "10.0.0.13"} ], "volume": {"size": 2}, "flavor": {"id": "02"}, "region": "regionOne", "datastore": { "version": "5.6", "type": "mysql", "version_number": "5.7.29" }, "tenant_id": tenant_id, "access": {"is_public": False, "allowed_cidrs": []}, "server_id": server_id, 'server': { 'id': server_id } } ] self.mgmt_client.list.return_value = common.Paginated( [instances.Instance(mock.MagicMock(), inst) for inst in insts]) parsed_args = self.check_parser(self.cmd, ["--all-projects"], [("all_projects", True)]) columns, data = self.cmd.take_action(parsed_args) self.mgmt_client.list.assert_called_once_with(**self.defaults) self.assertEqual( database_instances.ListDatabaseInstances.admin_columns, columns ) expected_instances = [ (instance_id, name, 'mysql', '5.6', 'ACTIVE', 'HEALTHY', False, [{"type": "private", "address": "10.0.0.13"}], '02', 2, '', server_id, tenant_id), ] self.assertEqual(expected_instances, data) def test_instance_list_for_project(self): instance_id = self.random_uuid() name = self.random_name('test-list') tenant_id = self.random_uuid() server_id = self.random_uuid() insts = [ { "id": instance_id, "name": name, "status": "ACTIVE", "operating_status": "HEALTHY", "addresses": [ {"type": "private", "address": "10.0.0.13"} ], "volume": {"size": 2}, "flavor": {"id": "02"}, "region": "regionOne", "datastore": { "version": "5.6", "type": "mysql", "version_number": "5.7.29" }, "tenant_id": tenant_id, "access": {"is_public": False, "allowed_cidrs": []}, "server_id": server_id, 'server': { 'id': server_id } } ] self.mgmt_client.list.return_value = common.Paginated( [instances.Instance(mock.MagicMock(), inst) for inst in insts]) parsed_args = self.check_parser(self.cmd, ["--project-id", tenant_id], [("project_id", tenant_id)]) columns, data = self.cmd.take_action(parsed_args) self.assertEqual( database_instances.ListDatabaseInstances.admin_columns, columns ) expected_instances = [ (instance_id, name, 'mysql', '5.6', 'ACTIVE', 'HEALTHY', False, [{"type": "private", "address": "10.0.0.13"}], '02', 2, '', server_id, tenant_id), ] self.assertEqual(expected_instances, data) expected_params = { 'include_clustered': False, 'limit': None, 'marker': None, 'project_id': tenant_id } self.mgmt_client.list.assert_called_once_with(**expected_params) class TestInstanceShow(TestInstances): def setUp(self): super(TestInstanceShow, self).setUp() self.cmd = database_instances.ShowDatabaseInstance(self.app, None) self.columns = ( 'addresses', 'allowed_cidrs', 'datastore', 'datastore_version', 'datastore_version_number', 'flavor', 'id', 'name', 'operating_status', 'public', 'region', 'replica_of', 'status', 'tenant_id', 'volume', ) def test_show(self): instance_id = self.random_uuid() name = self.random_name('test-show') flavor_id = self.random_uuid() primary_id = self.random_uuid() tenant_id = self.random_uuid() inst = { "id": instance_id, "name": name, "status": "ACTIVE", "operating_status": "HEALTHY", "addresses": [ {"type": "private", "address": "10.0.0.13"} ], "volume": {"size": 2}, "flavor": {"id": flavor_id}, "region": "regionOne", "datastore": { "version": "5.7.29", "type": "mysql", "version_number": "5.7.29" }, "tenant_id": tenant_id, "replica_of": {'id': primary_id}, "access": {"is_public": False, "allowed_cidrs": []}, } self.instance_client.get.return_value = instances.Instance( mock.MagicMock(), inst) parsed_args = self.check_parser(self.cmd, [instance_id], []) columns, data = self.cmd.take_action(parsed_args) values = ([{'address': '10.0.0.13', 'type': 'private'}], [], 'mysql', '5.7.29', '5.7.29', flavor_id, instance_id, name, 'HEALTHY', False, 'regionOne', primary_id, 'ACTIVE', tenant_id, 2) self.assertEqual(self.columns, columns) self.assertEqual(values, data) class TestDatabaseInstanceDelete(TestInstances): def setUp(self): super(TestDatabaseInstanceDelete, self).setUp() self.cmd = database_instances.DeleteDatabaseInstance(self.app, None) @mock.patch("troveclient.utils.get_resource_id_by_name") def test_instance_delete(self, mock_getid): mock_getid.return_value = "fake_uuid" args = ['instance1'] parsed_args = self.check_parser(self.cmd, args, []) self.cmd.take_action(parsed_args) mock_getid.assert_called_once_with(self.instance_client, "instance1") self.instance_client.delete.assert_called_with('fake_uuid') @mock.patch("troveclient.utils.get_resource_id_by_name") def test_instance_delete_with_exception(self, mock_getid): mock_getid.side_effect = exceptions.CommandError args = ['fakeinstance'] parsed_args = self.check_parser(self.cmd, args, []) self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) @mock.patch("troveclient.utils.get_resource_id_by_name") def test_instance_bulk_delete(self, mock_getid): instance_1 = uuidutils.generate_uuid() instance_2 = uuidutils.generate_uuid() mock_getid.return_value = instance_1 args = ["fake_instance", instance_2] parsed_args = self.check_parser(self.cmd, args, []) self.cmd.take_action(parsed_args) mock_getid.assert_called_once_with(self.instance_client, "fake_instance") calls = [mock.call(instance_1), mock.call(instance_2)] self.instance_client.delete.assert_has_calls(calls) @mock.patch("troveclient.utils.get_resource_id_by_name") def test_instance_force_delete(self, mock_getid): mock_getid.return_value = "fake_uuid" args = ['instance1', '--force'] parsed_args = self.check_parser(self.cmd, args, [('force', True)]) self.cmd.take_action(parsed_args) mock_getid.assert_called_once_with(self.instance_client, "instance1") self.instance_client.force_delete.assert_called_with('fake_uuid') class TestDatabaseInstanceCreate(TestInstances): values = ('2017-12-22T20:02:32', 'mysql', '5.6', '5.7.29', '310', '2468', 'test', 'test-net', 'net-id', 'BUILD', '2017-12-22T20:02:32', 1) columns = ( 'created', 'datastore', 'datastore_version', 'datastore_version_number', 'flavor', 'id', 'name', 'networks', 'networks_id', 'status', 'updated', 'volume', ) def setUp(self): super(TestDatabaseInstanceCreate, self).setUp() self.cmd = database_instances.CreateDatabaseInstance(self.app, None) self.data = self.fake_instances.get_instance_create() self.instance_client.create.return_value = self.data @mock.patch.object(utils, 'find_resource') def test_instance_create(self, mock_find): args = ['test-name', '--flavor', '103', '--size', '1', '--databases', 'db1', 'db2', '--users', 'u1:111', 'u2:111', '--datastore', "datastore", '--datastore-version', "datastore_version", '--nic', 'net-id=net1', '--replica-of', 'test', '--replica-count', '4', '--module', 'mod_id', '--is-public', '--allowed-cidr', '10.0.0.1/24', '--allowed-cidr', '192.168.0.1/24'] verifylist = [ ('name', 'test-name'), ('flavor', '103'), ('size', 1), ('databases', ['db1', 'db2']), ('users', ['u1:111', 'u2:111']), ('datastore', "datastore"), ('datastore_version', "datastore_version"), ('nics', 'net-id=net1'), ('replica_of', 'test'), ('replica_count', 4), ('modules', ['mod_id']), ('is_public', True), ('allowed_cidrs', ['10.0.0.1/24', '192.168.0.1/24']) ] parsed_args = self.check_parser(self.cmd, args, verifylist) columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) self.assertEqual(self.values, data) @mock.patch.object(utils, 'find_resource') def test_instance_create_without_allowed_cidrs(self, mock_find): resp = { "id": "a1fea1cf-18ad-48ab-bdfd-fce99a4b834e", "name": "test-mysql", "status": "BUILD", "flavor": { "id": "a48ea749-7ee3-4003-8aae-eb4e79773e2d" }, "datastore": { "type": "mysql", "version": "5.7.29", "version_number": "5.7.29" }, "region": "RegionOne", "access": { "is_public": True }, "volume": { "size": 1 }, "created": "2020-08-12T09:41:47", "updated": "2020-08-12T09:41:47", "service_status_updated": "2020-08-12T09:41:47" } self.instance_client.create.return_value = instances.Instance( mock.MagicMock(), resp) args = [ 'test-mysql', '--flavor', 'a48ea749-7ee3-4003-8aae-eb4e79773e2d', '--size', '1', '--datastore', "mysql", '--datastore-version', "5.7.29", '--nic', 'net-id=net1', '--is-public' ] verifylist = [ ('name', 'test-mysql'), ('flavor', 'a48ea749-7ee3-4003-8aae-eb4e79773e2d'), ('size', 1), ('datastore', "mysql"), ('datastore_version', "5.7.29"), ('nics', 'net-id=net1'), ('is_public', True), ('allowed_cidrs', None) ] parsed_args = self.check_parser(self.cmd, args, verifylist) columns, data = self.cmd.take_action(parsed_args) expected_columns = ( 'allowed_cidrs', 'created', 'datastore', 'datastore_version', 'datastore_version_number', 'flavor', 'id', 'name', 'public', 'region', 'service_status_updated', 'status', 'updated', 'volume', ) expected_values = ( [], "2020-08-12T09:41:47", "mysql", "5.7.29", "5.7.29", "a48ea749-7ee3-4003-8aae-eb4e79773e2d", "a1fea1cf-18ad-48ab-bdfd-fce99a4b834e", "test-mysql", True, "RegionOne", "2020-08-12T09:41:47", "BUILD", "2020-08-12T09:41:47", 1, ) self.assertEqual(expected_columns, columns) self.assertEqual(expected_values, data) @mock.patch.object(utils, 'find_resource') def test_instance_create_nic_param(self, mock_find): fake_id = self.random_uuid() mock_find.return_value.id = fake_id args = [ 'test-mysql', '--flavor', 'a48ea749-7ee3-4003-8aae-eb4e79773e2d', '--size', '1', '--datastore', "mysql", '--datastore-version', "5.7.29", '--nic', 'net-id=net1,subnet-id=subnet_id,ip-address=192.168.1.11', ] parsed_args = self.check_parser(self.cmd, args, []) self.cmd.take_action(parsed_args) self.instance_client.create.assert_called_once_with( 'test-mysql', flavor_id=fake_id, volume={"size": 1, "type": None}, databases=[], users=[], restorePoint=None, availability_zone=None, datastore='mysql', datastore_version='5.7.29', datastore_version_number=None, nics=[ {'network_id': 'net1', 'subnet_id': 'subnet_id', 'ip_address': '192.168.1.11'} ], configuration=None, replica_of=None, replica_count=None, modules=[], locality=None, region_name=None, access={'is_public': False} ) class TestDatabaseInstanceResetStatus(TestInstances): def setUp(self): super(TestDatabaseInstanceResetStatus, self).setUp() self.cmd = database_instances.ResetDatabaseInstanceStatus(self.app, None) @mock.patch.object(utils, 'find_resource') def test_instance_reset_status(self, mock_find): args = ['instance1'] mock_find.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, []) result = self.cmd.take_action(parsed_args) self.instance_client.reset_status.assert_called_with('instance1') self.assertIsNone(result) class TestDatabaseInstanceResizeFlavor(TestInstances): def setUp(self): super(TestDatabaseInstanceResizeFlavor, self).setUp() self.cmd = database_instances.ResizeDatabaseInstanceFlavor(self.app, None) @mock.patch.object(utils, 'find_resource') def test_instance_resize_flavor(self, mock_find): args = ['instance1', 'flavor_id'] mock_find.side_effect = [ mock.MagicMock(id='fake_instance_id'), mock.MagicMock(id='fake_flavor_id') ] parsed_args = self.check_parser(self.cmd, args, []) self.cmd.take_action(parsed_args) self.instance_client.resize_instance.assert_called_with( 'fake_instance_id', 'fake_flavor_id') class TestDatabaseInstanceUpgrade(TestInstances): def setUp(self): super(TestDatabaseInstanceUpgrade, self).setUp() self.cmd = database_instances.UpgradeDatabaseInstance(self.app, None) @mock.patch.object(utils, 'find_resource') def test_instance_upgrade(self, mock_find): args = ['instance1', 'datastore_version1'] mock_find.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, []) result = self.cmd.take_action(parsed_args) self.instance_client.upgrade.assert_called_with('instance1', 'datastore_version1') self.assertIsNone(result) class TestDatabaseInstanceResizeVolume(TestInstances): def setUp(self): super(TestDatabaseInstanceResizeVolume, self).setUp() self.cmd = database_instances.ResizeDatabaseInstanceVolume(self.app, None) @mock.patch.object(utils, 'find_resource') def test_instance_resize_volume(self, mock_find): args = ['instance1', '5'] mock_find.side_effect = ['instance1'] parsed_args = self.check_parser(self.cmd, args, []) result = self.cmd.take_action(parsed_args) self.instance_client.resize_volume.assert_called_with('instance1', 5) self.assertIsNone(result) class TestDatabaseInstanceForceDelete(TestInstances): def setUp(self): super(TestDatabaseInstanceForceDelete, self).setUp() self.cmd = (database_instances .ForceDeleteDatabaseInstance(self.app, None)) @mock.patch.object(utils, 'find_resource') def test_instance_force_delete(self, mock_find): args = ['instance1'] mock_find.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, []) result = self.cmd.take_action(parsed_args) self.instance_client.reset_status.assert_called_with('instance1') self.instance_client.delete.assert_called_with('instance1') self.assertIsNone(result) @mock.patch.object(utils, 'find_resource') def test_instance_force_delete_with_exception(self, mock_find): args = ['fakeinstance'] parsed_args = self.check_parser(self.cmd, args, []) mock_find.return_value = args[0] self.instance_client.delete.side_effect = exceptions.CommandError self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) class TestDatabaseInstancePromoteToReplicaSource(TestInstances): def setUp(self): super(TestDatabaseInstancePromoteToReplicaSource, self).setUp() self.cmd = database_instances.PromoteDatabaseInstanceToReplicaSource( self.app, None) @mock.patch.object(utils, 'find_resource') def test_instance_promote_to_replica_source(self, mock_find): args = ['instance'] mock_find.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, []) result = self.cmd.take_action(parsed_args) self.instance_client.promote_to_replica_source.assert_called_with( 'instance') self.assertIsNone(result) class TestDatabaseInstanceRestart(TestInstances): def setUp(self): super(TestDatabaseInstanceRestart, self).setUp() self.cmd = database_instances.RestartDatabaseInstance(self.app, None) @mock.patch.object(utils, 'find_resource') def test_instance_restart(self, mock_find): args = ['instance1'] mock_find.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, []) result = self.cmd.take_action(parsed_args) self.instance_client.restart.assert_called_with('instance1') self.assertIsNone(result) class TestDatabaseInstanceEjectReplicaSource(TestInstances): def setUp(self): super(TestDatabaseInstanceEjectReplicaSource, self).setUp() self.cmd = database_instances.EjectDatabaseInstanceReplicaSource( self.app, None) @mock.patch.object(utils, 'find_resource') def test_eject_replica_source(self, mock_find): args = ['instance'] mock_find.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, []) result = self.cmd.take_action(parsed_args) self.instance_client.eject_replica_source.assert_called_with( 'instance') self.assertIsNone(result) class TestDatabaseInstanceUpdate(TestInstances): def setUp(self): super(TestDatabaseInstanceUpdate, self).setUp() self.cmd = database_instances.UpdateDatabaseInstance(self.app, None) @mock.patch.object(utils, 'find_resource') def test_instance_update(self, mock_find): args = ['instance1', '--name', 'new_instance_name', '--detach_replica_source', '--remove_configuration'] mock_find.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, []) result = self.cmd.take_action(parsed_args) self.instance_client.update.assert_called_with( 'instance1', None, 'new_instance_name', True, True, is_public=None, allowed_cidrs=None) self.assertIsNone(result) def test_instance_update_access(self): ins_id = '4c397f77-750d-43df-8fc5-f7388e4316ee' args = [ins_id, '--name', 'new_instance_name', '--is-private', '--allowed-cidr', '10.0.0.0/24', '--allowed-cidr', '10.0.1.0/24'] parsed_args = self.check_parser(self.cmd, args, []) self.cmd.take_action(parsed_args) self.instance_client.update.assert_called_with( ins_id, None, 'new_instance_name', False, False, is_public=False, allowed_cidrs=['10.0.0.0/24', '10.0.1.0/24']) class TestInstanceReplicaDetach(TestInstances): def setUp(self): super(TestInstanceReplicaDetach, self).setUp() self.cmd = database_instances.DetachDatabaseInstanceReplica( self.app, None) @mock.patch.object(utils, 'find_resource') def test_instance_replica_detach(self, mock_find): args = ['instance'] mock_find.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, []) result = self.cmd.take_action(parsed_args) self.instance_client.update.assert_called_with( 'instance', detach_replica_source=True) self.assertIsNone(result) class TestDatabaseInstanceReboot(TestInstances): def setUp(self): super(TestDatabaseInstanceReboot, self).setUp() self.cmd = database_instances.RebootDatabaseInstance(self.app, None) @mock.patch.object(utils, 'find_resource') def test_instance_restart(self, mock_find): args = ['instance1'] mock_find.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, []) self.cmd.take_action(parsed_args) self.mgmt_client.reboot.assert_called_with('instance1') class TestDatabaseInstanceRebuild(TestInstances): def setUp(self): super(TestDatabaseInstanceRebuild, self).setUp() self.cmd = database_instances.RebuildDatabaseInstance(self.app, None) @mock.patch.object(utils, 'find_resource') def test_instance_rebuild(self, mock_find): args = ['instance1', 'image_id'] mock_find.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, []) self.cmd.take_action(parsed_args) self.mgmt_client.rebuild.assert_called_with('instance1', 'image_id') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/osc/v1/test_database_limits.py0000664000175000017500000000322400000000000026776 0ustar00zuulzuul00000000000000# 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 common from troveclient.osc.v1 import database_limits from troveclient.tests.osc.v1 import fakes class TestLimits(fakes.TestDatabasev1): fake_limits = fakes.FakeLimits() def setUp(self): super(TestLimits, self).setUp() self.limit_client = self.app.client_manager.database.limits class TestLimitList(TestLimits): columns = database_limits.ListDatabaseLimits.columns non_absolute_values = (200, 'DELETE', 200, 'MINUTE') def setUp(self): super(TestLimitList, self).setUp() self.cmd = database_limits.ListDatabaseLimits(self.app, None) data = [self.fake_limits.get_absolute_limits(), self.fake_limits.get_non_absolute_limits()] self.limit_client.list.return_value = common.Paginated(data) def test_limit_list_defaults(self): parsed_args = self.check_parser(self.cmd, [], []) columns, data = self.cmd.take_action(parsed_args) self.limit_client.list.assert_called_once_with() self.assertEqual(self.columns, columns) self.assertEqual([self.non_absolute_values], data) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/osc/v1/test_database_logs.py0000664000175000017500000000677300000000000026455 0ustar00zuulzuul00000000000000# 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 unittest import mock from osc_lib import utils from troveclient.osc.v1 import database_logs from troveclient.tests.osc.v1 import fakes class TestLogs(fakes.TestDatabasev1): fake_logs = fakes.FakeLogs() def setUp(self): super(TestLogs, self).setUp() self.instance_client = self.app.client_manager.database.instances class TestLogList(TestLogs): columns = database_logs.ListDatabaseLogs.columns values = [('general', 'USER', 'Partial', '128', '4096', 'data_logs', 'mysql-general'), ('slow_query', 'USER', 'Ready', '0', '128', 'None', 'None')] def setUp(self): super(TestLogList, self).setUp() self.cmd = database_logs.ListDatabaseLogs(self.app, None) data = self.fake_logs.get_logs() self.instance_client.log_list.return_value = data @mock.patch.object(utils, 'find_resource') def test_log_list(self, mock_find): args = ['instance'] mock_find.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, []) columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) self.assertEqual(self.values, data) class TestShowDatabaseInstanceLog(TestLogs): def setUp(self): super(TestShowDatabaseInstanceLog, self).setUp() self.cmd = database_logs.ShowDatabaseInstanceLog(self.app, None) self.columns = ( 'container', 'metafile', 'name', 'pending', 'prefix', 'published', 'status', 'type', ) @mock.patch.object(utils, 'find_resource') def test_show_instance_log(self, mock_find): mock_find.return_value = 'fake_instance_id' data = self.fake_logs.get_logs()[0] self.instance_client.log_show.return_value = data args = ['instance', 'logname'] parsed_args = self.check_parser(self.cmd, args, []) columns, values = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) self.assertCountEqual(data.to_dict().values(), values) class TestSetDatabaseInstanceLog(TestLogs): def setUp(self): super(TestSetDatabaseInstanceLog, self).setUp() self.cmd = database_logs.SetDatabaseInstanceLog(self.app, None) @mock.patch.object(utils, 'find_resource') def test_set_instance_log(self, mock_find): mock_find.return_value = 'fake_instance_id' data = self.fake_logs.get_logs()[0] data.status = 'Ready' self.instance_client.log_action.return_value = data args = ['instance1', 'log_name', '--enable'] parsed_args = self.check_parser(self.cmd, args, []) self.cmd.take_action(parsed_args) self.instance_client.log_action.assert_called_once_with( 'fake_instance_id', 'log_name', enable=True, disable=False, discard=False, publish=False ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/osc/v1/test_database_quota.py0000664000175000017500000000423500000000000026631 0ustar00zuulzuul00000000000000# 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.osc.v1 import database_quota from troveclient.tests.osc.v1 import fakes class TestQuota(fakes.TestDatabasev1): fake_quota = fakes.FakeQuota() def setUp(self): super(TestQuota, self).setUp() self.mock_client = self.app.client_manager.database self.quota_client = self.app.client_manager.database.quota class TestQuotaShow(TestQuota): columns = database_quota.ShowDatabaseQuota.columns values = [('instances', 2, 1, 10), ('backups', 4, 3, 50), ('volumes', 6, 5, 40)] def setUp(self): super(TestQuotaShow, self).setUp() self.cmd = database_quota.ShowDatabaseQuota(self.app, None) self.data = self.fake_quota.get_quotas() self.quota_client.show.return_value = self.data def test_show_quotas(self): args = ['tenant_id'] parsed_args = self.check_parser(self.cmd, args, []) columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) self.assertEqual(self.values, data) class TestQuotaUpdate(TestQuota): def setUp(self): super(TestQuotaUpdate, self).setUp() self.cmd = database_quota.UpdateDatabaseQuota(self.app, None) self.data = self.fake_quota.fake_instances_quota self.quota_client.update.return_value = self.data def test_update_quota(self): args = ['tenant_id', 'instances', '51'] parsed_args = self.check_parser(self.cmd, args, []) columns, data = self.cmd.take_action(parsed_args) self.assertEqual(('instances',), columns) self.assertEqual((51,), data) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/osc/v1/test_database_root.py0000664000175000017500000001210300000000000026454 0ustar00zuulzuul00000000000000# 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 unittest import mock from osc_lib import exceptions from osc_lib import utils from troveclient.osc.v1 import database_root from troveclient.tests.osc.v1 import fakes class TestRoot(fakes.TestDatabasev1): fake_root = fakes.FakeRoot() def setUp(self): super(TestRoot, self).setUp() self.mock_client = self.app.client_manager.database self.root_client = self.app.client_manager.database.root class TestRootEnable(TestRoot): def setUp(self): super(TestRootEnable, self).setUp() self.cmd = database_root.EnableDatabaseRoot(self.app, None) self.data = { 'instance': self.fake_root.post_instance_1234_root(), 'cluster': self.fake_root.post_cls_1234_root() } self.columns = ('name', 'password',) @mock.patch.object(utils, 'find_resource') def test_enable_instance_1234_root(self, mock_find): self.root_client.create_instance_root.return_value = ( self.data['instance']) args = ['1234'] parsed_args = self.check_parser(self.cmd, args, []) columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) self.assertEqual(('root', 'password',), data) @mock.patch.object(utils, 'find_resource') def test_enable_cluster_1234_root(self, mock_find): mock_find.side_effect = [exceptions.CommandError(), (None, 'cluster')] self.root_client.create_cluster_root.return_value = ( self.data['cluster']) args = ['1234'] parsed_args = self.check_parser(self.cmd, args, []) columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) self.assertEqual(('root', 'password',), data) @mock.patch.object(utils, 'find_resource') def test_enable_instance_root_with_password(self, mock_find): args = ['1234', '--root_password', 'secret'] parsed_args = self.check_parser(self.cmd, args, []) self.cmd.take_action(parsed_args) self.root_client.create_instance_root(None, root_password='secret') @mock.patch.object(utils, 'find_resource') def test_enable_cluster_root_with_password(self, mock_find): args = ['1234', '--root_password', 'secret'] parsed_args = self.check_parser(self.cmd, args, []) self.cmd.take_action(parsed_args) self.root_client.create_cluster_root(None, root_password='secret') class TestRootDisable(TestRoot): def setUp(self): super(TestRootDisable, self).setUp() self.cmd = database_root.DisableDatabaseRoot(self.app, None) self.data = self.fake_root.delete_instance_1234_root() @mock.patch.object(utils, 'find_resource') def test_disable_instance_1234_root(self, mock_find): self.root_client.disable_instance_root.return_value = self.data args = ['1234'] mock_find.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, []) result = self.cmd.take_action(parsed_args) self.root_client.disable_instance_root.assert_called_with('1234') self.assertIsNone(result) class TestRootShow(TestRoot): def setUp(self): super(TestRootShow, self).setUp() self.cmd = database_root.ShowDatabaseRoot(self.app, None) self.data = { 'instance': self.fake_root.get_instance_1234_root(), 'cluster': self.fake_root.get_cls_1234_root() } self.columns = ('is_root_enabled',) @mock.patch.object(utils, 'find_resource') def test_show_instance_1234_root(self, mock_find): self.root_client.is_instance_root_enabled.return_value = ( self.data['instance']) args = ['1234'] parsed_args = self.check_parser(self.cmd, args, []) columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) self.assertEqual(('True',), data) @mock.patch.object(utils, 'find_resource') def test_show_cluster_1234_root(self, mock_find): mock_find.side_effect = [exceptions.CommandError(), (None, 'cluster')] self.root_client.is_cluster_root_enabled.return_value = ( self.data['cluster']) args = ['1234'] parsed_args = self.check_parser(self.cmd, args, []) columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) self.assertEqual(('True',), data) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/osc/v1/test_database_users.py0000664000175000017500000002152000000000000026635 0ustar00zuulzuul00000000000000# 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 unittest import mock from osc_lib import exceptions from osc_lib import utils from troveclient import common from troveclient.osc.v1 import database_users from troveclient.tests.osc.v1 import fakes class TestUsers(fakes.TestDatabasev1): fake_users = fakes.FakeUsers() def setUp(self): super(TestUsers, self).setUp() self.user_client = self.app.client_manager.database.users class TestDatabaseUserCreate(TestUsers): def setUp(self): super(TestDatabaseUserCreate, self).setUp() self.cmd = database_users.CreateDatabaseUser(self.app, None) @mock.patch.object(utils, 'find_resource') def test_user_create(self, mock_find): args = ['instance1', 'user1', 'password1'] mock_find.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, []) result = self.cmd.take_action(parsed_args) user = {'name': 'user1', 'password': 'password1', 'databases': []} self.user_client.create.assert_called_with('instance1', [user]) self.assertIsNone(result) @mock.patch.object(utils, 'find_resource') def test_user_create_with_optional_args(self, mock_find): args = ['instance2', 'user2', 'password2', '--host', '1.1.1.1', '--databases', 'db1', 'db2'] mock_find.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, []) result = self.cmd.take_action(parsed_args) user = {'name': 'user2', 'password': 'password2', 'host': '1.1.1.1', 'databases': [{'name': 'db1'}, {'name': 'db2'}]} self.user_client.create.assert_called_with('instance2', [user]) self.assertIsNone(result) class TestUserList(TestUsers): columns = database_users.ListDatabaseUsers.columns values = ('harry', '%', 'db1') def setUp(self): super(TestUserList, self).setUp() self.cmd = database_users.ListDatabaseUsers(self.app, None) data = [self.fake_users.get_instances_1234_users_harry()] self.user_client.list.return_value = common.Paginated(data) @mock.patch.object(utils, 'find_resource') def test_user_list_defaults(self, mock_find): args = ['my_instance'] mock_find.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, []) columns, data = self.cmd.take_action(parsed_args) self.user_client.list.assert_called_once_with(*args) self.assertEqual(self.columns, columns) self.assertEqual([self.values], data) class TestUserShow(TestUsers): values = ([{'name': 'db1'}], '%', 'harry') def setUp(self): super(TestUserShow, self).setUp() self.cmd = database_users.ShowDatabaseUser(self.app, None) self.data = self.fake_users.get_instances_1234_users_harry() self.user_client.get.return_value = self.data self.columns = ( 'databases', 'host', 'name', ) @mock.patch.object(utils, 'find_resource') def test_user_show_defaults(self, mock_find): args = ['my_instance', 'harry'] mock_find.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, []) columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) self.assertEqual(self.values, data) class TestDatabaseUserDelete(TestUsers): def setUp(self): super(TestDatabaseUserDelete, self).setUp() self.cmd = database_users.DeleteDatabaseUser(self.app, None) @mock.patch.object(utils, 'find_resource') def test_user_delete(self, mock_find): args = ['userinstance', 'user1', '--host', '1.1.1.1'] mock_find.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, []) result = self.cmd.take_action(parsed_args) self.user_client.delete.assert_called_with('userinstance', 'user1', '1.1.1.1') self.assertIsNone(result) @mock.patch.object(utils, 'find_resource') def test_user_delete_without_host(self, mock_find): args = ['userinstance2', 'user1'] mock_find.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, []) result = self.cmd.take_action(parsed_args) self.user_client.delete.assert_called_with('userinstance2', 'user1', None) self.assertIsNone(result) @mock.patch.object(utils, 'find_resource') def test_user_delete_with_exception(self, mock_find): args = ['userfakeinstance', 'db1', '--host', '1.1.1.1'] parsed_args = self.check_parser(self.cmd, args, []) mock_find.side_effect = exceptions.CommandError self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) class TestDatabaseUserGrantAccess(TestUsers): def setUp(self): super(TestDatabaseUserGrantAccess, self).setUp() self.cmd = database_users.GrantDatabaseUserAccess(self.app, None) @mock.patch.object(utils, 'find_resource') def test_user_grant_access(self, mock_find): args = ['userinstance', 'user1', '--host', '1.1.1.1', 'db1'] verifylist = [ ('instance', 'userinstance'), ('name', 'user1'), ('host', '1.1.1.1'), ('databases', ['db1']), ] mock_find.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, verifylist) result = self.cmd.take_action(parsed_args) self.assertIsNone(result) class TestDatabaseUserRevokeAccess(TestUsers): def setUp(self): super(TestDatabaseUserRevokeAccess, self).setUp() self.cmd = database_users.RevokeDatabaseUserAccess(self.app, None) @mock.patch.object(utils, 'find_resource') def test_user_grant_access(self, mock_find): args = ['userinstance', 'user1', '--host', '1.1.1.1', 'db1'] verifylist = [ ('instance', 'userinstance'), ('name', 'user1'), ('host', '1.1.1.1'), ('databases', 'db1'), ] mock_find.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, verifylist) result = self.cmd.take_action(parsed_args) self.assertIsNone(result) class TestDatabaseUserShowAccess(TestUsers): columns = database_users.ShowDatabaseUserAccess.columns values = [('db_1',), ('db_2',)] def setUp(self): super(TestDatabaseUserShowAccess, self).setUp() self.cmd = database_users.ShowDatabaseUserAccess(self.app, None) self.data = self.fake_users.get_instances_1234_users_access() self.user_client.list_access.return_value = self.data @mock.patch.object(utils, 'find_resource') def test_user_grant_access(self, mock_find): args = ['userinstance', 'user1', '--host', '1.1.1.1'] verifylist = [ ('instance', 'userinstance'), ('name', 'user1'), ('host', '1.1.1.1'), ] mock_find.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, verifylist) columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) self.assertEqual(self.values, data) class TestDatabaseUserUpdateAttributes(TestUsers): def setUp(self): super(TestDatabaseUserUpdateAttributes, self).setUp() self.cmd = database_users.UpdateDatabaseUserAttributes(self.app, None) @mock.patch.object(utils, 'find_resource') def test_user__update_attributes(self, mock_find): args = ['userinstance', 'user1', '--host', '1.1.1.1', '--new_name', 'user2', '--new_password', '111111', '--new_host', '1.1.1.2'] verifylist = [ ('instance', 'userinstance'), ('name', 'user1'), ('host', '1.1.1.1'), ('new_name', 'user2'), ('new_password', '111111'), ('new_host', '1.1.1.2'), ] mock_find.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, verifylist) result = self.cmd.take_action(parsed_args) self.assertIsNone(result) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/osc/v1/test_databases.py0000664000175000017500000001013700000000000025601 0ustar00zuulzuul00000000000000# 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 unittest import mock from osc_lib import exceptions from osc_lib import utils from troveclient import common from troveclient.osc.v1 import databases from troveclient.tests.osc.v1 import fakes class TestDatabases(fakes.TestDatabasev1): fake_databases = fakes.FakeDatabases() def setUp(self): super(TestDatabases, self).setUp() self.mock_client = self.app.client_manager.database self.database_client = self.app.client_manager.database.databases class TestDatabaseCreate(TestDatabases): def setUp(self): super(TestDatabaseCreate, self).setUp() self.cmd = databases.CreateDatabase(self.app, None) @mock.patch.object(utils, 'find_resource') def test_database_create(self, mock_find): args = ['instance1', 'db1'] mock_find.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, []) result = self.cmd.take_action(parsed_args) self.database_client.create.assert_called_with('instance1', [{'name': 'db1'}]) self.assertIsNone(result) @mock.patch.object(utils, 'find_resource') def test_database_create_with_optional_args(self, mock_find): args = ['instance2', 'db2', '--character_set', 'utf8', '--collate', 'utf8_general_ci'] mock_find.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, []) database_dict = {'name': 'db2', 'collate': 'utf8_general_ci', 'character_set': 'utf8'} result = self.cmd.take_action(parsed_args) self.database_client.create.assert_called_with('instance2', [database_dict]) self.assertIsNone(result) class TestDatabaseList(TestDatabases): columns = databases.ListDatabases.columns values = ('fakedb1',) def setUp(self): super(TestDatabaseList, self).setUp() self.cmd = databases.ListDatabases(self.app, None) data = [self.fake_databases.get_databases_1()] self.database_client.list.return_value = common.Paginated(data) @mock.patch.object(utils, 'find_resource') def test_database_list_defaults(self, mock_find): args = ['my_instance'] mock_find.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, []) columns, data = self.cmd.take_action(parsed_args) self.database_client.list.assert_called_once_with(args[0]) self.assertEqual(self.columns, columns) self.assertEqual([tuple(self.values)], data) class TestDatabaseDelete(TestDatabases): def setUp(self): super(TestDatabaseDelete, self).setUp() self.cmd = databases.DeleteDatabase(self.app, None) @mock.patch.object(utils, 'find_resource') def test_database_delete(self, mock_find): args = ['instance1', 'db1'] mock_find.return_value = args[0] parsed_args = self.check_parser(self.cmd, args, []) result = self.cmd.take_action(parsed_args) self.database_client.delete.assert_called_with('instance1', 'db1') self.assertIsNone(result) @mock.patch.object(utils, 'find_resource') def test_database_delete_with_exception(self, mock_find): args = ['fakeinstance', 'db1'] parsed_args = self.check_parser(self.cmd, args, []) mock_find.side_effect = exceptions.CommandError self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/osc/v1/test_datastores.py0000664000175000017500000001647500000000000026036 0ustar00zuulzuul00000000000000# 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 oslo_utils import uuidutils from troveclient import common from troveclient import exceptions from troveclient.osc.v1 import datastores from troveclient.tests.osc.v1 import fakes class TestDatastores(fakes.TestDatabasev1): fake_datastores = fakes.FakeDatastores() def setUp(self): super(TestDatastores, self).setUp() self.datastore_client = self.app.client_manager.database.datastores self.datastore_version_client =\ self.app.client_manager.database.datastore_versions self.dsversion_mgmt_client =\ self.app.client_manager.database.mgmt_ds_versions class TestDatastoreList(TestDatastores): columns = datastores.ListDatastores.columns values = ('d-123', 'mysql') def setUp(self): super(TestDatastoreList, self).setUp() self.cmd = datastores.ListDatastores(self.app, None) data = [self.fake_datastores.get_datastores_d_123()] self.datastore_client.list.return_value = common.Paginated(data) def test_datastore_list_defaults(self): parsed_args = self.check_parser(self.cmd, [], []) columns, data = self.cmd.take_action(parsed_args) self.datastore_client.list.assert_called_once_with() self.assertEqual(self.columns, columns) self.assertEqual([self.values], data) class TestDatastoreShow(TestDatastores): values = ('5.6', 'd-123', 'mysql', '5.6 (v-56)') def setUp(self): super(TestDatastoreShow, self).setUp() self.cmd = datastores.ShowDatastore(self.app, None) self.data = self.fake_datastores.get_datastores_d_123() self.datastore_client.get.return_value = self.data self.columns = ( 'default_version', 'id', 'name', 'versions (id)', ) def test_show(self): args = ['mysql'] parsed_args = self.check_parser(self.cmd, args, []) columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) self.assertEqual(self.values, data) class TestDeleteDatastore(TestDatastores): def setUp(self): super(TestDeleteDatastore, self).setUp() self.cmd = datastores.DeleteDatastore(self.app, None) def test_delete_datastore(self): ds_id = uuidutils.generate_uuid() args = [ds_id] parsed_args = self.check_parser(self.cmd, args, []) self.cmd.take_action(parsed_args) self.datastore_client.delete.assert_called_once_with(ds_id) class TestDatastoreVersionList(TestDatastores): columns = datastores.ListDatastoreVersions.columns values = ('v-56', '5.6', '') def setUp(self): super(TestDatastoreVersionList, self).setUp() self.cmd = datastores.ListDatastoreVersions(self.app, None) self.data = [self.fake_datastores.get_datastores_d_123_versions()] self.datastore_version_client.list.return_value =\ common.Paginated(self.data) def test_datastore_version_list_defaults(self): args = ['mysql'] parsed_args = self.check_parser(self.cmd, args, []) columns, data = self.cmd.take_action(parsed_args) self.datastore_version_client.list.assert_called_once_with(args[0]) self.assertEqual(self.columns, columns) self.assertEqual([self.values], data) class TestDatastoreVersionShow(TestDatastores): values = ('v-56', '5.6') def setUp(self): super(TestDatastoreVersionShow, self).setUp() self.cmd = datastores.ShowDatastoreVersion(self.app, None) self.data = self.fake_datastores.get_datastores_d_123_versions() self.datastore_version_client.get.return_value = self.data self.columns = ( 'id', 'name', ) def test_datastore_version_show_defaults(self): args = ['5.6', '--datastore', 'mysql'] verifylist = [ ('datastore_version', '5.6'), ('datastore', 'mysql'), ] parsed_args = self.check_parser(self.cmd, args, verifylist) columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) self.assertEqual(self.values, data) def test_datastore_version_show_with_version_id_exception(self): args = [ 'v-56', ] verifylist = [ ('datastore_version', 'v-56'), ] parsed_args = self.check_parser(self.cmd, args, verifylist) self.assertRaises(exceptions.NoUniqueMatch, self.cmd.take_action, parsed_args) class TestDeleteDatastoreVersion(TestDatastores): def setUp(self): super(TestDeleteDatastoreVersion, self).setUp() self.cmd = datastores.DeleteDatastoreVersion(self.app, None) def test_delete_datastore_version(self): dsversion_id = uuidutils.generate_uuid() args = [dsversion_id] parsed_args = self.check_parser(self.cmd, args, []) self.cmd.take_action(parsed_args) self.dsversion_mgmt_client.delete.assert_called_once_with(dsversion_id) class TestCreateDatastoreVersion(TestDatastores): def setUp(self): super(TestCreateDatastoreVersion, self).setUp() self.cmd = datastores.CreateDatastoreVersion(self.app, None) def test_create_datastore_version(self): image_id = uuidutils.generate_uuid() args = ['new_name', 'ds_name', 'ds_manager', image_id, '--active', '--default', '--image-tags', 'trove,mysql', '--registry-ext', 'registry-ext', '--repl-strategy', 'repl_strategy'] parsed_args = self.check_parser(self.cmd, args, []) self.cmd.take_action(parsed_args) self.dsversion_mgmt_client.create.assert_called_once_with( 'new_name', 'ds_name', 'ds_manager', image_id, active='true', default='true', image_tags=['trove', 'mysql'], registry_ext="registry-ext", repl_strategy="repl_strategy", version=None) class TestUpdateDatastoreVersion(TestDatastores): def setUp(self): super(TestUpdateDatastoreVersion, self).setUp() self.cmd = datastores.UpdateDatastoreVersion(self.app, None) def test_update_datastore_version(self): version_id = uuidutils.generate_uuid() args = [version_id, '--registry-ext', 'registry-ext', '--repl-strategy', 'repl_strategy', '--image-tags', 'trove,mysql', '--enable', '--non-default'] parsed_args = self.check_parser(self.cmd, args, []) self.cmd.take_action(parsed_args) self.dsversion_mgmt_client.edit.assert_called_once_with( version_id, datastore_manager=None, image=None, active='true', default='false', image_tags=['trove', 'mysql'], registry_ext="registry-ext", repl_strategy="repl_strategy", name=None) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/test_accounts.py0000664000175000017500000000707100000000000024362 0ustar00zuulzuul00000000000000# 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 testtools from unittest import mock from troveclient import base from troveclient.v1 import accounts """ Unit tests for accounts.py """ class AccountTest(testtools.TestCase): def setUp(self): super(AccountTest, self).setUp() self.orig__init = accounts.Account.__init__ accounts.Account.__init__ = mock.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(testtools.TestCase): def setUp(self): super(AccountsTest, self).setUp() self.orig__init = accounts.Accounts.__init__ accounts.Accounts.__init__ = mock.Mock(return_value=None) self.accounts = accounts.Accounts() self.accounts.api = mock.Mock() self.accounts.api.client = mock.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.Mock(side_effect=side_effect_func) key_ = 'key' body_ = {key_: "test-value"} self.accounts.api.client.get = mock.Mock(return_value=('resp', body_)) self.assertEqual("test-value", self.accounts._list('url', key_)) self.accounts.api.client.get = mock.Mock(return_value=('resp', None)) self.assertRaises(Exception, self.accounts._list, 'url', None) def test_index(self): resp = mock.Mock() resp.status_code = 400 body = {"Accounts": {}} self.accounts.api.client.get = mock.Mock(return_value=(resp, body)) self.assertRaises(Exception, self.accounts.index) resp.status_code = 200 self.assertIsInstance(self.accounts.index(), base.Resource) self.accounts.api.client.get = mock.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.Mock() account_.name = "test-account" self.accounts._list = mock.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.Mock() account_.name = "account-name" self.assertEqual("account-name", accounts.Accounts._get_account_name(account_)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/test_apiclient_exceptions.py0000664000175000017500000000360000000000000026746 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json from troveclient.apiclient import exceptions from troveclient.tests import utils as test_utils class ExceptionsTestCase(test_utils.TestCase): def _test_from_response(self, body): data = { 'status_code': 503, 'headers': { 'Content-Type': 'application/json', 'x-compute-request-id': ( 'req-65d6443c-5910-4eb4-b48a-e69849c26836'), }, 'text': json.dumps(body) } response = test_utils.TestResponse(data) fake_url = 'http://localhost:8779/v1.0/fake/instances' error = exceptions.from_response(response, 'GET', fake_url) self.assertIsInstance(error, exceptions.ServiceUnavailable) def test_from_response_webob_pre_1_6_0(self): # Tests error responses before webob 1.6.0 where the error details # are nested in the response body. body = { 'serviceUnavailable': { 'message': 'Fake message.', 'code': 503 } } self._test_from_response(body) def test_from_response_webob_post_1_6_0(self): # Tests error responses from webob 1.6.0 where the error details # are in the response body. body = {'message': 'Fake message.', 'code': 503} self._test_from_response(body) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/test_backups.py0000664000175000017500000002557600000000000024205 0ustar00zuulzuul00000000000000# Copyright 2013 OpenStack Foundation # Copyright 2013 Rackspace Hosting # # 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 unittest import mock from unittest.mock import patch import uuid import testtools from troveclient.v1 import backups """ Unit tests for backups.py """ class BackupTest(testtools.TestCase): def setUp(self): super(BackupTest, self).setUp() self.backup_id = str(uuid.uuid4()) self.info = {'name': 'my backup', 'id': self.backup_id} self.api = mock.Mock() self.manager = backups.Backups(self.api) self.backup = backups.Backup(self.manager, self.info) def tearDown(self): super(BackupTest, self).tearDown() def test___repr__(self): self.assertEqual('', repr(self.backup)) class BackupManagerTest(testtools.TestCase): def setUp(self): super(BackupManagerTest, self).setUp() self.backups = backups.Backups(mock.Mock()) self.instance_with_id = mock.Mock() self.instance_with_id.id = 215 def tearDown(self): super(BackupManagerTest, self).tearDown() def test_create(self): create_mock = mock.Mock() self.backups._create = create_mock args = {'name': 'test_backup', 'instance': '1', 'incremental': False} body = {'backup': args} self.backups.create(**args) create_mock.assert_called_with('/backups', body, 'backup') def test_create_description(self): create_mock = mock.Mock() self.backups._create = create_mock args = {'name': 'test_backup', 'instance': '1', 'description': 'foo', 'incremental': False} body = {'backup': args} self.backups.create(**args) create_mock.assert_called_with('/backups', body, 'backup') def test_create_with_instance_obj(self): create_mock = mock.Mock() self.backups._create = create_mock args = {'name': 'test_backup', 'instance': self.instance_with_id.id, 'incremental': False} body = {'backup': args} self.backups.create('test_backup', self.instance_with_id) create_mock.assert_called_with('/backups', body, 'backup') def test_create_incremental(self): create_mock = mock.Mock() self.backups._create = create_mock args = {'name': 'test_backup', 'instance': '1', 'parent_id': 'foo', 'incremental': False} body = {'backup': args} self.backups.create(**args) create_mock.assert_called_with('/backups', body, 'backup') def test_create_incremental_2(self): create_mock = mock.Mock() self.backups._create = create_mock args = {'name': 'test_backup', 'instance': '1', 'incremental': True} body = {'backup': args} self.backups.create(**args) create_mock.assert_called_with('/backups', body, 'backup') def test_list(self): page_mock = mock.Mock() self.backups._paginated = page_mock limit = "test-limit" marker = "test-marker" self.backups.list(limit, marker) page_mock.assert_called_with("/backups", "backups", limit, marker, {}) def test_list_by_datastore(self): page_mock = mock.Mock() self.backups._paginated = page_mock limit = "test-limit" marker = "test-marker" datastore = "test-mysql" self.backups.list(limit, marker, datastore) page_mock.assert_called_with("/backups", "backups", limit, marker, {'datastore': datastore}) def test_list_by_instance(self): page_mock = mock.Mock() self.backups._paginated = page_mock instance_id = "fake_instance" self.backups.list(instance_id=instance_id) page_mock.assert_called_with("/backups", "backups", None, None, {'instance_id': instance_id}) def test_list_by_all_projects(self): page_mock = mock.Mock() self.backups._paginated = page_mock all_projects = True self.backups.list(all_projects=all_projects) page_mock.assert_called_with("/backups", "backups", None, None, {'all_projects': all_projects}) def test_get(self): get_mock = mock.Mock() self.backups._get = get_mock self.backups.get(1) get_mock.assert_called_with('/backups/1', 'backup') def test_delete(self): resp = mock.Mock() resp.status_code = 200 delete_mock = mock.Mock(return_value=(resp, None)) self.backups.api.client.delete = delete_mock self.backups.delete('backup1') delete_mock.assert_called_with('/backups/backup1') def test_delete_500(self): resp = mock.Mock() resp.status_code = 500 self.backups.api.client.delete = mock.Mock(return_value=(resp, None)) self.assertRaises(Exception, self.backups.delete, 'backup1') def test_delete_422(self): resp = mock.Mock() resp.status_code = 422 self.backups.api.client.delete = mock.Mock(return_value=(resp, None)) self.assertRaises(Exception, self.backups.delete, 'backup1') @patch('troveclient.v1.backups.mistral_client') def test_auth_mistral_client(self, mistral_client): with patch.object(self.backups.api.client, 'auth') as auth: self.backups._get_mistral_client() mistral_client.assert_called_with( auth_url=auth.auth_url, username=auth._username, api_key=auth._password, project_name=auth._project_name) def test_build_schedule(self): cron_trigger = mock.Mock() wf_input = {'name': 'foo', 'instance': 'myinst', 'parent_id': None} sched = self.backups._build_schedule(cron_trigger, wf_input) self.assertEqual(cron_trigger.name, sched.id) self.assertEqual(wf_input['name'], sched.name) self.assertEqual(wf_input['instance'], sched.instance) self.assertEqual(cron_trigger.workflow_input, sched.input) def test_schedule_create(self): instance = mock.Mock() pattern = mock.Mock() name = 'myback' def make_cron_trigger(name, wf, workflow_input=None, pattern=None): return mock.Mock(name=name, pattern=pattern, workflow_input=workflow_input) cron_triggers = mock.Mock() cron_triggers.create = mock.Mock(side_effect=make_cron_trigger) mistral_client = mock.Mock(cron_triggers=cron_triggers) sched = self.backups.schedule_create(instance, pattern, name, mistral_client=mistral_client) self.assertEqual(pattern, sched.pattern) self.assertEqual(name, sched.name) self.assertEqual(instance.id, sched.instance) def test_schedule_list(self): instance = mock.Mock(id='the_uuid') backup_name = "wf2" test_input = [('wf1', 'foo'), (backup_name, instance.id)] cron_triggers = mock.Mock() cron_triggers.list = mock.Mock( return_value=[ mock.Mock(workflow_input='{"name": "%s", "instance": "%s"}' % (name, inst), name=name) for name, inst in test_input ]) mistral_client = mock.Mock(cron_triggers=cron_triggers) sched_list = self.backups.schedule_list(instance, mistral_client) self.assertEqual(1, len(sched_list)) the_sched = sched_list.pop() self.assertEqual(backup_name, the_sched.name) self.assertEqual(instance.id, the_sched.instance) def test_schedule_show(self): instance = mock.Mock(id='the_uuid') backup_name = "myback" cron_triggers = mock.Mock() cron_triggers.get = mock.Mock( return_value=mock.Mock( name=backup_name, workflow_input='{"name": "%s", "instance": "%s"}' % (backup_name, instance.id))) mistral_client = mock.Mock(cron_triggers=cron_triggers) sched = self.backups.schedule_show("dummy", mistral_client) self.assertEqual(backup_name, sched.name) self.assertEqual(instance.id, sched.instance) def test_schedule_delete(self): cron_triggers = mock.Mock() cron_triggers.delete = mock.Mock() mistral_client = mock.Mock(cron_triggers=cron_triggers) self.backups.schedule_delete("dummy", mistral_client) cron_triggers.delete.assert_called() def test_execution_list(self): instance = mock.Mock(id='the_uuid') wf_input = '{"name": "wf2", "instance": "%s"}' % instance.id wf_name = self.backups.backup_create_workflow execution_list_result = [ [mock.Mock(id=1, input=wf_input, workflow_name=wf_name, to_dict=mock.Mock(return_value={'id': 1})), mock.Mock(id=2, input="{}", workflow_name=wf_name)], [mock.Mock(id=3, input=wf_input, workflow_name=wf_name, to_dict=mock.Mock(return_value={'id': 3})), mock.Mock(id=4, input="{}", workflow_name=wf_name)], [mock.Mock(id=5, input=wf_input, workflow_name=wf_name, to_dict=mock.Mock(return_value={'id': 5})), mock.Mock(id=6, input="{}", workflow_name=wf_name)], [mock.Mock(id=7, input=wf_input, workflow_name="bar"), mock.Mock(id=8, input="{}", workflow_name=wf_name)] ] cron_triggers = mock.Mock() cron_triggers.get = mock.Mock( return_value=mock.Mock(workflow_name=wf_name, workflow_input=wf_input)) mistral_executions = mock.Mock() mistral_executions.list = mock.Mock(side_effect=execution_list_result) mistral_client = mock.Mock(cron_triggers=cron_triggers, executions=mistral_executions) el = self.backups.execution_list("dummy", mistral_client, limit=2) self.assertEqual(2, len(el)) el = self.backups.execution_list("dummy", mistral_client, limit=2) self.assertEqual(1, len(el)) the_exec = el.pop() self.assertEqual(5, the_exec.id) def test_execution_delete(self): mistral_executions = mock.Mock() mistral_executions.delete = mock.Mock() mistral_client = mock.Mock(executions=mistral_executions) self.backups.execution_delete("dummy", mistral_client) mistral_executions.delete.assert_called() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/test_base.py0000664000175000017500000004202200000000000023450 0ustar00zuulzuul00000000000000# 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 unittest import mock import testtools from troveclient.apiclient import exceptions from troveclient import base from troveclient import common 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(testtools.TestCase): def test_getid(self): obj = "test" r = base.getid(obj) self.assertEqual(obj, r) test_id = "test_id" obj = mock.Mock() obj.id = test_id r = base.getid(obj) self.assertEqual(test_id, r) class ManagerTest(testtools.TestCase): def setUp(self): super(ManagerTest, self).setUp() self.orig__init = base.Manager.__init__ base.Manager.__init__ = mock.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.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.Mock with manager.completion_cache(cache_type, obj_class, mode): pass os.makedirs = mock.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") manager._mock_cache = mock.Mock() manager._mock_cache.write = mock.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.Mock() manager.api.client = mock.Mock() def side_effect_func(self, body, loaded=True): return body manager.resource_class = mock.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.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.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.Mock() manager.api.client = mock.Mock() response_key = "response_key" data_ = "test-data" body_ = {response_key: data_} url_ = "test_url_post" manager.api.client.post = mock.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 mockl = mock.Mock() mockl.side_effect = completion_cache_mock manager.completion_cache = mockl manager.resource_class = mock.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.Mock() manager.api.client = mock.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.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.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.Mock() self.manager.api.client = mock.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.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.Mock( return_value=(self.url_g, self.body_g) ) mockl = mock.Mock() mockl.side_effect = completion_cache_mock self.manager.completion_cache = mockl def tearDown(self): super(ManagerListTest, self).tearDown() def test_list_with_body_none(self): body = None li = self.manager._list("url", self.response_key, obj_class, body) self.assertEqual(len(self.data_g), len(li)) for i in range(0, len(li)): self.assertEqual(self.data_g[i], li[i]) def test_list_body_not_none(self): body = "something" li = self.manager._list("url", self.response_key, obj_class, body) self.assertEqual(len(self.data_p), len(li)) for i in range(0, len(li)): self.assertEqual(self.data_p[i], li[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.Mock(return_value=(url_, body_)) li = self.manager._list("url", self.response_key, obj_class, "something") data = data_["values"] self.assertEqual(len(data), len(li)) for i in range(0, len(li)): self.assertEqual(data[i], li[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.Mock(return_value=(url_, body_)) li = self.manager._list("url", self.response_key, obj_class, "something") self.assertEqual(len(data_), len(li)) class MangerPaginationTests(ManagerTest): def setUp(self): super(MangerPaginationTests, self).setUp() self.manager = base.Manager() self.manager.api = mock.Mock() self.manager.api.client = mock.Mock() self.manager.resource_class = base.Resource self.response_key = "response_key" self.data = [{"foo": "p1"}, {"foo": "p2"}] self.next_data = [{"foo": "p3"}, {"foo": "p4"}] self.marker = 'test-marker' self.limit = '20' self.url = "http://test_url" self.next_url = '%s?marker=%s&limit=%s' % (self.url, self.marker, self.limit) self.links = [{'href': self.next_url, 'rel': 'next'}] self.body = { self.response_key: self.data, 'links': self.links } self.next_body = {self.response_key: self.next_data} def side_effect(url): if url == self.url: return None, self.body # In python 3 the order in the dictionary is not constant # between runs. So we cant rely on the URL params to be # in the same order if ('marker=%s' % self.marker in url and 'limit=%s' % self.limit in url): self.next_url = url return None, self.next_body self.manager.api.client.get = mock.Mock(side_effect=side_effect) def tearDown(self): super(MangerPaginationTests, self).tearDown() def test_pagination(self): resp = self.manager._paginated(self.url, self.response_key) self.manager.api.client.get.assert_called_with(self.url) self.assertEqual('p1', resp[0].foo) self.assertEqual('p2', resp[1].foo) self.assertEqual(self.marker, resp.next) self.assertEqual(self.links, resp.links) self.assertIsInstance(resp, common.Paginated) def test_pagination_next(self): resp = self.manager._paginated(self.url, self.response_key, limit=self.limit, marker=self.marker) self.manager.api.client.get.assert_called_with(self.next_url) self.assertEqual('p3', resp[0].foo) self.assertEqual('p4', resp[1].foo) self.assertIsNone(resp.next) self.assertEqual([], resp.links) self.assertIsInstance(resp, common.Paginated) def test_pagination_error(self): self.manager.api.client.get = mock.Mock(return_value=(None, None)) self.assertRaises(Exception, self.manager._paginated, self.url, self.response_key) 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(testtools.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(self.manager.get('1234'), output) def test_find_by_str_id(self): output = utils.find_resource(self.manager, '1234') self.assertEqual(self.manager.get('1234'), output) def test_find_by_uuid(self): output = utils.find_resource(self.manager, UUID) self.assertEqual(self.manager.get(UUID), output) def test_find_by_str_name(self): output = utils.find_resource(self.manager, 'entity_one') self.assertEqual(self.manager.get('1234'), output) def test_find_by_str_displayname(self): output = utils.find_resource(self.manager, 'entity_three') self.assertEqual(self.manager.get('4242'), output) def test_find_by_int_name(self): output = utils.find_resource(self.manager, 9876) self.assertEqual(self.manager.get('5678'), output) class ResourceTest(testtools.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.Mock() manager.write_to_completion_cache = mock.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.assertIsNone(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.Mock() manager.write_to_completion_cache = mock.Mock(return_value=None) info_ = {"name": "test-human-id"} robj = base.Resource(manager, info_) self.assertIsNone(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.Mock(return_value=None) robj = base.Resource() robj._loaded = False 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.Mock() manager.get = None robj.manager = object() robj._get() manager = mock.Mock() robj.manager = mock.Mock() robj.id = "id" new = mock.Mock() new._info = {"name": "test-human-id", "test_attr": 5} robj.manager.get = mock.Mock(return_value=new) robj._get() self.assertEqual("test-human-id", robj.name) self.assertEqual(5, robj.test_attr) def test___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.assertFalse(robj.__eq__(other)) robj.id = "rid" other.id = "oid" self.assertFalse(robj.__eq__(other)) other.id = "rid" self.assertTrue(robj.__eq__(other)) # not instance of the same class other = mock.Mock() self.assertEqual(robj.__eq__(other), NotImplemented) 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) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/test_client.py0000664000175000017500000005514700000000000024030 0ustar00zuulzuul00000000000000# 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 unittest import mock import fixtures from keystoneauth1 import adapter import logging import requests import testtools from troveclient.apiclient import client from troveclient import client as other_client from troveclient import exceptions from troveclient import service_catalog import troveclient.v1.client class ClientTest(testtools.TestCase): def test_get_client_class_v1(self): version_map = other_client.get_version_map() output = client.BaseClient.get_class('database', '1.0', version_map) self.assertEqual(troveclient.v1.client.Client, output) def test_get_client_class_unknown(self): version_map = other_client.get_version_map() self.assertRaises(exceptions.UnsupportedVersion, client.BaseClient.get_class, 'database', '0', version_map) def test_client_with_auth_system_without_auth_plugin(self): self.assertRaisesRegex( exceptions.AuthSystemNotFound, "AuthSystemNotFound: 'something'", other_client.HTTPClient, user='user', password='password', projectid='project', timeout=2, auth_url="http://www.blah.com", auth_system='something') def test_client_with_auth_system_without_endpoint(self): auth_plugin = mock.Mock() auth_plugin.get_auth_url = mock.Mock(return_value=None) self.assertRaises( exceptions.EndpointNotFound, other_client.HTTPClient, user='user', password='password', projectid='project', timeout=2, auth_plugin=auth_plugin, auth_url=None, auth_system='something') def test_client_with_timeout(self): instance = other_client.HTTPClient(user='user', password='password', projectid='project', timeout=2, auth_url="http://www.blah.com", insecure=True) self.assertEqual(2, instance.timeout) mock_request = mock.Mock() mock_request.return_value = requests.Response() mock_request.return_value.status_code = 200 mock_request.return_value.headers = { 'x-server-management-url': 'blah.com', 'x-auth-token': 'blah', } with mock.patch('requests.request', mock_request): instance.authenticate() requests.request.assert_called_with( mock.ANY, mock.ANY, timeout=2, headers=mock.ANY, verify=mock.ANY) def test_client_unauthorized(self): instance = other_client.HTTPClient(user='user', password='password', projectid='project', timeout=2, auth_url="http://www.blah.com", cacert=mock.Mock()) instance.auth_token = 'foobar' instance.management_url = 'http://example.com' instance.get_service_url = mock.Mock(return_value='http://example.com') instance.version = 'v2.0' mock_request = mock.Mock() mock_request.side_effect = other_client.exceptions.Unauthorized(401) with mock.patch('requests.request', mock_request): self.assertRaises( exceptions.Unauthorized, instance.get, '/instances') def test_client_bad_request(self): instance = other_client.HTTPClient(user='user', password='password', projectid='project', timeout=2, auth_url="http://www.blah.com") instance.auth_token = 'foobar' instance.management_url = 'http://example.com' instance.get_service_url = mock.Mock(return_value='http://example.com') instance.version = 'v2.0' mock_request = mock.Mock() mock_request.side_effect = other_client.exceptions.BadRequest() with mock.patch('requests.request', mock_request): self.assertRaises( exceptions.BadRequest, instance.get, '/instances') def test_client_with_client_exception(self): instance = other_client.HTTPClient(user='user', password='password', projectid='project', timeout=2, auth_url="http://www.blah.com", retries=2) instance.auth_token = 'foobar' instance.management_url = 'http://example.com' instance.get_service_url = mock.Mock(return_value='http://example.com') instance.version = 'v2.0' mock_request = mock.Mock() mock_request.side_effect = other_client.exceptions.ClientException() type(mock_request.side_effect).code = mock.PropertyMock( side_effect=[501, 111]) with mock.patch('requests.request', mock_request): self.assertRaises( exceptions.ClientException, instance.get, '/instances') def test_client_connection_error(self): instance = other_client.HTTPClient(user='user', password='password', projectid='project', timeout=2, auth_url="http://www.blah.com", retries=2) instance.auth_token = 'foobar' instance.management_url = 'http://example.com' instance.get_service_url = mock.Mock(return_value='http://example.com') instance.version = 'v2.0' mock_request = mock.Mock() mock_request.side_effect = requests.exceptions.ConnectionError( 'connection refused') with mock.patch('requests.request', mock_request): self.assertRaisesRegex( exceptions.ClientException, 'Unable to establish connection: connection refused', instance.get, '/instances') @mock.patch.object(other_client.HTTPClient, 'request', return_value=(200, "{'versions':[]}")) def _check_version_url(self, management_url, version_url, mock_request): projectid = '25e469aa1848471b875e68cde6531bc5' instance = other_client.HTTPClient(user='user', password='password', projectid=projectid, auth_url="http://www.blah.com") instance.auth_token = 'foobar' instance.management_url = management_url % projectid mock_get_service_url = mock.Mock(return_value=instance.management_url) instance.get_service_url = mock_get_service_url instance.version = 'v2.0' # If passing None as the part of url, a client accesses the url which # doesn't include "v2/" for getting API version info. instance.get('') mock_request.assert_called_once_with(instance.management_url, 'GET', headers=mock.ANY) mock_request.reset_mock() # Otherwise, a client accesses the url which includes "v2/". instance.get('/instances') url = instance.management_url + '/instances' mock_request.assert_called_once_with(url, 'GET', headers=mock.ANY) def test_client_version_url(self): self._check_version_url('http://foo.com/v1/%s', 'http://foo.com/') def test_client_version_url_with_tenant_name(self): self._check_version_url('http://foo.com/trove/v1/%s', 'http://foo.com/trove/') def test_log_req(self): logger = self.useFixture( fixtures.FakeLogger( name='troveclient.client', format="%(message)s", level=logging.DEBUG, nuke_handlers=True ) ) cs = other_client.HTTPClient(user='user', password='password', projectid=None, auth_url="http://www.blah.com", http_log_debug=True) cs.http_log_req(('/foo', 'GET'), {'headers': {}}) cs.http_log_req(('/foo', 'GET'), {'headers': {'X-Auth-Token': 'totally_bogus'}}) cs.http_log_req( ('/foo', 'GET'), {'headers': {}, 'data': '{"auth": {"passwordCredentials": ' '{"password": "password"}}}'}) output = logger.output.split('\n') self.assertIn("REQ: curl -i /foo -X GET", output) self.assertIn( "REQ: curl -i /foo -X GET -H " '"X-Auth-Token: totally_bogus"', output) self.assertIn( "REQ: curl -i /foo -X GET -d " '\'{"auth": {"passwordCredentials": {"password":' ' "password"}}}\'', output) @mock.patch.object(service_catalog, 'ServiceCatalog') def test_client_auth_token(self, mock_service_catalog): auth_url = 'http://www.blah.com' proxy_token = 'foobar' proxy_tenant_id = 'user' mock_service_catalog.return_value.get_token = mock.Mock( return_value=proxy_token) instance = other_client.HTTPClient(proxy_token=proxy_token, proxy_tenant_id=proxy_tenant_id, user=None, password=None, tenant_id=proxy_tenant_id, projectid=None, timeout=2, auth_url=auth_url) instance.management_url = 'http://example.com' instance.get_service_url = mock.Mock(return_value='http://example.com') instance.version = 'v2.0' mock_request = mock.Mock() mock_request.return_value = requests.Response() mock_request.return_value.status_code = 200 mock_request.return_value.headers = { 'x-server-management-url': 'blah.com', 'x-auth-token': 'blah', } with mock.patch('requests.request', mock_request): instance.authenticate() mock_request.assert_called_with( 'GET', auth_url + '/tokens/foobar?belongsTo=user', headers={'User-Agent': 'python-troveclient', 'Accept': 'application/json', 'X-Auth-Token': proxy_token}, timeout=2, verify=True) @mock.patch.object(service_catalog, 'ServiceCatalog', side_effect=KeyError) def test_client_auth_token_authorization_failure(self, mock_service_catalog): auth_url = 'http://www.blah.com' proxy_token = 'foobar' proxy_tenant_id = 'user' mock_service_catalog.return_value.get_token = mock.Mock( return_value=proxy_token) instance = other_client.HTTPClient(proxy_token=proxy_token, proxy_tenant_id=proxy_tenant_id, user=None, password=None, tenant_id=proxy_tenant_id, projectid=None, timeout=2, auth_url=auth_url) instance.management_url = 'http://example.com' instance.get_service_url = mock.Mock(return_value='http://example.com') instance.version = 'v2.0' mock_request = mock.Mock() mock_request.return_value = requests.Response() mock_request.return_value.status_code = 200 mock_request.return_value.headers = { 'x-server-management-url': 'blah.com', 'x-auth-token': 'blah', } with mock.patch('requests.request', mock_request): self.assertRaises(exceptions.AuthorizationFailure, instance.authenticate) @mock.patch.object(service_catalog, 'ServiceCatalog', side_effect=other_client.exceptions.EndpointNotFound) def test_client_auth_token_endpoint_not_found(self, mock_service_catalog): auth_url = 'http://www.blah.com' proxy_token = 'foobar' proxy_tenant_id = 'user' mock_service_catalog.return_value.get_token = mock.Mock( return_value=proxy_token) instance = other_client.HTTPClient(proxy_token=proxy_token, proxy_tenant_id=proxy_tenant_id, user=None, password=None, tenant_id=proxy_tenant_id, projectid=None, timeout=2, auth_url=auth_url) instance.management_url = 'http://example.com' instance.get_service_url = mock.Mock(return_value='http://example.com') instance.version = 'v2.0' mock_request = mock.Mock() mock_request.return_value = requests.Response() mock_request.return_value.status_code = 200 mock_request.return_value.headers = { 'x-server-management-url': 'blah.com', 'x-auth-token': 'blah', } with mock.patch('requests.request', mock_request): self.assertRaises(exceptions.EndpointNotFound, instance.authenticate) @mock.patch.object(service_catalog, 'ServiceCatalog') def test_client_auth_token_v1_auth_failure(self, mock_service_catalog): auth_url = 'http://www.blah.com' proxy_token = 'foobar' proxy_tenant_id = 'user' mock_service_catalog.return_value.get_token = mock.Mock( return_value=proxy_token) instance = other_client.HTTPClient(proxy_token=proxy_token, proxy_tenant_id=proxy_tenant_id, user=None, password=None, tenant_id=proxy_tenant_id, projectid=None, timeout=2, auth_url=auth_url) instance.management_url = 'http://example.com' instance.get_service_url = mock.Mock(return_value='http://example.com') instance.version = 'v1.0' mock_request = mock.Mock() mock_request.return_value = requests.Response() mock_request.return_value.status_code = 200 mock_request.return_value.headers = { 'x-server-management-url': 'blah.com', 'x-auth-token': 'blah', } with mock.patch('requests.request', mock_request): self.assertRaises(exceptions.NoTokenLookupException, instance.authenticate) @mock.patch.object(service_catalog, 'ServiceCatalog') def test_client_auth_token_v1_auth(self, mock_service_catalog): auth_url = 'http://www.blah.com' proxy_token = 'foobar' mock_service_catalog.return_value.get_token = mock.Mock( return_value=proxy_token) instance = other_client.HTTPClient(user='user', password='password', projectid='projectid', timeout=2, auth_url=auth_url) instance.management_url = 'http://example.com' instance.get_service_url = mock.Mock(return_value='http://example.com') instance.version = 'v1.0' mock_request = mock.Mock() mock_request.return_value = requests.Response() mock_request.return_value.status_code = 200 mock_request.return_value.headers = { 'x-server-management-url': 'blah.com', } headers = {'Content-Type': 'application/json', 'Accept': 'application/json', 'User-Agent': 'python-troveclient'} with mock.patch('requests.request', mock_request): instance.authenticate() called_args, called_kwargs = mock_request.call_args self.assertEqual(('POST', 'http://www.blah.com/v2.0/tokens'), called_args) self.assertEqual(headers, called_kwargs['headers']) def test_client_get(self): auth_url = 'http://www.blah.com' instance = other_client.HTTPClient(user='user', password='password', projectid='project_id', timeout=2, auth_url=auth_url) instance._cs_request = mock.Mock() instance.get('clusters') instance._cs_request.assert_called_with('clusters', 'GET') def test_client_patch(self): auth_url = 'http://www.blah.com' body = mock.Mock() instance = other_client.HTTPClient(user='user', password='password', projectid='project_id', timeout=2, auth_url=auth_url) instance._cs_request = mock.Mock() instance.patch('instances/dummy-instance-id', body=body) instance._cs_request.assert_called_with( 'instances/dummy-instance-id', 'PATCH', body=body) def test_client_post(self): auth_url = 'http://www.blah.com' body = {"add_shard": {}} instance = other_client.HTTPClient(user='user', password='password', projectid='project_id', timeout=2, auth_url=auth_url) instance._cs_request = mock.Mock() instance.post('clusters/dummy-cluster-id', body=body) instance._cs_request.assert_called_with( 'clusters/dummy-cluster-id', 'POST', body=body) def test_client_put(self): auth_url = 'http://www.blah.com' body = {"user": {"password": "new_password"}} instance = other_client.HTTPClient(user='user', password='password', projectid='project_id', timeout=2, auth_url=auth_url) instance._cs_request = mock.Mock() instance.put('instances/dummy-instance-id/user/dummy-user', body=body) instance._cs_request.assert_called_with( 'instances/dummy-instance-id/user/dummy-user', 'PUT', body=body) def test_client_delete(self): auth_url = 'http://www.blah.com' instance = other_client.HTTPClient(user='user', password='password', projectid='project_id', timeout=2, auth_url=auth_url) instance._cs_request = mock.Mock() instance.delete('/backups/dummy-backup-id') instance._cs_request.assert_called_with('/backups/dummy-backup-id', 'DELETE') @mock.patch.object(adapter.LegacyJsonAdapter, 'request') def test_database_service_name(self, m_request): m_request.return_value = (mock.MagicMock(status_code=200), None) client = other_client.SessionClient(session=mock.MagicMock(), auth=mock.MagicMock()) client.request("http://no.where", 'GET') self.assertIsNone(client.database_service_name) client = other_client.SessionClient(session=mock.MagicMock(), auth=mock.MagicMock(), database_service_name='myservice') client.request("http://no.where", 'GET') self.assertEqual('myservice', client.database_service_name) @mock.patch.object(adapter.LegacyJsonAdapter, 'request') @mock.patch.object(adapter.LegacyJsonAdapter, 'get_endpoint', return_value=None) def test_error_sessionclient(self, m_end_point, m_request): m_request.return_value = (mock.MagicMock(status_code=200), None) self.assertRaises(exceptions.EndpointNotFound, other_client.SessionClient, session=mock.MagicMock(), auth=mock.MagicMock()) def test_construct_http_client(self): mock_request = mock.Mock() mock_request.return_value = requests.Response() mock_request.return_value.status_code = 200 mock_request.return_value.headers = { 'x-server-management-url': 'blah.com', 'x-auth-token': 'blah', } with mock.patch('requests.request', mock_request): self.assertIsInstance(other_client._construct_http_client(), other_client.HTTPClient) self.assertIsInstance( other_client._construct_http_client(session=mock.Mock(), auth=mock.Mock()), other_client.SessionClient) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/test_clusters.py0000664000175000017500000001270300000000000024405 0ustar00zuulzuul00000000000000# Copyright (c) 2014 eBay Software 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 testtools from unittest import mock from troveclient import base from troveclient.v1 import clusters """ Unit tests for clusters.py """ class ClusterTest(testtools.TestCase): def setUp(self): super(ClusterTest, self).setUp() def tearDown(self): super(ClusterTest, self).tearDown() @mock.patch.object(clusters.Cluster, '__init__', return_value=None) def test___repr__(self, mock_init): cluster = clusters.Cluster() cluster.name = "cluster-1" self.assertEqual('', cluster.__repr__()) @mock.patch.object(clusters.Cluster, '__init__', return_value=None) def test_delete(self, mock_init): cluster = clusters.Cluster() cluster.manager = mock.Mock() db_delete_mock = mock.Mock(return_value=None) cluster.manager.delete = db_delete_mock cluster.delete() self.assertEqual(1, db_delete_mock.call_count) class ClustersTest(testtools.TestCase): def setUp(self): super(ClustersTest, self).setUp() def tearDown(self): super(ClustersTest, self).tearDown() @mock.patch.object(clusters.Clusters, '__init__', return_value=None) def get_clusters(self, mock_init): clusters_test = clusters.Clusters() clusters_test.api = mock.Mock() clusters_test.api.client = mock.Mock() clusters_test.resource_class = mock.Mock(return_value="cluster-1") return clusters_test def test_create(self): def side_effect_func(path, body, resp_key): return path, body, resp_key clusters_test = self.get_clusters() clusters_test._create = mock.Mock(side_effect=side_effect_func) instances = [{'flavor-id': 11, 'volume': 2}] locality = 'affinity' extended_properties = { 'num_configsvr': 5, 'num_mongos': 7, 'configsvr_volume_size': 11, 'configsvr_volume_type': 'foo_type', 'mongos_volume_size': 12, 'mongos_volume_type': 'bar_type'} configuration = 'test-config' path, body, resp_key = clusters_test.create("test-name", "datastore", "datastore-version", instances, locality, extended_properties, configuration) self.assertEqual("/clusters", path) self.assertEqual("cluster", resp_key) self.assertEqual("test-name", body["cluster"]["name"]) self.assertEqual("datastore", body["cluster"]["datastore"]["type"]) self.assertEqual("datastore-version", body["cluster"]["datastore"]["version"]) self.assertEqual(instances, body["cluster"]["instances"]) self.assertEqual(locality, body["cluster"]["locality"]) self.assertEqual(extended_properties, body["cluster"]["extended_properties"]) self.assertEqual(configuration, body["cluster"]["configuration"]) def test_list(self): page_mock = mock.Mock() clusters_test = self.get_clusters() clusters_test._paginated = page_mock limit = "test-limit" marker = "test-marker" clusters_test.list(limit, marker) page_mock.assert_called_with("/clusters", "clusters", limit, marker) @mock.patch.object(base, 'getid', return_value="cluster1") def test_get(self, mock_id): def side_effect_func(path, inst): return path, inst clusters_test = self.get_clusters() clusters_test._get = mock.Mock(side_effect=side_effect_func) self.assertEqual(('/clusters/cluster1', 'cluster'), clusters_test.get(1)) def test_upgrade(self): resp = mock.Mock() resp.status_code = 200 body = None clusters_test = self.get_clusters() clusters_test.api.client.post = mock.Mock(return_value=(resp, body)) clusters_test.upgrade('cluster1', '5.6') resp.status_code = 500 self.assertRaises(Exception, clusters_test.upgrade, 'cluster1', '5.6') def test_delete(self): resp = mock.Mock() resp.status_code = 200 body = None clusters_test = self.get_clusters() clusters_test.api.client.delete = mock.Mock(return_value=(resp, body)) clusters_test.delete('cluster1') resp.status_code = 500 self.assertRaises(Exception, clusters_test.delete, 'cluster1') class ClusterStatusTest(testtools.TestCase): def test_constants(self): self.assertEqual("ACTIVE", clusters.ClusterStatus.ACTIVE) self.assertEqual("BUILD", clusters.ClusterStatus.BUILD) self.assertEqual("FAILED", clusters.ClusterStatus.FAILED) self.assertEqual("SHUTDOWN", clusters.ClusterStatus.SHUTDOWN) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/test_common.py0000664000175000017500000000625500000000000024036 0ustar00zuulzuul00000000000000# 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 testtools from unittest import mock from troveclient import common class CommonTest(testtools.TestCase): def test_check_for_exceptions(self): status = [400, 422, 500] for s in status: resp = mock.Mock() resp.status_code = s self.assertRaises(Exception, common.check_for_exceptions, resp, "body") def test_append_query_strings(self): url = "test-url" self.assertEqual(url, common.append_query_strings(url)) self.assertEqual(url, common.append_query_strings( url, limit=None, marker=None)) limit = "test-limit" marker = "test-marker" result = common.append_query_strings(url, limit=limit, marker=marker) self.assertTrue(result.startswith(url + '?')) self.assertIn("limit=%s" % limit, result) self.assertIn("marker=%s" % marker, result) self.assertEqual(1, result.count('&')) opts = {} self.assertEqual(url, common.append_query_strings( url, limit=None, marker=None, **opts)) opts = {'key1': 'value1', 'key2': None} result = common.append_query_strings(url, limit=None, marker=marker, **opts) self.assertTrue(result.startswith(url + '?')) self.assertEqual(1, result.count('&')) self.assertNotIn("limit=%s" % limit, result) self.assertIn("marker=%s" % marker, result) self.assertIn("key1=%s" % opts['key1'], result) self.assertNotIn("key2=%s" % opts['key2'], result) opts = {'key1': 'value1', 'key2': None, 'key3': False} result = common.append_query_strings(url, **opts) self.assertTrue(result.startswith(url + '?')) self.assertIn("key1=value1", result) self.assertNotIn("key2", result) self.assertIn("key3=False", result) class PaginatedTest(testtools.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) self.assertEqual(self.next_marker_, self.pgn.next) self.assertEqual(self.links_, self.pgn.links) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/test_configurations.py0000664000175000017500000002267700000000000025606 0ustar00zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # Copyright 2014 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 json import testtools from unittest import mock from troveclient import base from troveclient.v1 import configurations from troveclient.v1 import management """ Unit tests for configurations.py """ class ConfigurationTest(testtools.TestCase): def setUp(self): super(ConfigurationTest, self).setUp() self.orig__init = configurations.Configuration.__init__ configurations.Configuration.__init__ = mock.Mock(return_value=None) self.configuration = configurations.Configuration() def tearDown(self): super(ConfigurationTest, self).tearDown() configurations.Configuration.__init__ = self.orig__init def test___repr__(self): self.configuration.name = "config-1" self.assertEqual('', self.configuration.__repr__()) class ConfigurationsTest(testtools.TestCase): def setUp(self): super(ConfigurationsTest, self).setUp() self.orig__init = configurations.Configurations.__init__ configurations.Configurations.__init__ = mock.Mock(return_value=None) self.configurations = configurations.Configurations() self.configurations.api = mock.Mock() self.configurations.api.client = mock.Mock() self.orig_base_getid = base.getid base.getid = mock.Mock(return_value="configuration1") def tearDown(self): super(ConfigurationsTest, self).tearDown() configurations.Configurations.__init__ = self.orig__init base.getid = self.orig_base_getid def _get_mock_method(self): self._resp = mock.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.Mock(side_effect=side_effect_func) def test_create(self): self.configurations.api.client.post = self._get_mock_method() self._resp.status_code = 200 config = '{"test":12}' self.configurations.create('config1', config, 'test description') self.assertEqual('/configurations', self._url) expected = { 'description': 'test description', 'name': 'config1', 'values': json.loads(config) } self.assertEqual({"configuration": expected}, self._body) def test_delete(self): self.configurations.api.client.delete = self._get_mock_method() self._resp.status_code = 200 self.configurations.delete(27) self.assertEqual('/configurations/configuration1', self._url) self._resp.status_code = 500 self.assertRaises(Exception, self.configurations.delete, 34) def test_list(self): page_mock = mock.Mock() self.configurations._paginated = page_mock limit = "test-limit" marker = "test-marker" self.configurations.list(limit, marker) page_mock.assert_called_with("/configurations", "configurations", limit, marker) def test_get(self): def side_effect_func(path, config): return path, config self.configurations._get = mock.Mock(side_effect=side_effect_func) self.assertEqual(('/configurations/configuration1', 'configuration'), self.configurations.get(123)) def test_instances(self): page_mock = mock.Mock() self.configurations._paginated = page_mock limit = "test-limit" marker = "test-marker" configuration = "configuration1" self.configurations.instances(configuration, limit, marker) page_mock.assert_called_with( "/configurations/" + configuration + "/instances", "instances", limit, marker) def test_update(self): self.configurations.api.client.put = self._get_mock_method() self._resp.status_code = 200 config = '{"test":12}' self.configurations.update(27, config) self.assertEqual('/configurations/configuration1', self._url) self._resp.status_code = 500 self.assertRaises(Exception, self.configurations.update, 34) def test_edit(self): self.configurations.api.client.patch = self._get_mock_method() self._resp.status_code = 200 config = '{"test":12}' self.configurations.edit(27, config) self.assertEqual('/configurations/configuration1', self._url) self._resp.status_code = 500 self.assertRaises(Exception, self.configurations.edit, 34) class ConfigurationParametersTest(testtools.TestCase): def setUp(self): super(ConfigurationParametersTest, self).setUp() self.orig__init = configurations.ConfigurationParameters.__init__ configurations.ConfigurationParameters.__init__ = mock.Mock( return_value=None) self.config_params = configurations.ConfigurationParameters() self.config_params.api = mock.Mock() self.config_params.api.client = mock.Mock() def tearDown(self): super(ConfigurationParametersTest, self).tearDown() configurations.ConfigurationParameters.__init__ = self.orig__init def test_list_parameters(self): def side_effect_func(path, config): return path self.config_params._list = mock.Mock(side_effect=side_effect_func) self.assertEqual('/datastores/datastore/versions/version/parameters', self.config_params.parameters('datastore', 'version')) def test_get_parameter(self): def side_effect_func(path): return path self.config_params._get = mock.Mock(side_effect=side_effect_func) self.assertEqual( '/datastores/datastore/versions/version/parameters/key', self.config_params.get_parameter('datastore', 'version', 'key') ) def test_list_parameters_by_version(self): def side_effect_func(path, config): return path self.config_params._list = mock.Mock(side_effect=side_effect_func) self.assertEqual('/datastores/versions/version/parameters', self.config_params.parameters_by_version('version')) def test_get_parameter_by_version(self): def side_effect_func(path): return path self.config_params._get = mock.Mock(side_effect=side_effect_func) self.assertEqual('/datastores/versions/version/parameters/key', self.config_params.get_parameter_by_version( 'version', 'key')) class MgmtConfigurationParametersTest(testtools.TestCase): def setUp(self): super(MgmtConfigurationParametersTest, self).setUp() self.orig__init = management.MgmtConfigurationParameters.__init__ management.MgmtConfigurationParameters.__init__ = mock.Mock( return_value=None) self.config_params = management.MgmtConfigurationParameters() self.config_params.api = mock.Mock() self.config_params.api.client = mock.Mock() def tearDown(self): super(MgmtConfigurationParametersTest, self).tearDown() management.MgmtConfigurationParameters.__init__ = self.orig__init def _get_mock_method(self): self._resp = mock.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.Mock(side_effect=side_effect_func) def test_create(self): self.config_params.api.client.post = self._get_mock_method() self._resp.status_code = 200 self.config_params.create('id', 'config_name', 1, 'string') self.assertEqual('/mgmt/datastores/versions/id/parameters', self._url) expected = { "name": "config_name", "data_type": "string", "restart_required": 1 } self.assertEqual({"configuration-parameter": expected}, self._body) def test_modify(self): self.config_params.api.client.put = self._get_mock_method() self._resp.status_code = 200 self.config_params.modify('id', 'config_name', '1', 'string') self.assertEqual('/mgmt/datastores/versions/id/parameters/config_name', self._url) expected = { "name": "config_name", "data_type": "string", "restart_required": 1 } self.assertEqual({"configuration-parameter": expected}, self._body) def test_delete(self): self.config_params.api.client.delete = self._get_mock_method() self._resp.status_code = 200 self.config_params.delete('id', 'param_id') self.assertEqual('/mgmt/datastores/versions/id/parameters/param_id', self._url) self.assertIsNone(self._body) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/test_databases.py0000664000175000017500000000673700000000000024502 0ustar00zuulzuul00000000000000# Copyright 2014 Tesora Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import testtools from unittest import mock from troveclient.v1 import databases """ Unit tests for databases.py """ class DatabasesTest(testtools.TestCase): def setUp(self): super(DatabasesTest, self).setUp() self.orig__init = databases.Databases.__init__ databases.Databases.__init__ = mock.Mock(return_value=None) self.databases = databases.Databases() self.databases.api = mock.Mock() self.databases.api.client = mock.Mock() self.instance_with_id = mock.Mock() self.instance_with_id.id = 215 self.fakedb1 = ['db1'] self.fakedb2 = ['db1', 'db2'] def tearDown(self): super(DatabasesTest, self).tearDown() databases.Databases.__init__ = self.orig__init def _get_mock_method(self): self._resp = mock.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.Mock(side_effect=side_effect_func) def test_create(self): self.databases.api.client.post = self._get_mock_method() self._resp.status_code = 200 self.databases.create(23, self.fakedb1) self.assertEqual('/instances/23/databases', self._url) self.assertEqual({"databases": self.fakedb1}, self._body) self.databases.create(23, self.fakedb2) self.assertEqual('/instances/23/databases', self._url) self.assertEqual({"databases": self.fakedb2}, self._body) # test creation with the instance as an object self.databases.create(self.instance_with_id, self.fakedb1) self.assertEqual({"databases": self.fakedb1}, self._body) def test_delete(self): self.databases.api.client.delete = self._get_mock_method() self._resp.status_code = 200 self.databases.delete(27, self.fakedb1[0]) self.assertEqual('/instances/27/databases/%s' % self.fakedb1[0], self._url) self.databases.delete(self.instance_with_id, self.fakedb1[0]) self.assertEqual('/instances/%s/databases/%s' % (self.instance_with_id.id, self.fakedb1[0]), self._url) self._resp.status_code = 400 self.assertRaises(Exception, self.databases.delete, 34, self.fakedb1) def test_list(self): page_mock = mock.Mock() self.databases._paginated = page_mock self.databases.list('instance1') page_mock.assert_called_with('/instances/instance1/databases', 'databases', None, None) limit = 'test-limit' marker = 'test-marker' self.databases.list('instance1', limit, marker) page_mock.assert_called_with('/instances/instance1/databases', 'databases', limit, marker) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/test_datastores.py0000664000175000017500000002121000000000000024703 0ustar00zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # Copyright 2013 Mirantis, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import testtools from unittest import mock from troveclient import base from troveclient.v1 import datastores """ Unit tests for datastores.py """ class DatastoreTest(testtools.TestCase): def setUp(self): super(DatastoreTest, self).setUp() self.orig__init = datastores.Datastore.__init__ datastores.Datastore.__init__ = mock.Mock(return_value=None) self.datastore = datastores.Datastore() self.datastore.manager = mock.Mock() def tearDown(self): super(DatastoreTest, self).tearDown() datastores.Datastore.__init__ = self.orig__init def test___repr__(self): self.datastore.name = "datastore-1" self.assertEqual('', self.datastore.__repr__()) class DatastoresTest(testtools.TestCase): def setUp(self): super(DatastoresTest, self).setUp() self.orig__init = datastores.Datastores.__init__ datastores.Datastores.__init__ = mock.Mock(return_value=None) self.datastores = datastores.Datastores() self.datastores.api = mock.Mock() self.datastores.api.client = mock.Mock() self.datastores.resource_class = mock.Mock(return_value="ds-1") self.orig_base_getid = base.getid base.getid = mock.Mock(return_value="datastore1") def tearDown(self): super(DatastoresTest, self).tearDown() datastores.Datastores.__init__ = self.orig__init base.getid = self.orig_base_getid def test_list(self): page_mock = mock.Mock() self.datastores._paginated = page_mock limit = "test-limit" marker = "test-marker" self.datastores.list(limit, marker) page_mock.assert_called_with("/datastores", "datastores", limit, marker) self.datastores.list() page_mock.assert_called_with("/datastores", "datastores", None, None) def test_get(self): def side_effect_func(path, inst): return path, inst self.datastores._get = mock.Mock(side_effect=side_effect_func) self.assertEqual(('/datastores/datastore1', 'datastore'), self.datastores.get(1)) class DatastoreVersionsTest(testtools.TestCase): def setUp(self): super(DatastoreVersionsTest, self).setUp() self.orig__init = datastores.DatastoreVersions.__init__ datastores.DatastoreVersions.__init__ = mock.Mock(return_value=None) self.datastore_versions = datastores.DatastoreVersions() self.datastore_versions.api = mock.Mock() self.datastore_versions.api.client = mock.Mock() self.datastore_versions.resource_class = mock.Mock( return_value="ds_version-1") self.orig_base_getid = base.getid base.getid = mock.Mock(return_value="datastore_version1") def tearDown(self): super(DatastoreVersionsTest, self).tearDown() datastores.DatastoreVersions.__init__ = self.orig__init base.getid = self.orig_base_getid def test_list(self): page_mock = mock.Mock() self.datastore_versions._paginated = page_mock limit = "test-limit" marker = "test-marker" self.datastore_versions.list("datastore1", limit, marker) page_mock.assert_called_with("/datastores/datastore1/versions", "versions", limit, marker) def test_get(self): def side_effect_func(path, inst): return path, inst self.datastore_versions._get = mock.Mock(side_effect=side_effect_func) self.assertEqual(('/datastores/datastore1/versions/' 'datastore_version1', 'version'), self.datastore_versions.get("datastore1", "datastore_version1")) def test_get_by_uuid(self): def side_effect_func(path, inst): return path, inst self.datastore_versions._get = mock.Mock(side_effect=side_effect_func) self.assertEqual(('/datastores/versions/datastore_version1', 'version'), (self.datastore_versions. get_by_uuid("datastore_version1"))) class DatastoreVersionMembersTest(testtools.TestCase): def setUp(self): super(DatastoreVersionMembersTest, self).setUp() self.orig__init = datastores.DatastoreVersionMembers.__init__ datastores.DatastoreVersionMembers.__init__ = mock.Mock( return_value=None) self.datastore_version_members = datastores.DatastoreVersionMembers() self.datastore_version_members.api = mock.Mock() self.datastore_version_members.api.client = mock.Mock() self.datastore_version_members.resource_class = mock.Mock( return_value="ds_version_member-1") self.orig_base_getid = base.getid base.getid = mock.Mock(return_value="datastore_version_member1") def tearDown(self): super(DatastoreVersionMembersTest, self).tearDown() datastores.DatastoreVersionMembers.__init__ = self.orig__init base.getid = self.orig_base_getid def test_add(self): def side_effect_func(path, body, inst): return path, body, inst self.datastore_version_members._create = mock.Mock( side_effect=side_effect_func) p, b, i = self.datastore_version_members.add("data_store1", "datastore_version1", "tenant1") self.assertEqual( "/mgmt/datastores/data_store1/versions/datastore_version1/members", p) self.assertEqual("datastore_version_member", i) self.assertEqual("tenant1", b["member"]) def test_delete(self): def side_effect_func(path): return path self.datastore_version_members._delete = mock.Mock( side_effect=side_effect_func) p = self.datastore_version_members.delete("data_store1", "datastore_version1", "tenant1") self.assertEqual( "/mgmt/datastores/data_store1/versions/datastore_version1/members/" "tenant1", p) def test_list(self): page_mock = mock.Mock() self.datastore_version_members._list = page_mock limit = "test-limit" marker = "test-marker" self.datastore_version_members.list("datastore1", "datastore_version1", limit, marker) page_mock.assert_called_with("/mgmt/datastores/datastore1/versions/" "datastore_version1/members", "datastore_version_members", limit, marker) def test_get(self): def side_effect_func(path, inst): return path, inst self.datastore_version_members._get = mock.Mock( side_effect=side_effect_func) self.assertEqual(('/mgmt/datastores/datastore1/versions/' 'datastore_version1/members/tenant1', 'datastore_version_member'), self.datastore_version_members.get( "datastore1", "datastore_version1", "tenant1")) def test_get_by_tenant(self): page_mock = mock.Mock() self.datastore_version_members._list = page_mock limit = "test-limit" marker = "test-marker" self.datastore_version_members.get_by_tenant("datastore1", "tenant1", limit, marker) page_mock.assert_called_with("/mgmt/datastores/datastore1/versions/" "members/tenant1", "datastore_version_members", limit, marker) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/test_discover.py0000664000175000017500000000426300000000000024361 0ustar00zuulzuul00000000000000# 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. import inspect import types from unittest import mock import testtools import troveclient.shell class DiscoverTest(testtools.TestCase): def test_discover_extensions(self): def mock_discover_via_python_path(self): yield 'foo', types.ModuleType('foo') def mock_discover_via_contrib_path(self, version): yield 'bar', types.ModuleType('bar') def mock_discover_via_entry_points(self): yield 'baz', types.ModuleType('baz') @mock.patch.object(troveclient.shell.OpenStackTroveShell, '_discover_via_python_path', mock_discover_via_python_path) @mock.patch.object(troveclient.shell.OpenStackTroveShell, '_discover_via_contrib_path', mock_discover_via_contrib_path) @mock.patch.object(troveclient.shell.OpenStackTroveShell, '_discover_via_entry_points', mock_discover_via_entry_points) def test(): shell = troveclient.shell.OpenStackTroveShell() extensions = shell._discover_extensions('1.0') self.assertEqual(3, len(extensions)) names = sorted(['foo', 'bar', 'baz']) sorted_extensions = sorted(extensions, key=lambda ext: ext.name) for i in range(len(names)): ext = sorted_extensions[i] name = names[i] self.assertEqual(name, ext.name) self.assertTrue(inspect.ismodule(ext.module)) test() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/test_instances.py0000664000175000017500000002776600000000000024547 0ustar00zuulzuul00000000000000# 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 unittest import mock import testtools from troveclient import base from troveclient.v1 import instances """ Unit tests for instances.py """ class InstanceTest(testtools.TestCase): def setUp(self): super(InstanceTest, self).setUp() self.orig__init = instances.Instance.__init__ instances.Instance.__init__ = mock.Mock(return_value=None) self.instance = instances.Instance() self.instance.manager = mock.Mock() def tearDown(self): super(InstanceTest, self).tearDown() instances.Instance.__init__ = self.orig__init def test_list_databases(self): db_list = ['database1', 'database2'] self.instance.manager.databases = mock.Mock() self.instance.manager.databases.list = mock.Mock(return_value=db_list) self.assertEqual(db_list, self.instance.list_databases()) def test_delete(self): db_delete_mock = 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.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) def test_detach_replica(self): db_detach_mock = mock.Mock(return_value=None) self.instance.manager.edit = db_detach_mock self.instance.id = 1 self.instance.detach_replica() self.assertEqual(1, db_detach_mock.call_count) class InstancesTest(testtools.TestCase): def setUp(self): super(InstancesTest, self).setUp() self.orig__init = instances.Instances.__init__ instances.Instances.__init__ = mock.Mock(return_value=None) self.instances = instances.Instances() self.instances.api = mock.Mock() self.instances.api.client = mock.Mock() self.instances.resource_class = mock.Mock(return_value="instance-1") self.instance_with_id = mock.Mock() self.instance_with_id.id = 215 def tearDown(self): super(InstancesTest, self).tearDown() instances.Instances.__init__ = self.orig__init def test_create(self): def side_effect_func(path, body, inst): return path, body, inst self.instances._create = mock.Mock(side_effect=side_effect_func) nics = [{'net-id': '000'}] p, b, i = self.instances.create("test-name", 103, "test-volume", ['db1', 'db2'], ['u1', 'u2'], datastore="datastore", datastore_version="datastore-version", nics=nics, replica_of='test', replica_count=4, modules=['mod_id'], locality='affinity', access={'is_public': True}) 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("datastore", b["instance"]["datastore"]["type"]) self.assertEqual("datastore-version", b["instance"]["datastore"]["version"]) self.assertEqual(nics, b["instance"]["nics"]) self.assertEqual(103, b["instance"]["flavorRef"]) self.assertEqual('test', b['instance']['replica_of']) self.assertEqual([{'id': 'mod_id'}], b["instance"]["modules"]) self.assertEqual(4, b["instance"]["replica_count"]) self.assertEqual('affinity', b["instance"]["locality"]) self.assertEqual({'is_public': True}, b["instance"]["access"]) def test_list(self): page_mock = mock.Mock() self.instances._paginated = page_mock limit = "test-limit" marker = "test-marker" include_clustered = {'include_clustered': False} self.instances.list(limit, marker) page_mock.assert_called_with("/instances", "instances", limit, marker, include_clustered) def test_detailed_list(self): page_mock = mock.Mock() self.instances._paginated = page_mock limit = "test-limit" marker = "test-marker" include_clustered = {'include_clustered': False} self.instances.list(limit, marker, detailed=True) page_mock.assert_called_with("/instances/detail", "instances", limit, marker, include_clustered) def test_get(self): def side_effect_func(path, inst): return path, inst self.instances._get = mock.Mock(side_effect=side_effect_func) self.assertEqual(('/instances/instance1', 'instance'), self.instances.get('instance1')) def test_delete(self): resp = mock.Mock() resp.status_code = 200 body = None self.instances.api.client.delete = mock.Mock(return_value=(resp, body)) self.instances.delete('instance1') self.instances.delete(self.instance_with_id) resp.status_code = 500 self.assertRaises(Exception, self.instances.delete, 'instance1') def test__action(self): body = mock.Mock() resp = mock.Mock() resp.status_code = 200 self.instances.api.client.post = mock.Mock(return_value=(resp, body)) self.assertEqual('instance-1', self.instances._action(1, body)) self.instances.api.client.post = mock.Mock(return_value=(resp, None)) self.assertIsNone(self.instances._action(1, body)) def _set_action_mock(self): def side_effect_func(instance, body): self._instance_id = base.getid(instance) self._body = body self._instance_id = None self._body = None self.instances._action = mock.Mock(side_effect=side_effect_func) def _test_resize_volume(self, instance, id): self._set_action_mock() self.instances.resize_volume(instance, 1024) self.assertEqual(id, self._instance_id) self.assertEqual({"resize": {"volume": {"size": 1024}}}, self._body) def test_resize_volume_with_id(self): self._test_resize_volume(152, 152) def test_resize_volume_with_obj(self): self._test_resize_volume(self.instance_with_id, self.instance_with_id.id) def _test_resize_instance(self, instance, id): self._set_action_mock() self.instances.resize_instance(instance, 103) self.assertEqual(id, self._instance_id) self.assertEqual({"resize": {"flavorRef": 103}}, self._body) def test_resize_instance_with_id(self): self._test_resize_instance(4725, 4725) def test_resize_instance_with_obj(self): self._test_resize_instance(self.instance_with_id, self.instance_with_id.id) def _test_restart(self, instance, id): self._set_action_mock() self.instances.restart(instance) self.assertEqual(id, self._instance_id) self.assertEqual({'restart': {}}, self._body) def test_restart_with_id(self): self._test_restart(253, 253) def test_restart_with_obj(self): self._test_restart(self.instance_with_id, self.instance_with_id.id) def test_modify(self): resp = mock.Mock() resp.status_code = 200 body = None self.instances.api.client.put = mock.Mock(return_value=(resp, body)) self.instances.modify(123) self.instances.modify(123, 321) self.instances.modify(self.instance_with_id) self.instances.modify(self.instance_with_id, 123) resp.status_code = 500 self.assertRaises(Exception, self.instances.modify, 'instance1') def test_module_apply(self): resp = mock.Mock() resp.status_code = 200 body = {'modules': []} self.instances.api.client.post = mock.Mock(return_value=(resp, body)) self.instances.module_apply(self.instance_with_id, "mod_id") resp.status_code = 500 self.assertRaises(Exception, self.instances.module_apply, 'instance1', 'mod1') def test_module_remove(self): resp = mock.Mock() resp.status_code = 200 body = {'modules': []} self.instances.api.client.delete = mock.Mock(return_value=(resp, body)) self.instances.module_remove(self.instance_with_id, "mod_id") resp.status_code = 500 self.assertRaises(Exception, self.instances.module_remove, 'instance1', 'mod1') def test_module_query(self): resp = mock.Mock() resp.status_code = 200 body = {'modules': []} self.instances.api.client.get = mock.Mock(return_value=(resp, body)) self.instances.module_query(self.instance_with_id) resp.status_code = 500 self.assertRaises(Exception, self.instances.module_query, 'instance1') def test_module_retrieve(self): resp = mock.Mock() resp.status_code = 200 body = {'modules': []} self.instances.api.client.get = mock.Mock(return_value=(resp, body)) self.instances.module_retrieve(self.instance_with_id) resp.status_code = 500 self.assertRaises(Exception, self.instances.module_retrieve, 'instance1') def test_module_list_instance(self): resp = mock.Mock() resp.status_code = 200 body = {'modules': []} self.instances.api.client.get = mock.Mock(return_value=(resp, body)) self.instances.modules(self.instance_with_id) resp.status_code = 500 self.assertRaises(Exception, self.instances.modules, 'instance1') def test_upgrade(self): resp = mock.Mock() resp.status_code = 200 body = None self.instances.api.client.patch = mock.Mock(return_value=(resp, body)) self.instances.upgrade(self.instance_with_id, "5.6") resp.status_code = 500 self.assertRaises(Exception, self.instances.upgrade, 'instance1') def test_configuration(self): def side_effect_func(path, inst): return path, inst self.instances._get = mock.Mock(side_effect=side_effect_func) self.assertEqual(('/instances/instance1/configuration', 'instance'), self.instances.configuration('instance1')) class InstanceStatusTest(testtools.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) self.assertEqual("RESTART_REQUIRED", instances.InstanceStatus.RESTART_REQUIRED) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/test_limits.py0000664000175000017500000000642400000000000024045 0ustar00zuulzuul00000000000000# 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 testtools from unittest import mock from troveclient.v1 import limits class LimitsTest(testtools.TestCase): """This class tests the calling code for the Limits API.""" def setUp(self): super(LimitsTest, self).setUp() self.limits = limits.Limits(mock.Mock()) self.limits.api.client = mock.Mock() def tearDown(self): super(LimitsTest, self).tearDown() def test_list(self): resp = mock.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.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.Mock() resp.status_code = status_code body = {RESPONSE_KEY: { 'absolute': {}, 'rate': [ {'limit': []}]}} response = (resp, body) mock_get = mock.Mock(return_value=response) self.limits.api.client.get = mock_get self.assertRaises(Exception, self.limits.list) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/test_management.py0000664000175000017500000002665200000000000024665 0ustar00zuulzuul00000000000000# 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 testtools from unittest import mock from troveclient import base from troveclient.v1 import management """ Unit tests for management.py """ class RootHistoryTest(testtools.TestCase): def setUp(self): super(RootHistoryTest, self).setUp() self.orig__init = management.RootHistory.__init__ management.RootHistory.__init__ = mock.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(testtools.TestCase): def setUp(self): super(ManagementTest, self).setUp() self.orig__init = management.Management.__init__ management.Management.__init__ = mock.Mock(return_value=None) self.management = management.Management() self.management.api = mock.Mock() self.management.api.client = mock.Mock() self.orig_hist__init = management.RootHistory.__init__ self.orig_base_getid = base.getid base.getid = mock.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_show(self): def side_effect_func(path, instance): return path, instance self.management._get = mock.Mock(side_effect=side_effect_func) p, i = self.management.show(1) self.assertEqual(('/mgmt/instances/instance1', 'instance'), (p, i)) def test_list(self): page_mock = mock.Mock() self.management._paginated = page_mock self.management.list(deleted=True) page_mock.assert_called_with('/mgmt/instances', 'instances', None, None, query_strings={'deleted': True}) self.management.list(deleted=False, limit=10, marker="foo") page_mock.assert_called_with('/mgmt/instances', 'instances', 10, "foo", query_strings={"deleted": False}) def test_index(self): """index() is just wrapper for list()""" page_mock = mock.Mock() self.management._paginated = page_mock self.management.index(deleted=True) page_mock.assert_called_with('/mgmt/instances', 'instances', None, None, query_strings={'deleted': True}) self.management.index(deleted=False, limit=10, marker="foo") page_mock.assert_called_with('/mgmt/instances', 'instances', 10, "foo", query_strings={"deleted": False}) def test_root_enabled_history(self): self.management.api.client.get = mock.Mock(return_value=('resp', None)) self.assertRaises(Exception, self.management.root_enabled_history, "instance") body = {'root_history': 'rh'} self.management.api.client.get = mock.Mock(return_value=('resp', body)) management.RootHistory.__init__ = mock.Mock(return_value=None) rh = self.management.root_enabled_history("instance") self.assertIsInstance(rh, management.RootHistory) def test__action(self): resp = mock.Mock() self.management.api.client.post = mock.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.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(testtools.TestCase): def setUp(self): super(MgmtFlavorsTest, self).setUp() self.orig__init = management.MgmtFlavors.__init__ management.MgmtFlavors.__init__ = mock.Mock(return_value=None) self.flavors = management.MgmtFlavors() self.flavors.api = mock.Mock() self.flavors.api.client = mock.Mock() self.flavors.resource_class = mock.Mock(return_value="flavor-1") self.orig_base_getid = base.getid base.getid = mock.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.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"]) class MgmtDatastoreVersionsTest(testtools.TestCase): def setUp(self): super(MgmtDatastoreVersionsTest, self).setUp() self.orig__init = management.MgmtDatastoreVersions.__init__ management.MgmtDatastoreVersions.__init__ = mock.Mock( return_value=None) self.ds_version = management.MgmtDatastoreVersions() self.ds_version.api = mock.Mock() self.ds_version.api.client = mock.Mock() self.ds_version.resource_class = mock.Mock(return_value="ds-version-1") self.orig_base_getid = base.getid base.getid = mock.Mock(return_value="ds-version1") def tearDown(self): super(MgmtDatastoreVersionsTest, self).tearDown() management.MgmtDatastoreVersions.__init__ = self.orig__init base.getid = self.orig_base_getid def _get_mock_method(self): self._resp = mock.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.Mock(side_effect=side_effect_func) def test_create(self): def side_effect_func(path, body, *kw): return path, body self.ds_version._create = mock.Mock(side_effect=side_effect_func) p, b, = self.ds_version.create( "ds-version1", "mysql", "mysql", "image-id", ["mysql-server-5.5"], "registry-ext", "repl-strategy", "true", "true") self.assertEqual("/mgmt/datastore-versions", p) self.assertEqual("ds-version1", b["version"]["name"]) self.assertEqual("mysql", b["version"]["datastore_name"]) self.assertEqual("mysql", b["version"]["datastore_manager"]) self.assertEqual("image-id", b["version"]["image"]) self.assertEqual("registry-ext", b["version"]["registry_ext"]) self.assertEqual("repl-strategy", b["version"]["repl_strategy"]) self.assertEqual(["mysql-server-5.5"], b["version"]["packages"]) self.assertTrue(b["version"]["active"]) self.assertTrue(b["version"]["default"]) def test_get(self): def side_effect_func(path, ins): return path, ins self.ds_version._get = mock.Mock(side_effect=side_effect_func) p, i = self.ds_version.get('ds-version-1') self.assertEqual(('/mgmt/datastore-versions/ds-version-1', 'version'), (p, i)) def test_list(self): page_mock = mock.Mock() self.ds_version._paginated = page_mock self.ds_version.list() page_mock.assert_called_with('/mgmt/datastore-versions', 'versions', None, None) self.ds_version.list(limit=10, marker="foo") page_mock.assert_called_with('/mgmt/datastore-versions', 'versions', 10, "foo") def test_delete(self): resp = mock.Mock() resp.status_code = 202 self.ds_version.api.client.delete = mock.Mock( return_value=(resp, None) ) self.ds_version.delete('ds-version-1') self.assertEqual(1, self.ds_version.api.client.delete.call_count) self.ds_version.api.client.delete.assert_called_with( '/mgmt/datastore-versions/ds-version-1') resp.status_code = 400 self.assertRaises(Exception, self.ds_version.delete, 'ds-version-2') self.assertEqual(2, self.ds_version.api.client.delete.call_count) self.ds_version.api.client.delete.assert_called_with( '/mgmt/datastore-versions/ds-version-2') def test_edit(self): self.ds_version.api.client.patch = self._get_mock_method() self._resp.status_code = 202 self.ds_version.edit('ds-version-1', image="new-image-id") self.assertEqual('/mgmt/datastore-versions/ds-version-1', self._url) self.assertEqual({"image": "new-image-id"}, self._body) self._resp.status_code = 400 self.assertRaises(Exception, self.ds_version.edit, 'ds-version-1', "new-mgr", "non-existent-image") ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/test_metadata.py0000664000175000017500000001430500000000000024321 0ustar00zuulzuul00000000000000# Copyright 2014 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 json import testtools from troveclient.v1 import metadata from unittest import mock class TestMetadata(testtools.TestCase): def setUp(self): super(TestMetadata, self).setUp() self.orig__init = metadata.Metadata.__init__ metadata.Metadata.__init__ = mock.Mock(return_value=None) self.metadata = metadata.Metadata() self.metadata.manager = mock.Mock() self.metadata.api = mock.Mock() self.metadata.api.client = mock.Mock() self.instance_uuid = '3fbc8d6d-3f87-41d9-a4a1-060830dc6c4c' self.metadata_key = 'metakey' self.new_metadata_key = 'newmetakey' self.metadata_value = {'metavalue': [1, 2, 3]} def tearDown(self): super(TestMetadata, self).tearDown() metadata.Metadata.__init__ = self.orig__init def test_list(self): def side_effect_func(path, config): return path, config self.metadata._get = mock.Mock(side_effect=side_effect_func) path, config = self.metadata.list(self.instance_uuid) self.assertEqual('/instances/%s/metadata' % self.instance_uuid, path) self.assertEqual('metadata', config) def test_show(self): def side_effect_func(path, config): return path, config self.metadata._get = mock.Mock(side_effect=side_effect_func) path, config = self.metadata.show(self.instance_uuid, self.metadata_key) self.assertEqual('/instances/%s/metadata/%s' % (self.instance_uuid, self.metadata_key), path) self.assertEqual('metadata', config) def test_create(self): def side_effect_func(path, body, config): return path, body, config create_body = { 'metadata': { 'value': self.metadata_value } } self.metadata._create = mock.Mock(side_effect=side_effect_func) path, body, config = self.metadata.create(self.instance_uuid, self.metadata_key, self.metadata_value) self.assertEqual('/instances/%s/metadata/%s' % (self.instance_uuid, self.metadata_key), path) self.assertEqual(create_body, body) self.assertEqual('metadata', config) def test_edit(self): def side_effect_func(path, body): return path, body edit_body = { 'metadata': { 'value': self.metadata_value } } self.metadata._edit = mock.Mock(side_effect=side_effect_func) path, body = self.metadata.edit(self.instance_uuid, self.metadata_key, self.metadata_value) self.assertEqual('/instances/%s/metadata/%s' % (self.instance_uuid, self.metadata_key), path) self.assertEqual(edit_body, body) def test_update(self): def side_effect_func(path, body): return path, body update_body = { 'metadata': { 'key': self.new_metadata_key, 'value': self.metadata_value } } self.metadata._update = mock.Mock(side_effect=side_effect_func) path, body = self.metadata.update(self.instance_uuid, self.metadata_key, self.new_metadata_key, self.metadata_value) self.assertEqual('/instances/%s/metadata/%s' % (self.instance_uuid, self.metadata_key), path) self.assertEqual(update_body, body) def test_delete(self): def side_effect_func(path): return path self.metadata._delete = mock.Mock(side_effect=side_effect_func) path = self.metadata.delete(self.instance_uuid, self.metadata_key) self.assertEqual('/instances/%s/metadata/%s' % (self.instance_uuid, self.metadata_key), path) def test_parse_value_valid_json_in(self): value = {'one': [2, 3, 4]} ser_value = json.dumps(value) new_value = self.metadata._parse_value(ser_value) self.assertEqual(value, new_value) def test_parse_value_string_in(self): value = 'this is a string' new_value = self.metadata._parse_value(value) self.assertEqual(value, new_value) def test_parse_value_dict_in(self): value = {'one': [2, 3, 4]} new_value = self.metadata._parse_value(value) self.assertEqual(value, new_value) def test_parse_value_list_in(self): value = [2, 3, 4] new_value = self.metadata._parse_value(value) self.assertEqual(value, new_value) def test_parse_value_tuple_in(self): value = (2, 3, 4) new_value = self.metadata._parse_value(value) self.assertEqual(value, new_value) def test_parse_value_float_in(self): value = 1.32 new_value = self.metadata._parse_value(value) self.assertEqual(value, new_value) def test_parse_value_int_in(self): value = 1 new_value = self.metadata._parse_value(value) self.assertEqual(value, new_value) def test_parse_value_invalid_json_in(self): # NOTE(imsplitbit): it's worth mentioning here and in the code that # if you give _parse_value invalid json you get the string passed back # to you. value = "{'one': [2, 3, 4]}" new_value = self.metadata._parse_value(value) self.assertEqual(value, new_value) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/test_modules.py0000664000175000017500000001362400000000000024214 0ustar00zuulzuul00000000000000# Copyright 2016 Tesora, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import os import testtools from unittest import mock from troveclient.v1 import modules class TestModules(testtools.TestCase): def setUp(self): super(TestModules, self).setUp() self.mod_init_patcher = mock.patch( 'troveclient.v1.modules.Module.__init__', mock.Mock(return_value=None)) self.addCleanup(self.mod_init_patcher.stop) self.mod_init_patcher.start() self.mods_init_patcher = mock.patch( 'troveclient.v1.modules.Modules.__init__', mock.Mock(return_value=None)) self.addCleanup(self.mods_init_patcher.stop) self.mods_init_patcher.start() self.module_name = 'mod_1' self.module_id = 'mod-id' self.module = mock.Mock() self.module.id = self.module_id self.module.name = self.module_name self.modules = modules.Modules() self.modules.api = mock.Mock() self.modules.api.client = mock.Mock() self.modules.resource_class = mock.Mock(return_value=self.module_name) def tearDown(self): super(TestModules, self).tearDown() def test_create(self): def side_effect_func(path, body, mod): return path, body, mod text_contents = "my_contents" binary_contents = os.urandom(20) for contents in [text_contents, binary_contents]: self.modules._create = mock.Mock(side_effect=side_effect_func) path, body, mod = self.modules.create( self.module_name, "test", contents, description="my desc", all_tenants=False, datastore="ds", datastore_version="ds-version", auto_apply=True, visible=True, live_update=False, priority_apply=False, apply_order=5, full_access=True) self.assertEqual("/modules", path) self.assertEqual("module", mod) self.assertEqual(self.module_name, body["module"]["name"]) self.assertEqual("ds", body["module"]["datastore"]["type"]) self.assertEqual("ds-version", body["module"]["datastore"]["version"]) self.assertFalse(body["module"]["all_tenants"]) self.assertTrue(body["module"]["auto_apply"]) self.assertTrue(body["module"]["visible"]) self.assertFalse(body["module"]["live_update"]) self.assertFalse(body["module"]["priority_apply"]) self.assertEqual(5, body["module"]["apply_order"]) self.assertTrue(body["module"]["full_access"]) def test_update(self): resp = mock.Mock() resp.status_code = 200 body = {'module': None} self.modules.api.client.put = mock.Mock(return_value=(resp, body)) self.modules.update(self.module_id) self.modules.update(self.module_id, name='new_name') self.modules.update(self.module) self.modules.update(self.module, name='new_name') resp.status_code = 500 self.assertRaises(Exception, self.modules.update, self.module_name) def test_list(self): page_mock = mock.Mock() self.modules._paginated = page_mock limit = "test-limit" marker = "test-marker" self.modules.list(limit, marker) page_mock.assert_called_with( "/modules", "modules", limit, marker, query_strings=None) def test_get(self): def side_effect_func(path, inst): return path, inst self.modules._get = mock.Mock(side_effect=side_effect_func) self.assertEqual( ('/modules/%s' % self.module_name, 'module'), self.modules.get(self.module_name)) def test_delete(self): resp = mock.Mock() resp.status_code = 200 body = None self.modules.api.client.delete = mock.Mock(return_value=(resp, body)) self.modules.delete(self.module_name) self.modules.delete(self.module) resp.status_code = 500 self.assertRaises(Exception, self.modules.delete, self.module_name) def _test_instances(self, expected_query=None): page_mock = mock.Mock() self.modules._paginated = page_mock limit = "test-limit" marker = "test-marker" if not expected_query: expected_query = {} self.modules.instances(self.module_name, limit, marker, **expected_query) page_mock.assert_called_with("/modules/mod_1/instances", "instances", limit, marker, query_strings=expected_query) def test_instance_count(self): expected_query = {'include_clustered': True, 'count_only': True} self._test_instances(expected_query) def test_instances(self): expected_query = {'include_clustered': True} self._test_instances(expected_query) def test_reapply(self): resp = mock.Mock() resp.status_code = 200 body = None self.modules.api.client.put = mock.Mock(return_value=(resp, body)) self.modules.reapply(self.module_name) self.modules.reapply(self.module) resp.status_code = 500 self.assertRaises(Exception, self.modules.reapply, self.module_name) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/test_root.py0000664000175000017500000000334600000000000023527 0ustar00zuulzuul00000000000000# Copyright 2015 Tesora Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import testtools from unittest import mock from troveclient.v1 import root """ Unit tests for root.py """ class RootTest(testtools.TestCase): def setUp(self): super(RootTest, self).setUp() self.orig__init = root.Root.__init__ root.Root.__init__ = mock.Mock(return_value=None) self.root = root.Root() self.root.api = mock.Mock() self.root.api.client = mock.Mock() def tearDown(self): super(RootTest, self).tearDown() root.Root.__init__ = self.orig__init def _get_mock_method(self): self._resp = mock.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.Mock(side_effect=side_effect_func) def test_delete(self): self.root.api.client.delete = self._get_mock_method() self._resp.status_code = 200 self.root.delete(1234) self.assertEqual('/instances/1234/root', self._url) self._resp.status_code = 400 self.assertRaises(Exception, self.root.delete, 1234) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/test_secgroups.py0000664000175000017500000001047200000000000024554 0ustar00zuulzuul00000000000000# 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 testtools from unittest import mock from troveclient.v1 import security_groups """ Unit tests for security_groups.py """ class SecGroupTest(testtools.TestCase): def setUp(self): super(SecGroupTest, self).setUp() self.orig__init = security_groups.SecurityGroup.__init__ security_groups.SecurityGroup.__init__ = mock.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.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.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(testtools.TestCase): @mock.patch.object(security_groups.SecurityGroupRules, '__init__', mock.Mock(return_value=None)) @mock.patch.object(security_groups.SecurityGroupRule, '__init__', mock.Mock(return_value=None)) def setUp(self, *args): super(SecGroupRuleTest, self).setUp() self.security_group_rule = security_groups.SecurityGroupRule() self.security_group_rules = security_groups.SecurityGroupRules() def tearDown(self): super(SecGroupRuleTest, self).tearDown() 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_raw=True): return path, body, inst self.security_group_rules._create = mock.Mock( side_effect=side_effect_func ) p, b, i = self.security_group_rules.create(1, "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("0.0.0.0//0", b["security_group_rule"]["cidr"]) def test_delete(self): resp = mock.Mock() resp.status = 200 body = None self.security_group_rules.api = mock.Mock() self.security_group_rules.api.client = mock.Mock() self.security_group_rules.api.client.delete = mock.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) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/test_shell.py0000664000175000017500000003114500000000000023651 0ustar00zuulzuul00000000000000# # 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 io import json import re import sys from unittest import mock import fixtures from keystoneauth1 import fixture import requests_mock import testtools import uuid import troveclient.client from troveclient import exceptions import troveclient.shell V2_URL = "http://no.where/v2.0" V3_URL = "http://no.where/v3" FAKE_V2_ENV = {'OS_USERNAME': uuid.uuid4().hex, 'OS_PASSWORD': uuid.uuid4().hex, 'OS_TENANT_ID': uuid.uuid4().hex, 'OS_AUTH_URL': V2_URL} FAKE_V3_ENV = {'OS_USERNAME': uuid.uuid4().hex, 'OS_PASSWORD': uuid.uuid4().hex, 'OS_PROJECT_ID': uuid.uuid4().hex, 'OS_USER_DOMAIN_NAME': uuid.uuid4().hex, 'OS_AUTH_URL': V3_URL} UPDATED = '2013-03-06T00:00:00Z' TEST_SERVICE_CATALOG = [{ "endpoints": [{ "adminURL": "http://no.where/admin", "region": "RegionOne", "internalURL": "http://no.where/internal", "publicURL": "http://no.where/v1.0" }], "type": "database", "name": "trove" }] def _create_ver_list(versions): return {'versions': {'values': versions}} class ShellTest(testtools.TestCase): version_id = 'v2.0' links = [{'href': 'http://no.where/v2.0', 'rel': 'self'}] v2_version = fixture.V2Discovery(V2_URL) v2_version.updated_str = UPDATED v2_auth_response = json.dumps({ "access": { "token": { "expires": "2020-01-01T00:00:10.000123Z", "id": 'fakeToken', "tenant": { "id": uuid.uuid4().hex }, }, "user": { "id": uuid.uuid4().hex }, "serviceCatalog": TEST_SERVICE_CATALOG, }, }) def make_env(self, exclude=None, fake_env=FAKE_V2_ENV): env = dict((k, v) for k, v in fake_env.items() if k != exclude) self.useFixture(fixtures.MonkeyPatch('os.environ', env)) def setUp(self): super(ShellTest, self).setUp() self.useFixture(fixtures.MonkeyPatch( 'troveclient.client.get_client_class', mock.MagicMock)) def shell(self, argstr, exitcodes=(0,)): orig = sys.stdout orig_stderr = sys.stderr try: sys.stdout = io.StringIO() sys.stderr = io.StringIO() _shell = troveclient.shell.OpenStackTroveShell() _shell.main(argstr.split()) except SystemExit: exc_type, exc_value, exc_traceback = sys.exc_info() self.assertIn(exc_value.code, exitcodes) finally: stdout = sys.stdout.getvalue() sys.stdout.close() sys.stdout = orig stderr = sys.stderr.getvalue() sys.stderr.close() sys.stderr = orig_stderr return (stdout, stderr) def register_keystone_discovery_fixture(self, mreq): mreq.register_uri('GET', V2_URL, json=_create_ver_list([self.v2_version]), status_code=200) def test_help_unknown_command(self): self.assertRaises(exceptions.CommandError, self.shell, 'help foofoo') def test_help(self): required = [ '.*?^usage: ', '.*?^See "trove help COMMAND" for help on a specific command', ] stdout, stderr = self.shell('help') for r in required: self.assertThat( (stdout + stderr), testtools.matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE)) def test_no_username(self): required = ('You must provide a username' ' via either --os-username or' ' env[OS_USERNAME]') self.make_env(exclude='OS_USERNAME') try: self.shell('list') except exceptions.CommandError as message: self.assertEqual(required, message.args[0]) else: self.fail('CommandError not raised') def test_no_auth_url(self): required = ('You must provide an auth url' ' via either --os-auth-url or env[OS_AUTH_URL] ' 'or specify an auth_system which defines a default ' 'url with --os-auth-system or env[OS_AUTH_SYSTEM]',) self.make_env(exclude='OS_AUTH_URL') try: self.shell('list') except exceptions.CommandError as message: self.assertEqual(required, message.args) else: self.fail('CommandError not raised') @mock.patch('keystoneauth1.discover.get_version_data', return_value=[{'status': 'stable', 'id': version_id, 'links': links}]) @mock.patch('troveclient.v1.datastores.DatastoreVersions.list') @requests_mock.Mocker() def test_datastore_version_list(self, mock_discover, mock_list, mock_requests): expected = '\n'.join([ '+----+------+', '| ID | Name |', '+----+------+', '+----+------+', '' ]) self.make_env() self.register_keystone_discovery_fixture(mock_requests) mock_requests.register_uri('POST', "http://no.where/v2.0/tokens", text=self.v2_auth_response) stdout, stderr = self.shell('datastore-version-list XXX') self.assertEqual(expected, (stdout + stderr)) @mock.patch('keystoneauth1.discover.get_version_data', return_value=[{'status': 'stable', 'id': version_id, 'links': links}]) @mock.patch('troveclient.v1.datastores.Datastores.list') @requests_mock.Mocker() def test_get_datastore_list(self, mock_discover, mock_list, mock_requests): expected = '\n'.join([ '+----+------+', '| ID | Name |', '+----+------+', '+----+------+', '' ]) self.make_env() self.register_keystone_discovery_fixture(mock_requests) mock_requests.register_uri('POST', "http://no.where/v2.0/tokens", text=self.v2_auth_response) stdout, stderr = self.shell('datastore-list') self.assertEqual(expected, (stdout + stderr)) class ShellTestKeystoneV3(ShellTest): version_id = 'v3' links = [{'href': 'http://no.where/v3', 'rel': 'self'}] v3_version = fixture.V3Discovery(V3_URL) v3_version.updated_str = UPDATED test_service_catalog = [{ "endpoints": [{ "url": "http://no.where/v1.0/", "region": "RegionOne", "interface": "public" }, { "url": "http://no.where/v1.0", "region": "RegionOne", "interface": "internal" }, { "url": "http://no.where/v1.0", "region": "RegionOne", "interface": "admin" }], "type": "database", "name": "trove" }] service_catalog2 = [{ "endpoints": [{ "url": "http://no.where/vXYZ", "region": "RegionOne", "interface": "public" }], "type": "database", "name": "trove" }] v3_auth_response = json.dumps({ "token": { "methods": [ "token", "password" ], "expires_at": "2020-01-01T00:00:10.000123Z", "project": { "domain": { "id": uuid.uuid4().hex, "name": uuid.uuid4().hex }, "id": uuid.uuid4().hex, "name": uuid.uuid4().hex }, "user": { "domain": { "id": uuid.uuid4().hex, "name": uuid.uuid4().hex }, "id": uuid.uuid4().hex, "name": uuid.uuid4().hex }, "issued_at": "2013-05-29T16:55:21.468960Z", "catalog": test_service_catalog }, }) def make_env(self, exclude=None, fake_env=FAKE_V3_ENV): if 'OS_AUTH_URL' in fake_env: fake_env.update({'OS_AUTH_URL': 'http://no.where/v3'}) env = dict((k, v) for k, v in fake_env.items() if k != exclude) self.useFixture(fixtures.MonkeyPatch('os.environ', env)) def register_keystone_discovery_fixture(self, mreq): v3_url = "http://no.where/v3" v3_version = fixture.V3Discovery(v3_url) mreq.register_uri('GET', v3_url, json=_create_ver_list([v3_version]), status_code=200) def test_no_project_id(self): required = ( 'You must provide a ' 'project_id or project_name (with ' 'project_domain_name or project_domain_id) via ' ' --os-project-id (env[OS_PROJECT_ID])' ' --os-project-name (env[OS_PROJECT_NAME]),' ' --os-project-domain-id ' '(env[OS_PROJECT_DOMAIN_ID])' ' --os-project-domain-name ' '(env[OS_PROJECT_DOMAIN_NAME])' ) self.make_env(exclude='OS_PROJECT_ID') try: self.shell('list') except exceptions.CommandError as message: self.assertEqual(required, message.args[0]) else: self.fail('CommandError not raised') @mock.patch('keystoneauth1.discover.get_version_data', return_value=[{'status': 'stable', 'id': version_id, 'links': links}]) @mock.patch('troveclient.v1.datastores.DatastoreVersions.list') @requests_mock.Mocker() def test_datastore_version_list(self, mock_discover, mock_list, mock_requests): expected = '\n'.join([ '+----+------+', '| ID | Name |', '+----+------+', '+----+------+', '' ]) self.make_env() self.register_keystone_discovery_fixture(mock_requests) mock_requests.register_uri('POST', "http://no.where/v3/auth/tokens", headers={'X-Subject-Token': 'fakeToken'}, text=self.v3_auth_response) stdout, stderr = self.shell('datastore-version-list XXX') self.assertEqual(expected, (stdout + stderr)) @mock.patch('keystoneauth1.discover.get_version_data', return_value=[{'status': 'stable', 'id': version_id, 'links': links}]) @mock.patch('troveclient.v1.datastores.Datastores.list') @requests_mock.Mocker() def test_get_datastore_list(self, mock_discover, mock_list, mock_requests): expected = '\n'.join([ '+----+------+', '| ID | Name |', '+----+------+', '+----+------+', '' ]) self.make_env() self.register_keystone_discovery_fixture(mock_requests) mock_requests.register_uri('POST', "http://no.where/v3/auth/tokens", headers={'X-Subject-Token': 'fakeToken'}, text=self.v3_auth_response) stdout, stderr = self.shell('datastore-list') self.assertEqual(expected, (stdout + stderr)) @mock.patch('keystoneauth1.discover.get_version_data', return_value=[{'status': 'stable', 'id': version_id, 'links': links}]) @requests_mock.Mocker() def test_invalid_client_version(self, mock_discover, mock_requests): response = json.loads(self.v3_auth_response) response['token']['catalog'] = self.service_catalog2 self.make_env() self.register_keystone_discovery_fixture(mock_requests) mock_requests.register_uri('POST', "http://no.where/v3/auth/tokens", headers={'X-Subject-Token': 'fakeToken'}, text=json.dumps(response)) try: self.shell('datastore-list') except exceptions.UnsupportedVersion: pass else: self.fail('UnsupportedVersion not raised') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/test_users.py0000664000175000017500000001175200000000000023705 0ustar00zuulzuul00000000000000# 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 testtools from unittest import mock from troveclient.v1 import users """ Unit tests for users.py """ class UserTest(testtools.TestCase): def setUp(self): super(UserTest, self).setUp() self.orig__init = users.User.__init__ users.User.__init__ = mock.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(testtools.TestCase): def setUp(self): super(UsersTest, self).setUp() self.orig__init = users.Users.__init__ users.Users.__init__ = mock.Mock(return_value=None) self.users = users.Users() self.users.api = mock.Mock() self.users.api.client = mock.Mock() self.instance_with_id = mock.Mock() self.instance_with_id.id = 215 def tearDown(self): super(UsersTest, self).tearDown() users.Users.__init__ = self.orig__init def _get_mock_method(self): self._resp = mock.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.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) # test creation with the instance as an object self.users.create(self.instance_with_id, [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.users.delete(self.instance_with_id, 'user1') self.assertEqual('/instances/%s/users/user1' % self.instance_with_id.id, self._url) self._resp.status_code = 400 self.assertRaises(Exception, self.users.delete, 34, 'user1') def test_list(self): page_mock = mock.Mock() self.users._paginated = page_mock self.users.list('instance1') page_mock.assert_called_with('/instances/instance1/users', 'users', None, None) limit = 'test-limit' marker = 'test-marker' self.users.list('instance1', limit, marker) page_mock.assert_called_with('/instances/instance1/users', 'users', limit, marker) def test_update_no_changes(self): self.users.api.client.post = self._get_mock_method() self._resp.status_code = 200 username = 'upd_user' user = self._build_fake_user(username) self.users.create(99, [user]) instance = 'instance1' newuserattr = None self.assertRaises(Exception, self.users.update_attributes, instance, username, newuserattr) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/test_utils.py0000664000175000017500000000766400000000000023713 0ustar00zuulzuul00000000000000# 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 tempfile import testtools from troveclient import utils class UtilsTest(testtools.TestCase): def func(self): pass def test_add_hookable_mixin(self): hook_type = "hook_type" mixin = utils.HookableMixin() mixin.add_hook(hook_type, self.func) self.assertIn(hook_type, mixin._hooks_map) self.assertIn(self.func, mixin._hooks_map[hook_type]) def test_run_hookable_mixin(self): hook_type = "hook_type" mixin = utils.HookableMixin() mixin.add_hook(hook_type, self.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_encode_decode_data(self): text_data_str = 'This is a text string' try: text_data_bytes = bytes('This is a byte stream', 'utf-8') except TypeError: text_data_bytes = bytes('This is a byte stream') random_data_str = os.urandom(12) random_data_bytes = bytearray(os.urandom(12)) special_char_str = '\x00\xFF\x00\xFF\xFF\x00' special_char_bytes = bytearray( [ord(item) for item in special_char_str]) data = [text_data_str, text_data_bytes, random_data_str, random_data_bytes, special_char_str, special_char_bytes] for datum in data: # the deserialized data is always a bytearray try: expected_deserialized = bytearray( [ord(item) for item in datum]) except TypeError: expected_deserialized = bytearray( [item for item in datum]) serialized_data = utils.encode_data(datum) self.assertIsNotNone(serialized_data, "'%s' serialized is None" % datum) deserialized_data = utils.decode_data(serialized_data) self.assertIsNotNone(deserialized_data, "'%s' deserialized is None" % datum) self.assertEqual(expected_deserialized, deserialized_data, "Serialize/Deserialize failed") # Now we write the data to a file and read it back in # to make sure the round-trip doesn't change anything. with tempfile.NamedTemporaryFile() as temp_file: with open(temp_file.name, 'wb') as fh_w: fh_w.write( bytearray([ord(item) for item in serialized_data])) with open(temp_file.name, 'rb') as fh_r: new_serialized_data = fh_r.read() new_deserialized_data = utils.decode_data( new_serialized_data) self.assertIsNotNone(new_deserialized_data, "'%s' deserialized is None" % datum) self.assertEqual(expected_deserialized, new_deserialized_data, "Serialize/Deserialize with files failed") ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/tests/utils.py0000664000175000017500000000663700000000000022653 0ustar00zuulzuul00000000000000# Copyright [2015] Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import os from unittest import mock import fixtures import requests import testtools AUTH_URL = "http://localhost:5002/auth_url" AUTH_URL_V1 = "http://localhost:5002/auth_url/v1.0" AUTH_URL_V2 = "http://localhost:5002/auth_url/v2.0" URL_QUERY_SEPARATOR = '&' URL_SEPARATOR = '?' def order_url(url): """Returns the url with the query strings ordered, if they exist and there's more than one. Otherwise the url is returned unaltered. """ if URL_QUERY_SEPARATOR in url: parts = url.split(URL_SEPARATOR) if len(parts) == 2: queries = sorted(parts[1].split(URL_QUERY_SEPARATOR)) url = URL_SEPARATOR.join( [parts[0], URL_QUERY_SEPARATOR.join(queries)]) return url def _patch_mock_to_raise_for_invalid_assert_calls(): def raise_for_invalid_assert_calls(wrapped): def wrapper(_self, name): valid_asserts = [ 'assert_called_with', 'assert_called_once_with', 'assert_has_calls', 'assert_any_calls'] if name.startswith('assert') and name not in valid_asserts: raise AttributeError('%s is not a valid mock assert method' % name) return wrapped(_self, name) return wrapper mock.Mock.__getattr__ = raise_for_invalid_assert_calls( mock.Mock.__getattr__) # NOTE(gibi): needs to be called only once at import time # to patch the mock lib _patch_mock_to_raise_for_invalid_assert_calls() class TestCase(testtools.TestCase): TEST_REQUEST_BASE = { 'verify': True, } def setUp(self): super(TestCase, self).setUp() if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or os.environ.get('OS_STDOUT_CAPTURE') == '1'): stdout = self.useFixture(fixtures.StringStream('stdout')).stream self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout)) if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or os.environ.get('OS_STDERR_CAPTURE') == '1'): stderr = self.useFixture(fixtures.StringStream('stderr')).stream self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr)) class TestResponse(requests.Response): """Class used to wrap requests.Response and provide some convenience to initialize with a dict """ def __init__(self, data): super(TestResponse, self).__init__() self._text = None if isinstance(data, dict): self.status_code = data.get('status_code') self.headers = data.get('headers') # Fake the text attribute to streamline Response creation self._text = data.get('text') else: self.status_code = data @property def text(self): return self._text ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/utils.py0000664000175000017500000002541700000000000021506 0ustar00zuulzuul00000000000000# 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 base64 import json import os import uuid from openstackclient.identity import common as identity_common from oslo_utils import encodeutils from oslo_utils import uuidutils import prettytable from troveclient.apiclient import exceptions 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 environment variables. Returns the first environment variable set if none are non-empty, defaults to '' or keyword arg default """ for v in vars: value = os.environ.get(v, None) if value: return value return kwargs.get('default', '') def add_arg(f, *args, **kwargs): """Bind CLI arguments to a shell.py `do_foo` function.""" if not hasattr(f, 'arguments'): f.arguments = [] # NOTE(sirp): avoid dups that can occur when the module is shared across # tests. if (args, kwargs) not in f.arguments: # Because of the semantics of decorator composition if we just append # to the options list positional options will appear to be backwards. f.arguments.insert(0, (args, kwargs)) def unauthenticated(f): """Adds 'unauthenticated' attribute to decorated function. Usage:: @unauthenticated def mymethod(f): ... """ f.unauthenticated = True return f def isunauthenticated(f): """Decorator to mark authentication-non-required. 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 _output_override(objs, print_as): """Output override flag checking. If an output override global flag is set, print with override raise BaseException if no printing was overridden. """ if globals().get('json_output', False): if print_as == 'list': new_objs = [] for o in objs: new_objs.append(o._info) elif print_as == 'dict': new_objs = objs # pretty print the json print(json.dumps(new_objs, indent=' ')) else: raise BaseException('No valid output override') def print_list(objs, fields, formatters={}, order_by=None, obj_is_dict=False, labels={}): try: _output_override(objs, 'list') return except BaseException: pass # Make nice labels from the fields, if not provided in the labels arg if not labels: labels = {} for field in fields: if field not in labels: # No underscores (use spaces instead) and uppercase any ID's label = field.replace("_", " ").replace(" id", " ID") # Uppercase anything else that's less than 3 chars if len(label) < 3: label = label.upper() # Capitalize each word otherwise else: label = ' '.join(word[0].upper() + word[1:] for word in label.split()) labels[field] = label pt = prettytable.PrettyTable( [labels[field] for field in fields], caching=False) # set the default alignment to left-aligned align = dict((labels[field], 'l') for field in fields) set_align = True for obj in objs: row = [] for field in fields: if formatters and field in formatters: data = formatters[field](obj) elif obj_is_dict: data = obj.get(field, '') else: data = getattr(obj, field, '') if isinstance(data, str): row.append(data.encode('utf-8')) else: row.append(str(data)) # set the alignment to right-aligned if it's a numeric if set_align and hasattr(data, '__int__'): align[labels[field]] = 'r' set_align = False pt.add_row(row) pt._align = align if not order_by: order_by = fields[0] order_by = labels[order_by] print(pt.get_string(sortby=order_by)) def print_dict(d, key="Property"): try: _output_override(d, 'dict') return except BaseException: pass pt = prettytable.PrettyTable([key, 'Value'], caching=False) pt.align = 'l' [pt.add_row(list(r)) for r in d.items()] print(pt.get_string(sortby=key)) def get_resource_id(manager, id_or_name): if not uuidutils.is_uuid_like(id_or_name): try: id_or_name = get_resource_id_by_name(manager, id_or_name) except Exception as e: msg = ("Failed to get resource ID for %s, error: %s" % (id_or_name, str(e))) raise exceptions.CommandError(msg) return id_or_name def get_resource_id_by_name(manager, name): resource = manager.find(name=name) return resource.id def get_project_id(manager, id_or_name): if not uuidutils.is_uuid_like(id_or_name): try: project = identity_common.find_project(manager, id_or_name) id_or_name = project.id except Exception as e: msg = ("Failed to get project ID for %s, error: %s" % (id_or_name, str(e))) raise exceptions.CommandError(msg) return id_or_name def find_resource(manager, name_or_id): """Helper for the _find_* methods. This method should be replaced with osc_utils.find_resource() """ # first try to get entity as integer id # When the 'name_or_id' is int, covert it to string. # Reason is that manager cannot find instance when name_or_id # is integer and instance name is digital. # Related to bug/1740015. if isinstance(name_or_id, int): name_or_id = str(name_or_id) try: return manager.get(name_or_id) except 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) def is_admin(cs): is_admin = False try: is_admin = 'admin' in cs.client.auth.auth_ref.role_names except Exception: print("Warning: Could not determine current role. Assuming non-admin") pass return is_admin class HookableMixin(object): """Mixin so classes can register and run hooks.""" _hooks_map = {} @classmethod def add_hook(cls, hook_type, hook_func): if hook_type not in cls._hooks_map: cls._hooks_map[hook_type] = [] cls._hooks_map[hook_type].append(hook_func) @classmethod def run_hooks(cls, hook_type, *args, **kwargs): hook_funcs = cls._hooks_map.get(hook_type) or [] for hook_func in hook_funcs: hook_func(*args, **kwargs) def safe_issubclass(*args): """Like issubclass, but will just return False if not a class.""" try: if issubclass(*args): return True except TypeError: pass return False def is_uuid_like(val): """Returns validation of a value as a UUID. For our purposes, a UUID is a canonical form string: aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa """ try: return str(uuid.UUID(val)) == val except (TypeError, ValueError, AttributeError): return False def encode_data(data): """Encode the data using the base64 codec.""" try: # py27str - if we've got text data, this should encode it # py27aa/py34aa - if we've got a bytearray, this should work too encoded = str(base64.b64encode(data).decode('utf-8')) except TypeError: # py34str - convert to bytes first, then we can encode data_bytes = bytes([ord(item) for item in data]) encoded = base64.b64encode(data_bytes).decode('utf-8') return encoded def decode_data(data): """Encode the data using the base64 codec.""" # py27 & py34 seem to understand bytearray the same return bytearray([item for item in base64.b64decode(data)]) def do_action_with_msg(action, success_msg): """Helper to run an action with return message.""" action print(success_msg) def do_action_on_many(action, resources, success_msg, error_msg): """Helper to run an action on many resources.""" failure_flag = False for resource in resources: try: action(resource) print(success_msg % resource) except Exception as e: failure_flag = True print(encodeutils.safe_encode(str(e))) if failure_flag: raise exceptions.CommandError(error_msg) ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1726234752.494968 python-troveclient-8.6.0/troveclient/v1/0000775000175000017500000000000000000000000020311 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/v1/__init__.py0000664000175000017500000000000000000000000022410 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/v1/accounts.py0000664000175000017500000000410100000000000022476 0ustar00zuulzuul00000000000000# 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 import common 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) common.check_for_exceptions(resp, body, url) 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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/v1/backup_strategy.py0000664000175000017500000000423000000000000024051 0ustar00zuulzuul00000000000000# Copyright 2020 Catalyst Cloud # # 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 import common class BackupStrategy(base.Resource): def __repr__(self): return "" % (self.project_id, self.instance_id) class BackupStrategiesManager(base.ManagerWithFind): resource_class = BackupStrategy def list(self, instance_id=None, project_id=None): query_strings = {} if instance_id: query_strings["instance_id"] = instance_id if project_id: query_strings["project_id"] = project_id url = common.append_query_strings('/backup_strategies', **query_strings) return self._list(url, "backup_strategies") def create(self, instance_id=None, swift_container=None): backup_strategy = {} if instance_id: backup_strategy['instance_id'] = instance_id if swift_container: backup_strategy['swift_container'] = swift_container body = {"backup_strategy": backup_strategy} return self._create("/backup_strategies", body, "backup_strategy") def delete(self, instance_id=None, project_id=None): url = "/backup_strategies" query_strings = {} if instance_id: query_strings["instance_id"] = instance_id if project_id: query_strings["project_id"] = project_id url = common.append_query_strings('/backup_strategies', **query_strings) resp, body = self._delete(url) common.check_for_exceptions(resp, body, url) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/v1/backups.py0000664000175000017500000002514400000000000022321 0ustar00zuulzuul00000000000000# 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 json import uuid from mistralclient.api.client import client as mistral_client from troveclient import base from troveclient import common class Backup(base.Resource): """Backup is a resource used to hold backup information.""" def __repr__(self): return "" % self.name class Schedule(base.Resource): """Schedule is a resource used to hold information about scheduled backups. """ def __repr__(self): return "" % self.name class ScheduleExecution(base.Resource): """ScheduleExecution is a resource used to hold information about the execution of a scheduled backup. """ 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, datastore=None, instance_id=None, all_projects=False, project_id=None): """Get a list of all backups.""" query_strings = {} if datastore: query_strings["datastore"] = datastore if instance_id: query_strings["instance_id"] = instance_id if all_projects: query_strings["all_projects"] = True if project_id: query_strings["project_id"] = project_id return self._paginated("/backups", "backups", limit, marker, query_strings) def create(self, name, instance, description=None, parent_id=None, incremental=False, swift_container=None, restore_from=None, restore_ds_version=None, restore_size=None): """Create or restore a new backup. :param name: name for backup. :param instance: instance to backup. :param description: (optional). :param parent_id: base for incremental backup (optional). :param incremental: flag to indicate incremental backup based on last backup :param swift_container: Swift container name. :param restore_from: The original backup data location, typically this is a Swift object URL. :param restore_ds_version: ID of the local datastore version corresponding to the original backup. :param restore_size: The original backup size. :returns: :class:`Backups` """ body = { "backup": { "name": name, } } if restore_from: body['backup'].update({ 'restore_from': { 'remote_location': restore_from, 'local_datastore_version_id': restore_ds_version, 'size': restore_size } }) else: body['backup']['incremental'] = int(incremental) if instance: body['backup']['instance'] = base.getid(instance) if description: body['backup']['description'] = description if parent_id: body['backup']['parent_id'] = parent_id if swift_container: body['backup']['swift_container'] = swift_container return self._create("/backups", body, "backup") def delete(self, backup): """Delete the specified backup. :param backup: The backup to delete """ url = "/backups/%s" % base.getid(backup) resp, body = self.api.client.delete(url) common.check_for_exceptions(resp, body, url) backup_create_workflow = "trove.backup_create" def _get_mistral_client(self): if hasattr(self.api.client, 'auth'): auth_url = self.api.client.auth.auth_url user = self.api.client.auth._username key = self.api.client.auth._password tenant_name = self.api.client.auth._project_name else: auth_url = self.api.client.auth_url user = self.api.client.user key = self.api.client.password tenant_name = self.api.client.projectid return mistral_client(auth_url=auth_url, username=user, api_key=key, project_name=tenant_name) def _build_schedule(self, cron_trigger, wf_input): if isinstance(wf_input, str): wf_input = json.loads(wf_input) sched_info = {"id": cron_trigger.name, "name": wf_input["name"], "instance": wf_input['instance'], "parent_id": wf_input.get('parent_id', None), "created_at": cron_trigger.created_at, "next_execution_time": cron_trigger.next_execution_time, "pattern": cron_trigger.pattern, "input": cron_trigger.workflow_input } if hasattr(cron_trigger, 'updated_at'): sched_info["updated_at"] = cron_trigger.updated_at return Schedule(self, sched_info, loaded=True) def schedule_create(self, instance, pattern, name, description=None, incremental=None, mistral_client=None): """Create a new schedule to backup the given instance. :param instance: instance to backup. :param: pattern: cron pattern for schedule. :param name: name for backup. :param description: (optional). :param incremental: flag for incremental backup (optional). :returns: :class:`Backups` """ if not mistral_client: mistral_client = self._get_mistral_client() inst_id = base.getid(instance) cron_name = str(uuid.uuid4()) wf_input = {"instance": inst_id, "name": name, "description": description, "incremental": incremental } cron_trigger = mistral_client.cron_triggers.create( cron_name, self.backup_create_workflow, pattern=pattern, workflow_input=wf_input) return self._build_schedule(cron_trigger, wf_input) def schedule_list(self, instance, mistral_client=None): """Get a list of all backup schedules for an instance. :param: instance for which to list schedules. :rtype: list of :class:`Schedule`. """ inst_id = base.getid(instance) if not mistral_client: mistral_client = self._get_mistral_client() return [self._build_schedule(cron_trig, cron_trig.workflow_input) for cron_trig in mistral_client.cron_triggers.list() if inst_id in cron_trig.workflow_input] def schedule_show(self, schedule, mistral_client=None): """Get details of a backup schedule. :param: schedule to show. :rtype: :class:`Schedule`. """ if isinstance(schedule, Schedule): schedule = schedule.id if not mistral_client: mistral_client = self._get_mistral_client() schedule = mistral_client.cron_triggers.get(schedule) return self._build_schedule(schedule, schedule.workflow_input) def schedule_delete(self, schedule, mistral_client=None): """Remove a given backup schedule. :param schedule: schedule to delete. """ if isinstance(schedule, Schedule): schedule = schedule.id if not mistral_client: mistral_client = self._get_mistral_client() mistral_client.cron_triggers.delete(schedule) def execution_list(self, schedule, mistral_client=None, marker='', limit=None): """Get a list of all executions of a scheduled backup. :param: schedule for which to list executions. :rtype: list of :class:`ScheduleExecution`. """ if isinstance(schedule, Schedule): schedule = schedule.id if isinstance(marker, ScheduleExecution): marker = getattr(marker, 'id') if not mistral_client: mistral_client = self._get_mistral_client() cron_trigger = mistral_client.cron_triggers.get(schedule) ct_input = json.loads(cron_trigger.workflow_input) def mistral_execution_generator(): m = marker while True: try: the_list = mistral_client.executions.list( marker=m, limit=50, sort_dirs='desc' ) if the_list: for the_item in the_list: yield the_item m = the_list[-1].id else: return except StopIteration: return def execution_list_generator(): yielded = 0 for sexec in mistral_execution_generator(): if (sexec.workflow_name == cron_trigger.workflow_name and ct_input == json.loads(sexec.input)): yield ScheduleExecution(self, sexec.to_dict(), loaded=True) yielded += 1 if limit and yielded == limit: return return list(execution_list_generator()) def execution_delete(self, execution, mistral_client=None): """Remove a given schedule execution. :param id: id of execution to remove. """ exec_id = (execution.id if isinstance(execution, ScheduleExecution) else execution) if isinstance(execution, ScheduleExecution): execution = execution.name if not mistral_client: mistral_client = self._get_mistral_client() mistral_client.executions.delete(exec_id) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/v1/client.py0000664000175000017500000001216000000000000022141 0ustar00zuulzuul00000000000000# 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 as trove_client from troveclient.v1 import backup_strategy from troveclient.v1 import backups from troveclient.v1 import clusters from troveclient.v1 import configurations from troveclient.v1 import databases from troveclient.v1 import datastores from troveclient.v1 import flavors from troveclient.v1 import instances from troveclient.v1 import limits from troveclient.v1 import management from troveclient.v1 import metadata from troveclient.v1 import modules from troveclient.v1 import quota from troveclient.v1 import root from troveclient.v1 import security_groups from troveclient.v1 import users from troveclient.v1 import volume_types class Client(object): """Top-level object to access the OpenStack Database API. Create an instance with your creds:: >> client = Client(USERNAME, PASSWORD, project_id=TENANT_NAME, auth_url=AUTH_URL) Then call methods on its managers:: >> client.instances.list() ... """ def __init__(self, username=None, password=None, 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, auth_system='keystone', auth_plugin=None, session=None, auth=None, **kwargs): # self.limits = limits.LimitsManager(self) # extensions self.flavors = flavors.Flavors(self) self.volume_types = volume_types.VolumeTypes(self) self.users = users.Users(self) self.databases = databases.Databases(self) self.backups = backups.Backups(self) self.backup_strategies = backup_strategy.BackupStrategiesManager(self) self.clusters = clusters.Clusters(self) self.instances = instances.Instances(self) self.limits = limits.Limits(self) self.root = root.Root(self) self.security_group_rules = security_groups.SecurityGroupRules(self) self.security_groups = security_groups.SecurityGroups(self) self.datastores = datastores.Datastores(self) self.datastore_versions = datastores.DatastoreVersions(self) self.configurations = configurations.Configurations(self) self.configuration_parameters = configurations.ConfigurationParameters( self) self.metadata = metadata.Metadata(self) self.modules = modules.Modules(self) self.quota = quota.Quotas(self) self.mgmt_instances = management.Management(self) self.mgmt_ds_versions = management.MgmtDatastoreVersions(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 = trove_client._construct_http_client( username=username, password=password, project_id=project_id, auth_url=auth_url, insecure=insecure, timeout=timeout, tenant_id=tenant_id, proxy_token=proxy_token, proxy_tenant_id=proxy_tenant_id, region_name=region_name, endpoint_type=endpoint_type, service_type=service_type, service_name=service_name, database_service_name=database_service_name, retries=retries, http_log_debug=http_log_debug, cacert=cacert, bypass_url=bypass_url, auth_system=auth_system, auth_plugin=auth_plugin, session=session, auth=auth, **kwargs) 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() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/v1/clusters.py0000664000175000017500000001130700000000000022531 0ustar00zuulzuul00000000000000# Copyright (c) 2014 eBay Software 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 import base from troveclient import common class Cluster(base.Resource): """A Cluster is an opaque cluster used to store Database clusters.""" def __repr__(self): return "" % self.name def delete(self): """Delete the cluster.""" self.manager.delete(self) def force_delete(self): """Force delete the cluster""" self.manager.reset_status(self) self.manager.delete(self) class Clusters(base.ManagerWithFind): """Manage :class:`Cluster` resources.""" resource_class = Cluster def create(self, name, datastore, datastore_version, instances=None, locality=None, extended_properties=None, configuration=None): """Create (boot) a new cluster.""" body = {"cluster": { "name": name }} datastore_obj = { "type": datastore, "version": datastore_version } body["cluster"]["datastore"] = datastore_obj if instances: body["cluster"]["instances"] = instances if locality: body["cluster"]["locality"] = locality if extended_properties: body["cluster"]["extended_properties"] = extended_properties if configuration: body["cluster"]["configuration"] = configuration return self._create("/clusters", body, "cluster") def list(self, limit=None, marker=None): """Get a list of all clusters. :rtype: list of :class:`Cluster`. """ return self._paginated("/clusters", "clusters", limit, marker) def get(self, cluster): """Get a specific cluster. :rtype: :class:`Cluster` """ return self._get("/clusters/%s" % base.getid(cluster), "cluster") def delete(self, cluster): """Delete the specified cluster. :param cluster: The cluster to delete """ url = "/clusters/%s" % base.getid(cluster) resp, body = self.api.client.delete(url) common.check_for_exceptions(resp, body, url) def reset_status(self, cluster): """Reset the status of a cluster :param cluster: The cluster to reset """ body = {'reset-status': {}} self._action(cluster, body) def _action(self, cluster, body): """Perform a cluster "action" -- grow/shrink/etc.""" url = "/clusters/%s" % base.getid(cluster) resp, body = self.api.client.post(url, body=body) common.check_for_exceptions(resp, body, url) if body: return self.resource_class(self, body['cluster'], loaded=True) return body def add_shard(self, cluster): """Adds a shard to the specified cluster. :param cluster: The cluster to add a shard to """ url = "/clusters/%s" % base.getid(cluster) body = {"add_shard": {}} resp, body = self.api.client.post(url, body=body) common.check_for_exceptions(resp, body, url) if body: return self.resource_class(self, body, loaded=True) return body def grow(self, cluster, instances=None): """Grow a cluster. :param cluster: The cluster to grow :param instances: List of instances to add """ body = {"grow": instances} return self._action(cluster, body) def shrink(self, cluster, instances=None): """Shrink a cluster. :param cluster: The cluster to shrink :param instances: List of instances to drop """ body = {"shrink": instances} return self._action(cluster, body) def upgrade(self, cluster, datastore_version): """Upgrades a cluster to a new datastore version. :param cluster: The cluster to upgrade :param datastore_version: Datastore version to which to upgrade """ body = {"upgrade": {'datastore_version': datastore_version}} return self._action(cluster, body) class ClusterStatus(object): ACTIVE = "ACTIVE" BUILD = "BUILD" FAILED = "FAILED" SHUTDOWN = "SHUTDOWN" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/v1/configurations.py0000664000175000017500000001244000000000000023716 0ustar00zuulzuul00000000000000# Copyright (c) 2014 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json from troveclient import base from troveclient import common class Configuration(base.Resource): """Configuration is a resource used to hold configuration information.""" def __repr__(self): return "" % self.name class Configurations(base.ManagerWithFind): """Manage :class:`Configurations` information.""" resource_class = Configuration def get(self, configuration): """Get a specific configuration. :rtype: :class:`Configurations` """ return self._get("/configurations/%s" % base.getid(configuration), "configuration") def instances(self, configuration, limit=None, marker=None): """Get a list of instances on a configuration. :rtype: :class:`Configurations` """ return self._paginated("/configurations/%s/instances" % base.getid(configuration), "instances", limit, marker) def list(self, limit=None, marker=None): """Get a list of all configurations. :rtype: list of :class:`Configurations`. """ return self._paginated("/configurations", "configurations", limit, marker) def create(self, name, values, description=None, datastore=None, datastore_version=None, datastore_version_number=None): """Create a new configuration.""" body = { "configuration": { "name": name, "values": json.loads(values) } } datastore_obj = {} if datastore: datastore_obj["type"] = datastore if datastore_version: datastore_obj["version"] = datastore_version if datastore_version_number: datastore_obj["version_number"] = datastore_version_number if datastore_obj: body["configuration"]["datastore"] = datastore_obj if description: body['configuration']['description'] = description return self._create("/configurations", body, "configuration") def update(self, configuration, values, name=None, description=None): """Update an existing configuration.""" body = { "configuration": { "values": json.loads(values) } } if name: body['configuration']['name'] = name if description: body['configuration']['description'] = description url = "/configurations/%s" % base.getid(configuration) resp, body = self.api.client.put(url, body=body) common.check_for_exceptions(resp, body, url) def edit(self, configuration, values): """Update an existing configuration.""" body = { "configuration": { "values": json.loads(values) } } url = "/configurations/%s" % base.getid(configuration) resp, body = self.api.client.patch(url, body=body) common.check_for_exceptions(resp, body, url) def delete(self, configuration): """Delete the specified configuration. :param configuration: The configuration id to delete """ url = "/configurations/%s" % base.getid(configuration) resp, body = self.api.client.delete(url) common.check_for_exceptions(resp, body, url) class ConfigurationParameter(base.Resource): """Configuration Parameter.""" def __repr__(self): return "" % self.__dict__ class ConfigurationParameters(base.ManagerWithFind): """Manage :class:`ConfigurationParameters` information.""" resource_class = ConfigurationParameter def parameters(self, datastore, version): """Get a list of valid parameters that can be changed.""" return self._list("/datastores/%s/versions/%s/parameters" % (datastore, version), "configuration-parameters") def get_parameter(self, datastore, version, key): """Get a list of valid parameters that can be changed.""" return self._get("/datastores/%s/versions/%s/parameters/%s" % (datastore, version, key)) def parameters_by_version(self, version): """Get a list of valid parameters that can be changed.""" return self._list("/datastores/versions/%s/parameters" % version, "configuration-parameters") def get_parameter_by_version(self, version, key): """Get a list of valid parameters that can be changed.""" return self._get("/datastores/versions/%s/parameters/%s" % (version, key)) # Appease the abc gods def list(self): pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/v1/databases.py0000664000175000017500000000471700000000000022623 0ustar00zuulzuul00000000000000# 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 import common class Database(base.Resource): """Wikipedia definition for database. "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, databases): """Create new databases within the specified instance.""" body = {"databases": databases} url = "/instances/%s/databases" % base.getid(instance) resp, body = self.api.client.post(url, body=body) common.check_for_exceptions(resp, body, url) def delete(self, instance, dbname): """Delete an existing database in the specified instance.""" url = "/instances/%s/databases/%s" % (base.getid(instance), dbname) resp, body = self.api.client.delete(url) common.check_for_exceptions(resp, body, url) def list(self, instance, limit=None, marker=None): """Get a list of all Databases from the instance. :rtype: list of :class:`Database`. """ url = "/instances/%s/databases" % base.getid(instance) return self._paginated(url, "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") ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/v1/datastores.py0000664000175000017500000001233100000000000023034 0ustar00zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # Copyright 2013 Mirantis, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from troveclient import base class Datastore(base.Resource): def __repr__(self): return "" % self.name class DatastoreVersion(base.Resource): def __repr__(self): return "" % self.name def update(self, visibility=None): """Change something in a datastore version.""" self.manager.update(self.datastore, self.id, visibility) class DatastoreVersionMember(base.Resource): def __repr__(self): return "" % self.id class Datastores(base.ManagerWithFind): """Manage :class:`Datastore` resources.""" resource_class = Datastore def __repr__(self): return "" % id(self) def list(self, limit=None, marker=None): """Get a list of all datastores. :rtype: list of :class:`Datastore`. """ return self._paginated("/datastores", "datastores", limit, marker) def get(self, datastore): """Get a specific datastore. :rtype: :class:`Datastore` """ return self._get("/datastores/%s" % base.getid(datastore), "datastore") def delete(self, datastore): """Delete a specific datastore.""" return self._delete("/datastores/%s" % base.getid(datastore)) class DatastoreVersions(base.ManagerWithFind): """Manage :class:`DatastoreVersion` resources.""" resource_class = DatastoreVersion def __repr__(self): return "" % id(self) def list(self, datastore, limit=None, marker=None): """Get a list of all datastore versions. :rtype: list of :class:`DatastoreVersion`. """ return self._paginated("/datastores/%s/versions" % datastore, "versions", limit, marker) def get(self, datastore, datastore_version): """Get a specific datastore version. :rtype: :class:`DatastoreVersion` """ return self._get("/datastores/%s/versions/%s" % (datastore, base.getid(datastore_version)), "version") def get_by_uuid(self, datastore_version): """Get a specific datastore version. :rtype: :class:`DatastoreVersion` """ return self._get("/datastores/versions/%s" % base.getid(datastore_version), "version") def update(self, datastore, datastore_version, visibility): """Update a specific datastore version.""" body = { "datastore_version": { } } if visibility is not None: body["datastore_version"]["visibility"] = visibility url = ("/mgmt/datastores/%s/versions/%s" % (datastore, datastore_version)) return self._update(url, body=body) class DatastoreVersionMembers(base.ManagerWithFind): """Manage :class:`DatastoreVersionMember` resources.""" resource_class = DatastoreVersionMember def __repr__(self): return "" % id(self) def add(self, datastore, datastore_version, tenant): """Add a member to a datastore version.""" body = {"member": tenant} return self._create("/mgmt/datastores/%s/versions/%s/members" % (datastore, datastore_version), body, "datastore_version_member") def delete(self, datastore, datastore_version, member_id): """Delete a member from a datastore version.""" return self._delete("/mgmt/datastores/%s/versions/%s/members/%s" % (datastore, datastore_version, member_id)) def list(self, datastore, datastore_version, limit=None, marker=None): """List members of datastore version.""" return self._list("/mgmt/datastores/%s/versions/%s/members" % (datastore, datastore_version), "datastore_version_members", limit, marker) def get(self, datastore, datastore_version, member_id): """Get a datastore version member.""" return self._get("/mgmt/datastores/%s/versions/%s/members/%s" % (datastore, datastore_version, member_id), "datastore_version_member") def get_by_tenant(self, datastore, tenant, limit=None, marker=None): """List members by tenant id.""" return self._list("/mgmt/datastores/%s/versions/members/%s" % (datastore, tenant), "datastore_version_members", limit, marker) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/v1/diagnostics.py0000664000175000017500000000331700000000000023176 0ustar00zuulzuul00000000000000# 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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/v1/flavors.py0000664000175000017500000000371400000000000022344 0ustar00zuulzuul00000000000000# 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 other things, like RAM size.""" def __init__(self, manager, info, loaded=False): super(Flavor, self).__init__(manager, info, loaded) if self.id is None and self.str_id is not None: self.id = self.str_id 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 list_datastore_version_associated_flavors(self, datastore, version_id): """Get a list of all flavors for the specified datastore type and datastore version . :rtype: list of :class:`Flavor`. """ return self._list("/datastores/%s/versions/%s/flavors" % (datastore, version_id), "flavors") def get(self, flavor): """Get a specific flavor. :rtype: :class:`Flavor` """ return self._get("/flavors/%s" % base.getid(flavor), "flavor") ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/v1/hosts.py0000664000175000017500000000430200000000000022022 0ustar00zuulzuul00000000000000# 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 import common 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) common.check_for_exceptions(resp, body, url) 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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/v1/instances.py0000664000175000017500000004735600000000000022671 0ustar00zuulzuul00000000000000# 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 from troveclient import base from troveclient import common from troveclient import exceptions from troveclient import utils from troveclient.v1 import modules as core_modules from swiftclient import client as swift_client REBOOT_SOFT = 'SOFT' REBOOT_HARD = 'HARD' class Instance(base.Resource): """An Instance is an opaque instance used to store Database instances.""" def list_databases(self): return self.manager.databases.list(self) def delete(self): """Delete the instance.""" self.manager.delete(self) def force_delete(self): """Force delete the instance""" self.manager.reset_status(self) self.manager.delete(self) def restart(self): """Restart the database instance.""" self.manager.restart(self.id) def detach_replica(self): """Stops the replica database from being replicated to.""" self.manager.edit(self.id, detach_replica_source=True) class DatastoreLog(base.Resource): """A DatastoreLog is a log on the database guest instance.""" def __repr__(self): return "" % self.name class Instances(base.ManagerWithFind): """Manage :class:`Instance` resources.""" resource_class = Instance def _get_swift_client(self): if hasattr(self.api.client, 'auth'): auth_url = self.api.client.auth.auth_url user = self.api.client.auth._username key = self.api.client.auth._password tenant_name = self.api.client.auth._project_name auth_version = "3.0" else: auth_url = self.api.client.auth_url user = self.api.client.username key = self.api.client.password tenant_name = self.api.client.tenant auth_version = "2.0" # remove '/tokens' from the end of auth_url so it works for swift token_str = "/tokens" if auth_url.endswith(token_str): auth_url = auth_url[:-len(token_str)] region_name = self.api.client.region_name os_options = {'tenant_name': tenant_name, 'region_name': region_name} return swift_client.Connection( auth_url, user, key, auth_version=auth_version, os_options=os_options) def create(self, name, flavor_id=None, volume=None, databases=None, users=None, restorePoint=None, availability_zone=None, datastore=None, datastore_version=None, nics=None, configuration=None, replica_of=None, replica_count=None, modules=None, locality=None, region_name=None, access=None, datastore_version_number=None, **kwargs): """Create (boot) a new instance.""" body = {"instance": { "name": name, }} datastore_obj = {} if flavor_id: body["instance"]["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 if datastore: datastore_obj["type"] = datastore if datastore_version: datastore_obj["version"] = datastore_version if datastore_version_number: datastore_obj["version_number"] = datastore_version_number if datastore_obj: body["instance"]["datastore"] = datastore_obj if nics: body["instance"]["nics"] = nics if configuration: body["instance"]["configuration"] = base.getid(configuration) if replica_of: body["instance"]["replica_of"] = base.getid(replica_of) if replica_count: body["instance"]["replica_count"] = replica_count if modules: body["instance"]["modules"] = self._get_module_list(modules) if locality: body["instance"]["locality"] = locality if region_name: body["instance"]["region_name"] = region_name if access: body["instance"]["access"] = access return self._create("/instances", body, "instance") def modify(self, instance, configuration=None): """This method is deprecated, use update instead.""" body = { "instance": { } } if configuration is not None: body["instance"]["configuration"] = base.getid(configuration) url = "/instances/%s" % base.getid(instance) resp, body = self.api.client.put(url, body=body) common.check_for_exceptions(resp, body, url) def update(self, instance, configuration=None, name=None, detach_replica_source=False, remove_configuration=False, is_public=None, allowed_cidrs=None): """Update instance. The configuration change, detach_replica and access change cannot be updated at the same time. """ body = { "instance": {} } if configuration and remove_configuration: raise Exception("Cannot attach and detach configuration " "simultaneously.") if remove_configuration: body["instance"]["configuration"] = None if configuration is not None: body["instance"]["configuration"] = base.getid(configuration) if name is not None: body["instance"]["name"] = name if detach_replica_source: body["instance"]["replica_of"] = None if is_public is not None or allowed_cidrs is not None: body["instance"]['access'] = {} if is_public is not None: body["instance"]['access']['is_public'] = is_public if allowed_cidrs is not None: body["instance"]['access']['allowed_cidrs'] = allowed_cidrs url = "/instances/%s" % base.getid(instance) resp, body = self.api.client.put(url, body=body) common.check_for_exceptions(resp, body, url) def upgrade(self, instance, datastore_version): """Upgrades an instance with a new datastore version.""" body = { "instance": { "datastore_version": datastore_version } } url = "/instances/%s" % base.getid(instance) resp, body = self.api.client.patch(url, body=body) common.check_for_exceptions(resp, body, url) def list(self, limit=None, marker=None, include_clustered=False, detailed=False): """Get a list of all instances. :rtype: list of :class:`Instance`. """ detail = "/detail" if detailed else "" url = "/instances%s" % detail return self._paginated(url, "instances", limit, marker, {"include_clustered": include_clustered}) def get(self, instance): """Get a specific instances. :rtype: :class:`Instance` """ return self._get("/instances/%s" % base.getid(instance), "instance") def backups(self, instance, limit=None, marker=None): """Get the list of backups for a specific instance. :param instance: instance for which to list backups :param limit: max items to return :param marker: marker start point :rtype: list of :class:`Backups`. """ url = "/instances/%s/backups" % base.getid(instance) return self._paginated(url, "backups", limit, marker) def delete(self, instance): """Delete the specified instance. :param instance: A reference to the instance to delete """ url = "/instances/%s" % base.getid(instance) resp, body = self.api.client.delete(url) common.check_for_exceptions(resp, body, url) def reset_status(self, instance): """Reset the status of an instance. :param instance: A reference to the instance """ body = {'reset_status': {}} self._action(instance, body) def force_delete(self, instance): """Force delete the specified instance. :param instance: A reference to the instance to force delete """ self.reset_status(instance) self.delete(instance) def _action(self, instance, body): """Perform a server "action" -- reboot/rebuild/resize/etc.""" url = "/instances/%s/action" % base.getid(instance) resp, body = self.api.client.post(url, body=body) common.check_for_exceptions(resp, body, url) if body: return self.resource_class(self, body, loaded=True) return body def resize_volume(self, instance, volume_size): """Resize the volume on an existing instances.""" body = {"resize": {"volume": {"size": volume_size}}} self._action(instance, body) def resize_instance(self, instance, flavor_id): """Resizes an instance with a new flavor.""" body = {"resize": {"flavorRef": flavor_id}} self._action(instance, body) def restart(self, instance): """Restart the database instance. :param instance: The :class:`Instance` (or its ID) of the database instance to restart. """ body = {'restart': {}} self._action(instance, body) def configuration(self, instance): """Get a configuration on instances. :rtype: :class:`Instance` """ return self._get("/instances/%s/configuration" % base.getid(instance), "instance") def promote_to_replica_source(self, instance): """Promote a replica to be the new replica_source of its set :param instance: The :class:`Instance` (or its ID) of the database instance to promote. """ body = {'promote_to_replica_source': {}} self._action(instance, body) def eject_replica_source(self, instance): """Eject a replica source from its set :param instance: The :class:`Instance` (or its ID) of the database instance to eject. """ body = {'eject_replica_source': {}} self._action(instance, body) def modules(self, instance): """Get the list of modules for a specific instance.""" return self._modules_get(instance) def module_query(self, instance): """Query an instance about installed modules.""" return self._modules_get(instance, from_guest=True) def module_retrieve(self, instance, directory=None, prefix=None): """Retrieve the module data file from an instance. This includes the contents of the module data file. """ if directory: try: os.makedirs(directory, exist_ok=True) except TypeError: # py27 try: os.makedirs(directory) except OSError: if not os.path.isdir(directory): raise else: directory = '.' prefix = prefix or '' if prefix and not prefix.endswith('_'): prefix += '_' module_list = self._modules_get( instance, from_guest=True, include_contents=True) saved_modules = {} for module in module_list: filename = '%s%s_%s_%s.dat' % (prefix, module.name, module.datastore, module.datastore_version) full_filename = os.path.expanduser( os.path.join(directory, filename)) with open(full_filename, 'wb') as fh: fh.write(utils.decode_data(module.contents)) saved_modules[module.name] = full_filename return saved_modules def _modules_get(self, instance, from_guest=None, include_contents=None): url = "/instances/%s/modules" % base.getid(instance) query_strings = {} if from_guest is not None: query_strings["from_guest"] = from_guest if include_contents is not None: query_strings["include_contents"] = include_contents url = common.append_query_strings(url, **query_strings) resp, body = self.api.client.get(url) common.check_for_exceptions(resp, body, url) return [core_modules.Module(self, module, loaded=True) for module in body['modules']] def module_apply(self, instance, modules): """Apply modules to an instance.""" url = "/instances/%s/modules" % base.getid(instance) body = {"modules": self._get_module_list(modules)} resp, body = self.api.client.post(url, body=body) common.check_for_exceptions(resp, body, url) return [core_modules.Module(self, module, loaded=True) for module in body['modules']] def _get_module_list(self, modules): """Build a list of module ids.""" module_list = [] for module in modules: module_info = {'id': base.getid(module)} module_list.append(module_info) return module_list def module_remove(self, instance, module): """Remove a module from an instance. """ url = "/instances/%s/modules/%s" % (base.getid(instance), base.getid(module)) resp, body = self.api.client.delete(url) common.check_for_exceptions(resp, body, url) def log_list(self, instance): """Get a list of all guest logs. :param instance: The :class:`Instance` (or its ID) of the database instance to get the log for. :rtype: list of :class:`DatastoreLog`. """ url = '/instances/%s/log' % base.getid(instance) resp, body = self.api.client.get(url) common.check_for_exceptions(resp, body, url) return [DatastoreLog(self, log, loaded=True) for log in body['logs']] def log_show(self, instance, log_name): return self.log_action(instance, log_name) def log_action(self, instance, log_name, enable=None, disable=None, publish=None, discard=None): """Perform action on guest log. :param instance: The :class:`Instance` (or its ID) of the database instance to get the log for. :param log_name: The name of to publish :param enable: Turn on :param disable: Turn off :param publish: Publish log to associated container :param discard: Delete the associated container :rtype: List of :class:`DatastoreLog`. """ body = {"name": log_name} if enable: body.update({'enable': int(enable)}) if disable: body.update({'disable': int(disable)}) if publish: body.update({'publish': int(publish)}) if discard: body.update({'discard': int(discard)}) url = "/instances/%s/log" % base.getid(instance) resp, body = self.api.client.post(url, body=body) common.check_for_exceptions(resp, body, url) return DatastoreLog(self, body['log'], loaded=True) def _get_container_info(self, instance, log_name): try: log_info = self.log_show(instance, log_name) container = log_info.container prefix = log_info.prefix metadata_file = log_info.metafile return container, prefix, metadata_file except swift_client.ClientException as ex: if ex.http_status == 404: raise exceptions.GuestLogNotFoundError() raise def log_generator(self, instance, log_name, lines=50, swift=None): """Return generator to yield the last lines of guest log. :param instance: The :class:`Instance` (or its ID) of the database instance to get the log for. :param log_name: The name of to publish :param lines: Display last lines of log (0 for all lines) :param swift: Connection to swift :rtype: generator function to yield log as chunks. """ if not swift: swift = self._get_swift_client() def _log_generator(instance, log_name, lines, swift): try: container, prefix, metadata_file = self._get_container_info( instance, log_name) head, body = swift.get_container(container, prefix=prefix) log_obj_to_display = [] if lines: total_lines = lines partial_results = False parts = sorted(body, key=lambda obj: obj['last_modified'], reverse=True) for part in parts: obj_hdrs = swift.head_object(container, part['name']) obj_lines = int(obj_hdrs['x-object-meta-lines']) log_obj_to_display.insert(0, part) if obj_lines >= lines: partial_results = True break lines -= obj_lines if not partial_results: lines = total_lines part = log_obj_to_display.pop(0) hdrs, log_obj = swift.get_object(container, part['name']) log_by_lines = log_obj.decode().splitlines() yield "\n".join(log_by_lines[-1 * lines:]) + "\n" else: # Show all the logs log_obj_to_display = sorted( body, key=lambda obj: obj['last_modified']) for log_part in log_obj_to_display: headers, log_obj = swift.get_object(container, log_part['name']) yield log_obj.decode() except swift_client.ClientException as ex: if ex.http_status == 404: raise exceptions.GuestLogNotFoundError() raise return lambda: _log_generator(instance, log_name, lines, swift) def log_save(self, instance, log_name, filename=None): """Saves a guest log to a file. :param instance: The :class:`Instance` (or its ID) of the database instance to get the log for. :param log_name: The name of to publish :rtype: Filename to which log was saved """ written_file = filename or ( 'trove-' + instance.id + '-' + log_name + ".log") log_gen = self.log_generator(instance, log_name, lines=0) with open(written_file, 'w') as f: for log_obj in log_gen(): f.write(log_obj) return written_file class InstanceStatus(object): ACTIVE = "ACTIVE" BLOCKED = "BLOCKED" BUILD = "BUILD" FAILED = "FAILED" REBOOT = "REBOOT" RESIZE = "RESIZE" SHUTDOWN = "SHUTDOWN" RESTART_REQUIRED = "RESTART_REQUIRED" PROMOTING = "PROMOTING" EJECTING = "EJECTING" LOGGING = "LOGGING" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/v1/limits.py0000664000175000017500000000303200000000000022162 0ustar00zuulzuul00000000000000# 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.apiclient import exceptions from troveclient import base 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, 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 list(self): """Retrieve the limits.""" return self._list("/limits", "limits") ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/v1/management.py0000664000175000017500000002673000000000000023007 0ustar00zuulzuul00000000000000# 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 json from troveclient import base from troveclient import common from troveclient.v1 import clusters from troveclient.v1 import configurations from troveclient.v1 import datastores from troveclient.v1 import flavors from troveclient.v1 import instances class RootHistory(base.Resource): def __repr__(self): return ("" % (self.id, self.created, self.user)) class Management(base.ManagerWithFind): """Manage :class:`Instances` resources.""" resource_class = instances.Instance 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, **kwargs): """A wrapper for list method.""" return self.list(**kwargs) def list(self, limit=None, marker=None, deleted=False, **kwargs): """Get all the database instances.""" url = "/mgmt/instances" kwargs["deleted"] = deleted return self._paginated(url, "instances", limit, marker, query_strings=kwargs) 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) common.check_for_exceptions(resp, body, url) 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) def rebuild(self, instance_id, image_id): """Rebuild the underlying OS.""" body = {'rebuild': {'image_id': image_id}} self._action(instance_id, body) class MgmtClusters(base.ManagerWithFind): """Manage :class:`Cluster` resources.""" resource_class = clusters.Cluster # Appease the abc gods def list(self): pass def show(self, cluster): """Get details of one cluster.""" return self._get("/mgmt/clusters/%s" % base.getid(cluster), 'cluster') def index(self, deleted=None, limit=None, marker=None): """Show an overview of all local clusters. Optionally, filter by deleted status. :rtype: list of :class:`Cluster`. """ form = '' if deleted is not None: if deleted: form = "?deleted=true" else: form = "?deleted=false" url = "/mgmt/clusters%s" % form return self._paginated(url, "clusters", limit, marker) def _action(self, cluster_id, body): """Perform a cluster action, e.g. reset-task.""" url = "/mgmt/clusters/%s/action" % cluster_id resp, body = self.api.client.post(url, body=body) common.check_for_exceptions(resp, body, url) def reset_task(self, cluster_id): """Reset the current cluster task to NONE.""" body = {'reset-task': {}} self._action(cluster_id, body) class MgmtFlavors(base.ManagerWithFind): """Manage :class:`Flavor` resources.""" resource_class = flavors.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") class MgmtConfigurationParameters(configurations.ConfigurationParameters): def create(self, version, name, restart_required, data_type, max_size=None, min_size=None): """Mgmt call to create a new configuration parameter.""" body = { "configuration-parameter": { "name": name, "restart_required": int(restart_required), "data_type": data_type, } } if max_size is not None: body["configuration-parameter"]["max_size"] = max_size if min_size is not None: body["configuration-parameter"]["min_size"] = min_size url = "/mgmt/datastores/versions/%s/parameters" % version resp, body = self.api.client.post(url, body=body) common.check_for_exceptions(resp, body, url) def list_all_parameter_by_version(self, version): """List all configuration parameters deleted or not.""" return self._list("/mgmt/datastores/versions/%s/parameters" % version, "configuration-parameters") def get_any_parameter_by_version(self, version, key): """Get any configuration parameter deleted or not.""" return self._get("/mgmt/datastores/versions/%s/parameters/%s" % (version, key)) def modify(self, version, name, restart_required, data_type, max_size=None, min_size=None): """Mgmt call to modify an existing configuration parameter.""" body = { "configuration-parameter": { "name": name, "restart_required": int(restart_required), "data_type": data_type, } } if max_size is not None: body["configuration-parameter"]["max_size"] = max_size if min_size is not None: body["configuration-parameter"]["min_size"] = min_size output = { 'version': version, 'parameter_name': name } url = ("/mgmt/datastores/versions/%(version)s/" "parameters/%(parameter_name)s" % output) resp, body = self.api.client.put(url, body=body) common.check_for_exceptions(resp, body, url) def delete(self, version, name): """Mgmt call to delete a configuration parameter.""" output = { 'version_id': version, 'parameter_name': name } url = ("/mgmt/datastores/versions/%(version_id)s/" "parameters/%(parameter_name)s" % output) resp, body = self.api.client.delete(url) common.check_for_exceptions(resp, body, url) class MgmtDatastoreVersions(base.ManagerWithFind): """Manage :class:`DatastoreVersion` resources.""" resource_class = datastores.DatastoreVersion def list(self, limit=None, marker=None): """List all datastore versions.""" return self._paginated("/mgmt/datastore-versions", "versions", limit, marker) def get(self, datastore_version_id): """Get details of a datastore version.""" return self._get("/mgmt/datastore-versions/%s" % datastore_version_id, "version") def create(self, name, datastore_name, datastore_manager, image, packages=None, registry_ext=None, repl_strategy=None, active='true', default='false', image_tags=[], version=None): """Create a new datastore version.""" packages = packages or [] body = { "version": { "name": name, "datastore_name": datastore_name, "datastore_manager": datastore_manager, "image_tags": image_tags, "packages": packages, "active": json.loads(active), "default": json.loads(default) } } if image: body['version']['image'] = image if registry_ext: body['version']['registry_ext'] = registry_ext if repl_strategy: body['version']['repl_strategy'] = repl_strategy if version: body['version']['version'] = version return self._create("/mgmt/datastore-versions", body, None, True) def edit(self, datastore_version_id, datastore_manager=None, image=None, packages=None, registry_ext=None, repl_strategy=None, active=None, default=None, image_tags=None, name=None): """Update a datastore-version.""" packages = packages or [] body = {} if datastore_manager is not None: body['datastore_manager'] = datastore_manager if image is not None: body['image'] = image if packages: body['packages'] = packages if registry_ext: body['registry_ext'] = registry_ext if repl_strategy: body['repl_strategy'] = repl_strategy if active is not None: body['active'] = json.loads(active) if default is not None: body['default'] = json.loads(default) if image_tags is not None: body['image_tags'] = image_tags if name: body['name'] = name url = ("/mgmt/datastore-versions/%s" % datastore_version_id) resp, body = self.api.client.patch(url, body=body) common.check_for_exceptions(resp, body, url) def delete(self, datastore_version_id): """Delete a datastore version.""" url = ("/mgmt/datastore-versions/%s" % datastore_version_id) resp, body = self.api.client.delete(url) common.check_for_exceptions(resp, body, url) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/v1/metadata.py0000664000175000017500000000560700000000000022453 0ustar00zuulzuul00000000000000# Copyright 2014 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 json from troveclient import base class MetadataResource(base.Resource): def __getitem__(self, item): return self.__dict__[item] def __contains__(self, item): if item in self.__dict__: return True else: return False class Metadata(base.Manager): resource_class = MetadataResource def list(self, instance_id): return self._get('/instances/%s/metadata' % instance_id, 'metadata') def show(self, instance_id, key): return self._get('/instances/%s/metadata/%s' % (instance_id, key), 'metadata') def create(self, instance_id, key, value): body = { 'metadata': { 'value': self._parse_value(value) } } return self._create( '/instances/%s/metadata/%s' % (instance_id, key), body, 'metadata') def update(self, instance_id, key, newkey, value): body = { 'metadata': { 'key': newkey, 'value': self._parse_value(value) } } return self._update( '/instances/%s/metadata/%s' % (instance_id, key), body) def edit(self, instance_id, key, value): body = { 'metadata': { 'value': self._parse_value(value) } } return self._edit( '/instances/%s/metadata/%s' % (instance_id, key), body) def delete(self, instance_id, key): return self._delete('/instances/%s/metadata/%s' % (instance_id, key)) @staticmethod def _parse_value(value): """This method is used to parse if a string was passed to any of the methods we should first try to deserialize it using json.loads. This is needed to facilitate users passing serialized structures from the cli. :param value: A value of type dict, list, tuple, int, float, str :returns value: """ # NOTE(imsplitbit): if you give _parse_value invalid json you get # the string passed back to you. if isinstance(value, str): try: value = json.loads(value) except ValueError: # the value passed in was a string but not json pass return value ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/v1/modules.py0000664000175000017500000001623100000000000022336 0ustar00zuulzuul00000000000000# Copyright 2016 Tesora, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # from troveclient import base from troveclient import common from troveclient import utils class Module(base.Resource): ALL_KEYWORD = 'all' def __repr__(self): return "" % self.name def __hash__(self): return hash(repr(self)) def __eq__(self, other): if isinstance(other, self.__class__): return self.__dict__ == other.__dict__ else: return False class Modules(base.ManagerWithFind): """Manage :class:`Module` resources.""" resource_class = Module def create(self, name, module_type, contents, description=None, all_tenants=None, datastore=None, datastore_version=None, auto_apply=None, visible=None, live_update=None, priority_apply=None, apply_order=None, full_access=None): """Create a new module.""" contents = utils.encode_data(contents) body = {"module": { "name": name, "module_type": module_type, "contents": contents, }} if description is not None: body["module"]["description"] = description datastore_obj = {} if datastore: datastore_obj["type"] = datastore if datastore_version: datastore_obj["version"] = datastore_version if datastore_obj: body["module"]["datastore"] = datastore_obj if all_tenants is not None: body["module"]["all_tenants"] = int(all_tenants) if auto_apply is not None: body["module"]["auto_apply"] = int(auto_apply) if visible is not None: body["module"]["visible"] = int(visible) if live_update is not None: body["module"]["live_update"] = int(live_update) if priority_apply is not None: body["module"]["priority_apply"] = int(priority_apply) if apply_order is not None: body["module"]["apply_order"] = apply_order if full_access is not None: body["module"]["full_access"] = int(full_access) return self._create("/modules", body, "module") def update(self, module, name=None, module_type=None, contents=None, description=None, all_tenants=None, datastore=None, datastore_version=None, auto_apply=None, visible=None, live_update=None, all_datastores=None, all_datastore_versions=None, priority_apply=None, apply_order=None, full_access=None): """Update an existing module. Passing in datastore=None or datastore_version=None has the effect of making it available for all datastores/versions. """ body = { "module": { } } if name is not None: body["module"]["name"] = name if module_type is not None: body["module"]["type"] = module_type if contents is not None: contents = utils.encode_data(contents) body["module"]["contents"] = contents if description is not None: body["module"]["description"] = description datastore_obj = {} if datastore: datastore_obj["type"] = datastore if datastore_version: datastore_obj["version"] = datastore_version if datastore_obj: body["module"]["datastore"] = datastore_obj if all_datastores: body["module"]["all_datastores"] = int(all_datastores) if all_datastore_versions: body["module"]["all_datastore_versions"] = int( all_datastore_versions) if all_tenants is not None: body["module"]["all_tenants"] = int(all_tenants) if auto_apply is not None: body["module"]["auto_apply"] = int(auto_apply) if visible is not None: body["module"]["visible"] = int(visible) if live_update is not None: body["module"]["live_update"] = int(live_update) if priority_apply is not None: body["module"]["priority_apply"] = int(priority_apply) if apply_order is not None: body["module"]["apply_order"] = apply_order if full_access is not None: body["module"]["full_access"] = int(full_access) url = "/modules/%s" % base.getid(module) resp, body = self.api.client.put(url, body=body) common.check_for_exceptions(resp, body, url) return Module(self, body['module'], loaded=True) def list(self, limit=None, marker=None, datastore=None): """Get a list of all modules.""" query_strings = None if datastore: query_strings = {"datastore": base.getid(datastore)} return self._paginated( "/modules", "modules", limit, marker, query_strings=query_strings) def get(self, module): """Get a specific module.""" return self._get( "/modules/%s" % base.getid(module), "module") def delete(self, module): """Delete the specified module.""" url = "/modules/%s" % base.getid(module) resp, body = self.api.client.delete(url) common.check_for_exceptions(resp, body, url) def instances(self, module, limit=None, marker=None, include_clustered=False, count_only=False): """Get a list of all instances this module has been applied to.""" url = "/modules/%s/instances" % base.getid(module) query_strings = {} if include_clustered: query_strings['include_clustered'] = include_clustered if count_only: query_strings['count_only'] = count_only return self._paginated(url, "instances", limit, marker, query_strings=query_strings) def reapply(self, module, md5=None, include_clustered=None, batch_size=None, delay=None, force=None): """Reapplies the specified module.""" url = "/modules/%s/instances" % base.getid(module) body = { "reapply": { } } if md5: body["reapply"]["md5"] = md5 if include_clustered is not None: body["reapply"]["include_clustered"] = int(include_clustered) if batch_size is not None: body["reapply"]["batch_size"] = batch_size if delay is not None: body["reapply"]["batch_delay"] = delay if force is not None: body["reapply"]["force"] = int(force) resp, body = self.api.client.put(url, body=body) common.check_for_exceptions(resp, body, url) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/v1/quota.py0000664000175000017500000000366300000000000022024 0ustar00zuulzuul00000000000000# 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 import common 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) common.check_for_exceptions(resp, body, url) 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 [self.resource_class(self, quota) for quota in 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) common.check_for_exceptions(resp, body, url) 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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/v1/root.py0000664000175000017500000000714100000000000021651 0ustar00zuulzuul00000000000000# 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 import common from troveclient.v1 import users class Root(base.ManagerWithFind): """Manager class for Root resource.""" resource_class = users.User instances_url = "/instances/%s/root" clusters_url = "/clusters/%s/root" def create(self, instance): """Implements root-enable API. Enable the root user and return the root password for the specified db instance. """ return self.create_instance_root(instance) def create_instance_root(self, instance, root_password=None): """Implements root-enable for instances.""" return self._enable_root(self.instances_url % base.getid(instance), root_password) def create_cluster_root(self, cluster, root_password=None): """Implements root-enable for clusters.""" return self._enable_root(self.clusters_url % base.getid(cluster), root_password) def _enable_root(self, uri, root_password=None): """Implements root-enable API. Enable the root user and return the root password for the specified db instance or cluster. """ if root_password: resp, body = self.api.client.post(uri, body={"password": root_password}) else: resp, body = self.api.client.post(uri) common.check_for_exceptions(resp, body, uri) return body['user']['name'], body['user']['password'] def delete(self, instance): """Implements root-disable API. Disables access to the root user for the specified db instance. :param instance: The instance on which the root user is enabled """ self.disable_instance_root(instance) def disable_instance_root(self, instance): """Implements root-disable for instances.""" self._disable_root(self.instances_url % base.getid(instance)) def _disable_root(self, url): resp, body = self.api.client.delete(url) common.check_for_exceptions(resp, body, url) def is_root_enabled(self, instance): """Return whether root is enabled for the instance.""" return self.is_instance_root_enabled(instance) def is_instance_root_enabled(self, instance): """Returns whether root is enabled for the instance.""" return self._is_root_enabled(self.instances_url % base.getid(instance)) def is_cluster_root_enabled(self, cluster): """Returns whether root is enabled for the cluster.""" return self._is_root_enabled(self.clusters_url % base.getid(cluster)) def _is_root_enabled(self, uri): """Return whether root is enabled for the instance or the cluster.""" resp, body = self.api.client.get(uri) common.check_for_exceptions(resp, body, uri) return self.resource_class(self, body, loaded=True) # Appease the abc gods def list(self): pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/v1/security_groups.py0000664000175000017500000000560200000000000024134 0ustar00zuulzuul00000000000000# 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 import common 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, limit=None, marker=None): """Get a list of all security groups. :rtype: list of :class:`SecurityGroup`. """ return self._paginated("/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): """This resource is used to hold security group rule 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, cidr): """Create a new security group rule.""" body = {"security_group_rule": { "group_id": group_id, "cidr": cidr }} return self._create("/security-group-rules", body, "security_group_rule", return_raw=True) def delete(self, security_group_rule): """Delete the specified security group rule. :param security_group_rule: The security group rule to delete """ url = "/security-group-rules/%s" % base.getid(security_group_rule) resp, body = self.api.client.delete(url) common.check_for_exceptions(resp, body, url) # Appease the abc gods def list(self): pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/v1/shell.py0000664000175000017500000030015100000000000021772 0ustar00zuulzuul00000000000000# 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 argparse import json import sys import time from troveclient.i18n import _ from troveclient import exceptions from troveclient import utils from troveclient.v1 import modules INSTANCE_ARG_NAME = _('instance') INSTANCE_METAVAR = _('"opt=[,opt= ...] "') INSTANCE_ERROR = _("Instance argument(s) must be of the form --instance " "%s - see help for details.") % INSTANCE_METAVAR INSTANCE_HELP = _("Add an instance to the cluster. Specify multiple " "times to create multiple instances. " "Valid options are: flavor=, " "volume=, volume_type=, " "nic=', v4-fixed-ip=, " "port-id=>' " "(where net-id=network_id, v4-fixed-ip=IPv4r_fixed_address, " "port-id=port_id), availability_zone=, " "module=, type=, " "related_to=.") NIC_ERROR = _("Invalid NIC argument: %s. Must specify either net-id or port-id" " but not both. Please refer to help.") NO_LOG_FOUND_ERROR = _("ERROR: No published '%(log_name)s' log was found for " "%(instance)s.") LOCALITY_DOMAIN = ['affinity', 'anti-affinity'] EXT_PROPS_METAVAR = INSTANCE_METAVAR EXT_PROPS_HELP = _("Add extended properties for cluster create. " "Currently only support MongoDB options, other databases " "will be added in the future. " "MongoDB: " " num_configsvr=, " " num_mongos=, " " configsvr_volume_size=, " " configsvr_volume_type=, " " mongos_volume_size=, " " mongos_volume_type=.") 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): info = instance._info.copy() info['flavor'] = instance.flavor['id'] if hasattr(instance, 'volume'): info['volume'] = instance.volume['size'] if 'used' in instance.volume: info['volume_used'] = instance.volume['used'] if hasattr(instance, 'ip'): info['ip'] = ', '.join(instance.ip) if hasattr(instance, 'datastore'): info['datastore'] = instance.datastore['type'] info['datastore_version'] = instance.datastore['version'] if hasattr(instance, 'configuration'): info['configuration'] = instance.configuration['id'] if hasattr(instance, 'replica_of'): info['replica_of'] = instance.replica_of['id'] if hasattr(instance, 'replicas'): replicas = [replica['id'] for replica in instance.replicas] info['replicas'] = ', '.join(replicas) if hasattr(instance, 'networks'): info['networks'] = instance.networks['name'] info['networks_id'] = instance.networks['id'] if hasattr(instance, 'fault'): info.pop('fault', None) info['fault'] = instance.fault['message'] info['fault_date'] = instance.fault['created'] if 'details' in instance.fault and instance.fault['details']: info['fault_details'] = instance.fault['details'] info.pop('links', None) utils.print_dict(info) def _print_cluster(cluster, include_all=False): info = cluster._info.copy() info['datastore'] = cluster.datastore['type'] info['datastore_version'] = cluster.datastore['version'] info['task_name'] = cluster.task['name'] info['task_description'] = cluster.task['description'] info.pop('task', None) if include_all and hasattr(cluster, 'ip'): info['ip'] = ', '.join(cluster.ip) instances = info.pop('instances', None) if instances: info['instance_count'] = len(instances) info.pop('links', None) utils.print_dict(info) def _print_object(obj): # Get rid of those ugly links if obj._info.get('links'): del(obj._info['links']) # Fallback to str_id for flavors, where necessary if hasattr(obj, 'str_id'): obj._info['id'] = obj.id del(obj._info['str_id']) # Get datastore type and version, where necessary if hasattr(obj, 'datastore'): if 'type' in obj.datastore: obj._info['datastore'] = obj.datastore['type'] obj._info['datastore_version'] = obj.datastore['version'] utils.print_dict(obj._info) def _find_instance_or_cluster(cs, instance_or_cluster): """Returns an instance or cluster, found by id, along with the type of resource, instance or cluster, that was found. Raises CommandError if none is found. """ try: return _find_instance(cs, instance_or_cluster), 'instance' except exceptions.CommandError: try: return _find_cluster(cs, instance_or_cluster), 'cluster' except Exception: raise exceptions.CommandError( _("No instance or cluster with a name or ID of '%s' exists.") % instance_or_cluster) def _find_instance(cs, instance): """Get an instance by ID.""" return utils.find_resource(cs.instances, instance) def _find_cluster(cs, cluster): """Get a cluster by ID.""" return utils.find_resource(cs.clusters, cluster) def _find_flavor(cs, flavor): """Get a flavor by ID.""" return utils.find_resource(cs.flavors, flavor) def _find_volume_type(cs, volume_type): """Get a volume type by ID.""" return utils.find_resource(cs.volume_types, volume_type) def _find_backup(cs, backup): """Get a backup by ID.""" return utils.find_resource(cs.backups, backup) def _find_module(cs, module): """Get a module by ID.""" return utils.find_resource(cs.modules, module) def _find_datastore(cs, datastore): """Get a datastore by ID.""" return utils.find_resource(cs.datastores, datastore) def _find_datastore_version(cs, datastore_version): """Get a datastore version by ID.""" return utils.find_resource(cs.datastores, datastore_version) def _find_configuration(cs, configuration): """Get a configuration by ID.""" return utils.find_resource(cs.configurations, configuration) # Flavor related calls @utils.arg('--datastore_type', metavar='', default=None, help=_('Type of the datastore. For eg: mysql.')) @utils.arg("--datastore_version_id", metavar="", default=None, help=_("ID of the datastore version.")) @utils.service_type('database') def do_flavor_list(cs, args): """Lists available flavors.""" if args.datastore_type and args.datastore_version_id: flavors = cs.flavors.list_datastore_version_associated_flavors( args.datastore_type, args.datastore_version_id) elif not args.datastore_type and not args.datastore_version_id: flavors = cs.flavors.list() else: raise exceptions.MissingArgs(['datastore_type', 'datastore_version_id']) # Fallback to str_id where necessary. _flavors = [] for f in flavors: if not f.id and hasattr(f, 'str_id'): f.id = f.str_id _flavors.append(f) utils.print_list(_flavors, ['id', 'name', 'ram', 'vcpus', 'disk', 'ephemeral'], labels={'ram': 'RAM', 'vcpus': 'vCPUs', 'disk': 'Disk'}, order_by='ram') @utils.arg('flavor', metavar='', type=str, help=_('ID or name of the flavor.')) @utils.service_type('database') def do_flavor_show(cs, args): """Shows details of a flavor.""" flavor = _find_flavor(cs, args.flavor) _print_object(flavor) # Volume type related calls @utils.arg('--datastore_type', metavar='', default=None, help='Type of the datastore. For eg: mysql.') @utils.arg("--datastore_version_id", metavar="", default=None, help="ID of the datastore version.") @utils.service_type('database') def do_volume_type_list(cs, args): """Lists available volume types.""" if args.datastore_type and args.datastore_version_id: volume_types = cs.volume_types.\ list_datastore_version_associated_volume_types( args.datastore_type, args.datastore_version_id ) elif not args.datastore_type and not args.datastore_version_id: volume_types = cs.volume_types.list() else: raise exceptions.MissingArgs(['datastore_type', 'datastore_version_id']) utils.print_list(volume_types, ['id', 'name', 'is_public', 'description']) @utils.arg('volume_type', metavar='', help='ID or name of the volume type.') @utils.service_type('database') def do_volume_type_show(cs, args): """Shows details of a volume type.""" volume_type = _find_volume_type(cs, args.volume_type) _print_object(volume_type) # Instance related calls @utils.arg('--limit', metavar='', type=int, default=None, help=_('Limit the number of results displayed.')) @utils.arg('--marker', metavar='', type=str, default=None, help=_('Begin displaying the results for IDs greater than the ' 'specified marker. When used with --limit, set this to ' 'the last ID displayed in the previous run.')) @utils.arg('--include_clustered', '--include-clustered', dest='include_clustered', action="store_true", default=False, help=_("Include instances that are part of a cluster " "(default %(default)s). --include-clustered may be " "deprecated in the future, retaining just " "--include_clustered.")) @utils.service_type('database') def do_list(cs, args): """Lists all the instances.""" instances = cs.instances.list(limit=args.limit, marker=args.marker, include_clustered=args.include_clustered) _print_instances(instances) def _print_instances(instances, is_admin=False): for instance in instances: setattr(instance, 'flavor_id', instance.flavor['id']) if hasattr(instance, 'volume'): setattr(instance, 'size', instance.volume['size']) else: setattr(instance, 'size', '-') if not hasattr(instance, 'region'): setattr(instance, 'region', '') if hasattr(instance, 'datastore'): if instance.datastore.get('version'): setattr(instance, 'datastore_version', instance.datastore['version']) setattr(instance, 'datastore', instance.datastore['type']) fields = ['id', 'name', 'datastore', 'datastore_version', 'status', 'flavor_id', 'size', 'region'] if is_admin: fields.append('tenant_id') utils.print_list(instances, fields) @utils.arg('--limit', metavar='', type=int, default=None, help=_('Limit the number of results displayed.')) @utils.arg('--marker', metavar='', type=str, default=None, help=_('Begin displaying the results for IDs greater than the ' 'specified marker. When used with --limit, set this to ' 'the last ID displayed in the previous run.')) @utils.service_type('database') def do_cluster_list(cs, args): """Lists all the clusters.""" clusters = cs.clusters.list(limit=args.limit, marker=args.marker) for cluster in clusters: setattr(cluster, 'datastore_version', cluster.datastore['version']) setattr(cluster, 'datastore', cluster.datastore['type']) setattr(cluster, 'task_name', cluster.task['name']) utils.print_list(clusters, ['id', 'name', 'datastore', 'datastore_version', 'task_name']) @utils.arg('instance', metavar='', help=_('ID or name of the instance.')) @utils.service_type('database') def do_show(cs, args): """Shows details of an instance.""" instance = _find_instance(cs, args.instance) _print_instance(instance) @utils.arg('cluster', metavar='', help=_('ID or name of the cluster.')) @utils.service_type('database') def do_cluster_show(cs, args): """Shows details of a cluster.""" cluster = _find_cluster(cs, args.cluster) _print_cluster(cluster, include_all=True) @utils.arg('cluster', metavar='', help=_('ID or name of the cluster.')) @utils.service_type('database') def do_cluster_instances(cs, args): """Lists all instances of a cluster.""" cluster = _find_cluster(cs, args.cluster) instances = cluster._info['instances'] for instance in instances: instance['flavor_id'] = instance['flavor']['id'] if instance.get('volume'): instance['size'] = instance['volume']['size'] utils.print_list( instances, ['id', 'name', 'flavor_id', 'size', 'status'], obj_is_dict=True) @utils.arg('--' + INSTANCE_ARG_NAME, metavar=INSTANCE_METAVAR, action='append', dest='instances', default=[], help=INSTANCE_HELP) @utils.arg('cluster', metavar='', help=_('ID or name of the cluster.')) @utils.service_type('database') def do_cluster_grow(cs, args): """Adds more instances to a cluster.""" cluster = _find_cluster(cs, args.cluster) instances = _parse_instance_options(cs, args.instances, for_grow=True) cs.clusters.grow(cluster, instances=instances) @utils.arg('cluster', metavar='', help=_('ID or name of the cluster.')) @utils.arg('instances', metavar='', nargs='+', default=[], help=_("Drop instance(s) from the cluster. Specify " "multiple ids to drop multiple instances.")) @utils.service_type('database') def do_cluster_shrink(cs, args): """Drops instances from a cluster.""" cluster = _find_cluster(cs, args.cluster) instances = [{'id': _find_instance(cs, instance).id} for instance in args.instances] cs.clusters.shrink(cluster, instances=instances) @utils.arg('instance', metavar='', nargs='+', help=_('ID or name of the instance(s).')) @utils.service_type('database') def do_delete(cs, args): """Delete specified instance(s).""" utils.do_action_on_many( lambda s: cs.instances.delete(_find_instance(cs, s)), args.instance, _("Request to delete instance %s has been accepted."), _("Unable to delete the specified instance(s).")) @utils.arg('instance', metavar='', help=_('ID or name of the instance.')) @utils.service_type('database') def do_force_delete(cs, args): """Force delete an instance.""" instance = _find_instance(cs, args.instance) msg = _("Request to force delete instance %s " "has been accepted.") % instance.id cs.instances.reset_status(instance) utils.do_action_with_msg(cs.instances.delete(instance), msg) @utils.arg('instance', metavar='', help=_('ID or name of the instance.')) @utils.service_type('database') def do_reset_status(cs, args): """Set the task status of an instance to NONE if the instance is in BUILD or ERROR state. Resetting task status of an instance in BUILD state will allow the instance to be deleted. """ instance = _find_instance(cs, args.instance) cs.instances.reset_status(instance=instance) @utils.arg('cluster', metavar='', nargs='+', help=_('ID or name of the cluster(s).')) @utils.service_type('database') def do_cluster_delete(cs, args): """Delete specified cluster(s).""" utils.do_action_on_many( lambda s: cs.clusters.delete(_find_cluster(cs, s)), args.cluster, _("Request to delete cluster %s has been accepted."), _("Unable to delete the specified cluster(s).")) @utils.arg('cluster', metavar='', help=_('ID or name of the cluster.')) @utils.service_type('database') def do_cluster_force_delete(cs, args): """Force delete a cluster""" cluster = _find_cluster(cs, args.cluster) msg = _("Request to force delete cluster %s " "has been accepted.") % cluster.id cs.clusters.reset_status(cluster) utils.do_action_with_msg(cs.clusters.delete(cluster), msg) @utils.arg('cluster', metavar='', help=_('ID or name of the cluster.')) @utils.service_type('database') def do_cluster_reset_status(cs, args): """Set the cluster task to NONE.""" cluster = _find_cluster(cs, args.cluster) cs.clusters.reset_status(cluster) @utils.arg('cluster', metavar='', help=_('ID or name of the cluster.')) @utils.arg('datastore_version', metavar='', help=_('A datastore version name or ID.')) @utils.service_type('database') def do_cluster_upgrade(cs, args): """Upgrades a cluster to a new datastore version.""" cluster = _find_cluster(cs, args.cluster) cs.clusters.upgrade(cluster, args.datastore_version) @utils.arg('instance', metavar='', type=str, help=_('ID or name of the instance.')) @utils.arg('--name', metavar='', type=str, default=None, help=_('Name of the instance.')) @utils.arg('--configuration', metavar='', type=str, default=None, help=_('ID of the configuration reference to attach.')) @utils.arg('--detach_replica_source', '--detach-replica-source', dest='detach_replica_source', action="store_true", default=False, help=_('Detach the replica instance from its replication source. ' '--detach-replica-source may be deprecated in the future ' 'in favor of just --detach_replica_source')) @utils.arg('--remove_configuration', dest='remove_configuration', action="store_true", default=False, help=_('Drops the current configuration reference.')) @utils.service_type('database') def do_update(cs, args): """Updates an instance: Edits name, configuration, or replica source.""" instance = _find_instance(cs, args.instance) cs.instances.edit(instance, args.configuration, args.name, args.detach_replica_source, args.remove_configuration) @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 volume in GB. " "Required when volume support is enabled.")) @utils.arg('--volume_type', metavar='', type=str, default=None, help=_("Volume type. Optional when volume support is enabled.")) @utils.arg('flavor', metavar='', type=str, help=_('A flavor name or ID.')) @utils.arg('--databases', metavar='', help=_('Optional list of databases.'), nargs="+", default=[]) @utils.arg('--users', metavar='', help=_('Optional list of users.'), nargs="+", default=[]) @utils.arg('--backup', metavar='', default=None, help=_('A backup name or ID.')) @utils.arg('--availability_zone', metavar='', default=None, help=_('The Zone hint to give to Nova.')) @utils.arg('--datastore', metavar='', default=None, help=_('A datastore name or ID.')) @utils.arg('--datastore_version', metavar='', default=None, help=_('A datastore version name or ID.')) @utils.arg('--nic', metavar=",v4-fixed-ip=," "port-id=>", action='append', dest='nics', default=[], help=_("Create a NIC on the instance. " "Specify option multiple times to create multiple NICs. " "net-id: attach NIC to network with this ID " "(either port-id or net-id must be specified), " "v4-fixed-ip: IPv4 fixed address for NIC (optional), " "port-id: attach NIC to port with this ID " "(either port-id or net-id must be specified).")) @utils.arg('--configuration', metavar='', default=None, help=_('ID of the configuration group to attach to the instance.')) @utils.arg('--replica_of', metavar='', default=None, help=_('ID or name of an existing instance to replicate from.')) @utils.arg('--replica_count', metavar='', type=int, default=None, help=_('Number of replicas to create (defaults to 1 if replica_of ' 'specified).')) @utils.arg('--module', metavar='', type=str, dest='modules', action='append', default=[], help=_('ID or name of the module to apply. Specify multiple ' 'times to apply multiple modules.')) @utils.arg('--locality', metavar='', default=None, choices=LOCALITY_DOMAIN, help=_('Locality policy to use when creating replicas. Choose ' 'one of %(choices)s.')) @utils.arg('--region', metavar='', type=str, default=None, help=argparse.SUPPRESS) # help=_('Name of region in which to create the instance.')) @utils.service_type('database') def do_create(cs, args): """Creates a new instance.""" flavor_id = _find_flavor(cs, args.flavor).id volume = None if args.size is not None and args.size <= 0: raise exceptions.ValidationError( _("Volume size '%s' must be an integer and greater than 0.") % args.size) elif args.size: volume = {"size": args.size, "type": args.volume_type} restore_point = None if args.backup: restore_point = {"backupRef": _find_backup(cs, args.backup).id} replica_of = None replica_count = args.replica_count if args.replica_of: replica_of = _find_instance(cs, args.replica_of) replica_count = replica_count or 1 locality = None if args.locality: locality = args.locality if replica_of: raise exceptions.ValidationError( _('Cannot specify locality when adding replicas to existing ' 'master.')) databases = [{'name': value} for value in args.databases] users = [{'name': n, 'password': p, 'databases': databases} for (n, p) in [z.split(':')[:2] for z in args.users]] nics = [] for nic_str in args.nics: nic_info = dict([(k, v) for (k, v) in [z.split("=", 1)[:2] for z in nic_str.split(",")]]) _validate_nic_info(nic_info, nic_str) nics.append(nic_info) modules = [] for module in args.modules: modules.append(_find_module(cs, module).id) instance = cs.instances.create(args.name, flavor_id, volume=volume, databases=databases, users=users, restorePoint=restore_point, availability_zone=args.availability_zone, datastore=args.datastore, datastore_version=args.datastore_version, nics=nics, configuration=args.configuration, replica_of=replica_of, replica_count=replica_count, modules=modules, locality=locality, region_name=args.region) _print_instance(instance) def _validate_nic_info(nic_info, nic_str): # need one or the other, not both, not none (!= ~ XOR) if not (bool(nic_info.get('net-id')) != bool(nic_info.get('port-id'))): raise exceptions.ValidationError(NIC_ERROR % (_("nic='%s'") % nic_str)) def _get_flavor(cs, opts_str): flavor_name, opts_str = _strip_option(opts_str, 'flavor', True) flavor_id = _find_flavor(cs, flavor_name).id return str(flavor_id), opts_str def _get_networks(opts_str): nic_args_list, opts_str = _strip_option(opts_str, 'nic', is_required=False, quotes_required=True, allow_multiple=True) nic_info_list = [] for nic_args in nic_args_list: orig_nic_args = nic_args = _unquote(nic_args) nic_info = {} net_id, nic_args = _strip_option(nic_args, 'net-id', False) port_id, nic_args = _strip_option(nic_args, 'port-id', False) fixed_ipv4, nic_args = _strip_option(nic_args, 'v4-fixed-ip', False) if nic_args: raise exceptions.ValidationError( _("Unknown args '%s' in 'nic' option") % nic_args) if net_id: nic_info.update({'net-id': net_id}) if port_id: nic_info.update({'port-id': port_id}) if fixed_ipv4: nic_info.update({'v4-fixed-ip': fixed_ipv4}) _validate_nic_info(nic_info, orig_nic_args) nic_info_list.append(nic_info) return nic_info_list, opts_str def _unquote(value): def _strip_quotes(value, quote_char): if value: return value.strip(quote_char) return value return _strip_quotes(_strip_quotes(value, "'"), '"') def _get_volume(opts_str): volume_size, opts_str = _strip_option(opts_str, 'volume', is_required=True) volume_type, opts_str = _strip_option(opts_str, 'volume_type', is_required=False) volume_info = {"size": volume_size} if volume_type: volume_info.update({"type": volume_type}) return volume_info, opts_str def _get_availability_zone(opts_str): return _strip_option(opts_str, 'availability_zone', is_required=False) def _get_region(cs, opts_str): return _strip_option(opts_str, 'region', is_required=False) def _get_modules(cs, opts_str): modules, opts_str = _strip_option( opts_str, 'module', is_required=False, allow_multiple=True) module_list = [] for module in modules: module_info = {'id': _find_module(cs, module).id} module_list.append(module_info) return module_list, opts_str def _strip_option(opts_str, opt_name, is_required=True, quotes_required=False, allow_multiple=False): opt_value = [] if allow_multiple else None opts_str = opts_str.strip().strip(",") if opt_name in opts_str: try: split_str = '%s=' % opt_name parts = opts_str.split(split_str) before = parts[0] after = parts[1] if len(parts) > 2: if allow_multiple: after = split_str.join(parts[1:]) value, after = _strip_option( after, opt_name, is_required=is_required, quotes_required=quotes_required, allow_multiple=allow_multiple) opt_value.extend(value) else: raise exceptions.ValidationError(( _("Option '%s' found more than once in argument " "--instance ") % opt_name) + INSTANCE_METAVAR) # Handle complex (quoted) properties. Strip the quotes. quote = after[0] if quote in ["'", '"']: after = after[1:] else: if quotes_required: raise exceptions.ValidationError( _("Invalid '%s' option. The value must be quoted. " "(Or perhaps you're missing quotes around the " "entire argument string)") % opt_name) quote = '' split_str = '%s,' % quote parts = after.split(split_str) value = str(parts[0]).strip() if allow_multiple: opt_value.append(value) opt_value = list(set(opt_value)) else: opt_value = value opts_str = before + split_str.join(parts[1:]) except IndexError: raise exceptions.ValidationError( _("Invalid '%(name)s' parameter. %(error)s.") % {'name': opt_name, 'error': INSTANCE_ERROR}) if is_required and not opt_value: raise exceptions.MissingArgs([opt_name], message=(_("Missing option '%s' for " "argument --instance ") + INSTANCE_METAVAR)) return opt_value, opts_str.strip().strip(",") def _parse_instance_options(cs, instance_options, for_grow=False): instances = [] for instance_opts in instance_options: instance_info = {} flavor, instance_opts = _get_flavor(cs, instance_opts) instance_info["flavorRef"] = flavor volume, instance_opts = _get_volume(instance_opts) instance_info["volume"] = volume nics, instance_opts = _get_networks(instance_opts) if nics: for nic in nics: # replaces net-id with network_id if 'net-id' in nic: nic['network_id'] = nic.pop('net-id') if 'subnet-id' in nic: nic['subnet_id'] = nic.pop('subnet-id') if 'ip-address' in nic: nic['ip_address'] = nic.pop('ip-address') instance_info["nics"] = nics availability_zone, instance_opts = _get_availability_zone( instance_opts) if availability_zone: instance_info["availability_zone"] = availability_zone modules, instance_opts = _get_modules(cs, instance_opts) if modules: instance_info["modules"] = modules instance_type, instance_opts = _strip_option( instance_opts, 'type', is_required=False, allow_multiple=True) if instance_type: instance_info["type"] = instance_type if for_grow: related_to, instance_opts = _strip_option( instance_opts, 'related_to', is_required=False) if instance_type: instance_info["related_to"] = related_to name, instance_opts = _strip_option( instance_opts, 'name', is_required=False) if name: instance_info["name"] = name region, instance_opts = _get_region(cs, instance_opts) if region: instance_info["region"] = region if instance_opts: raise exceptions.ValidationError( _("Unknown option(s) '%s' specified for instance") % instance_opts) instances.append(instance_info) if len(instances) == 0: raise exceptions.MissingArgs([INSTANCE_ARG_NAME]) return instances def _parse_extended_properties(extended_properties): return dict([(k, v) for (k, v) in [kv.strip().split("=") for kv in extended_properties.split(",")]]) @utils.arg('name', metavar='', type=str, help=_('Name of the cluster.')) @utils.arg('datastore', metavar='', help=_('A datastore name or ID.')) @utils.arg('datastore_version', metavar='', help=_('A datastore version name or ID.')) @utils.arg('--' + INSTANCE_ARG_NAME, metavar=INSTANCE_METAVAR, action='append', dest='instances', default=[], help=INSTANCE_HELP) @utils.arg('--locality', metavar='', default=None, choices=LOCALITY_DOMAIN, help=_('Locality policy to use when creating cluster. Choose ' 'one of %(choices)s.')) @utils.arg('--extended_properties', metavar=EXT_PROPS_METAVAR, default=None, help=EXT_PROPS_HELP) @utils.arg('--configuration', metavar='', type=str, default=None, help=_('ID of the configuration group to attach to the cluster.')) @utils.service_type('database') def do_cluster_create(cs, args): """Creates a new cluster.""" instances = _parse_instance_options(cs, args.instances) extended_properties = {} if args.extended_properties: extended_properties = _parse_extended_properties( args.extended_properties) cluster = cs.clusters.create(args.name, args.datastore, args.datastore_version, instances=instances, locality=args.locality, extended_properties=extended_properties, configuration=args.configuration) _print_cluster(cluster) @utils.arg('instance', metavar='', type=str, help=_('ID or name of the instance.')) @utils.arg('flavor', metavar='', type=str, help=_('New flavor of the instance.')) @utils.service_type('database') def do_resize_instance(cs, args): """Resizes an instance with a new flavor.""" instance = _find_instance(cs, args.instance) flavor_id = _find_flavor(cs, args.flavor).id cs.instances.resize_instance(instance, flavor_id) @utils.arg('instance', metavar='', type=str, help=_('ID or name of the instance.')) @utils.arg('datastore_version', metavar='', help=_('A datastore version name or ID.')) @utils.service_type('database') def do_upgrade(cs, args): """Upgrades an instance to a new datastore version.""" instance = _find_instance(cs, args.instance) cs.instances.upgrade(instance, args.datastore_version) @utils.arg('instance', metavar='', type=str, help=_('ID or name of the instance.')) @utils.arg('size', metavar='', type=int, default=None, help=_('New size of the instance disk volume in GB.')) @utils.service_type('database') def do_resize_volume(cs, args): """Resizes the volume size of an instance.""" instance = _find_instance(cs, args.instance) cs.instances.resize_volume(instance, args.size) @utils.arg('instance', metavar='', type=str, help=_('ID or name of the instance.')) @utils.service_type('database') def do_restart(cs, args): """Restarts an instance.""" instance = _find_instance(cs, args.instance) cs.instances.restart(instance) # Replication related commands @utils.arg('instance', metavar='', type=str, help=_('ID or name of the instance.')) def do_detach_replica(cs, args): """Detaches a replica instance from its replication source.""" instance = _find_instance(cs, args.instance) cs.instances.edit(instance, detach_replica_source=True) @utils.arg('instance', metavar='', type=str, help=_('ID or name of the instance.')) def do_promote_to_replica_source(cs, args): """Promotes a replica to be the new replica source of its set.""" instance = _find_instance(cs, args.instance) cs.instances.promote_to_replica_source(instance) @utils.arg('instance', metavar='', type=str, help=_('ID or name of the instance.')) def do_eject_replica_source(cs, args): """Ejects a replica source from its set.""" instance = _find_instance(cs, args.instance) cs.instances.eject_replica_source(instance) # Backup related commands @utils.arg('backup', metavar='', help=_('ID or name of the backup.')) @utils.service_type('database') def do_backup_show(cs, args): """Shows details of a backup.""" backup = _find_backup(cs, args.backup) _print_object(backup) @utils.arg('instance', metavar='', help=_('ID or name of the instance.')) @utils.arg('--limit', metavar='', default=None, help=_('Return up to N number of the most recent backups.')) @utils.arg('--marker', metavar='', type=str, default=None, help=_('Begin displaying the results for IDs greater than the ' 'specified marker. When used with --limit, set this to ' 'the last ID displayed in the previous run.')) @utils.service_type('database') def do_backup_list_instance(cs, args): """Lists available backups for an instance.""" instance = _find_instance(cs, args.instance) items = cs.instances.backups(instance, limit=args.limit, marker=args.marker) backups = items while items.next and not args.limit: items = cs.instances.backups(instance, marker=items.next) backups += items utils.print_list(backups, ['id', 'name', 'status', 'parent_id', 'updated'], order_by='updated') @utils.arg('--limit', metavar='', default=None, help=_('Return up to N number of the most recent backups.')) @utils.arg('--marker', metavar='', type=str, default=None, help=_('Begin displaying the results for IDs greater than the ' 'specified marker. When used with --limit, set this to ' 'the last ID displayed in the previous run.')) @utils.arg('--datastore', metavar='', default=None, help=_('ID or name of the datastore (to filter backups by).')) @utils.service_type('database') def do_backup_list(cs, args): """Lists available backups.""" items = cs.backups.list(limit=args.limit, datastore=args.datastore, marker=args.marker) backups = items while items.next and not args.limit: items = cs.backups.list(marker=items.next) backups += items utils.print_list(backups, ['id', 'instance_id', 'name', 'status', 'parent_id', 'updated'], order_by='updated') @utils.arg('backup', metavar='', help=_('ID or name of the backup.')) @utils.service_type('database') def do_backup_delete(cs, args): """Deletes a backup.""" backup = _find_backup(cs, args.backup) cs.backups.delete(backup) @utils.arg('instance', metavar='', help=_('ID or name of the instance.')) @utils.arg('name', metavar='', help=_('Name of the backup.')) @utils.arg('--description', metavar='', default=None, help=_('An optional description for the backup.')) @utils.arg('--parent', metavar='', default=None, help=_('Optional ID of the parent backup to perform an' ' incremental backup from.')) @utils.arg('--incremental', action='store_true', default=False, help=_('Create an incremental backup based on the last' ' full or incremental backup. It will create a' ' full backup if no existing backup found.')) @utils.service_type('database') def do_backup_create(cs, args): """Creates a backup of an instance.""" instance = _find_instance(cs, args.instance) backup = cs.backups.create(args.name, instance, description=args.description, parent_id=args.parent, incremental=args.incremental) _print_object(backup) @utils.arg('instance', metavar='', help=_('ID or name of the instance.')) @utils.arg('pattern', metavar='', help=_('Cron style pattern describing schedule occurrence.')) @utils.arg('name', metavar='', help=_('Name of the backup.')) @utils.arg('--description', metavar='', default=None, help=_('An optional description for the backup.')) @utils.arg('--incremental', action="store_true", default=False, help=_('Flag to select incremental backup based on most recent' ' backup.')) @utils.service_type('database') def do_schedule_create(cs, args): """Schedules backups for an instance.""" instance = _find_instance(cs, args.instance) backup = cs.backups.schedule_create(instance, args.pattern, args.name, description=args.description, incremental=args.incremental) _print_object(backup) @utils.arg('instance', metavar='', help=_('ID or name of the instance.')) @utils.service_type('database') def do_schedule_list(cs, args): """Lists scheduled backups for an instance.""" instance = _find_instance(cs, args.instance) schedules = cs.backups.schedule_list(instance) utils.print_list(schedules, ['id', 'name', 'pattern', 'next_execution_time'], order_by='next_execution_time') @utils.arg('id', metavar='', help=_('Id of the schedule.')) @utils.service_type('database') def do_schedule_show(cs, args): """Shows details of a schedule.""" _print_object(cs.backups.schedule_show(args.id)) @utils.arg('id', metavar='', help=_('Id of the schedule.')) @utils.service_type('database') def do_schedule_delete(cs, args): """Deletes a schedule.""" cs.backups.schedule_delete(args.id) @utils.arg('id', metavar='', help=_('Id of the schedule.')) @utils.arg('--limit', metavar='', default=None, type=int, help=_('Return up to N number of the most recent executions.')) @utils.arg('--marker', metavar='', type=str, default=None, help=_('Begin displaying the results for IDs greater than the ' 'specified marker. When used with --limit, set this to ' 'the last ID displayed in the previous run.')) @utils.service_type('database') def do_execution_list(cs, args): """Lists executions of a scheduled backup of an instance.""" executions = cs.backups.execution_list(args.id, marker=args.marker, limit=args.limit) utils.print_list(executions, ['id', 'created_at', 'state', 'output'], labels={'created_at': 'Execution Time'}, order_by='created_at') @utils.arg('execution', metavar='', help=_('Id of the execution to delete.')) @utils.service_type('database') def do_execution_delete(cs, args): """Deletes an execution.""" cs.backups.execution_delete(args.execution) # Database related actions @utils.arg('instance', metavar='', help=_('ID or name of the instance.')) @utils.arg('name', metavar='', help=_('Name of the database.')) @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.""" instance, _ = _find_instance_or_cluster(cs, args.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(instance, [database_dict]) @utils.arg('instance', metavar='', help=_('ID or name of the instance.')) @utils.service_type('database') def do_database_list(cs, args): """Lists available databases on an instance.""" instance, _ = _find_instance_or_cluster(cs, args.instance) items = cs.databases.list(instance) databases = items while (items.next): items = cs.databases.list(instance, marker=items.next) databases += items utils.print_list(databases, ['name']) @utils.arg('instance', metavar='', help=_('ID or name 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 from an instance.""" instance, _ = _find_instance_or_cluster(cs, args.instance) cs.databases.delete(instance, args.database) # User related actions @utils.arg('instance', metavar='', help=_('ID or name 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 on an instance.""" instance, _ = _find_instance_or_cluster(cs, args.instance) 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(instance, [user]) @utils.arg('instance', metavar='', help=_('ID or name of the instance.')) @utils.service_type('database') def do_user_list(cs, args): """Lists the users for an instance.""" instance, _ = _find_instance_or_cluster(cs, args.instance) items = cs.users.list(instance) users = items while (items.next): items = cs.users.list(instance, marker=items.next) users += items for user in users: db_names = [db['name'] for db in user.databases] user.databases = ', '.join(db_names) utils.print_list(users, ['name', 'host', 'databases']) @utils.arg('instance', metavar='', help=_('ID or name 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 an instance.""" instance, _ = _find_instance_or_cluster(cs, args.instance) cs.users.delete(instance, args.name, hostname=args.host) @utils.arg('instance', metavar='', help=_('ID or name 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_show(cs, args): """Shows details of a user of an instance.""" instance, _ = _find_instance_or_cluster(cs, args.instance) user = cs.users.get(instance, args.name, hostname=args.host) _print_object(user) @utils.arg('instance', metavar='', help=_('ID or name 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_show_access(cs, args): """Shows access details of a user of an instance.""" instance, _ = _find_instance_or_cluster(cs, args.instance) access = cs.users.list_access(instance, args.name, hostname=args.host) utils.print_list(access, ['name']) @utils.arg('instance', metavar='', help=_('ID or name 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') def do_user_update_attributes(cs, args): """Updates a user's attributes on an instance. At least one optional argument must be provided. """ instance, _ = _find_instance_or_cluster(cs, args.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(instance, args.name, newuserattr=new_attrs, hostname=args.host) @utils.arg('instance', metavar='', help=_('ID or name 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.""" instance, _ = _find_instance_or_cluster(cs, args.instance) cs.users.grant(instance, args.name, args.databases, hostname=args.host) @utils.arg('instance', metavar='', help=_('ID or name 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.""" instance, _ = _find_instance_or_cluster(cs, args.instance) cs.users.revoke(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_object(absolute) utils.print_list(limits, ['value', 'verb', 'remaining', 'unit']) # Root related commands @utils.arg('instance_or_cluster', metavar='', help=_('ID or name of the instance or cluster.')) @utils.arg('--root_password', metavar='', default=None, help=_('Root password to set.')) @utils.service_type('database') def do_root_enable(cs, args): """Enables root for an instance and resets if already exists.""" instance_or_cluster, resource_type = _find_instance_or_cluster( cs, args.instance_or_cluster) if resource_type == 'instance': root = cs.root.create_instance_root(instance_or_cluster, args.root_password) else: root = cs.root.create_cluster_root(instance_or_cluster, args.root_password) utils.print_dict({'name': root[0], 'password': root[1]}) @utils.arg('instance', metavar='', help=_('ID or name of the instance.')) @utils.service_type('database') def do_root_disable(cs, args): """Disables root for an instance.""" instance = _find_instance(cs, args.instance) cs.root.disable_instance_root(instance) @utils.arg('instance_or_cluster', metavar='', help=_('ID or name of the instance or cluster.')) @utils.service_type('database') def do_root_show(cs, args): """Gets status if root was ever enabled for an instance or cluster.""" instance_or_cluster, resource_type = _find_instance_or_cluster( cs, args.instance_or_cluster) if resource_type == 'instance': root = cs.root.is_instance_root_enabled(instance_or_cluster) else: root = cs.root.is_cluster_root_enabled(instance_or_cluster) 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.""" items = cs.security_groups.list() sec_grps = items while (items.next): items = cs.security_groups.list() sec_grps += items utils.print_list(sec_grps, ['id', 'name', 'instance_id']) @utils.arg('security_group', metavar='', help=_('Security group ID.')) @utils.service_type('database') def do_secgroup_show(cs, args): """Shows details of a security group.""" sec_grp = cs.security_groups.get(args.security_group) del sec_grp._info['rules'] _print_object(sec_grp) @utils.arg('security_group', metavar='', help=_('Security group ID.')) @utils.arg('cidr', metavar='', help=_('CIDR address.')) @utils.service_type('database') def do_secgroup_add_rule(cs, args): """Creates a security group rule.""" rules = cs.security_group_rules.create( args.security_group, args.cidr) utils.print_list(rules, [ 'id', 'security_group_id', 'protocol', 'from_port', 'to_port', 'cidr', 'created'], obj_is_dict=True) @utils.arg('security_group', metavar='', help=_('Security group ID.')) @utils.service_type('database') def do_secgroup_list_rules(cs, args): """Lists all rules for a security group.""" sec_grp = cs.security_groups.get(args.security_group) rules = sec_grp._info['rules'] utils.print_list( rules, ['id', 'protocol', 'from_port', 'to_port', 'cidr'], obj_is_dict=True) @utils.arg('security_group_rule', metavar='', help=_('ID of 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) @utils.service_type('database') def do_datastore_list(cs, args): """Lists available datastores.""" datastores = cs.datastores.list() utils.print_list(datastores, ['id', 'name']) @utils.arg('datastore', metavar='', help=_('ID of the datastore.')) @utils.service_type('database') def do_datastore_show(cs, args): """Shows details of a datastore.""" datastore = cs.datastores.get(args.datastore) info = datastore._info.copy() versions = info.get('versions', []) versions_str = "\n".join( [ver['name'] + " (" + ver['id'] + ")" for ver in versions]) info['versions (id)'] = versions_str info.pop('versions', None) info.pop('links', None) if hasattr(datastore, 'default_version'): def_ver_id = getattr(datastore, 'default_version') info['default_version'] = [ ver['name'] for ver in versions if ver['id'] == def_ver_id][0] utils.print_dict(info) @utils.arg('datastore', metavar='', help=_('ID or name of the datastore.')) @utils.service_type('database') def do_datastore_version_list(cs, args): """Lists available versions for a datastore.""" datastore_versions = cs.datastore_versions.list(args.datastore) utils.print_list(datastore_versions, ['id', 'name']) @utils.arg('--datastore', metavar='', default=None, help=_('ID or name of the datastore. Optional if the ID of the' ' datastore_version is provided.')) @utils.arg('datastore_version', metavar='', help=_('ID or name of the datastore version.')) @utils.service_type('database') def do_datastore_version_show(cs, args): """Shows details of a datastore version.""" if args.datastore: datastore_version = cs.datastore_versions.get(args.datastore, args.datastore_version) elif utils.is_uuid_like(args.datastore_version): datastore_version = cs.datastore_versions.get_by_uuid( args.datastore_version) else: raise exceptions.NoUniqueMatch(_('The datastore name or id is required' ' to retrieve a datastore version' ' by name.')) _print_object(datastore_version) # configuration group related functions @utils.arg('instance', metavar='', type=str, help=_('ID or name of the instance.')) @utils.arg('configuration', metavar='', type=str, help=_('ID or name of the configuration group to attach to the' ' instance.')) @utils.service_type('database') def do_configuration_attach(cs, args): """Attaches a configuration group to an instance.""" instance = _find_instance(cs, args.instance) configuration = _find_configuration(cs, args.configuration) cs.instances.modify(instance, configuration) @utils.arg('name', metavar='', help=_('Name of the configuration group.')) @utils.arg('values', metavar='', help=_('Dictionary of the values to set.')) @utils.arg('--datastore', metavar='', help=_('Datastore assigned to the configuration group. Required if ' 'default datastore is not configured.')) @utils.arg('--datastore_version', metavar='', help=_('Datastore version ID assigned to the configuration group.')) @utils.arg('--description', metavar='', default=None, help=_('An optional description for the configuration group.')) @utils.service_type('database') def do_configuration_create(cs, args): """Creates a configuration group.""" config_grp = cs.configurations.create( args.name, args.values, description=args.description, datastore=args.datastore, datastore_version=args.datastore_version) config_grp._info['values'] = json.dumps(config_grp.values) _print_object(config_grp) @utils.arg('instance', metavar='', type=str, help=_('ID or name of the instance.')) @utils.service_type('database') def do_configuration_default(cs, args): """Shows the default configuration of an instance.""" instance = _find_instance(cs, args.instance) configs = cs.instances.configuration(instance) utils.print_dict(configs._info['configuration']) @utils.arg('configuration_group', metavar='', help=_('ID or name of the configuration group.')) @utils.service_type('database') def do_configuration_delete(cs, args): """Deletes a configuration group.""" configuration = _find_configuration(cs, args.configuration_group) cs.configurations.delete(configuration) @utils.arg('instance', metavar='', type=str, help=_('ID or name of the instance.')) @utils.service_type('database') def do_configuration_detach(cs, args): """Detaches a configuration group from an instance.""" instance = _find_instance(cs, args.instance) cs.instances.modify(instance) @utils.arg('--datastore', metavar='', default=None, help=_('ID or name of the datastore to list configuration ' 'parameters for. Optional if the ID of the' ' datastore_version is provided.')) @utils.arg('datastore_version', metavar='', help=_('Datastore version name or ID assigned to the ' 'configuration group.')) @utils.arg('parameter', metavar='', help=_('Name of the configuration parameter.')) @utils.service_type('database') def do_configuration_parameter_show(cs, args): """Shows details of a configuration parameter.""" if args.datastore: param = cs.configuration_parameters.get_parameter( args.datastore, args.datastore_version, args.parameter) elif utils.is_uuid_like(args.datastore_version): param = cs.configuration_parameters.get_parameter_by_version( args.datastore_version, args.parameter) else: raise exceptions.NoUniqueMatch(_('The datastore name or id is' ' required to retrieve the' ' parameter for the configuration' ' group by name.')) _print_object(param) @utils.arg('--datastore', metavar='', default=None, help=_('ID or name of the datastore to list configuration ' 'parameters for. Optional if the ID of the' ' datastore_version is provided.')) @utils.arg('datastore_version', metavar='', help=_('Datastore version name or ID assigned to the ' 'configuration group.')) @utils.service_type('database') def do_configuration_parameter_list(cs, args): """Lists available parameters for a configuration group.""" if args.datastore: params = cs.configuration_parameters.parameters( args.datastore, args.datastore_version) elif utils.is_uuid_like(args.datastore_version): params = cs.configuration_parameters.parameters_by_version( args.datastore_version) else: raise exceptions.NoUniqueMatch(_('The datastore name or id is required' ' to retrieve the parameters for the' ' configuration group by name.')) for param in params: setattr(param, 'min', getattr(param, 'min', '-')) setattr(param, 'max', getattr(param, 'max', '-')) utils.print_list( params, ['name', 'type', 'min', 'max', 'restart_required'], labels={'min': 'Min Size', 'max': 'Max Size'}) @utils.arg('configuration_group', metavar='', help=_('ID or name of the configuration group.')) @utils.arg('values', metavar='', help=_('Dictionary of the values to set.')) @utils.service_type('database') def do_configuration_patch(cs, args): """Patches a configuration group.""" configuration = _find_configuration(cs, args.configuration_group) cs.configurations.edit(configuration, args.values) @utils.arg('configuration_group', metavar='', help=_('ID or name of the configuration group.')) @utils.arg('--limit', metavar='', type=int, default=None, help=_('Limit the number of results displayed.')) @utils.arg('--marker', metavar='', type=str, default=None, help=_('Begin displaying the results for IDs greater than the ' 'specified marker. When used with --limit, set this to ' 'the last ID displayed in the previous run.')) @utils.service_type('database') def do_configuration_instances(cs, args): """Lists all instances associated with a configuration group.""" configuration = _find_configuration(cs, args.configuration_group) params = cs.configurations.instances(configuration, limit=args.limit, marker=args.marker) utils.print_list(params, ['id', 'name']) @utils.arg('--limit', metavar='', type=int, default=None, help=_('Limit the number of results displayed.')) @utils.arg('--marker', metavar='', type=str, default=None, help=_('Begin displaying the results for IDs greater than the ' 'specified marker. When used with --limit, set this to ' 'the last ID displayed in the previous run.')) @utils.service_type('database') def do_configuration_list(cs, args): """Lists all configuration groups.""" config_grps = cs.configurations.list(limit=args.limit, marker=args.marker) utils.print_list(config_grps, [ 'id', 'name', 'description', 'datastore_name', 'datastore_version_name']) @utils.arg('configuration_group', metavar='', help=_('ID or name of the configuration group.')) @utils.service_type('database') def do_configuration_show(cs, args): """Shows details of a configuration group.""" configuration = _find_configuration(cs, args.configuration_group) config_grp = cs.configurations.get(configuration) config_grp._info['values'] = json.dumps(config_grp.values) del config_grp._info['datastore_version_id'] _print_object(config_grp) @utils.arg('configuration_group', metavar='', help=_('ID or name of the configuration group.')) @utils.arg('values', metavar='', help=_('Dictionary of the values to set.')) @utils.arg('--name', metavar='', default=None, help=_('Name of the configuration group.')) @utils.arg('--description', metavar='', default=None, help=_('An optional description for the configuration group.')) @utils.service_type('database') def do_configuration_update(cs, args): """Updates a configuration group.""" configuration = _find_configuration(cs, args.configuration_group) cs.configurations.update(configuration, args.values, args.name, args.description) @utils.arg('instance_id', metavar='', help=_('UUID for instance.')) @utils.service_type('database') def do_metadata_list(cs, args): """Shows all metadata for instance .""" result = cs.metadata.list(args.instance_id) _print_object(result) @utils.arg('instance_id', metavar='', help=_('UUID for instance.')) @utils.arg('key', metavar='', help=_('Key to display.')) @utils.service_type('database') def do_metadata_show(cs, args): """Shows metadata entry for key and instance .""" result = cs.metadata.show(args.instance_id, args.key) _print_object(result) @utils.arg('instance_id', metavar='', help=_('UUID for instance.')) @utils.arg('key', metavar='', help=_('Key to replace.')) @utils.arg('value', metavar='', help=_('New value to assign to .')) @utils.service_type('database') def do_metadata_edit(cs, args): """Replaces metadata value with a new one, this is non-destructive.""" cs.metadata.edit(args.instance_id, args.key, args.value) @utils.arg('instance_id', metavar='', help=_('UUID for instance.')) @utils.arg('key', metavar='', help=_('Key to update.')) @utils.arg('newkey', metavar='', help=_('New key.')) @utils.arg('value', metavar='', help=_('Value to assign to .')) @utils.service_type('database') def do_metadata_update(cs, args): """Updates metadata, this is destructive.""" cs.metadata.update(args.instance_id, args.key, args.newkey, args.value) @utils.arg('instance_id', metavar='', help=_('UUID for instance.')) @utils.arg('key', metavar='', help=_('Key for assignment.')) @utils.arg('value', metavar='', help=_('Value to assign to .')) @utils.service_type('database') def do_metadata_create(cs, args): """Creates metadata in the database for instance .""" result = cs.metadata.create(args.instance_id, args.key, args.value) _print_object(result) @utils.arg('instance_id', metavar='', help=_('UUID for instance.')) @utils.arg('key', metavar='', help=_('Metadata key to delete.')) @utils.service_type('database') def do_metadata_delete(cs, args): """Deletes metadata for instance .""" cs.metadata.delete(args.instance_id, args.key) @utils.arg('--datastore', metavar='', help=_("Name or ID of datastore to list modules for. Use '%s' " "to list modules that apply to all datastores.") % modules.Module.ALL_KEYWORD) @utils.service_type('database') def do_module_list(cs, args): """Lists the modules available.""" datastore = None if args.datastore: if args.datastore.lower() == modules.Module.ALL_KEYWORD: datastore = args.datastore.lower() else: datastore = _find_datastore(cs, args.datastore) module_list = cs.modules.list(datastore=datastore) field_list = ['id', 'name', 'type', 'datastore', 'datastore_version', 'auto_apply', 'priority_apply', 'apply_order', 'is_admin', 'tenant', 'visible'] if not utils.is_admin(cs): field_list = field_list[:-2] utils.print_list( module_list, field_list, labels={'datastore_version': 'Version', 'priority_apply': 'Priority', 'apply_order': 'Order', 'is_admin': 'Admin'}) @utils.arg('module', metavar='', help=_('ID or name of the module.')) @utils.service_type('database') def do_module_show(cs, args): """Shows details of a module.""" module = _find_module(cs, args.module) _print_object(module) @utils.arg('name', metavar='', type=str, help=_('Name of the module.')) @utils.arg('type', metavar='', type=str, help=_('Type of the module. The type must be supported by a ' 'corresponding module plugin on the datastore it is ' 'applied to.')) @utils.arg('file', metavar='', type=argparse.FileType(mode='rb', bufsize=0), help=_('File containing data contents for the module.')) @utils.arg('--description', metavar='', type=str, help=_('Description of the module.')) @utils.arg('--datastore', metavar='', help=_('Name or ID of datastore this module can be applied to. ' 'If not specified, module can be applied to all ' 'datastores.')) @utils.arg('--datastore_version', metavar='', help=_('Name or ID of datastore version this module can be applied ' 'to. If not specified, module can be applied to all ' 'versions.')) @utils.arg('--auto_apply', action='store_true', default=False, help=_('Automatically apply this module when creating an instance ' 'or cluster. Admin only.')) @utils.arg('--all_tenants', action='store_true', default=False, help=_('Module is valid for all tenants. Admin only.')) @utils.arg('--hidden', action='store_true', default=False, help=_('Hide this module from non-Admin. Useful in creating ' 'auto-apply modules without cluttering up module lists. ' 'Admin only.')) @utils.arg('--live_update', action='store_true', default=False, help=_('Allow module to be updated even if it is already applied ' 'to a current instance or cluster.')) @utils.arg('--priority_apply', action='store_true', default=False, help=_('Sets a priority for applying the module. All priority ' 'modules will be applied before non-priority ones. ' 'Admin only.')) @utils.arg('--apply_order', type=int, default=5, choices=range(0, 10), help=_('Sets an order for applying the module. Modules with a ' 'lower value will be applied before modules with a higher ' 'value. Modules having the same value may be ' 'applied in any order (default %(default)s).')) @utils.arg('--full_access', action='store_true', default=None, help=_("Marks a module as 'non-admin', unless an admin-only " "option was specified. Admin only.")) @utils.service_type('database') def do_module_create(cs, args): """Create a module.""" contents = args.file.read() if not contents: raise exceptions.ValidationError( _("The file '%s' must contain some data") % args.file) module = cs.modules.create( args.name, args.type, contents, description=args.description, all_tenants=args.all_tenants, datastore=args.datastore, datastore_version=args.datastore_version, auto_apply=args.auto_apply, visible=not args.hidden, live_update=args.live_update, priority_apply=args.priority_apply, apply_order=args.apply_order, full_access=args.full_access) _print_object(module) @utils.arg('module', metavar='', type=str, help=_('Name or ID of the module.')) @utils.arg('--name', metavar='', type=str, default=None, help=_('Name of the module.')) @utils.arg('--type', metavar='', type=str, default=None, help=_('Type of the module. The type must be supported by a ' 'corresponding module driver plugin on the datastore it is ' 'applied to.')) @utils.arg('--file', metavar='', type=argparse.FileType('rb', 0), default=None, help=_('File containing data contents for the module.')) @utils.arg('--description', metavar='', type=str, default=None, help=_('Description of the module.')) @utils.arg('--datastore', metavar='', default=None, help=_('Name or ID of datastore this module can be applied to. ' 'If not specified, module can be applied to all ' 'datastores.')) @utils.arg('--all_datastores', default=None, action='store_const', const=True, help=_('Module is valid for all datastores.')) @utils.arg('--datastore_version', metavar='', default=None, help=_('Name or ID of datastore version this module can be applied ' 'to. If not specified, module can be applied to all ' 'versions.')) @utils.arg('--all_datastore_versions', default=None, action='store_const', const=True, help=_('Module is valid for all datastore versions.')) @utils.arg('--auto_apply', action='store_true', default=None, help=_('Automatically apply this module when creating an instance ' 'or cluster. Admin only.')) @utils.arg('--no_auto_apply', dest='auto_apply', action='store_false', default=None, help=_('Do not automatically apply this module when creating an ' 'instance or cluster. Admin only.')) @utils.arg('--all_tenants', action='store_true', default=None, help=_('Module is valid for all tenants. Admin only.')) @utils.arg('--no_all_tenants', dest='all_tenants', action='store_false', default=None, help=_('Module is valid for current tenant only. Admin only.')) @utils.arg('--hidden', action='store_true', default=None, help=_('Hide this module from non-admin users. Useful in creating ' 'auto-apply modules without cluttering up module lists. ' 'Admin only.')) @utils.arg('--no_hidden', dest='hidden', action='store_false', default=None, help=_('Allow all users to see this module. Admin only.')) @utils.arg('--live_update', action='store_true', default=None, help=_('Allow module to be updated or deleted even if it is ' 'already applied to a current instance or cluster.')) @utils.arg('--no_live_update', dest='live_update', action='store_false', default=None, help=_('Restricts a module from being updated or deleted if it is ' 'already applied to a current instance or cluster.')) @utils.arg('--priority_apply', action='store_true', default=None, help=_('Sets a priority for applying the module. All priority ' 'modules will be applied before non-priority ones. ' 'Admin only.')) @utils.arg('--no_priority_apply', dest='priority_apply', action='store_false', default=None, help=_('Removes apply priority from the module. Admin only.')) @utils.arg('--apply_order', type=int, default=None, choices=range(0, 10), help=_('Sets an order for applying the module. Modules with a ' 'lower value will be applied before modules with a higher ' 'value. Modules having the same value may be ' 'applied in any order (default %(default)s).')) @utils.arg('--full_access', action='store_true', default=None, help=_("Marks a module as 'non-admin', unless an admin-only " "option was specified. Admin only.")) @utils.arg('--no_full_access', dest='full_access', action='store_false', default=None, help=_('Restricts modification access for non-admin. Admin only.')) @utils.service_type('database') def do_module_update(cs, args): """Update a module.""" module = _find_module(cs, args.module) contents = args.file.read() if args.file else None visible = not args.hidden if args.hidden is not None else None datastore_args = {'datastore': args.datastore, 'datastore_version': args.datastore_version} updated_module = cs.modules.update( module, name=args.name, module_type=args.type, contents=contents, description=args.description, all_tenants=args.all_tenants, auto_apply=args.auto_apply, visible=visible, live_update=args.live_update, all_datastores=args.all_datastores, all_datastore_versions=args.all_datastore_versions, priority_apply=args.priority_apply, apply_order=args.apply_order, full_access=args.full_access, **datastore_args) _print_object(updated_module) @utils.arg('module', metavar='', type=str, help=_('Name or ID of the module.')) @utils.arg('--md5', metavar='', type=str, default=None, help=_('Reapply the module only to instances applied ' 'with the specific md5.')) @utils.arg('--include_clustered', action='store_true', default=False, help=_('Include instances that are part of a cluster ' '(default %(default)s).')) @utils.arg('--batch_size', metavar='', type=int, default=None, help=_('Number of instances to reapply the module to before ' 'sleeping.')) @utils.arg('--delay', metavar='', type=int, default=None, help=_('Time to sleep in seconds between applying batches.')) @utils.arg('--force', action='store_true', default=False, help=_('Force reapply even on modules already having the ' 'current MD5')) @utils.service_type('database') def do_module_reapply(cs, args): """Reapply a module.""" module = _find_module(cs, args.module) cs.modules.reapply(module, args.md5, args.include_clustered, args.batch_size, args.delay, args.force) @utils.arg('module', metavar='', help=_('ID or name of the module.')) @utils.service_type('database') def do_module_delete(cs, args): """Delete a module.""" module = _find_module(cs, args.module) cs.modules.delete(module) @utils.arg('instance', metavar='', type=str, help=_('ID or name of the instance.')) @utils.service_type('database') def do_module_list_instance(cs, args): """Lists the modules that have been applied to an instance.""" instance = _find_instance(cs, args.instance) module_list = cs.instances.modules(instance) utils.print_list( module_list, ['id', 'name', 'type', 'md5', 'created', 'updated']) @utils.arg('module', metavar='', type=str, help=_('ID or name of the module.')) @utils.arg('--include_clustered', action="store_true", default=False, help=_("Include instances that are part of a cluster " "(default %(default)s).")) @utils.arg('--limit', metavar='', default=None, help=_('Return up to N number of the most recent results.')) @utils.arg('--marker', metavar='', type=str, default=None, help=_('Begin displaying the results for IDs greater than the ' 'specified marker. When used with --limit, set this to ' 'the last ID displayed in the previous run.')) @utils.service_type('database') def do_module_instances(cs, args): """Lists the instances that have a particular module applied.""" module = _find_module(cs, args.module) items = cs.modules.instances( module, limit=args.limit, marker=args.marker, include_clustered=args.include_clustered) instance_list = items while not args.limit and items.next: items = cs.modules.instances(module, marker=items.next) instance_list += items _print_instances(instance_list, utils.is_admin(cs)) @utils.arg('module', metavar='', type=str, help=_('ID or name of the module.')) @utils.arg('--include_clustered', action="store_true", default=False, help=_("Include instances that are part of a cluster " "(default %(default)s).")) @utils.service_type('database') def do_module_instance_count(cs, args): """Lists a count of the instances for each module md5.""" module = _find_module(cs, args.module) count_list = cs.modules.instances( module, include_clustered=args.include_clustered, count_only=True) field_list = ['module_name', 'min_updated_date', 'max_updated_date', 'module_md5', 'current', 'instance_count'] utils.print_list(count_list, field_list, labels={'module_md5': 'Module MD5', 'instance_count': 'Count', 'module_id': 'Module ID'}) @utils.arg('cluster', metavar='', help=_('ID or name of the cluster.')) @utils.service_type('database') def do_cluster_modules(cs, args): """Lists all modules for each instance of a cluster.""" cluster = _find_cluster(cs, args.cluster) instances = cluster._info['instances'] module_list = [] for instance in instances: new_list = cs.instances.modules(instance['id']) for item in new_list: item.instance_id = instance['id'] item.instance_name = instance['name'] module_list += new_list utils.print_list( module_list, ['instance_name', 'name', 'type', 'md5', 'created', 'updated'], labels={'name': 'Module Name', 'type': 'Module Type'}) @utils.arg('instance', metavar='', type=str, help=_('ID or name of the instance.')) @utils.arg('modules', metavar='', type=str, nargs='+', default=[], help=_('ID or name of the module.')) @utils.service_type('database') def do_module_apply(cs, args): """Apply modules to an instance.""" instance = _find_instance(cs, args.instance) modules = [] for module in args.modules: modules.append(_find_module(cs, module)) result_list = cs.instances.module_apply(instance, modules) utils.print_list( result_list, ['name', 'type', 'datastore', 'datastore_version', 'status', 'message'], labels={'datastore_version': 'Version'}) @utils.arg('instance', metavar='', type=str, help=_('ID or name of the instance.')) @utils.arg('module', metavar='', type=str, help=_('ID or name of the module.')) @utils.service_type('database') def do_module_remove(cs, args): """Remove a module from an instance.""" instance = _find_instance(cs, args.instance) module = _find_module(cs, args.module) cs.instances.module_remove(instance, module) @utils.arg('instance', metavar='', type=str, help=_('ID or name of the instance.')) @utils.service_type('database') def do_module_query(cs, args): """Query the status of the modules on an instance.""" instance = _find_instance(cs, args.instance) result_list = cs.instances.module_query(instance) utils.print_list( result_list, ['name', 'type', 'datastore', 'datastore_version', 'status', 'message', 'created', 'updated'], labels={'datastore_version': 'Version'}) @utils.arg('instance', metavar='', type=str, help=_('ID or name of the instance.')) @utils.arg('--directory', metavar='', type=str, help=_('Directory to write module content files in. It will ' 'be created if it does not exist. Defaults to the ' 'current directory.')) @utils.arg('--prefix', metavar='', type=str, help=_('Prefix to prepend to generated filename for each module.')) @utils.service_type('database') def do_module_retrieve(cs, args): """Retrieve module contents from an instance.""" instance = _find_instance(cs, args.instance) saved_modules = cs.instances.module_retrieve( instance, args.directory, args.prefix) for module_name, filename in saved_modules.items(): print(_("Module contents for '%(module)s' written to '%(file)s'") % {'module': module_name, 'file': filename}) @utils.arg('instance', metavar='', help=_('Id or Name of the instance.')) @utils.service_type('database') def do_log_list(cs, args): """Lists the log files available for instance.""" instance = _find_instance(cs, args.instance) log_list = cs.instances.log_list(instance) utils.print_list(log_list, ['name', 'type', 'status', 'published', 'pending', 'container', 'prefix']) @utils.arg('instance', metavar='', help=_('Id or Name of the instance.')) @utils.arg('log_name', metavar='', help=_('Name of log to show.')) @utils.service_type('database') def do_log_show(cs, args): """Instructs Trove guest to show details of log.""" try: instance = _find_instance(cs, args.instance) log_info = cs.instances.log_show(instance, args.log_name) _print_object(log_info) except exceptions.GuestLogNotFoundError: print(NO_LOG_FOUND_ERROR % {'log_name': args.log_name, 'instance': instance}) except Exception as ex: error_msg = ex.message.split('\n') print(error_msg[0]) @utils.arg('instance', metavar='', help=_('Id or Name of the instance.')) @utils.arg('log_name', metavar='', help=_('Name of log to publish.')) @utils.service_type('database') def do_log_enable(cs, args): """Instructs Trove guest to start collecting log details.""" try: instance = _find_instance(cs, args.instance) log_info = cs.instances.log_action(instance, args.log_name, enable=True) _print_object(log_info) except exceptions.GuestLogNotFoundError: print(NO_LOG_FOUND_ERROR % {'log_name': args.log_name, 'instance': instance}) except Exception as ex: error_msg = ex.message.split('\n') print(error_msg[0]) @utils.arg('instance', metavar='', help=_('Id or Name of the instance.')) @utils.arg('log_name', metavar='', help=_('Name of log to publish.')) @utils.arg('--disable', action='store_true', default=False, help=_('Disable the collection of the specified log.')) @utils.service_type('database') def do_log_disable(cs, args): """Instructs Trove guest to stop collecting log details.""" try: instance = _find_instance(cs, args.instance) log_info = cs.instances.log_action(instance, args.log_name, disable=args.discard) _print_object(log_info) except exceptions.GuestLogNotFoundError: print(NO_LOG_FOUND_ERROR % {'log_name': args.log_name, 'instance': instance}) except Exception as ex: error_msg = ex.message.split('\n') print(error_msg[0]) @utils.arg('instance', metavar='', help=_('Id or Name of the instance.')) @utils.arg('log_name', metavar='', help=_('Name of log to publish.')) @utils.arg('--disable', action='store_true', default=False, help=_('Stop collection of specified log.')) @utils.arg('--discard', action='store_true', default=False, help=_('Discard published contents of specified log.')) @utils.service_type('database') def do_log_publish(cs, args): """Instructs Trove guest to publish latest log entries on instance.""" try: instance = _find_instance(cs, args.instance) log_info = cs.instances.log_action( instance, args.log_name, publish=True, disable=args.disable, discard=args.discard) _print_object(log_info) except exceptions.GuestLogNotFoundError: print(NO_LOG_FOUND_ERROR % {'log_name': args.log_name, 'instance': instance}) except Exception as ex: error_msg = ex.message.split('\n') print(error_msg[0]) @utils.arg('instance', metavar='', help=_('Id or Name of the instance.')) @utils.arg('log_name', metavar='', help=_('Name of log to publish.')) @utils.service_type('database') def do_log_discard(cs, args): """Instructs Trove guest to discard the container of the published log.""" try: instance = _find_instance(cs, args.instance) log_info = cs.instances.log_action(instance, args.log_name, discard=True) _print_object(log_info) except exceptions.GuestLogNotFoundError: print(NO_LOG_FOUND_ERROR % {'log_name': args.log_name, 'instance': instance}) except Exception as ex: error_msg = ex.message.split('\n') print(error_msg[0]) @utils.arg('instance', metavar='', help=_('Id or Name of the instance.')) @utils.arg('log_name', metavar='', help=_('Name of log to publish.')) @utils.arg('--lines', metavar='', default=50, type=int, help=_('Publish latest entries from guest before display.')) @utils.service_type('database') def do_log_tail(cs, args): """Display log entries for instance.""" try: instance = _find_instance(cs, args.instance) log_gen = cs.instances.log_generator(instance, args.log_name, args.lines) for log_part in log_gen(): print(log_part, end="") except exceptions.GuestLogNotFoundError: print(NO_LOG_FOUND_ERROR % {'log_name': args.log_name, 'instance': instance}) except Exception as ex: error_msg = ex.message.split('\n') print(error_msg[0]) @utils.arg('instance', metavar='', help=_('Id or Name of the instance.')) @utils.arg('log_name', metavar='', help=_('Name of log to publish.')) @utils.arg('--file', metavar='', default=None, help=_('Path of file to save log to for instance.')) @utils.service_type('database') def do_log_save(cs, args): """Save log file for instance.""" try: instance = _find_instance(cs, args.instance) filename = cs.instances.log_save(instance, args.log_name, filename=args.file) print(_('Log "%(log_name)s" written to %(file_name)s') % {'log_name': args.log_name, 'file_name': filename}) except exceptions.GuestLogNotFoundError: print(NO_LOG_FOUND_ERROR % {'log_name': args.log_name, 'instance': instance}) except Exception as ex: error_msg = ex.message.split('\n') print(error_msg[0]) # @utils.arg('datastore_version', # metavar='', # help='Datastore version name or UUID assigned to the ' # 'configuration group.') # @utils.arg('name', metavar='', # help='Name of the datastore configuration parameter.') # @utils.arg('restart_required', metavar='', # help='Flags the instance to require a restart if this ' # 'configuration parameter is new or changed.') # @utils.arg('data_type', metavar='', # help='Data type of the datastore configuration parameter.') # @utils.arg('--max_size', metavar='', # help='Maximum size of the datastore configuration parameter.') # @utils.arg('--min_size', metavar='', # help='Minimum size of the datastore configuration parameter.') # @utils.service_type('database') # def do_configuration_parameter_create(cs, args): # """Create datastore configuration parameter""" # cs.mgmt_config_params.create( # args.datastore_version, # args.name, # args.restart_required, # args.data_type, # args.max_size, # args.min_size, # ) # @utils.arg('datastore_version', # metavar='', # help='Datastore version name or UUID assigned to the ' # 'configuration group.') # @utils.arg('name', metavar='', # help='Name of the datastore configuration parameter.') # @utils.arg('restart_required', metavar='', # help='Sets the datastore configuration parameter if it ' # 'requires a restart or not.') # @utils.arg('data_type', metavar='', # help='Data type of the datastore configuration parameter.') # @utils.arg('--max_size', metavar='', # help='Maximum size of the datastore configuration parameter.') # @utils.arg('--min_size', metavar='', # help='Minimum size of the datastore configuration parameter.') # @utils.service_type('database') # def do_configuration_parameter_modify(cs, args): # """Modify datastore configuration parameter""" # cs.mgmt_config_params.modify( # args.datastore_version, # args.name, # args.restart_required, # args.data_type, # args.max_size, # args.min_size, # ) # @utils.arg('datastore_version', # metavar='', # help='Datastore version name or UUID assigned to the ' # 'configuration group.') # @utils.arg('name', metavar='', # help='UUID of the datastore configuration parameter.') # @utils.service_type('database') # def do_configuration_parameter_delete(cs, args): # """Modify datastore configuration parameter""" # cs.mgmt_config_params.delete( # args.datastore_version, # args.name, # ) @utils.arg('tenant_id', metavar='', help=_('Id of tenant for which to show quotas.')) @utils.service_type('database') def do_quota_show(cs, args): """Show quotas for a tenant.""" utils.print_list(cs.quota.show(args.tenant_id), ['resource', 'in_use', 'reserved', 'limit']) @utils.arg('tenant_id', metavar='', help=_('Id of tenant for which to update quotas.')) @utils.arg('resource', metavar='', help=_('Id of resource to change.')) @utils.arg('limit', metavar='', type=int, help=_('New limit to set for the named resource.')) @utils.service_type('database') def do_quota_update(cs, args): """Update quotas for a tenant.""" utils.print_dict(cs.quota.update(args.tenant_id, {args.resource: args.limit})) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/v1/storage.py0000664000175000017500000000270700000000000022335 0ustar00zuulzuul00000000000000# 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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/v1/users.py0000664000175000017500000001162200000000000022026 0ustar00zuulzuul00000000000000# 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 import common from troveclient import exceptions from troveclient.v1 import databases 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, users): """Create users with permissions to the specified databases.""" body = {"users": users} url = "/instances/%s/users" % base.getid(instance) resp, body = self.api.client.post(url, body=body) common.check_for_exceptions(resp, body, url) def delete(self, instance, username, hostname=None): """Delete an existing user in the specified instance.""" user = common.quote_user_host(username, hostname) url = "/instances/%s/users/%s" % (base.getid(instance), user) resp, body = self.api.client.delete(url) common.check_for_exceptions(resp, body, url) def list(self, instance, limit=None, marker=None): """Get a list of all Users from the instance's Database. :rtype: list of :class:`User`. """ url = "/instances/%s/users" % base.getid(instance) return self._paginated(url, "users", limit, marker) def get(self, instance, username, hostname=None): """Get a single User from the instance's Database. :rtype: :class:`User`. """ user = common.quote_user_host(username, hostname) url = "/instances/%s/users/%s" % (base.getid(instance), 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`. """ if not newuserattr: raise exceptions.ValidationError("No updates specified for user %s" % username) instance_id = base.getid(instance) user = common.quote_user_host(username, hostname) user_dict = {} user_dict['user'] = newuserattr url = "/instances/%s/users/%s" % (instance_id, user) resp, body = self.api.client.put(url, body=user_dict) common.check_for_exceptions(resp, body, url) def list_access(self, instance, username, hostname=None): """Show all databases the given user has access to.""" instance_id = base.getid(instance) user = common.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) common.check_for_exceptions(resp, body, url) 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 = common.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) common.check_for_exceptions(resp, body, url) def revoke(self, instance, username, database, hostname=None): """Revoke from an existing user access permissions to a database.""" instance_id = base.getid(instance) user = common.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) common.check_for_exceptions(resp, body, url) 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) common.check_for_exceptions(resp, body, url) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1726234724.0 python-troveclient-8.6.0/troveclient/v1/volume_types.py0000664000175000017500000000374700000000000023431 0ustar00zuulzuul00000000000000# Copyright 2016 Tesora, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from troveclient import base class VolumeType(base.Resource): """A VolumeType is an Cinder volume type.""" def __init__(self, manager, info, loaded=False): super(VolumeType, self).__init__(manager, info, loaded) if self.id is None and self.str_id is not None: self.id = self.str_id def __repr__(self): return "" % self.name class VolumeTypes(base.ManagerWithFind): """Manage :class:`VolumeType` resources.""" resource_class = VolumeType def list(self): """Get a list of all volume-types. :rtype: list of :class:`VolumeType`. """ return self._list("/volume-types", "volume_types") def list_datastore_version_associated_volume_types(self, datastore, version_id): """Get a list of all volume-types for the specified datastore type and datastore version . :rtype: list of :class:`VolumeType`. """ return self._list("/datastores/%s/versions/%s/volume-types" % (datastore, version_id), "volume_types") def get(self, volume_type): """Get a specific volume-type. :rtype: :class:`VolumeType` """ return self._get("/volume-types/%s" % base.getid(volume_type), "volume_type")