././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740683659.4306943 python_magnumclient-4.8.0/0000775000175000017500000000000000000000000015632 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/.coveragerc0000664000175000017500000000014500000000000017753 0ustar00zuulzuul00000000000000[run] branch = True source = magnumclient omit = magnumclient/tests/* [report] ignore_errors = True ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/.mailmap0000664000175000017500000000013100000000000017246 0ustar00zuulzuul00000000000000# Format is: # # ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/.stestr.conf0000664000175000017500000000006300000000000020102 0ustar00zuulzuul00000000000000[DEFAULT] test_path=${OS_TEST_PATH:-./} top_dir=./ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/.zuul.yaml0000664000175000017500000000041500000000000017573 0ustar00zuulzuul00000000000000- project: templates: - check-requirements - openstack-cover-jobs - openstack-python3-jobs - publish-openstack-docs-pti - build-release-notes-jobs-python3 check: jobs: - openstack-tox-cover: voting: false ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683659.0 python_magnumclient-4.8.0/AUTHORS0000664000175000017500000001423500000000000016707 0ustar00zuulzuul00000000000000Aaron-DH Abhishek Chanda Abhishek Chanda Adrian Otto Akhila Kishore Amey Bhide Andreas Jaeger Andreas Jaeger Andrew Melton Anh Tran Bharat Kunwar Bharat Kunwar Bharath Thiruveedula Boris Pilka Cedric Brandily Christoph Jansen Clenimar Filemon Corey Bryant Corey O'Brien Costin Gamenț Dai Dang Van Dale Smith Dane LeBlanc Daneyon Hansen Daniel Abad Davanum Srinivas Davanum Srinivas David Liu David Rabel Diogo Guerra Diogo Guerra Dmitriy Rabotyagov Dmitriy Rabotyagov Doug Hellmann Eli Qiao Eric Brown Erik Olof Gunnar Andersson Fang Fenghua <449171342@qq.com> Fang fenghua <449171342@qq.com> Feilong Wang Felipe Reyes Feng Shengqin Flavio Percoco Ghanshyam Mann Haiwei Xu Hangdong Zhang Hervé Beraud Hieu LE Hongbin Lu Hongbin Lu Hongbn Lu Hua Wang Ian Cordasco Jake Yip Jake Yip James Page James Page Janek Lehr Janonymous Jason Dunsmore Jay Lau (Guangya Liu) Jay Lau Jennifer Carlucci Jeremy Stanley Johannes Grassler Kennan Kennan Kiran_totad Lan Qi song Lin Yang Lucky samadhiya M V P Nitesh Madhuri Madhuri Kumari Madhuri Kumari Madhuri Kumari Manjeet Singh Bhatia Mark Goddard Michael Lekkas Michal Arbet Michal Nasiadka Mike Fedosin Monty Taylor Motohiro OTSUKA Murali Allada Namrata Nguyen Hai Niall Bunting OTSUKA, Yuanying OTSUKA, Yuanying OpenStack Release Bot PanFengyun Pavlo Shchelokovskyy Pierre Riteau Rajiv Kumar Ricardo Rocha Sean McGinnis Sergey Vilgelm Spyros Trigazis Spyros Trigazis Stephen Watson Steven Dake Steven Dake Surojit Pathak Swapnil Kulkarni (coolsvap) Takashi Kajinami Theodoros Tsioutsias Tobias Urdin Tom Cammann Tom Cammann Tovin Seven Travis Holton Vieri <15050873171@163.com> Vijendar Komalla Vikas Choudhary Vilobh Meshram Vipul Nayyar Vivek Jain Vu Cong Tuan Wanlong Gao Wenzhi Yu XiaojueGuan Yang Hongyang Yolanda Robla Zhenguo Niu chenaidong1 chenlx coldmoment coldmoment digambar digambar gecong1973 guo yunxian houming-wang jacky06 kangyufei kartik494 kavithahr maliki melissaml npraveen35 pawnesh.kumar phelanm qingszhao rajat29 ricolin ricolin ricolin shu-mutou space sunjia ting.wang venkatamahesh wangbo wanghui wangjiaqi07 wangqun wangzihao wu.chunyang wu.shiming xiexs yanghuichan yangyong yatin yatin yatin karel yatinkarel ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/CONTRIBUTING.rst0000664000175000017500000000110400000000000020267 0ustar00zuulzuul00000000000000If you would like to contribute to the development of OpenStack, you must follow the steps in this page: https://docs.openstack.org/infra/manual/developers.html Once those steps have been completed, changes to OpenStack should be submitted for review via the Gerrit tool, following the workflow documented at: https://docs.openstack.org/infra/manual/developers.html#development-workflow Pull requests submitted through GitHub will be ignored. Bugs should be filed on Storyboard, not GitHub: https://storyboard.openstack.org/#!/project/openstack/python-magnumclient ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683659.0 python_magnumclient-4.8.0/ChangeLog0000664000175000017500000006024100000000000017407 0ustar00zuulzuul00000000000000CHANGES ======= 4.8.0 ----- * reno: Update master for unmaintained/2023.1 * Bump hacking * Update master for stable/2024.2 4.7.0 ----- 4.6.0 ----- * Update VerifiedHTTPSConnection for Python >= 3.12 * Refactor TLS cert fetching for config * Remove extraneous quote in non-TLS kubeconfig 4.5.0 ----- * Display project\_id for cluster template * Replace deprecated inspector.getargspec * Update Python runtime for 2024.2 * Always use io module * Add insecure\_registry to osc part of the client * reno: Update master for unmaintained/zed * Update master for stable/2024.1 4.4.0 ----- * reno: Update master for unmaintained/yoga * Update python classifier in setup.cfg 4.3.0 ----- * Bugfix: master\_lb\_enabled flag now works as intended * setup.cfg: rename deprecated dash-separated options * Update master for stable/2023.2 * Use TOX\_CONSTRAINTS\_FILE 4.2.0 ----- * Allow directly output cluster config * Fix multi-line strings spaces * Add python 3.10 to setup.cfg metadata * Update master for stable/2023.1 * Clean up tox.ini 4.1.0 ----- * Update client.authentication.k8s.io api version for kubeconfig * Support tox4 * Remove translation sections from setup.cfg * Remove pypy and bump py37 to py38 * Switch to 2023.1 Python3 unit tests and generic template name * Update master for stable/zed 4.0.0 ----- * Drop bay and baymodel * Update python testing as per zed cycle testing runtime * remove unicode from code * Imported Translations from Zanata 3.7.0 ----- * Remove translation sections from setup.cfg * Drop lower-constraints.txt and its testing * Fix test\_help\_on\_subcommand on Python 3.10 * Update master for stable/yoga 3.6.0 ----- * Add Python3 wallaby unit tests * Update master for stable/victoria * Update master for stable/xena 3.5.0 ----- * Fix master\_lb\_enabled for cluster creation * Fix failing to parse json error msg * Update master for stable/wallaby 3.4.0 ----- * Uncap PrettyTable * Add CT tags argument and field to client * Nodegroup min\_node\_count should default to 0 * Drop lower constraints testing 3.3.0 ----- * Show docker\_volume\_size in \`cluster show\` 3.2.1 ----- * Migrate testing to Ubuntu Focal 3.2.0 ----- * Use overlay2 driver by default * Use unittest.mock instead of mock * Support master\_lb\_enabled when creating cluster * Show cluster fixed\_network, fixed\_subnet, floating\_ip\_enabled * Print response after issuing CA rotate request * Stop to use the \_\_future\_\_ module 3.1.0 ----- * Switch to newer openstackdocstheme and reno versions * Rename variables to address pep8 error * Labels override * Fix hacking min version to 3.0.1 * Remove six * Fix raw\_request of SessionClient * Add py38 package metadata * Support updating cluster health status * Use unittest.mock instead of third party mock * Add Python3 victoria unit tests * Update master for stable/ussuri * Check response type in \_extract\_error\_json * Cleanup py27 support 3.0.0 ----- * Update master for stable/train * Update to hacking 3.0 * Update hacking for Python3 * Drop py27 tests * Bugfix: Use fields option for cluster template list 2.17.0 ------ * Allow cluster config for any cluster state * Add nodegroup CRUD commands * replace launchpad urls with storyboard urls * Replace git.openstack.org URLs with opendev.org URLs 2.15.0 ------ * Support network, subnet and FIP when creating cluster 2.14.0 ------ * Add Python 3 Train unit tests * Conditional hidden arg for backward compatibility * Fix coverage test * Blacklist bandit 1.6.0 and cap Sphinx on Python2 * Display project\_id for cluster show * OpenDev Migration Patch * Dropping the py35 testing 2.13.0 ------ * Add nodegroup list/show commands * Support upgrade API * Support resize api * Update master for stable/stein * Support health\_status on client side * python3 fixes 2.12.0 ------ * Keystone auth support * add python 3.7 unit test job * Add hidden property to cluster template * Fix py37 compatibility * Use oslo\_serialization instead of the json module directly * Use template for lower-constraints * Change openstack-dev to openstack-discuss * Add Python 3.6 classifier to setup.cfg * add python 3.6 unit test job * Refactor the getid method base.py * Trivial: Update pypi url to new url 2.11.0 ------ * Fix crash on Service catalog empty 403 response * add python 3.6 unit test job * switch documentation job to new PTI * import zuul job settings from project-config * Add release notes for magnum client * [k8s] Add embed certs to config 2.10.0 ------ * Switch to stestr * osc: Don't pass parameters with null value * Update links in README * fix tox python3 overrides * Fix entrypoints for quotas * Follow the new PTI for document build * add lower-constraints job * OSC command for magnum quota's * Add deprecation warnings to magnum client commands * Replace six.iteritems() with dict.items() in python-magnumclient * OSC command for ca-show, ca-sign, ca-roatate and stats-list * Now cluster-template-update works for "labels" * Make cluster-config rbac compatible for kubebernetes 2.8.0 ----- * Add disable floating ip parameter * Updated from global requirements * Cleanup test-requirements * Updated from global requirements * Updated from global requirements * Set --labels default to None on cluster create * OSC: Add --flavor to coe cluster create * Add missing master\_flavor\_id in cluster attributes * Avoid tox\_install.sh for constraints support * Updated from global requirements * Replace six.iteritems() with .items() * OSC: Add --master-flavor to coe cluster create * Make cluster config --force a boolean * Updated from global requirements * Add --labels for cluster-create * OSC: Add --labels to coe cluster create * Add oslo.log as requirement * OSC: Add magnum service-list command * Now \`name\` is a positional argument in cluster creation * OSC: Remove unused files * inline comment typo fix 2.7.0 ----- * Remove log translations * Updated from global requirements * OSC: Add cluster config command * OSC 4/4 Add remaining cluster commands * OSC 3/4 Add remaining CT commands * OSC 2/4 Add Cluster Create and List * Updated from global requirements * OSC 1/4 Add CT create and UT framework * Remove docker-volume-size from cluster-list * Updated from global requirements * Make cluster name positional in ca-show * remove slash from kubernetes cluster context name * Update the documentation link for doc migration * Updated from global requirements * Updated from global requirements * switch to openstackdocstheme * Don't set a default for docker\_volume\_size * Updated from global requirements * Make --profile load from environment variables * Add --docker-volume-size for cluster-create * Updated from global requirements * Change assertTrue(isinstance()) by optimal assert 2.6.0 ----- * Add the support for 'detail' flag * Updated from global requirements * Replace assertRaisesRegexp with assertRaisesRegex * Updated from global requirements * Updated from global requirements * Magnum client suport insecure\_registry * Use assertIsNone(...) instead of assertEqual(None, ...) * Updated from global requirements * Updated from global requirements * Make --cluster option required for ca-rotate * Move cover.sh to the tools directory * Remove support for keyapir UUID * Fix wrong path reference * Update .gitignore to ignore .eggs * Increase Test coverage * The python 3.4 support is removed * Correct mistake from OSprofiler help * Fix UT for duplicate name args test case * Updated from global requirements * Add magnum client support for resource quotas * Update test requirement * Allow name as positional argument * Print exception details on update failure * Simplify magnumclient.shell.OpenStackMagnumShell.main() * Compare test coverage with the master branch * Updated from global requirements 2.5.0 ----- * Add usage docs for magnum client * Updated from global requirements * Increase UT Coverage * Add the OSC 'cluster template list' command * Add osc-lib to requirements.txt * Add ca-rotate command to magnumclient 2.4.0 ----- * Adding stats-list command to magnum client * Added link for modindex * Increase UT Coverage * Fix passing TLS\_VERIFIED in clusters/bays\_shell * Increase UT Coverage * Integrate OSprofiler in Magnum Client * Remove H903 error in sources * Updated from global requirements * Fix: swarm cluster-config, bay-config with tls\_disabled * Updated from global requirements * Pass 'api\_version' to create HTTPClient * Mark help messages for Translation * Allow cluster-config on cluster status 'ROLLBACK\_COMPLETE' * Add OSC command for cluster\_templates list * Rollback cluster/bay on update failure * Updated from global requirements * Magnum cluster-config/bay-config compatible with py3 * Updated from global requirements * Show team and repo badges on README * Remove Not used Classes/Methods from apiclient.base * Use assert\_called\_\*/assert\_not\_called to verify mock calls * Fix: some typos in unit test * Use assert\_called\_\*/assert\_not\_called to verify mock calls * Renames \*-id parameters * Implement Parameter Deprecation * Use assert\_called\_\*/assert\_not\_called to verify mock calls * Add a deprecation message to the bay\* commands' help text * Updated from global requirements * Increase UT coverage * Remove invalid check for 'manifest' path * Updated from global requirements * Add .venv directory to .gitignore * Enable DeprecationWarning in test environments * Updated from global requirements * Add \_\_ne\_\_ built-in function * Add missing options for HTTPClient if auth\_token given * Updated from global requirements * Remove white space between print and () * Add --keypair-id for cluster-create * Replace assertTrue(a in b) with assertIn(a, b) * Updated from global requirements * Fix the generated k8s config file * cluster-config return absolute path * delete python bytecode including pyo before every test run * Fix a keyward arguement error on bay-config * Add magnum.bash\_completion This can support the function of autocomplete for magnum's commands * Cluster creation command returns complete cluster uuid * Sync tools/tox\_install.sh 2.3.0 ----- * Updated from global requirements * Magnum client to support sync and async bay opts * Add microversioning support for httpclient * Add floating\_ip\_enabled attributes to baymodel * Cleanup coverage configuration * Fixes bay\_uuid parameter issue in functional tests * Fix to use HttpClient if token is given * Updates certificate CLI to use cluster * Updated from global requirements * Adds 'cluster' and 'cluster template' * Append value using comma if key exists in label * bay-config return absolute path * Updated from global requirements * Use testr coverage feature * Use upper constraints for all jobs in tox.ini * Updated from global requirements * Enabled magnum client to display detailed information * Updated from global requirements * Add Python 3.5 classifier and venv * Base OSC plugin support * Use os-client-config in shell * Remove discover from test-requirements * Increase unit test coverage for module baymodels * Update the magnum client to send the latest version * Updated from global requirements * Add shell command bay-config * Display baymodel info with bay-show command 2.2.0 ----- * Updated from global requirements * Bay\_create\_timeout should be set to 60 default * Prints '-' instead of 'None' when data is None * Updated from global requirements * Pass a flag to disable LB in baymodels * Log appropriate error while exception * Add fixed\_subnet attributes to baymodel * Updated from global requirements * Support OS\_PROJECT\_\* env variables * Completely remove openstack common modules * Updated from global requirements * Add docker-storage-driver argument to baymodel * Updated from global requirements * Updated from global requirements * Updated from global requirements * Tox test should respect upper-constraints * Switch to a new service type "container-infra" * Add mesos\_slave\_executor\_environment\_variables validate in CLI * Add some fields back to bay\_list * Updated from global requirements * Add '--fields' to show more columns for bay-list * Update the home-page with developer documentation * Correcting help messages of baymodel * Add python3 to python classifiers * Add '--fields' to show more columns for baymodel-list * Corrected spacing mistake in baymodels\_shell.py * Updated from global requirements * Fix the bug when some value in labels has comma, it will fail * Remove k8s pods, rcs, svc and container API calls 2.1.0 ----- * Updated from global requirements * Add endpoint\_override parameter to python-magnumclient * Updated from global requirements * Revert "Parameter format change for ca-show and ca-sign" * Updated from global requirements * Parameter format change for ca-show and ca-sign * Updated from global requirements * Fix the container-list with --limit 'a negative number' * Remove the update function of container * Add missing user message * Add marker/limit/sort-key/sort-dir features for bay-list * Add marker/limit/sort-key/sort-dir features for container-list * Allow semicolons in list of labels * Add unit tests for MServiceManager.list() method * Add unit tests for ContainerManager.list() method * Add unit tests for BayModelManager.list() method * Add unit tests for BayManager.list() method * Adapt http response error message parsing 2.0.0 ----- * Fix incorrect initialization of OrderedDict * Remove bandit.yaml in favor of defaults * Revert "Completely remove openstack common modules" * Use six.u instead of u'' * Completely remove openstack common modules * Add param for magnum baymodel-list * Move bandit into pep8 * Client : Create BayModel with server type(VM/BM) * Updated from global requirements * Sync with oslo-incubator * Updated from global requirements * Remove node object from magnumclient * Exception not catched when bay create failed * Fix a spell typo * The rc-list should contain bay\_uuid * Remove unused attribute "ssh\_authorized\_key" * Update translation setup * Fix incorrect help message * Add Keystone v3 compatibility * Updated from global requirements * Adds Container Volume Model volume\_driver support * Python 3.4 compatibility * Add --insecure option * Fix endpoint\_types * Updated from global requirements * Replace six.iteritems(dict) with dict.items() * Change the endpoint env varible * Prevent list rcs when bay is not ready * raise exception when create rc with invalid bay status * raise exception when create container with invalid bay status * raise exception when create pod with invalid bay status * Revert "Pass environment variables of proxy to tox" * Add debug testenv in tox * Remove check for bay state on ca-sign and ca-show * Fix bypass\_url and errors with no headers * Remove unnecessary check when create bay * Fix test\_keys\_and\_vals\_to\_strs dict assert * Add unit test for pod-create with invalid bay status * Prevent list pods while bay is not ready * Updated from global requirements * Python 3 deprecated the logger.warn method in favor of warning * Remove references to \_i18n and apiclient.exceptions * Handle list object when print a dict * Put py34 first in the env order of tox * Updated from global requirements * Packages missing from requirements.txt * Add type validation and default for some parameter * Add py3 compatibility for unicode builtin * Add optional parameter --bay when doing container list * Updated from global requirements * Drop py33 support * use wild card for passing env variables * Add registry\_enabled option to baymodel-create * Improve client master\_count validation * Improve client node\_count validation * Fix default bay create timeout * Remove py26 support * Fix POD CLI to work with Object from Bay * Fix RC CLI to work with Object from Bay * Add .idea directory to .gitignore * Fix Service CLI to work with Object from Bay * Handle faultstring when using SessionClient * fix wrong function description * fix exceptions.from\_response() parameter * Fix the ouput of 'container-create' when it fails * Make bandit included in test-requirements.txt * Correct help message of '--name' for baymodel-create * Remove test\_shell\_args.py 1.1.0 ----- * Revise help message of '--docker-volume-size' for baymodel-create * Updated from global requirements * Improve readme contents * Split v1 shell sub-command into specific files * Updated from global requirements * Fix RC CLI to work with Object from Bay changes * Update baymodel-update help doc * Delete python bytecode before every test run * Add .DS\_Store to .gitignore * Updated from global requirements * Use keystoneauth to create a Session * Make image as required for do\_container\_create * Add test for container\_create * Add unversioned client constructor * Fix rc cli to work with bay identifier * Updated from global requirements * Updated from global requirements * Add the introduce of the command "magnum help ca-show" * Add the introduce of the command "magnum help ca-sign" 1.0.0.0b1 --------- * Updated from global requirements * Pass bay\_ident to k8s objects methods * Add an option to specify container memory size * Add support for 'baymodel-update' in python-client * Support for public baymodels * Rename 'insecure' baymodel attribute to 'tls\_disabled' * Client: Pass bay\_uuid to Service Read/Write API * Client: Pass bay\_uuid to Pod Read/Write API * Client: Pass bay\_uuid to RC Read/Write API * Add insecure flag to baymodel * Magnum show cmds display dictionaries with unicode u chars * Adding 'magnum service-list' * Add certificates operations * Change ignore-errors to ignore\_errors * Add support for python >= 3.4 * Updated from global requirements * Adds Labels Support * Adds Container Network Model network\_driver support * Update help message for coe service related command * Add missed space between two words * Updated from global requirements * Rename existing service-\* to coe-service-\* * Updated from global requirements * Remove name from test token * This adds proxy feature in magnum client * Updated from global requirements * Updated from global requirements * Updated from global requirements * Add support for multiple master nodes * Updated from global requirements * Remove uuidutils from openstack.common * Updated from global requirements * Updated from global requirements * Remove H803 rule * Updated from global requirements * Updated from global requirements * Updated from global requirements * Rename image\_id to image when create a container * Updated from global requirements * Add missing dependency oslo.serialization * Updated from global requirements * Add additional arguments to CLI for container-create * Pass environment variables of proxy to tox * Change container-execute to container-exec * Updated from global requirements * Sync from latest oslo-incubator * Updated from global requirements * Fix translation setup 0.2.1 ----- * Make metavar's consistent * Setup for translation * Updated from global requirements * Bump up to newer hacking * Add support of container resource management with "name" * Add support for container status * Drop use of 'oslo' namespace package 0.2.0 ----- * Remove links attribute from pod show * Log the correct url in debug mode * Add heat timeout to bay-create for magnum client * Add coe attribute to BayModel creation * Add bay\_uuid to container create * Update README to work with release tools * Add support for pod name in pod-update command 0.1.0 ----- * Add bay status check when rc create * Add bay status check when service create * fix a typo in log * Add bay status check when pod create * Rename swarm-token to discovery-url * Support pass command field when create a container * Delegate magnum url search to url\_for method * Remove side effect in get\_keystoneclient * Add unittests for magnumclient.v1.client module * Correct mock use in TestCommandLineArgument.setUp * Uncap library requirements for liberty * Support update a replication controller * Support update a service * Add swarm\_token to bay-create call * Support update a pod * Authenticate once * Add error info detail for magnum cli * Update bandit for latest usage * Add tox bandit support for python-magnumclient * Remove unused get\_projects\_list method * Add support for bay name in bay-update command * Allow bay name when replication controller is created * Update .gitreview for project rename * Allow baymodel name when bay is created * Allow bay name when pod is created * Allow bay name when service is created * Support keystone region * Rename k8s specific bay attributes * Add support of baymodel resource management with "name" * Add support of rc resource management with "name" * Add support of service resource management with "name" * Allow specification of ssh\_authorized\_key 0.0.1 ----- * Add support of pod resource management with "name" * Remove some default values for baymodel create * Add support of bay resource management with "name" * Fix container exec output * Fix the container logs output * Make baymodel-create fail when no argument passed * Display bay status column * tidy up language on one shell help command * Allow adding master flavor id to baymodel * Allow specification of fixed\_network * Allow specification of docker\_volume\_size * Support multiple args in some magnum commands * Remove '--id' option from magnum commands * Make bay-create fail when no argument passed * Adding test for magnumclient argument parsing * Add support for updating a bay * Adding tests for v1/shell.py * Adding test for shell.py * Container logs should use HTTP GET * Add help info for container operations * Update help message for baymodel operations * Add help for magnum object show * Remove 'desc' from container output * Rename \`resource\`\_data/url attributes to manifest/manifest\_url * Default version should be v1 in magnum * Fix an error on not finding log handler * Add id as required for bay-show/delete * Revert "Fix an error on not finding log handler" * Change rc\_data to replicationcontroller\_data * Fix an error on not finding log handler * Make replication controller client works * Remove 'desc' from docker creation attribute * Update test\_update to test\_pod\_update for pod client test * Add client test for k8s replication controller * Add client test for k8s service * Add client test for magnum containers * Add client test for magnum baymodel * Add client test for magnum bay * Add client test for magnum node * Support file path as a service manifest data * Adjusted CLI argument names to use dashes rather than underscores * Add client test for test\_pods * Support file path as a pod data * Add unit test for magnumclient/common/httpclient.py * Add unit test for magnumclient/common/utils.py * Update K8S Pod and Service input parameter * Make id as required when delete or show object info * Construct URI properly for container-execute * Add k8s replication support for magnum client * Ensure a id string is passed when --id is used * Change folder of api to v1 for magumclient * Get container-list CLI working * Do not advertise py33 compatibility * Set HTTP PUT Content-Length for container actions * Implement container actions * Add image\_id attribute for container * Service create only need service filename * pod create only need pod filename * Implement client for service operations * Update parmater name for \_show\_pod * Add baymodel\_id to bay\_create * Add flavor\_id property to baymodel object * Implement pod client CLI * Make bay-create operate again as a result of baymodel * Make bay create work with new bay model structure * Type and image\_id are removed from server * Don't show links when showing bay * Make baymodel-list only show name and UUID * Make baymodel not show links * Implement baymodel in client * Add node object to the python client * Add image\_id and node\_count to bay-create * Workflow documentation is now in infra-manual * Make separate Bay objects for each bay object * Complete implementation of bay operations * Docstring cleanups * Delete bay rather then container when requested * Misc cleanups * Make container in sync with bays and pods * Make pod objects work in ReST Client API * Make bay objects work in ReST Client API * Modify container list attributes * Implements basic container operations * Add client resources and managers * Update apiclient from oslo-incubator * Add apiclient library from oslo-incubator * Added default service type * Skeleton for the cli client * Include the auth module from oslo * Sync common code from oslo-incubator * Added project required files * Add service API methods to what should be implemented * Boilerplate client for communicating with ReST API * Initial commit ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/LICENSE0000664000175000017500000002607500000000000016651 0ustar00zuulzuul00000000000000Apache 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. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740683659.4306943 python_magnumclient-4.8.0/PKG-INFO0000644000175000017500000000556300000000000016736 0ustar00zuulzuul00000000000000Metadata-Version: 2.1 Name: python-magnumclient Version: 4.8.0 Summary: Client library for Magnum API Home-page: https://docs.openstack.org/python-magnumclient/latest/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org 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 :: Implementation :: CPython Classifier: Programming Language :: Python :: 3 :: Only 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 License-File: LICENSE Requires-Dist: pbr!=2.1.0,>=2.0.0 Requires-Dist: keystoneauth1>=3.4.0 Requires-Dist: stevedore>=1.20.0 Requires-Dist: requests>=2.14.2 Requires-Dist: oslo.i18n>=3.15.3 Requires-Dist: oslo.log>=3.36.0 Requires-Dist: oslo.serialization!=2.19.1,>=2.18.0 Requires-Dist: oslo.utils>=3.33.0 Requires-Dist: os-client-config>=1.28.0 Requires-Dist: osc-lib>=1.8.0 Requires-Dist: PrettyTable>=0.7.2 Requires-Dist: cryptography>=3.0 Requires-Dist: decorator>=3.4.0 ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/python-magnumclient.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on Python bindings to the Magnum API ================================= .. image:: https://img.shields.io/pypi/v/python-magnumclient.svg :target: https://pypi.org/project/python-magnumclient/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/python-magnumclient.svg :target: https://pypi.org/project/python-magnumclient/ :alt: Downloads This is a client library for Magnum built on the Magnum API. It provides a Python API (the ``magnumclient`` module) and a command-line tool (``magnum``). Development takes place via the usual OpenStack processes as outlined in the `developer guide `_. * License: Apache License, Version 2.0 * `PyPi`_ - package installation * `Online Documentation`_ * `Storyboard project`_ - story and task management * `Bugs`_ - issue tracking * `Source`_ .. _PyPi: https://pypi.org/project/python-magnumclient .. _Online Documentation: https://docs.openstack.org/python-magnumclient/latest/ .. _Storyboard project: https://storyboard.openstack.org/#!/project/openstack/python-magnumclient .. _Bugs: https://storyboard.openstack.org/#!/project/openstack/python-magnumclient .. _Source: https://opendev.org/openstack/python-magnumclient ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/README.rst0000664000175000017500000000275100000000000017326 0ustar00zuulzuul00000000000000======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/python-magnumclient.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on Python bindings to the Magnum API ================================= .. image:: https://img.shields.io/pypi/v/python-magnumclient.svg :target: https://pypi.org/project/python-magnumclient/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/python-magnumclient.svg :target: https://pypi.org/project/python-magnumclient/ :alt: Downloads This is a client library for Magnum built on the Magnum API. It provides a Python API (the ``magnumclient`` module) and a command-line tool (``magnum``). Development takes place via the usual OpenStack processes as outlined in the `developer guide `_. * License: Apache License, Version 2.0 * `PyPi`_ - package installation * `Online Documentation`_ * `Storyboard project`_ - story and task management * `Bugs`_ - issue tracking * `Source`_ .. _PyPi: https://pypi.org/project/python-magnumclient .. _Online Documentation: https://docs.openstack.org/python-magnumclient/latest/ .. _Storyboard project: https://storyboard.openstack.org/#!/project/openstack/python-magnumclient .. _Bugs: https://storyboard.openstack.org/#!/project/openstack/python-magnumclient .. _Source: https://opendev.org/openstack/python-magnumclient ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740683659.3946953 python_magnumclient-4.8.0/doc/0000775000175000017500000000000000000000000016377 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/doc/requirements.txt0000664000175000017500000000013400000000000021661 0ustar00zuulzuul00000000000000sphinx>=2.0.0,!=2.1.0 # BSD openstackdocstheme>=2.2.0 # Apache-2.0 reno>=3.1.0 # Apache-2.0 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740683659.3946953 python_magnumclient-4.8.0/doc/source/0000775000175000017500000000000000000000000017677 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/doc/source/conf.py0000664000175000017500000000526500000000000021206 0ustar00zuulzuul00000000000000# -*- coding: utf-8 -*- # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import os import sys sys.path.insert(0, os.path.abspath('../..')) # -- General configuration ---------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'sphinx.ext.autodoc', #'sphinx.ext.intersphinx', 'openstackdocstheme' ] # autodoc generation is a bit aggressive and a nuisance when doing heavy # text edit cycles. # execute "export SPHINX_DEBUG=1" in your terminal to disable # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = 'python-magnumclient' copyright = '2013, OpenStack Foundation' # 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 # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. modindex_common_prefix = ['magnumclient.'] # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. # html_theme_path = ["."] html_theme = 'openstackdocs' # html_static_path = ['static'] # openstackdocstheme options openstackdocs_repo_name = 'openstack/python-magnumclient' openstackdocs_bug_project = 'python-magnumclient' openstackdocs_bug_tag = '' # Output file base name for HTML help builder. htmlhelp_basename = '%sdoc' % project # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass # [howto/manual]). latex_documents = [ ('index', '%s.tex' % project, '%s Documentation' % project, 'OpenStack Foundation', 'manual'), ] # Example configuration for intersphinx: refer to the Python standard library. #intersphinx_mapping = {'http://docs.python.org/': None} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/doc/source/contributing.rst0000664000175000017500000000011400000000000023134 0ustar00zuulzuul00000000000000============ Contributing ============ .. include:: ../../CONTRIBUTING.rst ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/doc/source/index.rst0000664000175000017500000000047700000000000021550 0ustar00zuulzuul00000000000000=============================================== Welcome to python-magnumclient's documentation! =============================================== Contents -------- .. toctree:: :maxdepth: 2 readme installation usage contributing Indices and tables ------------------ * :ref:`genindex` * :ref:`search` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/doc/source/installation.rst0000664000175000017500000000034100000000000023130 0ustar00zuulzuul00000000000000============ Installation ============ At the command line:: $ pip install python-magnumclient Or, if you have virtualenvwrapper installed:: $ mkvirtualenv python-magnumclient $ pip install python-magnumclient././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/doc/source/readme.rst0000664000175000017500000000010600000000000021663 0ustar00zuulzuul00000000000000============ Introduction ============ .. include:: ../../README.rst ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/doc/source/usage.rst0000664000175000017500000000406500000000000021542 0ustar00zuulzuul00000000000000=========================================== Python bindings to the OpenStack Magnum API =========================================== This is a client for the OpenStack Magnum API. It includes a Python API (the :mod:`magnumclient` module) and a command-line script (installed as :program:`magnum`). Python API ========== To use python-magnumclient in a project, create a client instance using the keystoneauth session API:: from keystoneauth1.identity import v3 from keystoneauth1 import session from keystoneclient.v3 import client from magnumclient.client import Client magnum_endpoint = "http://magnum.example.com:9511/v1" auth = v3.Password(auth_url='http://my.keystone.com:5000/v3', username='myuser', password='mypassword', project_name='myproject', user_domain_id='default', project_domain_id='default') sess = session.Session(auth=auth) magnum = Client('1', endpoint_override=magnum_endpoint, session=sess) magnum.clusters.list() For more information on keystoneauth API, see `Using Sessions`_. .. _Using Sessions: https://docs.openstack.org/keystoneauth/latest/using-sessions.html Command-line tool ================= In order to use the CLI, you must provide your OpenStack username, password, project name, user domain ID, project domain ID, and auth endpoint. Use the corresponding configuration options (--os-username, --os-password, --os-project-name, --os-project-domain-id, --os-user-domain-id, and --os-auth-url) or set them in environment variables:: export OS_USERNAME=myuser export OS_PASSWORD=mypassword export OS_PROJECT_NAME=myproject export OS_USER_DOMAIN_ID=default export OS_PROJECT_DOMAIN_ID=default export OS_AUTH_URL=http://my.keystone.com:5000/v3 From there, all shell commands take the form:: magnum [arguments...] Run :program:`magnum help` to see a complete listing of available commands. Run :program:`magnum help ` to get detailed help for that command. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740683659.3986952 python_magnumclient-4.8.0/magnumclient/0000775000175000017500000000000000000000000020315 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/__init__.py0000664000175000017500000000124300000000000022426 0ustar00zuulzuul00000000000000# -*- coding: utf-8 -*- # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import pbr.version __version__ = pbr.version.VersionInfo( 'python-magnumclient').version_string() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/client.py0000664000175000017500000000160600000000000022150 0ustar00zuulzuul00000000000000# Copyright (c) 2015 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from magnumclient.v1 import client def Client(version='1', **kwargs): """Factory function to create a new container service client.""" if version != '1': raise ValueError( "magnum only has one API version. Valid values for 'version'" " are '1'") return client.Client(**kwargs) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740683659.3986952 python_magnumclient-4.8.0/magnumclient/common/0000775000175000017500000000000000000000000021605 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/common/__init__.py0000664000175000017500000000000000000000000023704 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740683659.3986952 python_magnumclient-4.8.0/magnumclient/common/apiclient/0000775000175000017500000000000000000000000023555 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/common/apiclient/__init__.py0000664000175000017500000000000000000000000025654 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/common/apiclient/base.py0000664000175000017500000000667400000000000025056 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. """ import copy class Resource(object): """Base class for OpenStack resources (tenant, user, etc.). This is pretty much just a bag for attributes. """ def __init__(self, manager, info, loaded=False): """Populate and bind to a manager. :param manager: BaseManager object :param info: dictionary representing resource attributes :param loaded: prevent lazy-loading if set to True """ self.manager = manager self._info = info self._add_details(info) self._loaded = loaded def __repr__(self): reprkeys = sorted(k for k in self.__dict__.keys() if k[0] != '_' and k != 'manager') info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys) return "<%s %s>" % (self.__class__.__name__, info) 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 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): """Support for lazy loading details. Some clients, such as novaclient have the option to lazy load the details, details which can be loaded with this function. """ # 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) self._add_details( {'x_request_id': self.manager.client.last_request_id}) def __eq__(self, other): if not isinstance(other, Resource): return NotImplemented # two resources of different types are not equal if not isinstance(other, self.__class__): return False if hasattr(self, 'id') and hasattr(other, 'id'): return self.id == other.id return self._info == other._info def __ne__(self, other): return not self.__eq__(other) def is_loaded(self): return self._loaded def set_loaded(self, val): self._loaded = val def to_dict(self): return copy.deepcopy(self._info) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/common/apiclient/exceptions.py0000664000175000017500000003106000000000000026310 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. """ ######################################################################## # # THIS MODULE IS DEPRECATED # # Please refer to # https://etherpad.openstack.org/p/kilo-magnumclient-library-proposals for # the discussion leading to this deprecation. # # We recommend checking out the python-openstacksdk project # (https://launchpad.net/python-openstacksdk) instead. # ######################################################################## import inspect import sys from magnumclient.i18n import _ class ClientException(Exception): """The base exception class for all exceptions this library raises.""" pass 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 ConnectionError(ClientException): """Cannot connect to API service.""" pass class ConnectionRefused(ConnectionError): """Connection refused while trying to 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 an AuthSystem that is not installed.""" def __init__(self, auth_system): super(AuthSystemNotFound, self).__init__( _("AuthSystemNotFound: %r") % auth_system) self.auth_system = auth_system 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: %r") % 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 HTTPRedirection(HttpError): """HTTP Redirection.""" message = _("HTTP Redirection") 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 MultipleChoices(HTTPRedirection): """HTTP 300 - Multiple Choices. Indicates multiple options for the resource that the client may follow. """ http_status = 300 message = _("Multiple Choices") 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 """ req_id = response.headers.get("x-openstack-request-id") # NOTE(hdd) true for older versions of nova and cinder if not req_id: req_id = response.headers.get("x-compute-request-id") kwargs = { "http_status": response.status_code, "response": response, "method": method, "url": url, "request_id": req_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 isinstance(body, dict): error = body.get(list(body)[0]) if isinstance(error, dict): kwargs["message"] = (error.get("message") or error.get("faultstring")) kwargs["details"] = (error.get("details") or str(body)) elif content_type.startswith("text/"): kwargs["details"] = getattr(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=1740683612.0 python_magnumclient-4.8.0/magnumclient/common/base.py0000664000175000017500000001114400000000000023072 0ustar00zuulzuul00000000000000# -*- coding: utf-8 -*- # # Copyright 2012 OpenStack LLC. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Base utilities to build API operation managers and objects on top of. """ import copy from urllib import parse as urlparse from magnumclient.common.apiclient import base def getid(obj): """Wrapper to get object's ID. Abstracts the common pattern of allowing both an object or an object's ID (UUID) as a parameter when dealing with relationships. """ return getattr(obj, 'id', obj) class Manager(object): """Provides CRUD operations with a particular API.""" resource_class = None def __init__(self, api): self.api = api def _create(self, url, body): resp, body = self.api.json_request('POST', url, body=body) if body: return self.resource_class(self, body) def _format_body_data(self, body, response_key): if response_key: try: data = body[response_key] except KeyError: return [] else: data = body if not isinstance(data, list): data = [data] return data def _list_pagination(self, url, response_key=None, obj_class=None, limit=None): """Retrieve a list of items. The Magnum API is configured to return a maximum number of items per request, (FIXME: see Magnum's api.max_limit option). This iterates over the 'next' link (pagination) in the responses, to get the number of items specified by 'limit'. If 'limit' is None this function will continue pagination until there are no more values to be returned. :param url: a partial URL, e.g. '/nodes' :param response_key: the key to be looked up in response dictionary, e.g. 'nodes' :param obj_class: class for constructing the returned objects. :param limit: maximum number of items to return. If None returns everything. """ if obj_class is None: obj_class = self.resource_class if limit is not None: limit = int(limit) object_list = [] object_count = 0 limit_reached = False while url: resp, body = self.api.json_request('GET', url) data = self._format_body_data(body, response_key) for obj in data: object_list.append(obj_class(self, obj, loaded=True)) object_count += 1 if limit and object_count >= limit: # break the for loop limit_reached = True break # break the while loop and return if limit_reached: break url = body.get('next') if url: # NOTE(lucasagomes): We need to edit the URL to remove # the scheme and netloc url_parts = list(urlparse.urlparse(url)) url_parts[0] = url_parts[1] = '' url = urlparse.urlunparse(url_parts) return object_list def _list(self, url, response_key=None, obj_class=None, body=None): resp, body = self.api.json_request('GET', url) if obj_class is None: obj_class = self.resource_class data = self._format_body_data(body, response_key) return [obj_class(self, res, loaded=True) for res in data if res] def _update(self, url, body=None, method='PATCH', response_key=None): if body: resp, resp_body = self.api.json_request(method, url, body=body) else: resp, resp_body = self.api.raw_request(method, url) # PATCH/PUT requests may not return a body if resp_body: return self.resource_class(self, resp_body) def _delete(self, url): self.api.raw_request('DELETE', url) class Resource(base.Resource): """Represents a particular instance of an object (tenant, user, etc). This is pretty much just a bag for attributes. """ def to_dict(self): return copy.deepcopy(self._info) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/common/cliutils.py0000664000175000017500000003730000000000000024012 0ustar00zuulzuul00000000000000# Copyright 2012 Red Hat, 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. # W0603: Using the global statement # W0621: Redefining name %s from outer scope # pylint: disable=W0603,W0621 import getpass import inspect import os import sys import textwrap import decorator from magnumclient.common.apiclient import exceptions from oslo_utils import encodeutils from oslo_utils import strutils import prettytable from magnumclient.i18n import _ DEPRECATION_BASE = ('%sThe --%s parameter is deprecated and ' 'will be removed in a future release. Use the ' '<%s> positional parameter %s.') NAME_DEPRECATION_HELP = DEPRECATION_BASE % ('', 'name', 'name', 'instead') NAME_DEPRECATION_WARNING = DEPRECATION_BASE % ( 'WARNING: ', 'name', 'name', 'to avoid seeing this message') CLUSTER_DEPRECATION_HELP = DEPRECATION_BASE % ('', 'cluster', 'cluster', 'instead') CLUSTER_DEPRECATION_WARNING = DEPRECATION_BASE % ( 'WARNING: ', 'cluster', 'cluster', 'to avoid seeing this message') MAGNUM_CLIENT_DEPRECATION_WARNING = ( 'WARNING: The magnum client is deprecated and will be removed in a future ' 'release.\nUse the OpenStack client to avoid seeing this message.') def deprecation_message(preamble, new_name): msg = ('%s This parameter is deprecated and will be removed in a future ' 'release. Use --%s instead.' % (preamble, new_name)) return msg class MissingArgs(Exception): """Supplied arguments are not sufficient for calling a function.""" def __init__(self, missing): self.missing = missing msg = _("Missing arguments: %s") % ", ".join(missing) super(MissingArgs, self).__init__(msg) class DuplicateArgs(Exception): """More than one of the same argument type was passed.""" def __init__(self, param, dupes): msg = _('Duplicate "%(param)s" arguments: %(dupes)s') % { 'param': param, 'dupes': ", ".join(dupes)} super(DuplicateArgs, self).__init__(msg) def validate_args(fn, *args, **kwargs): """Check that the supplied args are sufficient for calling a function. >>> validate_args(lambda a: None) Traceback (most recent call last): ... MissingArgs: Missing argument(s): a >>> validate_args(lambda a, b, c, d: None, 0, c=1) Traceback (most recent call last): ... MissingArgs: Missing argument(s): b, d :param fn: the function to check :param arg: the positional arguments supplied :param kwargs: the keyword arguments supplied """ argspec = inspect.getfullargspec(fn) num_defaults = len(argspec.defaults or []) required_args = argspec.args[:len(argspec.args) - num_defaults] def isbound(method): return getattr(method, '__self__', None) is not None if isbound(fn): required_args.pop(0) missing = [arg for arg in required_args if arg not in kwargs] missing = missing[len(args):] if missing: raise MissingArgs(missing) def validate_name_args(positional_name, optional_name): if optional_name: print(NAME_DEPRECATION_WARNING) if positional_name and optional_name: raise DuplicateArgs("", (positional_name, optional_name)) def validate_cluster_args(positional_cluster, optional_cluster): if optional_cluster: print(CLUSTER_DEPRECATION_WARNING) if positional_cluster and optional_cluster: raise DuplicateArgs("", (positional_cluster, optional_cluster)) def deprecated(message): """Decorator for marking a call as deprecated by printing a given message. Example: >>> @deprecated("Bay functions are deprecated and should be replaced by " ... "calls to cluster") ... def bay_create(args): ... pass """ @decorator.decorator def wrapper(func, *args, **kwargs): print(message) return func(*args, **kwargs) return wrapper def deprecation_map(dep_map): """Decorator for applying a map of deprecating arguments to a function. The map connects deprecating arguments and their replacements. The shell.py script uses this map to create mutually exclusive argument groups in argparse and also prints a deprecation warning telling the user to switch to the updated argument. NOTE: This decorator MUST be the outermost in the chain of argument decorators to work correctly. Example usage: >>> @deprecation_map({ "old-argument": "new-argument" }) ... @args("old-argument", required=True) ... @args("new-argument", required=True) ... def do_command_line_stuff(): ... pass """ def _decorator(func): if not hasattr(func, 'arguments'): return func func.deprecated_groups = [] for old_param, new_param in dep_map.items(): old_info, new_info = None, None required = False for (args, kwargs) in func.arguments: if old_param in args: old_info = (args, kwargs) # Old arguments shouldn't be required if they were not # previously, so prioritize old requirement if 'required' in kwargs: required = kwargs['required'] # Set to false so argparse doesn't get angry kwargs['required'] = False elif new_param in args: new_info = (args, kwargs) kwargs['required'] = False if old_info and new_info: break # Add a tuple of (old, new, required), which in turn is: # ((old_args, old_kwargs), (new_args, new_kwargs), required) func.deprecated_groups.append((old_info, new_info, required)) # Remove arguments that would be duplicated by the groups we made func.arguments.remove(old_info) func.arguments.remove(new_info) return func return _decorator def arg(*args, **kwargs): """Decorator for CLI args. Example: >>> @arg("name", help="Name of the new entity") ... def entity_create(args): ... pass """ def _decorator(func): add_arg(func, *args, **kwargs) return func return _decorator def env(*args, **kwargs): """Returns the first environment variable set. If all are empty, defaults to '' or keyword arg `default`. """ for arg in args: value = os.environ.get(arg) if value: return value return kwargs.get('default', '') def add_arg(func, *args, **kwargs): """Bind CLI arguments to a shell.py `do_foo` function.""" if not hasattr(func, 'arguments'): func.arguments = [] # NOTE(sirp): avoid dups that can occur when the module is shared across # tests. if (args, kwargs) not in func.arguments: # Because of the semantics of decorator composition if we just append # to the options list positional options will appear to be backwards. func.arguments.insert(0, (args, kwargs)) def unauthenticated(func): """Adds 'unauthenticated' attribute to decorated function. Usage: >>> @unauthenticated ... def mymethod(f): ... pass """ func.unauthenticated = True return func def isunauthenticated(func): """Checks if the function does not require authentication. Mark such functions with the `@unauthenticated` decorator. :returns: bool """ return getattr(func, 'unauthenticated', False) def print_list(objs, fields, formatters=None, sortby_index=0, mixed_case_fields=None, field_labels=None): """Print a list or objects as a table, one row per object. :param objs: iterable of :class:`Resource` :param fields: attributes that correspond to columns, in order :param formatters: `dict` of callables for field formatting :param sortby_index: index of the field for sorting table rows :param mixed_case_fields: fields corresponding to object attributes that have mixed case names (e.g., 'serverId') :param field_labels: Labels to use in the heading of the table, default to fields. """ formatters = formatters or {} mixed_case_fields = mixed_case_fields or [] field_labels = field_labels or fields if len(field_labels) != len(fields): raise ValueError(_("Field labels list %(labels)s has different number " "of elements than fields list %(fields)s"), {'labels': field_labels, 'fields': fields}) if sortby_index is None: kwargs = {} else: kwargs = {'sortby': field_labels[sortby_index]} pt = prettytable.PrettyTable(field_labels) pt.align = 'l' for o in objs: row = [] for field in fields: data = '-' if field in formatters: data = formatters[field](o) else: if field in mixed_case_fields: field_name = field.replace(' ', '_') else: field_name = field.lower().replace(' ', '_') data = getattr(o, field_name, '') if data is None: data = '-' row.append(data) pt.add_row(row) print(encodeutils.safe_encode(pt.get_string(**kwargs)).decode()) def keys_and_vals_to_strs(dictionary): """Recursively convert a dictionary's keys and values to strings. :param dictionary: dictionary whose keys/vals are to be converted to strs """ def to_str(k_or_v): if isinstance(k_or_v, dict): return keys_and_vals_to_strs(k_or_v) elif isinstance(k_or_v, str): return str(k_or_v) else: return k_or_v return dict((to_str(k), to_str(v)) for k, v in dictionary.items()) def print_dict(dct, dict_property="Property", wrap=0): """Print a `dict` as a table of two columns. :param dct: `dict` to print :param dict_property: name of the first column :param wrap: wrapping for the second column """ pt = prettytable.PrettyTable([dict_property, 'Value']) pt.align = 'l' for k, v in dct.items(): # convert dict to str to check length if isinstance(v, dict): v = str(keys_and_vals_to_strs(v)) if wrap > 0: v = textwrap.fill(str(v), wrap) # if value has a newline, add in multiple rows # e.g. fault with stacktrace if v and isinstance(v, str) and r'\n' in v: lines = v.strip().split(r'\n') col1 = k for line in lines: pt.add_row([col1, line]) col1 = '' elif isinstance(v, list): val = str([str(i) for i in v]) if val is None: val = '-' pt.add_row([k, val]) else: if v is None: v = '-' pt.add_row([k, v]) print(encodeutils.safe_encode(pt.get_string()).decode()) def get_password(max_password_prompts=3): """Read password from TTY.""" verify = strutils.bool_from_string(env("OS_VERIFY_PASSWORD")) pw = None if hasattr(sys.stdin, "isatty") and sys.stdin.isatty(): # Check for Ctrl-D try: for __ in range(max_password_prompts): pw1 = getpass.getpass("OS Password: ") if verify: pw2 = getpass.getpass("Please verify: ") else: pw2 = pw1 if pw1 == pw2 and pw1: pw = pw1 break except EOFError: pass return pw def service_type(stype): """Adds 'service_type' attribute to decorated function. Usage: .. code-block:: python @service_type('volume') def mymethod(f): ... """ def inner(f): f.service_type = stype return f return inner def get_service_type(f): """Retrieves service type from function.""" return getattr(f, 'service_type', None) def pretty_choice_list(lst): return ', '.join("'%s'" % i for i in lst) def exit(msg=''): if msg: print(msg, file=sys.stderr) sys.exit(1) def _format_field_name(attr): """Format an object attribute in a human-friendly way.""" # Split at ':' and leave the extension name as-is. parts = attr.rsplit(':', 1) name = parts[-1].replace('_', ' ') # Don't title() on mixed case if name.isupper() or name.islower(): name = name.title() parts[-1] = name return ': '.join(parts) def make_field_formatter(attr, filters=None): """Given an object attribute. Return a formatted field name and a formatter suitable for passing to print_list. Optionally pass a dict mapping attribute names to a function. The function will be passed the value of the attribute and should return the string to display. """ filter_ = None if filters: filter_ = filters.get(attr) def get_field(obj): field = getattr(obj, attr, '') if field and filter_: field = filter_(field) return field name = _format_field_name(attr) formatter = get_field return name, formatter def _get_list_table_columns_and_formatters(fields, objs, exclude_fields=(), filters=None): """Check and add fields to output columns. If there is any value in fields that not an attribute of obj, CommandError will be raised. If fields has duplicate values (case sensitive), we will make them unique and ignore duplicate ones. :param fields: A list of string contains the fields to be printed. :param objs: An list of object which will be used to check if field is valid or not. Note, we don't check fields if obj is None or empty. :param exclude_fields: A tuple of string which contains the fields to be excluded. :param filters: A dictionary defines how to get value from fields, this is useful when field's value is a complex object such as dictionary. :return: columns, formatters. columns is a list of string which will be used as table header. formatters is a dictionary specifies how to display the value of the field. They can be [], {}. :raise: magnumclient.common.apiclient.exceptions.CommandError. """ if objs and isinstance(objs, list): obj = objs[0] else: obj = None fields = None columns = [] formatters = {} if fields: non_existent_fields = [] exclude_fields = set(exclude_fields) for field in fields.split(','): if not hasattr(obj, field): non_existent_fields.append(field) continue if field in exclude_fields: continue field_title, formatter = make_field_formatter(field, filters) columns.append(field_title) formatters[field_title] = formatter exclude_fields.add(field) if non_existent_fields: raise exceptions.CommandError( _("Non-existent fields are specified: %s") % non_existent_fields ) return columns, formatters ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/common/httpclient.py0000664000175000017500000004151200000000000024340 0ustar00zuulzuul00000000000000# -*- coding: utf-8 -*- # # Copyright 2012 OpenStack LLC. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from http import client as http_client import io import logging import os import socket import ssl from urllib import parse as urlparse from keystoneauth1 import adapter from oslo_serialization import jsonutils from oslo_utils import importutils from magnumclient import exceptions osprofiler_web = importutils.try_import("osprofiler.web") LOG = logging.getLogger(__name__) USER_AGENT = 'python-magnumclient' CHUNKSIZE = 1024 * 64 # 64kB API_VERSION = '/v1' DEFAULT_API_VERSION = 'latest' def _extract_error_json_text(body_json): error_json = {} if 'error_message' in body_json: raw_msg = body_json['error_message'] error_json = jsonutils.loads(raw_msg) elif 'error' in body_json: error_body = body_json['error'] error_json = {'faultstring': error_body['title'], 'debuginfo': error_body['message']} else: error_body = body_json['errors'][0] error_json = {'faultstring': error_body['title']} if 'detail' in error_body: error_json['debuginfo'] = error_body['detail'] elif 'description' in error_body: error_json['debuginfo'] = error_body['description'] return error_json def _extract_error_json(body, resp): """Return error_message from the HTTP response body.""" try: content_type = resp.headers.get("Content-Type", "") except AttributeError: content_type = "" if content_type.startswith("application/json"): try: body_json = resp.json() return _extract_error_json_text(body_json) except AttributeError: body_json = jsonutils.loads(body) return _extract_error_json_text(body_json) except ValueError: return {} else: try: body_json = jsonutils.loads(body) return _extract_error_json_text(body_json) except ValueError: return {} class HTTPClient(object): def __init__(self, endpoint, api_version=DEFAULT_API_VERSION, **kwargs): self.endpoint = endpoint self.auth_token = kwargs.get('token') self.auth_ref = kwargs.get('auth_ref') self.api_version = api_version self.connection_params = self.get_connection_params(endpoint, **kwargs) @staticmethod def get_connection_params(endpoint, **kwargs): parts = urlparse.urlparse(endpoint) # trim API version and trailing slash from endpoint path = parts.path path = path.rstrip('/').rstrip(API_VERSION) _args = (parts.hostname, parts.port, path) _kwargs = {'timeout': (float(kwargs.get('timeout')) if kwargs.get('timeout') else 600)} if parts.scheme == 'https': _class = VerifiedHTTPSConnection _kwargs['ca_file'] = kwargs.get('ca_file', None) _kwargs['cert_file'] = kwargs.get('cert_file', None) _kwargs['key_file'] = kwargs.get('key_file', None) _kwargs['insecure'] = kwargs.get('insecure', False) elif parts.scheme == 'http': _class = http_client.HTTPConnection else: msg = 'Unsupported scheme: %s' % parts.scheme raise exceptions.EndpointException(msg) return (_class, _args, _kwargs) def get_connection(self): _class = self.connection_params[0] return _class(*self.connection_params[1][0:2], **self.connection_params[2]) def log_curl_request(self, method, url, kwargs): curl = ['curl -i -X %s' % method] for (key, value) in kwargs['headers'].items(): header = '-H \'%s: %s\'' % (key, value) curl.append(header) conn_params_fmt = [ ('key_file', '--key %s'), ('cert_file', '--cert %s'), ('ca_file', '--cacert %s'), ] for (key, fmt) in conn_params_fmt: value = self.connection_params[2].get(key) if value: curl.append(fmt % value) if self.connection_params[2].get('insecure'): curl.append('-k') if 'body' in kwargs: curl.append('-d \'%s\'' % kwargs['body']) curl.append('%s/%s' % (self.endpoint, url.lstrip(API_VERSION))) LOG.debug(' '.join(curl)) @staticmethod def log_http_response(resp, body=None): status = (resp.version / 10.0, resp.status, resp.reason) dump = ['\nHTTP/%.1f %s %s' % status] dump.extend(['%s: %s' % (k, v) for k, v in resp.getheaders()]) dump.append('') if body: dump.extend([body, '']) LOG.debug('\n'.join(dump)) def _make_connection_url(self, url): (_class, _args, _kwargs) = self.connection_params base_url = _args[2] return '%s/%s' % (base_url, url.lstrip('/')) def _http_request(self, url, method, **kwargs): """Send an http request with the specified characteristics. Wrapper around httplib.HTTP(S)Connection.request to handle tasks such as setting headers and error handling. """ # Copy the kwargs so we can reuse the original in case of redirects kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {})) kwargs['headers'].setdefault('User-Agent', USER_AGENT) if self.api_version: version_string = 'container-infra %s' % self.api_version kwargs['headers'].setdefault( 'OpenStack-API-Version', version_string) if self.auth_token: kwargs['headers'].setdefault('X-Auth-Token', self.auth_token) self.log_curl_request(method, url, kwargs) conn = self.get_connection() try: conn_url = self._make_connection_url(url) conn.request(method, conn_url, **kwargs) resp = conn.getresponse() except socket.gaierror as e: message = ("Error finding address for %(url)s: %(e)s" % dict(url=url, e=e)) raise exceptions.EndpointNotFound(message) except (socket.error, socket.timeout) as e: endpoint = self.endpoint message = ("Error communicating with %(endpoint)s %(e)s" % dict(endpoint=endpoint, e=e)) raise exceptions.ConnectionRefused(message) body_iter = ResponseBodyIterator(resp) # Read body into string if it isn't obviously image data body_str = None if resp.getheader('content-type', None) != 'application/octet-stream': # decoding byte to string is necessary for Python 3.4 compatibility # this issues has not been found with Python 3.4 unit tests # because the test creates a fake http response of type str # the if statement satisfies test (str) and real (bytes) behavior body_list = [ chunk.decode("utf-8") if isinstance(chunk, bytes) else chunk for chunk in body_iter ] body_str = ''.join(body_list) self.log_http_response(resp, body_str) body_iter = io.StringIO(body_str) else: self.log_http_response(resp) if 400 <= resp.status < 600: LOG.warning("Request returned failure status.") error_json = _extract_error_json(body_str, resp) raise exceptions.from_response( resp, error_json.get('faultstring'), error_json.get('debuginfo'), method, url) elif resp.status in (301, 302, 305): # Redirected. Reissue the request to the new location. return self._http_request(resp['location'], method, **kwargs) elif resp.status == 300: raise exceptions.from_response(resp, method=method, url=url) return resp, body_iter def json_request(self, method, url, **kwargs): kwargs.setdefault('headers', {}) kwargs['headers'].setdefault('Content-Type', 'application/json') kwargs['headers'].setdefault('Accept', 'application/json') if 'body' in kwargs: kwargs['body'] = jsonutils.dumps(kwargs['body']) resp, body_iter = self._http_request(url, method, **kwargs) content_type = resp.getheader('content-type', None) if resp.status == 204 or resp.status == 205 or content_type is None: return resp, list() if 'application/json' in content_type: body = ''.join([chunk for chunk in body_iter]) try: body = jsonutils.loads(body) except ValueError: LOG.error('Could not decode response body as JSON') else: body = None return resp, body def raw_request(self, method, url, **kwargs): kwargs.setdefault('headers', {}) kwargs['headers'].setdefault('Content-Type', 'application/octet-stream') return self._http_request(url, method, **kwargs) class VerifiedHTTPSConnection(http_client.HTTPSConnection): """httplib-compatibile connection using client-side SSL authentication :see http://code.activestate.com/recipes/ 577548-https-httplib-client-connection-with-certificate-v/ """ def __init__(self, host, port, key_file=None, cert_file=None, ca_file=None, timeout=None, insecure=False): http_client.HTTPSConnection.__init__(self, host, port) self.key_file = key_file self.cert_file = cert_file if ca_file is not None: self.ca_file = ca_file else: self.ca_file = self.get_system_ca_file() self.timeout = timeout self.insecure = insecure def connect(self): """Connect to a host on a given (SSL) port. If ca_file is pointing somewhere, use it to check Server Certificate. Redefined/copied and extended from httplib.py:1105 (Python 2.6.x). This is needed to pass cert_reqs=ssl.CERT_REQUIRED as parameter to ssl.wrap_socket(), which forces SSL to check server certificate against our client certificate. """ sock = socket.create_connection((self.host, self.port), self.timeout) context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) if self._tunnel_host: self.sock = sock self._tunnel() if self.insecure is True: context.check_hostname = False context.verify_mode = ssl.CERT_NONE else: context.verify_mode = ssl.CERT_REQUIRED context.load_verify_locations(self.ca_file) if self.cert_file: context.load_cert_chain(self.cert_file, self.key_file) self.sock = context.wrap_socket(sock) @staticmethod def get_system_ca_file(): """Return path to system default CA file.""" # Standard CA file locations for Debian/Ubuntu, RedHat/Fedora, # Suse, FreeBSD/OpenBSD ca_path = ['/etc/ssl/certs/ca-certificates.crt', '/etc/pki/tls/certs/ca-bundle.crt', '/etc/ssl/ca-bundle.pem', '/etc/ssl/cert.pem'] for ca in ca_path: if os.path.exists(ca): return ca return None class SessionClient(adapter.LegacyJsonAdapter): """HTTP client based on Keystone client session.""" def __init__(self, user_agent=USER_AGENT, logger=LOG, api_version=DEFAULT_API_VERSION, *args, **kwargs): self.user_agent = USER_AGENT self.api_version = api_version super(SessionClient, self).__init__(*args, **kwargs) def _http_request(self, url, method, **kwargs): if url.startswith(API_VERSION): url = url[len(API_VERSION):] kwargs.setdefault('user_agent', self.user_agent) kwargs.setdefault('auth', self.auth) kwargs.setdefault('endpoint_override', self.endpoint_override) # Copy the kwargs so we can reuse the original in case of redirects kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {})) kwargs['headers'].setdefault('User-Agent', self.user_agent) # NOTE(tovin07): osprofiler_web.get_trace_id_headers does not add any # headers in case if osprofiler is not initialized. if osprofiler_web: kwargs['headers'].update(osprofiler_web.get_trace_id_headers()) if self.api_version: version_string = 'container-infra %s' % self.api_version kwargs['headers'].setdefault( 'OpenStack-API-Version', version_string) endpoint_filter = kwargs.setdefault('endpoint_filter', {}) endpoint_filter.setdefault('interface', self.interface) endpoint_filter.setdefault('service_type', self.service_type) endpoint_filter.setdefault('region_name', self.region_name) resp = self.session.request(url, method, raise_exc=False, **kwargs) if 400 <= resp.status_code < 600: error_json = _extract_error_json(resp.content, resp) raise exceptions.from_response( resp, error_json.get('faultstring'), error_json.get('debuginfo'), method, url) elif resp.status_code in (301, 302, 305): # Redirected. Reissue the request to the new location. location = resp.headers.get('location') resp = self._http_request(location, method, **kwargs) elif resp.status_code == 300: raise exceptions.from_response(resp, method=method, url=url) return resp def json_request(self, method, url, **kwargs): kwargs.setdefault('headers', {}) kwargs['headers'].setdefault('Content-Type', 'application/json') kwargs['headers'].setdefault('Accept', 'application/json') if 'body' in kwargs: kwargs['data'] = jsonutils.dumps(kwargs.pop('body')) resp = self._http_request(url, method, **kwargs) body = resp.content content_type = resp.headers.get('content-type', None) status = resp.status_code if status == 204 or status == 205 or content_type is None: return resp, list() if 'application/json' in content_type: try: body = resp.json() except ValueError: LOG.error('Could not decode response body as JSON') else: body = None return resp, body def raw_request(self, method, url, **kwargs): kwargs.setdefault('headers', {}) kwargs['headers'].setdefault('Content-Type', 'application/octet-stream') resp = self._http_request(url, method, **kwargs) body = resp.content status = resp.status_code content_type = resp.headers.get('content-type', None) if status == 204 or status == 205 or content_type is None: return resp, list() if 'application/json' in content_type: try: body = resp.json() except ValueError: LOG.error('Could not decode response body as JSON') else: body = None return resp, body class ResponseBodyIterator(object): """A class that acts as an iterator over an HTTP response.""" def __init__(self, resp): self.resp = resp def __iter__(self): while True: try: yield self.next() except StopIteration: return def __bool__(self): return hasattr(self, 'items') __nonzero__ = __bool__ # Python 2.x compatibility def next(self): chunk = self.resp.read(CHUNKSIZE) if chunk: return chunk else: raise StopIteration def _construct_http_client(*args, **kwargs): session = kwargs.pop('session', None) auth = kwargs.pop('auth', None) if session: service_type = kwargs.pop('service_type', 'baremetal') interface = kwargs.pop('endpoint_type', None) region_name = kwargs.pop('region_name', None) return SessionClient(session=session, auth=auth, interface=interface, service_type=service_type, region_name=region_name, service_name=None, user_agent='python-magnumclient') else: return HTTPClient(*args, **kwargs) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/common/utils.py0000664000175000017500000002710500000000000023324 0ustar00zuulzuul00000000000000# -*- coding: utf-8 -*- # # Copyright 2012 OpenStack LLC. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import os from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives import serialization from cryptography import x509 from cryptography.x509.oid import NameOID from oslo_serialization import base64 from oslo_serialization import jsonutils from magnumclient import exceptions as exc from magnumclient.i18n import _ def common_filters(marker=None, limit=None, sort_key=None, sort_dir=None): """Generate common filters for any list request. :param marker: entity ID from which to start returning entities. :param limit: maximum number of entities to return. :param sort_key: field to use for sorting. :param sort_dir: direction of sorting: 'asc' or 'desc'. :returns: list of string filters. """ filters = [] if isinstance(limit, int): filters.append('limit=%s' % limit) if marker is not None: filters.append('marker=%s' % marker) if sort_key is not None: filters.append('sort_key=%s' % sort_key) if sort_dir is not None: filters.append('sort_dir=%s' % sort_dir) return filters def split_and_deserialize(string): """Split and try to JSON deserialize a string. Gets a string with the KEY=VALUE format, split it (using '=' as the separator) and try to JSON deserialize the VALUE. :returns: A tuple of (key, value). """ try: key, value = string.split("=", 1) except ValueError: raise exc.CommandError(_('Attributes must be a list of ' 'PATH=VALUE not "%s"') % string) try: value = jsonutils.loads(value) except ValueError: pass return (key, value) def args_array_to_patch(op, attributes): patch = [] for attr in attributes: # Sanitize if not attr.startswith('/'): attr = '/' + attr if op in ['add', 'replace']: path, value = split_and_deserialize(attr) if path == "/labels" or path == "/health_status_reason": a = [] a.append(value) value = str(handle_labels(a)) patch.append({'op': op, 'path': path, 'value': value}) else: patch.append({'op': op, 'path': path, 'value': value}) elif op == "remove": # For remove only the key is needed patch.append({'op': op, 'path': attr}) else: raise exc.CommandError(_('Unknown PATCH operation: %s') % op) return patch def handle_labels(labels): labels = format_labels(labels) if 'mesos_slave_executor_env_file' in labels: environment_variables_data = handle_json_from_file( labels['mesos_slave_executor_env_file']) labels['mesos_slave_executor_env_variables'] = jsonutils.dumps( environment_variables_data) return labels def format_labels(lbls, parse_comma=True): '''Reformat labels into dict of format expected by the API.''' if not lbls: return {} if parse_comma: # expect multiple invocations of --labels but fall back # to either , or ; delimited if only one --labels is specified if len(lbls) == 1 and lbls[0].count('=') > 1: lbls = lbls[0].replace(';', ',').split(',') labels = {} for lbl in lbls: try: (k, v) = lbl.split(('='), 1) except ValueError: raise exc.CommandError(_('labels must be a list of KEY=VALUE ' 'not %s') % lbl) if k not in labels: labels[k] = v else: labels[k] += ",%s" % v return labels def print_list_field(field): return lambda obj: ', '.join(getattr(obj, field)) def handle_json_from_file(json_arg): """Attempts to read JSON file by the file url. :param json_arg: May be a file name containing the JSON. :returns: A list or dictionary parsed from JSON. """ try: with open(json_arg, 'r') as f: json_arg = f.read().strip() json_arg = jsonutils.loads(json_arg) except IOError as e: err = _("Cannot get JSON from file '%(file)s'. " "Error: %(err)s") % {'err': e, 'file': json_arg} raise exc.InvalidAttribute(err) except ValueError as e: err = (_("For JSON: '%(string)s', error: '%(err)s'") % {'err': e, 'string': json_arg}) raise exc.InvalidAttribute(err) return json_arg def config_cluster(cluster, cluster_template, cfg_dir, force=False, certs=None, use_keystone=False, direct_output=False): """Return and write configuration for the given cluster.""" if cluster_template.coe == 'kubernetes': return _config_cluster_kubernetes(cluster, cluster_template, cfg_dir, force, certs, use_keystone, direct_output) elif (cluster_template.coe == 'swarm' or cluster_template.coe == 'swarm-mode'): return _config_cluster_swarm(cluster, cluster_template, cfg_dir, force, certs) def _config_cluster_kubernetes(cluster, cluster_template, cfg_dir, force=False, certs=None, use_keystone=False, direct_output=False): """Return and write configuration for the given kubernetes cluster.""" cfg_file = "%s/config" % cfg_dir if cluster_template.tls_disabled or certs is None: cfg = ("apiVersion: v1\n" "clusters:\n" "- cluster:\n" " server: %(api_address)s\n" " name: %(name)s\n" "contexts:\n" "- context:\n" " cluster: %(name)s\n" " user: %(name)s\n" " name: %(name)s\n" "current-context: %(name)s\n" "kind: Config\n" "preferences: {}\n" "users:\n" "- name: %(name)s\n" % {'name': cluster.name, 'api_address': cluster.api_address}) else: if not use_keystone: cfg = ("apiVersion: v1\n" "clusters:\n" "- cluster:\n" " certificate-authority-data: %(ca)s\n" " server: %(api_address)s\n" " name: %(name)s\n" "contexts:\n" "- context:\n" " cluster: %(name)s\n" " user: admin\n" " name: default\n" "current-context: default\n" "kind: Config\n" "preferences: {}\n" "users:\n" "- name: admin\n" " user:\n" " client-certificate-data: %(cert)s\n" " client-key-data: %(key)s\n" % {'name': cluster.name, 'api_address': cluster.api_address, 'key': base64.encode_as_text(certs['key']), 'cert': base64.encode_as_text(certs['cert']), 'ca': base64.encode_as_text(certs['ca'])}) else: cfg = ("apiVersion: v1\n" "clusters:\n" "- cluster:\n" " certificate-authority-data: %(ca)s\n" " server: %(api_address)s\n" " name: %(name)s\n" "contexts:\n" "- context:\n" " cluster: %(name)s\n" " user: openstackuser\n" " name: openstackuser@kubernetes\n" "current-context: openstackuser@kubernetes\n" "kind: Config\n" "preferences: {}\n" "users:\n" "- name: openstackuser\n" " user:\n" " exec:\n" " command: /bin/bash\n" " apiVersion: client.authentication.k8s.io/v1beta1\n" " args:\n" " - -c\n" " - >\n" " if [ -z ${OS_TOKEN} ]; then\n" " echo 'Error: Missing OpenStack credential from environment variable $OS_TOKEN' > /dev/stderr\n" # noqa " exit 1\n" " else\n" " echo '{ \"apiVersion\": \"client.authentication.k8s.io/v1beta1\", \"kind\": \"ExecCredential\", \"status\": { \"token\": \"'\"${OS_TOKEN}\"'\"}}'\n" # noqa " fi\n" % {'name': cluster.name, 'api_address': cluster.api_address, 'ca': base64.encode_as_text(certs['ca'])}) if direct_output: return cfg if os.path.exists(cfg_file) and not force: raise exc.CommandError("File %s exists, aborting." % cfg_file) else: f = open(cfg_file, "w") f.write(cfg) f.close() if 'csh' in os.environ['SHELL']: return "setenv KUBECONFIG %s\n" % cfg_file else: return "export KUBECONFIG=%s\n" % cfg_file def _config_cluster_swarm(cluster, cluster_template, cfg_dir, force=False, certs=None): """Return and write configuration for the given swarm cluster.""" tls = "" if cluster_template.tls_disabled else True if 'csh' in os.environ['SHELL']: result = ("setenv DOCKER_HOST %(docker_host)s\n" "setenv DOCKER_CERT_PATH %(cfg_dir)s\n" "setenv DOCKER_TLS_VERIFY %(tls)s\n" % {'docker_host': cluster.api_address, 'cfg_dir': cfg_dir, 'tls': tls} ) else: result = ("export DOCKER_HOST=%(docker_host)s\n" "export DOCKER_CERT_PATH=%(cfg_dir)s\n" "export DOCKER_TLS_VERIFY=%(tls)s\n" % {'docker_host': cluster.api_address, 'cfg_dir': cfg_dir, 'tls': tls} ) return result def generate_csr_and_key(): """Return a dict with a new csr and key.""" key = rsa.generate_private_key( public_exponent=65537, key_size=2048, backend=default_backend()) csr = x509.CertificateSigningRequestBuilder().subject_name( x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, u"admin"), x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"system:masters") ])).sign(key, hashes.SHA256(), default_backend()) result = { 'csr': csr.public_bytes( encoding=serialization.Encoding.PEM).decode("utf-8"), 'key': key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption()).decode("utf-8"), } return result ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/exceptions.py0000664000175000017500000000556500000000000023063 0ustar00zuulzuul00000000000000# -*- coding: utf-8 -*- # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from magnumclient.common.apiclient import exceptions from magnumclient.common.apiclient.exceptions import * # noqa # NOTE(akurilin): This alias is left here since v.0.1.3 to support backwards # compatibility. InvalidEndpoint = exceptions.EndpointException CommunicationError = exceptions.ConnectionRefused HTTPBadRequest = exceptions.BadRequest HTTPInternalServerError = exceptions.InternalServerError HTTPNotFound = exceptions.NotFound HTTPServiceUnavailable = exceptions.ServiceUnavailable class AmbiguousAuthSystem(exceptions.ClientException): """Could not obtain token and endpoint using provided credentials.""" pass # Alias for backwards compatibility AmbigiousAuthSystem = AmbiguousAuthSystem class InvalidAttribute(exceptions.ClientException): pass def from_response(response, message=None, traceback=None, method=None, url=None): """Return an HttpError instance based on response from httplib/requests.""" error_body = {} if message: error_body['message'] = message if traceback: error_body['details'] = traceback if hasattr(response, 'status') and not hasattr(response, 'status_code'): # NOTE(akurilin): These modifications around response object give # ability to get all necessary information in method `from_response` # from common code, which expecting response object from `requests` # library instead of object from `httplib/httplib2` library. response.status_code = response.status response.headers = { 'Content-Type': response.getheader('content-type', "")} if hasattr(response, 'status_code'): # NOTE(hongbin): This allows SessionClient to handle faultstring. response.json = lambda: {'error': error_body} if (response.headers.get('Content-Type', '').startswith('text/') and not hasattr(response, 'text')): # NOTE(clif_h): There seems to be a case in the # common.apiclient.exceptions module where if the # content-type of the response is text/* then it expects # the response to have a 'text' attribute, but that # doesn't always seem to necessarily be the case. # This is to work around that problem. response.text = '' return exceptions.from_response(response, method, url) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/i18n.py0000664000175000017500000000151500000000000021450 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. """oslo_i18n integration module for magnumclient. See https://docs.openstack.org/oslo.i18n/latest/user/usage.html . """ import oslo_i18n _translators = oslo_i18n.TranslatorFactory(domain='magnumclient') # The primary translation function using the well-known name "_" _ = _translators.primary ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740683659.3986952 python_magnumclient-4.8.0/magnumclient/osc/0000775000175000017500000000000000000000000021101 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/osc/__init__.py0000664000175000017500000000000000000000000023200 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/osc/plugin.py0000664000175000017500000000365000000000000022755 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_MAJOR_API_VERSION = '1' DEFAULT_MAGNUM_API_VERSION = 'latest' API_VERSION_OPTION = 'os_container_infra_api_version' API_NAME = 'container_infra' API_VERSIONS = { '1': 'magnumclient.v1.client.Client', } def make_client(instance): """Returns a magnum client.""" magnum_client = utils.get_client_class( API_NAME, instance._api_version[API_NAME], API_VERSIONS) LOG.debug('Instantiating magnum client: %s', magnum_client) client = magnum_client(session=instance.session, region_name=instance._region_name, interface=instance._interface, insecure=instance._insecure, ca_cert=instance._cacert, api_version=DEFAULT_MAGNUM_API_VERSION) return client def build_option_parser(parser): """Hook to add global options""" parser.add_argument( '--os-container-infra-api-version', metavar='', default=utils.env( 'OS_CONTAINER_INFRA_API_VERSION', default=DEFAULT_MAJOR_API_VERSION), help='Container-Infra API version, default=' + DEFAULT_MAJOR_API_VERSION + ' (Env: OS_CONTAINER_INFRA_API_VERSION)') return parser ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740683659.4026952 python_magnumclient-4.8.0/magnumclient/osc/v1/0000775000175000017500000000000000000000000021427 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/osc/v1/__init__.py0000664000175000017500000000000000000000000023526 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/osc/v1/certificates.py0000664000175000017500000000730600000000000024454 0ustar00zuulzuul00000000000000# Copyright 2015 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import os from magnumclient.i18n import _ from osc_lib.command import command def _show_cert(certificate): try: print(certificate.pem) except AttributeError: return None def _get_target_uuid(cs, args): target = None if args.cluster: target = cs.clusters.get(args.cluster) return target.uuid class RotateCa(command.Command): _description = _("Rotate the CA certificate for cluster to revoke access.") def get_parser(self, prog_name): parser = super(RotateCa, 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): mag_client = self.app.client_manager.container_infra cluster = mag_client.clusters.get(parsed_args.cluster) opts = { 'cluster_uuid': cluster.uuid } mag_client.certificates.rotate_ca(**opts) print("Request to rotate the CA certificate for cluster %s " "has been accepted." % cluster.uuid) class ShowCa(command.Command): _description = _("Show details about the CA certificate for a cluster.") def get_parser(self, prog_name): parser = super(ShowCa, self).get_parser(prog_name) # NOTE: All arguments are positional and, if not provided # with a default, required. parser.add_argument('cluster', metavar='', help='ID or name of the cluster') return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) mag_client = self.app.client_manager.container_infra cluster = mag_client.clusters.get(parsed_args.cluster) cert = mag_client.certificates.get(cluster.uuid) _show_cert(cert) class SignCa(command.Command): _description = _("Generate the CA certificate for a cluster.") def get_parser(self, prog_name): parser = super(SignCa, self).get_parser(prog_name) # NOTE: All arguments are positional and, if not provided # with a default, required. parser.add_argument('cluster', metavar='', help='ID or name of the cluster') parser.add_argument('csr', metavar='', help='File path of csr file to send to Magnum' ' to get signed.') return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) mag_client = self.app.client_manager.container_infra opts = { 'cluster_uuid': _get_target_uuid(mag_client, parsed_args) } if parsed_args.csr is None or not os.path.isfile(parsed_args.csr): print('A CSR must be provided.') return with open(parsed_args.csr, 'r') as f: opts['csr'] = f.read() cert = mag_client.certificates.create(**opts) _show_cert(cert) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/osc/v1/cluster_templates.py0000664000175000017500000004110000000000000025534 0ustar00zuulzuul00000000000000# Copyright 2016 EasyStack. 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 magnumclient.common import utils as magnum_utils from magnumclient.exceptions import InvalidAttribute from magnumclient.i18n import _ from osc_lib.command import command from osc_lib import utils as osc_utils from oslo_log import log as logging CLUSTER_TEMPLATE_ATTRIBUTES = [ 'insecure_registry', 'labels', 'updated_at', 'floating_ip_enabled', 'fixed_subnet', 'master_flavor_id', 'uuid', 'no_proxy', 'https_proxy', 'tls_disabled', 'keypair_id', 'public', 'http_proxy', 'docker_volume_size', 'server_type', 'external_network_id', 'cluster_distro', 'image_id', 'volume_driver', 'registry_enabled', 'docker_storage_driver', 'apiserver_port', 'name', 'created_at', 'network_driver', 'fixed_network', 'coe', 'flavor_id', 'master_lb_enabled', 'dns_nameserver', 'project_id', 'hidden', 'tags', ] def _show_cluster_template(cluster_template): del cluster_template._info['links'] for field in cluster_template._info: if cluster_template._info[field] is None: setattr(cluster_template, field, '-') columns = CLUSTER_TEMPLATE_ATTRIBUTES return columns, osc_utils.get_item_properties(cluster_template, columns) class CreateClusterTemplate(command.ShowOne): """Create a Cluster Template.""" _description = _("Create a Cluster Template.") def get_parser(self, prog_name): parser = super(CreateClusterTemplate, self).get_parser(prog_name) parser.add_argument( 'name', metavar='', help=_('Name of the cluster template to create.')) parser.add_argument( '--coe', required=True, metavar='', help=_('Specify the Container Orchestration Engine to use.')) parser.add_argument( '--image', required=True, metavar='', help=_('The name or UUID of the base image to customize for the ' 'Cluster.')) parser.add_argument( '--external-network', dest='external_network', required=True, metavar='', help=_('The external Neutron network name or UUID to connect to ' 'this Cluster Template.')) parser.add_argument( '--keypair', metavar='', help=_('The name or UUID of the SSH keypair to load into the ' 'Cluster nodes.')) parser.add_argument( '--fixed-network', dest='fixed_network', metavar='', help=_('The private Neutron network name to connect to this ' 'Cluster model.')) parser.add_argument( '--fixed-subnet', dest='fixed_subnet', metavar='', help=_('The private Neutron subnet name to connect to Cluster.')) parser.add_argument( '--network-driver', dest='network_driver', metavar='', help=_('The network driver name for instantiating container ' 'networks.')) parser.add_argument( '--volume-driver', dest='volume_driver', metavar='', help=_('The volume driver name for instantiating container ' 'volume.')) parser.add_argument( '--dns-nameserver', dest='dns_nameserver', metavar='', default='8.8.8.8', help=_('The DNS nameserver to use for this cluster template.')) parser.add_argument( '--flavor', metavar='', default='m1.medium', help=_('The nova flavor name or UUID to use when launching the ' 'Cluster.')) parser.add_argument( '--master-flavor', dest='master_flavor', metavar='', help=_('The nova flavor name or UUID to use when launching the ' 'master node of the Cluster.')) parser.add_argument( '--docker-volume-size', dest='docker_volume_size', metavar='', type=int, help=_('Specify the number of size in GB for the docker volume ' 'to use.')) parser.add_argument( '--docker-storage-driver', dest='docker_storage_driver', metavar='', default='overlay2', help=_('Select a docker storage driver. Supported: devicemapper, ' 'overlay, overlay2. Default: overlay2')) parser.add_argument( '--http-proxy', dest='http_proxy', metavar='', help=_('The http_proxy address to use for nodes in Cluster.')) parser.add_argument( '--https-proxy', dest='https_proxy', metavar='', help=_('The https_proxy address to use for nodes in Cluster.')) parser.add_argument( '--no-proxy', dest='no_proxy', metavar='', help=_('The no_proxy address to use for nodes in Cluster.')) parser.add_argument( '--labels', metavar='', action='append', default=[], help=_('Arbitrary labels in the form of key=value pairs to ' 'associate with a cluster template. May be used multiple ' 'times.')) parser.add_argument( '--tls-disabled', dest='tls_disabled', action='store_true', default=False, help=_('Disable TLS in the Cluster.')) parser.add_argument( '--public', action='store_true', default=False, help=_('Make cluster template public.')) parser.add_argument( '--registry-enabled', dest='registry_enabled', action='store_true', default=False, help=_('Enable docker registry in the Cluster')) parser.add_argument( '--insecure-registry', dest='insecure_registry', help=_('The URL pointing to users own private insecure docker' 'registry to deploy and run docker containers.')) parser.add_argument( '--server-type', dest='server_type', metavar='', default='vm', help=_('Specify the server type to be used for example vm. ' 'For this release default server type will be vm.')) parser.add_argument( '--master-lb-enabled', dest='master_lb_enabled', action='store_true', default=False, help=_('Indicates whether created Clusters should have a load ' 'balancer for master nodes or not.')) parser.add_argument( '--floating-ip-enabled', dest='floating_ip_enabled', default=[], action='append_const', const=True, help=_('Indicates whether created Clusters should have a ' 'floating ip.')) parser.add_argument( '--floating-ip-disabled', dest='floating_ip_enabled', action='append_const', const=False, help=_('Disables floating ip creation on the new Cluster')) parser.add_argument( '--hidden', dest='hidden', action='store_true', default=False, help=_('Indicates the cluster template should be hidden.')) parser.add_argument( '--visible', dest='hidden', action='store_false', help=_('Indicates the cluster template should be visible.')) parser.add_argument( '--tags', action='append', default=[], metavar='<--tags tag1 --tags tag2,tag3>', help=_('Tags to be added to the cluster template.')) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) mag_client = self.app.client_manager.container_infra args = { 'name': parsed_args.name, 'image_id': parsed_args.image, 'keypair_id': parsed_args.keypair, 'external_network_id': parsed_args.external_network, 'coe': parsed_args.coe, 'fixed_network': parsed_args.fixed_network, 'fixed_subnet': parsed_args.fixed_subnet, 'network_driver': parsed_args.network_driver, 'volume_driver': parsed_args.volume_driver, 'dns_nameserver': parsed_args.dns_nameserver, 'flavor_id': parsed_args.flavor, 'master_flavor_id': parsed_args.master_flavor, 'docker_volume_size': parsed_args.docker_volume_size, 'docker_storage_driver': parsed_args.docker_storage_driver, 'http_proxy': parsed_args.http_proxy, 'https_proxy': parsed_args.https_proxy, 'no_proxy': parsed_args.no_proxy, 'labels': magnum_utils.handle_labels(parsed_args.labels), 'tls_disabled': parsed_args.tls_disabled, 'public': parsed_args.public, 'registry_enabled': parsed_args.registry_enabled, 'insecure_registry': parsed_args.insecure_registry, 'server_type': parsed_args.server_type, 'master_lb_enabled': parsed_args.master_lb_enabled, } # NOTE: Make new arguments backward compatible if parsed_args.hidden: args['hidden'] = parsed_args.hidden if parsed_args.tags: args['tags'] = ','.join(set( (','.join(parsed_args.tags)).split(','))) if len(parsed_args.floating_ip_enabled) > 1: raise InvalidAttribute('--floating-ip-enabled and ' '--floating-ip-disabled are ' 'mutually exclusive and ' 'should be specified only once.') elif len(parsed_args.floating_ip_enabled) == 1: args['floating_ip_enabled'] = parsed_args.floating_ip_enabled[0] deprecated = ['devicemapper', 'overlay'] if args['docker_storage_driver'] in deprecated: print("WARNING: Docker storage drivers %s are deprecated and will " "be removed in a future release. Use overlay2 instead." % deprecated) ct = mag_client.cluster_templates.create(**args) print("Request to create cluster template %s accepted" % parsed_args.name) return _show_cluster_template(ct) class DeleteClusterTemplate(command.Command): """Delete a Cluster Template.""" _description = _("Delete a Cluster Template.") def get_parser(self, prog_name): parser = super(DeleteClusterTemplate, self).get_parser(prog_name) parser.add_argument( 'cluster-templates', metavar='', nargs='+', help=_('ID or name of the (cluster template)s to delete.')) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) mag_client = self.app.client_manager.container_infra for cluster_template in getattr(parsed_args, 'cluster-templates'): try: mag_client.cluster_templates.delete(cluster_template) print( "Request to delete cluster template %s has been accepted." % cluster_template) except Exception as e: print("Delete for cluster template " "%(cluster_template)s failed: %(e)s" % {'cluster_template': cluster_template, 'e': e}) class ListTemplateCluster(command.Lister): """List Cluster Templates.""" _description = _("List Cluster Templates.") log = logging.getLogger(__name__ + ".ListTemplateCluster") def get_parser(self, prog_name): parser = super(ListTemplateCluster, self).get_parser(prog_name) parser.add_argument( '--limit', metavar='', type=int, help=_('Maximum number of cluster templates to return')) parser.add_argument( '--sort-key', metavar='', help=_('Column to sort results by')) parser.add_argument( '--sort-dir', metavar='', choices=['desc', 'asc'], help=_('Direction to sort. "asc" or "desc".')) parser.add_argument( '--fields', default=None, metavar='', help=_('Comma-separated list of fields to display. ' 'Available fields: uuid, name, coe, image_id, public, ' 'link, apiserver_port, server_type, tls_disabled, ' 'registry_enabled' ) ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) mag_client = self.app.client_manager.container_infra columns = ['uuid', 'name', 'tags'] if parsed_args.fields: columns += parsed_args.fields.split(',') cts = mag_client.cluster_templates.list(limit=parsed_args.limit, sort_key=parsed_args.sort_key, sort_dir=parsed_args.sort_dir) return ( columns, (osc_utils.get_item_properties(ct, columns) for ct in cts) ) class ShowClusterTemplate(command.ShowOne): """Show a Cluster Template.""" _description = _("Show a Cluster Template.") log = logging.getLogger(__name__ + ".ShowClusterTemplate") def get_parser(self, prog_name): parser = super(ShowClusterTemplate, self).get_parser(prog_name) parser.add_argument( 'cluster-template', metavar='', help=_('ID or name of the cluster template to show.')) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) mag_client = self.app.client_manager.container_infra ct = getattr(parsed_args, 'cluster-template', None) cluster_template = mag_client.cluster_templates.get(ct) return _show_cluster_template(cluster_template) class UpdateClusterTemplate(command.ShowOne): """Update a Cluster Template.""" log = logging.getLogger(__name__ + ".UpdateClusterTemplate") def get_parser(self, prog_name): parser = super(UpdateClusterTemplate, self).get_parser(prog_name) parser.add_argument( 'cluster-template', metavar='', help=_('The name or UUID of cluster template to update')) parser.add_argument( 'op', metavar='', choices=['add', 'replace', 'remove'], help=_("Operations: one of 'add', 'replace' or 'remove'")) parser.add_argument( 'attributes', metavar='', nargs='+', action='append', default=[], help=_( "Attributes to add/replace or remove (only PATH is necessary " "on remove)")) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) mag_client = self.app.client_manager.container_infra patch = magnum_utils.args_array_to_patch(parsed_args.op, parsed_args.attributes[0]) name = getattr(parsed_args, 'cluster-template', None) ct = mag_client.cluster_templates.update(name, patch) return _show_cluster_template(ct) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/osc/v1/clusters.py0000664000175000017500000005003600000000000023651 0ustar00zuulzuul00000000000000# Copyright 2016 EasyStack. 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 magnumclient.common import utils as magnum_utils from magnumclient import exceptions from magnumclient.i18n import _ from osc_lib.command import command from osc_lib import utils CLUSTER_ATTRIBUTES = [ 'status', 'health_status', 'cluster_template_id', 'node_addresses', 'uuid', 'stack_id', 'status_reason', 'created_at', 'updated_at', 'coe_version', 'labels', 'labels_overridden', 'labels_skipped', 'labels_added', 'fixed_network', 'fixed_subnet', 'floating_ip_enabled', 'faults', 'keypair', 'api_address', 'master_addresses', 'master_lb_enabled', 'create_timeout', 'node_count', 'discovery_url', 'docker_volume_size', 'master_count', 'container_version', 'name', 'master_flavor_id', 'flavor_id', 'health_status_reason', 'project_id', ] class CreateCluster(command.Command): _description = _("Create a cluster") def get_parser(self, prog_name): parser = super(CreateCluster, self).get_parser(prog_name) # NOTE: All arguments are positional and, if not provided # with a default, required. parser.add_argument('--cluster-template', dest='cluster_template', required=True, metavar='', help='ID or name of the cluster template.') parser.add_argument('--discovery-url', dest='discovery_url', metavar='', help=('Specifies custom delivery url for ' 'node discovery.')) parser.add_argument('--docker-volume-size', dest='docker_volume_size', type=int, metavar='', help=('The size in GB for the docker volume to ' 'use.')) parser.add_argument('--labels', metavar='', action='append', help=_('Arbitrary labels in the form of key=value ' 'pairs to associate with a cluster ' 'template. May be used multiple times.')) parser.add_argument('--keypair', default=None, metavar='', help='UUID or name of the keypair to use.') parser.add_argument('--master-count', dest='master_count', type=int, default=1, metavar='', help='The number of master nodes for the cluster.') parser.add_argument('name', metavar='', help='Name of the cluster to create.') parser.add_argument('--node-count', dest='node_count', type=int, default=1, metavar='', help='The cluster node count.') parser.add_argument('--timeout', type=int, default=60, metavar='', help=('The timeout for cluster creation time. The ' 'default is 60 minutes.')) parser.add_argument( '--master-flavor', dest='master_flavor', metavar='', help=_('The nova flavor name or UUID to use when launching the ' 'master node of the Cluster.')) parser.add_argument( '--flavor', metavar='', help=_('The nova flavor name or UUID to use when launching the ' 'Cluster.')) parser.add_argument( '--fixed-network', dest='fixed_network', metavar='', help=_('The private Neutron network name to connect to this ' 'Cluster template.')) parser.add_argument( '--fixed-subnet', dest='fixed_subnet', metavar='', help=_('The private Neutron subnet name to connect to Cluster.')) parser.add_argument( '--floating-ip-enabled', dest='floating_ip_enabled', default=[], action='append_const', const=True, help=_('Indicates whether created Clusters should have a ' 'floating ip.')) parser.add_argument( '--floating-ip-disabled', dest='floating_ip_enabled', action='append_const', const=False, help=_('Disables floating ip creation on the new Cluster')) parser.add_argument( '--merge-labels', dest='merge_labels', action='store_true', default=False, help=_('The labels provided will be merged with the labels ' 'configured in the specified cluster template.')) parser.add_argument( '--master-lb-enabled', dest='master_lb_enabled', action='append_const', default=[], const=True, help=_('Enable master LB creation on the new cluster')) parser.add_argument( '--master-lb-disabled', dest='master_lb_enabled', action='append_const', const=False, help=_('Disable master LB creation on the new cluster')) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) mag_client = self.app.client_manager.container_infra args = { 'cluster_template_id': parsed_args.cluster_template, 'create_timeout': parsed_args.timeout, 'discovery_url': parsed_args.discovery_url, 'keypair': parsed_args.keypair, 'master_count': parsed_args.master_count, 'name': parsed_args.name, 'node_count': parsed_args.node_count, } if len(parsed_args.floating_ip_enabled) > 1: raise exceptions.InvalidAttribute( '--floating-ip-enabled and ' '--floating-ip-disabled are ' 'mutually exclusive and ' 'should be specified only once.') elif len(parsed_args.floating_ip_enabled) == 1: args['floating_ip_enabled'] = parsed_args.floating_ip_enabled[0] if parsed_args.labels is not None: args['labels'] = magnum_utils.handle_labels(parsed_args.labels) if parsed_args.docker_volume_size is not None: args['docker_volume_size'] = parsed_args.docker_volume_size if parsed_args.master_flavor is not None: args['master_flavor_id'] = parsed_args.master_flavor if parsed_args.flavor is not None: args['flavor_id'] = parsed_args.flavor if parsed_args.fixed_network is not None: args["fixed_network"] = parsed_args.fixed_network if parsed_args.fixed_subnet is not None: args["fixed_subnet"] = parsed_args.fixed_subnet if parsed_args.merge_labels: # We are only sending this if it's True. This # way we avoid breaking older APIs. args["merge_labels"] = parsed_args.merge_labels if parsed_args.master_lb_enabled: args["master_lb_enabled"] = parsed_args.master_lb_enabled if len(parsed_args.master_lb_enabled) > 1: raise exceptions.InvalidAttribute( '--master-lb-enabled and ' '--master-lb-disabled are ' 'mutually exclusive and ' 'should be specified only once.') elif len(parsed_args.master_lb_enabled) == 1: args['master_lb_enabled'] = parsed_args.master_lb_enabled[0] if (not args['master_lb_enabled'] and parsed_args.master_count > 1): raise exceptions.InvalidAttribute( 'Master node count can only be one if master ' 'loadbalancer is disabled.') cluster = mag_client.clusters.create(**args) print("Request to create cluster %s accepted" % cluster.uuid) class DeleteCluster(command.Command): _description = _("Delete a cluster") def get_parser(self, prog_name): parser = super(DeleteCluster, self).get_parser(prog_name) parser.add_argument( 'cluster', nargs='+', metavar='', help='ID or name of the cluster(s) to delete.') return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) mag_client = self.app.client_manager.container_infra for cluster in parsed_args.cluster: mag_client.clusters.delete(cluster) print("Request to delete cluster %s has been accepted." % cluster) class ListCluster(command.Lister): _description = _("List clusters") def get_parser(self, prog_name): parser = super(ListCluster, self).get_parser(prog_name) parser.add_argument( '--limit', metavar='', type=int, help=_('Maximum number of clusters to return')) parser.add_argument( '--sort-key', metavar='', help=_('Column to sort results by')) parser.add_argument( '--sort-dir', metavar='', choices=['desc', 'asc'], help=_('Direction to sort. "asc" or "desc".')) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) mag_client = self.app.client_manager.container_infra columns = [ 'uuid', 'name', 'keypair', 'node_count', 'master_count', 'status', 'health_status'] clusters = mag_client.clusters.list(limit=parsed_args.limit, sort_key=parsed_args.sort_key, sort_dir=parsed_args.sort_dir) return ( columns, (utils.get_item_properties(c, columns) for c in clusters) ) class ShowCluster(command.ShowOne): _description = _("Show a Cluster") def get_parser(self, prog_name): parser = super(ShowCluster, self).get_parser(prog_name) parser.add_argument( 'cluster', metavar='', help=_('ID or name of the cluster to show.') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) columns = CLUSTER_ATTRIBUTES mag_client = self.app.client_manager.container_infra cluster = mag_client.clusters.get(parsed_args.cluster) return (columns, utils.get_item_properties(cluster, columns)) class UpdateCluster(command.Command): _description = _("Update a Cluster") def get_parser(self, prog_name): parser = super(UpdateCluster, self).get_parser(prog_name) parser.add_argument( 'cluster', metavar='', help=_('The name or UUID of cluster to update')) parser.add_argument( 'op', metavar='', choices=['add', 'replace', 'remove'], help=_("Operations: one of 'add', 'replace' or 'remove'")) parser.add_argument( 'attributes', metavar='', nargs='+', action='append', default=[], help=_( "Attributes to add/replace or remove (only PATH is necessary " "on remove)")) parser.add_argument( '--rollback', action='store_true', dest='rollback', default=False, help=_('Rollback cluster on update failure.')) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) mag_client = self.app.client_manager.container_infra patch = magnum_utils.args_array_to_patch(parsed_args.op, parsed_args.attributes[0]) mag_client.clusters.update(parsed_args.cluster, patch) print("Request to update cluster %s has been accepted." % parsed_args.cluster) class ConfigCluster(command.Command): _description = _("Get Configuration for a Cluster") def get_parser(self, prog_name): parser = super(ConfigCluster, self).get_parser(prog_name) parser.add_argument( 'cluster', metavar='', help=_('The name or UUID of cluster to update')) parser.add_argument( '--dir', metavar='', default='.', help=_('Directory to save the certificate and config files.')) parser.add_argument( '--force', action='store_true', dest='force', default=False, help=_('Overwrite files if existing.')) parser.add_argument( '--output-certs', action='store_true', dest='output_certs', default=False, help=_('Output certificates in separate files.')) parser.add_argument( '--use-certificate', action='store_true', dest='use_certificate', default=True, help=_('Use certificate in config files.')) parser.add_argument( '--use-keystone', action='store_true', dest='use_keystone', default=False, help=_('Use Keystone token in config files.')) return parser def take_action(self, parsed_args): """Configure native client to access cluster. You can source the output of this command to get the native client of the corresponding COE configured to access the cluster. """ if parsed_args.use_keystone: parsed_args.use_certificate = False if not parsed_args.use_certificate: parsed_args.use_keystone = True self.log.debug("take_action(%s)", parsed_args) mag_client = self.app.client_manager.container_infra parsed_args.dir = os.path.abspath(parsed_args.dir) cluster = mag_client.clusters.get(parsed_args.cluster) if cluster.api_address is None: self.log.warning("WARNING: The cluster's api_address is" " not known yet.") cluster_template = mag_client.cluster_templates.get( cluster.cluster_template_id) tls = self._fetch_tls( mag_client=mag_client, cluster_uuid=cluster.uuid, tls_required=(not cluster_template.tls_disabled), certkey_required=(not parsed_args.use_keystone) ) if parsed_args.output_certs: self._write_certs( certs=tls, path=parsed_args.dir, force=parsed_args.force ) print(magnum_utils.config_cluster( cluster, cluster_template, parsed_args.dir, force=parsed_args.force, certs=tls, use_keystone=parsed_args.use_keystone)) def _fetch_tls( self, mag_client, cluster_uuid, tls_required, certkey_required=True, ): if not tls_required: return {} opts = { 'cluster_uuid': cluster_uuid, } tls = { 'ca': mag_client.certificates.get(**opts).pem } if certkey_required: csr = magnum_utils.generate_csr_and_key() opts['csr'] = csr['csr'] tls['cert'] = mag_client.certificates.create(**opts).pem tls['key'] = csr['key'] return tls def _write_certs(self, certs, path, force): for k in ('key', 'cert', 'ca'): fname = "%s/%s.pem" % (path, k) if os.path.exists(fname): if not force: raise Exception("File %s exists, aborting." % fname) os.remove(fname) if k not in certs: # key and cert aren't always generated continue with open(fname, "w") as f: f.write(certs[k]) class ResizeCluster(command.Command): _description = _("Resize a Cluster") def get_parser(self, prog_name): parser = super(ResizeCluster, self).get_parser(prog_name) parser.add_argument( 'cluster', metavar='', help=_('The name or UUID of cluster to update')) parser.add_argument( 'node_count', type=int, help=_("Desired node count of the cluser.")) parser.add_argument( '--nodes-to-remove', metavar='', action='append', help=_('Server ID of the nodes to be removed. Repeat to add ' 'more server ID')) parser.add_argument( '--nodegroup', metavar='', help=_('The name or UUID of the nodegroup of current cluster.')) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) mag_client = self.app.client_manager.container_infra cluster = mag_client.clusters.get(parsed_args.cluster) mag_client.clusters.resize(cluster.uuid, parsed_args.node_count, parsed_args.nodes_to_remove, parsed_args.nodegroup) print("Request to resize cluster %s has been accepted." % parsed_args.cluster) class UpgradeCluster(command.Command): _description = _("Upgrade a Cluster") def get_parser(self, prog_name): parser = super(UpgradeCluster, self).get_parser(prog_name) parser.add_argument( 'cluster', metavar='', help=_('The name or UUID of cluster to update')) parser.add_argument( 'cluster_template', help=_("The new cluster template ID will be upgraded to.")) parser.add_argument( '--max-batch-size', metavar='', type=int, default=1, help=_("The max batch size for upgrading each time.")) parser.add_argument( '--nodegroup', metavar='', help=_('The name or UUID of the nodegroup of current cluster.')) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) mag_client = self.app.client_manager.container_infra cluster = mag_client.clusters.get(parsed_args.cluster) mag_client.clusters.upgrade(cluster.uuid, parsed_args.cluster_template, parsed_args.max_batch_size, parsed_args.nodegroup) print("Request to upgrade cluster %s has been accepted." % parsed_args.cluster) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/osc/v1/mservices.py0000664000175000017500000000257100000000000024006 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.command import command from osc_lib import utils def _get_client(obj, parsed_args): obj.log.debug("take_action(%s)" % parsed_args) return obj.app.client_manager.container_infra class ListService(command.Lister): """Print a list of Magnum services.""" log = logging.getLogger(__name__ + ".ListService") def get_parser(self, prog_name): parser = super(ListService, self).get_parser(prog_name) return parser def take_action(self, parsed_args): client = _get_client(self, parsed_args) services = client.mservices.list() columns = ('id', 'host', 'binary', 'state', 'disabled', 'disabled_reason', 'created_at', 'updated_at') return (columns, (utils.get_item_properties(service, columns) for service in services)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/osc/v1/nodegroups.py0000664000175000017500000002530700000000000024175 0ustar00zuulzuul00000000000000# Copyright (c) 2018 European Organization for Nuclear Research. # 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 magnumclient.common import utils as magnum_utils from magnumclient.i18n import _ from osc_lib.command import command from osc_lib import utils NODEGROUP_ATTRIBUTES = [ 'uuid', 'name', 'cluster_id', 'project_id', 'docker_volume_size', 'labels', 'labels_overridden', 'labels_skipped', 'labels_added', 'flavor_id', 'image_id', 'node_addresses', 'node_count', 'role', 'max_node_count', 'min_node_count', 'is_default', 'stack_id', 'status', 'status_reason', ] class CreateNodeGroup(command.Command): _description = _("Create a nodegroup") def get_parser(self, prog_name): parser = super(CreateNodeGroup, self).get_parser(prog_name) # NOTE: All arguments are positional and, if not provided # with a default, required. parser.add_argument('--docker-volume-size', dest='docker_volume_size', type=int, metavar='', help=('The size in GB for the docker volume to ' 'use.')) parser.add_argument('--labels', metavar='', action='append', help=_('Arbitrary labels in the form of key=value' 'pairs to associate with a nodegroup. ' 'May be used multiple times.')) parser.add_argument('cluster', metavar='', help='Name of the nodegroup to create.') parser.add_argument('name', metavar='', help='Name of the nodegroup to create.') parser.add_argument('--node-count', dest='node_count', type=int, default=1, metavar='', help='The nodegroup node count.') parser.add_argument('--min-nodes', dest='min_node_count', type=int, default=0, metavar='', help='The nodegroup minimum node count.') parser.add_argument('--max-nodes', dest='max_node_count', type=int, default=None, metavar='', help='The nodegroup maximum node count.') parser.add_argument('--role', dest='role', type=str, default='worker', metavar='', help=('The role of the nodegroup')) parser.add_argument( '--image', metavar='', help=_('The name or UUID of the base image to customize for the ' 'NodeGroup.')) parser.add_argument( '--flavor', metavar='', help=_('The nova flavor name or UUID to use when launching the ' 'nodes in this NodeGroup.')) parser.add_argument( '--merge-labels', dest='merge_labels', action='store_true', default=False, help=_('The labels provided will be merged with the labels ' 'configured in the specified cluster.')) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) mag_client = self.app.client_manager.container_infra args = { 'name': parsed_args.name, 'node_count': parsed_args.node_count, 'max_node_count': parsed_args.max_node_count, 'min_node_count': parsed_args.min_node_count, 'role': parsed_args.role, } if parsed_args.labels is not None: args['labels'] = magnum_utils.handle_labels(parsed_args.labels) if parsed_args.docker_volume_size is not None: args['docker_volume_size'] = parsed_args.docker_volume_size if parsed_args.flavor is not None: args['flavor_id'] = parsed_args.flavor if parsed_args.image is not None: args['image_id'] = parsed_args.image if parsed_args.merge_labels: # We are only sending this if it's True. This # way we avoid breaking older APIs. args["merge_labels"] = parsed_args.merge_labels cluster_id = parsed_args.cluster nodegroup = mag_client.nodegroups.create(cluster_id, **args) print("Request to create nodegroup %s accepted" % nodegroup.uuid) class DeleteNodeGroup(command.Command): _description = _("Delete a nodegroup") def get_parser(self, prog_name): parser = super(DeleteNodeGroup, self).get_parser(prog_name) parser.add_argument( 'cluster', metavar='', help=_('ID or name of the cluster where the nodegroup(s) ' 'belong(s).')) parser.add_argument( 'nodegroup', nargs='+', metavar='', help='ID or name of the nodegroup(s) to delete.') return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) mag_client = self.app.client_manager.container_infra cluster_id = parsed_args.cluster for ng in parsed_args.nodegroup: mag_client.nodegroups.delete(cluster_id, ng) print("Request to delete nodegroup %s has been accepted." % ng) class ListNodeGroup(command.Lister): _description = _("List nodegroups") def get_parser(self, prog_name): parser = super(ListNodeGroup, self).get_parser(prog_name) parser.add_argument( 'cluster', metavar='', help=_('ID or name of the cluster where the nodegroup belongs.')) parser.add_argument( '--limit', metavar='', type=int, help=_('Maximum number of nodegroups to return')) parser.add_argument( '--sort-key', metavar='', help=_('Column to sort results by')) parser.add_argument( '--sort-dir', metavar='', choices=['desc', 'asc'], help=_('Direction to sort. "asc" or "desc".')) parser.add_argument( '--role', metavar='', help=_('List the nodegroups in the cluster with this role')) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) mag_client = self.app.client_manager.container_infra columns = ['uuid', 'name', 'flavor_id', 'image_id', 'node_count', 'status', 'role'] cluster_id = parsed_args.cluster nodegroups = mag_client.nodegroups.list(cluster_id, limit=parsed_args.limit, sort_key=parsed_args.sort_key, sort_dir=parsed_args.sort_dir, role=parsed_args.role) return ( columns, (utils.get_item_properties(n, columns) for n in nodegroups) ) class ShowNodeGroup(command.ShowOne): _description = _("Show a nodegroup") def get_parser(self, prog_name): parser = super(ShowNodeGroup, self).get_parser(prog_name) parser.add_argument( 'cluster', metavar='', help=_('ID or name of the cluster where the nodegroup belongs.')) parser.add_argument( 'nodegroup', metavar='', help=_('ID or name of the nodegroup to show.') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) columns = NODEGROUP_ATTRIBUTES mag_client = self.app.client_manager.container_infra cluster_id = parsed_args.cluster nodegroup = mag_client.nodegroups.get(cluster_id, parsed_args.nodegroup) return (columns, utils.get_item_properties(nodegroup, columns)) class UpdateNodeGroup(command.Command): _description = _("Update a Nodegroup") def get_parser(self, prog_name): parser = super(UpdateNodeGroup, self).get_parser(prog_name) parser.add_argument( 'cluster', metavar='', help=_('ID or name of the cluster where the nodegroup belongs.')) parser.add_argument( 'nodegroup', metavar='', help=_('The name or UUID of cluster to update')) parser.add_argument( 'op', metavar='', choices=['add', 'replace', 'remove'], help=_("Operations: one of 'add', 'replace' or 'remove'")) parser.add_argument( 'attributes', metavar='', nargs='+', action='append', default=[], help=_( "Attributes to add/replace or remove (only PATH is necessary " "on remove)")) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) mag_client = self.app.client_manager.container_infra patch = magnum_utils.args_array_to_patch(parsed_args.op, parsed_args.attributes[0]) cluster_id = parsed_args.cluster mag_client.nodegroups.update(cluster_id, parsed_args.nodegroup, patch) print("Request to update nodegroup %s has been accepted." % parsed_args.nodegroup) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/osc/v1/quotas.py0000664000175000017500000002062200000000000023317 0ustar00zuulzuul00000000000000# Copyright 2015 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from magnumclient.common import cliutils as utils from magnumclient.common import utils as magnum_utils from magnumclient.i18n import _ from osc_lib.command import command QUOTA_ATTRIBUTES = [ 'resource', 'created_at', 'updated_at', 'hard_limit', 'project_id', 'id' ] def _show_quota(quota): utils.print_dict(quota._info) class CreateQuotas(command.Command): _description = _("Create a quota.") def get_parser(self, prog_name): parser = super(CreateQuotas, self).get_parser(prog_name) parser.add_argument('--project-id', required=True, metavar='', help='Project ID') parser.add_argument('--resource', required=True, metavar='', help='Resource name.') parser.add_argument('--hard-limit', metavar='', type=int, default=1, help='Max resource limit (default: hard-limit=1)') return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) mag_client = self.app.client_manager.container_infra opts = { 'project_id': parsed_args.project_id, 'resource': parsed_args.resource, 'hard_limit': parsed_args.hard_limit } try: quota = mag_client.quotas.create(**opts) _show_quota(quota) except Exception as e: print("Create quota for project_id %(id)s resource %(res)s failed:" " %(e)s" % {'id': parsed_args.project_id, 'res': parsed_args.resource, 'e': e}) class DeleteQuotas(command.Command): _description = _("Delete specified resource quota.") def get_parser(self, prog_name): parser = super(DeleteQuotas, self).get_parser(prog_name) parser.add_argument('--project-id', required=True, metavar='', help='Project ID') parser.add_argument('--resource', required=True, metavar='', help='Resource name.') return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) mag_client = self.app.client_manager.container_infra try: mag_client.quotas.delete(parsed_args.project_id, parsed_args.resource) print("Request to delete quota for project id %(id)s and resource " "%(res)s has been accepted." % {'id': parsed_args.project_id, 'res': parsed_args.resource}) except Exception as e: print("Quota delete failed for project id %(id)s and resource " "%(res)s :%(e)s" % {'id': parsed_args.project_id, 'res': parsed_args.resource, 'e': e}) class ShowQuotas(command.Command): _description = _("Show details about the given project resource quota.") def get_parser(self, prog_name): parser = super(ShowQuotas, self).get_parser(prog_name) parser.add_argument('--project-id', required=True, metavar='', help='Project ID') parser.add_argument('--resource', required=True, metavar='', help='Resource name.') return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) mag_client = self.app.client_manager.container_infra project_id = parsed_args.project_id resource = parsed_args.resource quota = mag_client.quotas.get(project_id, resource) _show_quota(quota) class UpdateQuotas(command.Command): _description = _("Update information about the given " "project resource quota.") def get_parser(self, prog_name): parser = super(UpdateQuotas, self).get_parser(prog_name) parser.add_argument('--project-id', required=True, metavar='', help='Project ID') parser.add_argument('--resource', required=True, metavar='', help='Resource name.') parser.add_argument('--hard-limit', metavar='', type=int, default=1, help='Max resource limit (default: hard-limit=1)') return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) mag_client = self.app.client_manager.container_infra opts = { 'project_id': parsed_args.project_id, 'resource': parsed_args.resource, 'hard_limit': parsed_args.hard_limit } try: quota = mag_client.quotas.update(parsed_args.project_id, parsed_args.resource, opts) _show_quota(quota) except Exception as e: print("Update quota for project_id %(id)s resource %(res)s failed:" " %(e)s" % {'id': parsed_args.project_id, 'res': parsed_args.resource, 'e': e}) class ListQuotas(command.Command): _description = _("Print a list of available quotas.") def get_parser(self, prog_name): parser = super(ListQuotas, self).get_parser(prog_name) parser.add_argument('--marker', metavar='', default=None, help=_('The last quota UUID of the previous page; ' 'displays list of quotas after "marker".')) parser.add_argument('--limit', metavar='', type=int, help='Maximum number of quotas to return.') parser.add_argument('--sort-key', metavar='', help='Column to sort results by.') parser.add_argument('--sort-dir', metavar='', choices=['desc', 'asc'], help='Direction to sort. "asc" or "desc".') parser.add_argument('--all-tenants', action='store_true', default=False, help='Flag to indicate list all tenant quotas.') return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) mag_client = self.app.client_manager.container_infra quotas = mag_client.quotas.list(marker=parsed_args.marker, limit=parsed_args.limit, sort_key=parsed_args.sort_key, sort_dir=parsed_args.sort_dir, all_tenants=parsed_args.all_tenants) columns = ['project_id', 'resource', 'hard_limit'] utils.print_list(quotas, columns, {'versions': magnum_utils.print_list_field('versions')}, sortby_index=None) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/osc/v1/stats.py0000664000175000017500000000264100000000000023142 0ustar00zuulzuul00000000000000# Copyright 2015 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from magnumclient.common import cliutils as utils from magnumclient.i18n import _ from osc_lib.command import command class ListStats(command.Command): _description = _("Show stats for the given project_id") def get_parser(self, prog_name): parser = super(ListStats, self).get_parser(prog_name) parser.add_argument('project_id', metavar='', help='Project ID') return parser def take_action(self, parsed_args): mag_client = self.app.client_manager.container_infra opts = { 'project_id': parsed_args.project_id } stats = mag_client.stats.list(**opts) try: utils.print_dict(stats._info) except AttributeError: return None ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/shell.py0000664000175000017500000006303300000000000022003 0ustar00zuulzuul00000000000000# Copyright 2014 # The Cloudscaling Group, 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. ### # This code is taken from python-novaclient. Goal is minimal modification. ### """ Command-line interface to the OpenStack Magnum API. """ import argparse import logging import os import sys from oslo_utils import encodeutils from oslo_utils import importutils from oslo_utils import strutils from magnumclient.common import cliutils from magnumclient import exceptions as exc from magnumclient.i18n import _ from magnumclient.v1 import client as client_v1 from magnumclient.v1 import shell as shell_v1 from magnumclient import version profiler = importutils.try_import("osprofiler.profiler") HAS_KEYRING = False all_errors = ValueError try: import keyring HAS_KEYRING = True try: if isinstance(keyring.get_keyring(), keyring.backend.GnomeKeyring): import gnomekeyring all_errors = (ValueError, gnomekeyring.IOError, gnomekeyring.NoKeyringDaemonError) except Exception: pass except ImportError: pass LATEST_API_VERSION = ('1', 'latest') DEFAULT_INTERFACE = 'public' DEFAULT_SERVICE_TYPE = 'container-infra' logger = logging.getLogger(__name__) def positive_non_zero_float(text): if text is None: return None try: value = float(text) except ValueError: msg = "%s must be a float" % text raise argparse.ArgumentTypeError(msg) if value <= 0: msg = "%s must be greater than 0" % text raise argparse.ArgumentTypeError(msg) return value class MagnumClientArgumentParser(argparse.ArgumentParser): def __init__(self, *args, **kwargs): super(MagnumClientArgumentParser, self).__init__(*args, **kwargs) def error(self, message): """error(message: string) Prints a usage message incorporating the message to stderr and exits. """ self.print_usage(sys.stderr) # FIXME(lzyeval): if changes occur in argparse.ArgParser._check_value choose_from = ' (choose from' progparts = self.prog.partition(' ') self.exit(2, "error: %(errmsg)s\nTry '%(mainp)s help %(subp)s'" " for more information.\n" % {'errmsg': message.split(choose_from)[0], 'mainp': progparts[0], 'subp': progparts[2]}) class OpenStackMagnumShell(object): def get_base_parser(self): parser = MagnumClientArgumentParser( prog='magnum', description=__doc__.strip(), epilog='See "magnum 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=version.version_info.version_string()) parser.add_argument('--debug', default=False, action='store_true', help=_("Print debugging output.")) parser.add_argument('--os-cache', default=strutils.bool_from_string( cliutils.env('OS_CACHE', default=False)), action='store_true', help=_("Use the auth token cache. Defaults to " "False if env[OS_CACHE] is not set.")) parser.add_argument('--os-region-name', metavar='', default=os.environ.get('OS_REGION_NAME'), help=_('Region name. ' 'Default=env[OS_REGION_NAME].')) # TODO(mattf) - add get_timings support to Client # parser.add_argument('--timings', # default=False, # action='store_true', # help="Print call timing info") # TODO(mattf) - use timeout # parser.add_argument('--timeout', # default=600, # metavar='', # type=positive_non_zero_float, # help="Set HTTP call timeout (in seconds)") parser.add_argument('--os-auth-url', metavar='', default=cliutils.env('OS_AUTH_URL', default=None), help=_('Defaults to env[OS_AUTH_URL].')) parser.add_argument('--os-user-id', metavar='', default=cliutils.env('OS_USER_ID', default=None), help=_('Defaults to env[OS_USER_ID].')) parser.add_argument('--os-username', metavar='', default=cliutils.env('OS_USERNAME', default=None), help=_('Defaults to env[OS_USERNAME].')) parser.add_argument('--os-user-domain-id', metavar='', default=cliutils.env('OS_USER_DOMAIN_ID', default=None), help=_('Defaults to env[OS_USER_DOMAIN_ID].')) parser.add_argument('--os-user-domain-name', metavar='', default=cliutils.env('OS_USER_DOMAIN_NAME', default=None), help=_('Defaults to env[OS_USER_DOMAIN_NAME].')) parser.add_argument('--os-project-id', metavar='', default=cliutils.env('OS_PROJECT_ID', default=None), help=_('Defaults to env[OS_PROJECT_ID].')) parser.add_argument('--os-project-name', metavar='', default=cliutils.env('OS_PROJECT_NAME', default=None), help=_('Defaults to env[OS_PROJECT_NAME].')) parser.add_argument('--os-tenant-id', metavar='', default=cliutils.env('OS_TENANT_ID', default=None), help=argparse.SUPPRESS) parser.add_argument('--os-tenant-name', metavar='', default=cliutils.env('OS_TENANT_NAME', default=None), help=argparse.SUPPRESS) parser.add_argument('--os-project-domain-id', metavar='', default=cliutils.env('OS_PROJECT_DOMAIN_ID', default=None), help=_('Defaults to env[OS_PROJECT_DOMAIN_ID].')) parser.add_argument('--os-project-domain-name', metavar='', default=cliutils.env('OS_PROJECT_DOMAIN_NAME', default=None), help=_('Defaults to ' 'env[OS_PROJECT_DOMAIN_NAME].')) parser.add_argument('--os-token', metavar='', default=cliutils.env('OS_TOKEN', default=None), help=_('Defaults to env[OS_TOKEN].')) parser.add_argument('--os-password', metavar='', default=cliutils.env('OS_PASSWORD', default=None), help=_('Defaults to env[OS_PASSWORD].')) parser.add_argument('--service-type', metavar='', help=_('Defaults to container-infra for all ' 'actions.')) parser.add_argument('--service_type', help=argparse.SUPPRESS) parser.add_argument('--endpoint-type', metavar='', default=cliutils.env('OS_ENDPOINT_TYPE', default=None), help=argparse.SUPPRESS) parser.add_argument('--os-endpoint-type', metavar='', default=cliutils.env('OS_ENDPOINT_TYPE', default=None), help=_('Defaults to env[OS_ENDPOINT_TYPE]')) parser.add_argument('--os-interface', metavar='', default=cliutils.env( 'OS_INTERFACE', default=DEFAULT_INTERFACE), help=argparse.SUPPRESS) parser.add_argument('--os-cloud', metavar='', default=cliutils.env('OS_CLOUD', default=None), help=_('Defaults to env[OS_CLOUD].')) # NOTE(dtroyer): We can't add --endpoint_type here due to argparse # thinking usage-list --end is ambiguous; but it # works fine with only --endpoint-type present # Go figure. I'm leaving this here for doc purposes. # parser.add_argument('--endpoint_type', # help=argparse.SUPPRESS) parser.add_argument('--magnum-api-version', metavar='', default=cliutils.env( 'MAGNUM_API_VERSION', default='latest'), help=_('Accepts "api", ' 'defaults to env[MAGNUM_API_VERSION].')) parser.add_argument('--magnum_api_version', help=argparse.SUPPRESS) parser.add_argument('--os-cacert', metavar='', default=cliutils.env('OS_CACERT', default=None), help=_('Specify a CA bundle file to use in ' 'verifying a TLS (https) server ' 'certificate. Defaults to env[OS_CACERT].')) parser.add_argument('--os-endpoint-override', metavar='', default=cliutils.env('OS_ENDPOINT_OVERRIDE', default=None), help=_("Use this API endpoint instead of the " "Service Catalog.")) parser.add_argument('--bypass-url', metavar='', default=cliutils.env('BYPASS_URL', default=None), dest='bypass_url', help=argparse.SUPPRESS) parser.add_argument('--bypass_url', help=argparse.SUPPRESS) parser.add_argument('--insecure', default=cliutils.env('MAGNUMCLIENT_INSECURE', default=False), action='store_true', help=_("Do not verify https connections")) if profiler: parser.add_argument('--profile', metavar='HMAC_KEY', default=cliutils.env('OS_PROFILE', default=None), help='HMAC key to use for encrypting context ' 'data for performance profiling of operation. ' 'This key should be the value of the HMAC key ' 'configured for the OSprofiler middleware in ' 'magnum; it is specified in the Magnum ' 'configuration file at ' '"/etc/magnum/magnum.conf". ' 'Without the key, profiling will not be ' 'triggered even if OSprofiler is enabled on ' 'the server side.') return parser def get_subcommand_parser(self, version): parser = self.get_base_parser() self.subcommands = {} subparsers = parser.add_subparsers(metavar='') try: actions_modules = { '1': shell_v1.COMMAND_MODULES }[version] except KeyError: actions_modules = shell_v1.COMMAND_MODULES for actions_module in actions_modules: self._find_actions(subparsers, actions_module) self._find_actions(subparsers, self) self._add_bash_completion_subparser(subparsers) return parser def _add_bash_completion_subparser(self, subparsers): subparser = ( subparsers.add_parser('bash_completion', add_help=False, formatter_class=OpenStackHelpFormatter) ) self.subcommands['bash_completion'] = subparser subparser.set_defaults(func=self.do_bash_completion) def _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 '' action_help = desc.strip() arguments = getattr(callback, 'arguments', []) group_args = getattr(callback, 'deprecated_groups', []) subparser = ( subparsers.add_parser(command, help=action_help, description=desc, add_help=False, formatter_class=OpenStackHelpFormatter) ) subparser.add_argument('-h', '--help', action='help', help=argparse.SUPPRESS,) self.subcommands[command] = subparser for (old_info, new_info, req) in group_args: group = subparser.add_mutually_exclusive_group(required=req) group.add_argument(*old_info[0], **old_info[1]) group.add_argument(*new_info[0], **new_info[1]) for (args, kwargs) in arguments: subparser.add_argument(*args, **kwargs) subparser.set_defaults(func=callback) def setup_debugging(self, debug): if debug: streamformat = "%(levelname)s (%(module)s:%(lineno)d) %(message)s" # Set up the root logger to debug so that the submodules can # print debug messages logging.basicConfig(level=logging.DEBUG, format=streamformat) else: streamformat = "%(levelname)s %(message)s" logging.basicConfig(level=logging.CRITICAL, format=streamformat) def _check_version(self, api_version): if api_version == 'latest': return LATEST_API_VERSION else: try: versions = tuple(int(i) for i in api_version.split('.')) except ValueError: versions = () if len(versions) == 1: # Default value of magnum_api_version is '1'. # If user not specify the value of api version, not passing # headers at all. magnum_api_version = None elif len(versions) == 2: magnum_api_version = api_version # In the case of '1.0' if versions[1] == 0: magnum_api_version = None else: msg = _("The requested API version %(ver)s is an unexpected " "format. Acceptable formats are 'X', 'X.Y', or the " "literal string '%(latest)s'." ) % {'ver': api_version, 'latest': 'latest'} raise exc.CommandError(msg) api_major_version = versions[0] return (api_major_version, magnum_api_version) def _ensure_auth_info(self, args): if not cliutils.isunauthenticated(args.func): if (not (args.os_token and (args.os_auth_url or args.os_endpoint_override)) and not args.os_cloud ): if not (args.os_username or args.os_user_id): raise exc.CommandError( "You must provide a username via either --os-username " "or via env[OS_USERNAME]" ) if not args.os_password: raise exc.CommandError( "You must provide a password via either " "--os-password, env[OS_PASSWORD], or prompted " "response" ) if (not args.os_project_name and not args.os_project_id): raise exc.CommandError( "You must provide a project name or project id via " "--os-project-name, --os-project-id, " "env[OS_PROJECT_NAME] or env[OS_PROJECT_ID]" ) if not args.os_auth_url: raise exc.CommandError( "You must provide an auth url via either " "--os-auth-url or via env[OS_AUTH_URL]" ) def main(self, argv): # NOTE(Christoph Jansen): With Python 3.4 argv somehow becomes a Map. # This hack fixes it. argv = list(argv) # Parse args once to find version and debug settings parser = self.get_base_parser() (options, args) = parser.parse_known_args(argv) self.setup_debugging(options.debug) # NOTE(dtroyer): Hackery to handle --endpoint_type due to argparse # thinking usage-list --end is ambiguous; but it # works fine with only --endpoint-type present # Go figure. if '--endpoint_type' in argv: spot = argv.index('--endpoint_type') argv[spot] = '--endpoint-type' # build available subcommands based on version (api_major_version, magnum_api_version) = ( self._check_version(options.magnum_api_version)) subcommand_parser = ( self.get_subcommand_parser(api_major_version) ) self.parser = subcommand_parser if options.help or not argv: subcommand_parser.print_help() return 0 args = subcommand_parser.parse_args(argv) # Short-circuit and deal with help right away. # NOTE(jamespage): args.func is not guaranteed with python >= 3.4 if not hasattr(args, 'func') or 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 if not args.service_type: args.service_type = DEFAULT_SERVICE_TYPE if args.bypass_url: args.os_endpoint_override = args.bypass_url args.os_project_id = (args.os_project_id or args.os_tenant_id) args.os_project_name = (args.os_project_name or args.os_tenant_name) self._ensure_auth_info(args) try: client = { '1': client_v1, }[api_major_version] except KeyError: client = client_v1 args.os_endpoint_type = (args.os_endpoint_type or args.endpoint_type) if args.os_endpoint_type: args.os_interface = args.os_endpoint_type if args.os_interface.endswith('URL'): args.os_interface = args.os_interface[:-3] kwargs = {} if profiler: kwargs["profile"] = args.profile self.cs = client.Client( cloud=args.os_cloud, user_id=args.os_user_id, username=args.os_username, password=args.os_password, auth_token=args.os_token, project_id=args.os_project_id, project_name=args.os_project_name, user_domain_id=args.os_user_domain_id, user_domain_name=args.os_user_domain_name, project_domain_id=args.os_project_domain_id, project_domain_name=args.os_project_domain_name, auth_url=args.os_auth_url, service_type=args.service_type, region_name=args.os_region_name, magnum_url=args.os_endpoint_override, interface=args.os_interface, insecure=args.insecure, api_version=args.magnum_api_version, **kwargs ) self._check_deprecation(args.func, argv) try: args.func(self.cs, args) except (cliutils.DuplicateArgs, cliutils.MissingArgs): self.do_help(args) raise if profiler and args.profile: trace_id = profiler.get().get_base_id() print("To display trace use the command:\n\n" " osprofiler trace show --html %s " % trace_id) def _check_deprecation(self, func, argv): if not hasattr(func, 'deprecated_groups'): return for (old_info, new_info, required) in func.deprecated_groups: old_param = old_info[0][0] new_param = new_info[0][0] old_value, new_value = None, None for i in range(len(argv)): cur_arg = argv[i] if cur_arg == old_param: old_value = argv[i + 1] elif cur_arg == new_param[0]: new_value = argv[i + 1] if old_value and not new_value: print( 'WARNING: The %s parameter is deprecated and will be ' 'removed in a future release. Use the %s parameter to ' 'avoid seeing this message.' % (old_param, new_param)) def _dump_timings(self, timings): class Tyme(object): def __init__(self, url, seconds): self.url = url self.seconds = seconds results = [Tyme(url, end - start) for url, start, end in timings] total = 0.0 for tyme in results: total += tyme.seconds results.append(Tyme("Total", total)) cliutils.print_list(results, ["url", "seconds"], sortby_index=None) def do_bash_completion(self, _args): """Prints arguments for bash-completion. Prints all of the commands and options to stdout so that the magnum.bash_completion script doesn't have to hard code them. """ commands = set() options = set() for sc_str, sc in self.subcommands.items(): commands.add(sc_str) for option in sc._optionals._option_string_actions.keys(): options.add(option) commands.remove('bash-completion') commands.remove('bash_completion') print(' '.join(commands | options)) @cliutils.arg('command', metavar='', nargs='?', help=_('Display help for .')) def do_help(self, args): """Display help about this program or one of its subcommands.""" # NOTE(jamespage): args.command is not guaranteed with python >= 3.4 command = getattr(args, 'command', '') if command: if args.command in self.subcommands: self.subcommands[args.command].print_help() else: raise exc.CommandError("'%s' is not a valid subcommand" % args.command) else: self.parser.print_help() # I'm picky about my shell help. class OpenStackHelpFormatter(argparse.HelpFormatter): def start_section(self, heading): # Title-case the headings heading = '%s%s' % (heading[0].upper(), heading[1:]) super(OpenStackHelpFormatter, self).start_section(heading) def main(): try: OpenStackMagnumShell().main(map(encodeutils.safe_decode, sys.argv[1:])) except Exception as e: logger.debug(e, exc_info=1) print("ERROR: %s" % encodeutils.safe_encode(str(e)), file=sys.stderr) sys.exit(1) if __name__ == "__main__": main() ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740683659.4066951 python_magnumclient-4.8.0/magnumclient/tests/0000775000175000017500000000000000000000000021457 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/__init__.py0000664000175000017500000000000000000000000023556 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/base.py0000664000175000017500000000363600000000000022753 0ustar00zuulzuul00000000000000# -*- coding: utf-8 -*- # Copyright 2010-2011 OpenStack Foundation # 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 os import fixtures import testtools _TRUE_VALUES = ('true', '1', 'yes') class TestCase(testtools.TestCase): """Test case base class for all unit tests.""" def setUp(self): """Run before each test method to initialize test environment.""" super(TestCase, self).setUp() test_timeout = os.environ.get('OS_TEST_TIMEOUT', 60) try: test_timeout = int(test_timeout) except ValueError: # If timeout value is invalid, set a default timeout. test_timeout = 60 if test_timeout <= 0: test_timeout = 60 self.useFixture(fixtures.Timeout(test_timeout, gentle=True)) self.useFixture(fixtures.NestedTempfile()) self.useFixture(fixtures.TempHomeDir()) if os.environ.get('OS_STDOUT_CAPTURE') in _TRUE_VALUES: stdout = self.useFixture(fixtures.StringStream('stdout')).stream self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout)) if os.environ.get('OS_STDERR_CAPTURE') in _TRUE_VALUES: stderr = self.useFixture(fixtures.StringStream('stderr')).stream self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr)) self.log_fixture = self.useFixture(fixtures.FakeLogger()) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740683659.4066951 python_magnumclient-4.8.0/magnumclient/tests/osc/0000775000175000017500000000000000000000000022243 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/osc/__init__.py0000664000175000017500000000000000000000000024342 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740683659.4066951 python_magnumclient-4.8.0/magnumclient/tests/osc/unit/0000775000175000017500000000000000000000000023222 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/osc/unit/__init__.py0000664000175000017500000000000000000000000025321 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/osc/unit/osc_fakes.py0000664000175000017500000001525300000000000025537 0ustar00zuulzuul00000000000000# Copyright 2013 Nebula 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. # from unittest import mock from oslo_serialization import jsonutils import sys from keystoneauth1 import fixture import requests AUTH_TOKEN = "foobar" AUTH_URL = "http://0.0.0.0" USERNAME = "itchy" PASSWORD = "scratchy" PROJECT_NAME = "poochie" REGION_NAME = "richie" INTERFACE = "catchy" VERSION = "3" TEST_RESPONSE_DICT = fixture.V2Token(token_id=AUTH_TOKEN, user_name=USERNAME) _s = TEST_RESPONSE_DICT.add_service('identity', name='keystone') _s.add_endpoint(AUTH_URL + ':5000/v2.0') _s = TEST_RESPONSE_DICT.add_service('network', name='neutron') _s.add_endpoint(AUTH_URL + ':9696') _s = TEST_RESPONSE_DICT.add_service('compute', name='nova') _s.add_endpoint(AUTH_URL + ':8774/v2.1') _s = TEST_RESPONSE_DICT.add_service('image', name='glance') _s.add_endpoint(AUTH_URL + ':9292') _s = TEST_RESPONSE_DICT.add_service('object', name='swift') _s.add_endpoint(AUTH_URL + ':8080/v1') TEST_RESPONSE_DICT_V3 = fixture.V3Token(user_name=USERNAME) TEST_RESPONSE_DICT_V3.set_project_scope() TEST_VERSIONS = fixture.DiscoveryList(href=AUTH_URL) def to_unicode_dict(catalog_dict): """Converts dict to unicode dict """ if isinstance(catalog_dict, dict): return {to_unicode_dict(key): to_unicode_dict(value) for key, value in catalog_dict.items()} elif isinstance(catalog_dict, list): return [to_unicode_dict(element) for element in catalog_dict] elif isinstance(catalog_dict, str): return catalog_dict + u"" else: return catalog_dict 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 class FakeLog(object): def __init__(self): self.messages = {} def debug(self, msg): self.messages['debug'] = msg def info(self, msg): self.messages['info'] = msg def warning(self, msg): self.messages['warning'] = msg def error(self, msg): self.messages['error'] = msg def critical(self, msg): self.messages['critical'] = msg class FakeApp(object): def __init__(self, _stdout, _log): self.stdout = _stdout self.client_manager = None self.stdin = sys.stdin self.stdout = _stdout or sys.stdout self.stderr = sys.stderr self.log = _log class FakeOptions(object): def __init__(self, **kwargs): self.os_beta_command = False class FakeClient(object): def __init__(self, **kwargs): self.endpoint = kwargs['endpoint'] self.token = kwargs['token'] class FakeClientManager(object): _api_version = { 'image': '2', } def __init__(self): self.compute = None self.identity = None self.image = None self.object_store = None self.volume = None self.network = None self.session = None self.auth_ref = None self.auth_plugin_name = None self.network_endpoint_enabled = True def get_configuration(self): return { 'auth': { 'username': USERNAME, 'password': PASSWORD, 'token': AUTH_TOKEN, }, 'region': REGION_NAME, 'identity_api_version': VERSION, } def is_network_endpoint_enabled(self): return self.network_endpoint_enabled class FakeModule(object): def __init__(self, name, version): self.name = name self.__version__ = version # Workaround for openstacksdk case self.version = mock.Mock() self.version.__version__ = version class FakeResource(object): def __init__(self, manager=None, info=None, loaded=False, methods=None): """Set attributes and methods for a resource. :param manager: The resource manager :param Dictionary info: A dictionary with all attributes :param bool loaded: True if the resource is loaded in memory :param Dictionary methods: A dictionary with all methods """ info = info or {} methods = methods or {} self.__name__ = type(self).__name__ self.manager = manager self._info = info self._add_details(info) self._add_methods(methods) self._loaded = loaded def _add_details(self, info): for (k, v) in info.items(): setattr(self, k, v) def _add_methods(self, methods): """Fake methods with MagicMock objects. For each <@key, @value> pairs in methods, add an callable MagicMock object named @key as an attribute, and set the mock's return_value to @value. When users access the attribute with (), @value will be returned, which looks like a function call. """ for (name, ret) in methods.items(): method = mock.Mock(return_value=ret) setattr(self, name, method) def __repr__(self): reprkeys = sorted(k for k in self.__dict__.keys() if k[0] != '_' and k != 'manager') info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys) return "<%s %s>" % (self.__class__.__name__, info) def keys(self): return self._info.keys() def to_dict(self): return self._info @property def info(self): return self._info def __getitem__(self, item): return self._info.get(item) def get(self, item, default=None): return self._info.get(item, default) class FakeResponse(requests.Response): def __init__(self, headers=None, status_code=200, data=None, encoding=None): super(FakeResponse, self).__init__() headers = headers or {} self.status_code = status_code self.headers.update(headers) self._content = jsonutils.dump_as_bytes(data) class FakeModel(dict): def __getattr__(self, key): try: return self[key] except KeyError: raise AttributeError(key) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/osc/unit/osc_utils.py0000664000175000017500000000477300000000000025613 0ustar00zuulzuul00000000000000# Copyright 2012-2013 OpenStack Foundation # Copyright 2013 Nebula 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 fixtures import os import testtools from openstackclient.tests.unit import fakes class ParserException(Exception): pass 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)) def assertNotCalled(self, m, msg=None): """Assert a function was not called""" if m.called: if not msg: msg = 'method %s should not have been called' % m self.fail(msg) 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.fake_log = fakes.FakeLog() self.app = fakes.FakeApp(self.fake_stdout, self.fake_log) self.app.client_manager = fakes.FakeClientManager() self.app.options = fakes.FakeOptions() 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 ParserException("Argument parse failed") for av in verify_args: attr, value = av if attr: self.assertIn(attr, parsed_args) self.assertEqual(value, getattr(parsed_args, attr)) return parsed_args ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740683659.4106948 python_magnumclient-4.8.0/magnumclient/tests/osc/unit/v1/0000775000175000017500000000000000000000000023550 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/osc/unit/v1/__init__.py0000664000175000017500000000000000000000000025647 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/osc/unit/v1/fakes.py0000664000175000017500000002471400000000000025223 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 argparse import copy import datetime import uuid from magnumclient.tests.osc.unit import osc_fakes from magnumclient.tests.osc.unit import osc_utils class FakeBaseModel(object): def __repr__(self): return "<" + self.__class__.model_name + "%s>" % self._info class FakeBaseModelManager(object): def list(self, limit=None, marker=None, sort_key=None, sort_dir=None, detail=False): pass def get(self, id): pass def create(self, **kwargs): pass def delete(self, id): pass def update(self, id, patch): pass def rotate_ca(self, **kwargs): pass class FakeStatsModelManager(object): def list(self, **kwargs): pass class FakeQuotasModelManager(object): def get(self, id, resource): pass def create(self, **kwargs): pass def delete(self, id): pass class FakeNodeGroupManager(object): def list(self, cluster_id, limit=None, marker=None, sort_key=None, sort_dir=None, detail=False): pass def get(self, cluster_id, id): pass def create(self, cluster_id, **kwargs): pass def delete(self, cluster_id, id): pass def update(self, cluster_id, id, patch): pass class FakeCertificatesModelManager(FakeBaseModelManager): def get(self, cluster_uuid): pass class MagnumFakeContainerInfra(object): def __init__(self): self.cluster_templates = FakeBaseModelManager() self.clusters = FakeBaseModelManager() self.mservices = FakeBaseModelManager() self.certificates = FakeCertificatesModelManager() self.stats = FakeStatsModelManager() self.quotas = FakeQuotasModelManager() self.nodegroups = FakeNodeGroupManager() class MagnumFakeClientManager(osc_fakes.FakeClientManager): def __init__(self): super(MagnumFakeClientManager, self).__init__() self.container_infra = MagnumFakeContainerInfra() class MagnumParseException(Exception): """The base exception class for all exceptions this library raises.""" def __init__(self, message=None, details=None): self.message = message or "Argument parse exception" self.details = details or None def __str__(self): return self.message class TestMagnumClientOSCV1(osc_utils.TestCase): def setUp(self): super(TestMagnumClientOSCV1, self).setUp() self.fake_stdout = osc_fakes.FakeStdout() self.fake_log = osc_fakes.FakeLog() self.app = osc_fakes.FakeApp(self.fake_stdout, self.fake_log) self.namespace = argparse.Namespace() self.app.client_manager = MagnumFakeClientManager() 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 MagnumParseException() for av in verify_args: attr, value = av if attr: self.assertIn(attr, parsed_args) self.assertEqual(value, getattr(parsed_args, attr)) return parsed_args class FakeClusterTemplate(object): """Fake one or more ClusterTemplate.""" @staticmethod def create_one_cluster_template(attrs=None): """Create a fake ClusterTemplate. :param Dictionary attrs: A dictionary with all attributes :return: A FakeResource object, with flavor_id, image_id, and so on """ attrs = attrs or {} # set default attributes. ct_info = { 'links': [], 'insecure_registry': None, 'labels': None, 'updated_at': None, 'floating_ip_enabled': True, 'fixed_subnet': None, 'master_flavor_id': None, 'uuid': uuid.uuid4().hex, 'no_proxy': None, 'https_proxy': None, 'tls_disabled': False, 'keypair_id': None, 'public': False, 'http_proxy': None, 'docker_volume_size': None, 'server_type': 'vm', 'external_network_id': 'public', 'cluster_distro': 'fedora-atomic', 'image_id': 'fedora-atomic-latest', 'volume_driver': None, 'registry_enabled': False, 'docker_storage_driver': 'devicemapper', 'apiserver_port': None, 'name': 'fake-ct-' + uuid.uuid4().hex, 'created_at': datetime.datetime.now(), 'network_driver': 'flannel', 'fixed_network': None, 'coe': 'kubernetes', 'flavor_id': 'm1.medium', 'master_lb_enabled': False, 'dns_nameserver': '8.8.8.8', 'project_id': uuid.uuid4().hex, 'hidden': False, 'tags': "", } # Overwrite default attributes. ct_info.update(attrs) ct = osc_fakes.FakeResource(info=copy.deepcopy(ct_info), loaded=True) return ct @staticmethod def create_cluster_templates(attrs=None, count=2): """Create multiple fake cluster templates. :param Dictionary attrs: A dictionary with all attributes :param int count: The number of cluster templates to fake :return: A list of FakeResource objects faking the cluster templates """ cts = [] for i in range(0, count): cts.append(FakeClusterTemplate.create_one_cluster_template(attrs)) return cts class FakeCluster(object): """Fake one or more Cluster.""" @staticmethod def create_one_cluster(attrs=None): """Create a fake Cluster. :param Dictionary attrs: A dictionary with all attributes :return: A FakeResource object, with flavor_id, image_id, and so on """ attrs = attrs or {} # set default attributes. cluster_info = { 'status': 'CREATE_IN_PROGRESS', 'health_status': 'HEALTHY', 'cluster_template_id': 'fake-ct', 'node_addresses': [], 'uuid': '3a369884-b6ba-484f-a206-919b4b718aff', 'stack_id': 'c4554582-77bd-4734-8f1a-72c3c40e5fb4', 'status_reason': None, 'labels': {}, 'labels_overridden': {}, 'labels_added': {}, 'labels_skipped': {}, 'fixed_network': 'fixed-network', 'fixed_subnet': 'fixed-subnet', 'floating_ip_enabled': True, 'created_at': '2017-03-16T18:40:39+00:00', 'updated_at': '2017-03-16T18:40:45+00:00', 'coe_version': None, 'faults': None, 'keypair': 'fakekey', 'api_address': None, 'master_addresses': [], 'create_timeout': 60, 'node_count': 1, 'discovery_url': 'https://fake.cluster', 'docker_volume_size': 1, 'master_count': 1, 'container_version': None, 'name': 'fake-cluster', 'master_flavor_id': None, 'flavor_id': 'm1.medium', 'project_id': None, 'health_status_reason': {'api': 'ok'}, 'master_lb_enabled': False, } # Overwrite default attributes. cluster_info.update(attrs) cluster = osc_fakes.FakeResource(info=copy.deepcopy(cluster_info), loaded=True) return cluster class FakeCert(object): def __init__(self, pem): self.pem = pem class FakeQuota(object): """Fake one or more Quota""" @staticmethod def create_one_quota(attrs=None): """Create a fake quota :param Dictionary attrs: A dictionary with all attributes :return: A FakeResource object, with project_id, resource and so on """ attrs = attrs or {} quota_info = { 'resource': 'Cluster', 'created_at': '2017-09-15T05:40:34+00:00', 'updated_at': '2017-09-15T05:40:34+00:00', 'hard_limit': 1, 'project_id': 'be24b6fba2ed4476b2bd01fa8f0ba74e', 'id': 10, 'name': 'fake-quota', } quota_info.update(attrs) quota = osc_fakes.FakeResource(info=copy.deepcopy(quota_info), loaded=True) return quota class FakeNodeGroup(object): """Fake one or more NodeGroup.""" @staticmethod def create_one_nodegroup(attrs=None): """Create a fake NodeGroup. :param Dictionary attrs: A dictionary with all attributes :return: A FakeResource object, with flavor_id, image_id, and so on """ attrs = attrs or {} # set default attributes. nodegroup_info = { 'created_at': '2017-03-16T18:40:39+00:00', 'updated_at': '2017-03-16T18:40:45+00:00', 'uuid': '3a369884-b6ba-484f-a206-919b4b718aff', 'cluster_id': 'fake-cluster', 'docker_volume_size': None, 'node_addresses': [], 'labels': {}, 'labels_overridden': {}, 'labels_added': {}, 'labels_skipped': {}, 'node_count': 1, 'name': 'fake-nodegroup', 'flavor_id': 'm1.medium', 'image_id': 'fedora-latest', 'project_id': None, 'role': 'worker', 'max_node_count': 10, 'min_node_count': 1, 'is_default': False, 'stack_id': '3a369884-b6ba-484f-fake-stackb718aff', 'status': 'CREATE_COMPLETE', 'status_reason': 'None', 'master_lb_enabled': False, } # Overwrite default attributes. nodegroup_info.update(attrs) nodegroup = osc_fakes.FakeResource(info=copy.deepcopy(nodegroup_info), loaded=True) return nodegroup ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/osc/unit/v1/test_certificates.py0000664000175000017500000001071100000000000027626 0ustar00zuulzuul00000000000000# Copyright 2015 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock from magnumclient.osc.v1 import certificates as osc_certificates from magnumclient.tests.osc.unit.v1 import fakes as magnum_fakes class TestCertificate(magnum_fakes.TestMagnumClientOSCV1): def setUp(self): super(TestCertificate, self).setUp() self.clusters_mock = self.app.client_manager.container_infra.clusters class TestRotateCa(TestCertificate): def setUp(self): super(TestRotateCa, self).setUp() attr = dict() attr['name'] = 'fake-cluster-1' self._cluster = magnum_fakes.FakeCluster.create_one_cluster(attr) self.clusters_mock.get = mock.Mock() self.clusters_mock.get.return_value = self._cluster self.cmd = osc_certificates.RotateCa(self.app, None) def test_rotate_ca(self): arglist = ['fake-cluster'] verifylist = [ ('cluster', 'fake-cluster') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.clusters_mock.get.assert_called_once_with('fake-cluster') def test_rotate_ca_missing_args(self): arglist = [] verifylist = [] self.assertRaises(magnum_fakes.MagnumParseException, self.check_parser, self.cmd, arglist, verifylist) class TestShowCa(TestCertificate): def setUp(self): super(TestShowCa, self).setUp() attr = dict() attr['name'] = 'fake-cluster-1' self._cluster = magnum_fakes.FakeCluster.create_one_cluster(attr) self.clusters_mock.get = mock.Mock() self.clusters_mock.get.return_value = self._cluster self.cmd = osc_certificates.ShowCa(self.app, None) def test_show_ca(self): arglist = ['fake-cluster'] verifylist = [ ('cluster', 'fake-cluster') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.clusters_mock.get.assert_called_once_with('fake-cluster') def test_show_ca_missing_args(self): arglist = [] verifylist = [] self.assertRaises(magnum_fakes.MagnumParseException, self.check_parser, self.cmd, arglist, verifylist) class TestSignCa(TestCertificate): test_csr_path = 'magnumclient/tests/test_csr/test.csr' def setUp(self): super(TestSignCa, self).setUp() attr = dict() attr['name'] = 'fake-cluster-1' self._cluster = magnum_fakes.FakeCluster.create_one_cluster(attr) self.clusters_mock.get = mock.Mock() self.clusters_mock.get.return_value = self._cluster self.cmd = osc_certificates.SignCa(self.app, None) def test_sign_ca(self): arglist = ['fake-cluster', self.test_csr_path] verifylist = [ ('cluster', 'fake-cluster'), ('csr', self.test_csr_path) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.clusters_mock.get.assert_called_once_with('fake-cluster') def test_sign_ca_without_csr(self): arglist = ['fake-cluster'] verifylist = [ ('cluster', 'fake-cluster') ] self.assertRaises(magnum_fakes.MagnumParseException, self.check_parser, self.cmd, arglist, verifylist) def test_sign_ca_without_cluster(self): arglist = [self.test_csr_path] verifylist = [ ('csr', self.test_csr_path) ] self.assertRaises(magnum_fakes.MagnumParseException, self.check_parser, self.cmd, arglist, verifylist) def test_show_ca_missing_args(self): arglist = [] verifylist = [] self.assertRaises(magnum_fakes.MagnumParseException, self.check_parser, self.cmd, arglist, verifylist) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/osc/unit/v1/test_cluster_templates.py0000664000175000017500000003245100000000000030725 0ustar00zuulzuul00000000000000# Copyright 2016 Easystack. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import copy from unittest import mock from unittest.mock import call from magnumclient.exceptions import InvalidAttribute from magnumclient.osc.v1 import cluster_templates as osc_ct from magnumclient.tests.osc.unit.v1 import fakes as magnum_fakes from osc_lib import exceptions as osc_exceptions class TestClusterTemplate(magnum_fakes.TestMagnumClientOSCV1): default_create_args = { 'coe': 'kubernetes', 'dns_nameserver': '8.8.8.8', 'docker_storage_driver': 'overlay2', 'docker_volume_size': None, 'external_network_id': 'public', 'fixed_network': None, 'fixed_subnet': None, 'flavor_id': 'm1.medium', 'http_proxy': None, 'https_proxy': None, 'image_id': 'fedora-atomic-latest', 'keypair_id': None, 'labels': {}, 'master_flavor_id': None, 'master_lb_enabled': False, 'name': 'fake-ct-1', 'network_driver': None, 'no_proxy': None, 'public': False, 'registry_enabled': False, 'insecure_registry': None, 'server_type': 'vm', 'tls_disabled': False, 'volume_driver': None, } def setUp(self): super(TestClusterTemplate, self).setUp() self.cluster_templates_mock = ( self.app.client_manager.container_infra.cluster_templates) class TestClusterTemplateCreate(TestClusterTemplate): def setUp(self): super(TestClusterTemplateCreate, self).setUp() attr = dict() attr['name'] = 'fake-ct-1' self.new_ct = ( magnum_fakes.FakeClusterTemplate.create_one_cluster_template(attr)) self.cluster_templates_mock.create = mock.Mock() self.cluster_templates_mock.create.return_value = self.new_ct self.cluster_templates_mock.get = mock.Mock() self.cluster_templates_mock.get.return_value = copy.deepcopy( self.new_ct) self.cluster_templates_mock.update = mock.Mock() self.cluster_templates_mock.update.return_value = self.new_ct # Get the command object to test self.cmd = osc_ct.CreateClusterTemplate(self.app, None) self.data = tuple(map(lambda x: getattr(self.new_ct, x), osc_ct.CLUSTER_TEMPLATE_ATTRIBUTES)) def test_cluster_template_create_required_args_pass(self): """Verifies required arguments.""" arglist = [ '--coe', self.new_ct.coe, '--external-network', self.new_ct.external_network_id, '--image', self.new_ct.image_id, self.new_ct.name ] verifylist = [ ('coe', self.new_ct.coe), ('external_network', self.new_ct.external_network_id), ('image', self.new_ct.image_id), ('name', self.new_ct.name) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.cluster_templates_mock.create.assert_called_with( **self.default_create_args) def test_cluster_template_create_missing_required_arg(self): """Verifies missing required arguments.""" arglist = [ '--external-network', self.new_ct.external_network_id, '--image', self.new_ct.image_id ] verifylist = [ ('external_network', self.new_ct.external_network_id), ('image', self.new_ct.image_id) ] self.assertRaises(magnum_fakes.MagnumParseException, self.check_parser, self.cmd, arglist, verifylist) # Verify all required args are checked and not just coe arglist.append('--coe') arglist.append(self.new_ct.coe) verifylist.append(('coe', self.new_ct.coe)) arglist.remove('--image') arglist.remove(self.new_ct.image_id) verifylist.remove(('image', self.new_ct.image_id)) self.assertRaises(magnum_fakes.MagnumParseException, self.check_parser, self.cmd, arglist, verifylist) arglist.remove('--external-network') arglist.remove(self.new_ct.external_network_id) verifylist.remove( ('external_network', self.new_ct.external_network_id)) self.assertRaises(magnum_fakes.MagnumParseException, self.check_parser, self.cmd, arglist, verifylist) def test_cluster_template_create_floating_ips(self): """Verifies floating ip parameters.""" arglist = [ '--coe', self.new_ct.coe, '--external-network', self.new_ct.external_network_id, '--image', self.new_ct.image_id, '--floating-ip-enabled', self.new_ct.name ] verifylist = [ ('coe', self.new_ct.coe), ('external_network', self.new_ct.external_network_id), ('image', self.new_ct.image_id), ('floating_ip_enabled', [True]), ('name', self.new_ct.name) ] self.default_create_args['floating_ip_enabled'] = True parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.default_create_args.pop('floating_ip_enabled') arglist.append('--floating-ip-disabled') verifylist.remove(('floating_ip_enabled', [True])) verifylist.append(('floating_ip_enabled', [True, False])) parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaises(InvalidAttribute, self.cmd.take_action, parsed_args) class TestClusterTemplateDelete(TestClusterTemplate): def setUp(self): super(TestClusterTemplateDelete, self).setUp() self.cluster_templates_mock.delete = mock.Mock() self.cluster_templates_mock.delete.return_value = None # Get the command object to test self.cmd = osc_ct.DeleteClusterTemplate(self.app, None) def test_cluster_template_delete_one(self): arglist = ['foo'] verifylist = [('cluster-templates', ['foo'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.cluster_templates_mock.delete.assert_called_with('foo') def test_cluster_template_delete_multiple(self): arglist = ['foo', 'bar'] verifylist = [('cluster-templates', ['foo', 'bar'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.cluster_templates_mock.delete.assert_has_calls([call('foo'), call('bar')]) def test_cluster_template_delete_bad_uuid(self): self.cluster_templates_mock.delete.side_effect = ( osc_exceptions.NotFound(404)) arglist = ['foo'] verifylist = [('cluster-templates', ['foo'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) returns = self.cmd.take_action(parsed_args) self.assertEqual(returns, None) def test_cluster_template_delete_no_uuid(self): arglist = [] verifylist = [('cluster-templates', [])] self.assertRaises(magnum_fakes.MagnumParseException, self.check_parser, self.cmd, arglist, verifylist) class TestClusterTemplateList(TestClusterTemplate): attr = dict() attr['name'] = 'fake-ct-1' _cluster_template = ( magnum_fakes.FakeClusterTemplate.create_one_cluster_template(attr)) attr['name'] = 'fake-ct-2' _cluster_template2 = ( magnum_fakes.FakeClusterTemplate.create_one_cluster_template(attr)) columns = [ 'uuid', 'name', 'tags', ] datalist = ( (_cluster_template.uuid, _cluster_template.name, _cluster_template.tags), (_cluster_template2.uuid, _cluster_template2.name, _cluster_template.tags) ) def setUp(self): super(TestClusterTemplateList, self).setUp() self.cluster_templates_mock.list = mock.Mock() self.cluster_templates_mock.list.return_value = [ self._cluster_template, self._cluster_template2 ] # Get the command object to test self.cmd = osc_ct.ListTemplateCluster(self.app, None) def test_cluster_template_list_no_options(self): arglist = [] verifylist = [ ('limit', None), ('sort_key', None), ('sort_dir', None), ('fields', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.cluster_templates_mock.list.assert_called_with( limit=None, sort_dir=None, sort_key=None, ) self.assertEqual(self.columns, columns) index = 0 for d in data: self.assertEqual(self.datalist[index], d) index += 1 def test_cluster_template_list_options(self): arglist = [ '--limit', '1', '--sort-key', 'key', '--sort-dir', 'asc', '--fields', 'field1,field2' ] verifylist = [ ('limit', 1), ('sort_key', 'key'), ('sort_dir', 'asc'), ('fields', 'field1,field2'), ] verifycolumns = self.columns + ['field1', 'field2'] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.cluster_templates_mock.list.assert_called_with( limit=1, sort_dir='asc', sort_key='key', ) self.assertEqual(verifycolumns, columns) def test_cluster_template_list_bad_sort_dir_fail(self): arglist = [ '--sort-dir', 'foo' ] verifylist = [ ('limit', None), ('sort_key', None), ('sort_dir', 'foo'), ('fields', None), ] self.assertRaises(magnum_fakes.MagnumParseException, self.check_parser, self.cmd, arglist, verifylist) class TestClusterTemplateShow(TestClusterTemplate): attr = dict() attr['name'] = 'fake-ct-1' _cluster_template = ( magnum_fakes.FakeClusterTemplate.create_one_cluster_template(attr)) columns = osc_ct.CLUSTER_TEMPLATE_ATTRIBUTES def setUp(self): super(TestClusterTemplateShow, self).setUp() datalist = map(lambda x: getattr(self._cluster_template, x), self.columns) self.show_data_list = tuple(map(lambda x: x if x is not None else '-', datalist)) self.cluster_templates_mock.get = mock.Mock() self.cluster_templates_mock.get.return_value = self._cluster_template # Get the command object to test self.cmd = osc_ct.ShowClusterTemplate(self.app, None) def test_cluster_template_show(self): arglist = ['fake-ct-1'] verifylist = [('cluster-template', 'fake-ct-1')] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.cluster_templates_mock.get.assert_called_with('fake-ct-1') self.assertEqual(self.columns, columns) self.assertEqual(self.show_data_list, data) def test_cluster_template_show_no_ct_fail(self): arglist = [] verifylist = [] self.assertRaises(magnum_fakes.MagnumParseException, self.check_parser, self.cmd, arglist, verifylist) class TestClusterTemplateUpdate(TestClusterTemplate): def setUp(self): super(TestClusterTemplateUpdate, self).setUp() attr = dict() attr['name'] = 'fake-ct-1' ct = magnum_fakes.FakeClusterTemplate.create_one_cluster_template(attr) self.cluster_templates_mock.update = mock.Mock() self.cluster_templates_mock.update.return_value = ct # Get the command object to test self.cmd = osc_ct.UpdateClusterTemplate(self.app, None) def test_cluster_template_update_pass(self): arglist = ['foo', 'remove', 'bar'] verifylist = [ ('cluster-template', 'foo'), ('op', 'remove'), ('attributes', [['bar']]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.cluster_templates_mock.update.assert_called_with( 'foo', [{'op': 'remove', 'path': '/bar'}] ) def test_cluster_template_update_bad_op(self): arglist = ['foo', 'bar', 'snafu'] verifylist = [ ('cluster-template', 'foo'), ('op', 'bar'), ('attributes', ['snafu']) ] self.assertRaises(magnum_fakes.MagnumParseException, self.check_parser, self.cmd, arglist, verifylist) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/osc/unit/v1/test_clusters.py0000664000175000017500000004265000000000000027034 0ustar00zuulzuul00000000000000# Copyright 2016 Easystack. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import copy from io import StringIO import os import sys import tempfile from unittest import mock from contextlib import contextmanager from unittest.mock import call from magnumclient import exceptions from magnumclient.osc.v1 import clusters as osc_clusters from magnumclient.tests.osc.unit.v1 import fakes as magnum_fakes class TestCluster(magnum_fakes.TestMagnumClientOSCV1): def setUp(self): super(TestCluster, self).setUp() self.clusters_mock = self.app.client_manager.container_infra.clusters self.certificates_mock = \ self.app.client_manager.container_infra.certificates class TestClusterCreate(TestCluster): def setUp(self): super(TestClusterCreate, self).setUp() attr = dict() attr['name'] = 'fake-cluster-1' self._cluster = magnum_fakes.FakeCluster.create_one_cluster(attr) self._default_args = { 'cluster_template_id': 'fake-ct', 'create_timeout': 60, 'discovery_url': None, 'keypair': None, 'master_count': 1, 'name': 'fake-cluster-1', 'node_count': 1, } self.clusters_mock.create = mock.Mock() self.clusters_mock.create.return_value = self._cluster self.clusters_mock.get = mock.Mock() self.clusters_mock.get.return_value = copy.deepcopy(self._cluster) self.clusters_mock.update = mock.Mock() self.clusters_mock.update.return_value = self._cluster # Get the command object to test self.cmd = osc_clusters.CreateCluster(self.app, None) self.data = tuple(map(lambda x: getattr(self._cluster, x), osc_clusters.CLUSTER_ATTRIBUTES)) def test_cluster_create_required_args_pass(self): """Verifies required arguments.""" arglist = [ '--cluster-template', self._cluster.cluster_template_id, self._cluster.name ] verifylist = [ ('cluster_template', self._cluster.cluster_template_id), ('name', self._cluster.name) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.clusters_mock.create.assert_called_with(**self._default_args) def test_cluster_create_missing_required_arg(self): """Verifies missing required arguments.""" arglist = [ self._cluster.name ] verifylist = [ ('name', self._cluster.name) ] self.assertRaises(magnum_fakes.MagnumParseException, self.check_parser, self.cmd, arglist, verifylist) def test_cluster_create_with_labels(self): """Verifies labels are properly parsed when given as argument.""" expected_args = self._default_args expected_args['labels'] = { 'arg1': 'value1', 'arg2': 'value2' } arglist = [ '--cluster-template', self._cluster.cluster_template_id, '--labels', 'arg1=value1', '--labels', 'arg2=value2', self._cluster.name ] verifylist = [ ('cluster_template', self._cluster.cluster_template_id), ('labels', ['arg1=value1', 'arg2=value2']), ('name', self._cluster.name) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.clusters_mock.create.assert_called_with(**expected_args) def test_cluster_create_with_lb_disabled(self): """Verifies master lb disabled properly parsed.""" expected_args = self._default_args expected_args['master_lb_enabled'] = False arglist = [ '--cluster-template', self._cluster.cluster_template_id, '--master-lb-disabled', self._cluster.name ] verifylist = [ ('cluster_template', self._cluster.cluster_template_id), ('master_lb_enabled', [False]), ('name', self._cluster.name) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.clusters_mock.create.assert_called_with(**expected_args) class TestClusterDelete(TestCluster): def setUp(self): super(TestClusterDelete, self).setUp() self.clusters_mock.delete = mock.Mock() self.clusters_mock.delete.return_value = None # Get the command object to test self.cmd = osc_clusters.DeleteCluster(self.app, None) def test_cluster_delete_one(self): arglist = ['foo'] verifylist = [('cluster', ['foo'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.clusters_mock.delete.assert_called_with('foo') def test_cluster_delete_multiple(self): arglist = ['foo', 'bar'] verifylist = [('cluster', ['foo', 'bar'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.clusters_mock.delete.assert_has_calls([call('foo'), call('bar')]) def test_cluster_delete_bad_uuid(self): arglist = ['foo'] verifylist = [('cluster', ['foo'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) returns = self.cmd.take_action(parsed_args) self.assertEqual(returns, None) def test_cluster_delete_no_uuid(self): arglist = [] verifylist = [('cluster', [])] self.assertRaises(magnum_fakes.MagnumParseException, self.check_parser, self.cmd, arglist, verifylist) class TestClusterList(TestCluster): attr = dict() attr['name'] = 'fake-cluster-1' _cluster = magnum_fakes.FakeCluster.create_one_cluster(attr) columns = [ 'uuid', 'name', 'keypair', 'node_count', 'master_count', 'status', 'health_status' ] datalist = ( ( _cluster.uuid, _cluster.name, _cluster.keypair, _cluster.node_count, _cluster.master_count, _cluster.status, _cluster.health_status, ), ) def setUp(self): super(TestClusterList, self).setUp() self.clusters_mock.list = mock.Mock() self.clusters_mock.list.return_value = [self._cluster] # Get the command object to test self.cmd = osc_clusters.ListCluster(self.app, None) def test_cluster_list_no_options(self): arglist = [] verifylist = [ ('limit', None), ('sort_key', None), ('sort_dir', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.clusters_mock.list.assert_called_with( limit=None, sort_dir=None, sort_key=None, ) self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) def test_cluster_list_options(self): arglist = [ '--limit', '1', '--sort-key', 'key', '--sort-dir', 'asc' ] verifylist = [ ('limit', 1), ('sort_key', 'key'), ('sort_dir', 'asc') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.clusters_mock.list.assert_called_with( limit=1, sort_dir='asc', sort_key='key', ) def test_cluster_list_bad_sort_dir_fail(self): arglist = [ '--sort-dir', 'foo' ] verifylist = [ ('limit', None), ('sort_key', None), ('sort_dir', 'foo'), ('fields', None), ] self.assertRaises(magnum_fakes.MagnumParseException, self.check_parser, self.cmd, arglist, verifylist) class TestClusterUpdate(TestCluster): def setUp(self): super(TestClusterUpdate, self).setUp() self.clusters_mock.update = mock.Mock() self.clusters_mock.update.return_value = None # Get the command object to test self.cmd = osc_clusters.UpdateCluster(self.app, None) def test_cluster_update_pass(self): arglist = ['foo', 'remove', 'bar'] verifylist = [ ('cluster', 'foo'), ('op', 'remove'), ('attributes', [['bar']]), ('rollback', False) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.clusters_mock.update.assert_called_with( 'foo', [{'op': 'remove', 'path': '/bar'}] ) def test_cluster_update_bad_op(self): arglist = ['foo', 'bar', 'snafu'] verifylist = [ ('cluster', 'foo'), ('op', 'bar'), ('attributes', ['snafu']), ('rollback', False) ] self.assertRaises(magnum_fakes.MagnumParseException, self.check_parser, self.cmd, arglist, verifylist) class TestClusterShow(TestCluster): def setUp(self): super(TestClusterShow, self).setUp() attr = dict() attr['name'] = 'fake-cluster-1' self._cluster = magnum_fakes.FakeCluster.create_one_cluster(attr) self.clusters_mock.get = mock.Mock() self.clusters_mock.get.return_value = self._cluster # Get the command object to test self.cmd = osc_clusters.ShowCluster(self.app, None) self.data = tuple(map(lambda x: getattr(self._cluster, x), osc_clusters.CLUSTER_ATTRIBUTES)) def test_cluster_show_pass(self): arglist = ['fake-cluster'] verifylist = [ ('cluster', 'fake-cluster') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.clusters_mock.get.assert_called_with('fake-cluster') self.assertEqual(osc_clusters.CLUSTER_ATTRIBUTES, columns) self.assertEqual(self.data, data) def test_cluster_show_no_cluster_fail(self): arglist = [] verifylist = [] self.assertRaises(magnum_fakes.MagnumParseException, self.check_parser, self.cmd, arglist, verifylist) @contextmanager def capture(command, *args, **kwargs): out, sys.stdout = sys.stdout, StringIO() try: command(*args, **kwargs) sys.stdout.seek(0) yield sys.stdout.read() finally: sys.stdout = out class TestClusterConfig(TestCluster): def setUp(self): super(TestClusterConfig, self).setUp() attr = dict() attr['name'] = 'fake-cluster-1' attr['status'] = 'CREATE_COMPLETE' self._cluster = magnum_fakes.FakeCluster.create_one_cluster(attr) self.clusters_mock.get = mock.Mock() self.clusters_mock.get.return_value = self._cluster cert = magnum_fakes.FakeCert(pem='foo bar') self.certificates_mock.create = mock.Mock() self.certificates_mock.create.return_value = cert self.certificates_mock.get = mock.Mock() self.certificates_mock.get.return_value = cert # Fake the cluster_template attr = dict() attr['name'] = 'fake-ct' self._cluster_template = \ magnum_fakes.FakeClusterTemplate.create_one_cluster_template(attr) self.cluster_templates_mock = \ self.app.client_manager.container_infra.cluster_templates self.cluster_templates_mock.get = mock.Mock() self.cluster_templates_mock.get.return_value = self._cluster_template # Get the command object to test self.cmd = osc_clusters.ConfigCluster(self.app, None) def test_cluster_config_no_cluster_fail(self): arglist = [] verifylist = [] self.assertRaises(magnum_fakes.MagnumParseException, self.check_parser, self.cmd, arglist, verifylist) @mock.patch.dict(os.environ, {'SHELL': '/bin/bash'}) def test_cluster_config_custom_dir_with_config_only_works_if_force(self): tmp_dir = tempfile.mkdtemp() open(os.path.join(tmp_dir, 'config'), 'a').close() # touch config arglist = ['fake-cluster', '--dir', tmp_dir] verifylist = [ ('cluster', 'fake-cluster'), ('force', False), ('dir', tmp_dir), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) self.clusters_mock.get.assert_called_with('fake-cluster') arglist = ['fake-cluster', '--force', '--dir', tmp_dir] verifylist = [ ('cluster', 'fake-cluster'), ('force', True), ('dir', tmp_dir), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) expected_value = '''\ export KUBECONFIG={}/config '''.format(tmp_dir) with capture(self.cmd.take_action, parsed_args) as output: self.assertEqual(expected_value, output) self.clusters_mock.get.assert_called_with('fake-cluster') @mock.patch.dict(os.environ, {'SHELL': '/bin/bash'}) def test_cluster_config_with_custom_dir(self): tmp_dir = tempfile.mkdtemp() arglist = ['fake-cluster', '--dir', tmp_dir] verifylist = [ ('cluster', 'fake-cluster'), ('dir', tmp_dir), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) expected_value = '''\ export KUBECONFIG={}/config '''.format(tmp_dir) with capture(self.cmd.take_action, parsed_args) as output: self.assertEqual(expected_value, output) self.clusters_mock.get.assert_called_with('fake-cluster') @mock.patch.dict(os.environ, {'SHELL': '/bin/bash'}) def test_cluster_config_creates_config_in_cwd_if_not_dir_specified(self): tmp_dir = tempfile.mkdtemp() os.chdir(tmp_dir) arglist = ['fake-cluster'] verifylist = [ ('cluster', 'fake-cluster'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) expected_value = '''\ export KUBECONFIG={}/config '''.format(os.getcwd()) with capture(self.cmd.take_action, parsed_args) as output: self.assertEqual(expected_value, output) self.clusters_mock.get.assert_called_with('fake-cluster') class TestClusterResize(TestCluster): def setUp(self): super(TestClusterResize, self).setUp() self.cluster = mock.Mock() self.cluster.uuid = "UUID1" self.clusters_mock.resize = mock.Mock() self.clusters_mock.resize.return_value = None self.clusters_mock.get = mock.Mock() self.clusters_mock.get.return_value = self.cluster # Get the command object to test self.cmd = osc_clusters.ResizeCluster(self.app, None) def test_cluster_resize_pass(self): arglist = ['foo', '2'] verifylist = [ ('cluster', 'foo'), ('node_count', 2), ('nodes_to_remove', None), ('nodegroup', None) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.clusters_mock.resize.assert_called_with( "UUID1", 2, None, None ) def test_cluster_resize_to_zero_pass(self): arglist = ['foo', '0'] verifylist = [ ('cluster', 'foo'), ('node_count', 0), ('nodes_to_remove', None), ('nodegroup', None) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.clusters_mock.resize.assert_called_with( "UUID1", 0, None, None ) class TestClusterUpgrade(TestCluster): def setUp(self): super(TestClusterUpgrade, self).setUp() self.cluster = mock.Mock() self.cluster.uuid = "UUID1" self.clusters_mock.upgrade = mock.Mock() self.clusters_mock.upgrade.return_value = None self.clusters_mock.get = mock.Mock() self.clusters_mock.get.return_value = self.cluster # Get the command object to test self.cmd = osc_clusters.UpgradeCluster(self.app, None) def test_cluster_upgrade_pass(self): cluster_template_id = 'TEMPLATE_ID' arglist = ['foo', cluster_template_id] verifylist = [ ('cluster', 'foo'), ('cluster_template', cluster_template_id), ('max_batch_size', 1), ('nodegroup', None) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.clusters_mock.upgrade.assert_called_with( "UUID1", cluster_template_id, 1, None ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/osc/unit/v1/test_mservices.py0000664000175000017500000000332100000000000027160 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 magnumclient.osc.v1 import mservices from magnumclient.tests.osc.unit.v1 import fakes class TestServiceList(fakes.TestMagnumClientOSCV1): columns = ('id', 'host', 'binary', 'state', 'disabled', 'disabled_reason', 'created_at', 'updated_at') def setUp(self): super(TestServiceList, self).setUp() self.mservices_mock = self.app.client_manager.container_infra.mservices self.mservices_mock.list = mock.Mock() fake_service = mock.Mock( Binary='magnum-conductor', Host='Host1', Status='enabled', State='up', Updated_at=None, Disabled_Reason=None, ) fake_service.name = 'test_service' self.mservices_mock.list.return_value = [fake_service] # Get the command object to test self.cmd = mservices.ListService(self.app, None) def test_service_list(self): arglist = [] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.mservices_mock.list.assert_called_with() self.assertEqual(self.columns, columns) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/osc/unit/v1/test_nodegroups.py0000664000175000017500000002466400000000000027362 0ustar00zuulzuul00000000000000# Copyright (c) 2018 European Organization for Nuclear Research. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from unittest import mock from unittest.mock import call from magnumclient.osc.v1 import nodegroups as osc_nodegroups from magnumclient.tests.osc.unit.v1 import fakes as magnum_fakes class TestNodeGroup(magnum_fakes.TestMagnumClientOSCV1): def setUp(self): super(TestNodeGroup, self).setUp() self.ng_mock = self.app.client_manager.container_infra.nodegroups class TestNodeGroupCreate(TestNodeGroup): def setUp(self): super(TestNodeGroupCreate, self).setUp() self.nodegroup = magnum_fakes.FakeNodeGroup.create_one_nodegroup() self.ng_mock.create = mock.Mock() self.ng_mock.create.return_value = self.nodegroup self.ng_mock.get = mock.Mock() self.ng_mock.get.return_value = copy.deepcopy(self.nodegroup) self.ng_mock.update = mock.Mock() self.ng_mock.update.return_value = self.nodegroup self._default_args = { 'name': 'fake-nodegroup', 'node_count': 1, 'role': 'worker', 'min_node_count': 0, 'max_node_count': None, } # Get the command object to test self.cmd = osc_nodegroups.CreateNodeGroup(self.app, None) self.data = tuple(map(lambda x: getattr(self.nodegroup, x), osc_nodegroups.NODEGROUP_ATTRIBUTES)) def test_nodegroup_create_required_args_pass(self): """Verifies required arguments.""" arglist = [ self.nodegroup.cluster_id, self.nodegroup.name ] verifylist = [ ('cluster', self.nodegroup.cluster_id), ('name', self.nodegroup.name) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.ng_mock.create.assert_called_with(self.nodegroup.cluster_id, **self._default_args) def test_nodegroup_create_missing_required_arg(self): """Verifies missing required arguments.""" arglist = [ self.nodegroup.name ] verifylist = [ ('name', self.nodegroup.name) ] self.assertRaises(magnum_fakes.MagnumParseException, self.check_parser, self.cmd, arglist, verifylist) def test_nodegroup_create_with_labels(self): """Verifies labels are properly parsed when given as argument.""" expected_args = self._default_args expected_args['labels'] = { 'arg1': 'value1', 'arg2': 'value2' } arglist = [ '--labels', 'arg1=value1', '--labels', 'arg2=value2', self.nodegroup.cluster_id, self.nodegroup.name ] verifylist = [ ('labels', ['arg1=value1', 'arg2=value2']), ('name', self.nodegroup.name), ('cluster', self.nodegroup.cluster_id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.ng_mock.create.assert_called_with(self.nodegroup.cluster_id, **expected_args) class TestNodeGroupDelete(TestNodeGroup): def setUp(self): super(TestNodeGroupDelete, self).setUp() self.ng_mock.delete = mock.Mock() self.ng_mock.delete.return_value = None # Get the command object to test self.cmd = osc_nodegroups.DeleteNodeGroup(self.app, None) def test_nodegroup_delete_one(self): arglist = ['foo', 'fake-nodegroup'] verifylist = [ ('cluster', 'foo'), ('nodegroup', ['fake-nodegroup']) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.ng_mock.delete.assert_called_with('foo', 'fake-nodegroup') def test_nodegroup_delete_multiple(self): arglist = ['foo', 'fake-nodegroup1', 'fake-nodegroup2'] verifylist = [ ('cluster', 'foo'), ('nodegroup', ['fake-nodegroup1', 'fake-nodegroup2']) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.ng_mock.delete.assert_has_calls( [call('foo', 'fake-nodegroup1'), call('foo', 'fake-nodegroup2')] ) def test_nodegroup_delete_no_args(self): arglist = [] verifylist = [ ('cluster', ''), ('nodegroup', []) ] self.assertRaises(magnum_fakes.MagnumParseException, self.check_parser, self.cmd, arglist, verifylist) class TestNodeGroupUpdate(TestNodeGroup): def setUp(self): super(TestNodeGroupUpdate, self).setUp() self.ng_mock.update = mock.Mock() self.ng_mock.update.return_value = None # Get the command object to test self.cmd = osc_nodegroups.UpdateNodeGroup(self.app, None) def test_nodegroup_update_pass(self): arglist = ['foo', 'ng1', 'remove', 'bar'] verifylist = [ ('cluster', 'foo'), ('nodegroup', 'ng1'), ('op', 'remove'), ('attributes', [['bar']]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.ng_mock.update.assert_called_with( 'foo', 'ng1', [{'op': 'remove', 'path': '/bar'}] ) def test_nodegroup_update_bad_op(self): arglist = ['cluster', 'ng1', 'foo', 'bar'] verifylist = [ ('cluster', 'cluster'), ('nodegroup', 'ng1'), ('op', 'foo'), ('attributes', ['bar']) ] self.assertRaises(magnum_fakes.MagnumParseException, self.check_parser, self.cmd, arglist, verifylist) class TestNodeGroupShow(TestNodeGroup): def setUp(self): super(TestNodeGroupShow, self).setUp() self.nodegroup = magnum_fakes.FakeNodeGroup.create_one_nodegroup() self.ng_mock.get = mock.Mock() self.ng_mock.get.return_value = self.nodegroup self.data = tuple(map(lambda x: getattr(self.nodegroup, x), osc_nodegroups.NODEGROUP_ATTRIBUTES)) # Get the command object to test self.cmd = osc_nodegroups.ShowNodeGroup(self.app, None) def test_nodegroup_show_pass(self): arglist = ['fake-cluster', 'fake-nodegroup'] verifylist = [ ('cluster', 'fake-cluster'), ('nodegroup', 'fake-nodegroup') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.ng_mock.get.assert_called_with( 'fake-cluster', 'fake-nodegroup') self.assertEqual(osc_nodegroups.NODEGROUP_ATTRIBUTES, columns) self.assertEqual(self.data, data) def test_nodegroup_show_no_nodegroup_fail(self): arglist = ['fake-cluster'] verifylist = [ ('cluster', 'fake-cluster'), ('nodegroup', '') ] self.assertRaises(magnum_fakes.MagnumParseException, self.check_parser, self.cmd, arglist, verifylist) def test_nodegroup_show_no_args(self): arglist = [] verifylist = [ ('cluster', ''), ('nodegroup', '') ] self.assertRaises(magnum_fakes.MagnumParseException, self.check_parser, self.cmd, arglist, verifylist) class TestNodeGroupList(TestNodeGroup): nodegroup = magnum_fakes.FakeNodeGroup.create_one_nodegroup() columns = ['uuid', 'name', 'flavor_id', 'image_id', 'node_count', 'status', 'role'] datalist = ( ( nodegroup.uuid, nodegroup.name, nodegroup.flavor_id, nodegroup.image_id, nodegroup.node_count, nodegroup.status, nodegroup.role, ), ) def setUp(self): super(TestNodeGroupList, self).setUp() self.ng_mock.list = mock.Mock() self.ng_mock.list.return_value = [self.nodegroup] # Get the command object to test self.cmd = osc_nodegroups.ListNodeGroup(self.app, None) def test_nodegroup_list_no_options(self): arglist = [] verifylist = [ ('cluster', ''), ('limit', None), ('sort_key', None), ('sort_dir', None), ] self.assertRaises(magnum_fakes.MagnumParseException, self.check_parser, self.cmd, arglist, verifylist) def test_nodegroup_list_ok(self): arglist = ['fake-cluster'] verifylist = [ ('cluster', 'fake-cluster'), ('limit', None), ('sort_key', None), ('sort_dir', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.ng_mock.list.assert_called_with( 'fake-cluster', limit=None, sort_dir=None, sort_key=None, role=None, ) self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) def test_nodegroup_list_options(self): arglist = [ 'fake-cluster', '--limit', '1', '--sort-key', 'key', '--sort-dir', 'asc' ] verifylist = [ ('cluster', 'fake-cluster'), ('limit', 1), ('sort_key', 'key'), ('sort_dir', 'asc') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.ng_mock.list.assert_called_with( 'fake-cluster', limit=1, sort_dir='asc', sort_key='key', role=None ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/osc/unit/v1/test_quotas.py0000664000175000017500000002350200000000000026477 0ustar00zuulzuul00000000000000# Copyright 2015 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock from magnumclient.osc.v1 import quotas as osc_quotas from magnumclient.tests.osc.unit.v1 import fakes as magnum_fakes class TestQuotas(magnum_fakes.TestMagnumClientOSCV1): def setUp(self): super(TestQuotas, self).setUp() self.quotas_mock = self.app.client_manager.container_infra.quotas class TestQuotasCreate(TestQuotas): def setUp(self): super(TestQuotasCreate, self).setUp() attr = dict() attr['name'] = 'fake-quota' attr['project_id'] = 'abc' attr['resource'] = 'Cluster' self._quota = magnum_fakes.FakeQuota.create_one_quota(attr) self._default_args = { 'project_id': 'abc', 'resource': 'Cluster', 'hard_limit': 1 } self.quotas_mock.create = mock.Mock() self.quotas_mock.create.return_value = self._quota self.cmd = osc_quotas.CreateQuotas(self.app, None) self.data = tuple(map(lambda x: getattr(self._quota, x), osc_quotas.QUOTA_ATTRIBUTES)) def test_quotas_create(self): arglist = [ '--project-id', 'abc', '--resource', 'Cluster' ] verifylist = [ ('project_id', 'abc'), ('resource', 'Cluster') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.quotas_mock.create.assert_called_with(**self._default_args) def test_quotas_create_with_hardlimit(self): arglist = [ '--project-id', 'abc', '--resource', 'Cluster', '--hard-limit', '10' ] verifylist = [ ('project_id', 'abc'), ('resource', 'Cluster'), ('hard_limit', 10) ] self._default_args['hard_limit'] = 10 parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.quotas_mock.create.assert_called_with(**self._default_args) def test_quotas_create_wrong_projectid(self): arglist = ['abcd'] verifylist = [ ('project_id', 'abcd') ] self.assertRaises(magnum_fakes.MagnumParseException, self.check_parser, self.cmd, arglist, verifylist) def test_quotas_create_missing_args(self): arglist = [] verifylist = [] self.assertRaises(magnum_fakes.MagnumParseException, self.check_parser, self.cmd, arglist, verifylist) def test_quotas_create_with_wrong_args(self): arglist = [ '--project-id', 'abc', '--resources', 'Cluster', # Misspelling 'resources' '--hard-limit', '10' ] verifylist = [] self.assertRaises(magnum_fakes.MagnumParseException, self.check_parser, self.cmd, arglist, verifylist) class TestQuotasDelete(TestQuotas): def setUp(self): super(TestQuotasDelete, self).setUp() self.quotas_mock.delete = mock.Mock() self.quotas_mock.delete.return_value = None self.cmd = osc_quotas.DeleteQuotas(self.app, None) def test_quotas_delete(self): arglist = [ '--project-id', 'abc', '--resource', 'Cluster' ] verifylist = [ ('project_id', 'abc'), ('resource', 'Cluster') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.quotas_mock.delete.assert_called_with('abc', 'Cluster') def test_quotas_delete_no_project_id(self): arglist = [ '--resource', 'Cluster' ] verifylist = [ ('resource', 'Cluster') ] self.assertRaises(magnum_fakes.MagnumParseException, self.check_parser, self.cmd, arglist, verifylist) def test_quotas_delete_no_resource(self): arglist = [ '--project-id', 'abc', ] verifylist = [ ('project_id', 'abc') ] self.assertRaises(magnum_fakes.MagnumParseException, self.check_parser, self.cmd, arglist, verifylist) def test_quotas_delete_missing_args(self): arglist = [] verifylist = [] self.assertRaises(magnum_fakes.MagnumParseException, self.check_parser, self.cmd, arglist, verifylist) def test_quotas_delete_wrong_args(self): arglist = [ '--project-ids', 'abc', # Misspelling 'ids' instead of 'id' '--resource', 'Cluster' ] verifylist = [] self.assertRaises(magnum_fakes.MagnumParseException, self.check_parser, self.cmd, arglist, verifylist) class TestQuotasShow(TestQuotas): def setUp(self): super(TestQuotasShow, self).setUp() attr = dict() attr['name'] = 'fake-quota' attr['project_id'] = 'abc' attr['resource'] = 'Cluster' self._quota = magnum_fakes.FakeQuota.create_one_quota(attr) self._default_args = { 'project_id': 'abc', 'resource': 'Cluster', } self.quotas_mock.get = mock.Mock() self.quotas_mock.get.return_value = self._quota self.cmd = osc_quotas.ShowQuotas(self.app, None) self.data = tuple(map(lambda x: getattr(self._quota, x), osc_quotas.QUOTA_ATTRIBUTES)) def test_quotas_show(self): arglist = [ '--project-id', 'abc', '--resource', 'Cluster' ] verifylist = [ ('project_id', 'abc'), ('resource', 'Cluster') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.quotas_mock.get.assert_called_with('abc', 'Cluster') def test_quotas_show_missing_args(self): arglist = [] verifylist = [] self.assertRaises(magnum_fakes.MagnumParseException, self.check_parser, self.cmd, arglist, verifylist) class TestQuotasUpdate(TestQuotas): def setUp(self): super(TestQuotasUpdate, self).setUp() attr = dict() attr['name'] = 'fake-quota' attr['project_id'] = 'abc' attr['resource'] = 'Cluster' self._quota = magnum_fakes.FakeQuota.create_one_quota(attr) self._default_args = { 'project_id': 'abc', 'resource': 'Cluster', 'hard_limit': 10 } self.quotas_mock.update = mock.Mock() self.quotas_mock.update.return_value = self._quota self.cmd = osc_quotas.UpdateQuotas(self.app, None) def test_quotas_update(self): arglist = [ '--project-id', 'abc', '--resource', 'Cluster', '--hard-limit', '10' ] verifylist = [ ('project_id', 'abc'), ('resource', 'Cluster'), ('hard_limit', 10) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.quotas_mock.update.assert_called_with('abc', 'Cluster', self._default_args) def test_quotas_update_missing_args(self): arglist = ['abcd'] verifylist = [ ('project_id', 'abcd') ] self.assertRaises(magnum_fakes.MagnumParseException, self.check_parser, self.cmd, arglist, verifylist) def test_quotas_update_wrong_args(self): arglist = [ '--project-id', 'abc', '--resource', 'Cluster', '--hard-limits', '10' # Misspelling 'hard-limits' ] verifylist = [ ('project_id', 'abc'), ('resource', 'Cluster'), ('hard_limit', 10) ] self.assertRaises(magnum_fakes.MagnumParseException, self.check_parser, self.cmd, arglist, verifylist) class TestQuotasList(TestQuotas): def setUp(self): super(TestQuotasList, self).setUp() attr = dict() attr['name'] = 'fake-quota' attr['project_id'] = 'abc' attr['resource'] = 'Cluster' self._quota = magnum_fakes.FakeQuota.create_one_quota(attr) self.quotas_mock.list = mock.Mock() self.quotas_mock.list.return_value = [self._quota] self.cmd = osc_quotas.ListQuotas(self.app, None) def test_quotas_list_with_no_options(self): arglist = [ ] verifylist = [ ('limit', None), ('sort_key', None), ('sort_dir', None), ('marker', None), ('all_tenants', False) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.quotas_mock.list.assert_called_with( limit=None, sort_dir=None, sort_key=None, marker=None, all_tenants=False ) def test_quotas_list_wrong_args(self): arglist = ['--wrong'] verifylist = [ ('wrong') ] self.assertRaises(magnum_fakes.MagnumParseException, self.check_parser, self.cmd, arglist, verifylist) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/osc/unit/v1/test_stats.py0000664000175000017500000000444300000000000026324 0ustar00zuulzuul00000000000000# Copyright 2015 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock from magnumclient.osc.v1 import stats as osc_stats from magnumclient.tests.osc.unit.v1 import fakes as magnum_fakes class TestStats(magnum_fakes.TestMagnumClientOSCV1): def setUp(self): super(TestStats, self).setUp() self.clusters_mock = self.app.client_manager.container_infra.stats class TestStatsList(TestStats): def setUp(self): super(TestStatsList, self).setUp() attr = dict() attr['name'] = 'fake-cluster-1' attr['project_id'] = 'abc' attr['node_count'] = 2 attr['master_count'] = 1 self._cluster = magnum_fakes.FakeCluster.create_one_cluster(attr) self.clusters_mock.list = mock.Mock() self.clusters_mock.list.return_value = self._cluster self.cmd = osc_stats.ListStats(self.app, None) def test_stats_list(self): arglist = ['abc'] verifylist = [ ('project_id', 'abc') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.clusters_mock.list.assert_called_once_with(project_id='abc') def test_stats_list_wrong_projectid(self): arglist = ['abcd'] verifylist = [ ('project_id', 'abcd') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.clusters_mock.list.assert_called_once_with(project_id='abcd') def test_stats_list_missing_args(self): arglist = [] verifylist = [] self.assertRaises(magnum_fakes.MagnumParseException, self.check_parser, self.cmd, arglist, verifylist) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/test_client.py0000664000175000017500000000267600000000000024361 0ustar00zuulzuul00000000000000# Copyright (c) 2015 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import testtools from unittest import mock from magnumclient import client class ClientTest(testtools.TestCase): @mock.patch('magnumclient.v1.client.Client') def test_no_version_argument(self, mock_magnum_client): client.Client(auth_token='mytoken', magnum_url='http://myurl/') mock_magnum_client.assert_called_with( auth_token='mytoken', magnum_url='http://myurl/') @mock.patch('magnumclient.v1.client.Client') def test_valid_version_argument(self, mock_magnum_client): client.Client(version='1', magnum_url='http://myurl/') mock_magnum_client.assert_called_with(magnum_url='http://myurl/') @mock.patch('magnumclient.v1.client.Client') def test_invalid_version_argument(self, mock_magnum_client): self.assertRaises( ValueError, client.Client, version='2', magnum_url='http://myurl/') ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740683659.4106948 python_magnumclient-4.8.0/magnumclient/tests/test_csr/0000775000175000017500000000000000000000000023305 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/test_csr/test.csr0000664000175000017500000000001300000000000024767 0ustar00zuulzuul00000000000000'fake-csr' ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/test_httpclient.py0000664000175000017500000004504700000000000025260 0ustar00zuulzuul00000000000000# Copyright 2015 OpenStack LLC. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from http import client as http_client import io from unittest import mock from oslo_serialization import jsonutils import socket from magnumclient.common.apiclient.exceptions import GatewayTimeout from magnumclient.common.apiclient.exceptions import MultipleChoices from magnumclient.common import httpclient as http from magnumclient import exceptions as exc from magnumclient.tests import utils NORMAL_ERROR = 0 ERROR_DICT = 1 ERROR_LIST_WITH_DETAIL = 2 ERROR_LIST_WITH_DESC = 3 def _get_error_body(faultstring=None, debuginfo=None, err_type=NORMAL_ERROR): if err_type == NORMAL_ERROR: error_body = { 'faultstring': faultstring, 'debuginfo': debuginfo } raw_error_body = jsonutils.dumps(error_body) body = {'error_message': raw_error_body} elif err_type == ERROR_DICT: body = {'error': {'title': faultstring, 'message': debuginfo}} elif err_type == ERROR_LIST_WITH_DETAIL: main_body = {'title': faultstring, 'detail': debuginfo} body = {'errors': [main_body]} elif err_type == ERROR_LIST_WITH_DESC: main_body = {'title': faultstring, 'description': debuginfo} body = {'errors': [main_body]} raw_body = jsonutils.dumps(body) return raw_body HTTP_CLASS = http_client.HTTPConnection HTTPS_CLASS = http.VerifiedHTTPSConnection DEFAULT_TIMEOUT = 600 class HttpClientTest(utils.BaseTestCase): def test_url_generation_trailing_slash_in_base(self): client = http.HTTPClient('http://localhost/') url = client._make_connection_url('/v1/resources') self.assertEqual('/v1/resources', url) def test_url_generation_without_trailing_slash_in_base(self): client = http.HTTPClient('http://localhost') url = client._make_connection_url('/v1/resources') self.assertEqual('/v1/resources', url) def test_url_generation_prefix_slash_in_path(self): client = http.HTTPClient('http://localhost/') url = client._make_connection_url('/v1/resources') self.assertEqual('/v1/resources', url) def test_url_generation_without_prefix_slash_in_path(self): client = http.HTTPClient('http://localhost') url = client._make_connection_url('v1/resources') self.assertEqual('/v1/resources', url) def test_server_exception_empty_body(self): error_body = _get_error_body() fake_resp = utils.FakeResponse({'content-type': 'application/json'}, io.StringIO(error_body), version=1, status=500) client = http.HTTPClient('http://localhost/') client.get_connection = ( lambda *a, **kw: utils.FakeConnection(fake_resp)) error = self.assertRaises(exc.InternalServerError, client.json_request, 'GET', '/v1/resources') self.assertEqual('Internal Server Error (HTTP 500)', str(error)) def test_server_exception_msg_only(self): error_msg = 'test error msg' error_body = _get_error_body(error_msg, err_type=ERROR_DICT) fake_resp = utils.FakeResponse({'content-type': 'application/json'}, io.StringIO(error_body), version=1, status=500) client = http.HTTPClient('http://localhost/') client.get_connection = ( lambda *a, **kw: utils.FakeConnection(fake_resp)) error = self.assertRaises(exc.InternalServerError, client.json_request, 'GET', '/v1/resources') self.assertEqual(error_msg + ' (HTTP 500)', str(error)) def test_server_exception_msg_and_traceback(self): error_msg = 'another test error' error_trace = ("\"Traceback (most recent call last):\\n\\n " "File \\\"/usr/local/lib/python2.7/...") error_body = _get_error_body(error_msg, error_trace, ERROR_LIST_WITH_DESC) fake_resp = utils.FakeResponse({'content-type': 'application/json'}, io.StringIO(error_body), version=1, status=500) client = http.HTTPClient('http://localhost/') client.get_connection = ( lambda *a, **kw: utils.FakeConnection(fake_resp)) error = self.assertRaises(exc.InternalServerError, client.json_request, 'GET', '/v1/resources') self.assertEqual( '%(error)s (HTTP 500)\n%(trace)s' % {'error': error_msg, 'trace': error_trace}, "%(error)s\n%(details)s" % {'error': str(error), 'details': str(error.details)}) def test_server_exception_address(self): endpoint = 'https://magnum-host:6385' client = http.HTTPClient(endpoint, token='foobar', insecure=True, ca_file='/path/to/ca_file') client.get_connection = ( lambda *a, **kw: utils.FakeConnection(exc=socket.gaierror)) self.assertRaises(exc.EndpointNotFound, client.json_request, 'GET', '/v1/resources', body='farboo') def test_server_exception_socket(self): client = http.HTTPClient('http://localhost/', token='foobar') client.get_connection = ( lambda *a, **kw: utils.FakeConnection(exc=socket.error)) self.assertRaises(exc.ConnectionRefused, client.json_request, 'GET', '/v1/resources') def test_server_exception_endpoint(self): endpoint = 'https://magnum-host:6385' client = http.HTTPClient(endpoint, token='foobar', insecure=True, ca_file='/path/to/ca_file') client.get_connection = ( lambda *a, **kw: utils.FakeConnection(exc=socket.gaierror)) self.assertRaises(exc.EndpointNotFound, client.json_request, 'GET', '/v1/resources', body='farboo') def test_get_connection(self): endpoint = 'https://magnum-host:6385' client = http.HTTPClient(endpoint) conn = client.get_connection() self.assertTrue(conn, http.VerifiedHTTPSConnection) def test_get_connection_exception(self): endpoint = 'http://magnum-host:6385/' expected = (HTTP_CLASS, ('magnum-host', 6385, ''), {'timeout': DEFAULT_TIMEOUT}) params = http.HTTPClient.get_connection_params(endpoint) self.assertEqual(expected, params) def test_get_connection_params_with_ssl(self): endpoint = 'https://magnum-host:6385' expected = (HTTPS_CLASS, ('magnum-host', 6385, ''), { 'timeout': DEFAULT_TIMEOUT, 'ca_file': None, 'cert_file': None, 'key_file': None, 'insecure': False, }) params = http.HTTPClient.get_connection_params(endpoint) self.assertEqual(expected, params) def test_get_connection_params_with_ssl_params(self): endpoint = 'https://magnum-host:6385' ssl_args = { 'ca_file': '/path/to/ca_file', 'cert_file': '/path/to/cert_file', 'key_file': '/path/to/key_file', 'insecure': True, } expected_kwargs = {'timeout': DEFAULT_TIMEOUT} expected_kwargs.update(ssl_args) expected = (HTTPS_CLASS, ('magnum-host', 6385, ''), expected_kwargs) params = http.HTTPClient.get_connection_params(endpoint, **ssl_args) self.assertEqual(expected, params) def test_get_connection_params_with_timeout(self): endpoint = 'http://magnum-host:6385' expected = (HTTP_CLASS, ('magnum-host', 6385, ''), {'timeout': 300.0}) params = http.HTTPClient.get_connection_params(endpoint, timeout=300) self.assertEqual(expected, params) def test_get_connection_params_with_version(self): endpoint = 'http://magnum-host:6385/v1' expected = (HTTP_CLASS, ('magnum-host', 6385, ''), {'timeout': DEFAULT_TIMEOUT}) params = http.HTTPClient.get_connection_params(endpoint) self.assertEqual(expected, params) def test_get_connection_params_with_version_trailing_slash(self): endpoint = 'http://magnum-host:6385/v1/' expected = (HTTP_CLASS, ('magnum-host', 6385, ''), {'timeout': DEFAULT_TIMEOUT}) params = http.HTTPClient.get_connection_params(endpoint) self.assertEqual(expected, params) def test_get_connection_params_with_subpath(self): endpoint = 'http://magnum-host:6385/magnum' expected = (HTTP_CLASS, ('magnum-host', 6385, '/magnum'), {'timeout': DEFAULT_TIMEOUT}) params = http.HTTPClient.get_connection_params(endpoint) self.assertEqual(expected, params) def test_get_connection_params_with_subpath_trailing_slash(self): endpoint = 'http://magnum-host:6385/magnum/' expected = (HTTP_CLASS, ('magnum-host', 6385, '/magnum'), {'timeout': DEFAULT_TIMEOUT}) params = http.HTTPClient.get_connection_params(endpoint) self.assertEqual(expected, params) def test_get_connection_params_with_subpath_version(self): endpoint = 'http://magnum-host:6385/magnum/v1' expected = (HTTP_CLASS, ('magnum-host', 6385, '/magnum'), {'timeout': DEFAULT_TIMEOUT}) params = http.HTTPClient.get_connection_params(endpoint) self.assertEqual(expected, params) def test_get_connection_params_with_subpath_version_trailing_slash(self): endpoint = 'http://magnum-host:6385/magnum/v1/' expected = (HTTP_CLASS, ('magnum-host', 6385, '/magnum'), {'timeout': DEFAULT_TIMEOUT}) params = http.HTTPClient.get_connection_params(endpoint) self.assertEqual(expected, params) def test_get_connection_params_with_unsupported_scheme(self): endpoint = 'foo://magnum-host:6385/magnum/v1/' self.assertRaises(exc.EndpointException, http.HTTPClient.get_connection_params, endpoint) def test_401_unauthorized_exception(self): error_body = _get_error_body(err_type=ERROR_LIST_WITH_DETAIL) fake_resp = utils.FakeResponse({'content-type': 'text/plain'}, io.StringIO(error_body), version=1, status=401) client = http.HTTPClient('http://localhost/') client.get_connection = (lambda *a, **kw: utils.FakeConnection(fake_resp)) self.assertRaises(exc.Unauthorized, client.json_request, 'GET', '/v1/resources') def test_server_redirect_exception(self): fake_redirect_resp = utils.FakeResponse( {'content-type': 'application/octet-stream'}, 'foo', version=1, status=301) fake_resp = utils.FakeResponse( {'content-type': 'application/octet-stream'}, 'bar', version=1, status=300) client = http.HTTPClient('http://localhost/') conn = utils.FakeConnection(fake_redirect_resp, redirect_resp=fake_resp) client.get_connection = (lambda *a, **kw: conn) self.assertRaises(MultipleChoices, client.json_request, 'GET', '/v1/resources') def test_server_body_undecode_json(self): err = "foo" fake_resp = utils.FakeResponse( {'content-type': 'application/json'}, io.StringIO(err), version=1, status=200) client = http.HTTPClient('http://localhost/') conn = utils.FakeConnection(fake_resp) client.get_connection = (lambda *a, **kw: conn) resp, body = client.json_request('GET', '/v1/resources') self.assertEqual(resp, fake_resp) self.assertEqual(err, body) def test_server_success_body_app(self): fake_resp = utils.FakeResponse( {'content-type': 'application/octet-stream'}, 'bar', version=1, status=200) client = http.HTTPClient('http://localhost/') conn = utils.FakeConnection(fake_resp) client.get_connection = (lambda *a, **kw: conn) resp, body = client.json_request('GET', '/v1/resources') self.assertEqual(resp, fake_resp) self.assertIsNone(body) def test_server_success_body_none(self): fake_resp = utils.FakeResponse( {'content-type': None}, io.StringIO('bar'), version=1, status=200) client = http.HTTPClient('http://localhost/') conn = utils.FakeConnection(fake_resp) client.get_connection = (lambda *a, **kw: conn) resp, body = client.json_request('GET', '/v1/resources') self.assertEqual(resp, fake_resp) self.assertIsInstance(body, list) def test_server_success_body_json(self): err = _get_error_body() fake_resp = utils.FakeResponse( {'content-type': 'application/json'}, io.StringIO(err), version=1, status=200) client = http.HTTPClient('http://localhost/') conn = utils.FakeConnection(fake_resp) client.get_connection = (lambda *a, **kw: conn) resp, body = client.json_request('GET', '/v1/resources') self.assertEqual(resp, fake_resp) self.assertEqual(jsonutils.dumps(body), err) def test_raw_request(self): fake_resp = utils.FakeResponse( {'content-type': 'application/octet-stream'}, 'bar', version=1, status=200) client = http.HTTPClient('http://localhost/') conn = utils.FakeConnection(fake_resp) client.get_connection = (lambda *a, **kw: conn) resp, body = client.raw_request('GET', '/v1/resources') self.assertEqual(resp, fake_resp) self.assertIsInstance(body, http.ResponseBodyIterator) class SessionClientTest(utils.BaseTestCase): def test_server_exception_msg_and_traceback(self): error_msg = 'another test error' error_trace = ("\"Traceback (most recent call last):\\n\\n " "File \\\"/usr/local/lib/python2.7/...") error_body = _get_error_body(error_msg, error_trace) fake_session = utils.FakeSession({'Content-Type': 'application/json'}, error_body, 500) client = http.SessionClient(session=fake_session) error = self.assertRaises(exc.InternalServerError, client.json_request, 'GET', '/v1/resources') self.assertEqual( '%(error)s (HTTP 500)\n%(trace)s' % {'error': error_msg, 'trace': error_trace}, "%(error)s\n%(details)s" % {'error': str(error), 'details': str(error.details)}) def test_server_exception_empty_body(self): error_body = _get_error_body() fake_session = utils.FakeSession({'Content-Type': 'application/json'}, error_body, 500) client = http.SessionClient(session=fake_session) error = self.assertRaises(exc.InternalServerError, client.json_request, 'GET', '/v1/resources') self.assertEqual('Internal Server Error (HTTP 500)', str(error)) def test_bypass_url(self): fake_response = utils.FakeSessionResponse( {}, content="", status_code=201) fake_session = mock.MagicMock() fake_session.request.side_effect = [fake_response] client = http.SessionClient( session=fake_session, endpoint_override='http://magnum') client.json_request('GET', '/v1/clusters') self.assertEqual( fake_session.request.call_args[1]['endpoint_override'], 'http://magnum' ) def test_exception(self): fake_response = utils.FakeSessionResponse( {}, content="", status_code=504) fake_session = mock.MagicMock() fake_session.request.side_effect = [fake_response] client = http.SessionClient( session=fake_session, endpoint_override='http://magnum') self.assertRaises(GatewayTimeout, client.json_request, 'GET', '/v1/resources') def test_construct_http_client_return_httpclient(self): client = http._construct_http_client('http://localhost/') self.assertIsInstance(client, http.HTTPClient) def test_construct_http_client_return_sessionclient(self): fake_session = mock.MagicMock() client = http._construct_http_client(session=fake_session) self.assertIsInstance(client, http.SessionClient) def test_raw_request(self): fake_response = utils.FakeSessionResponse( {'content-type': 'application/octet-stream'}, content="", status_code=200) fake_session = mock.MagicMock() fake_session.request.side_effect = [fake_response] client = http.SessionClient( session=fake_session, endpoint_override='http://magnum') resp, resp_body = client.raw_request('GET', '/v1/clusters') self.assertEqual( fake_session.request.call_args[1]['headers']['Content-Type'], 'application/octet-stream' ) self.assertEqual(None, resp_body) self.assertEqual(fake_response, resp) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/test_magnumclient.py0000664000175000017500000000142300000000000025553 0ustar00zuulzuul00000000000000# -*- coding: utf-8 -*- # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ test_magnumclient ---------------------------------- Tests for `magnumclient` module. """ from magnumclient.tests import base class TestMagnumclient(base.TestCase): def test_something(self): pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/test_utils.py0000664000175000017500000002330400000000000024232 0ustar00zuulzuul00000000000000# -*- coding: utf-8 -*- # # Copyright 2013 OpenStack LLC. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import builtins import collections from unittest import mock from oslo_serialization import jsonutils import tempfile from magnumclient.common import cliutils from magnumclient.common import utils from magnumclient import exceptions as exc from magnumclient.tests import utils as test_utils class CommonFiltersTest(test_utils.BaseTestCase): def test_limit(self): result = utils.common_filters(limit=42) self.assertEqual(['limit=42'], result) def test_limit_0(self): result = utils.common_filters(limit=0) self.assertEqual(['limit=0'], result) def test_limit_negative_number(self): result = utils.common_filters(limit=-2) self.assertEqual(['limit=-2'], result) def test_other(self): for key in ('marker', 'sort_key', 'sort_dir'): result = utils.common_filters(**{key: 'test'}) self.assertEqual(['%s=test' % key], result) class SplitAndDeserializeTest(test_utils.BaseTestCase): def test_split_and_deserialize(self): ret = utils.split_and_deserialize('str=foo') self.assertEqual(('str', 'foo'), ret) ret = utils.split_and_deserialize('int=1') self.assertEqual(('int', 1), ret) ret = utils.split_and_deserialize('bool=false') self.assertEqual(('bool', False), ret) ret = utils.split_and_deserialize('list=[1, "foo", 2]') self.assertEqual(('list', [1, "foo", 2]), ret) ret = utils.split_and_deserialize('dict={"foo": 1}') self.assertEqual(('dict', {"foo": 1}), ret) ret = utils.split_and_deserialize('str_int="1"') self.assertEqual(('str_int', "1"), ret) def test_split_and_deserialize_fail(self): self.assertRaises(exc.CommandError, utils.split_and_deserialize, 'foo:bar') class ArgsArrayToPatchTest(test_utils.BaseTestCase): def test_args_array_to_patch(self): my_args = { 'attributes': ['str=foo', 'int=1', 'bool=true', 'list=[1, 2, 3]', 'dict={"foo": "bar"}'], 'op': 'add', } patch = utils.args_array_to_patch(my_args['op'], my_args['attributes']) self.assertEqual([{'op': 'add', 'value': 'foo', 'path': '/str'}, {'op': 'add', 'value': 1, 'path': '/int'}, {'op': 'add', 'value': True, 'path': '/bool'}, {'op': 'add', 'value': [1, 2, 3], 'path': '/list'}, {'op': 'add', 'value': {"foo": "bar"}, 'path': '/dict'}], patch) def test_args_array_to_patch_format_error(self): my_args = { 'attributes': ['foobar'], 'op': 'add', } self.assertRaises(exc.CommandError, utils.args_array_to_patch, my_args['op'], my_args['attributes']) def test_args_array_to_patch_remove(self): my_args = { 'attributes': ['/foo', 'extra/bar'], 'op': 'remove', } patch = utils.args_array_to_patch(my_args['op'], my_args['attributes']) self.assertEqual([{'op': 'remove', 'path': '/foo'}, {'op': 'remove', 'path': '/extra/bar'}], patch) def test_args_array_to_patch_invalid_op(self): my_args = { 'attributes': ['/foo', 'extra/bar'], 'op': 'invalid', } self.assertRaises(exc.CommandError, utils.args_array_to_patch, my_args['op'], my_args['attributes']) class FormatLabelsTest(test_utils.BaseTestCase): def test_format_label_none(self): self.assertEqual({}, utils.format_labels(None)) def test_format_labels(self): la = utils.format_labels([ 'K1=V1,K2=V2,' 'K3=V3,K4=V4,' 'K5=V5']) self.assertEqual({'K1': 'V1', 'K2': 'V2', 'K3': 'V3', 'K4': 'V4', 'K5': 'V5' }, la) def test_format_labels_semicolon(self): la = utils.format_labels([ 'K1=V1;K2=V2;' 'K3=V3;K4=V4;' 'K5=V5']) self.assertEqual({'K1': 'V1', 'K2': 'V2', 'K3': 'V3', 'K4': 'V4', 'K5': 'V5' }, la) def test_format_labels_mix_commas_semicolon(self): la = utils.format_labels([ 'K1=V1,K2=V2,' 'K3=V3;K4=V4,' 'K5=V5']) self.assertEqual({'K1': 'V1', 'K2': 'V2', 'K3': 'V3', 'K4': 'V4', 'K5': 'V5' }, la) def test_format_labels_split(self): la = utils.format_labels([ 'K1=V1,' 'K2=V22222222222222222222222222222' '222222222222222222222222222,' 'K3=3.3.3.3']) self.assertEqual({'K1': 'V1', 'K2': 'V22222222222222222222222222222' '222222222222222222222222222', 'K3': '3.3.3.3'}, la) def test_format_labels_multiple(self): la = utils.format_labels([ 'K1=V1', 'K2=V22222222222222222222222222222' '222222222222222222222222222', 'K3=3.3.3.3']) self.assertEqual({'K1': 'V1', 'K2': 'V22222222222222222222222222222' '222222222222222222222222222', 'K3': '3.3.3.3'}, la) def test_format_labels_multiple_colon_values(self): la = utils.format_labels([ 'K1=V1', 'K2=V2,V22,V222,V2222', 'K3=3.3.3.3']) self.assertEqual({'K1': 'V1', 'K2': 'V2,V22,V222,V2222', 'K3': '3.3.3.3'}, la) def test_format_labels_parse_comma_false(self): la = utils.format_labels( ['K1=V1,K2=2.2.2.2,K=V'], parse_comma=False) self.assertEqual({'K1': 'V1,K2=2.2.2.2,K=V'}, la) def test_format_labels_multiple_values_per_labels(self): la = utils.format_labels([ 'K1=V1', 'K1=V2']) self.assertEqual({'K1': 'V1,V2'}, la) def test_format_label_special_label(self): labels = ['K1=V1,K22.2.2.2'] la = utils.format_labels( labels, parse_comma=True) self.assertEqual({'K1': 'V1,K22.2.2.2'}, la) def test_format_multiple_bad_label(self): labels = ['K1=V1', 'K22.2.2.2'] ex = self.assertRaises(exc.CommandError, utils.format_labels, labels) self.assertEqual('labels must be a list of KEY=VALUE ' 'not K22.2.2.2', str(ex)) class CliUtilsTest(test_utils.BaseTestCase): def test_keys_and_vals_to_strs(self): dict_in = {'a': '1', 'b': {'x': 1, 'y': '2', 'z': '3'}, 'c': 7} dict_exp = collections.OrderedDict([ ('a', '1'), ('b', collections.OrderedDict([ ('x', 1), ('y', '2'), ('z', '3')])), ('c', 7)]) dict_out = cliutils.keys_and_vals_to_strs(dict_in) dict_act = collections.OrderedDict([ ('a', dict_out['a']), ('b', collections.OrderedDict(sorted(dict_out['b'].items()))), ('c', dict_out['c'])]) self.assertEqual(str(dict_exp), str(dict_act)) class HandleJsonFromFileTest(test_utils.BaseTestCase): def test_handle_json_from_file_bad_json(self): contents = 'foo' with tempfile.NamedTemporaryFile(mode='w') as f: f.write(contents) f.flush() self.assertRaisesRegex(exc.InvalidAttribute, 'For JSON', utils.handle_json_from_file, f.name) def test_handle_json_from_file_valid_file(self): contents = '{"step": "upgrade", "interface": "deploy"}' with tempfile.NamedTemporaryFile(mode='w') as f: f.write(contents) f.flush() steps = utils.handle_json_from_file(f.name) self.assertEqual(jsonutils.loads(contents), steps) @mock.patch.object(builtins, 'open', autospec=True) def test_handle_json_from_file_open_fail(self, mock_open): mock_file_object = mock.MagicMock() mock_file_handle = mock.MagicMock() mock_file_handle.__enter__.return_value = mock_file_object mock_open.return_value = mock_file_handle mock_file_object.read.side_effect = IOError with tempfile.NamedTemporaryFile(mode='w') as f: self.assertRaisesRegex(exc.InvalidAttribute, "from file", utils.handle_json_from_file, f.name) mock_open.assert_called_once_with(f.name, 'r') mock_file_object.read.assert_called_once_with() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/utils.py0000664000175000017500000001425200000000000023175 0ustar00zuulzuul00000000000000# Copyright 2012 OpenStack LLC. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy import datetime import io import os from oslo_serialization import jsonutils import queue import sys import fixtures import testtools from magnumclient.common import httpclient as http from magnumclient import shell FAKE_ENV = {'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_PROJECT_NAME': 'project_name', 'OS_AUTH_URL': 'http://no.where/v2.0'} class BaseTestCase(testtools.TestCase): def setUp(self): super(BaseTestCase, self).setUp() self.useFixture(fixtures.FakeLogger()) class FakeAPI(object): def __init__(self, responses): self.responses = responses self.calls = [] def _request(self, method, url, headers=None, body=None): call = (method, url, headers or {}, body) self.calls.append(call) return self.responses[url][method] def raw_request(self, *args, **kwargs): response = self._request(*args, **kwargs) body_iter = http.ResponseBodyIterator(io.StringIO(response[1])) return FakeResponse(response[0]), body_iter def json_request(self, *args, **kwargs): response = self._request(*args, **kwargs) return FakeResponse(response[0]), response[1] class FakeConnection(object): def __init__(self, response=None, **kwargs): self._response = queue.Queue() self._response.put(response) self._last_request = None self._exc = kwargs['exc'] if 'exc' in kwargs else None if 'redirect_resp' in kwargs: self._response.put(kwargs['redirect_resp']) def request(self, method, conn_url, **kwargs): self._last_request = (method, conn_url, kwargs) if self._exc: raise self._exc def setresponse(self, response): self._response = response def getresponse(self): return self._response.get() class FakeResponse(object): def __init__(self, headers, body=None, version=None, status=None, reason=None): """Fake object to help testing. :param headers: dict representing HTTP response headers :param body: file-like object """ self.headers = headers self.body = body self.version = version self.status = status self.reason = reason def __getitem__(self, key): if key == 'location': return 'fake_url' else: return None def getheaders(self): return copy.deepcopy(self.headers).items() def getheader(self, key, default): return self.headers.get(key, default) def read(self, amt): return self.body.read(amt) class FakeServiceCatalog(object): def url_for(self, endpoint_type, service_type, attr=None, filter_value=None): if attr == 'region' and filter_value: return 'http://regionhost:6385/v1/f14b41234' else: return 'http://localhost:6385/v1/f14b41234' class FakeKeystone(object): service_catalog = FakeServiceCatalog() timestamp = datetime.datetime.utcnow() + datetime.timedelta(days=5) def __init__(self, auth_token): self.auth_token = auth_token self.auth_ref = { 'token': {'expires': FakeKeystone.timestamp.strftime( '%Y-%m-%dT%H:%M:%S.%f'), 'id': 'd1a541311782870742235'} } class TestCase(testtools.TestCase): TEST_REQUEST_BASE = { 'verify': True, } def setUp(self): super(TestCase, self).setUp() if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or os.environ.get('OS_STDOUT_CAPTURE') == '1'): stdout = self.useFixture(fixtures.StringStream('stdout')).stream self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout)) if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or os.environ.get('OS_STDERR_CAPTURE') == '1'): stderr = self.useFixture(fixtures.StringStream('stderr')).stream self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr)) def make_env(self, exclude=None, fake_env=FAKE_ENV): env = dict((k, v) for k, v in fake_env.items() if k != exclude) self.useFixture(fixtures.MonkeyPatch('os.environ', env)) def shell(self, argstr, exitcodes=(0,)): orig = sys.stdout orig_stderr = sys.stderr try: sys.stdout = io.StringIO() sys.stderr = io.StringIO() _shell = shell.OpenStackMagnumShell() _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) class FakeSessionResponse(object): def __init__(self, headers, content=None, status_code=None): self.headers = headers self.content = content self.status_code = status_code def json(self): if self.content is not None: return jsonutils.loads(self.content) else: return {} class FakeSession(object): def __init__(self, headers, content=None, status_code=None): self.headers = headers self.content = content self.status_code = status_code def request(self, url, method, **kwargs): return FakeSessionResponse(self.headers, self.content, self.status_code) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740683659.4146948 python_magnumclient-4.8.0/magnumclient/tests/v1/0000775000175000017500000000000000000000000022005 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/v1/__init__.py0000664000175000017500000000000000000000000024104 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/v1/shell_test_base.py0000664000175000017500000000640300000000000025522 0ustar00zuulzuul00000000000000# Copyright 2015 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import re from unittest import mock from testtools import matchers from magnumclient.tests import utils FAKE_ENV = {'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_PROJECT_NAME': 'project_name', 'OS_AUTH_URL': 'http://no.where/v2.0', 'BYPASS_URL': 'http://magnum'} class TestCommandLineArgument(utils.TestCase): _unrecognized_arg_error = [ r'.*?^usage: ', r'.*?^error: unrecognized arguments:', r".*?^Try 'magnum help ' for more information.", ] _mandatory_group_arg_error = [ r'.*?^usage: ', r'.*?^error: one of the arguments', r".*?^Try 'magnum help ", ] _too_many_group_arg_error = [ r'.*?^usage: ', r'.*?^error: (argument \-\-[a-z\-]*: not allowed with argument )', r".*?^Try 'magnum help ", ] _mandatory_arg_error = [ r'.*?^usage: ', r'.*?^error: (the following arguments|argument)', r".*?^Try 'magnum help ", ] _duplicate_arg_error = [ r'.*?^usage: ', r'.*?^error: (Duplicate "<.*>" arguments:)', r".*?^Try 'magnum help ", ] _deprecated_warning = [ r'.*(WARNING: The \-\-[a-z\-]* parameter is deprecated)+', (r'.*(Use the [\<\-a-z\-\>]* (positional )*parameter to avoid seeing ' 'this message)+') ] _few_argument_error = [ r'.*?^usage: magnum ', r'.*?^error: (the following arguments|too few arguments)', r".*?^Try 'magnum help ", ] _invalid_value_error = [ r'.*?^usage: ', r'.*?^error: argument .*: invalid .* value:', r".*?^Try 'magnum help ", ] def setUp(self): super(TestCommandLineArgument, self).setUp() self.make_env(fake_env=FAKE_ENV) session_client = mock.patch( 'magnumclient.common.httpclient.SessionClient') session_client.start() loader = mock.patch('keystoneauth1.loading.get_plugin_loader') loader.start() session = mock.patch('keystoneauth1.session.Session') session.start() self.addCleanup(session_client.stop) self.addCleanup(loader.stop) self.addCleanup(session.stop) def _test_arg_success(self, command, keyword=None): stdout, stderr = self.shell(command) if keyword: self.assertIn(keyword, (stdout + stderr)) def _test_arg_failure(self, command, error_msg): stdout, stderr = self.shell(command, (2,)) for line in error_msg: self.assertThat((stdout + stderr), matchers.MatchesRegex(line, re.DOTALL | re.MULTILINE)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/v1/test_certificates.py0000664000175000017500000000574300000000000026074 0ustar00zuulzuul00000000000000# Copyright 2015 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy import testtools from magnumclient import exceptions from magnumclient.tests import utils from magnumclient.v1 import certificates CERT1 = { 'cluster_uuid': '5d12f6fd-a196-4bf0-ae4c-1f639a523a53', 'pem': 'fake-pem' } CERT2 = { 'cluster_uuid': '5d12f6fd-a196-4bf0-ae4c-1f639a523a53', 'pem': 'fake-pem', 'csr': 'fake-csr', } CREATE_CERT = {'cluster_uuid': '5d12f6fd-a196-4bf0-ae4c-1f639a523a53', 'csr': 'fake-csr'} fake_responses = { '/v1/certificates': { 'POST': ( {}, CERT2, ) }, '/v1/certificates/%s' % CERT1['cluster_uuid']: { 'GET': ( {}, CERT1 ), 'PATCH': ( {}, None, ) } } class CertificateManagerTest(testtools.TestCase): def setUp(self): super(CertificateManagerTest, self).setUp() self.api = utils.FakeAPI(fake_responses) self.mgr = certificates.CertificateManager(self.api) def test_cert_show_by_id(self): cert = self.mgr.get(CERT1['cluster_uuid']) expect = [ ('GET', '/v1/certificates/%s' % CERT1['cluster_uuid'], {}, None) ] self.assertEqual(expect, self.api.calls) self.assertEqual(CERT1['cluster_uuid'], cert.cluster_uuid) self.assertEqual(CERT1['pem'], cert.pem) def test_cert_create(self): cert = self.mgr.create(**CREATE_CERT) expect = [ ('POST', '/v1/certificates', {}, CREATE_CERT), ] self.assertEqual(expect, self.api.calls) self.assertEqual(CERT2['cluster_uuid'], cert.cluster_uuid) self.assertEqual(CERT2['pem'], cert.pem) self.assertEqual(CERT2['csr'], cert.csr) def test_create_fail(self): create_cert_fail = copy.deepcopy(CREATE_CERT) create_cert_fail["wrong_key"] = "wrong" self.assertRaisesRegex(exceptions.InvalidAttribute, ("Key must be in %s" % ','.join(certificates.CREATION_ATTRIBUTES)), self.mgr.create, **create_cert_fail) self.assertEqual([], self.api.calls) def test_rotate_ca(self): self.mgr.rotate_ca(cluster_uuid=CERT1['cluster_uuid']) expect = [ ('PATCH', '/v1/certificates/%s' % CERT1['cluster_uuid'], {}, None) ] self.assertEqual(expect, self.api.calls) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/v1/test_certificates_shell.py0000664000175000017500000001315200000000000027254 0ustar00zuulzuul00000000000000# Copyright 2015 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock from magnumclient.common import cliutils from magnumclient.tests.v1 import shell_test_base from magnumclient.v1 import certificates_shell class ShellTest(shell_test_base.TestCommandLineArgument): @mock.patch('magnumclient.v1.clusters.ClusterManager.get') @mock.patch('magnumclient.v1.certificates.CertificateManager.get') def test_cluster_ca_show_success(self, mock_cert_get, mock_cluster_get): mockcluster = mock.MagicMock() mockcluster.status = "CREATE_COMPLETE" mockcluster.uuid = "xxx" mock_cluster_get.return_value = mockcluster self._test_arg_success('ca-show xxx') expected_args = {} expected_args['cluster_uuid'] = mockcluster.uuid mock_cert_get.assert_called_once_with(**expected_args) @mock.patch('magnumclient.v1.clusters.ClusterManager.get') @mock.patch('magnumclient.v1.certificates.CertificateManager.get') def test_ca_show_failure_duplicate_arg(self, mock_cert_get, mock_cluster_get): self.assertRaises(cliutils.DuplicateArgs, self._test_arg_failure, 'ca-show foo --cluster foo', self._duplicate_arg_error) mock_cert_get.assert_not_called() mock_cluster_get.assert_not_called() @mock.patch('os.path.isfile') @mock.patch('magnumclient.v1.clusters.ClusterManager.get') @mock.patch('magnumclient.v1.certificates.CertificateManager.create') def test_cluster_ca_sign_success( self, mock_cert_create, mock_cluster_get, mock_isfile): mock_isfile.return_value = True mockcluster = mock.MagicMock() mockcluster.status = "CREATE_COMPLETE" mockcluster.uuid = "xxx" mock_cluster_get.return_value = mockcluster fake_csr = 'fake-csr' mock_file = mock.mock_open(read_data=fake_csr) with mock.patch.object(certificates_shell, 'open', mock_file): self._test_arg_success('ca-sign ' '--csr path/csr.pem ' '--cluster xxx') expected_args = {} expected_args['cluster_uuid'] = mockcluster.uuid expected_args['csr'] = fake_csr mock_cert_create.assert_called_once_with(**expected_args) @mock.patch('os.path.isfile') @mock.patch('magnumclient.v1.clusters.ClusterManager.get') @mock.patch('magnumclient.v1.certificates.CertificateManager.create') def test_cluster_ca_sign_with_not_csr( self, mock_cert_create, mock_cluster_get, mock_isfile): mock_isfile.return_value = False mockcluster = mock.MagicMock() mockcluster.status = "CREATE_COMPLETE" mock_cluster_get.return_value = mockcluster fake_csr = 'fake-csr' mock_file = mock.mock_open(read_data=fake_csr) with mock.patch.object(certificates_shell, 'open', mock_file): self._test_arg_success('ca-sign ' '--csr path/csr.pem ' '--cluster xxx') mock_isfile.assert_called_once_with('path/csr.pem') mock_file.assert_not_called() mock_cert_create.assert_not_called() @mock.patch('magnumclient.v1.clusters.ClusterManager.get') @mock.patch('magnumclient.v1.certificates.CertificateManager.get') def test_ca_show_failure_with_invalid_field(self, mock_cert_get, mock_cluster_get): self.assertRaises(cliutils.MissingArgs, self._test_arg_failure, 'ca-show', self._few_argument_error) mock_cert_get.assert_not_called() mock_cluster_get.assert_not_called() @mock.patch('magnumclient.v1.clusters.ClusterManager.get') @mock.patch('magnumclient.v1.certificates.CertificateManager.rotate_ca') def test_ca_rotate(self, mock_rotate_ca, mock_cluster_get): mockcluster = mock.MagicMock() mockcluster.status = "CREATE_COMPLETE" mockcluster.uuid = "xxx" mock_cluster_get.return_value = mockcluster mock_rotate_ca.return_value = None self._test_arg_success('ca-rotate ' '--cluster xxx') expected_args = {} expected_args['cluster_uuid'] = mockcluster.uuid mock_rotate_ca.assert_called_once_with(**expected_args) @mock.patch('magnumclient.v1.clusters.ClusterManager.get') @mock.patch('magnumclient.v1.certificates.CertificateManager.rotate_ca') def test_ca_rotate_no_cluster_arg(self, mock_rotate_ca, mock_cluster_get): _error_msg = [ (".*(error: argument --cluster is required|" # py27 compatibility "error: the following arguments are required: --cluster).*"), ".*Try 'magnum help ca-rotate' for more information.*" ] self._test_arg_failure('ca-rotate', _error_msg) mock_rotate_ca.assert_not_called() mock_cluster_get.assert_not_called() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/v1/test_client.py0000664000175000017500000002073500000000000024703 0ustar00zuulzuul00000000000000# Copyright (c) 2015 Thales Services SAS # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 keystoneauth1.exceptions import catalog from magnumclient.v1 import client class ClientInitializeTest(testtools.TestCase): def _load_session_kwargs(self): return { 'username': None, 'project_id': None, 'project_name': None, 'auth_url': None, 'password': None, 'auth_type': 'password', 'insecure': False, 'user_domain_id': None, 'user_domain_name': None, 'project_domain_id': None, 'project_domain_name': None, 'auth_token': None, 'timeout': 600, } def _load_service_type_kwargs(self): return { 'interface': 'public', 'region_name': None, 'service_name': None, 'service_type': 'container-infra', } def _session_client_kwargs(self, session): kwargs = self._load_service_type_kwargs() kwargs['endpoint_override'] = None kwargs['session'] = session kwargs['api_version'] = None return kwargs @mock.patch('magnumclient.common.httpclient.SessionClient') @mock.patch('magnumclient.v1.client._load_session') @mock.patch('magnumclient.v1.client._load_service_type', return_value='container-infra') def test_init_with_session(self, mock_load_service_type, mock_load_session, mock_http_client): session = mock.Mock() client.Client(session=session) mock_load_session.assert_not_called() mock_load_service_type.assert_called_once_with( session, **self._load_service_type_kwargs() ) mock_http_client.assert_called_once_with( **self._session_client_kwargs(session) ) def _test_init_with_secret(self, init_func, mock_load_service_type, mock_load_session, mock_http_client,): expected_password = 'expected_password' session = mock.Mock() mock_load_session.return_value = session init_func(expected_password) load_session_args = self._load_session_kwargs() load_session_args['password'] = expected_password mock_load_session.assert_called_once_with( **load_session_args ) mock_load_service_type.assert_called_once_with( session, **self._load_service_type_kwargs() ) mock_http_client.assert_called_once_with( **self._session_client_kwargs(session) ) @mock.patch('magnumclient.common.httpclient.SessionClient') @mock.patch('magnumclient.v1.client._load_session') @mock.patch('magnumclient.v1.client._load_service_type', return_value='container-infra') def test_init_with_password(self, mock_load_service_type, mock_load_session, mock_http_client): self._test_init_with_secret( lambda x: client.Client(password=x), mock_load_service_type, mock_load_session, mock_http_client ) @mock.patch('magnumclient.common.httpclient.SessionClient') @mock.patch('magnumclient.v1.client._load_session') @mock.patch('magnumclient.v1.client._load_service_type', return_value='container-infra') def test_init_with_api_key(self, mock_load_service_type, mock_load_session, mock_http_client): self._test_init_with_secret( lambda x: client.Client(api_key=x), mock_load_service_type, mock_load_session, mock_http_client ) @mock.patch('magnumclient.common.httpclient.HTTPClient') def test_init_with_auth_token(self, mock_http_client,): expected_token = 'expected_password' expected_magnum_url = 'expected_magnum_url' expected_api_version = 'expected_api_version' expected_insecure = False expected_timeout = 600 expected_kwargs = {'expected_key': 'expected_value'} client.Client(auth_token=expected_token, magnum_url=expected_magnum_url, api_version=expected_api_version, timeout=expected_timeout, insecure=expected_insecure, **expected_kwargs) mock_http_client.assert_called_once_with( expected_magnum_url, token=expected_token, api_version=expected_api_version, timeout=expected_timeout, insecure=expected_insecure, **expected_kwargs) def _test_init_with_interface(self, init_func, mock_load_service_type, mock_load_session, mock_http_client): expected_interface = 'admin' session = mock.Mock() mock_load_session.return_value = session init_func(expected_interface) mock_load_session.assert_called_once_with( **self._load_session_kwargs() ) expected_kwargs = self._load_service_type_kwargs() expected_kwargs['interface'] = expected_interface mock_load_service_type.assert_called_once_with( session, **expected_kwargs ) expected_kwargs = self._session_client_kwargs(session) expected_kwargs['interface'] = expected_interface mock_http_client.assert_called_once_with( **expected_kwargs ) @mock.patch('magnumclient.common.httpclient.SessionClient') @mock.patch('magnumclient.v1.client._load_session') @mock.patch('magnumclient.v1.client._load_service_type', return_value='container-infra') def test_init_with_interface(self, mock_load_service_type, mock_load_session, mock_http_client): self._test_init_with_interface( lambda x: client.Client(interface=x), mock_load_service_type, mock_load_session, mock_http_client ) @mock.patch('magnumclient.common.httpclient.SessionClient') @mock.patch('magnumclient.v1.client._load_session') @mock.patch('magnumclient.v1.client._load_service_type', return_value='container-infra') def test_init_with_endpoint_type(self, mock_load_service_type, mock_load_session, mock_http_client): self._test_init_with_interface( lambda x: client.Client(interface='public', endpoint_type=('%sURL' % x)), mock_load_service_type, mock_load_session, mock_http_client ) @mock.patch('magnumclient.common.httpclient.SessionClient') @mock.patch('magnumclient.v1.client._load_session') def test_init_with_legacy_service_type(self, mock_load_session, mock_http_client): session = mock.Mock() mock_load_session.return_value = session session.get_endpoint.side_effect = [ catalog.EndpointNotFound(), mock.Mock() ] client.Client(username='myuser', auth_url='authurl') expected_kwargs = self._session_client_kwargs(session) expected_kwargs['service_type'] = 'container' mock_http_client.assert_called_once_with( **expected_kwargs ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/v1/test_clusters.py0000664000175000017500000003113200000000000025262 0ustar00zuulzuul00000000000000# Copyright 2015 IBM Corp # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy import testtools from testtools import matchers from magnumclient import exceptions from magnumclient.tests import utils from magnumclient.v1 import clusters CLUSTER1 = {'id': 123, 'uuid': '66666666-7777-8888-9999-000000000001', 'name': 'cluster1', 'cluster_template_id': 'e74c40e0-d825-11e2-a28f-0800200c9a61', 'stack_id': '5d12f6fd-a196-4bf0-ae4c-1f639a523a51', 'api_address': '172.17.2.1', 'node_addresses': ['172.17.2.3'], 'node_count': 2, 'master_count': 1, } CLUSTER2 = {'id': 124, 'uuid': '66666666-7777-8888-9999-000000000002', 'name': 'cluster2', 'cluster_template_id': 'e74c40e0-d825-11e2-a28f-0800200c9a62', 'stack_id': '5d12f6fd-a196-4bf0-ae4c-1f639a523a52', 'api_address': '172.17.2.2', 'node_addresses': ['172.17.2.4'], 'node_count': 2, 'master_count': 1, } CREATE_CLUSTER = copy.deepcopy(CLUSTER1) del CREATE_CLUSTER['id'] del CREATE_CLUSTER['uuid'] del CREATE_CLUSTER['stack_id'] del CREATE_CLUSTER['api_address'] del CREATE_CLUSTER['node_addresses'] UPDATED_CLUSTER = copy.deepcopy(CLUSTER1) NEW_NAME = 'newcluster' UPDATED_CLUSTER['name'] = NEW_NAME RESIZED_CLUSTER = copy.deepcopy(CLUSTER1) RESIZED_NODE_COUNT = 5 UPDATED_CLUSTER['node_count'] = RESIZED_NODE_COUNT UPGRADED_CLUSTER = copy.deepcopy(CLUSTER1) UPGRADED_TO_TEMPLATE = "eabbc463-0d3f-49dc-8519-cb6b59507bd6" UPGRADED_CLUSTER['cluster_template_id'] = UPGRADED_TO_TEMPLATE fake_responses = { '/v1/clusters': { 'GET': ( {}, {'clusters': [CLUSTER1, CLUSTER2]}, ), 'POST': ( {}, CREATE_CLUSTER, ), }, '/v1/clusters/%s' % CLUSTER1['id']: { 'GET': ( {}, CLUSTER1 ), 'DELETE': ( {}, None, ), 'PATCH': ( {}, UPDATED_CLUSTER, ), }, '/v1/clusters/%s/?rollback=True' % CLUSTER1['id']: { 'PATCH': ( {}, UPDATED_CLUSTER, ), }, '/v1/clusters/%s' % CLUSTER1['name']: { 'GET': ( {}, CLUSTER1 ), 'DELETE': ( {}, None, ), 'PATCH': ( {}, UPDATED_CLUSTER, ), }, '/v1/clusters/?limit=2': { 'GET': ( {}, {'clusters': [CLUSTER1, CLUSTER2]}, ), }, '/v1/clusters/?marker=%s' % CLUSTER2['uuid']: { 'GET': ( {}, {'clusters': [CLUSTER1, CLUSTER2]}, ), }, '/v1/clusters/?limit=2&marker=%s' % CLUSTER2['uuid']: { 'GET': ( {}, {'clusters': [CLUSTER1, CLUSTER2]}, ), }, '/v1/clusters/?sort_dir=asc': { 'GET': ( {}, {'clusters': [CLUSTER1, CLUSTER2]}, ), }, '/v1/clusters/?sort_key=uuid': { 'GET': ( {}, {'clusters': [CLUSTER1, CLUSTER2]}, ), }, '/v1/clusters/?sort_key=uuid&sort_dir=desc': { 'GET': ( {}, {'clusters': [CLUSTER2, CLUSTER1]}, ), }, '/v1/clusters/%s/actions/resize' % CLUSTER1['uuid']: { 'POST': ( {}, UPDATED_CLUSTER ), }, '/v1/clusters/%s/actions/upgrade' % CLUSTER1['uuid']: { 'POST': ( {}, UPGRADED_CLUSTER ), } } class ClusterManagerTest(testtools.TestCase): def setUp(self): super(ClusterManagerTest, self).setUp() self.api = utils.FakeAPI(fake_responses) self.mgr = clusters.ClusterManager(self.api) def test_cluster_list(self): clusters = self.mgr.list() expect = [ ('GET', '/v1/clusters', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(clusters, matchers.HasLength(2)) def _test_cluster_list_with_filters(self, limit=None, marker=None, sort_key=None, sort_dir=None, detail=False, expect=[]): clusters_filter = self.mgr.list(limit=limit, marker=marker, sort_key=sort_key, sort_dir=sort_dir, detail=detail) self.assertEqual(expect, self.api.calls) self.assertThat(clusters_filter, matchers.HasLength(2)) def test_cluster_list_with_limit(self): expect = [ ('GET', '/v1/clusters/?limit=2', {}, None), ] self._test_cluster_list_with_filters( limit=2, expect=expect) def test_cluster_list_with_marker(self): expect = [ ('GET', '/v1/clusters/?marker=%s' % CLUSTER2['uuid'], {}, None), ] self._test_cluster_list_with_filters( marker=CLUSTER2['uuid'], expect=expect) def test_cluster_list_with_marker_limit(self): expect = [ ('GET', '/v1/clusters/?limit=2&marker=%s' % CLUSTER2['uuid'], {}, None), ] self._test_cluster_list_with_filters( limit=2, marker=CLUSTER2['uuid'], expect=expect) def test_cluster_list_with_sort_dir(self): expect = [ ('GET', '/v1/clusters/?sort_dir=asc', {}, None), ] self._test_cluster_list_with_filters( sort_dir='asc', expect=expect) def test_cluster_list_with_sort_key(self): expect = [ ('GET', '/v1/clusters/?sort_key=uuid', {}, None), ] self._test_cluster_list_with_filters( sort_key='uuid', expect=expect) def test_cluster_list_with_sort_key_dir(self): expect = [ ('GET', '/v1/clusters/?sort_key=uuid&sort_dir=desc', {}, None), ] self._test_cluster_list_with_filters( sort_key='uuid', sort_dir='desc', expect=expect) def test_cluster_show_by_id(self): cluster = self.mgr.get(CLUSTER1['id']) expect = [ ('GET', '/v1/clusters/%s' % CLUSTER1['id'], {}, None) ] self.assertEqual(expect, self.api.calls) self.assertEqual(CLUSTER1['name'], cluster.name) self.assertEqual(CLUSTER1['cluster_template_id'], cluster.cluster_template_id) def test_cluster_show_by_name(self): cluster = self.mgr.get(CLUSTER1['name']) expect = [ ('GET', '/v1/clusters/%s' % CLUSTER1['name'], {}, None) ] self.assertEqual(expect, self.api.calls) self.assertEqual(CLUSTER1['name'], cluster.name) self.assertEqual(CLUSTER1['cluster_template_id'], cluster.cluster_template_id) def test_cluster_create(self): cluster = self.mgr.create(**CREATE_CLUSTER) expect = [ ('POST', '/v1/clusters', {}, CREATE_CLUSTER), ] self.assertEqual(expect, self.api.calls) self.assertTrue(cluster) def test_cluster_create_with_keypair(self): cluster_with_keypair = dict() cluster_with_keypair.update(CREATE_CLUSTER) cluster_with_keypair['keypair'] = 'test_key' cluster = self.mgr.create(**cluster_with_keypair) expect = [ ('POST', '/v1/clusters', {}, cluster_with_keypair), ] self.assertEqual(expect, self.api.calls) self.assertTrue(cluster) def test_cluster_create_with_docker_volume_size(self): cluster_with_volume_size = dict() cluster_with_volume_size.update(CREATE_CLUSTER) cluster_with_volume_size['docker_volume_size'] = 20 cluster = self.mgr.create(**cluster_with_volume_size) expect = [ ('POST', '/v1/clusters', {}, cluster_with_volume_size), ] self.assertEqual(expect, self.api.calls) self.assertTrue(cluster) def test_cluster_create_with_labels(self): cluster_with_labels = dict() cluster_with_labels.update(CREATE_CLUSTER) cluster_with_labels['labels'] = "key=val" cluster = self.mgr.create(**cluster_with_labels) expect = [ ('POST', '/v1/clusters', {}, cluster_with_labels), ] self.assertEqual(expect, self.api.calls) self.assertTrue(cluster) def test_cluster_create_with_discovery_url(self): cluster_with_discovery = dict() cluster_with_discovery.update(CREATE_CLUSTER) cluster_with_discovery['discovery_url'] = 'discovery_url' cluster = self.mgr.create(**cluster_with_discovery) expect = [ ('POST', '/v1/clusters', {}, cluster_with_discovery), ] self.assertEqual(expect, self.api.calls) self.assertTrue(cluster) def test_cluster_create_with_cluster_create_timeout(self): cluster_with_timeout = dict() cluster_with_timeout.update(CREATE_CLUSTER) cluster_with_timeout['create_timeout'] = '15' cluster = self.mgr.create(**cluster_with_timeout) expect = [ ('POST', '/v1/clusters', {}, cluster_with_timeout), ] self.assertEqual(expect, self.api.calls) self.assertTrue(cluster) def test_cluster_create_fail(self): CREATE_CLUSTER_FAIL = copy.deepcopy(CREATE_CLUSTER) CREATE_CLUSTER_FAIL["wrong_key"] = "wrong" self.assertRaisesRegex(exceptions.InvalidAttribute, ("Key must be in %s" % ','.join(clusters.CREATION_ATTRIBUTES)), self.mgr.create, **CREATE_CLUSTER_FAIL) self.assertEqual([], self.api.calls) def test_cluster_delete_by_id(self): cluster = self.mgr.delete(CLUSTER1['id']) expect = [ ('DELETE', '/v1/clusters/%s' % CLUSTER1['id'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertIsNone(cluster) def test_cluster_delete_by_name(self): cluster = self.mgr.delete(CLUSTER1['name']) expect = [ ('DELETE', '/v1/clusters/%s' % CLUSTER1['name'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertIsNone(cluster) def test_cluster_update(self): patch = {'op': 'replace', 'value': NEW_NAME, 'path': '/name'} cluster = self.mgr.update(id=CLUSTER1['id'], patch=patch) expect = [ ('PATCH', '/v1/clusters/%s' % CLUSTER1['id'], {}, patch), ] self.assertEqual(expect, self.api.calls) self.assertEqual(NEW_NAME, cluster.name) def test_cluster_update_with_rollback(self): patch = {'op': 'replace', 'value': NEW_NAME, 'path': '/name'} cluster = self.mgr.update(id=CLUSTER1['id'], patch=patch, rollback=True) expect = [ ('PATCH', '/v1/clusters/%s/?rollback=True' % CLUSTER1['id'], {}, patch), ] self.assertEqual(expect, self.api.calls) self.assertEqual(NEW_NAME, cluster.name) def test_cluster_resize(self): body = {'node_count': RESIZED_NODE_COUNT} cluster = self.mgr.resize(CLUSTER1["uuid"], **body) expect = [ ('POST', '/v1/clusters/%s/actions/resize' % CLUSTER1['uuid'], {}, body), ] self.assertEqual(expect, self.api.calls) self.assertEqual(RESIZED_NODE_COUNT, cluster.node_count) def test_cluster_upgrade(self): body = {'cluster_template': UPGRADED_TO_TEMPLATE, 'max_batch_size': 1} cluster = self.mgr.upgrade(CLUSTER1["uuid"], **body) expect = [ ('POST', '/v1/clusters/%s/actions/upgrade' % CLUSTER1['uuid'], {}, body), ] self.assertEqual(expect, self.api.calls) self.assertEqual(UPGRADED_TO_TEMPLATE, cluster.cluster_template_id) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/v1/test_clusters_shell.py0000664000175000017500000006172100000000000026460 0ustar00zuulzuul00000000000000# Copyright 2015 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock from magnumclient.common import cliutils from magnumclient import exceptions from magnumclient.tests.v1 import shell_test_base from magnumclient.tests.v1 import test_clustertemplates_shell from magnumclient.v1.clusters import Cluster class FakeCluster(Cluster): def __init__(self, manager=None, info={}, **kwargs): Cluster.__init__(self, manager=manager, info=info) self.uuid = kwargs.get('uuid', 'x') self.keypair = kwargs.get('keypair', 'x') self.docker_volume_size = kwargs.get('docker_volume_size', 3) self.labels = kwargs.get('labels', 'key=val') self.name = kwargs.get('name', 'x') self.cluster_template_id = kwargs.get('cluster_template_id', 'x') self.stack_id = kwargs.get('stack_id', 'x') self.status = kwargs.get('status', 'x') self.master_count = kwargs.get('master_count', 1) self.node_count = kwargs.get('node_count', 1) self.links = kwargs.get('links', []) self.create_timeout = kwargs.get('create_timeout', 60) class FakeCert(object): def __init__(self, pem): self.pem = pem class ShellTest(shell_test_base.TestCommandLineArgument): def _get_expected_args_list(self, marker=None, limit=None, sort_dir=None, sort_key=None): expected_args = {} expected_args['marker'] = marker expected_args['limit'] = limit expected_args['sort_dir'] = sort_dir expected_args['sort_key'] = sort_key return expected_args def _get_expected_args_create(self, cluster_template_id, name=None, master_count=1, node_count=1, create_timeout=60, keypair=None, docker_volume_size=None, labels=None, discovery_url=None): expected_args = {} expected_args['name'] = name expected_args['cluster_template_id'] = cluster_template_id expected_args['master_count'] = master_count expected_args['node_count'] = node_count expected_args['create_timeout'] = create_timeout expected_args['discovery_url'] = discovery_url expected_args['keypair'] = keypair if docker_volume_size is not None: expected_args['docker_volume_size'] = docker_volume_size if labels is not None: expected_args['labels'] = labels return expected_args @mock.patch('magnumclient.v1.clusters.ClusterManager.list') def test_cluster_list_success(self, mock_list): self._test_arg_success('cluster-list') expected_args = self._get_expected_args_list() mock_list.assert_called_once_with(**expected_args) @mock.patch('magnumclient.v1.clusters.ClusterManager.list') def test_cluster_list_success_with_arg(self, mock_list): self._test_arg_success('cluster-list ' '--marker some_uuid ' '--limit 1 ' '--sort-dir asc ' '--sort-key uuid') expected_args = self._get_expected_args_list('some_uuid', 1, 'asc', 'uuid') mock_list.assert_called_once_with(**expected_args) @mock.patch('magnumclient.v1.clusters.ClusterManager.list') def test_cluster_list_ignored_duplicated_field(self, mock_list): mock_list.return_value = [FakeCluster()] self._test_arg_success( 'cluster-list --fields status,status,status,name', keyword=('\n| uuid | name | keypair | node_count | master_count | ' 'status |\n')) expected_args = self._get_expected_args_list() mock_list.assert_called_once_with(**expected_args) @mock.patch('magnumclient.v1.clusters.ClusterManager.list') def test_cluster_list_failure_with_invalid_field(self, mock_list): mock_list.return_value = [FakeCluster()] _error_msg = [".*?^Non-existent fields are specified: ['xxx','zzz']"] self.assertRaises(exceptions.CommandError, self._test_arg_failure, 'cluster-list --fields xxx,stack_id,zzz,status', _error_msg) expected_args = self._get_expected_args_list() mock_list.assert_called_once_with(**expected_args) @mock.patch('magnumclient.v1.clusters.ClusterManager.list') def test_cluster_list_failure_invalid_arg(self, mock_list): _error_msg = [ '.*?^usage: magnum cluster-list ', '.*?^error: argument --sort-dir: invalid choice: ', ".*?^Try 'magnum help cluster-list' for more information." ] self._test_arg_failure('cluster-list --sort-dir aaa', _error_msg) mock_list.assert_not_called() @mock.patch('magnumclient.v1.clusters.ClusterManager.list') def test_cluster_list_failure(self, mock_list): self._test_arg_failure('cluster-list --wrong', self._unrecognized_arg_error) mock_list.assert_not_called() @mock.patch('magnumclient.v1.cluster_templates.ClusterTemplateManager.get') @mock.patch('magnumclient.v1.clusters.ClusterManager.create') def test_cluster_create_success(self, mock_create, mock_get): mock_ct = mock.MagicMock() mock_ct.uuid = 'xxx' mock_get.return_value = mock_ct self._test_arg_success('cluster-create test ' '--cluster-template xxx ' '--node-count 123 --timeout 15') expected_args = self._get_expected_args_create('xxx', name='test', node_count=123, create_timeout=15) mock_create.assert_called_with(**expected_args) self._test_arg_success('cluster-create --cluster-template xxx') expected_args = self._get_expected_args_create('xxx') mock_create.assert_called_with(**expected_args) self._test_arg_success('cluster-create --cluster-template xxx ' '--keypair x') expected_args = self._get_expected_args_create('xxx', keypair='x') mock_create.assert_called_with(**expected_args) self._test_arg_success('cluster-create --cluster-template xxx ' '--docker-volume-size 20') expected_args = self._get_expected_args_create('xxx', docker_volume_size=20) self._test_arg_success('cluster-create --cluster-template xxx ' '--labels key=val') expected_args = self._get_expected_args_create('xxx', labels={'key': 'val'}) mock_create.assert_called_with(**expected_args) self._test_arg_success('cluster-create test ' '--cluster-template xxx') expected_args = self._get_expected_args_create('xxx', name='test') mock_create.assert_called_with(**expected_args) self._test_arg_success('cluster-create --cluster-template xxx ' '--node-count 123') expected_args = self._get_expected_args_create('xxx', node_count=123) mock_create.assert_called_with(**expected_args) self._test_arg_success('cluster-create --cluster-template xxx ' '--node-count 123 --master-count 123') expected_args = self._get_expected_args_create('xxx', master_count=123, node_count=123) mock_create.assert_called_with(**expected_args) self._test_arg_success('cluster-create --cluster-template xxx ' '--timeout 15') expected_args = self._get_expected_args_create('xxx', create_timeout=15) mock_create.assert_called_with(**expected_args) @mock.patch('magnumclient.v1.cluster_templates.ClusterTemplateManager.get') @mock.patch('magnumclient.v1.clusters.ClusterManager.create') def test_cluster_create_deprecation_warnings(self, mock_create, mock_get): self._test_arg_failure('cluster-create --cluster-template xxx ' '--keypair-id x', self._deprecated_warning) self.assertTrue(mock_create.called) self.assertTrue(mock_get.called) self._test_arg_failure('cluster-create --cluster-template xxx ' '--name foo ', self._deprecated_warning) self.assertTrue(mock_create.called) self.assertTrue(mock_get.called) @mock.patch('magnumclient.v1.cluster_templates.ClusterTemplateManager.get') @mock.patch('magnumclient.v1.clusters.ClusterManager.create') def test_cluster_create_deprecation_errors(self, mock_create, mock_get): self._test_arg_failure('cluster-create --cluster-template xxx ' '--keypair-id x --keypair x', self._too_many_group_arg_error) self.assertFalse(mock_create.called) self.assertFalse(mock_get.called) @mock.patch('magnumclient.v1.cluster_templates.ClusterTemplateManager.get') @mock.patch('magnumclient.v1.clusters.ClusterManager.get') def test_cluster_show_clustertemplate_metadata(self, mock_cluster, mock_clustertemplate): mock_cluster.return_value = mock.MagicMock(cluster_template_id=0) mock_clustertemplate.return_value = \ test_clustertemplates_shell.FakeClusterTemplate(info={'links': 0, 'uuid': 0, 'id': 0, 'name': ''}) self._test_arg_success('cluster-show --long x') mock_cluster.assert_called_once_with('x') mock_clustertemplate.assert_called_once_with(0) @mock.patch('magnumclient.v1.cluster_templates.ClusterTemplateManager.get') @mock.patch('magnumclient.v1.clusters.ClusterManager.create') def _test_cluster_create_success(self, cmd, expected_args, expected_kwargs, mock_create, mock_get): mock_ct = mock.MagicMock() mock_ct.uuid = 'xxx' mock_get.return_value = mock_ct self._test_arg_success(cmd) expected = self._get_expected_args_create(*expected_args, **expected_kwargs) mock_create.assert_called_with(**expected) def test_cluster_create_success_only_clustertemplate_arg(self): self._test_cluster_create_success( 'cluster-create --cluster-template xxx', ['xxx'], {}) @mock.patch('magnumclient.v1.cluster_templates.ClusterTemplateManager.get') @mock.patch('magnumclient.v1.clusters.ClusterManager.create') def test_cluster_create_success_only_positional_name(self, mock_create, mock_get): self._test_cluster_create_success( 'cluster-create foo --cluster-template xxx', ['xxx'], {'name': 'foo'}) @mock.patch('magnumclient.v1.cluster_templates.ClusterTemplateManager.get') @mock.patch('magnumclient.v1.clusters.ClusterManager.create') def test_cluster_create_success_only_optional_name(self, mock_create, mock_get): self._test_cluster_create_success( 'cluster-create --name foo --cluster-template xxx', ['xxx'], {'name': 'foo'}) @mock.patch('magnumclient.v1.clusters.ClusterManager.create') def test_cluster_create_failure_only_name(self, mock_create): self._test_arg_failure('cluster-create --name test', self._mandatory_arg_error) mock_create.assert_not_called() @mock.patch('magnumclient.v1.clusters.ClusterManager.create') def test_cluster_create_failure_only_keypair(self, mock_create): self._test_arg_failure('cluster-create --keypair test', self._mandatory_arg_error) mock_create.assert_not_called() @mock.patch('magnumclient.v1.clusters.ClusterManager.create') def test_cluster_create_failure_only_docker_volume_size(self, mock_create): self._test_arg_failure('cluster-create --docker_volume_size 20', self._mandatory_arg_error) mock_create.assert_not_called() @mock.patch('magnumclient.v1.clusters.ClusterManager.create') def test_cluster_create_failure_only_labels(self, mock_create): self._test_arg_failure('cluster-create --labels key=val', self._mandatory_arg_error) @mock.patch('magnumclient.v1.clusters.ClusterManager.create') def test_cluster_create_failure_only_node_count(self, mock_create): self._test_arg_failure('cluster-create --node-count 1', self._mandatory_arg_error) mock_create.assert_not_called() @mock.patch('magnumclient.v1.clusters.ClusterManager.create') def test_cluster_create_failure_invalid_node_count(self, mock_create): self._test_arg_failure('cluster-create --cluster-template xxx ' '--node-count test', self._invalid_value_error) mock_create.assert_not_called() @mock.patch('magnumclient.v1.clusters.ClusterManager.create') def test_cluster_create_failure_only_cluster_create_timeout(self, mock_create): self._test_arg_failure('cluster-create --timeout 15', self._mandatory_arg_error) mock_create.assert_not_called() @mock.patch('magnumclient.v1.clusters.ClusterManager.create') def test_cluster_create_failure_no_arg(self, mock_create): self._test_arg_failure('cluster-create', self._mandatory_arg_error) mock_create.assert_not_called() @mock.patch('magnumclient.v1.clusters.ClusterManager.create') def test_cluster_create_failure_invalid_master_count(self, mock_create): self._test_arg_failure('cluster-create --cluster-template xxx ' '--master-count test', self._invalid_value_error) mock_create.assert_not_called() @mock.patch('magnumclient.v1.clusters.ClusterManager.create') def test_cluster_create_failure_duplicate_name(self, mock_create): self.assertRaises(cliutils.DuplicateArgs, self._test_arg_failure, 'cluster-create foo --name bar ' '--cluster-template xxx', self._duplicate_arg_error) mock_create.assert_not_called() @mock.patch('magnumclient.v1.clusters.ClusterManager.delete') def test_cluster_delete_success(self, mock_delete): self._test_arg_success('cluster-delete xxx') mock_delete.assert_called_once_with('xxx') @mock.patch('magnumclient.v1.clusters.ClusterManager.delete') def test_cluster_delete_multiple_id_success(self, mock_delete): self._test_arg_success('cluster-delete xxx xyz') calls = [mock.call('xxx'), mock.call('xyz')] mock_delete.assert_has_calls(calls) @mock.patch('magnumclient.v1.clusters.ClusterManager.delete') def test_cluster_delete_failure_no_arg(self, mock_delete): self._test_arg_failure('cluster-delete', self._few_argument_error) mock_delete.assert_not_called() @mock.patch('magnumclient.v1.clusters.ClusterManager.get') def test_cluster_show_success(self, mock_show): self._test_arg_success('cluster-show xxx') mock_show.assert_called_once_with('xxx') @mock.patch('magnumclient.v1.clusters.ClusterManager.get') def test_cluster_show_failure_no_arg(self, mock_show): self._test_arg_failure('cluster-show', self._few_argument_error) mock_show.assert_not_called() @mock.patch('magnumclient.v1.clusters.ClusterManager.update') def test_cluster_update_success(self, mock_update): self._test_arg_success('cluster-update test add test=test') patch = [{'op': 'add', 'path': '/test', 'value': 'test'}] mock_update.assert_called_once_with('test', patch, False) @mock.patch('magnumclient.v1.clusters.ClusterManager.update') def test_cluster_update_success_many_attribute(self, mock_update): self._test_arg_success('cluster-update test add test=test test1=test1') patch = [{'op': 'add', 'path': '/test', 'value': 'test'}, {'op': 'add', 'path': '/test1', 'value': 'test1'}] mock_update.assert_called_once_with('test', patch, False) @mock.patch('magnumclient.v1.clusters.ClusterManager.update') def test_cluster_update_success_rollback(self, mock_update): self._test_arg_success('cluster-update test add test=test --rollback') patch = [{'op': 'add', 'path': '/test', 'value': 'test'}] mock_update.assert_called_once_with('test', patch, True) @mock.patch('magnumclient.v1.clusters.ClusterManager.update') def test_cluster_update_rollback_old_api_version(self, mock_update): self.assertRaises( exceptions.CommandError, self.shell, '--magnum-api-version 1.2 cluster-update ' 'test add test=test --rollback') mock_update.assert_not_called() @mock.patch('magnumclient.v1.clusters.ClusterManager.update') def test_cluster_update_failure_wrong_op(self, mock_update): _error_msg = [ '.*?^usage: magnum cluster-update ', '.*?^error: argument : invalid choice: ', ".*?^Try 'magnum help cluster-update' for more information." ] self._test_arg_failure('cluster-update test wrong test=test', _error_msg) mock_update.assert_not_called() @mock.patch('magnumclient.v1.clusters.ClusterManager.update') def test_cluster_update_failure_wrong_attribute(self, mock_update): _error_msg = [ '.*?^ERROR: Attributes must be a list of PATH=VALUE' ] self.assertRaises(exceptions.CommandError, self._test_arg_failure, 'cluster-update test add test', _error_msg) mock_update.assert_not_called() @mock.patch('magnumclient.v1.clusters.ClusterManager.update') def test_cluster_update_failure_few_args(self, mock_update): _error_msg = [ '.*?^usage: magnum cluster-update ', '.*?^error: (the following arguments|too few arguments)', ".*?^Try 'magnum help cluster-update' for more information." ] self._test_arg_failure('cluster-update', _error_msg) mock_update.assert_not_called() self._test_arg_failure('cluster-update test', _error_msg) mock_update.assert_not_called() self._test_arg_failure('cluster-update test add', _error_msg) mock_update.assert_not_called() @mock.patch('magnumclient.v1.cluster_templates.ClusterTemplateManager.get') @mock.patch('magnumclient.v1.clusters.ClusterManager.get') def test_cluster_config_success(self, mock_cluster, mock_clustertemplate): mock_cluster.return_value = FakeCluster(status='UPDATE_COMPLETE') self._test_arg_success('cluster-config xxx') mock_cluster.assert_called_with('xxx') mock_cluster.return_value = FakeCluster(status='CREATE_COMPLETE') self._test_arg_success('cluster-config xxx') mock_cluster.assert_called_with('xxx') self._test_arg_success('cluster-config --dir /tmp xxx') mock_cluster.assert_called_with('xxx') self._test_arg_success('cluster-config --force xxx') mock_cluster.assert_called_with('xxx') self._test_arg_success('cluster-config --dir /tmp --force xxx') mock_cluster.assert_called_with('xxx') @mock.patch('magnumclient.v1.clusters.ClusterManager.get') def test_cluster_config_failure_no_arg(self, mock_cluster): self._test_arg_failure('cluster-config', self._few_argument_error) mock_cluster.assert_not_called() @mock.patch('magnumclient.v1.clusters.ClusterManager.get') def test_cluster_config_failure_wrong_arg(self, mock_cluster): self._test_arg_failure('cluster-config xxx yyy', self._unrecognized_arg_error) mock_cluster.assert_not_called() @mock.patch('os.path.exists') @mock.patch('magnumclient.v1.certificates.CertificateManager.create') @mock.patch('magnumclient.v1.certificates.CertificateManager.get') @mock.patch('magnumclient.v1.cluster_templates.ClusterTemplateManager.get') @mock.patch('magnumclient.v1.clusters.ClusterManager.get') def _test_cluster_config_success(self, mock_cluster, mock_ct, mock_cert_get, mock_cert_create, mock_exists, coe, shell, tls_disable): cert = FakeCert(pem='foo bar') mock_exists.return_value = False mock_cluster.return_value = FakeCluster(status='CREATE_COMPLETE', info={ 'name': 'Kluster', 'api_address': '10.0.0.1'}, cluster_template_id='fake_ct', uuid='fake_cluster') mock_cert_get.return_value = cert mock_cert_create.return_value = cert mock_ct.return_value = test_clustertemplates_shell.\ FakeClusterTemplate(coe=coe, name='fake_ct', tls_disabled=tls_disable) with mock.patch.dict('os.environ', {'SHELL': shell}): self._test_arg_success('cluster-config test_cluster') self.assertTrue(mock_exists.called) mock_cluster.assert_called_once_with('test_cluster') mock_ct.assert_called_once_with('fake_ct') if not tls_disable: mock_cert_create.assert_called_once_with( cluster_uuid='fake_cluster', csr=mock.ANY) mock_cert_get.assert_called_once_with(cluster_uuid='fake_cluster') def test_cluster_config_swarm_success_with_tls_csh(self): self._test_cluster_config_success(coe='swarm', shell='csh', tls_disable=False) def test_cluster_config_swarm_success_with_tls_non_csh(self): self._test_cluster_config_success(coe='swarm', shell='zsh', tls_disable=False) def test_cluster_config_swarm_success_without_tls_csh(self): self._test_cluster_config_success(coe='swarm', shell='csh', tls_disable=True) def test_cluster_config_swarm_success_without_tls_non_csh(self): self._test_cluster_config_success(coe='swarm', shell='zsh', tls_disable=True) def test_cluster_config_k8s_success_with_tls_csh(self): self._test_cluster_config_success(coe='kubernetes', shell='csh', tls_disable=False) def test_cluster_config_k8s_success_with_tls_non_csh(self): self._test_cluster_config_success(coe='kubernetes', shell='zsh', tls_disable=False) def test_cluster_config_k8s_success_without_tls_csh(self): self._test_cluster_config_success(coe='kubernetes', shell='csh', tls_disable=True) def test_cluster_config_k8s_success_without_tls_non_csh(self): self._test_cluster_config_success(coe='kubernetes', shell='zsh', tls_disable=True) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/v1/test_clustertemplates.py0000664000175000017500000004060700000000000027025 0ustar00zuulzuul00000000000000# Copyright 2015 IBM Corp # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy import testtools from testtools import matchers from magnumclient import exceptions from magnumclient.tests import utils from magnumclient.v1 import cluster_templates CLUSTERTEMPLATE1 = { 'id': 123, 'uuid': '66666666-7777-8888-9999-000000000001', 'name': 'clustertemplate1', 'image_id': 'clustertemplate1-image', 'master_flavor_id': 'm1.tiny', 'flavor_id': 'm1.small', 'external_network_id': 'd1f02cfb-d27f-4068-9332-84d907cb0e21', 'fixed_network': 'private', 'fixed_subnet': 'private-subnet', 'network_driver': 'libnetwork', 'volume_driver': 'rexray', 'dns_nameserver': '8.8.1.1', 'docker_volume_size': '71', 'docker_storage_driver': 'devicemapper', 'coe': 'swarm', 'http_proxy': 'http_proxy', 'https_proxy': 'https_proxy', 'no_proxy': 'no_proxy', 'labels': 'key1=val1,key11=val11', 'tls_disabled': False, 'public': False, 'registry_enabled': False, 'master_lb_enabled': True, 'floating_ip_enabled': True, 'hidden': False } CLUSTERTEMPLATE2 = { 'id': 124, 'uuid': '66666666-7777-8888-9999-000000000002', 'name': 'clustertemplate2', 'image_id': 'clustertemplate2-image', 'flavor_id': 'm2.small', 'master_flavor_id': 'm2.tiny', 'external_network_id': 'd1f02cfb-d27f-4068-9332-84d907cb0e22', 'fixed_network': 'private2', 'network_driver': 'flannel', 'volume_driver': 'cinder', 'dns_nameserver': '8.8.1.2', 'docker_volume_size': '71', 'docker_storage_driver': 'overlay', 'coe': 'kubernetes', 'labels': 'key2=val2,key22=val22', 'tls_disabled': True, 'public': True, 'registry_enabled': True} CREATE_CLUSTERTEMPLATE = copy.deepcopy(CLUSTERTEMPLATE1) del CREATE_CLUSTERTEMPLATE['id'] del CREATE_CLUSTERTEMPLATE['uuid'] UPDATED_CLUSTERTEMPLATE = copy.deepcopy(CLUSTERTEMPLATE1) NEW_NAME = 'newcluster' UPDATED_CLUSTERTEMPLATE['name'] = NEW_NAME fake_responses = { '/v1/clustertemplates': { 'GET': ( {}, {'clustertemplates': [CLUSTERTEMPLATE1, CLUSTERTEMPLATE2]}, ), 'POST': ( {}, CREATE_CLUSTERTEMPLATE, ), }, '/v1/clustertemplates/%s' % CLUSTERTEMPLATE1['id']: { 'GET': ( {}, CLUSTERTEMPLATE1 ), 'DELETE': ( {}, None, ), 'PATCH': ( {}, UPDATED_CLUSTERTEMPLATE, ), }, '/v1/clustertemplates/%s' % CLUSTERTEMPLATE1['name']: { 'GET': ( {}, CLUSTERTEMPLATE1 ), 'DELETE': ( {}, None, ), 'PATCH': ( {}, UPDATED_CLUSTERTEMPLATE, ), }, '/v1/clustertemplates/detail': { 'GET': ( {}, {'clustertemplates': [CLUSTERTEMPLATE1, CLUSTERTEMPLATE2]}, ), }, '/v1/clustertemplates/?limit=2': { 'GET': ( {}, {'clustertemplates': [CLUSTERTEMPLATE1, CLUSTERTEMPLATE2]}, ), }, '/v1/clustertemplates/?marker=%s' % CLUSTERTEMPLATE2['uuid']: { 'GET': ( {}, {'clustertemplates': [CLUSTERTEMPLATE1, CLUSTERTEMPLATE2]}, ), }, '/v1/clustertemplates/?limit=2&marker=%s' % CLUSTERTEMPLATE2['uuid']: { 'GET': ( {}, {'clustertemplates': [CLUSTERTEMPLATE1, CLUSTERTEMPLATE2]}, ), }, '/v1/clustertemplates/?sort_dir=asc': { 'GET': ( {}, {'clustertemplates': [CLUSTERTEMPLATE1, CLUSTERTEMPLATE2]}, ), }, '/v1/clustertemplates/?sort_key=uuid': { 'GET': ( {}, {'clustertemplates': [CLUSTERTEMPLATE1, CLUSTERTEMPLATE2]}, ), }, '/v1/clustertemplates/?sort_key=uuid&sort_dir=desc': { 'GET': ( {}, {'clustertemplates': [CLUSTERTEMPLATE2, CLUSTERTEMPLATE1]}, ), }, } class ClusterTemplateManagerTest(testtools.TestCase): def setUp(self): super(ClusterTemplateManagerTest, self).setUp() self.api = utils.FakeAPI(fake_responses) self.mgr = cluster_templates.ClusterTemplateManager(self.api) def test_clustertemplate_list(self): clustertemplates = self.mgr.list() expect = [ ('GET', '/v1/clustertemplates', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(clustertemplates, matchers.HasLength(2)) def _test_clustertemplate_list_with_filters( self, limit=None, marker=None, sort_key=None, sort_dir=None, detail=False, expect=[]): clustertemplates_filter = self.mgr.list(limit=limit, marker=marker, sort_key=sort_key, sort_dir=sort_dir, detail=detail) self.assertEqual(expect, self.api.calls) self.assertThat(clustertemplates_filter, matchers.HasLength(2)) def test_clustertemplate_list_with_detail(self): expect = [ ('GET', '/v1/clustertemplates/detail', {}, None), ] self._test_clustertemplate_list_with_filters( detail=True, expect=expect) def test_clustertemplate_list_with_limit(self): expect = [ ('GET', '/v1/clustertemplates/?limit=2', {}, None), ] self._test_clustertemplate_list_with_filters( limit=2, expect=expect) def test_clustertemplate_list_with_marker(self): expect = [ ('GET', '/v1/clustertemplates/?marker=%s' % CLUSTERTEMPLATE2['uuid'], {}, None), ] self._test_clustertemplate_list_with_filters( marker=CLUSTERTEMPLATE2['uuid'], expect=expect) def test_clustertemplate_list_with_marker_limit(self): expect = [ ('GET', '/v1/clustertemplates/?limit=2&marker=%s' % CLUSTERTEMPLATE2['uuid'], {}, None), ] self._test_clustertemplate_list_with_filters( limit=2, marker=CLUSTERTEMPLATE2['uuid'], expect=expect) def test_clustertemplate_list_with_sort_dir(self): expect = [ ('GET', '/v1/clustertemplates/?sort_dir=asc', {}, None), ] self._test_clustertemplate_list_with_filters( sort_dir='asc', expect=expect) def test_clustertemplate_list_with_sort_key(self): expect = [ ('GET', '/v1/clustertemplates/?sort_key=uuid', {}, None), ] self._test_clustertemplate_list_with_filters( sort_key='uuid', expect=expect) def test_clustertemplate_list_with_sort_key_dir(self): expect = [ ('GET', '/v1/clustertemplates/?sort_key=uuid&sort_dir=desc', {}, None), ] self._test_clustertemplate_list_with_filters( sort_key='uuid', sort_dir='desc', expect=expect) def test_clustertemplate_show_by_id(self): cluster_template = self.mgr.get(CLUSTERTEMPLATE1['id']) expect = [ ('GET', '/v1/clustertemplates/%s' % CLUSTERTEMPLATE1['id'], {}, None) ] self.assertEqual(expect, self.api.calls) self.assertEqual(CLUSTERTEMPLATE1['name'], cluster_template.name) self.assertEqual(CLUSTERTEMPLATE1['image_id'], cluster_template.image_id) self.assertEqual(CLUSTERTEMPLATE1['docker_volume_size'], cluster_template.docker_volume_size) self.assertEqual(CLUSTERTEMPLATE1['docker_storage_driver'], cluster_template.docker_storage_driver) self.assertEqual(CLUSTERTEMPLATE1['fixed_network'], cluster_template.fixed_network) self.assertEqual(CLUSTERTEMPLATE1['fixed_subnet'], cluster_template.fixed_subnet) self.assertEqual(CLUSTERTEMPLATE1['coe'], cluster_template.coe) self.assertEqual(CLUSTERTEMPLATE1['http_proxy'], cluster_template.http_proxy) self.assertEqual(CLUSTERTEMPLATE1['https_proxy'], cluster_template.https_proxy) self.assertEqual(CLUSTERTEMPLATE1['no_proxy'], cluster_template.no_proxy) self.assertEqual(CLUSTERTEMPLATE1['network_driver'], cluster_template.network_driver) self.assertEqual(CLUSTERTEMPLATE1['volume_driver'], cluster_template.volume_driver) self.assertEqual(CLUSTERTEMPLATE1['labels'], cluster_template.labels) self.assertEqual(CLUSTERTEMPLATE1['tls_disabled'], cluster_template.tls_disabled) self.assertEqual(CLUSTERTEMPLATE1['public'], cluster_template.public) self.assertEqual(CLUSTERTEMPLATE1['registry_enabled'], cluster_template.registry_enabled) self.assertEqual(CLUSTERTEMPLATE1['master_lb_enabled'], cluster_template.master_lb_enabled) self.assertEqual(CLUSTERTEMPLATE1['floating_ip_enabled'], cluster_template.floating_ip_enabled) self.assertEqual(CLUSTERTEMPLATE1['hidden'], cluster_template.hidden) def test_clustertemplate_show_by_name(self): cluster_template = self.mgr.get(CLUSTERTEMPLATE1['name']) expect = [ ('GET', '/v1/clustertemplates/%s' % CLUSTERTEMPLATE1['name'], {}, None) ] self.assertEqual(expect, self.api.calls) self.assertEqual(CLUSTERTEMPLATE1['name'], cluster_template.name) self.assertEqual(CLUSTERTEMPLATE1['image_id'], cluster_template.image_id) self.assertEqual(CLUSTERTEMPLATE1['docker_volume_size'], cluster_template.docker_volume_size) self.assertEqual(CLUSTERTEMPLATE1['docker_storage_driver'], cluster_template.docker_storage_driver) self.assertEqual(CLUSTERTEMPLATE1['fixed_network'], cluster_template.fixed_network) self.assertEqual(CLUSTERTEMPLATE1['fixed_subnet'], cluster_template.fixed_subnet) self.assertEqual(CLUSTERTEMPLATE1['coe'], cluster_template.coe) self.assertEqual(CLUSTERTEMPLATE1['http_proxy'], cluster_template.http_proxy) self.assertEqual(CLUSTERTEMPLATE1['https_proxy'], cluster_template.https_proxy) self.assertEqual(CLUSTERTEMPLATE1['no_proxy'], cluster_template.no_proxy) self.assertEqual(CLUSTERTEMPLATE1['network_driver'], cluster_template.network_driver) self.assertEqual(CLUSTERTEMPLATE1['volume_driver'], cluster_template.volume_driver) self.assertEqual(CLUSTERTEMPLATE1['labels'], cluster_template.labels) self.assertEqual(CLUSTERTEMPLATE1['tls_disabled'], cluster_template.tls_disabled) self.assertEqual(CLUSTERTEMPLATE1['public'], cluster_template.public) self.assertEqual(CLUSTERTEMPLATE1['registry_enabled'], cluster_template.registry_enabled) self.assertEqual(CLUSTERTEMPLATE1['master_lb_enabled'], cluster_template.master_lb_enabled) self.assertEqual(CLUSTERTEMPLATE1['floating_ip_enabled'], cluster_template.floating_ip_enabled) self.assertEqual(CLUSTERTEMPLATE1['hidden'], cluster_template.hidden) def test_clustertemplate_create(self): cluster_template = self.mgr.create(**CREATE_CLUSTERTEMPLATE) expect = [ ('POST', '/v1/clustertemplates', {}, CREATE_CLUSTERTEMPLATE), ] self.assertEqual(expect, self.api.calls) self.assertTrue(cluster_template) self.assertEqual(CLUSTERTEMPLATE1['docker_volume_size'], cluster_template.docker_volume_size) self.assertEqual(CLUSTERTEMPLATE1['docker_storage_driver'], cluster_template.docker_storage_driver) def test_clustertemplate_create_with_keypair(self): cluster_template_with_keypair = dict() cluster_template_with_keypair.update(CREATE_CLUSTERTEMPLATE) cluster_template_with_keypair['keypair_id'] = 'test_key' cluster_template = self.mgr.create(**cluster_template_with_keypair) expect = [ ('POST', '/v1/clustertemplates', {}, cluster_template_with_keypair), ] self.assertEqual(expect, self.api.calls) self.assertTrue(cluster_template) self.assertEqual(CLUSTERTEMPLATE1['docker_volume_size'], cluster_template.docker_volume_size) self.assertEqual(CLUSTERTEMPLATE1['docker_storage_driver'], cluster_template.docker_storage_driver) def test_clustertemplate_create_with_docker_volume_size(self): cluster_template_with_docker_volume_size = dict() cluster_template_with_docker_volume_size.update(CREATE_CLUSTERTEMPLATE) cluster_template_with_docker_volume_size['docker_volume_size'] = 11 cluster_template = self.mgr.create( **cluster_template_with_docker_volume_size) expect = [ ('POST', '/v1/clustertemplates', {}, cluster_template_with_docker_volume_size), ] self.assertEqual(expect, self.api.calls) self.assertTrue(cluster_template) self.assertEqual(CLUSTERTEMPLATE1['docker_volume_size'], cluster_template.docker_volume_size) self.assertEqual(CLUSTERTEMPLATE1['docker_storage_driver'], cluster_template.docker_storage_driver) def test_clustertemplate_create_fail(self): CREATE_CLUSTERTEMPLATE_FAIL = copy.deepcopy(CREATE_CLUSTERTEMPLATE) CREATE_CLUSTERTEMPLATE_FAIL["wrong_key"] = "wrong" self.assertRaisesRegex( exceptions.InvalidAttribute, ("Key must be in %s" % ','.join(cluster_templates.CREATION_ATTRIBUTES)), self.mgr.create, **CREATE_CLUSTERTEMPLATE_FAIL) self.assertEqual([], self.api.calls) def test_clustertemplate_delete_by_id(self): cluster_template = self.mgr.delete(CLUSTERTEMPLATE1['id']) expect = [ ('DELETE', '/v1/clustertemplates/%s' % CLUSTERTEMPLATE1['id'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertIsNone(cluster_template) def test_clustertemplate_delete_by_name(self): cluster_template = self.mgr.delete(CLUSTERTEMPLATE1['name']) expect = [ ('DELETE', '/v1/clustertemplates/%s' % CLUSTERTEMPLATE1['name'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertIsNone(cluster_template) def test_clustertemplate_update(self): patch = {'op': 'replace', 'value': NEW_NAME, 'path': '/name'} cluster_template = self.mgr.update(id=CLUSTERTEMPLATE1['id'], patch=patch) expect = [ ('PATCH', '/v1/clustertemplates/%s' % CLUSTERTEMPLATE1['id'], {}, patch), ] self.assertEqual(expect, self.api.calls) self.assertEqual(NEW_NAME, cluster_template.name) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/v1/test_clustertemplates_shell.py0000664000175000017500000011701600000000000030213 0ustar00zuulzuul00000000000000# Copyright 2015 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock from magnumclient.common.apiclient import exceptions from magnumclient.tests.v1 import shell_test_base from magnumclient.v1.cluster_templates import ClusterTemplate class FakeClusterTemplate(ClusterTemplate): def __init__(self, manager=None, info={}, **kwargs): ClusterTemplate.__init__(self, manager=manager, info=info) self.apiserver_port = kwargs.get('apiserver_port', None) self.uuid = kwargs.get('uuid', 'x') self.links = kwargs.get('links', []) self.server_type = kwargs.get('server_type', 'vm') self.image_id = kwargs.get('image', 'x') self.tls_disabled = kwargs.get('tls_disabled', False) self.registry_enabled = kwargs.get('registry_enabled', False) self.coe = kwargs.get('coe', 'x') self.public = kwargs.get('public', False) self.name = kwargs.get('name', 'x') self.hidden = kwargs.get('hidden', False) class ShellTest(shell_test_base.TestCommandLineArgument): def _get_expected_args_list(self, limit=None, sort_dir=None, sort_key=None, detail=False): expected_args = {} expected_args['limit'] = limit expected_args['sort_dir'] = sort_dir expected_args['sort_key'] = sort_key expected_args['detail'] = detail return expected_args def _get_expected_args(self, image_id, external_network_id, coe, master_flavor_id=None, name=None, keypair_id=None, fixed_network=None, fixed_subnet=None, network_driver=None, volume_driver=None, dns_nameserver='8.8.8.8', flavor_id='m1.medium', docker_storage_driver='devicemapper', docker_volume_size=None, http_proxy=None, https_proxy=None, no_proxy=None, labels={}, tls_disabled=False, public=False, master_lb_enabled=False, server_type='vm', registry_enabled=False, insecure_registry=None, hidden=False): expected_args = {} expected_args['image_id'] = image_id expected_args['external_network_id'] = external_network_id expected_args['coe'] = coe expected_args['master_flavor_id'] = master_flavor_id expected_args['name'] = name expected_args['keypair_id'] = keypair_id expected_args['fixed_network'] = fixed_network expected_args['fixed_subnet'] = fixed_subnet expected_args['network_driver'] = network_driver expected_args['volume_driver'] = volume_driver expected_args['dns_nameserver'] = dns_nameserver expected_args['flavor_id'] = flavor_id expected_args['docker_volume_size'] = docker_volume_size expected_args['docker_storage_driver'] = docker_storage_driver expected_args['http_proxy'] = http_proxy expected_args['https_proxy'] = https_proxy expected_args['no_proxy'] = no_proxy expected_args['labels'] = labels expected_args['tls_disabled'] = tls_disabled expected_args['public'] = public expected_args['master_lb_enabled'] = master_lb_enabled expected_args['server_type'] = server_type expected_args['registry_enabled'] = registry_enabled expected_args['insecure_registry'] = insecure_registry expected_args['hidden'] = hidden return expected_args @mock.patch( 'magnumclient.v1.cluster_templates.ClusterTemplateManager.create') def test_cluster_template_create_success(self, mock_create): self._test_arg_success('cluster-template-create ' '--name test ' '--image-id test_image ' '--keypair-id test_keypair ' '--external-network-id test_net ' '--coe swarm ' '--dns-nameserver test_dns ' '--flavor-id test_flavor ' '--fixed-network private ' '--fixed-subnet private-subnet ' '--volume-driver test_volume ' '--network-driver test_driver ' '--labels key=val ' '--master-flavor-id test_flavor ' '--docker-volume-size 10 ' '--docker-storage-driver devicemapper ' '--public ' '--server-type vm ' '--master-lb-enabled ') expected_args = \ self._get_expected_args(name='test', image_id='test_image', keypair_id='test_keypair', coe='swarm', external_network_id='test_net', dns_nameserver='test_dns', public=True, flavor_id='test_flavor', master_flavor_id='test_flavor', fixed_network='private', fixed_subnet='private-subnet', server_type='vm', network_driver='test_driver', volume_driver='test_volume', docker_storage_driver='devicemapper', docker_volume_size=10, master_lb_enabled=True, labels={'key': 'val'}) mock_create.assert_called_with(**expected_args) self._test_arg_success('cluster-template-create ' '--keypair-id test_keypair ' '--external-network-id test_net ' '--image-id test_image ' '--coe kubernetes ' '--name test ' '--server-type vm') expected_args = \ self._get_expected_args(name='test', image_id='test_image', keypair_id='test_keypair', coe='kubernetes', external_network_id='test_net', server_type='vm') mock_create.assert_called_with(**expected_args) @mock.patch( 'magnumclient.v1.cluster_templates.ClusterTemplateManager.create') def test_cluster_template_create_success_no_servertype(self, mock_create): self._test_arg_success('cluster-template-create ' '--name test ' '--image-id test_image ' '--keypair-id test_keypair ' '--external-network-id test_net ' '--coe swarm ' '--dns-nameserver test_dns ' '--flavor-id test_flavor ' '--fixed-network public ' '--network-driver test_driver ' '--labels key=val ' '--master-flavor-id test_flavor ' '--docker-volume-size 10 ' '--docker-storage-driver devicemapper ' '--public ') expected_args = \ self._get_expected_args(name='test', image_id='test_image', keypair_id='test_keypair', coe='swarm', external_network_id='test_net', dns_nameserver='test_dns', public=True, flavor_id='test_flavor', master_flavor_id='test_flavor', fixed_network='public', network_driver='test_driver', docker_storage_driver='devicemapper', docker_volume_size=10, labels={'key': 'val'}) mock_create.assert_called_with(**expected_args) self._test_arg_success('cluster-template-create ' '--keypair-id test_keypair ' '--external-network-id test_net ' '--image-id test_image ' '--coe kubernetes ' '--name test ') expected_args = \ self._get_expected_args(name='test', image_id='test_image', keypair_id='test_keypair', coe='kubernetes', external_network_id='test_net') mock_create.assert_called_with(**expected_args) @mock.patch( 'magnumclient.v1.cluster_templates.ClusterTemplateManager.create') def test_cluster_template_create_success_with_registry_enabled( self, mock_create): self._test_arg_success('cluster-template-create ' '--name test ' '--network-driver test_driver ' '--keypair-id test_keypair ' '--external-network-id test_net ' '--image-id test_image ' '--coe swarm ' '--registry-enabled') expected_args = \ self._get_expected_args(name='test', image_id='test_image', keypair_id='test_keypair', coe='swarm', external_network_id='test_net', network_driver='test_driver', registry_enabled=True) mock_create.assert_called_with(**expected_args) @mock.patch( 'magnumclient.v1.cluster_templates.ClusterTemplateManager.create') def test_cluster_template_create_public_success(self, mock_create): self._test_arg_success('cluster-template-create ' '--name test --network-driver test_driver ' '--keypair-id test_keypair ' '--external-network-id test_net ' '--image-id test_image ' '--coe swarm ' '--public ' '--server-type vm') expected_args = \ self._get_expected_args(name='test', image_id='test_image', keypair_id='test_keypair', coe='swarm', external_network_id='test_net', public=True, server_type='vm', network_driver='test_driver') mock_create.assert_called_with(**expected_args) @mock.patch( 'magnumclient.v1.cluster_templates.ClusterTemplateManager.create') def test_cluster_template_create_success_with_master_flavor(self, mock_create): self._test_arg_success('cluster-template-create ' '--name test ' '--image-id test_image ' '--keypair-id test_keypair ' '--external-network-id test_net ' '--coe swarm ' '--dns-nameserver test_dns ' '--master-flavor-id test_flavor') expected_args = \ self._get_expected_args(name='test', image_id='test_image', keypair_id='test_keypair', coe='swarm', external_network_id='test_net', dns_nameserver='test_dns', master_flavor_id='test_flavor') mock_create.assert_called_with(**expected_args) @mock.patch( 'magnumclient.v1.cluster_templates.ClusterTemplateManager.create') def test_cluster_template_create_docker_vol_size_success(self, mock_create): self._test_arg_success('cluster-template-create ' '--name test --docker-volume-size 4514 ' '--keypair-id test_keypair ' '--external-network-id test_net ' '--image-id test_image ' '--coe swarm ' '--server-type vm') expected_args = \ self._get_expected_args(name='test', image_id='test_image', keypair_id='test_keypair', coe='swarm', external_network_id='test_net', server_type='vm', docker_volume_size=4514) mock_create.assert_called_with(**expected_args) @mock.patch( 'magnumclient.v1.cluster_templates.ClusterTemplateManager.create') def test_cluster_template_create_docker_storage_driver_success( self, mock_create): self._test_arg_success('cluster-template-create ' '--name test ' '--keypair-id test_keypair ' '--external-network-id test_net ' '--image-id test_image ' '--docker-storage-driver devicemapper ' '--coe swarm' ) expected_args = \ self._get_expected_args(name='test', image_id='test_image', keypair_id='test_keypair', coe='swarm', external_network_id='test_net', docker_storage_driver='devicemapper') mock_create.assert_called_with(**expected_args) @mock.patch( 'magnumclient.v1.cluster_templates.ClusterTemplateManager.create') def test_cluster_template_create_fixed_network_success(self, mock_create): self._test_arg_success('cluster-template-create ' '--name test --fixed-network private ' '--keypair-id test_keypair ' '--external-network-id test_net ' '--image-id test_image ' '--coe swarm ' '--server-type vm') expected_args = \ self._get_expected_args(name='test', image_id='test_image', keypair_id='test_keypair', coe='swarm', fixed_network='private', external_network_id='test_net', server_type='vm') mock_create.assert_called_with(**expected_args) @mock.patch( 'magnumclient.v1.cluster_templates.ClusterTemplateManager.create') def test_cluster_template_create_network_driver_success(self, mock_create): self._test_arg_success('cluster-template-create ' '--name test --network-driver test_driver ' '--keypair-id test_keypair ' '--external-network-id test_net ' '--image-id test_image ' '--coe swarm ' '--server-type vm') expected_args = \ self._get_expected_args(name='test', image_id='test_image', keypair_id='test_keypair', coe='swarm', external_network_id='test_net', server_type='vm', network_driver='test_driver') mock_create.assert_called_with(**expected_args) @mock.patch( 'magnumclient.v1.cluster_templates.ClusterTemplateManager.create') def test_cluster_template_create_volume_driver_success(self, mock_create): self._test_arg_success('cluster-template-create ' '--name test --volume-driver test_volume ' '--keypair-id test_keypair ' '--external-network-id test_net ' '--image-id test_image ' '--coe swarm ' '--server-type vm') expected_args = \ self._get_expected_args(name='test', image_id='test_image', keypair_id='test_keypair', coe='swarm', external_network_id='test_net', server_type='vm', volume_driver='test_volume') mock_create.assert_called_with(**expected_args) @mock.patch( 'magnumclient.v1.cluster_templates.ClusterTemplateManager.create') def test_cluster_template_create_http_proxy_success(self, mock_create): self._test_arg_success('cluster-template-create ' '--name test --fixed-network private ' '--keypair-id test_keypair ' '--external-network-id test_net ' '--image-id test_image ' '--coe swarm ' '--http-proxy http_proxy ' '--server-type vm') expected_args = \ self._get_expected_args(name='test', image_id='test_image', keypair_id='test_keypair', coe='swarm', external_network_id='test_net', fixed_network='private', server_type='vm', http_proxy='http_proxy') mock_create.assert_called_with(**expected_args) @mock.patch( 'magnumclient.v1.cluster_templates.ClusterTemplateManager.create') def test_cluster_template_create_https_proxy_success(self, mock_create): self._test_arg_success('cluster-template-create ' '--name test --fixed-network private ' '--keypair-id test_keypair ' '--external-network-id test_net ' '--image-id test_image ' '--coe swarm ' '--https-proxy https_proxy ' '--server-type vm') expected_args = \ self._get_expected_args(name='test', image_id='test_image', keypair_id='test_keypair', coe='swarm', external_network_id='test_net', fixed_network='private', server_type='vm', https_proxy='https_proxy') mock_create.assert_called_with(**expected_args) @mock.patch( 'magnumclient.v1.cluster_templates.ClusterTemplateManager.create') def test_cluster_template_create_no_proxy_success(self, mock_create): self._test_arg_success('cluster-template-create ' '--name test --fixed-network private ' '--keypair-id test_keypair ' '--external-network-id test_net ' '--image-id test_image ' '--coe swarm ' '--no-proxy no_proxy ' '--server-type vm') expected_args = \ self._get_expected_args(name='test', image_id='test_image', keypair_id='test_keypair', coe='swarm', external_network_id='test_net', fixed_network='private', server_type='vm', no_proxy='no_proxy') mock_create.assert_called_with(**expected_args) @mock.patch( 'magnumclient.v1.cluster_templates.ClusterTemplateManager.create') def test_cluster_template_create_labels_success(self, mock_create): self._test_arg_success('cluster-template-create ' '--name test ' '--labels key=val ' '--keypair-id test_keypair ' '--external-network-id test_net ' '--image-id test_image ' '--coe swarm ' '--server-type vm') expected_args = \ self._get_expected_args(name='test', image_id='test_image', keypair_id='test_keypair', coe='swarm', external_network_id='test_net', server_type='vm', labels={'key': 'val'}) mock_create.assert_called_with(**expected_args) @mock.patch( 'magnumclient.v1.cluster_templates.ClusterTemplateManager.create') def test_cluster_template_create_separate_labels_success(self, mock_create): self._test_arg_success('cluster-template-create ' '--name test ' '--labels key1=val1 ' '--labels key2=val2 ' '--keypair-id test_keypair ' '--external-network-id test_net ' '--image-id test_image ' '--coe swarm ' '--server-type vm') expected_args = \ self._get_expected_args(name='test', image_id='test_image', keypair_id='test_keypair', coe='swarm', external_network_id='test_net', server_type='vm', labels={'key1': 'val1', 'key2': 'val2'}) mock_create.assert_called_with(**expected_args) @mock.patch( 'magnumclient.v1.cluster_templates.ClusterTemplateManager.create') def test_cluster_template_create_combined_labels_success(self, mock_create): self._test_arg_success('cluster-template-create ' '--name test ' '--labels key1=val1,key2=val2 ' '--keypair-id test_keypair ' '--external-network-id test_net ' '--image-id test_image ' '--coe swarm ' '--server-type vm') expected_args = \ self._get_expected_args(name='test', image_id='test_image', keypair_id='test_keypair', coe='swarm', external_network_id='test_net', server_type='vm', labels={'key1': 'val1', 'key2': 'val2'}) mock_create.assert_called_with(**expected_args) @mock.patch( 'magnumclient.v1.cluster_templates.ClusterTemplateManager.create') def test_cluster_template_create_success_only_positional_name(self, mock_create): self._test_arg_success('cluster-template-create ' 'test ' '--labels key1=val1,key2=val2 ' '--keypair-id test_keypair ' '--external-network-id test_net ' '--image-id test_image ' '--coe swarm ' '--server-type vm') expected_args = \ self._get_expected_args(name='test', image_id='test_image', keypair_id='test_keypair', coe='swarm', external_network_id='test_net', server_type='vm', labels={'key1': 'val1', 'key2': 'val2'}) mock_create.assert_called_with(**expected_args) @mock.patch( 'magnumclient.v1.cluster_templates.ClusterTemplateManager.create') def test_cluster_template_create_failure_duplicate_name(self, mock_create): self._test_arg_failure('cluster-template-create ' 'foo --name test', self._mandatory_arg_error) mock_create.assert_not_called() @mock.patch( 'magnumclient.v1.cluster_templates.ClusterTemplateManager.create') def test_cluster_template_create_failure_few_arg(self, mock_create): self._test_arg_failure('cluster-template-create ' '--name test', self._mandatory_arg_error) mock_create.assert_not_called() self._test_arg_failure('cluster-template-create ' '--image-id test', self._mandatory_arg_error) mock_create.assert_not_called() self._test_arg_failure('cluster-template-create ' '--keypair-id test', self._mandatory_arg_error) mock_create.assert_not_called() self._test_arg_failure('cluster-template-create ' '--external-network-id test', self._mandatory_arg_error) mock_create.assert_not_called() self._test_arg_failure('cluster-template-create ' '--coe test', self._mandatory_group_arg_error) mock_create.assert_not_called() self._test_arg_failure('cluster-template-create ' '--coe test ' '--external-network test ', self._mandatory_group_arg_error) mock_create.assert_not_called() self._test_arg_failure('cluster-template-create ' '--coe test ' '--image test ', self._mandatory_group_arg_error) mock_create.assert_not_called() self._test_arg_failure('cluster-template-create ' '--server-type test', self._mandatory_arg_error) mock_create.assert_not_called() self._test_arg_failure('cluster-template-create', self._mandatory_arg_error) mock_create.assert_not_called() @mock.patch( 'magnumclient.v1.cluster_templates.ClusterTemplateManager.create') def test_cluster_template_create_deprecation_errors(self, mock_create): required_args = ('cluster-template-create ' '--coe test --external-network public --image test ') self._test_arg_failure('cluster-template-create --coe test ' '--external-network-id test ' '--external-network test ', self._too_many_group_arg_error) mock_create.assert_not_called() self._test_arg_failure('cluster-template-create --coe test ' '--image-id test ' '--image test ', self._too_many_group_arg_error) mock_create.assert_not_called() self._test_arg_failure(required_args + '--flavor test --flavor-id test', self._too_many_group_arg_error) mock_create.assert_not_called() self._test_arg_failure(required_args + '--master-flavor test --master-flavor-id test', self._too_many_group_arg_error) mock_create.assert_not_called() self._test_arg_failure(required_args + '--keypair test --keypair-id test', self._too_many_group_arg_error) mock_create.assert_not_called() @mock.patch( 'magnumclient.v1.cluster_templates.ClusterTemplateManager.create') def test_cluster_template_create_deprecation_warnings(self, mock_create): required_args = ('cluster-template-create ' '--coe test --external-network public --image test ') self._test_arg_failure('cluster-template-create ' '--coe test ' '--external-network-id test ' '--image test ', self._deprecated_warning) expected_args = \ self._get_expected_args(image_id='test', coe='test', external_network_id='test') mock_create.assert_called_with(**expected_args) self._test_arg_failure('cluster-template-create ' '--coe test ' '--external-network test ' '--image-id test ', self._deprecated_warning) expected_args = \ self._get_expected_args(image_id='test', coe='test', external_network_id='test') mock_create.assert_called_with(**expected_args) self._test_arg_failure('cluster-template-create ' '--coe test ' '--external-network-id test ' '--image-id test ', self._deprecated_warning) expected_args = \ self._get_expected_args(image_id='test', coe='test', external_network_id='test') mock_create.assert_called_with(**expected_args) self._test_arg_failure(required_args + '--keypair-id test', self._deprecated_warning) expected_args = \ self._get_expected_args(image_id='test', coe='test', keypair_id='test', external_network_id='public') mock_create.assert_called_with(**expected_args) self._test_arg_failure(required_args + '--flavor-id test', self._deprecated_warning) expected_args = \ self._get_expected_args(image_id='test', coe='test', flavor_id='test', external_network_id='public') mock_create.assert_called_with(**expected_args) self._test_arg_failure(required_args + '--master-flavor-id test', self._deprecated_warning) expected_args = \ self._get_expected_args(image_id='test', coe='test', master_flavor_id='test', external_network_id='public') mock_create.assert_called_with(**expected_args) self._test_arg_failure(required_args + '--name foo', self._deprecated_warning) expected_args = \ self._get_expected_args(image_id='test', coe='test', name='foo', external_network_id='public') mock_create.assert_called_with(**expected_args) @mock.patch('magnumclient.v1.cluster_templates.ClusterTemplateManager.get') def test_cluster_template_show_success(self, mock_show): self._test_arg_success('cluster-template-show xxx') mock_show.assert_called_once_with('xxx') @mock.patch('magnumclient.v1.cluster_templates.ClusterTemplateManager.get') def test_cluster_template_show_failure_no_arg(self, mock_show): self._test_arg_failure('cluster-template-show', self._few_argument_error) mock_show.assert_not_called() @mock.patch( 'magnumclient.v1.cluster_templates.ClusterTemplateManager.delete') def test_cluster_template_delete_success(self, mock_delete): self._test_arg_success('cluster-template-delete xxx') mock_delete.assert_called_once_with('xxx') @mock.patch( 'magnumclient.v1.cluster_templates.ClusterTemplateManager.delete') def test_cluster_template_delete_multiple_id_success(self, mock_delete): self._test_arg_success('cluster-template-delete xxx xyz') calls = [mock.call('xxx'), mock.call('xyz')] mock_delete.assert_has_calls(calls) @mock.patch( 'magnumclient.v1.cluster_templates.ClusterTemplateManager.delete') def test_cluster_template_delete_failure_no_arg(self, mock_delete): self._test_arg_failure('cluster-template-delete', self._few_argument_error) mock_delete.assert_not_called() @mock.patch( 'magnumclient.v1.cluster_templates.ClusterTemplateManager.update') def test_cluster_template_update_success(self, mock_update): self._test_arg_success('cluster-template-update test add test=test') patch = [{'op': 'add', 'path': '/test', 'value': 'test'}] mock_update.assert_called_once_with('test', patch) @mock.patch( 'magnumclient.v1.cluster_templates.ClusterTemplateManager.update') def test_cluster_template_update_success_many_attribute(self, mock_update): self._test_arg_success('cluster-template-update test ' 'add test=test test1=test1') patch = [{'op': 'add', 'path': '/test', 'value': 'test'}, {'op': 'add', 'path': '/test1', 'value': 'test1'}] mock_update.assert_called_once_with('test', patch) @mock.patch( 'magnumclient.v1.cluster_templates.ClusterTemplateManager.update') def test_cluster_template_update_label(self, mock_update): self._test_arg_success('cluster-template-update test ' 'replace labels=key1=val1') patch = [{'op': 'replace', 'path': '/labels', 'value': "{'key1': 'val1'}"}] mock_update.assert_called_once_with('test', patch) @mock.patch( 'magnumclient.v1.cluster_templates.ClusterTemplateManager.update') def test_cluster_template_update_failure_wrong_op(self, mock_update): _error_msg = [ '.*?^usage: magnum cluster-template-update ', '.*?^error: argument : invalid choice: ', ".*?^Try 'magnum help cluster-template-update' " "for more information." ] self._test_arg_failure('cluster-template-update test wrong test=test', _error_msg) mock_update.assert_not_called() @mock.patch( 'magnumclient.v1.cluster_templates.ClusterTemplateManager.update') def test_cluster_template_update_failure_few_args(self, mock_update): _error_msg = [ '.*?^usage: magnum cluster-template-update ', '.*?^error: (the following arguments|too few arguments)', ".*?^Try 'magnum help cluster-template-update' " "for more information." ] self._test_arg_failure('cluster-template-update', _error_msg) mock_update.assert_not_called() self._test_arg_failure('cluster-template-update test', _error_msg) mock_update.assert_not_called() self._test_arg_failure('cluster-template-update test add', _error_msg) mock_update.assert_not_called() @mock.patch( 'magnumclient.v1.cluster_templates.ClusterTemplateManager.list') def test_cluster_template_list_success(self, mock_list): self._test_arg_success('cluster-template-list') expected_args = self._get_expected_args_list() mock_list.assert_called_once_with(**expected_args) @mock.patch( 'magnumclient.v1.cluster_templates.ClusterTemplateManager.list') def test_cluster_template_list_success_with_arg(self, mock_list): self._test_arg_success('cluster-template-list ' '--limit 1 ' '--sort-dir asc ' '--sort-key uuid') expected_args = self._get_expected_args_list(1, 'asc', 'uuid') mock_list.assert_called_once_with(**expected_args) @mock.patch( 'magnumclient.v1.cluster_templates.ClusterTemplateManager.list') def test_cluster_template_list_success_detailed(self, mock_list): self._test_arg_success('cluster-template-list ' '--detail') expected_args = self._get_expected_args_list(detail=True) mock_list.assert_called_once_with(**expected_args) @mock.patch( 'magnumclient.v1.cluster_templates.ClusterTemplateManager.list') def test_cluster_template_list_ignored_duplicated_field(self, mock_list): mock_list.return_value = [FakeClusterTemplate()] self._test_arg_success( 'cluster-template-list --fields coe,coe,coe,name,name', keyword='\n| uuid | name | Coe |\n') # Output should be # +------+------+-----+ # | uuid | name | Coe | # +------+------+-----+ # | x | x | x | # +------+------+-----+ expected_args = self._get_expected_args_list() mock_list.assert_called_once_with(**expected_args) @mock.patch( 'magnumclient.v1.cluster_templates.ClusterTemplateManager.list') def test_cluster_template_list_failure_with_invalid_field(self, mock_list): mock_list.return_value = [FakeClusterTemplate()] _error_msg = [".*?^Non-existent fields are specified: ['xxx','zzz']"] self.assertRaises(exceptions.CommandError, self._test_arg_failure, 'cluster-template-list --fields xxx,coe,zzz', _error_msg) expected_args = self._get_expected_args_list() mock_list.assert_called_once_with(**expected_args) @mock.patch( 'magnumclient.v1.cluster_templates.ClusterTemplateManager.list') def test_cluster_template_list_failure_invalid_arg(self, mock_list): _error_msg = [ '.*?^usage: magnum cluster-template-list ', '.*?^error: argument --sort-dir: invalid choice: ', ".*?^Try 'magnum help cluster-template-list' for more information." ] self._test_arg_failure('cluster-template-list --sort-dir aaa', _error_msg) mock_list.assert_not_called() @mock.patch( 'magnumclient.v1.cluster_templates.ClusterTemplateManager.list') def test_cluster_template_list_failure(self, mock_list): self._test_arg_failure('cluster-template-list --wrong', self._unrecognized_arg_error) mock_list.assert_not_called() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/v1/test_mservices.py0000664000175000017500000001104500000000000025417 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 testtools from testtools import matchers from magnumclient.tests import utils from magnumclient.v1 import mservices SERVICE1 = {'id': 123, 'host': 'fake-host1', 'binary': 'fake-bin1', 'state': 'up', } SERVICE2 = {'id': 124, 'host': 'fake-host2', 'binary': 'fake-bin2', 'state': 'down', } fake_responses = { '/v1/mservices': { 'GET': ( {}, {'mservices': [SERVICE1, SERVICE2]}, ), }, '/v1/mservices/?limit=2': { 'GET': ( {}, {'mservices': [SERVICE1, SERVICE2]}, ), }, '/v1/mservices/?marker=%s' % SERVICE2['id']: { 'GET': ( {}, {'mservices': [SERVICE1, SERVICE2]}, ), }, '/v1/mservices/?limit=2&marker=%s' % SERVICE2['id']: { 'GET': ( {}, {'mservices': [SERVICE2, SERVICE1]}, ), }, '/v1/mservices/?sort_dir=asc': { 'GET': ( {}, {'mservices': [SERVICE1, SERVICE2]}, ), }, '/v1/mservices/?sort_key=id': { 'GET': ( {}, {'mservices': [SERVICE1, SERVICE2]}, ), }, '/v1/mservices/?sort_key=id&sort_dir=desc': { 'GET': ( {}, {'mservices': [SERVICE2, SERVICE1]}, ), }, } class MServiceManagerTest(testtools.TestCase): def setUp(self): super(MServiceManagerTest, self).setUp() self.api = utils.FakeAPI(fake_responses) self.mgr = mservices.MServiceManager(self.api) def test_coe_service_list(self): mservices = self.mgr.list() expect = [ ('GET', '/v1/mservices', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(mservices, matchers.HasLength(2)) def _test_coe_service_list_with_filters( self, limit=None, marker=None, sort_key=None, sort_dir=None, detail=False, expect=[]): mservices_filter = self.mgr.list(limit=limit, marker=marker, sort_key=sort_key, sort_dir=sort_dir, detail=detail) self.assertEqual(expect, self.api.calls) self.assertThat(mservices_filter, matchers.HasLength(2)) def test_coe_service_list_with_limit(self): expect = [ ('GET', '/v1/mservices/?limit=2', {}, None), ] self._test_coe_service_list_with_filters( limit=2, expect=expect) def test_coe_service_list_with_marker(self): expect = [ ('GET', '/v1/mservices/?marker=%s' % SERVICE2['id'], {}, None), ] self._test_coe_service_list_with_filters( marker=SERVICE2['id'], expect=expect) def test_coe_service_list_with_marker_limit(self): expect = [ ('GET', '/v1/mservices/?limit=2&marker=%s' % SERVICE2['id'], {}, None), ] self._test_coe_service_list_with_filters( limit=2, marker=SERVICE2['id'], expect=expect) def test_coe_service_list_with_sort_dir(self): expect = [ ('GET', '/v1/mservices/?sort_dir=asc', {}, None), ] self._test_coe_service_list_with_filters( sort_dir='asc', expect=expect) def test_coe_service_list_with_sort_key(self): expect = [ ('GET', '/v1/mservices/?sort_key=id', {}, None), ] self._test_coe_service_list_with_filters( sort_key='id', expect=expect) def test_coe_service_list_with_sort_key_dir(self): expect = [ ('GET', '/v1/mservices/?sort_key=id&sort_dir=desc', {}, None), ] self._test_coe_service_list_with_filters( sort_key='id', sort_dir='desc', expect=expect) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/v1/test_mservices_shell.py0000664000175000017500000000236100000000000026607 0ustar00zuulzuul00000000000000# Copyright 2015 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock from magnumclient.tests.v1 import shell_test_base class ShellTest(shell_test_base.TestCommandLineArgument): @mock.patch('magnumclient.v1.mservices.MServiceManager.list') def test_magnum_service_list_success(self, mock_list): self._test_arg_success('service-list') mock_list.assert_called_once_with() @mock.patch('magnumclient.v1.mservices.MServiceManager.list') def test_magnum_service_list_failure(self, mock_list): self._test_arg_failure('service-list --wrong', self._unrecognized_arg_error) mock_list.assert_not_called() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/v1/test_nodegroups.py0000664000175000017500000002461400000000000025612 0ustar00zuulzuul00000000000000# Copyright (c) 2018 European Organization for Nuclear Research. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy import testtools from testtools import matchers from magnumclient import exceptions from magnumclient.tests import utils from magnumclient.v1 import nodegroups NODEGROUP1 = { 'id': 123, 'uuid': '66666666-7777-8888-9999-000000000001', 'cluster_id': '66666666-7777-8888-9999-000000000000', 'name': 'test-worker', 'node_addresses': ['172.17.2.3'], 'node_count': 2, 'project_id': 'fake_project', 'labels': {}, 'flavor_id': 'fake_flavor_1', 'image_id': 'fake_image', 'is_default': True, 'role': 'worker', 'max_node_count': 10, 'min_node_count': 0 } NODEGROUP2 = { 'id': 124, 'uuid': '66666666-7777-8888-9999-000000000002', 'cluster_id': '66666666-7777-8888-9999-000000000000', 'name': 'test-master', 'node_addresses': ['172.17.2.4'], 'node_count': 2, 'project_id': 'fake_project', 'labels': {}, 'flavor_id': 'fake_flavor_1', 'image_id': 'fake_image', 'is_default': True, 'role': 'master', 'max_node_count': 10, 'min_node_count': 0 } CREATE_NODEGROUP = copy.deepcopy(NODEGROUP1) del CREATE_NODEGROUP['id'] del CREATE_NODEGROUP['uuid'] del CREATE_NODEGROUP['node_addresses'] del CREATE_NODEGROUP['is_default'] del CREATE_NODEGROUP['cluster_id'] UPDATED_NODEGROUP = copy.deepcopy(NODEGROUP1) NEW_NODE_COUNT = 9 UPDATED_NODEGROUP['node_count'] = NEW_NODE_COUNT fake_responses = { '/v1/clusters/test/nodegroups/': { 'GET': ( {}, {'nodegroups': [NODEGROUP1, NODEGROUP2]}, ), 'POST': ( {}, CREATE_NODEGROUP, ), }, '/v1/clusters/test/nodegroups/%s' % NODEGROUP1['id']: { 'GET': ( {}, NODEGROUP1 ), 'DELETE': ( {}, None, ), 'PATCH': ( {}, UPDATED_NODEGROUP, ), }, '/v1/clusters/test/nodegroups/%s/?rollback=True' % NODEGROUP1['id']: { 'PATCH': ( {}, UPDATED_NODEGROUP, ), }, '/v1/clusters/test/nodegroups/%s' % NODEGROUP1['name']: { 'GET': ( {}, NODEGROUP1 ), 'DELETE': ( {}, None, ), 'PATCH': ( {}, UPDATED_NODEGROUP, ), }, '/v1/clusters/test/nodegroups/?limit=2': { 'GET': ( {}, {'nodegroups': [NODEGROUP1, NODEGROUP2]}, ), }, '/v1/clusters/test/nodegroups/?marker=%s' % NODEGROUP2['uuid']: { 'GET': ( {}, {'nodegroups': [NODEGROUP1, NODEGROUP2]}, ), }, '/v1/clusters/test/nodegroups/?limit=2&marker=%s' % NODEGROUP2['uuid']: { 'GET': ( {}, {'nodegroups': [NODEGROUP1, NODEGROUP2]}, ), }, '/v1/clusters/test/nodegroups/?sort_dir=asc': { 'GET': ( {}, {'nodegroups': [NODEGROUP1, NODEGROUP2]}, ), }, '/v1/clusters/test/nodegroups/?sort_key=uuid': { 'GET': ( {}, {'nodegroups': [NODEGROUP1, NODEGROUP2]}, ), }, '/v1/clusters/test/nodegroups/?sort_key=uuid&sort_dir=desc': { 'GET': ( {}, {'nodegroups': [NODEGROUP2, NODEGROUP1]}, ), }, } class NodeGroupManagerTest(testtools.TestCase): def setUp(self): super(NodeGroupManagerTest, self).setUp() self.api = utils.FakeAPI(fake_responses) self.mgr = nodegroups.NodeGroupManager(self.api) self.cluster_id = 'test' self.base_path = '/v1/clusters/test/nodegroups/' def test_nodegroup_list(self): clusters = self.mgr.list(self.cluster_id) expect = [ ('GET', self.base_path, {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(clusters, matchers.HasLength(2)) def _test_nodegroup_list_with_filters(self, cluster_id, limit=None, marker=None, sort_key=None, sort_dir=None, detail=False, expect=[]): nodegroup_filter = self.mgr.list(cluster_id, limit=limit, marker=marker, sort_key=sort_key, sort_dir=sort_dir, detail=detail) self.assertEqual(expect, self.api.calls) self.assertThat(nodegroup_filter, matchers.HasLength(2)) def test_nodegroup_list_with_limit(self): expect = [ ('GET', self.base_path + '?limit=2', {}, None), ] self._test_nodegroup_list_with_filters( self.cluster_id, limit=2, expect=expect) def test_nodegroup_list_with_marker(self): filter_ = '?marker=%s' % NODEGROUP2['uuid'] expect = [ ('GET', self.base_path + filter_, {}, None), ] self._test_nodegroup_list_with_filters( self.cluster_id, marker=NODEGROUP2['uuid'], expect=expect) def test_nodegroup_list_with_marker_limit(self): filter_ = '?limit=2&marker=%s' % NODEGROUP2['uuid'] expect = [ ('GET', self.base_path + filter_, {}, None), ] self._test_nodegroup_list_with_filters( self.cluster_id, limit=2, marker=NODEGROUP2['uuid'], expect=expect) def test_nodegroup_list_with_sort_dir(self): expect = [ ('GET', '/v1/clusters/test/nodegroups/?sort_dir=asc', {}, None), ] self._test_nodegroup_list_with_filters( self.cluster_id, sort_dir='asc', expect=expect) def test_nodegroup_list_with_sort_key(self): expect = [ ('GET', '/v1/clusters/test/nodegroups/?sort_key=uuid', {}, None), ] self._test_nodegroup_list_with_filters( self.cluster_id, sort_key='uuid', expect=expect) def test_nodegroup_list_with_sort_key_dir(self): expect = [ ('GET', self.base_path + '?sort_key=uuid&sort_dir=desc', {}, None), ] self._test_nodegroup_list_with_filters( self.cluster_id, sort_key='uuid', sort_dir='desc', expect=expect) def test_nodegroup_show_by_name(self): nodegroup = self.mgr.get(self.cluster_id, NODEGROUP1['name']) expect = [ ('GET', self.base_path + '%s' % NODEGROUP1['name'], {}, None) ] self.assertEqual(expect, self.api.calls) self.assertEqual(NODEGROUP1['name'], nodegroup.name) def test_nodegroup_show_by_id(self): nodegroup = self.mgr.get(self.cluster_id, NODEGROUP1['id']) expect = [ ('GET', self.base_path + '%s' % NODEGROUP1['id'], {}, None) ] self.assertEqual(expect, self.api.calls) self.assertEqual(NODEGROUP1['name'], nodegroup.name) def test_nodegroup_delete_by_id(self): nodegroup = self.mgr.delete(self.cluster_id, NODEGROUP1['id']) expect = [ ('DELETE', self.base_path + '%s' % NODEGROUP1['id'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertIsNone(nodegroup) def test_nodegroup_delete_by_name(self): nodegroup = self.mgr.delete(self.cluster_id, NODEGROUP1['name']) expect = [ ('DELETE', self.base_path + '%s' % NODEGROUP1['name'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertIsNone(nodegroup) def test_nodegroup_update(self): patch = {'op': 'replace', 'value': NEW_NODE_COUNT, 'path': '/node_count'} nodegroup = self.mgr.update(self.cluster_id, id=NODEGROUP1['id'], patch=patch) expect = [ ('PATCH', self.base_path + '%s' % NODEGROUP1['id'], {}, patch), ] self.assertEqual(expect, self.api.calls) self.assertEqual(NEW_NODE_COUNT, nodegroup.node_count) def test_nodegroup_create(self): nodegroup = self.mgr.create(self.cluster_id, **CREATE_NODEGROUP) expect = [ ('POST', self.base_path, {}, CREATE_NODEGROUP), ] self.assertEqual(expect, self.api.calls) self.assertTrue(nodegroup) def test_nodegroup_create_with_docker_volume_size(self): ng_with_volume_size = dict() ng_with_volume_size.update(CREATE_NODEGROUP) ng_with_volume_size['docker_volume_size'] = 20 nodegroup = self.mgr.create(self.cluster_id, **ng_with_volume_size) expect = [ ('POST', self.base_path, {}, ng_with_volume_size), ] self.assertEqual(expect, self.api.calls) self.assertTrue(nodegroup) def test_nodegroup_create_with_labels(self): ng_with_labels = dict() ng_with_labels.update(CREATE_NODEGROUP) ng_with_labels['labels'] = "key=val" nodegroup = self.mgr.create(self.cluster_id, **ng_with_labels) expect = [ ('POST', self.base_path, {}, ng_with_labels), ] self.assertEqual(expect, self.api.calls) self.assertTrue(nodegroup) def test_nodegroup_create_fail(self): CREATE_NODEGROUP_FAIL = copy.deepcopy(CREATE_NODEGROUP) CREATE_NODEGROUP_FAIL["wrong_key"] = "wrong" self.assertRaisesRegex(exceptions.InvalidAttribute, ("Key must be in %s" % ','.join(nodegroups.CREATION_ATTRIBUTES)), self.mgr.create, self.cluster_id, **CREATE_NODEGROUP_FAIL) self.assertEqual([], self.api.calls) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/v1/test_quotas.py0000664000175000017500000001027400000000000024736 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 copy import testtools from testtools import matchers from magnumclient.tests import utils from magnumclient.v1 import quotas QUOTA1 = { 'id': 123, 'resource': "Cluster", 'hard_limit': 5, 'project_id': 'abc' } QUOTA2 = { 'id': 124, 'resource': "Cluster", 'hard_limit': 10, 'project_id': 'bcd' } CREATE_QUOTA = copy.deepcopy(QUOTA1) del CREATE_QUOTA['id'] UPDATED_QUOTA = copy.deepcopy(QUOTA2) NEW_HARD_LIMIT = 20 UPDATED_QUOTA['hard_limit'] = NEW_HARD_LIMIT fake_responses = { '/v1/quotas?all_tenants=True': { 'GET': ( {}, {'quotas': [QUOTA1, QUOTA2]}, ), }, '/v1/quotas': { 'GET': ( {}, {'quotas': [QUOTA1]}, ), 'POST': ( {}, QUOTA1, ), }, '/v1/quotas/%(id)s/%(res)s' % {'id': QUOTA2['project_id'], 'res': QUOTA2['resource']}: { 'GET': ( {}, QUOTA2, ), 'PATCH': ( {}, UPDATED_QUOTA, ), 'DELETE': ( {}, None, ), }, } class QuotasManagerTest(testtools.TestCase): def setUp(self): super(QuotasManagerTest, self).setUp() self.api = utils.FakeAPI(fake_responses) self.mgr = quotas.QuotasManager(self.api) def test_list_quotas(self): quotas = self.mgr.list() expect = [ ('GET', '/v1/quotas', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(quotas, matchers.HasLength(1)) def test_list_quotas_all(self): quotas = self.mgr.list(all_tenants=True) expect = [ ('GET', '/v1/quotas?all_tenants=True', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(quotas, matchers.HasLength(2)) def test_show_project_resource_quota(self): expect = [ ('GET', '/v1/quotas/%(id)s/%(res)s' % {'id': QUOTA2['project_id'], 'res': QUOTA2['resource']}, {}, None), ] quotas = self.mgr.get(QUOTA2['project_id'], QUOTA2['resource']) self.assertEqual(expect, self.api.calls) expected_quotas = QUOTA2 self.assertEqual(expected_quotas, quotas._info) def test_quota_create(self): quota = self.mgr.create(**CREATE_QUOTA) expect = [ ('POST', '/v1/quotas', {}, CREATE_QUOTA), ] self.assertEqual(expect, self.api.calls) self.assertEqual(QUOTA1, quota._info) def test_quota_update(self): patch = { 'resource': "Cluster", 'hard_limit': NEW_HARD_LIMIT, 'project_id': 'bcd' } quota = self.mgr.update(id=QUOTA2['project_id'], resource=QUOTA2['resource'], patch=patch) expect = [ ('PATCH', '/v1/quotas/%(id)s/%(res)s' % { 'id': QUOTA2['project_id'], 'res': QUOTA2['resource']}, {}, patch), ] self.assertEqual(expect, self.api.calls) self.assertEqual(NEW_HARD_LIMIT, quota.hard_limit) def test_quota_delete(self): quota = self.mgr.delete(QUOTA2['project_id'], QUOTA2['resource']) expect = [ ('DELETE', '/v1/quotas/%(id)s/%(res)s' % {'id': QUOTA2['project_id'], 'res': QUOTA2['resource']}, {}, None), ] self.assertEqual(expect, self.api.calls) self.assertIsNone(quota) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/v1/test_quotas_shell.py0000664000175000017500000001240400000000000026122 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 magnumclient.tests.v1 import shell_test_base class ShellTest(shell_test_base.TestCommandLineArgument): def _get_expected_args_list(self, marker=None, limit=None, sort_dir=None, sort_key=None, all_tenants=False): expected_args = {} expected_args['marker'] = marker expected_args['limit'] = limit expected_args['sort_dir'] = sort_dir expected_args['sort_key'] = sort_key expected_args['all_tenants'] = False return expected_args def _get_expected_args_create(self, project_id, resource, hard_limit): expected_args = {} expected_args['project_id'] = project_id expected_args['resource'] = resource expected_args['hard_limit'] = hard_limit return expected_args @mock.patch('magnumclient.v1.quotas.QuotasManager.list') def test_quotas_list_success(self, mock_list): self._test_arg_success('quotas-list') expected_args = self._get_expected_args_list() mock_list.assert_called_once_with(**expected_args) @mock.patch('magnumclient.v1.quotas.QuotasManager.list') def test_quotas_list_failure(self, mock_list): self._test_arg_failure('quotas-list --wrong', self._unrecognized_arg_error) mock_list.assert_not_called() @mock.patch('magnumclient.v1.quotas.QuotasManager.create') def test_quotas_create_success(self, mock_create): self._test_arg_success('quotas-create --project-id abc ' '--resource Cluster ' '--hard-limit 15') expected_args = self._get_expected_args_create('abc', 'Cluster', 15) mock_create.assert_called_with(**expected_args) @mock.patch('magnumclient.v1.quotas.QuotasManager.create') def test_quotas_create_failure_only_project_id(self, mock_create): self._test_arg_failure('quotas-create --project-id abc', self._mandatory_arg_error) mock_create.assert_not_called() @mock.patch('magnumclient.v1.quotas.QuotasManager.create') def test_quotas_create_failure_only_resource(self, mock_create): self._test_arg_failure('quotas-create --resource Cluster', self._mandatory_arg_error) mock_create.assert_not_called() @mock.patch('magnumclient.v1.quotas.QuotasManager.create') def test_quotas_create_failure_only_hard_limit(self, mock_create): self._test_arg_failure('quotas-create --hard-limit 10', self._mandatory_arg_error) mock_create.assert_not_called() @mock.patch('magnumclient.v1.quotas.QuotasManager.create') def test_quotas_create_failure_no_arg(self, mock_create): self._test_arg_failure('quotas-create', self._mandatory_arg_error) mock_create.assert_not_called() @mock.patch('magnumclient.v1.quotas.QuotasManager.delete') def test_quotas_delete_success(self, mock_delete): self._test_arg_success( 'quotas-delete --project-id xxx --resource Cluster') mock_delete.assert_called_once_with('xxx', 'Cluster') @mock.patch('magnumclient.v1.quotas.QuotasManager.delete') def test_quotas_delete_failure_no_project_id(self, mock_delete): self._test_arg_failure('quotas-delete --resource Cluster', self._mandatory_arg_error) mock_delete.assert_not_called() @mock.patch('magnumclient.v1.quotas.QuotasManager.delete') def test_quotas_delete_failure_no_resource(self, mock_delete): self._test_arg_failure('quotas-delete --project-id xxx', self._mandatory_arg_error) mock_delete.assert_not_called() @mock.patch('magnumclient.v1.quotas.QuotasManager.get') def test_quotas_show_success(self, mock_show): self._test_arg_success('quotas-show --project-id abc ' '--resource Cluster') mock_show.assert_called_once_with('abc', 'Cluster') @mock.patch('magnumclient.v1.quotas.QuotasManager.get') def test_quotas_show_failure_no_arg(self, mock_show): self._test_arg_failure('quotas-show', self._mandatory_arg_error) mock_show.assert_not_called() @mock.patch('magnumclient.v1.quotas.QuotasManager.update') def test_quotas_update_success(self, mock_update): self._test_arg_success('quotas-update --project-id abc ' '--resource Cluster ' '--hard-limit 20') patch = {'project_id': 'abc', 'resource': 'Cluster', 'hard_limit': 20} mock_update.assert_called_once_with('abc', 'Cluster', patch) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/v1/test_stats.py0000664000175000017500000000557400000000000024567 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 testtools from magnumclient.tests import utils from magnumclient.v1 import stats CLUSTER1 = {'id': 123, 'uuid': '66666666-7777-8888-9999-000000000001', 'name': 'cluster1', 'cluster_template_id': 'e74c40e0-d825-11e2-a28f-0800200c9a61', 'stack_id': '5d12f6fd-a196-4bf0-ae4c-1f639a523a51', 'api_address': '172.17.2.1', 'node_addresses': ['172.17.2.3'], 'node_count': 2, 'master_count': 1, 'project_id': 'abc' } CLUSTER2 = {'id': 124, 'uuid': '66666666-7777-8888-9999-000000000002', 'name': 'cluster2', 'cluster_template_id': 'e74c40e0-d825-11e2-a28f-0800200c9a62', 'stack_id': '5d12f6fd-a196-4bf0-ae4c-1f639a523a52', 'api_address': '172.17.2.2', 'node_addresses': ['172.17.2.4'], 'node_count': 2, 'master_count': 1, 'project_id': 'bcd' } nc = 'node_count' mc = 'master_count' C1 = CLUSTER1 C2 = CLUSTER2 fake_responses = { '/v1/stats': { 'GET': ( {}, {'clusters': 2, 'nodes': C1[nc] + C1[mc] + C2[nc] + C2[mc]}, ) }, '/v1/stats?project_id=%s' % C2['project_id']: { 'GET': ( {}, {'clusters': 1, 'nodes': C2[nc] + C2[mc]}, ) }, } class StatsManagerTest(testtools.TestCase): def setUp(self): super(StatsManagerTest, self).setUp() self.api = utils.FakeAPI(fake_responses) self.mgr = stats.StatsManager(self.api) def test_stats(self): stats = self.mgr.list() expect = [ ('GET', '/v1/stats', {}, None), ] self.assertEqual(expect, self.api.calls) expected_stats = {'clusters': 2, 'nodes': C1[nc] + C1[mc] + C2[nc] + C2[mc]} self.assertEqual(expected_stats, stats._info) def test_stats_with_project_id(self): expect = [ ('GET', '/v1/stats?project_id=%s' % CLUSTER2['project_id'], {}, None), ] stats = self.mgr.list(project_id=CLUSTER2['project_id']) self.assertEqual(expect, self.api.calls) expected_stats = {'clusters': 1, 'nodes': C2[nc] + C2[mc]} self.assertEqual(expected_stats, stats._info) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/tests/v1/test_stats_shell.py0000664000175000017500000000376400000000000025755 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 magnumclient.tests.v1 import shell_test_base from magnumclient.v1.stats import Stats class FakeStats(Stats): def __init__(self, manager=None, info={}, **kwargs): Stats.__init__(self, manager=manager, info=info) self.clusters = kwargs.get('clusters', 0) self.nodes = kwargs.get('nodes', 0) class ShellTest(shell_test_base.TestCommandLineArgument): def _get_expected_args_list(self, project_id=None): expected_args = {} expected_args['project_id'] = project_id return expected_args @mock.patch( 'magnumclient.v1.stats.StatsManager.list') def test_stats_get_success(self, mock_list): self._test_arg_success('stats-list') expected_args = self._get_expected_args_list() mock_list.assert_called_once_with(**expected_args) @mock.patch( 'magnumclient.v1.stats.StatsManager.list') def test_stats_get_success_with_arg(self, mock_list): self._test_arg_success('stats-list ' '--project-id 111 ') expected_args = self._get_expected_args_list('111') mock_list.assert_called_once_with(**expected_args) @mock.patch( 'magnumclient.v1.stats.StatsManager.list') def test_stats_get_failure(self, mock_list): self._test_arg_failure('stats-list --wrong', self._unrecognized_arg_error) mock_list.assert_not_called() ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740683659.4186947 python_magnumclient-4.8.0/magnumclient/v1/0000775000175000017500000000000000000000000020643 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/v1/__init__.py0000664000175000017500000000000000000000000022742 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/v1/basemodels.py0000664000175000017500000001074700000000000023344 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 magnumclient.common import base from magnumclient.common import utils from magnumclient import exceptions CREATION_ATTRIBUTES = ['name', 'image_id', 'flavor_id', 'master_flavor_id', 'keypair_id', 'external_network_id', 'fixed_network', 'fixed_subnet', 'dns_nameserver', 'docker_volume_size', 'labels', 'coe', 'http_proxy', 'https_proxy', 'no_proxy', 'network_driver', 'tls_disabled', 'public', 'registry_enabled', 'volume_driver', 'server_type', 'docker_storage_driver', 'master_lb_enabled', 'floating_ip_enabled', 'hidden', 'tags'] OUTPUT_ATTRIBUTES = CREATION_ATTRIBUTES + ['apiserver_port', 'created_at', 'insecure_registry', 'links', 'updated_at', 'cluster_distro', 'uuid'] class BaseModel(base.Resource): # model_name needs to be overridden by any derived class. # model_name should be capitalized and singular, e.g. "Cluster" model_name = '' def __repr__(self): return "<" + self.__class__.model_name + "%s>" % self._info class BaseModelManager(base.Manager): # api_name needs to be overridden by any derived class. # api_name should be pluralized and lowercase, e.g. "clustertemplates", as # it shows up in the URL path: "/v1/{api_name}" api_name = '' @classmethod def _path(cls, id=None): return '/v1/' + cls.api_name + \ '/%s' % id if id else '/v1/' + cls.api_name def list(self, limit=None, marker=None, sort_key=None, sort_dir=None, detail=False): """Retrieve a list of cluster templates. :param marker: Optional, the UUID of a template, eg the last template from a previous result set. Return the next result set. :param limit: The maximum number of results to return per request, if: 1) limit > 0, the maximum number of cluster templates to return. 2) limit == 0, return the entire list of cluster templates. 3) limit param is NOT specified (None), the number of items returned respect the maximum imposed by the Magnum API (see Magnum's api.max_limit option). :param sort_key: Optional, field used for sorting. :param sort_dir: Optional, direction of sorting, either 'asc' (the default) or 'desc'. :param detail: Optional, boolean whether to return detailed information about cluster templates. :returns: A list of cluster templates. """ if limit is not None: limit = int(limit) filters = utils.common_filters(marker, limit, sort_key, sort_dir) path = '' if detail: path += 'detail' if filters: path += '?' + '&'.join(filters) if limit is None: return self._list(self._path(path), self.__class__.api_name) else: return self._list_pagination(self._path(path), self.__class__.api_name, limit=limit) def get(self, id): try: return self._list(self._path(id))[0] except IndexError: return None def create(self, **kwargs): new = {} for (key, value) in kwargs.items(): if key in CREATION_ATTRIBUTES: new[key] = value else: raise exceptions.InvalidAttribute( "Key must be in %s" % ",".join(CREATION_ATTRIBUTES)) return self._create(self._path(), new) def delete(self, id): return self._delete(self._path(id)) def update(self, id, patch): return self._update(self._path(id), patch) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/v1/baseunit.py0000664000175000017500000000755700000000000023045 0ustar00zuulzuul00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from magnumclient.common import base from magnumclient.common import utils from magnumclient import exceptions # Derived classes may append their own custom attributes to this default list CREATION_ATTRIBUTES = ['name', 'node_count', 'discovery_url', 'master_count'] class BaseTemplate(base.Resource): # template_name must be overridden by any derived class. # template_name should be an uppercase plural, e.g. "Clusters" template_name = '' def __repr__(self): return "<" + self.__class__.template_name + " %s>" % self._info class BaseTemplateManager(base.Manager): # template_name must be overridden by any derived class. # template_name should be a lowercase plural, e.g. "clusters" template_name = '' @classmethod def _path(cls, id=None): return '/v1/' + cls.template_name + \ '/%s' % id if id else '/v1/' + cls.template_name def list(self, limit=None, marker=None, sort_key=None, sort_dir=None, detail=False): """Retrieve a list of clusters. :param marker: Optional, the UUID of a cluster, eg the last cluster from a previous result set. Return the next result set. :param limit: The maximum number of results to return per request, if: 1) limit > 0, the maximum number of clusters to return. 2) limit == 0, return the entire list of clusters. 3) limit param is NOT specified (None), the number of items returned respect the maximum imposed by the Magnum API (see Magnum's api.max_limit option). :param sort_key: Optional, field used for sorting. :param sort_dir: Optional, direction of sorting, either 'asc' (the default) or 'desc'. :param detail: Optional, boolean whether to return detailed information about clusters. :returns: A list of clusters. """ if limit is not None: limit = int(limit) filters = utils.common_filters(marker, limit, sort_key, sort_dir) path = '' if detail: path += 'detail' if filters: path += '?' + '&'.join(filters) if limit is None: return self._list(self._path(path), self.__class__.template_name) else: return self._list_pagination(self._path(path), self.__class__.template_name, limit=limit) def get(self, id): try: return self._list(self._path(id))[0] except IndexError: return None def create(self, **kwargs): new = {} for (key, value) in kwargs.items(): if key in CREATION_ATTRIBUTES: new[key] = value else: raise exceptions.InvalidAttribute( "Key must be in %s" % ",".join(CREATION_ATTRIBUTES)) return self._create(self._path(), new) def delete(self, id): return self._delete(self._path(id)) def update(self, id, patch, rollback=False): url = self._path(id) if rollback: url += '/?rollback=True' return self._update(url, patch) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/v1/certificates.py0000664000175000017500000000320300000000000023660 0ustar00zuulzuul00000000000000# Copyright 2015 Rackspace, 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 magnumclient.common import base from magnumclient import exceptions CREATION_ATTRIBUTES = ['cluster_uuid', 'csr'] class Certificate(base.Resource): def __repr__(self): return "" % self._info class CertificateManager(base.Manager): resource_class = Certificate @staticmethod def _path(id=None): return '/v1/certificates/%s' % id if id else '/v1/certificates' def get(self, cluster_uuid): try: return self._list(self._path(cluster_uuid))[0] except IndexError: return None def create(self, **kwargs): new = {} for (key, value) in kwargs.items(): if key in CREATION_ATTRIBUTES: new[key] = value else: raise exceptions.InvalidAttribute( "Key must be in %s" % ",".join(CREATION_ATTRIBUTES)) return self._create(self._path(), new) def rotate_ca(self, **kwargs): return self._update(self._path(id=kwargs['cluster_uuid'])) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/v1/certificates_shell.py0000664000175000017500000000553100000000000025055 0ustar00zuulzuul00000000000000# Copyright 2015 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import os.path from magnumclient.common import cliutils as utils from magnumclient.i18n import _ def _show_cert(certificate): print(certificate.pem) def _get_target_uuid(cs, args): target = None if args.cluster: target = cs.clusters.get(args.cluster) else: raise utils.MissingArgs(['--cluster']) return target.uuid @utils.arg('postional_cluster', metavar='', nargs='?', default=None, help=_('ID or name of the cluster.')) @utils.arg('--cluster', metavar='', default=None, help=(_('ID or name of the cluster. %s') % utils.CLUSTER_DEPRECATION_HELP)) @utils.deprecated(utils.MAGNUM_CLIENT_DEPRECATION_WARNING) def do_ca_show(cs, args): """Show details about the CA certificate for a cluster.""" utils.validate_cluster_args(args.postional_cluster, args.cluster) args.cluster = args.postional_cluster or args.cluster opts = { 'cluster_uuid': _get_target_uuid(cs, args) } cert = cs.certificates.get(**opts) _show_cert(cert) @utils.arg('--csr', metavar='', help=_('File path of the csr file to send to Magnum' ' to get signed.')) @utils.arg('--cluster', required=False, metavar='', help=_('ID or name of the cluster.')) @utils.deprecated(utils.MAGNUM_CLIENT_DEPRECATION_WARNING) def do_ca_sign(cs, args): """Generate the CA certificate for a cluster.""" opts = { 'cluster_uuid': _get_target_uuid(cs, args) } if args.csr is None or not os.path.isfile(args.csr): print('A CSR must be provided.') return with open(args.csr, 'r') as f: opts['csr'] = f.read() cert = cs.certificates.create(**opts) _show_cert(cert) @utils.arg('--cluster', required=True, metavar='', help=_('ID or name of the cluster.')) @utils.deprecated(utils.MAGNUM_CLIENT_DEPRECATION_WARNING) def do_ca_rotate(cs, args): """Rotate the CA certificate for a cluster to revoke access.""" cluster = cs.clusters.get(args.cluster) opts = { 'cluster_uuid': cluster.uuid } cs.certificates.rotate_ca(**opts) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/v1/client.py0000664000175000017500000001766600000000000022513 0ustar00zuulzuul00000000000000# Copyright 2014 # The Cloudscaling Group, 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. from keystoneauth1.exceptions import catalog from keystoneauth1 import session as ksa_session import os_client_config from oslo_utils import importutils from magnumclient.common import httpclient from magnumclient.v1 import certificates from magnumclient.v1 import cluster_templates from magnumclient.v1 import clusters from magnumclient.v1 import mservices from magnumclient.v1 import nodegroups from magnumclient.v1 import quotas from magnumclient.v1 import stats profiler = importutils.try_import("osprofiler.profiler") DEFAULT_SERVICE_TYPE = 'container-infra' LEGACY_DEFAULT_SERVICE_TYPE = 'container' def _load_session(cloud=None, insecure=False, timeout=None, **kwargs): cloud_config = os_client_config.OpenStackConfig() cloud_config = cloud_config.get_one_cloud( cloud=cloud, verify=not insecure, **kwargs) verify, cert = cloud_config.get_requests_verify_args() auth = cloud_config.get_auth() session = ksa_session.Session( auth=auth, verify=verify, cert=cert, timeout=timeout) return session def _load_service_type(session, service_type=None, service_name=None, interface=None, region_name=None): try: # Trigger an auth error so that we can throw the exception # we always have session.get_endpoint( service_type=service_type, service_name=service_name, interface=interface, region_name=region_name) except catalog.EndpointNotFound: service_type = LEGACY_DEFAULT_SERVICE_TYPE try: session.get_endpoint( service_type=service_type, service_name=service_name, interface=interface, region_name=region_name) except Exception as e: raise RuntimeError(str(e)) except Exception as e: raise RuntimeError(str(e)) return service_type def _load_session_client(session=None, endpoint_override=None, username=None, project_id=None, project_name=None, auth_url=None, password=None, auth_type=None, insecure=None, user_domain_id=None, user_domain_name=None, project_domain_id=None, project_domain_name=None, auth_token=None, timeout=None, service_type=None, service_name=None, interface=None, region_name=None, api_version=None, **kwargs): if not session: session = _load_session( username=username, project_id=project_id, project_name=project_name, auth_url=auth_url, password=password, auth_type=auth_type, insecure=insecure, user_domain_id=user_domain_id, user_domain_name=user_domain_name, project_domain_id=project_domain_id, project_domain_name=project_domain_name, auth_token=auth_token, timeout=timeout, **kwargs ) if not endpoint_override: service_type = _load_service_type( session, service_type=service_type, service_name=service_name, interface=interface, region_name=region_name, ) return httpclient.SessionClient( service_type=service_type, service_name=service_name, interface=interface, region_name=region_name, session=session, endpoint_override=endpoint_override, api_version=api_version, ) class Client(object): def __init__(self, username=None, api_key=None, project_id=None, project_name=None, auth_url=None, magnum_url=None, endpoint_type=None, endpoint_override=None, service_type=DEFAULT_SERVICE_TYPE, region_name=None, input_auth_token=None, session=None, password=None, auth_type='password', interface=None, service_name=None, insecure=False, user_domain_id=None, user_domain_name=None, project_domain_id=None, project_domain_name=None, auth_token=None, timeout=600, api_version=None, **kwargs): # We have to keep the api_key are for backwards compat, but let's # remove it from the rest of our code since it's not a keystone # concept if not password: password = api_key # Backwards compat for people passing in input_auth_token if input_auth_token: auth_token = input_auth_token # Backwards compat for people passing in endpoint_type if endpoint_type: interface = endpoint_type # osc sometimes give 'None' value if not interface: interface = 'public' if interface.endswith('URL'): interface = interface[:-3] # fix (yolanda): os-cloud-config is using endpoint_override # instead of magnum_url if magnum_url and not endpoint_override: endpoint_override = magnum_url if endpoint_override and auth_token: self.http_client = httpclient.HTTPClient( endpoint_override, token=auth_token, api_version=api_version, timeout=timeout, insecure=insecure, **kwargs ) else: self.http_client = _load_session_client( session=session, endpoint_override=endpoint_override, username=username, project_id=project_id, project_name=project_name, auth_url=auth_url, password=password, auth_type=auth_type, insecure=insecure, user_domain_id=user_domain_id, user_domain_name=user_domain_name, project_domain_id=project_domain_id, project_domain_name=project_domain_name, auth_token=auth_token, timeout=timeout, service_type=service_type, service_name=service_name, interface=interface, region_name=region_name, api_version=api_version, **kwargs ) self.clusters = clusters.ClusterManager(self.http_client) self.certificates = certificates.CertificateManager(self.http_client) self.cluster_templates = \ cluster_templates.ClusterTemplateManager(self.http_client) self.mservices = mservices.MServiceManager(self.http_client) profile = kwargs.pop("profile", None) if profiler and profile: # Initialize the root of the future trace: the created trace ID # will be used as the very first parent to which all related # traces will be bound to. The given HMAC key must correspond to # the one set in magnum-api magnum.conf, otherwise the latter # will fail to check the request signature and will skip # initialization of osprofiler on the server side. profiler.init(profile) self.stats = stats.StatsManager(self.http_client) self.quotas = quotas.QuotasManager(self.http_client) self.nodegroups = nodegroups.NodeGroupManager(self.http_client) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/v1/cluster_templates.py0000664000175000017500000000164100000000000024756 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 magnumclient.v1 import basemodels CREATION_ATTRIBUTES = basemodels.CREATION_ATTRIBUTES CREATION_ATTRIBUTES.append('insecure_registry') class ClusterTemplate(basemodels.BaseModel): model_name = "ClusterTemplate" class ClusterTemplateManager(basemodels.BaseModelManager): api_name = "clustertemplates" resource_class = ClusterTemplate ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/v1/cluster_templates_shell.py0000664000175000017500000003256000000000000026151 0ustar00zuulzuul00000000000000# Copyright 2015 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from magnumclient.common import cliutils as utils from magnumclient.common import utils as magnum_utils from magnumclient.exceptions import InvalidAttribute from magnumclient.i18n import _ from magnumclient.v1 import basemodels # Maps old parameter names to their new names and whether they are required DEPRECATING_PARAMS = { "--external-network-id": "--external-network", "--flavor-id": "--flavor", "--image-id": "--image", "--keypair-id": "--keypair", "--master-flavor-id": "--master-flavor", } def _show_cluster_template(cluster_template): del cluster_template._info['links'] utils.print_dict(cluster_template._info) @utils.deprecation_map(DEPRECATING_PARAMS) @utils.arg('positional_name', metavar='', nargs='?', default=None, help=_('Name of the cluster template to create.')) @utils.arg('--name', metavar='', default=None, help=(_('Name of the cluster template to create. %s') % utils.NAME_DEPRECATION_HELP)) @utils.arg('--image-id', dest='image', required=True, metavar='', help=utils.deprecation_message( 'The name or UUID of the base image to customize for the ' 'Cluster.', 'image')) @utils.arg('--image', dest='image', required=True, metavar='', help=_('The name or UUID of the base image to customize for the ' 'Cluster.')) @utils.arg('--keypair-id', dest='keypair', metavar='', help=utils.deprecation_message( 'The name of the SSH keypair to load into the ' 'Cluster nodes.', 'keypair')) @utils.arg('--keypair', dest='keypair', metavar='', help=_('The name of the SSH keypair to load into the ' 'Cluster nodes.')) @utils.arg('--external-network-id', dest='external_network', required=True, metavar='', help=utils.deprecation_message( 'The external Neutron network name or UUID to connect to ' 'this Cluster Template.', 'external-network')) @utils.arg('--external-network', dest='external_network', required=True, metavar='', help=_('The external Neutron network name or UUID to connect to ' 'this Cluster Template.')) @utils.arg('--coe', required=True, metavar='', help=_('Specify the Container Orchestration Engine to use.')) @utils.arg('--fixed-network', metavar='', help=_('The private Neutron network name to connect to this Cluster' ' model.')) @utils.arg('--fixed-subnet', metavar='', help=_('The private Neutron subnet name to connect to Cluster.')) @utils.arg('--network-driver', metavar='', help=_('The network driver name for instantiating container' ' networks.')) @utils.arg('--volume-driver', metavar='', help=_('The volume driver name for instantiating container' ' volume.')) @utils.arg('--dns-nameserver', metavar='', default='8.8.8.8', help=_('The DNS nameserver to use for this cluster template.')) @utils.arg('--flavor-id', dest='flavor', metavar='', default='m1.medium', help=utils.deprecation_message( 'The nova flavor name or UUID to use when launching the ' 'Cluster.', 'flavor')) @utils.arg('--flavor', dest='flavor', metavar='', default='m1.medium', help=_('The nova flavor name or UUID to use when launching the ' 'Cluster.')) @utils.arg('--master-flavor-id', dest='master_flavor', metavar='', help=utils.deprecation_message( 'The nova flavor name or UUID to use when launching the master' ' node of the Cluster.', 'master-flavor')) @utils.arg('--master-flavor', dest='master_flavor', metavar='', help=_('The nova flavor name or UUID to use when launching the' ' master node of the Cluster.')) @utils.arg('--docker-volume-size', metavar='', type=int, help=_('Specify the number of size in GB ' 'for the docker volume to use.')) @utils.arg('--docker-storage-driver', metavar='', default='devicemapper', help=_('Select a docker storage driver. Supported: devicemapper, ' 'overlay. Default: devicemapper')) @utils.arg('--http-proxy', metavar='', help=_('The http_proxy address to use for nodes in Cluster.')) @utils.arg('--https-proxy', metavar='', help=_('The https_proxy address to use for nodes in Cluster.')) @utils.arg('--no-proxy', metavar='', help=_('The no_proxy address to use for nodes in Cluster.')) @utils.arg('--labels', metavar='', action='append', default=[], help=_('Arbitrary labels in the form of key=value pairs ' 'to associate with a cluster template. ' 'May be used multiple times.')) @utils.arg('--tls-disabled', action='store_true', default=False, help=_('Disable TLS in the Cluster.')) @utils.arg('--public', action='store_true', default=False, help=_('Make cluster template public.')) @utils.arg('--registry-enabled', action='store_true', default=False, help=_('Enable docker registry in the Cluster')) @utils.arg('--server-type', metavar='', default='vm', help=_('Specify the server type to be used ' 'for example vm. For this release ' 'default server type will be vm.')) @utils.arg('--master-lb-enabled', action='store_true', default=False, help=_('Indicates whether created Clusters should have a load ' 'balancer for master nodes or not.')) @utils.arg('--floating-ip-enabled', action='append_const', const=True, default=[], dest='floating_ip_enabled', help=_('Indicates whether created Clusters should have a ' 'floating ip.')) @utils.arg('--floating-ip-disabled', action='append_const', const=False, default=[], dest='floating_ip_enabled', help=_('Disables floating ip creation on the new Cluster')) @utils.arg('--insecure-registry', metavar='', help='url of docker registry') @utils.arg('--hidden', action='store_true', default=False, help=_('Make cluster template hidden.')) @utils.arg('--visible', dest='hidden', action='store_false', help=_('Make cluster template visible.')) @utils.deprecated(utils.MAGNUM_CLIENT_DEPRECATION_WARNING) def do_cluster_template_create(cs, args): """Create a cluster template.""" args.command = 'cluster-template-create' utils.validate_name_args(args.positional_name, args.name) opts = {} opts['name'] = args.positional_name or args.name opts['flavor_id'] = args.flavor opts['master_flavor_id'] = args.master_flavor opts['image_id'] = args.image opts['keypair_id'] = args.keypair opts['external_network_id'] = args.external_network opts['fixed_network'] = args.fixed_network opts['fixed_subnet'] = args.fixed_subnet opts['network_driver'] = args.network_driver opts['volume_driver'] = args.volume_driver opts['dns_nameserver'] = args.dns_nameserver opts['docker_volume_size'] = args.docker_volume_size opts['docker_storage_driver'] = args.docker_storage_driver opts['coe'] = args.coe opts['http_proxy'] = args.http_proxy opts['https_proxy'] = args.https_proxy opts['no_proxy'] = args.no_proxy opts['labels'] = magnum_utils.handle_labels(args.labels) opts['tls_disabled'] = args.tls_disabled opts['public'] = args.public opts['registry_enabled'] = args.registry_enabled opts['server_type'] = args.server_type opts['master_lb_enabled'] = args.master_lb_enabled opts['insecure_registry'] = args.insecure_registry opts['hidden'] = args.hidden if len(args.floating_ip_enabled) > 1: raise InvalidAttribute('--floating-ip-enabled and ' '--floating-ip-disabled are ' 'mutually exclusive and ' 'should be specified only once.') elif len(args.floating_ip_enabled) == 1: opts['floating_ip_enabled'] = args.floating_ip_enabled[0] cluster_template = cs.cluster_templates.create(**opts) _show_cluster_template(cluster_template) @utils.arg('cluster_templates', metavar='', nargs='+', help=_('ID or name of the (cluster template)s to delete.')) @utils.deprecated(utils.MAGNUM_CLIENT_DEPRECATION_WARNING) def do_cluster_template_delete(cs, args): """Delete specified cluster template.""" for cluster_template in args.cluster_templates: try: cs.cluster_templates.delete(cluster_template) print("Request to delete cluster template %s has been accepted." % cluster_template) except Exception as e: print("Delete for cluster template " "%(cluster_template)s failed: %(e)s" % {'cluster_template': cluster_template, 'e': e}) @utils.arg('cluster_template', metavar='', help=_('ID or name of the cluster template to show.')) @utils.deprecated(utils.MAGNUM_CLIENT_DEPRECATION_WARNING) def do_cluster_template_show(cs, args): """Show details about the given cluster template.""" cluster_template = cs.cluster_templates.get(args.cluster_template) _show_cluster_template(cluster_template) @utils.arg('--limit', metavar='', type=int, help=_('Maximum number of cluster templates to return')) @utils.arg('--sort-key', metavar='', help=_('Column to sort results by')) @utils.arg('--sort-dir', metavar='', choices=['desc', 'asc'], help=_('Direction to sort. "asc" or "desc".')) @utils.arg('--fields', default=None, metavar='', help=_('Comma-separated list of fields to display. ' 'Available fields: uuid, name, coe, image_id, public, link, ' 'apiserver_port, server_type, tls_disabled, registry_enabled' ) ) @utils.arg('--detail', action='store_true', default=False, help=_('Show detailed information about the cluster templates.') ) @utils.deprecated(utils.MAGNUM_CLIENT_DEPRECATION_WARNING) def do_cluster_template_list(cs, args): """Print a list of cluster templates.""" nodes = cs.cluster_templates.list(limit=args.limit, sort_key=args.sort_key, sort_dir=args.sort_dir, detail=args.detail) if args.detail: columns = basemodels.OUTPUT_ATTRIBUTES else: columns = ['uuid', 'name'] columns += utils._get_list_table_columns_and_formatters( args.fields, nodes, exclude_fields=(c.lower() for c in columns))[0] utils.print_list(nodes, columns, {'versions': magnum_utils.print_list_field('versions')}, sortby_index=None) @utils.arg('cluster_template', metavar='', help=_("UUID or name of cluster template")) @utils.arg( 'op', metavar='', choices=['add', 'replace', 'remove'], help=_("Operations: 'add', 'replace' or 'remove'")) @utils.arg( 'attributes', metavar='', nargs='+', action='append', default=[], help=_("Attributes to add/replace or remove " "(only PATH is necessary on remove)")) @utils.deprecated(utils.MAGNUM_CLIENT_DEPRECATION_WARNING) def do_cluster_template_update(cs, args): """Updates one or more cluster template attributes.""" patch = magnum_utils.args_array_to_patch(args.op, args.attributes[0]) cluster_template = cs.cluster_templates.update(args.cluster_template, patch) _show_cluster_template(cluster_template) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/v1/clusters.py0000664000175000017500000000474000000000000023066 0ustar00zuulzuul00000000000000# Copyright 2014 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from magnumclient.v1 import baseunit CREATION_ATTRIBUTES = baseunit.CREATION_ATTRIBUTES CREATION_ATTRIBUTES.append('cluster_template_id') CREATION_ATTRIBUTES.append('create_timeout') CREATION_ATTRIBUTES.append('keypair') CREATION_ATTRIBUTES.append('docker_volume_size') CREATION_ATTRIBUTES.append('labels') CREATION_ATTRIBUTES.append('master_flavor_id') CREATION_ATTRIBUTES.append('flavor_id') CREATION_ATTRIBUTES.append('fixed_network') CREATION_ATTRIBUTES.append('fixed_subnet') CREATION_ATTRIBUTES.append('floating_ip_enabled') CREATION_ATTRIBUTES.append('merge_labels') CREATION_ATTRIBUTES.append('master_lb_enabled') class Cluster(baseunit.BaseTemplate): template_name = "Clusters" class ClusterManager(baseunit.BaseTemplateManager): resource_class = Cluster template_name = 'clusters' def resize(self, cluster_uuid, node_count, nodes_to_remove=[], nodegroup=None): url = self._path(cluster_uuid) + "/actions/resize" post_body = {"node_count": node_count} if nodes_to_remove: post_body.update({"nodes_to_remove": nodes_to_remove}) if nodegroup: post_body.update({"nodegroup": nodegroup}) resp, resp_body = self.api.json_request("POST", url, body=post_body) if resp_body: return self.resource_class(self, resp_body) def upgrade(self, cluster_uuid, cluster_template, max_batch_size=1, nodegroup=None): url = self._path(cluster_uuid) + "/actions/upgrade" post_body = {"cluster_template": cluster_template} if max_batch_size: post_body.update({"max_batch_size": max_batch_size}) if nodegroup: post_body.update({"nodegroup": nodegroup}) resp, resp_body = self.api.json_request("POST", url, body=post_body) if resp_body: return self.resource_class(self, resp_body) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/v1/clusters_shell.py0000664000175000017500000002507200000000000024256 0ustar00zuulzuul00000000000000# Copyright 2015 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import os from magnumclient.common import cliutils as utils from magnumclient.common import utils as magnum_utils from magnumclient import exceptions from magnumclient.i18n import _ # Maps old parameter names to their new names and whether they are required # e.g. keypair-id to keypair DEPRECATING_PARAMS = { "--keypair-id": "--keypair", } def _show_cluster(cluster): del cluster._info['links'] utils.print_dict(cluster._info) @utils.arg('--marker', metavar='', default=None, help=_('The last cluster UUID of the previous page; ' 'displays list of clusters after "marker".')) @utils.arg('--limit', metavar='', type=int, help=_('Maximum number of clusters to return.')) @utils.arg('--sort-key', metavar='', help=_('Column to sort results by.')) @utils.arg('--sort-dir', metavar='', choices=['desc', 'asc'], help=_('Direction to sort. "asc" or "desc".')) @utils.arg('--fields', default=None, metavar='', help=_('Comma-separated list of fields to display. ' 'Available fields: uuid, name, cluster_template_id, ' 'stack_id, status, master_count, node_count, links, ' 'create_timeout' ) ) @utils.deprecated(utils.MAGNUM_CLIENT_DEPRECATION_WARNING) def do_cluster_list(cs, args): """Print a list of available clusters.""" clusters = cs.clusters.list(marker=args.marker, limit=args.limit, sort_key=args.sort_key, sort_dir=args.sort_dir) columns = [ 'uuid', 'name', 'keypair', 'node_count', 'master_count', 'status' ] columns += utils._get_list_table_columns_and_formatters( args.fields, clusters, exclude_fields=(c.lower() for c in columns))[0] utils.print_list(clusters, columns, {'versions': magnum_utils.print_list_field('versions')}, sortby_index=None) @utils.deprecation_map(DEPRECATING_PARAMS) @utils.arg('positional_name', metavar='', nargs='?', default=None, help=_('Name of the cluster to create.')) @utils.arg('--name', metavar='', default=None, help=(_('Name of the cluster to create. %s') % utils.NAME_DEPRECATION_HELP)) @utils.arg('--cluster-template', required=True, metavar='', help=_('ID or name of the cluster template.')) @utils.arg('--keypair-id', dest='keypair', metavar='', default=None, help=utils.deprecation_message( 'Name of the keypair to use for this cluster.', 'keypair')) @utils.arg('--keypair', dest='keypair', metavar='', default=None, help=_('Name of the keypair to use for this cluster.')) @utils.arg('--docker-volume-size', metavar='', type=int, help=_('The size in GB for the docker volume to use')) @utils.arg('--labels', metavar='', action='append', help=_('Arbitrary labels in the form of key=value pairs ' 'to associate with a cluster. ' 'May be used multiple times.')) @utils.arg('--node-count', metavar='', type=int, default=1, help=_('The cluster node count.')) @utils.arg('--master-count', metavar='', type=int, default=1, help=_('The number of master nodes for the cluster.')) @utils.arg('--discovery-url', metavar='', help=_('Specifies custom discovery url for node discovery.')) @utils.arg('--timeout', metavar='', type=int, default=60, help=_('The timeout for cluster creation in minutes. The default ' 'is 60 minutes.')) @utils.deprecated(utils.MAGNUM_CLIENT_DEPRECATION_WARNING) def do_cluster_create(cs, args): """Create a cluster.""" args.command = 'cluster-create' utils.validate_name_args(args.positional_name, args.name) cluster_template = cs.cluster_templates.get(args.cluster_template) opts = dict() opts['name'] = args.positional_name or args.name opts['cluster_template_id'] = cluster_template.uuid opts['keypair'] = args.keypair if args.docker_volume_size is not None: opts['docker_volume_size'] = args.docker_volume_size if args.labels is not None: opts['labels'] = magnum_utils.handle_labels(args.labels) opts['node_count'] = args.node_count opts['master_count'] = args.master_count opts['discovery_url'] = args.discovery_url opts['create_timeout'] = args.timeout try: cluster = cs.clusters.create(**opts) # support for non-async in 1.1 if args.magnum_api_version and args.magnum_api_version == '1.1': _show_cluster(cluster) else: uuid = str(cluster._info['uuid']) print("Request to create cluster %s has been accepted." % uuid) except Exception as e: print("Create for cluster %s failed: %s" % (opts['name'], e)) @utils.arg('cluster', metavar='', nargs='+', help=_('ID or name of the (cluster)s to delete.')) @utils.deprecated(utils.MAGNUM_CLIENT_DEPRECATION_WARNING) def do_cluster_delete(cs, args): """Delete specified cluster.""" for id in args.cluster: try: cs.clusters.delete(id) print("Request to delete cluster %s has been accepted." % id) except Exception as e: print("Delete for cluster %(cluster)s failed: %(e)s" % {'cluster': id, 'e': e}) @utils.arg('cluster', metavar='', help=_('ID or name of the cluster to show.')) @utils.arg('--long', action='store_true', default=False, help=_('Display extra associated cluster template info.')) @utils.deprecated(utils.MAGNUM_CLIENT_DEPRECATION_WARNING) def do_cluster_show(cs, args): """Show details about the given cluster.""" cluster = cs.clusters.get(args.cluster) if args.long: cluster_template = \ cs.cluster_templates.get(cluster.cluster_template_id) del cluster_template._info['links'], cluster_template._info['uuid'] for key in cluster_template._info: if 'clustertemplate_' + key not in cluster._info: cluster._info['clustertemplate_' + key] = \ cluster_template._info[key] _show_cluster(cluster) @utils.arg('cluster', metavar='', help=_("UUID or name of cluster")) @utils.arg('--rollback', action='store_true', default=False, help=_('Rollback cluster on update failure.')) @utils.arg( 'op', metavar='', choices=['add', 'replace', 'remove'], help=_("Operations: 'add', 'replace' or 'remove'")) @utils.arg( 'attributes', metavar='', nargs='+', action='append', default=[], help=_("Attributes to add/replace or remove " "(only PATH is necessary on remove)")) @utils.deprecated(utils.MAGNUM_CLIENT_DEPRECATION_WARNING) def do_cluster_update(cs, args): """Update information about the given cluster.""" if args.rollback and args.magnum_api_version and \ args.magnum_api_version in ('1.0', '1.1', '1.2'): raise exceptions.CommandError( "Rollback is not supported in API v%s. " "Please use API v1.3+." % args.magnum_api_version) patch = magnum_utils.args_array_to_patch(args.op, args.attributes[0]) try: cluster = cs.clusters.update(args.cluster, patch, args.rollback) except Exception as e: print("ERROR: %s" % e.details) return if args.magnum_api_version and args.magnum_api_version == '1.1': _show_cluster(cluster) else: print("Request to update cluster %s has been accepted." % args.cluster) @utils.arg('cluster', metavar='', help=_('ID or name of the cluster to retrieve config.')) @utils.arg('--dir', metavar='', default='.', help=_('Directory to save the certificate and config files.')) @utils.arg('--force', action='store_true', default=False, help=_('Overwrite files if existing.')) @utils.deprecated(utils.MAGNUM_CLIENT_DEPRECATION_WARNING) def do_cluster_config(cs, args): """Configure native client to access cluster. You can source the output of this command to get the native client of the corresponding COE configured to access the cluster. Example: eval $(magnum cluster-config ). """ args.dir = os.path.abspath(args.dir) cluster = cs.clusters.get(args.cluster) if (hasattr(cluster, 'api_address') and cluster.api_address is None): print("WARNING: The cluster's api_address is not known yet.") cluster_template = cs.cluster_templates.get(cluster.cluster_template_id) opts = { 'cluster_uuid': cluster.uuid, } # Create a new Certificate and Key, sign it if not cluster_template.tls_disabled: tls = magnum_utils.generate_csr_and_key() tls['ca'] = cs.certificates.get(**opts).pem opts['csr'] = tls['csr'] tls['cert'] = cs.certificates.create(**opts).pem for k in ('key', 'cert', 'ca'): fname = "%s/%s.pem" % (args.dir, k) if os.path.exists(fname) and not args.force: raise Exception("File %s exists, aborting." % fname) else: f = open(fname, "w") f.write(tls[k]) f.close() print(magnum_utils.config_cluster(cluster, cluster_template, cfg_dir=args.dir, force=args.force)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/v1/mservices.py0000664000175000017500000000474600000000000023230 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 magnumclient.common import base from magnumclient.common import utils class MService(base.Resource): def __repr__(self): return "" % self._info class MServiceManager(base.Manager): resource_class = MService @staticmethod def _path(id=None): return '/v1/mservices/%s' % id if id else '/v1/mservices' def list(self, marker=None, limit=None, sort_key=None, sort_dir=None, detail=False): """Retrieve list of magnum services. :param marker: Optional, the ID of a magnum service, eg the last services from a previous result set. Return the next result set. :param limit: The maximum number of results to return per request, if: 1) limit > 0, the maximum number of services to return. 2) limit == 0, return the entire list of services. 3) limit param is NOT specified (None), the number of items returned respect the maximum imposed by the Magnum API (see Magnum's api.max_limit option). :param sort_key: Optional, field used for sorting. :param sort_dir: Optional, direction of sorting, either 'asc' (the default) or 'desc'. :param detail: Optional, boolean whether to return detailed information about services. :returns: A list of services. """ if limit is not None: limit = int(limit) filters = utils.common_filters(marker, limit, sort_key, sort_dir) path = '' if detail: path += 'detail' if filters: path += '?' + '&'.join(filters) if limit is None: return self._list(self._path(path), "mservices") else: return self._list_pagination(self._path(path), "mservices", limit=limit) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/v1/mservices_shell.py0000664000175000017500000000210200000000000024377 0ustar00zuulzuul00000000000000# Copyright 2015 NEC Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from magnumclient.common import cliutils as utils from magnumclient.common import utils as magnum_utils def do_service_list(cs, args): """Print a list of magnum services.""" mservices = cs.mservices.list() columns = ('id', 'host', 'binary', 'state', 'disabled', 'disabled_reason', 'created_at', 'updated_at') utils.print_list(mservices, columns, {'versions': magnum_utils.print_list_field('versions')}) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/v1/nodegroups.py0000664000175000017500000000564000000000000023407 0ustar00zuulzuul00000000000000# Copyright (c) 2018 European Organization for Nuclear Research. # 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 magnumclient.common import utils from magnumclient import exceptions from magnumclient.v1 import baseunit CREATION_ATTRIBUTES = ['docker_volume_size', 'labels', 'flavor_id', 'image_id', 'project_id', 'node_count', 'name', 'role', 'min_node_count', 'max_node_count', 'merge_labels'] class NodeGroup(baseunit.BaseTemplate): template_name = "NodeGroups" class NodeGroupManager(baseunit.BaseTemplateManager): resource_class = NodeGroup template_name = 'nodegroups' api_name = 'nodegroups' @classmethod def _path(cls, cluster_id, id=None): path = '/v1/clusters/%s/%s/' % (cluster_id, cls.template_name) if id: path += str(id) return path def list(self, cluster_id, limit=None, marker=None, sort_key=None, sort_dir=None, role=None, detail=False): if limit is not None: limit = int(limit) filters = utils.common_filters(marker, limit, sort_key, sort_dir) path = '' if role: filters.append('role=%s' % role) if detail: path += 'detail' if filters: path += '?' + '&'.join(filters) if limit is None: return self._list(self._path(cluster_id, id=path), self.__class__.api_name) else: return self._list_pagination(self._path(cluster_id, id=path), self.__class__.api_name, limit=limit) def get(self, cluster_id, id): try: return self._list(self._path(cluster_id, id=id))[0] except IndexError: return None def create(self, cluster_id, **kwargs): new = {} for (key, value) in kwargs.items(): if key in CREATION_ATTRIBUTES: new[key] = value else: raise exceptions.InvalidAttribute( "Key must be in %s" % ",".join(CREATION_ATTRIBUTES)) return self._create(self._path(cluster_id), new) def delete(self, cluster_id, id): return self._delete(self._path(cluster_id, id=id)) def update(self, cluster_id, id, patch): return self._update(self._path(cluster_id, id=id), patch) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/v1/quotas.py0000664000175000017500000000462200000000000022535 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 magnumclient.common import utils from magnumclient import exceptions from magnumclient.v1 import basemodels CREATION_ATTRIBUTES = ['project_id', 'resource', 'hard_limit'] class Quotas(basemodels.BaseModel): model_name = "Quotas" class QuotasManager(basemodels.BaseModelManager): api_name = "quotas" resource_class = Quotas @staticmethod def _path(id=None, resource=None): if not id: return '/v1/quotas' return '/v1/quotas/%(id)s/%(res)s' % {'id': id, 'res': resource} def list(self, limit=None, marker=None, sort_key=None, sort_dir=None, all_tenants=False): if limit is not None: limit = int(limit) filters = utils.common_filters(marker, limit, sort_key, sort_dir) if all_tenants: filters.append('all_tenants=True') path = self._path() if filters: path += '?' + '&'.join(filters) if limit is None: return self._list(path, self.api_name) else: return self._list_pagination(path, self.api_name, limit=limit) def get(self, id, resource): try: return self._list(self._path(id, resource))[0] except IndexError: return None def create(self, **kwargs): new = {} for (key, value) in kwargs.items(): if key in CREATION_ATTRIBUTES: new[key] = value else: raise exceptions.InvalidAttribute( "Key must be in %s" % ",".join(CREATION_ATTRIBUTES)) return self._create(self._path(), new) def delete(self, id, resource): return self._delete(self._path(id, resource)) def update(self, id, resource, patch): url = self._path(id, resource) return self._update(url, patch) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/v1/quotas_shell.py0000664000175000017500000001216400000000000023724 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 magnumclient.common import cliutils as utils from magnumclient.common import utils as magnum_utils from magnumclient.i18n import _ def _show_quota(quota): utils.print_dict(quota._info) @utils.arg('--marker', metavar='', default=None, help=_('The last quota UUID of the previous page; ' 'displays list of quotas after "marker".')) @utils.arg('--limit', metavar='', type=int, help=_('Maximum number of quotas to return.')) @utils.arg('--sort-key', metavar='', help=_('Column to sort results by.')) @utils.arg('--sort-dir', metavar='', choices=['desc', 'asc'], help=_('Direction to sort. "asc" or "desc".')) @utils.arg('--all-tenants', action='store_true', default=False, help=_('Flag to indicate list all tenant quotas.')) @utils.deprecated(utils.MAGNUM_CLIENT_DEPRECATION_WARNING) def do_quotas_list(cs, args): """Print a list of available quotas.""" quotas = cs.quotas.list(marker=args.marker, limit=args.limit, sort_key=args.sort_key, sort_dir=args.sort_dir, all_tenants=args.all_tenants) columns = ['project_id', 'resource', 'hard_limit'] utils.print_list(quotas, columns, {'versions': magnum_utils.print_list_field('versions')}, sortby_index=None) @utils.arg('--project-id', required=True, metavar='', help=_('Project Id.')) @utils.arg('--resource', required=True, metavar='', help=_('Resource name.')) @utils.arg('--hard-limit', metavar='', type=int, default=1, help=_('Max resource limit.')) @utils.deprecated(utils.MAGNUM_CLIENT_DEPRECATION_WARNING) def do_quotas_create(cs, args): """Create a quota.""" opts = dict() opts['project_id'] = args.project_id opts['resource'] = args.resource opts['hard_limit'] = args.hard_limit try: quota = cs.quotas.create(**opts) _show_quota(quota) except Exception as e: print("Create quota for project_id %(id)s resource %(res)s failed: " "%(e)s" % {'id': args.project_id, 'res': args.resource, 'e': e.details}) @utils.arg('--project-id', required=True, metavar='', help=_('Project ID.')) @utils.arg('--resource', required=True, metavar='', help=_('Resource name')) @utils.deprecated(utils.MAGNUM_CLIENT_DEPRECATION_WARNING) def do_quotas_delete(cs, args): """Delete specified resource quota.""" try: cs.quotas.delete(args.project_id, args.resource) print("Request to delete quota for project id %(id)s and resource " "%(res)s has been accepted." % { 'id': args.project_id, 'res': args.resource}) except Exception as e: print("Quota delete failed for project id %(id)s and resource " "%(res)s :%(e)s" % {'id': args.project_id, 'res': args.resource, 'e': e.details}) @utils.arg('--project-id', required=True, metavar='', help=_('Project ID.')) @utils.arg('--resource', required=True, metavar='', help=_('Resource name')) @utils.deprecated(utils.MAGNUM_CLIENT_DEPRECATION_WARNING) def do_quotas_show(cs, args): """Show details about the given project resource quota.""" quota = cs.quotas.get(args.project_id, args.resource) _show_quota(quota) @utils.arg('--project-id', required=True, metavar='', help=_('Project Id.')) @utils.arg('--resource', required=True, metavar='', help=_('Resource name.')) @utils.arg('--hard-limit', metavar='', type=int, default=1, help=_('Max resource limit.')) @utils.deprecated(utils.MAGNUM_CLIENT_DEPRECATION_WARNING) def do_quotas_update(cs, args): """Update information about the given project resource quota.""" patch = dict() patch['project_id'] = args.project_id patch['resource'] = args.resource patch['hard_limit'] = args.hard_limit quota = cs.quotas.update(args.project_id, args.resource, patch) _show_quota(quota) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/v1/shell.py0000664000175000017500000000176200000000000022332 0ustar00zuulzuul00000000000000# Copyright 2014 # The Cloudscaling Group, 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. from magnumclient.v1 import certificates_shell from magnumclient.v1 import cluster_templates_shell from magnumclient.v1 import clusters_shell from magnumclient.v1 import mservices_shell from magnumclient.v1 import quotas_shell from magnumclient.v1 import stats_shell COMMAND_MODULES = [ certificates_shell, clusters_shell, cluster_templates_shell, mservices_shell, stats_shell, quotas_shell, ] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/v1/stats.py0000664000175000017500000000201700000000000022353 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 magnumclient.common import base class Stats(base.Resource): def __repr__(self): return "" % self._info class StatsManager(base.Manager): resource_class = Stats @staticmethod def _path(id=None): return '/v1/stats?project_id=%s' % id if id else '/v1/stats' def list(self, project_id=None): try: return self._list(self._path(project_id))[0] except IndexError: return None ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/v1/stats_shell.py0000664000175000017500000000201700000000000023542 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 magnumclient.common import cliutils as utils from magnumclient.i18n import _ @utils.arg('--project-id', required=False, metavar='', help=_('Project ID')) @utils.deprecated(utils.MAGNUM_CLIENT_DEPRECATION_WARNING) def do_stats_list(cs, args): """Show stats for the given project_id""" opts = { 'project_id': args.project_id } stats = cs.stats.list(**opts) utils.print_dict(stats._info) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/magnumclient/version.py0000664000175000017500000000124200000000000022353 0ustar00zuulzuul00000000000000# Copyright 2014 # The Cloudscaling Group, 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. from pbr import version version_info = version.VersionInfo('python-magnumclient') ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740683659.4266944 python_magnumclient-4.8.0/python_magnumclient.egg-info/0000775000175000017500000000000000000000000023410 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683659.0 python_magnumclient-4.8.0/python_magnumclient.egg-info/PKG-INFO0000644000175000017500000000556300000000000024514 0ustar00zuulzuul00000000000000Metadata-Version: 2.1 Name: python-magnumclient Version: 4.8.0 Summary: Client library for Magnum API Home-page: https://docs.openstack.org/python-magnumclient/latest/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org 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 :: Implementation :: CPython Classifier: Programming Language :: Python :: 3 :: Only 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 License-File: LICENSE Requires-Dist: pbr!=2.1.0,>=2.0.0 Requires-Dist: keystoneauth1>=3.4.0 Requires-Dist: stevedore>=1.20.0 Requires-Dist: requests>=2.14.2 Requires-Dist: oslo.i18n>=3.15.3 Requires-Dist: oslo.log>=3.36.0 Requires-Dist: oslo.serialization!=2.19.1,>=2.18.0 Requires-Dist: oslo.utils>=3.33.0 Requires-Dist: os-client-config>=1.28.0 Requires-Dist: osc-lib>=1.8.0 Requires-Dist: PrettyTable>=0.7.2 Requires-Dist: cryptography>=3.0 Requires-Dist: decorator>=3.4.0 ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/python-magnumclient.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on Python bindings to the Magnum API ================================= .. image:: https://img.shields.io/pypi/v/python-magnumclient.svg :target: https://pypi.org/project/python-magnumclient/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/python-magnumclient.svg :target: https://pypi.org/project/python-magnumclient/ :alt: Downloads This is a client library for Magnum built on the Magnum API. It provides a Python API (the ``magnumclient`` module) and a command-line tool (``magnum``). Development takes place via the usual OpenStack processes as outlined in the `developer guide `_. * License: Apache License, Version 2.0 * `PyPi`_ - package installation * `Online Documentation`_ * `Storyboard project`_ - story and task management * `Bugs`_ - issue tracking * `Source`_ .. _PyPi: https://pypi.org/project/python-magnumclient .. _Online Documentation: https://docs.openstack.org/python-magnumclient/latest/ .. _Storyboard project: https://storyboard.openstack.org/#!/project/openstack/python-magnumclient .. _Bugs: https://storyboard.openstack.org/#!/project/openstack/python-magnumclient .. _Source: https://opendev.org/openstack/python-magnumclient ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683659.0 python_magnumclient-4.8.0/python_magnumclient.egg-info/SOURCES.txt0000664000175000017500000001047500000000000025303 0ustar00zuulzuul00000000000000.coveragerc .mailmap .stestr.conf .zuul.yaml AUTHORS CONTRIBUTING.rst ChangeLog LICENSE README.rst requirements.txt setup.cfg setup.py test-requirements.txt tox.ini doc/requirements.txt doc/source/conf.py doc/source/contributing.rst doc/source/index.rst doc/source/installation.rst doc/source/readme.rst doc/source/usage.rst magnumclient/__init__.py magnumclient/client.py magnumclient/exceptions.py magnumclient/i18n.py magnumclient/shell.py magnumclient/version.py magnumclient/common/__init__.py magnumclient/common/base.py magnumclient/common/cliutils.py magnumclient/common/httpclient.py magnumclient/common/utils.py magnumclient/common/apiclient/__init__.py magnumclient/common/apiclient/base.py magnumclient/common/apiclient/exceptions.py magnumclient/osc/__init__.py magnumclient/osc/plugin.py magnumclient/osc/v1/__init__.py magnumclient/osc/v1/certificates.py magnumclient/osc/v1/cluster_templates.py magnumclient/osc/v1/clusters.py magnumclient/osc/v1/mservices.py magnumclient/osc/v1/nodegroups.py magnumclient/osc/v1/quotas.py magnumclient/osc/v1/stats.py magnumclient/tests/__init__.py magnumclient/tests/base.py magnumclient/tests/test_client.py magnumclient/tests/test_httpclient.py magnumclient/tests/test_magnumclient.py magnumclient/tests/test_utils.py magnumclient/tests/utils.py magnumclient/tests/osc/__init__.py magnumclient/tests/osc/unit/__init__.py magnumclient/tests/osc/unit/osc_fakes.py magnumclient/tests/osc/unit/osc_utils.py magnumclient/tests/osc/unit/v1/__init__.py magnumclient/tests/osc/unit/v1/fakes.py magnumclient/tests/osc/unit/v1/test_certificates.py magnumclient/tests/osc/unit/v1/test_cluster_templates.py magnumclient/tests/osc/unit/v1/test_clusters.py magnumclient/tests/osc/unit/v1/test_mservices.py magnumclient/tests/osc/unit/v1/test_nodegroups.py magnumclient/tests/osc/unit/v1/test_quotas.py magnumclient/tests/osc/unit/v1/test_stats.py magnumclient/tests/test_csr/test.csr magnumclient/tests/v1/__init__.py magnumclient/tests/v1/shell_test_base.py magnumclient/tests/v1/test_certificates.py magnumclient/tests/v1/test_certificates_shell.py magnumclient/tests/v1/test_client.py magnumclient/tests/v1/test_clusters.py magnumclient/tests/v1/test_clusters_shell.py magnumclient/tests/v1/test_clustertemplates.py magnumclient/tests/v1/test_clustertemplates_shell.py magnumclient/tests/v1/test_mservices.py magnumclient/tests/v1/test_mservices_shell.py magnumclient/tests/v1/test_nodegroups.py magnumclient/tests/v1/test_quotas.py magnumclient/tests/v1/test_quotas_shell.py magnumclient/tests/v1/test_stats.py magnumclient/tests/v1/test_stats_shell.py magnumclient/v1/__init__.py magnumclient/v1/basemodels.py magnumclient/v1/baseunit.py magnumclient/v1/certificates.py magnumclient/v1/certificates_shell.py magnumclient/v1/client.py magnumclient/v1/cluster_templates.py magnumclient/v1/cluster_templates_shell.py magnumclient/v1/clusters.py magnumclient/v1/clusters_shell.py magnumclient/v1/mservices.py magnumclient/v1/mservices_shell.py magnumclient/v1/nodegroups.py magnumclient/v1/quotas.py magnumclient/v1/quotas_shell.py magnumclient/v1/shell.py magnumclient/v1/stats.py magnumclient/v1/stats_shell.py python_magnumclient.egg-info/PKG-INFO python_magnumclient.egg-info/SOURCES.txt python_magnumclient.egg-info/dependency_links.txt python_magnumclient.egg-info/entry_points.txt python_magnumclient.egg-info/not-zip-safe python_magnumclient.egg-info/pbr.json python_magnumclient.egg-info/requires.txt python_magnumclient.egg-info/top_level.txt releasenotes/notes/add-ct-tags-argument-3129c5038e95757e.yaml releasenotes/notes/bug-1870264-1b4f14618b8422f9.yaml releasenotes/notes/drop-bay-and-baymodel-79afa8d8161687ef.yaml releasenotes/notes/partial_osc_implementation_for_certificate-4597c20b59c152e1.yaml releasenotes/notes/partial_osc_implementation_for_quotas-33f44c0496d721f8.yaml releasenotes/source/2023.1.rst releasenotes/source/2023.2.rst releasenotes/source/2024.1.rst releasenotes/source/2024.2.rst releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/rocky.rst releasenotes/source/stein.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/locale/en_GB/LC_MESSAGES/releasenotes.po tools/magnum.bash_completion././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683659.0 python_magnumclient-4.8.0/python_magnumclient.egg-info/dependency_links.txt0000664000175000017500000000000100000000000027456 0ustar00zuulzuul00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683659.0 python_magnumclient-4.8.0/python_magnumclient.egg-info/entry_points.txt0000664000175000017500000000373600000000000026717 0ustar00zuulzuul00000000000000[console_scripts] magnum = magnumclient.shell:main [openstack.cli.extension] container_infra = magnumclient.osc.plugin [openstack.container_infra.v1] coe_ca_rotate = magnumclient.osc.v1.certificates:RotateCa coe_ca_show = magnumclient.osc.v1.certificates:ShowCa coe_ca_sign = magnumclient.osc.v1.certificates:SignCa coe_cluster_config = magnumclient.osc.v1.clusters:ConfigCluster coe_cluster_create = magnumclient.osc.v1.clusters:CreateCluster coe_cluster_delete = magnumclient.osc.v1.clusters:DeleteCluster coe_cluster_list = magnumclient.osc.v1.clusters:ListCluster coe_cluster_resize = magnumclient.osc.v1.clusters:ResizeCluster coe_cluster_show = magnumclient.osc.v1.clusters:ShowCluster coe_cluster_template_create = magnumclient.osc.v1.cluster_templates:CreateClusterTemplate coe_cluster_template_delete = magnumclient.osc.v1.cluster_templates:DeleteClusterTemplate coe_cluster_template_list = magnumclient.osc.v1.cluster_templates:ListTemplateCluster coe_cluster_template_show = magnumclient.osc.v1.cluster_templates:ShowClusterTemplate coe_cluster_template_update = magnumclient.osc.v1.cluster_templates:UpdateClusterTemplate coe_cluster_update = magnumclient.osc.v1.clusters:UpdateCluster coe_cluster_upgrade = magnumclient.osc.v1.clusters:UpgradeCluster coe_nodegroup_create = magnumclient.osc.v1.nodegroups:CreateNodeGroup coe_nodegroup_delete = magnumclient.osc.v1.nodegroups:DeleteNodeGroup coe_nodegroup_list = magnumclient.osc.v1.nodegroups:ListNodeGroup coe_nodegroup_show = magnumclient.osc.v1.nodegroups:ShowNodeGroup coe_nodegroup_update = magnumclient.osc.v1.nodegroups:UpdateNodeGroup coe_quotas_create = magnumclient.osc.v1.quotas:CreateQuotas coe_quotas_delete = magnumclient.osc.v1.quotas:DeleteQuotas coe_quotas_list = magnumclient.osc.v1.quotas:ListQuotas coe_quotas_show = magnumclient.osc.v1.quotas:ShowQuotas coe_quotas_update = magnumclient.osc.v1.quotas:UpdateQuotas coe_service_list = magnumclient.osc.v1.mservices:ListService coe_stats_list = magnumclient.osc.v1.stats:ListStats ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683659.0 python_magnumclient-4.8.0/python_magnumclient.egg-info/not-zip-safe0000664000175000017500000000000100000000000025636 0ustar00zuulzuul00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683659.0 python_magnumclient-4.8.0/python_magnumclient.egg-info/pbr.json0000664000175000017500000000005600000000000025067 0ustar00zuulzuul00000000000000{"git_version": "f28d71a", "is_release": true}././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683659.0 python_magnumclient-4.8.0/python_magnumclient.egg-info/requires.txt0000664000175000017500000000040300000000000026005 0ustar00zuulzuul00000000000000pbr!=2.1.0,>=2.0.0 keystoneauth1>=3.4.0 stevedore>=1.20.0 requests>=2.14.2 oslo.i18n>=3.15.3 oslo.log>=3.36.0 oslo.serialization!=2.19.1,>=2.18.0 oslo.utils>=3.33.0 os-client-config>=1.28.0 osc-lib>=1.8.0 PrettyTable>=0.7.2 cryptography>=3.0 decorator>=3.4.0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683659.0 python_magnumclient-4.8.0/python_magnumclient.egg-info/top_level.txt0000664000175000017500000000001500000000000026136 0ustar00zuulzuul00000000000000magnumclient ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740683659.3866956 python_magnumclient-4.8.0/releasenotes/0000775000175000017500000000000000000000000020323 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740683659.4226947 python_magnumclient-4.8.0/releasenotes/notes/0000775000175000017500000000000000000000000021453 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/releasenotes/notes/add-ct-tags-argument-3129c5038e95757e.yaml0000664000175000017500000000051700000000000030264 0ustar00zuulzuul00000000000000--- features: - | When creating a cluster template the administrator can use --tags argument to add any information that he considers important. The received text is a comma separated list with the pretended tags. This information is also shown when the user lists all the available cluster templates. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/releasenotes/notes/bug-1870264-1b4f14618b8422f9.yaml0000664000175000017500000000026100000000000026023 0ustar00zuulzuul00000000000000--- fixes: - | [`bug 1870264 `_] Fixed bug where the error message from the response could not be parsed. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/releasenotes/notes/drop-bay-and-baymodel-79afa8d8161687ef.yaml0000664000175000017500000000011100000000000030640 0ustar00zuulzuul00000000000000--- deprecations: - | Remove support for bays and baymodels. ././@PaxHeader0000000000000000000000000000021500000000000011453 xustar0000000000000000119 path=python_magnumclient-4.8.0/releasenotes/notes/partial_osc_implementation_for_certificate-4597c20b59c152e1.yaml 22 mtime=1740683612.0 python_magnumclient-4.8.0/releasenotes/notes/partial_osc_implementation_for_certificate-4597c20b59c10000664000175000017500000000015200000000000033670 0ustar00zuulzuul00000000000000--- features: - | Implemented Openstack command for ca-show, ca-sign, ca-rotate and stats-list. ././@PaxHeader0000000000000000000000000000021000000000000011446 xustar0000000000000000114 path=python_magnumclient-4.8.0/releasenotes/notes/partial_osc_implementation_for_quotas-33f44c0496d721f8.yaml 22 mtime=1740683612.0 python_magnumclient-4.8.0/releasenotes/notes/partial_osc_implementation_for_quotas-33f44c0496d721f8.0000664000175000017500000000021300000000000033402 0ustar00zuulzuul00000000000000--- features: - | Implemented Openstack command for quotas-create, quotas-update, quotas-delete, quotas-show and quotas-list ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740683659.4266944 python_magnumclient-4.8.0/releasenotes/source/0000775000175000017500000000000000000000000021623 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/releasenotes/source/2023.1.rst0000664000175000017500000000021000000000000023073 0ustar00zuulzuul00000000000000=========================== 2023.1 Series Release Notes =========================== .. release-notes:: :branch: unmaintained/2023.1 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/releasenotes/source/2023.2.rst0000664000175000017500000000020200000000000023075 0ustar00zuulzuul00000000000000=========================== 2023.2 Series Release Notes =========================== .. release-notes:: :branch: stable/2023.2 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/releasenotes/source/2024.1.rst0000664000175000017500000000020200000000000023075 0ustar00zuulzuul00000000000000=========================== 2024.1 Series Release Notes =========================== .. release-notes:: :branch: stable/2024.1 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/releasenotes/source/2024.2.rst0000664000175000017500000000020200000000000023076 0ustar00zuulzuul00000000000000=========================== 2024.2 Series Release Notes =========================== .. release-notes:: :branch: stable/2024.2 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/releasenotes/source/conf.py0000664000175000017500000002174000000000000023126 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. # Magnum-Client documentation build configuration file, created by # sphinx-quickstart on Tue Dec 20 11:53:00 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. # 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 = [ 'openstackdocstheme', 'reno.sphinxext', ] # 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 = 'MagnumClientReleaseNotes' copyright = '2016, OpenStack Foundation' # 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 = 'sphinx' # 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 = 'MagnumClientReleaseNotesdoc' # -- Options for LaTeX output --------------------------------------------- # The item in 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': '', latex_elements = {} # 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', 'MagnumClientReleaseNotes.tex', 'MagnumClient ReleaseNotes Documentation', 'OpenStack Foundation', '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', 'MagnumClientReleaseNotes', 'MagnumClient ReleaseNotes Documentation', ['OpenStack Foundation'], 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', 'MagnumClientReleaseNotes', 'MagnumClient ReleaseNotes Documentation', 'OpenStack Foundation', 'MagnumClientReleaseNotes', 'One line description of project.', '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/'] # -- Options for openstackdocstheme ------------------------------------------- openstackdocs_repo_name = 'openstack/python-magnumclient' openstackdocs_bug_project = 'python-magnumclient' openstackdocs_bug_tag = '' openstackdocs_auto_name = False ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/releasenotes/source/index.rst0000664000175000017500000000151200000000000023463 0ustar00zuulzuul00000000000000============================ Magnum Client Release Notes ============================ .. toctree:: :maxdepth: 1 unreleased 2024.2 2024.1 2023.2 2023.1 zed yoga xena wallaby victoria ussuri train stein rocky OpenStack Releases ------------------ The MagnumClient release that was current when the corresponding OpenStack release was made is shown below: ================= ==================== OpenStack Release MagnumClient Release ================= ==================== Rocky 2.10.0 ================= ==================== Further details for historical OpenStack releases are found at the `OpenStack Releases`_ page. .. _`OpenStack Releases`: https://releases.openstack.org/ Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740683659.3866956 python_magnumclient-4.8.0/releasenotes/source/locale/0000775000175000017500000000000000000000000023062 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740683659.3866956 python_magnumclient-4.8.0/releasenotes/source/locale/en_GB/0000775000175000017500000000000000000000000024034 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740683659.4266944 python_magnumclient-4.8.0/releasenotes/source/locale/en_GB/LC_MESSAGES/0000775000175000017500000000000000000000000025621 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po0000664000175000017500000000714000000000000030654 0ustar00zuulzuul00000000000000# Andi Chandler , 2022. #zanata msgid "" msgstr "" "Project-Id-Version: MagnumClientReleaseNotes\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-05-11 09:57+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2022-06-13 07:37+0000\n" "Last-Translator: Andi Chandler \n" "Language-Team: English (United Kingdom)\n" "Language: en_GB\n" "X-Generator: Zanata 4.3.3\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" msgid "2.10.0" msgstr "2.10.0" msgid "3.1.0" msgstr "3.1.0" msgid "3.4.0" msgstr "3.4.0" msgid ":ref:`genindex`" msgstr ":ref:`genindex`" msgid ":ref:`modindex`" msgstr ":ref:`modindex`" msgid ":ref:`search`" msgstr ":ref:`search`" msgid "Bug Fixes" msgstr "Bug Fixes" msgid "Current Release Notes" msgstr "Current Release Notes" msgid "" "Further details for historical OpenStack releases are found at the " "`OpenStack Releases`_ page." msgstr "" "Further details for historical OpenStack releases are found at the " "`OpenStack Releases`_ page." msgid "" "Implemented Openstack command for ca-show, ca-sign, ca-rotate and stats-list." msgstr "" "Implemented Openstack command for ca-show, ca-sign, ca-rotate and stats-list." msgid "" "Implemented Openstack command for quotas-create, quotas-update, quotas-" "delete, quotas-show and quotas-list" msgstr "" "Implemented Openstack command for quotas-create, quotas-update, quotas-" "delete, quotas-show and quotas-list" msgid "Indices and tables" msgstr "Indices and tables" msgid "Magnum Client Release Notes" msgstr "Magnum Client Release Notes" msgid "MagnumClient Release" msgstr "MagnumClient Release" msgid "New Features" msgstr "New Features" msgid "OpenStack Release" msgstr "OpenStack Release" msgid "OpenStack Releases" msgstr "OpenStack Releases" msgid "Rocky" msgstr "Rocky" msgid "Rocky Series Release Notes" msgstr "Rocky Series Release Notes" msgid "Stein Series Release Notes" msgstr "Stein Series Release Notes" msgid "" "The MagnumClient release that was current when the corresponding OpenStack " "release was made is shown below:" msgstr "" "The MagnumClient release that was current when the corresponding OpenStack " "release was made is shown below:" msgid "Train Series Release Notes" msgstr "Train Series Release Notes" msgid "Ussuri Series Release Notes" msgstr "Ussuri Series Release Notes" msgid "Victoria Series Release Notes" msgstr "Victoria Series Release Notes" msgid "Wallaby Series Release Notes" msgstr "Wallaby Series Release Notes" msgid "" "When creating a cluster template the administrator can use --tags " "argument to add any information that he considers important. The received " "text is a comma separated list with the pretended tags. This information is " "also shown when the user lists all the available cluster templates." msgstr "" "When creating a cluster template the administrator can use --tags " "argument to add any information that he considers important. The received " "text is a comma-separated list with the pretended tags. This information is " "also shown when the user lists all the available cluster templates." msgid "Xena Series Release Notes" msgstr "Xena Series Release Notes" msgid "Yoga Series Release Notes" msgstr "Yoga Series Release Notes" msgid "" "[`bug 1870264 `_] Fixed bug where the error message from the response could " "not be parsed." msgstr "" "[`bug 1870264 `_] Fixed bug where the error message from the response could " "not be parsed." ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/releasenotes/source/rocky.rst0000664000175000017500000000022100000000000023477 0ustar00zuulzuul00000000000000=================================== Rocky Series Release Notes =================================== .. release-notes:: :branch: stable/rocky ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/releasenotes/source/stein.rst0000664000175000017500000000022100000000000023472 0ustar00zuulzuul00000000000000=================================== Stein Series Release Notes =================================== .. release-notes:: :branch: stable/stein ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/releasenotes/source/train.rst0000664000175000017500000000017600000000000023476 0ustar00zuulzuul00000000000000========================== Train Series Release Notes ========================== .. release-notes:: :branch: stable/train ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/releasenotes/source/unreleased.rst0000664000175000017500000000012600000000000024503 0ustar00zuulzuul00000000000000===================== Current Release Notes ===================== .. release-notes:: ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/releasenotes/source/ussuri.rst0000664000175000017500000000020200000000000023701 0ustar00zuulzuul00000000000000=========================== Ussuri Series Release Notes =========================== .. release-notes:: :branch: stable/ussuri ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/releasenotes/source/victoria.rst0000664000175000017500000000021200000000000024170 0ustar00zuulzuul00000000000000============================= Victoria Series Release Notes ============================= .. release-notes:: :branch: stable/victoria ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/releasenotes/source/wallaby.rst0000664000175000017500000000020600000000000024006 0ustar00zuulzuul00000000000000============================ Wallaby Series Release Notes ============================ .. release-notes:: :branch: stable/wallaby ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/releasenotes/source/xena.rst0000664000175000017500000000017200000000000023310 0ustar00zuulzuul00000000000000========================= Xena Series Release Notes ========================= .. release-notes:: :branch: stable/xena ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/releasenotes/source/yoga.rst0000664000175000017500000000020000000000000023304 0ustar00zuulzuul00000000000000========================= Yoga Series Release Notes ========================= .. release-notes:: :branch: unmaintained/yoga ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/releasenotes/source/zed.rst0000664000175000017500000000017400000000000023141 0ustar00zuulzuul00000000000000======================== Zed Series Release Notes ======================== .. release-notes:: :branch: unmaintained/zed ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/requirements.txt0000664000175000017500000000120500000000000021114 0ustar00zuulzuul00000000000000# Requirements lower bounds listed here are our best effort to keep them up to # date but we do not test them so no guarantee of having them all correct. If # you find any incorrect lower bounds, let us know or propose a fix. pbr!=2.1.0,>=2.0.0 # Apache-2.0 keystoneauth1>=3.4.0 # Apache-2.0 stevedore>=1.20.0 # Apache-2.0 requests>=2.14.2 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.log>=3.36.0 # Apache-2.0 oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 os-client-config>=1.28.0 # Apache-2.0 osc-lib>=1.8.0 # Apache-2.0 PrettyTable>=0.7.2 # BSD cryptography>=3.0 # BSD/Apache-2.0 decorator>=3.4.0 # BSD ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740683659.4306943 python_magnumclient-4.8.0/setup.cfg0000664000175000017500000000562400000000000017462 0ustar00zuulzuul00000000000000[metadata] name = python-magnumclient summary = Client library for Magnum API description_file = README.rst author = OpenStack author_email = openstack-discuss@lists.openstack.org home_page = https://docs.openstack.org/python-magnumclient/latest/ python_requires = >=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 :: Implementation :: CPython Programming Language :: Python :: 3 :: Only 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 = magnumclient [entry_points] console_scripts = magnum = magnumclient.shell:main openstack.cli.extension = container_infra = magnumclient.osc.plugin openstack.container_infra.v1 = coe_cluster_template_create = magnumclient.osc.v1.cluster_templates:CreateClusterTemplate coe_cluster_template_delete = magnumclient.osc.v1.cluster_templates:DeleteClusterTemplate coe_cluster_template_list = magnumclient.osc.v1.cluster_templates:ListTemplateCluster coe_cluster_template_show = magnumclient.osc.v1.cluster_templates:ShowClusterTemplate coe_cluster_template_update = magnumclient.osc.v1.cluster_templates:UpdateClusterTemplate coe_cluster_create = magnumclient.osc.v1.clusters:CreateCluster coe_cluster_list = magnumclient.osc.v1.clusters:ListCluster coe_cluster_delete = magnumclient.osc.v1.clusters:DeleteCluster coe_cluster_show = magnumclient.osc.v1.clusters:ShowCluster coe_cluster_update = magnumclient.osc.v1.clusters:UpdateCluster coe_cluster_config = magnumclient.osc.v1.clusters:ConfigCluster coe_cluster_resize = magnumclient.osc.v1.clusters:ResizeCluster coe_cluster_upgrade = magnumclient.osc.v1.clusters:UpgradeCluster coe_ca_rotate = magnumclient.osc.v1.certificates:RotateCa coe_ca_show = magnumclient.osc.v1.certificates:ShowCa coe_ca_sign = magnumclient.osc.v1.certificates:SignCa coe_stats_list = magnumclient.osc.v1.stats:ListStats coe_quotas_create = magnumclient.osc.v1.quotas:CreateQuotas coe_quotas_delete = magnumclient.osc.v1.quotas:DeleteQuotas coe_quotas_update = magnumclient.osc.v1.quotas:UpdateQuotas coe_quotas_show = magnumclient.osc.v1.quotas:ShowQuotas coe_quotas_list = magnumclient.osc.v1.quotas:ListQuotas coe_service_list = magnumclient.osc.v1.mservices:ListService coe_nodegroup_list = magnumclient.osc.v1.nodegroups:ListNodeGroup coe_nodegroup_show = magnumclient.osc.v1.nodegroups:ShowNodeGroup coe_nodegroup_create = magnumclient.osc.v1.nodegroups:CreateNodeGroup coe_nodegroup_delete = magnumclient.osc.v1.nodegroups:DeleteNodeGroup coe_nodegroup_update = magnumclient.osc.v1.nodegroups:UpdateNodeGroup [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/setup.py0000664000175000017500000000127100000000000017345 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=1740683612.0 python_magnumclient-4.8.0/test-requirements.txt0000664000175000017500000000050600000000000022074 0ustar00zuulzuul00000000000000hacking>=6.1.0,<6.2.0 # Apache-2.0 bandit!=1.6.0,>=1.1.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD python-openstackclient>=3.12.0 # Apache-2.0 oslotest>=3.2.0 # Apache-2.0 osprofiler>=1.4.0 # Apache-2.0 stestr>=2.0.0 # Apache-2.0 testscenarios>=0.4 # Apache-2.0/BSD testtools>=2.2.0 # MIT ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740683659.4266944 python_magnumclient-4.8.0/tools/0000775000175000017500000000000000000000000016772 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/tools/magnum.bash_completion0000664000175000017500000000211300000000000023343 0ustar00zuulzuul00000000000000_magnum_opts="" # lazy init _magnum_flags="" # lazy init _magnum_opts_exp="" # lazy init _magnum() { local cur prev nbc cflags COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" if [ "x$_magnum_opts" == "x" ] ; then nbc="`magnum bash-completion | sed -e "s/ *-h */ /" -e "s/ *-i */ /"`" _magnum_opts="`echo "$nbc" | sed -e "s/--[a-z0-9_-]*//g" -e "s/ */ /g"`" _magnum_flags="`echo " $nbc" | sed -e "s/ [^-][^-][a-z0-9_-]*//g" -e "s/ */ /g"`" _magnum_opts_exp="`echo "$_magnum_opts" | tr ' ' '|'`" fi if [[ " ${COMP_WORDS[@]} " =~ " "($_magnum_opts_exp)" " && "$prev" != "help" ]] ; then COMPLETION_CACHE=~/.magnumclient/*/*-cache cflags="$_magnum_flags "$(cat $COMPLETION_CACHE 2> /dev/null | tr '\n' ' ') COMPREPLY=($(compgen -W "${cflags}" -- ${cur})) else COMPREPLY=($(compgen -W "${_magnum_opts}" -- ${cur})) fi return 0 } complete -F _magnum magnum ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740683612.0 python_magnumclient-4.8.0/tox.ini0000664000175000017500000000344700000000000017155 0ustar00zuulzuul00000000000000[tox] minversion = 3.18 envlist = py3,pep8 [testenv] usedevelop = True allowlist_externals = find setenv = VIRTUAL_ENV={envdir} PYTHONWARNINGS=default::DeprecationWarning 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 "*.py[c|o]" -delete stestr run --slowest {posargs} [testenv:bandit] basepython = python3 deps = -c{env:UPPER_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/branch/master/upper-constraints.txt} -r{toxinidir}/test-requirements.txt commands = bandit -r magnumclient -x tests -n5 -ll [testenv:debug] basepython = python3 commands = oslo_debug_helper -t magnumclient/tests {posargs} [testenv:pep8] basepython = python3 commands = flake8 # Run security linter bandit -r magnumclient -x tests -n5 -ll [testenv:docs] basepython = python3 deps = -r{toxinidir}/doc/requirements.txt commands = sphinx-build -W -b html doc/source doc/build/html [testenv:venv] basepython = python3 commands = {posargs} [testenv:cover] basepython = python3 setenv = PYTHON=coverage run --source magnumclient --parallel-mode commands = stestr run {posargs} coverage combine coverage html -d cover coverage xml -o cover/coverage.xml coverage report [flake8] # E123, E125 skipped as they are invalid PEP-8. show-source = True ignore = E123,E125,W503,W504 builtins = _ exclude=.venv,.git,.tox,dist,doc,,*lib/python*,*egg,build [hacking] import_exceptions = magnumclient._i18n [testenv:releasenotes] basepython = python3 deps = -r{toxinidir}/doc/requirements.txt commands = rm -rf releasenotes/build sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html