././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1725030944.8644686 python-novaclient-18.7.0/0000775000175000017500000000000000000000000015313 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/.coveragerc0000664000175000017500000000014100000000000017430 0ustar00zuulzuul00000000000000[run] branch = True source = novaclient omit = novaclient/tests/* [report] ignore_errors = True ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/.mailmap0000664000175000017500000000267300000000000016744 0ustar00zuulzuul00000000000000Antony Messerli root Chris Behrens comstud Joe Gordon Johannes Erdfelt jerdfelt Andy Smith termie hwbi Nikolay Sokolov Nokolay Sokolov Nikolay Sokolov Nokolay Sokolov zhangguoqing ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/.pre-commit-config.yaml0000664000175000017500000000155100000000000021576 0ustar00zuulzuul00000000000000--- default_language_version: # force all unspecified python hooks to run python3 python: python3 repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.1.0 hooks: - id: trailing-whitespace - id: mixed-line-ending args: ['--fix', 'lf'] - id: check-byte-order-marker - id: check-executables-have-shebangs - id: check-merge-conflict - id: debug-statements - id: check-yaml files: .*\.(yaml|yml)$ - repo: https://github.com/Lucas-C/pre-commit-hooks rev: v1.1.13 hooks: - id: remove-tabs exclude: '.*\.(svg)$' - repo: local hooks: - id: flake8 name: flake8 additional_dependencies: - hacking>=6.1.0,<6.2.0 language: python entry: flake8 files: '^.*\.py$' exclude: '^(doc|releasenotes|tools)/.*$' ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/.stestr.conf0000664000175000017500000000011000000000000017554 0ustar00zuulzuul00000000000000[DEFAULT] test_path=${OS_TEST_PATH:-./novaclient/tests/unit} top_dir=./ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/.zuul.yaml0000664000175000017500000000135600000000000017261 0ustar00zuulzuul00000000000000- job: name: python-novaclient-functional parent: devstack-tox-functional timeout: 7200 required-projects: - openstack/nova - openstack/python-novaclient vars: openrc_enable_export: true devstack_localrc: KEYSTONE_ADMIN_ENDPOINT: true NEUTRON_ENFORCE_SCOPE: false irrelevant-files: - ^.*\.rst$ - ^doc/.*$ - ^releasenotes/.*$ - project: templates: - check-requirements - lib-forward-testing-python3 - openstack-cover-jobs - openstack-python3-jobs - publish-openstack-docs-pti - release-notes-jobs-python3 check: jobs: - python-novaclient-functional gate: jobs: - python-novaclient-functional ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030944.0 python-novaclient-18.7.0/AUTHORS0000664000175000017500000004762700000000000016403 0ustar00zuulzuul00000000000000Aaron Lee Aaron Rosen Aarti Kriplani Abhishek Chanda Abhishek Kekane Abhishek Lahiri Abhishek Talwar Adam Gandelman Adam Spiers Akihiro MOTOKI Akihiro Motoki Akshil Verma Ala Rezmerita Alan Pevec Alessandro Pilotti Alessandro Pilotti Alessio Ababilov Alessio Ababilov Alex Gaynor Alex Glikson Alex Meade Alexis Lee Alvaro Lopez Garcia Amandeep Anand Shanmugam Andrea Rosa Andreas Jaeger Andreas Jaeger Andrew Bogott Andrew Laski Andrew Lazarev Andrey Brindeyev Andrey Kurilin Andrey Kurilin Andrey Pavlov Andrey Volkov Andy Hill Andy McCrae Andy Smith Anita Kuno Ankit Agrawal Anna Babich Anthony Young Antony Messerli Arata Notsu Armando Migliaccio Artom Lifshitz Arundhati Surpur Arvinder Singh Atsushi SAKAI Augustina Ragwitz Avishay Traeger Balazs Gibizer Balazs Gibizer Balazs Gibizer Bartosz Górski Bartosz Górski Bartosz Górski Ben Nemec Ben Nemec Bin Zhou Bo Wang Boris Pavlovic Brian Elliott Brian Lamar Brian Waldon Brianna Poulos Cao ShuFeng Cao Shufeng Carlos Goncalves Cedric Brandily Chang Bo Guo ChangBo Guo(gcb) Chaozhe.Chen Chen Chen Hanxiao Chmouel Boudjnah Chmouel Boudjnah Chris Behrens Chris Buccella Chris Dent Chris Friesen Chris Jones Chris Krelle Chris St. Pierre Chris Yeoh Christian Berendt Christoph Gysin Christopher J Schaefer Christopher MacGown Christopher Yeoh Chuck Chuck Carmack Chuck Short Chung Chih, Hung Clark Boylan Claudiu Belu Clenimar Filemon Clint Byrum Cole Robinson Corey Bryant Cyril Roelandt Dai Dang Van Dan Prince Dan Smith Dan Smith Dan Wendlandt Daniel P. Berrange Daniel Wallace Dao Cong Tien Davanum Srinivas Davanum Srinivas Dave Walker (Daviey) Dave Wilde David Hu David Kranz David Moreau Simard David Scannell David Wittman David Wlazlo Dean Troyer Diana Clarke Dinesh Bhor Dirk Mueller Dominik Heidler Doug Hellmann Doug Hellmann Ed Leafe EdLeafe Eldar Nugaev Eli Qiao Elod Illes Emanuele Rocca Eoghan Glynn Eric Brown Eric Fried Eric Guo Eric Harney Eugeniya Kudryashova Feodor Tersin Feodor Tersin Flaper Fesp Flavio Percoco Florent Flament François Charlier Gabriel Hurley Gary Kotton Gaurav Gupta Ghanshyam Ghanshyam Mann Ghe Rivero Gleb Stepanov Gordon Chung Guoqiang Ding Gábor Antal Haiwei Xu Hans Lindgren Haomeng, Wang He Jie Xu Hengqing Hu Hironori Shiina Huanxuan Ao Hugh Saunders Ian Cordasco Ian Cordasco Ian Wienand Ihar Hrachyshka Ikuo Kumagai Ilya Alekseyev Istvan Imre JUN JIE NAN Jacek Tomasiak Jackie Truong Jake Dahn Jake Yip Jakub Ruzicka James E. Blair James E. Blair James Meredith James Penick Jamie Lennox Jamie Lennox Janonymous Jaroslav Henner Jason Dunsmore Jason Kölker Jason Straw Javeme Jay Lau Jay Pipes Jeffrey Guan Jens Rosenboom Jeremy Liu Jeremy Stanley Jesse Andrews Jiajun Liu Jianing YANG Jiao Pengju Jim Rollenhagen Jimmy McCrory Joao Targino Joe Gordon Joe Heck Johannes Erdfelt John Garbutt John Tran Josh Kearney Joshua Harlow Juan Manuel Olle Julie Pichon Julien Danjou Jyotsna Katie McLaughlin Kaushik Chandrashekar Ken'ichi Ohmichi Ken'ichi Ohmichi Kevin L. Mitchell Kevin L. Mitchell Kevin_Zheng Kiall Mac Innes Kien Nguyen Kieran Spear Kirill Shileev Kravchenko Pavel Kristi Nikolla Kui Shi Kurt Taylor Kyrylo Romanenko Lajos Katona Laurens Van Houtven <_@lvh.cc> Leandro I. Costantino Lee Yarwood Leo Toyoda Liang Chen LiuNanke Lorin Hochstein Lorin Hochstein Luigi Toscano Luong Anh Tuan Lvov Maxim M. David Bennett Mahesh Panchaksharaiah Major Hayden Mark Doffman Mark McClain Mark McLoughlin Masaki Matsushita Masayuki Igawa Matt Dietz Matt Fischer Matt Riedemann Matt Riedemann Matt Stephenson Matt Thompson Matthew Farrellee Matthew Gilliard Matthew Treinish Melanie Witt Michael Basnight Michael Davies Michael Still Michal Dulko Miguel Grinberg Mike Lundy Mitsuhiko Yamazaki Mitsuhiro Tanino Monty Taylor Morgan Fainberg Nachi Ueno Natsuki Maruyama Nguyen Hai Nguyen Hung Phuong Nicholas Mistry Nick Shobe Nicolas Simonds Nikola Dipanov Nikolay Sokolov Noorul Islam K M Ollie Leahy Ondřej Nový OpenStack Release Bot Paul Voccio Paul Voccio Pavel Kholkin Pavel Shkitin Pavlo Shchelokovskyy Pawel Koniszewski Pedro Navarro Peng Yong Pete Savage Phil Day Pádraig Brady Qin Zhao Radomir Dopieralski Radoslav Gerganov Rafael Rivero Rafi Khardalian Rahul Rajesh Tailor Rajiv Kumar Rakesh H S Ralf Haferkamp Ramaraja Ramaraja Ramachandran Rami Vaknin Ravi Shekhar Jethani René Ribaud Richard Jones Rick Harris Ritesh Paiboina Robie Basak Rohan Rhishikesh Kanade Roman Podoliaka Roman Podolyaka Roman Rader Ronald Bradford Rui Chen Rui Zang Russell Bryant Russell Cloran Russell Sim Ryan Hallisey Ryan Wilson Sahid Orentino Ferdjaoui Sahid Orentino Ferdjaoui Sam Morrison Sandy Walsh SandyWalsh Sanjay Kumar Singh Sarah Ulmer Sascha Peilicke Sascha Peilicke Sascha Peilicke Satheesh Ulaganathan Sathish Nagappan Scott Devoid Scott Moser Sean Dague Sean Dague Sean McCully Sean McGinnis Sean Mooney Sen Yang Sergey Lukjanov Sergey Nikitin Sergio Cazzolato Shane Wang ShaoHe Feng Shrirang Phadke Shuangtai Tian Simon Leinen Solly Ross Stanislaw Pitucha Stanisław Pitucha Stef T Stephen Finucane Stephen Finucane Steven Kaufer Sujitha Sulochan Acharya Sumanth Nagadavalli Surya Seetharaman Sushil Kumar Sven Anderson Svetlana Shturm Swapnil Kulkarni (coolsvap) Sylvain Bauza Takashi Kajinami Takashi Kajinami Takashi NATSUME Takashi Natsume Takashi Sogabe Tao Li Tatiana Kholkina Theodoros Tsioutsias Thiago Paiva Thierry Carrez Thomas Bechtold Thomas Goirand Thomas Schreiber TianTian Gao Tihomir Trifonov Timofey Durakov Tom Cammann Tomi Juvonen Tomofumi Hayashi Tony Breeds Tony Xu Tovin Seven Trey Morris Ubuntu Ukesh Kumar Vasudevan Unmesh Gurjar Unmesh Gurjar Vasyl Khomenko Veronica Musso Victor Coutellier Victor Morales Victor Stinner Victoria Martínez de la Cruz Vincent Hou Vincent Untz Vishvananda Ishaya Vitaliy Kolosov Vu Cong Tuan Wang Wei William Wolf Wu Wenxiang Xavier Queralt Xiao Chen Xiao Hanyu Xiaowei Qian Yaguang Tang Yaguang Tang Yikun Jiang Yongli He Your Name YuehuiLei Yufang Zhang Yunhong, Jiang Yuriy Taraday Yusuke Ide Yuuichi Fujioka Zane Bitter Zhengguang Zhenguo Niu Zhenzan Zhou Zhi Yan Liu ZhiQiang Fan ZhiQiang Fan Zhihai Zhihai Song ZhijunWei Zhiteng Huang Zhongyue Luo ZhuChunzhan Ziad Sawalha abhishek.talwar annegentle asmita singh bhagyashris chen-li chenaidong1 chenke chenxing chenxing dineshbhor fujioka yuuichi gengchc2 gengjh ghanshyam gtt116 guoqingzhang henriquetruta hgangwx huangtianhua hwbi igormilovanovic int32bit ivan-zhu jakedahn jeremyfreudberg jichenjc john-griffith jonnary kangyufei kylin7-sg lei zhang liaonanhai libing licanwei liu-sheng liuan liuqing liuyamin liyingjun liyingjun lizheming lrqrun lzyeval masumotok melanie witt melanie witt melwitt meretiko neetu nidhimittalhada openstack ozg pengyuesheng qingszhao qiufossen rahulram raiesmh08 rajiv.kumar ricolin ricolin rsritesh sampathP sathish-nagappan shihanzhang shilpa.devharakar shu-mutou songwenping sonu.kumar sridhargaddam sunjia sunjiazz tengqm unicell vagrant venkatamahesh vnathan wangxiyuan whoami-rajat wingwj wu.chunyang wu.shiming xiexs yamini sardana yanghuichan yangyapeng yatin karel yatinkarel yuyafei zhang-jinnan zhang.lei zhangbailin zhangboye zhangdaolong zhangjl zhangtralon zhangyangyang zhangyanzi zhaolihui zhengyao1 zhiyanliu zhu.boxiang Édouard Thuleau 翟小君 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/CONTRIBUTING.rst0000664000175000017500000000120300000000000017750 0ustar00zuulzuul00000000000000The source repository for this project can be found at: https://opendev.org/openstack/python-novaclient Pull requests submitted through GitHub are not monitored. To start contributing to OpenStack, follow the steps in the contribution guide to set up and use Gerrit: https://docs.openstack.org/contributors/code-and-documentation/quick-start.html Bugs should be filed on Launchpad: https://bugs.launchpad.net/python-novaclient For more specific information about contributing to this repository, see the python-novaclient contributor guide: https://docs.openstack.org/python-novaclient/latest/contributor/contributing.html ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030944.0 python-novaclient-18.7.0/ChangeLog0000664000175000017500000027665200000000000017107 0ustar00zuulzuul00000000000000CHANGES ======= 18.7.0 ------ * [doc] change directive noindex to no-index * Fix Python 3.12 compatibility * tox: Add Python-specific functional envs * functional: Handle multiple networks * support get and list server's metadata * Remove old excludes * reno: Update master for unmaintained/zed * Update master for stable/2024.1 18.6.0 ------ * Bump microversion to 2.96 * reno: Update master for unmaintained/xena * reno: Update master for unmaintained/wallaby * reno: Update master for unmaintained/victoria 18.5.0 ------ * reno: Update master for unmaintained/yoga * Update hacking version in pre commit config * Bump hacking * Update python classifier in setup.cfg * coveragerc: Fix wrong omitted directory * Disable NEUTRON\_ENFORCE\_SCOPE at function job * Fix typos * add pyproject.toml to support pip 23.1 * Update master for stable/2023.2 18.4.0 ------ * Typo - nova CLI deprecation warning * Update master for stable/2023.1 18.3.0 ------ * Bump microversion to 2.95 * Make tox.ini tox 4.0.0 compatible * tests: Fix Python 3.11 compatibility * trivial: Remove use of kwargs * Update python classifier for python 3.10 18.2.0 ------ * Remove unnecessary testing code * Switch to 2023.1 Python3 unit tests and generic template name * Update master for stable/zed * MV 2.93 - Add support to rebuild boot volume 18.1.0 ------ * Fix a fixture for keypairs tests * Add support for 2.92 : keypair import mandatory * Microversion 2.91: Support specifying destination host to unshelve * Imported Translations from Zanata * Replace old URLs with new ones * Imported Translations from Zanata 18.0.0 ------ * Drop lower-constraints.txt and its testing * Update python classifier as per testing runtime * Remove unnecessary packages in bindep.txt * Add openssl in bindep.txt * Add Python3 zed unit tests * Update pre-commit hook, hacking versions * docs: Update docs to reflect deprecation status * Deprecate nova CLI * Remove USE\_PYTHON3 in .zuul.yaml * Update master for stable/yoga 17.7.0 ------ * Updating python testing classifier as per Yoga testing runtime * Fix check job failures * Add Python3 yoga unit tests * Update master for stable/xena 17.6.0 ------ * Microversion 2.90 - Configurable hostnames * tests: Add missing 'nova update' unit tests * Add pre-commit * Microversion 2.89 - os-volume\_attachments * Use importlib instead of imp * Use Block Storage API v3 instead of API v2 17.5.0 ------ * Change minversion of tox to 3.18.0 * Refactor constructing request body * setup.cfg: Replace dashes with underscores * Use py3 as the default runtime for tox * Add unit tests for client logger * When creating a client, pass the default logger * Add Python3 xena unit tests * Update master for stable/wallaby * Use well named anchor into the microversion history 17.4.0 ------ * Add support for microversion v2.88 * requirements: Remove simplejson * Uncap PrettyTable 17.3.0 ------ * Deprecate agent commands and APIs * Fix undesirable raw Python error * Fix a functional test for 'nova agent-list' * Remove Babel from lower-constraints.txt * Cleanup py27 support * Remove the unused coding style modules * Remove install unnecessary packages * Add Python3 wallaby unit tests * Update master for stable/victoria 17.2.1 ------ * zuul functional job: drop the custom playbooks * Remove unused code * Add a cleanup for a server in a functional test 17.2.0 ------ * migrate testing to ubuntu focal * Switch legacy Zuul jobs to native Zuul v3 jobs * use stevedore to load extensions 17.1.0 ------ * Remove Babel requirement * Add link to PDF document * Switch to newer openstackdocstheme and reno versions * Bump hacking min version to 3.0.1 * Remove mock in lower-constraints.txt * Use unittest.mock instead of third party mock * Remove future imports * doc: Update Testing document * Bump default tox env from py37 to py38 * Switch to using TOX\_CONSTRAINTS\_FILE * Add py38 package metadata * [Community goal] Update contributor documentation * Add Python3 victoria unit tests * Update master for stable/ussuri 17.0.0 ------ * Microversion 2.87 - Stable device boot from volume rescue * [Trivial] FUP: Enhanced description for 'server list --config-drive' help * FUP: Add volume-update CLI pre V285 tests * Microversion 2.86 - Extra spec validation * Microversion 2.85: Change volume-update CLI * Make 'server list --config-drive' a boolean option * Microversion 2.84 - action event fault details * Microversion 2.83 - Add more filters for the nova list command * Update to hacking 3.0 * Microversion 2.82 - nova cyborg interaction * Don't print user\_data for 'nova show' * Random cleanups * Bump to hacking 2.x * Remove six * tox: Configure 'ignore\_basepython\_conflict' * setup.cfg: Various Python 3 fixes * trivial: Remove 'u' prefix from string * doc: Update Testing document * doc: Fix supported version descriptions * Stop supporting and testing python2 * Switch to Ussuri jobs * Add minor version [21] to the test\_versions 16.0.0 ------ * Add aggregate-cache-images command and client routines * Add images.GlanceManager.find\_images() bulk query * Add functional test for migration-list in v2.80 * Microversion 2.80: Add user\_id/project\_id to migration-list API * PDF documentation build * Remove cells v1 and extension commands and APIs * Add release note for bug 1845322 * Stop silently ignoring invalid 'nova boot --hint' options * Update master for stable/train * Add a check for --config-drive option on nova boot * doc: Add support microversions for options 15.1.0 ------ * Microversion 2.79: Add delete\_on\_termination to volume-attach API * Microversion 2.78 - show server topology 15.0.0 ------ * Microversion 2.77: Support Specifying AZ to unshelve * Clarify --migration-type migration value as cold migration * Follow up for microversion 2.75 * API microversion 2.76: Add 'power-update' external event * Microversion 2.75 - Multiple API cleanup changes * Add --migration-type and --source-compute to migration-list * docs: clarify nova migration-list --host option * Update api-ref location * Bump the openstackdocstheme extension to 1.20 * doc: Clarify versioned wrapped method 14.2.0 ------ * Deprecate cells v1 and extension commands and APIs * Add a guide to add a new microversion support * Add host and hypervisor\_hostname to create servers * Add Python 3 Train unit tests * Remove deprecated methods and properties * Modify the url of upper\_constraints\_file * Add Python 3 Train unit tests * Blacklist sphinx 2.1.0 (autodoc bug) * Add irrelevant files in dsvm job again * Revert "Add irrelevant files in dsvm job" * Add irrelevant files in dsvm job * Fix duplicate object description error * Blacklist python-cinderclient 4.0.0 * Bump openstackdocstheme to 1.30.0 14.1.0 ------ * Add a description of --on-shared-storage * Set the lower limit of api\_version for volume\_type * Allow passing negative values for the locked search\_opt in cs.servers.list * Allow searching for hypervisors and getting back details * Optimize limit option docs string description for novaclient 14.0.0 ------ * Cap sphinx for py2 and drop keyring dependency * [Docs] Update client docs to add reason and locked options * Microversion 2.73: Support adding the reason behind a server lock * Use SHA256 instead of MD5 in completion cache * Tiny fix of documentation * Updates for OpenDev transition * Drop py35 tests * OpenDev Migration Patch * Add test for console-log and docs for bug 1746534 * Revert "Fix crashing console-log" * Replace openstack.org git:// URLs with https:// * Remove deprecated options * Update master for stable/stein 13.0.0 ------ * Add support for microversion v2.72 * Microversion 2.71 - show server group * Remove unnecessary if statement * Add support for microversion 2.70 - expose device tags * Fix changes-before values in an instance action test * Handle unicode multi-byte characters * API microversion 2.69: Handles Down Cells * Make Server.networks use a predictable sort order * Fix output of interface-attach command * add python 3.7 unit test job * Microversion 2.68: Remove 'forced' live migrations, evacuations 12.0.0 ------ * Remove deprecated novaclient.v2.contrib modules * Add a note in "nova service-delete" help about deleting computes * Update hacking version * Fix flavor keyerror when nova boot vm * Change openstack-dev to openstack-discuss * Add Python 3.6 classifier to setup.cfg * Fix a type of block\_device\_mapping\_v2 in a comment * Fixes Python3 issue in decoding password * Deprecate the unused instance-name * Replace MB with MiB 11.1.0 ------ * Recommend against using --force for evacuate/live migration * Add support for microversion 2.67: BDMv2 volume\_type * doc: Start using openstackdoctheme's extlink extension * Follow up "Fix up userdata argument to rebuild" * Update the CLI reference * Update the contributor guide * Fix up userdata argument to rebuild * Fix test\_instance\_action functional test failure * Add support changes-before for microversion 2.66 * docs: Add redirects * Follow the new PTI for document build * Improve the description of optional arguments * Cleanup zuul.yaml * Add missing options in CLI reference * add lib-forward-testing-python3 test job * add python 3.6 unit test job * switch documentation job to new PTI * import zuul job settings from project-config * Enable split logging for cinder-novaclient interaction * Replace os-client-config to openstacksdk * Use uuidutils of oslo.utils * Fix server strings in reboot operation * Refactor the getid method in novaclient/base.py * Use jsonutils of oslo.serialization * Update reno for stable/rocky 11.0.0 ------ * Fix the help text for server-group-create * Fix inconsistency * Add support for microversion 2.65 * Microversion 2.64 - Use new format policy in server group * Switch to stestr * Rename --endpoint-override to --os-endpoint-override * Add release note link in README * Fix help text in server-group-create * Fix trusted-image-certificate-id help text * Microversion 2.63 - Add trusted\_image\_certificates * Add CLI docs reference for flavor-update * Remove doc/build when building docs * Modify novaclient to support basic attributes * Remove PyPI downloads * fix tox python3 overrides 10.3.0 ------ * Add a note in the nova CLI reference about using OSC * Import nova CLI reference from openstack-manuals * Revert "Fix listing of instances above API max\_limit" * Fix the incorrect cirros default password * Fix the policy argument in server-group-create * Make sure microversion < 2.62 does not show host(Id) for instance actions * Microversion 2.62 - Add host/hostId to instance action event * Trivial: Update pypi url to new url 10.2.0 ------ * [CLI] Fix token auth type * Microversion 2.61 - support extra\_specs in flavor API * Fix comments in novaclient/tests/unit/fakes.py * Stop printing flavor details on successful flavor-delete * Replace GB with GiB * Fix AttributeError in getting a resource ID * Fix validation for command arguments * Updated from global requirements * add lower-constraints job * Fix local test fails with pypy * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Add os-testr in test-requirements.txt * Fix a comment in novaclient/api\_versions.py * Clean imports in code * Fix the docstring for the update method * Updated from global requirements * Updated from global requirements * Remove 2 redundant methods * Fix crashing console-log * Fix listing of instances above API max\_limit * nova limits ERROR (Exception): Field names must be unique * Zuul: Remove project name * Implement hypervisor hostname exact pattern match * Add CLI to show instance usage audit logs * Update reno for stable/queens * Updated from global requirements 10.1.0 ------ * Add support for microversion 2.60 - volume multiattach * Microversion 2.59 - Migrations list pagination * Update documentation links * Updated from global requirements * Add missing spaces in \`nova list --changes-since\` help * Microversion 2.58 - Instance actions list pagination * Add support for the 2.57 microversion 10.0.0 ------ * Remove deprecated services binary CLI arg * Remove deprecated fixedip/floatingip/virtual interface CLIs/APIs * Remove deprecated os-hosts CLIs and python API bindings * Remove deprecated cloudpipe CLIs and python API bindings * Remove deprecated certs CLIs and python bindings * Remove deprecated MigrationManager.list cell\_name kwarg * Fix being able to create a reno using tox -e venv * Remove incorrect legacy QuotaSet.id property * Updated from global requirements * boot: error out if no images match the property from --image-with * Update new documentation PTI jobs * boot: show warning if more than one match when setting --image-with * Remove irrelevant note * flavor create: clarify --swap description * Updated from global requirements * Updated from global requirements * Optimize jobs run on novaclient * Remove deprecated command in nova.rst * CommandError is raised for invalid server fields * inject file: add method of showing quota value of injecting files for 'rebuild' command * Updated from global requirements * Microversion 2.56 - Enable cold migration with target host * Avoid tox\_install.sh for constraints support * inject file: add description of injecting multiple files * [ci] Use pseudo-random names for new resources * Add support for microversion 2.55 - flavor description * Use utils.prepare\_query\_string instead of duplicated code * Move zuulv3 jobs to project repo * [functional] Remove duplication of boot helper * Microversion 2.54 - Enable reset keypair while rebuild * Remove setting of version/release from releasenotes * Remove SecretsHelper * Stop posting to os-volumes\_boot * Updated from global requirements * Fix missing metavar in aggregate-update * Updated from global requirements * Updated from global requirements * Use generic user for both zuul v2 and v3 * Update "The nova Shell Utility" in the user guide * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Allow boot server with multiple nics * Remove substitutions for command error msg * tools: Remove dead script * Updated from global requirements * Fix reservation\_id not supported by Nova API * Replace six.itervalues() with dict.values() in python-novaclient * Update reno for stable/pike * Updated from global requirements * Remove run\_tests.sh * Update URLs in docs, comments and setup.cfg * Updated from global requirements 9.1.0 ----- * doc: Remove Makefile * Allow tuple as for nics value * Help text for "--matching" is not clear * Be clear about hypevisors.search used in a few CLIs * Change Service repr to use self.id always * Microversion 2.53 - services and hypervisors using UUIDs * Add 'Forced down' column in serivce-list * Updated from global requirements * Updated from global requirements * Updated from global requirements * Expect id and disabled\_reason in GET /os-services response * Updated from global requirements * Adjust test\_resize\_down\_revert to account for counting quotas * Updated from global requirements * Microversion 2.52 - Support tag when boot * Updated from global requirements * Fix the inappropriate parameter name * Add resize down test which also verifies quota changes * Add functional test for resize-confirm plus quota validation * Add support for the 2.51 microversion * Microversion 2.50 - fix quota class sets resource usage * Updated from global requirements * Remove custom autodoc implementation * doc: Switch from oslosphinx to openstackdocstheme * doc: Create directory structure for docs migration * Microversion 2.49 - Virt device tagged attach * Fix cropping the endpoint url * Removed extra word 'method' from the NOTE * Deprecate binary argument in nova service enable/disable/force-down CLIs * Updated from global requirements * Microversion 2.48: Standardization of VM diagnostics * 2.47: Show flavor info in server details * Cleanup duplicated methods * Make --profile load from environment variables 9.0.1 ----- * Revert "client.logger.warning wrongly used in migrations" * Clean up ShellTest unit tests * Fix setting 'global\_request\_id' in SessionClient 9.0.0 ----- * Updated from global requirements * Prevent 2.32 release note from showing up in 9.0.0 release notes * Skip rebuild functional test due to persistent vif plugging timeout * strip the remote prefixes from the release note branch specifiers * 2.46: match nova API version * Help message for aggregate-update is ambiguous * client.logger.warning wrongly used in migrations * Better handle key=value pair not being key=value * Error out if nic auto or none are not alone * Updated from global requirements * Have python-novaclient support global\_request\_id * Updated from global requirements * Remove various deprecated options * Updated from global requirements * Updated from global requirements * Fix help message and description for volume-update * Fix warning for deprecated cert commands * Mark cloudpipe deprecated in novaclient * Fix a typo * Updated from global requirements * Updated from global requirements * Remove 1.1 extension comment * 2.45: createImage/createBackup image\_id is in response body * 2.44: Deprecate multinic/floatingIP actions and os-virtual-interfaces * 2.43: Deprecate novaclient /os-hosts usage * Add \`instance-uuid\` flag to the migration-list * Remove direct dependency on requests * Microversion 2.42 - Fix tag attribute disappearing * doc: Remove cruft from conf.py * Explicitly set 'builders' option * Add novaclient client\_name and client\_version to user-agent * Updated from global requirements * Fix cinder volume leakage * Updated from global requirements * Deprecate certs commands and APIs * Revise \`nova list\` description * Update reno for removed network CLIs 8.0.0 ----- * Remove deprecated network APIs * Update Compute API Guide pointer * Fix 'nova list --fields' error in no instances case * Explicitly specify arguments of server\_groups creation * Remove version 1.1 support * Remove log translations * Updated from global requirements * Imported Translations from Zanata * Set test timout to 300 seconds * Drop deprecated aggregate-update positional args * Remove deprecated floating\_ips APIs * Remove deprecated fixed\_ips APIs * Remove deprecated security\_groups APIs * Remove deprecated security\_group\_rules APIs * Remove deprecated security\_group\_default\_rules APIs * Remove the deprecated fping API * Remove deprecated floating\_ip\_pools API * Remove deprecated floating\_ips\_bulk API * Remove deprecated floating IP DNS domain/entry APIs * Remove deprecated tenant network APIs * Remove deprecated baremetal CLIs and APIs * Remove py34 tox env and pypi classifier * Fix aggregate\_update name and availability\_zone clash * Use Sphinx 1.5 warning-is-error * Remove duplicate methods * Fix ValueError when incorrect metadata passed * Updated from global requirements * Tags and Metadata fields with unicode cannot be correctly displayed * [Fix gate]Update test requirement * Release note for cell\_name deprecation * Remove functional tests for removed commands * Deperecate cell\_name cli arg for migration-list * Updated from global requirements * Grammar typo in the comments for function set\_meta * Fix devstack python-novaclient warning * Remove deprecated network-related resource commands * Remove deprecated image commands/API bindings * Add functional test for "nova boot --image-with" * Updated from global requirements * Updated from global requirements * Update reno for stable/ocata 7.1.0 ----- * Add release not for fixing token auth method * Fix functional tests to deal with multiple networks * ListExtResource given in place of ListExtManager * Pass relevant parameters to Token based authentication * Updated from global requirements * x-openstack-request-id logged twice in logs * Add profiling support to novaclient * Fix help strings * Make \_console() public * Allow multiple tag add/delete from cli * Updated from global requirements * Enable coverage report in console output * Fix "Message object do not support addition" * Add support for showing aggregate UUIDs (v2.41) * Blacklist rather than whitelist autodoc modules 7.0.0 ----- * Clarify some release notes prior to the 7.0.0 release * Replaces uuid.uuid4 with uuidutils.generate\_uuid() * Microversion 2.40 - Simple tenant usage pagination * Fixed the \_\_ne\_\_ implementation in base.Resource * Add some missing modules in API reference * Deprecate volume\_service\_name argument * [proxy-api] microversion 2.39 deprecates image-metadata proxy API * Remove all code related to HTTPClient * Create keystone session instance if not present * Make SessionClient interface similar to HTTPClient * Deprecate connection\_pool variable * Transmit all auth related vars from cli to inner methods * Deprecate proxy\_token and proxy\_tenant\_id args * Clarify meaning of project\_id var * Rename interface to endpoint\_type * Rename api\_key to password * Rename bypass\_url to endpoint\_override * Remove redundant args of \_construct\_http\_client * Introduce helper for checking args deprecation * Sort arguments for client's methods * Updated from global requirements * Remove internal attribute access from shell * Add limit and offset to server-groups list * Restict usage \*args for novaclient.client.Client * Add version pin for image list function * CONF.osapi\_max\_limit -> CONF.api.max\_limit * Replace six.iteritems() with .items() * Usage missing from generated docs * remove variable '\_' from tests * Add min-disk and min-ram filters to list flavors * Fix doc generation errors * Check source\_type and destination\_type when booting with bdm provided * Updated from global requirements * Fix can't process the resource with name 'help' * Use more specific asserts in tests * Revert "Microversion 2.39 - Simple tenant usage pagination" * Updated from global requirements * Microversion 2.39 - Simple tenant usage pagination * Move all extensions from contrib dir * Use upper-constraints when running tox * Correct copy/paste errors in help * Show team and repo badges on README * Fix import statement order * Remove unused test code * Remove unused code * Fix the description of hypervisors.list * Change fake server id as str to fit real server id type * modified the description of service.list * Add version pin for image related function * Updated from global requirements * Bump client microversion to 2.38 * Replace oslo\_utils.timeutils.isotime * Updated from global requirements * Updated from global requirements * Add Python 3.5 classifier and venv * Updated from global requirements * Remove support for non-keystone auth systems * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Make "policy" a mandatory argument for server-group-create * Update docs for instructions on deprecating commands * Remove unused helper volume methods in v2 shell * Remove deprecated commands * Add timezone disclaimer to docstring * Enable release notes translation * Clean up requests-mock usage * Replace requests mocking with requests-mock * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Update reno for stable/newton * Fix incorrect output of "nova show" for long user data * Modify use of assertTrue(A in B) * Updated from global requirements 6.0.0 ----- * Fix test\_trigger\_crash\_dump\_in\_locked\_state\_nonadmin test * Fix 'UnicodeEncodeError' for unicode values * Fixes TypeError on Client instantiation * Updated from global requirements * Updated from global requirements * Replace functions 'Dict.get' and 'del' with 'Dict.pop' * Removed unused 'install\_venv' module * [functional] Do not discover same resources for every test * functional tests fail if cirros image not exist * Updated from global requirements * Pick first image if can't find the specific image * Add support for v2.37 and auto-allocated networking * Cap image API deprecated methods at 2.35 * Cap baremetal python APIs at 2.35 * Deprecate all the nova-network functions in the python API * Deprecate network-\* commands and clamp to microversion 2.35 * [functional] Skip tests if API doesn't support microversion * Updated from global requirements * Store api\_version object in one place 5.1.0 ----- * Add eggs to gitignore list * Use glanceclient for functional tests * Skip nova-network-only tests if using Neutron * Make wait\_for\_server\_os\_boot wait longer * Handle successful response in console functional tests * Use neutron for network name -> id resolution * Look up image names directly in glance * Move other-requirements.txt to bindep.txt * Make novaclient functional tests use pretty tox * Updated from global requirements * Added smaller flavors for novaclient functional tests to use * Split nic parsing out of \_boot method * Fix boot --nic error message for v2.32 boundary * Updated from global requirements * Microversion 2.35 adds keypairs pagination support * Modify flatten method to display an empty dict * remove start\_version arg for keypairs v2.10 shell * Added support for microversion 2.34 * Add support for microversion 2.33 * Fix python35 job failures * Refactor test\_servers APIVersion setup * Updated from global requirements * Updated from global requirements * Remove discover from test-requirements * Change all test URLs to use example.com * Fix deprecation message for --volume-service-name 5.0.0 ----- * Deprecate baremetal API and CLI interfaces * Add a missing i18n support * Correctly handle NOVACLIENT\_INESCURE * Virtual device tagging client support * Update clouds.yaml.sample * Updated from global requirements * base.Resource not define \_\_ne\_\_() built-in function * Remove white space between print and () * Clean up deprecated CLI options * functional: fix a deprecation warning in test\_auth.TestAuthentication * functional: skip test\_auth\_via\_keystone\_vX if X is not available * Raise an exception in v2.client for direct\_use * Add support for microversion 2.31 * Updated from global requirements * Updated from global requirements * [functional] Fix wrong message in server description test * Deprecated the \`--volume-service-name\` option * Make it possible to list quotas with details * List system dependencies for running common tests * make string.letters python3 compatible * Updated from global requirements * Fix the help message for 'get-mks-console' * Add support for microversion 2.30 * Add support for microversion 2.29 * Updated from global requirements * [functional] make tests work with only keystone v3 * Updated from global requirements * Update README to comply with Identity v3 * Extend microversion stuff to support resource classes 4.1.0 ----- * Added functional tests for server tags (microverison 2.26) * Add support for microversion 2.28 * TrivialFix: Added missed value in string formatting * Log request\_id for each api call * Functional tests for server's description * Updated from global requirements * Updated from global requirements * Update to microversion 2.27 * Fix funtional test gate failure caused by keystone client change * Fix nova host-evacuate for v2.14 * Updated from global requirements * Use tempes.lib instead of tempes\_lib * Deprecate --tenant option from flavor-access-list * Added Support of microverison 2.26 * Name and AZ should as be optional param on aggregate-update * Updated from global requirements * Update the home-page with developer documentation * Updated from global requirements * Updated from global requirements * Clean the duplicated columns for "nova network-list" * Updated from global requirements * [Trivial] change some functions in novaclient/utils.py to public * Add info for user\_id in v2.10 * Enhance descriptions for get and clear password * Add a note message for reboot * Decorate FakeHTTPClient with versions * Restrict positional arguments for Client * Use common find\_server from v2.shell 4.0.0 ----- * Updated from global requirements * Deprecate image list/show/delete/update CLIs/APIs * Make functional tests work with v3 keystone * Updated from global requirements * Fix typos in docstrings and comments * Remove busted baremetal CLIs and API bindings * Updated from global requirements * Add default values for domain related options * Remove deprecated volume(snapshot) commands/bindings * Switch to 2.1 default api\_version in v2.Client * [tests] initialize client objects inside setUp * Fix ServerGroup.NAME\_ATTR 3.4.0 ----- * Using glance 'image-list'/'image-show' in boot help message * Fix host-evacuate-live for 2.25 microversion * Use keystoneclient python bindings for testing * Removed unused Oslo Incubator code * Adding tox support for bandit * aggregate-details changed to aggregate-show * Handle error response for webob>=1.6.0 * Validate shutdown value of --block-device * Add changes-since support when list servers * Update reno for stable/mitaka * Wrap interface\_list by proper resource class * nova add-secgroup help updated with secgroup id * Remove additional 'timeout' element * Make it clear that host-servers-migrate is a cold migration * Return a less dramatic message for public flavors * Remove unused code in tests/unit/v2/fakes.py * Fix a typo in novaclient/v2/hosts.py * Remove an unused method in novaclient/shell.py * Overwrite Usage class's get() function * Remove console expectation from NMI tests * Use novaclient/utils directly and remove openstack/common (4/4) * Use novaclient/utils directly and remove openstack/common * Use novaclient/utils directly and remove openstack/common (2/4) * Use novaclient/utils directly and remove openstack/common (1/4) 3.3.0 ----- * The novaclient Python API doc keystoneauth example fixed * [microversion] Bump to 2.25 * Support for abort an ongoing live migration * Add two server-migration commands and bump migration-list command * Use isinstance instead of type * Support for forcing live migration to complete * Adds missing internationalization for help message * [microversions] Enable 2.21 * [microversions] fix help msg for versioned args * Prepare to move extension into core plugin * Use assertIsNone instead of assertEqual(None, \*\*\*) * Functional tests for trigger-crash-dump (microversion 2.17) * Fix string interpolation at logging call * Deprecate run\_test.sh * Updated from global requirements * Add a way to discover only contrib extensions * [functional] Move code for boot vm to base testcase * [microversions] Enable 2.20 * [microversions] Turn off check for header in response * [microversions] Add support for 2.19 * Allow to specify a network for functional tests * Provide user with coherent interface to get request\_ids * [microversions] Skip microversion 2.18 * Fix running functional tests against deployment with insecure SSL * Updated from global requirements * Make \_poll\_for\_status more user-friendly * Fix omission of request\_ids returned to user * Remove unnecessary filter from Resource's \_\_repr\_\_() function * Use # noqa to ignore one line but not whole file * Add release notes for return-request-id-to-caller * Update translation setup * Add return-request-id-to-caller function(5/5) * Add return-request-id-to-caller function(4/5) * Add return-request-id-to-caller function(3/5) * Support to boot a VM with network name * Add return-request-id-to-caller function(2/5) * Add return-request-id-to-caller function(1/5) * Add wrapper classes for return-request-id-to-caller * [microversions] Add support for 2.17 * Allow restore command with name param * Updated from global requirements * [microversions] Extend shell with 2.12 * Remove argparse from requirements * Added support for Nova microversion 2.16 * Functional tests for os-services * Functional tests for flavors with public and non-public access * Updated from global requirements * Updated from global requirements * Updated from global requirements * Fix URLs for CLI Reference and API * Add functional tests launcher for py3 env * [microversions] Add support for 2.15 * Updated from global requirements * Test: Clean v2 client userwarning * Fix broken link in documentation * Fix W503 line break before binary operator * Add a mechanism to call out deprecated options * Updated from global requirements * Replace assertTrue(isinstance()) by optimal assert 3.2.0 ----- * Use assertTrue/False instead of assertEqual(T/F) * Add python 2.7 comment * [microversions] Add support for 2.14 * Fix extension loading from python path on Python 2.7 * Wrong usage of "a/an" * Fix help strings * Validate the fixed ip address passed with --nic * [microversions] Add support for API microversion 2.13 * [microversions] share one object for shell arguments * Put py34 first in the env order of tox * Fixed test\_shell which can't test microversions>=2.4 * Updated from global requirements * document search\_opts parameter * Cleanup needless code from oslo-incubator 3.1.0 ----- * Validation for arguments of list command passed by "--fields" 3.0.0 ----- * Allow command line for virtual-interface-list * Removes MANIFEST.in as it is not needed explicitely by PBR * Updated from global requirements * Drop py33 support * Migrate to keystoneauth from keystoneclient * Deprecated tox -downloadcache option removed * Updated from global requirements * [functional-test] add v2.8 functional test * [functional-tests] fix version of novaclient instance * remove the default arguments "[]" * Change the logic for the client to retrive resources * Fix help message in case of microversions * Updated from global requirements * [microversions] Increase max version to 2.12 * [microversions] Increase max version to 2.11 * [microversions] Add support for 2.10 * [microversions] update test\_versions with implemented versions * encode the url parameters * Fix H404/405 violations in novaclient/v2/[a-f] * Fix H404/405 violations in novaclient/tests/\* * Fix multiline string with missing space * [microversions] Increase max version to 2.9 * [microversions] Add support for 2.8 * Enable pass instance name as parameter in nova cli * Fix H404/405 violations in client.py,base.py,api\_version.py * Help msg about libvirt always default device names * Fix a Typo in Docstring * Updated from global requirements * Remove python 2.6 support from python-novaclient * [microversions] Increase max version to 2.7 * Optimize "open" method with context manager * force releasenotes warnings to be treated as errors * Updated from global requirements * Fix repr of a host from a hosts.list() * Updated from global requirements * Add accessIPv4 and accessIPv6 when create server * Add reno for release notes management * Check response headers for microversions * Not transform to str on potential unicode fields * Updated from global requirements * Print current nova default nova API microversion * improve readme contents * Updated from global requirements * Last sync from oslo-incubator * Fix typo in error message 2.35.0 ------ * Added command for device to cinder volume mapping * Functional tests for os-extended-server-attributes * Remove --tenant suggestion for flavor-access-list * Add v2 support for optional project\_id in version discovery urls * Remove novaclient.v1\_1 module 2.34.0 ------ * Revert "Remove novaclient.v1\_1 module" * Revert "Do not expose exceptions from requests library" * Check flavor option before image checks * Updated from global requirements 2.33.0 ------ * Do not expose exceptions from requests library * make project\_id optional in urls for version discovery * Support the server side remote-console API changes * Correct usage of API\_MAX\_VERSION and DEFAULT\_OS\_COMPUTE\_API\_VERSION * Rely on devstack for clouds.yaml * Refactor parsing metadata to a common function * Updated from global requirements * Updated from global requirements * Use v2.0 in clouds.yaml auth\_url for functional test job * Add sort\_dir/key to flavor list * Add pagination params for flavor list * Increase timeout when testing admin timeout * Fix incorrect help for 'nova flavor-create' 2.32.0 ------ * Revert "Allow display project-id for server groups" * Test that microversions are not skipped * Updated from global requirements * Set DEFAULT\_OS\_COMPUTE\_API\_VERSION to 2.5 2.31.0 ------ * Always send volume\_id when booting with legacy bdm * Change default service\_type for volumes managers * Add --metadata as optional input when do nova image-create * Remove novaclient.v1\_1 module * Use dictionary literal for dictionary creation 2.30.0 ------ * Change ignore-errors to ignore\_errors * Modify nova help list message for --tenant * Split functional tests for legacy(V2.1) and microversions * Specify api\_version for functional tests * Improve help strings * Updated from global requirements * Encode unicode filter arguments for server list * [docs] Fix length of underline * Updated from global requirements * Fix nova bash-completion needs authentication * Show reserved status for a fixed\_ip if available * Fix unicode issue in shell.main() error handling * Remove redundant help command test and commonize * Fix the homepage url in setup.cfg * Fix mistakes in comments * Modify "nova keypair-show" Positional arguments help information 2.29.0 ------ * make sure os\_password is set for auth\_plugins * [BugFix] Change parameters for legacy bdm * Set api\_version to 2.0 by default for v2.Client * Deprecate 'novaclient.client.get\_client\_class' * Restrict direct usage of novaclient.v2.client * Fix bugs with rackspace * Add unit tests for different help commands * Enable i18n with Babel * Fix nova --help needs authentication 2.28.1 ------ * [Bug-Fix] Update requests body for quota-update * Adds missing internationalization for help message 2.28.0 ------ * workaround for RAX repose configuration * fix novaclient functional tests for new devstack config * Don't assume oscomputeversions is correctly deployed * Update path to subunit2html in post\_test\_hook * Option to specify max servers for live evacuate 2.27.0 ------ * Allow to reboot multiple servers * Add method for better random name * Launch functional tests serially * Add mechanism to vm list to return all resources * Fixed typo Errors in comments * Updated from global requirements * Add "limit" option to servers list cli * Add 'marker' argument to server list cli * Fix a fault of request logging with no credentials * Add response message when the state of a server is reset * Fix versions.list for v2.1 Nova API * Add help message for floating ip bulk operation * Correct the files's description "overrwriter" * Updated from global requirements * Updated from global requirements * Add ability to use default major version * Copy cli arguments in api\_versions.wraps decorator * Change docstring of api\_versions.discover\_version * Allow display project-id for server groups * Remove \_discover\_extensions * Updated from global requirements * Support forcing service down * Adds support for x509 certificates as keypairs * Set "latest" as default compute api version * Add version discover and check in CLI * Specify NIC option for nova boot * Refactor and update test\_instances and test\_volumes\_api * Use keystoneclient's TCPKeepAliveAdapter * Add 'deleted' status check in \_poll\_for\_status * Add "get\_current" method to VersionManager * Implements 'microversions' api type - Part 2 * Fixes table when there are multiline in result data * rename root-password to set-password * Updated from global requirements * hypervisor command can't use cell format id to show hypervisor * Set iso8601 log level to WARNING * Updated from global requirements * Implements 'microversions' api type - Part 1 * Updated from global requirements * Updated from global requirements * Improve hypervisor-show print list * Updated from global requirements * Fix resolving image.id in servers.boot * Added marker functionality to flavours and images * Updated from global requirements * Updated from global requirements * Change future nova version number references based on new values * cleanup openstack-common.conf and sync updated files * Updated from global requirements * Revert "Allow admin user to get all tenant's floating IPs" * Add a sample clouds.yaml * Pass full path to pkgutil.iter\_modules() * Cache a new token when the existing token expires * Add help message for secgroup-add/del-default-rule * Cleanup various inaccuracies in the README.rst * Update weblinks * Adds support to set admin password from the cli * Adding missing nova read only CLI test * Updated from global requirements * Fix all doc warnings and gate on warnings * Add docs tox env 2.26.0 ------ * Remove unused novaclient.tests.unit.v2.utils module * Add documentation on command deprecation process * Deprecate volume/volume-type/volume-snapshot CRUD CLIs/APIs * Do not check requirements when loading entry points * Eliminate test comprehensions * Remove redundant check for version of \`requests\` * Use clouds.yaml for functional test credentials * pass credentials via config file instead of magic * server-group-list support 'all\_projects' parameter 2.25.0 ------ * Drop use of 'oslo' namespace package * Reuse uuidutils frim oslo\_utils * Sync latest code from oslo-incubator * Updated from global requirements * Make \_discover\_extensions public * Updated from global requirements * nova client now support limits subcommand * Don't use SessionClient for version-list API * Add min/max microversions to version-list cmd * Deprecate v1.1 and remove v3 2.24.1 ------ * fix FloatingIP repr * Don't lookup service url when bypass\_url is given * Revert "nova flavor-show command is inconsistent" * add ips to novaclient server manager * Update README to work with release tools * refactor functional test base class to no inherit from tempest\_lib 2.24.0 ------ * Remove all imports from oslo namespace * Fix typo on class Client sample * Uncap library requirements for liberty * Report better error message --ephemeral poor usage * Fix displaying of an unavailable flavor of a showing instance * Handle binary userdata files such as gzip * Add --all-tenants option to 'nova delete' * Combine test cases for checking nova limits response * Fix repr of FloatingIPBulk * Fix comments on metadata number limitation * Corrected help for nova boot when attaching block device * Removes reference to v3 nova api from novaclient docs * Don't record time when self.timing is False * Updated from global requirements * Revert 'Remove image to local block device mapping' 2.23.0 ------ * Ensure the use of volume endpoint in volumes apis * Add missing servers.create parameter documentation * Add Client object to documentation * Add a test for the TCPKeepAliveAdapter * Update help message for nova boot --file * Fix typo in socket attribute name * nova client cinder query param changed to display\_name * nova flavor-show command is inconsistent * Update help messages for default security group commands * Add functional testing README * Fix client usage in api example * Updated from global requirements * Cleanup in asserts in python-novaclient * Update version of novaclient in the docs * Cleanup in test\_images and image\_fakes 2.22.0 ------ * Fix description of parameters in nova-client functions * Enable check for E124 rule * Removed unused 'e' from 'try except' statements * allow --endpoint-type internal|admin|public * Fixed redeclared test\_names * Updated from global requirements * add pretty\_tox to nova functional tests * add functional test for nova volume-attach bug * Revert "Overhaul bash-completion to support non-UUID based IDs" 2.21.0 ------ * Change commands name from net-\* to tenant-network-\* * Updated from global requirements * Copy functional tests from tempest cli read only * Add all tenants search opt to del instnce by name * Updated from global requirements * Check 'auth\_url' is presented while authentication * Wrong help for nova usage command * Add support for keypair-add command reading public key from stdin * Compare dicts for POST data in test\_client\_reauth * Remove image to local block device mapping * Change the unsuitable item caching for completion * Moved set of asserts from post\_servers\_1234\_action methods * Change logic in find\_resource() to identify upper-case/lower-case name * Add first pass at post\_test\_hook for functional tests * Rename v1\_1 to v2 * Fix issue of quota-show and quota-defaults * Refer to the admin password consistently * Update volumes.get() docstring to correctly reflect functionality * First pass at tempest\_lib based functional testing * Add OS\_TEST\_PATH to testr * Move unit tests into unit test directory * whitelist find in testenv * Updated from global requirements * Use TCP Keep-Alive on the socket level * Updated from global requirements * In strings/comments change Ip/ip to IP * Improving the help of the lock command * Updated from global requirements * Move to hacking 0.10 * Updated from global requirements * Adds separate class for Hypervisor Stats * Directly using base64 encoding for injected files * Document unexpected need for --all-tenants when using --tenant * Add 'Id' column to floating-ip-list * Pass kwargs through to the adapter * Display tenant id with nova list --all-tenants * novaclient sort parameters support * Reject non existent mock assert calls * Updated from global requirements * pass id to ServerGroupsManager in ServerGroup.delete() * Workflow documentation is now in infra-manual * Updated from global requirements * Remove aliases for \`args\` and \`env\` in utils * Use \`arg\` and \`env\` from cliutils: v1\_1/shell * Remove code related to V3 * Enable check for E128 rule * Fix E128 failures in novaclient/tests * Fix E128 failures in novaclient/v3 * Fix E128 failures in novaclient/v1\_1/ * Fix E128 failures in novaclient/v1\_1/shell * Updated from global requirements * Curl statements to include globoff for IPv6 URLs * Support using the Keystone V3 API from the Nova CLI * Add limits to V3 and improve limits formatting in shell * Fix the help comment about metadata * Updated from global requirements * support OS\_ENDPOINT\_TYPE in nova client * Updated from global requirements * Enable check for E129 * Enable check for E127 * Enable check for E126 * Propose 'tox' as tests launcher * Update novaclient shell to use shared arguments from Session * Allow to start/stop multiple servers * Define helper to run an action on many resources * Add missing parameters for server rescue * Remove E12\* from list of deliberately ignored flake8 rules * no way to delete valid/invalid security rule * Sync latest code * Port to use oslo.i18n * Avoid "ambiguous option" when only current/deprecated forms match * Updated from global requirements * Clarify "nova scrub" help text * Updated from global requirements * Show 'state' and 'status' in hypervisor-list * return 130 for keyboard interrupt * Fix parameter description in create\_server * Enable check for E123 * Enable check for E122 * Enable check for E121 * Use oslo.serialization * Use common code instead of novaclient.utils 2.20.0 ------ * Updated from global requirements * Corrects typos "coearse," "proejct," and "unecrypts" * Add list by user to shell * Add retry\_after only to exceptions supporting it 2.19.0 ------ * secgroup-create description is not optional * Live migrate each instance from one host to other hosts * novaclient: Convert v3 boot command with v2.1 spec (bdm) * Stop using intersphinx * novaclient: Convert v3 boot command with v2.1 spec (user-data) * novaclient: Convert v3 boot command with v2.1 spec (security-groups) * delete python bytecode before every test run * Add support for the server group quotas * quota delete tenant\_id parameter should be required * Don't display duplicated security groups * Updated from global requirements * add new command get-serial-console * Make findall support server side filtering * Fix test mistake with requests-mock * Use oslo.utils * Use Token fixtures from keystoneclient * Update requirements.txt to include keystoneclient * Updated from global requirements * Updated from global requirements * Enhance network-list to allow --fields * Adding Nova Client support for auto find host APIv2 * Adding Nova Client support for auto find host APIv3 * Add filtering by service to hosts list command * Quickstart (README) doc should refer to nova * Updated from global requirements * Fix listing of flavor-list (V1\_1) to display swap value * Use adapter from keystoneclient * Fix the return code of the command "delete" * Fix variable error for nova --service-type * Convert to requests-mock * Enable several checks and do not check docs/source/conf.py * Updated from global requirements * Fix order of arguments in assertEquals * Enable check for E131 * Add support for security-group-default-rules * Fix rxtx\_factor name for creating a flavor * Allow selecting the network for doing the ssh with * fix host resource repr to use 'host' attribute * Enable H233 * Don't log sensitive auth data * Enabled hacking checks H305 and H307 * Edits on help strings * Add support for new fields in network create 2.18.1 ------ * Add "version-list" for listing REST API versions * Description is mandatory parameter when creating Security Group * Revert "Reuse exceptions from Oslo" * Updated from global requirements * Revert "Set default http-based exception as \`HttpError\`" 2.18.0 ------ * Adding multiple server support to nova reset-state * Update nova boot help * Convert Server Group Tests to httpretty * Convert security group tests to httpretty * Convert security group rules tests to httpretty * Convert Quota tests to httpretty * Fix the wrong dest of 'vlan' option and add new 'vlan\_start' option * Fixing flavor access \_\_repr\_\_ method * Allow us to use keystoneclient's session * Fix the section name in CONTRIBUTING.rst * Adds clarification note for project\_id vs tenant\_id * Filter endpoints by region whenever possible * Convert network tests to httpretty * Convert limit tests to httpretty * Convert keypair tests to httpretty * Convert image tests to httpretty * Convert Hypervisor tests to httpretty * Convert hosts tests to httpretty * Add missing parameters for server rebuild * Fixes typo in error message of do\_network\_create * Mention keystoneclient.Session use in docs * Fix using a variable which is not defined * Fix booting from volume when using api v3 * Sync apiclient from oslo-incubator * Convert server tests to httpretty * Convert floating IP pool tests to httpretty * add disk bus and device type to volume attach * Fix listing of Server in floating-ip-list * Sync Oslo's apiclient * Overhaul bash-completion to support non-UUID based IDs * Add some security group tests for the V1\_1 API * Updated from global requirements * Allow updating fixed\_ips quotas in quota-class-update (v2 shell only) * Look for all accessible flavors by default, not just public ones * Enable F841 * Updated from global requirements * Remove quota-class-\* commands from v3 shell * Updated from global requirements * "nova boot" should not log an error if subsidiary commands fail * Bump hacking to 0.9.x series * Add posargs support to flake8 call * Fixes wrong value description for volume-detach * adjust to {SHA1} convention for token * 'policy' should be required in server\_group\_create * Adding cornercases for set\_metadata * add tox target for python 3.4 * mask keystone token in debug output * Remove \_print\_volume from volume-update * Add way to specify key-name from environ * Convert Floating IP bulk tests to httpretty * Convert Floating IPs DNS tests to httpretty * Convert Floating IP tests to httpretty * Overwrite HelpFormatter constructur to extend argument column * Add swap measurement unit (MB) to CLI output * Change help message for volume-update * Add NOVACLIENT\_BYPASS\_URL env variable * Logical error in flavors unset\_keys method * Removed now unnecesary workaround for PyPy * Change help for --poll option in Nova commands * Revert "Remove quota-class subcommand" * Synced jsonutils from oslo-incubator * Convert fPing tests to httpretty * Convert Fixed IP tests to httpretty * Convert Cloud Pipe tests to httpretty * In Py3 decode the output of base64.decode * Convert certificate tests to httpretty * Set default http-based exception as \`HttpError\` * Convert Availability Zone testing to httpretty * Convert agent testing to httpretty * debug level logs should not be translated * Add missing dependent module gettextutils * Allow the default value of 1 to be set for boot multiple * Add extension-list command for v3 API * Add mailmap entry * Some Help Messages Missing Translation Support * Print message if instance is successfully deleted v3 * Enable delete multiple server groups in one request * Make port-id and net-id keys mutually exclusive * Remove duplicate test test\_boot\_multiple * Deprecate num-instances in favor of min/max count * Make help description of rescue/unrescue more useful * Print message if instance is successfully deleted * Updated from global requirements * Fix documentation for config\_drive boot parameter * Synced jsonutils from oslo-incubator * Fix for "nova help list" command * Fixed a typo in a comment * Reuse exceptions from Oslo * Updated from global requirements * Fix the incorrect return messages in keypair show and delete * Fix wrong fake return values for Nova V3 client tests * Add unit test for keypair's api * Fix the unlimited length of console-log * Fix mac address and task\_state in baremetal-node-list * Updated from global requirements * Convert aggregates testing to use httpretty * Fix name arg help for volume-type-create * Add service-delete subcommand to delete a service * Updated from global requirements * Fix session handling in novaclient * Print adminPass when rebuilding from shell * Remove py3kcompat * Split test\_rebuild() into two tests * Allow admin user to get all tenant's floating IPs * Fix for invalid literal ValueError parsing ipv6 url(s) * Remove unused arguments to \_boot() * typo in novaclient * Sync with Oslo-Incubator * Start using oslosphinx theme for docs * Fix error when run with no arguments on Python 3 * Avoid AttributeError in servers.Server.\_\_repr\_\_ * Raise exception when poll returns error state * Correct the help sting of volume-type-delete * Print a useful message for unknown server errors * Show Exception Name in Shell Output * Work around pypy testing issue * Support IPv6 when booting instances * Updated from global requirements * Do auth\_url.rstrip('/') only if auth\_url is set * Explain how to delete a metadata in aggregate-set-metadata * Nova CLI for server groups * Removes copy of output of 'nova help' from README * Fix authentication bug when booting an server in V3 2.17.0 ------ * Allow user ID for authentication * Add os-server-external-events support * Fix some spelling mistakes * Add classifiers for specific versions of Python * Remove quota-class subcommand * Re-add install\_venv\_common to openstack-common.conf * oslo sync apiclient and cliutils * Fix in in novaclient, to avoid excessive conns * Revert "'name' should as be optional param on aggregate-update" * Add service-list show \`id\` column * oslo-sync of low hanging fruit * Updated from global requirements * 'name' should as be optional param on aggregate-update 2.16.0 ------ * Fix typo in novaclient * Remove usage of module py3kcompat * Updated from global requirements * Invalid client version message unclear * Remove None for dict.get() * Replace assertEqual(None, \*) with assertIsNone in tests * Fix element miss in client request body * Fix i18n messages in novaclient, part II * Update broken command line reference link * Fix spelling miss of password\_func variable * Fix copy/paste errors in print messages * Remove invalid parameter of quota-update * Remove tox locale overrides * Fix python 3.3 unit test job * Adds support for the get\_rdp\_console API * Support building wheels (PEP-427) * Fixed polling after boot in shell * Update my mailmap * Fix Serivce class AttributeError * [UT] Fixed floating\_ip\_pools fake return to expected one * [UT] Removed duplicate key from dict in fake baremetal\_node * Fixed multi validation and wrong fail calls in unit tests * Fixed super constructor call for TestResponse class * Flavor ExtraSpecs containing '/' cannot be deleted * Removed undefined method in install\_env.py file * Fix i18n messages in novaclient, part I * Adds ability to boot a server via the Nova V3 API * Removes unsupported volume commands from V3 API support * Reuse Resource from oslo * Updates nova client to use the latest oslo files * Using common methods from oslo cliutils * Add tests for boot method of v3 shell * Replace basestring by six.string\_types * Removes use of timeutils.set\_time\_override * Fix logic for "nova flavor-show 0#" * Sync with global requirements * Don't call CS if a token + URL are provided * Sync cliutils from oslo * Sync apiclient from oslo * Fix QuotaClassSet and their tests * assertTrue(isinstance) replace by assertIsInstance * Remove the coverage extension code * shell: refactor boot to use \_print\_server * Don't slugify() None names * Adds volume support for the V3 API * Fixes ambiguous cli output between "None" and NoneType * Support list deleted servers for admin * Using floating-ip-{associate|disassociate} * Removes vim configuration headers * Adds quota usage support for the V3 API * Fix tab-completion of --flags under OS X * Remove class\_name parameter from quota\_class * Ensure that the diagnostics are user friendly * Code cleanup: use oslo's to\_slug() instead of slugify() * Added v3 interfaces in reference doc * Enable pep8 check for config.py in doc * Generate interfaces reference doc * Ensure that nova client prints dictionaries and arrays correctly * Replace some utils.bool\_from\_str with strutils * Allow empty response in service-list * Nova aggregate-details should be more human friendly * Removed duplicated import * Adding additional tests for novaclient ssh * Fix "device" as the optional para on volume-attach * Adds simple tenant usage support for the Nova V3 API * Adds keypairs support for the Nova V3 API * Adds certificates support for Nova V3 API * Adds aggregates support for Nova V3 API * Adds hypervisor support for Nova V3 API * Adds services support for Nova V3 API * Adds second part of quotas support for Nova V3 API * Adds first part of quotas support for Nova V3 API * Adds availability zone support for Nova V3 API * Adds basic servers support for the Nova V3 API * add support for nova ssh user@host * remove duplicate six import * Allow multiple volume delete from cli like Cinder * Fixed autodoc can't import/find class error * Expose the rebuild preserve-ephemeral extension * Stop using deprecated keyring backends * Adds images support for Nova V3 API * Remove commands not supported by Nova V3 API * Adds agent support for Nova V3 API * Adds flavor access support for Nova V3 API * Adds flavor support for Nova V3 API * Enables H403 pep8 rules * Allow graceful shutdown on Ctrl+C * Enables H306 pep8 rules * Enables E711,E721,E712 pep8 rules * Updates tox.ini to use new features * Updated from global requirements * Remove the release.rst file * Fix docstring on novaclient * add support for server set metadata item * Fix incorrect help message on flavor\_access action * Fix inappropriate comment for delete FloatingIP * Enable hacking check for Apache 2.0 license * Sets default service type for Nova V3 API * Fix the inappropriate comment for flavor * Adds a --show option to the image-create subcommand * Updates .gitignore * Allows users to retrieve ciphered VM passwords * Fix inappropriate comment for flavor create api * Fix typo in novaclient * Removes unnecessary pass * Updated from global requirements * Discrepancy between README.rst and nova help * nova security-group-\* should support uuid as input * Change "project" to "project\_id" in cloudpipe-create * Fix single H234 Bug to make Hacking 0.8 pass * Flatten hypervisor-show dictionary for printing * Revert "Nova aggregate-details should be more human friendly" * Update mailmap for Joe Gordon * Print security groups as a human readable list * Adds locking to completion caches * Nova aggregate-details should be more human friendly * Make 'nova ssh' automatically fall back to private address * Quote URL in curl output to handle query params * Add --insecure to curl output if required * Apply six for metaclass * Updated from global requirements * Remove deprecated NOVA\_RAX\_AUTH * Print dicts in alphabetical order * Make os-cache retry on an invalid token * Document and make OS\_CACHE work * Revert "Add-in some re-auth logic when using os\_cache" * Align mocking pattern for test case * py33: use six.StringIO() to mock stdout/stderr * py33: sort the files parameters of "--files" * py33: sort hosts while treeize AvailabilityZone * py33: unify the input of request to json format * py33: align the order of parameters for urlencode() * py33: sort dict for test\_add\_floating\_ip\_to\_fixed * py33: iteration order of dict is unpredictable * Updated from global requirements * py33: 'str' does not support the buffer interface * assertEquals is deprecated, use assertEqual * py33: align the order of parameters for urlencode() * Add shelve/unshelve/shelve-offload command * py33: uuid verification in find\_resource() * py33: don't encode security\_group * Add-in some re-auth logic when using os\_cache * if we have a valid auth token, use it instead of generating a new one * py33: safe\_encode() returns bytes in Python 3 * py33: unknown encoding: base64 Edit * Fix AttributeError in Keypair.\_add\_details() * Fixed several test failures on Python3 * Make nova CLI use term "server" where possible * py33: dict.keys() is not a list in python3 * Corrected several usage of keys() for Python 3 * py33: 'dict\_keys' object does not support indexing * Corrected usage of len(filter(...)) * Update pbr usage * Clean up a little cruft * Novaclient shell list command should support a minimal server list 2.15.0 ------ * Add v3 HostManager * Create v3 tests directory * Fix the print order of quota-show * assertEquals is deprecated, use assertEqual * Small bugfix for client v3 * Modify --num-instances flag description to clarify limit upper bound * Add a block device for the image when using BDMv2 * python3: Compatibility for iteritems differences * python3: Fix traceback while running unit tests * python3: Fix Traceback while running unit tests * Unittests added for client v1\_1 * Python3: Fix traceback while running unit tests * Python3: Use six.StringIO for io.Bytes() * Update oslo from oslo-incubator * Add delete method to Flavor class * New syntax to boot from a block device mapping * Allow name argument to flavor-access-add * Add support for os-assisted-volume-snapshots * Suport instance list pagination in novaclient, Part I * Add interface for listing security groups of an instance * Added support for running the tests under PyPy with tox * python3: Fix imports for py2/py3 * Upgrade to Hacking 0.7 * Sync py3kcompat from oslo * Update mailmap * Update mailmap * Added 'nova migration-list' command * Fix and gate on H501, no locals for string formatting * Update oslo * Allow name argument to flavor-access-add * python3: Fix traceback while running tests * Fix the help messages to specify image/flavor name * Clean up inaccurate docstrings of server list() method * Remove old references * Enable v3 api code * Begin adding v3 api support * change 'Host' object's 'host' attribute to 'host\_name' * Updated from global requirements * Do not restrict flavor to only ID and integers * Fix typo and grammar in docstring only 2.14.1 ------ * remove requests version max 2.14.0 ------ * Sync with global requirements * Add support for swap\_volume * FakeClient: fix the arguments of a string format * Support programmatic use of disk config extension * Check whether the security group id is integer * Fixing host-action on V2 * Add user quota client API support * Fix net-id metavar for interface-attach * make findall in novaclient/base.py more efficient * Fix the help text process and the generated wrong help * Remove python 2.4 and python 2.5 support * Enable force\_delete and restore instance via novaclient * Add name argument to aggregate commands * Add name argument to hypervisor commands * recognize 429 as an rate limiting status * Fix backwards-incompatible API change (method signature) * Fix and enable gating on H402 * Add AgregatesManager.get() * Skip setting volume\_size if not given * Fix interface-list got none mac address * Remove uncessary code related to nova start/stop * make v2\_auth and plugin\_auth explictly return their results * Sync install\_venv\_common from oslo * Clean up and make HACKING.rst point to openstack-dev/hacking * CLI for disable service reason * Allow tenant ID for authentication * Adds zsh completion * Bring stdout/stderr capturing in line w/ nova * Fixup trivial License Header mismatch * Remove Diablo compatibility options * python3: Fix print statements * python3: Compatibility for iteritems differences * python3: Fix unicode compatibility python2/python3 * Return Customer's Quota Usage through Admin API * Discard possibly expired token before re-authenticating * Support force update quota * Update help for --nic opt and make net-id or port-id required * Adds support for ExtendedFloatingIps APi extension * Remove explicit distribute depend * Cells Support * Set default value of flavorid to "auto" * Migrate each instances of a host to another * Set/Delete metadata on all instances of a host * The 'nova keypair-show key\_name' command added * Use Python 3.x compatible except: construct * Delete a quota through admin api * Exit w/ valid code when no servers are deleted * Evacuate each instance from one host to another * python3: Introduce py33 to tox.ini * Start using Hacking and PyFlakes * Add update method of security group name and description * Fix shell tests for older prettytable versions * Provide nova CLI man page * Improve error messages for invalid --nic / --file * 100% test coverage for security groups and rules * Add MethodNotAllowed and Conflict exception classes * Move tests into the novaclient package * Add CONTRIBUTING file * Rename requires files to standard names * Code cleanup in advance of flake8 * Migrate to flake8 * Revert "Support force update quota" * Only add logging handlers if there currently aren't any * Convert to more modern openstack-common.conf format * Cleanup unused local variables * Reuse oslo for is\_uuid\_like() implementation * Synchronize code from oslo * Migrate to pbr * Cleanup nova subcommands for security groups and rules * Make ManagerWithFind abstract and fix its descendants * Cleanup some flavor commands * Fix the default parameter in print\_list * Fix for --bridge-interface being ignore by nova network-create * Add setuptools\_git-\*.egg to .gitignore * Expose retry\_after attribute of OverLimit exception * Adds extended status fields to nova list * Clean up exceptions.from\_response * Allow deleting multiple images from shell * Synchronize code from oslo * Add 'flavor-list --all' admin switch * Fix nova instance-action-list output field and order * Make list flavor show extra specs optional * Use HTTP keep-alive feature in HTTPClient class * Cleanup unused import * Make --vlan option work in network-create in VLAN mode * Support force update quota * make sure .get() also updates \_info * Add coverage-reset command to reset Nova coverage data * Fixing shell command 'service-disable' description * Correct a unit test failure that crept into trunk * Fix problem with nova --version * Make "multi\_host" True when it is set to 'T' in network\_create * Fix IBM copyright strings * Allow for bypass\_url when using proxy\_token 2.13.0 ------ * Fix mispelt x-auth-token header * Remove actions command from servers * do not ignore --os-cache * Improve authentication plugins management * Skip security groups w/ no protocol * catch NoKeyringDaemonError from gnomekeyring * Ensure shell tests use isolated env variables set * Update to latest openstack.common.setup * setuptools: remove data\_files section * Use correct filter name for listing of instances 2.12.0 ------ * Don't check build/ for pep8 violations * Add support for retrieving instance-actions info * Split commands properly for bash completion test * Remove extraneous output during testing * Use setuptools-git to include files from the repo * Update tools/pip-requires for prettytable * Fix keypair-delete help documents * Add support for the new fixed\_ip quota * Set up debug level on root logger * Remove unused import * Fix Copyright Headers from LLC to Foundation * Removes tenant IDs checking for nova quota operations * Make os-services API extensions consistent with Nova * Revert API changes in "Unify Manager.\_update behaviour" * Use keyring for testing * Show Tenant\_ID for secgroup-list with all-tenant * Additional "Unify Manager.\_update behaviour" cleanup * Add wrap option to nova credentials for humans * Check if tenant flag is uuid\_like for all quota operations * Fix nova boot --num-instances option checking * Fix typo in error message * Extend test coverage for v1\_1/shell.py * Decodes input and encodes output * Fixed bug with password prompt, added tests * Make ip\_protocol parameter in security groups rules case insensitive * Fixes the output of 'os-evacuate' command * Update the docstring of cloudpipe-configure command * Accept 201 status code on POST * Fix how tests were failing due to missing attributes * Missing import for gnomekeyring * A minimum of Python3 fixes so that installation works without errors/warnings * Allows admins to reset-network of an instance * Remove prov\_vlan\_id from baremetal * Add support for os-attach-interfaces * Added limit to image-list in a preparatory step toward addressing bug 1001345 * Extend test coverage (shell, fping) 2.11.1 ------ * Issue when gnomekeyring is present but not the current backend * Avoid doing a deep copy on the availability zone manager * Allow extensions to provide a name when discovered on the python path * Fix IOError with gnomekeyring.find\_network\_password\_sync * Expand and improve baremetal API * Fix nova availability-zone-list for admin users * Make availability\_zone in aggregate\_create optional * Corrects 2nd argument type for logging 2.11.0 ------ * Add format options to 'nova coverage-report' * Update to requests >= 0.8 * Mask permissions on private key files * Fix run\_tests.sh --coverage * Support showing extra fields in server list * management\_url not set by authenticate method * Update .coveragerc * Show the summary or details of all availability zones of a region * Upgrade to pep8 1.3.3 * Fixed 7 pep8 errors * Live migration with an auto selection of dest * Add help about the id 'auto' for flavor-create * Fix default format of 'nova coverage-report' * Add usage command to show usage for single tenant * Store tenant\_id from keystone and use for quotas * Show the details of the added bare-metal resource * Fix the usage of password, keyrings, and tokens * Added homedir path expansion for keypair-add * Migrate from nose to testr * \_get\_secgroup returns first group even if multiple groups match * Fix bash completion on osx * Check tenant\_id's format in "nova quota-update" * ClientExceptions should include url and method * Adds baremetal nova API support * RateLimit does not have method attribute * make print\_dict split strings with newlines into multiple rows * Allow for image selection using the metadata properties * Add support for get\_spice\_console RPC API * Ensure list output function can support non-sorting printing * Allow request timeout to be specified * Implement get password for novaclient * Adds tenant network support to the client * Update functionality of coverage extension * Fix a couple of broken shell tests * Update hosts API action calls (startup etc.) * When logging request include request data * Add support for instance evacuate * Fix the help text of add-fixed-ip * Move from untitest2 to testtools * Update README.rst * Unify Manager.\_update behaviour * Fix some usage messages of 'nova volume-\*' * add num\_instances option for nova boot * Use requests module for HTTP/HTTPS * Fix find for alphanumeic flavor id/name * Make --tenant a required arg for quota-show * Add support for the coverage extension * Specify some arguments by name * Makes the OS\_NO\_CACHE env variable work again * Add optional argument to include reservations in os-used-limits * Add nova client support for nova-manage agent command * Adds --os-cache to replace old --no-cache * Adds support for security group/rules quotas * Adds nova client support for nova-manage network command * add host-update help info param * Fix argument checking method for 'nova list --flavor' command * Fix a wrong substition for '-h' in bash completion * Fixed nics param ignored when bdm is specified * Adds support for key\_pairs quota * Adds support for injected\_file\_path\_bytes quota * Adds nova client support for nova-manage floating command 2.10.0 ------ * Remove unnecessary casts in flavor create * Validate that rxtx\_factor is a float * Adds nova client support for nova-manage vpn command * Fix aggregate command help messages * Add nova client support for nova-manage account scrub command * Adds nova client support for nova-manage fixed command * Implement fping calls in nova client * Expand help message for 'migrate' to explain how the new host is selected * Improved quota output * Boot from volume without image supplied * Added --extra-opts to the nova ssh command * Cleans up the flavor creation code. Fixes bug 1080891 * Adding support to filter instances by tenant from the admin api * Make sure create\_image returns result * make tenant id optional for quota-defaults and quota-show * fix hypervisor-servers for hypervisors without servers * discover extensions via entry points * show help when calling without arguments * Add nova client support for nova-manage service command * Updated the help text for nova list command * Fixes setup compatibility issue on Windows * include projectid in the cache key * Fixes utils.findresource checking for integer * Allows deletion of multiple servers through CLI * Add ability of nova client to display availability zones when listing hosts * Validate that boolean parameters are boolean * Auto-Assign Flavor ID * Pull in latest openstack-common changes and fix a minor PEP8 issue * Add OpenStack trove classifier for PyPI * Exception handling for 'nova flavor-create' arguments * Add support for backup instance * Add simple os-api extension cli extension * Raises Exception on improper Auth Configuration * Do not prefer ALL\_TENANTS environment variable to command line arguments * Encode user data to utf-8 when creating a server * Add --all-tenants option to volume-list 2.9.0 ----- * Show volume and snapshot data on create * Fixes setup compatibility issue on Windows * allow empty network list to be requested * Work around httplib2 tunnelling bug * Add support for all-tenants search opt to secgroup-list * expose os-networks extension to CLI * Add support for Unicode secgroup names * Support flavor extra specs in nova client * Optionally faster 'nova show' * Makes handling of nic args more robust * Show instances built from deleted snapshots * Add ConnectionRefused exception 2.8.0 ----- * Fix usage-list date range to use UTC time * Show POST in debug with curl * Fixes doc string and string formatting * Add the image\_id arg to volume create * Make region case insensitive * Fix PEP8 issues * Add -X to DELETE and PUT in debug mode * Implement project specific flavors API, client bindings * Add missing port-id usage info * Change '\_' to '-' in options * Adding --version option * Added -nic port-id= support * Implement network calls in nova client * Add nosehtmloutput as a test dependency * split req and response logging this allows capture of timestamps prior to and after request for timing also did some pep8 1.3 cleanup while I was in there * Add availability\_zone support for volume creation * Adds support for autogenerated device on attach * Allow resources to use any field as 'name' * gitignore ChangeLog and add to MANIFEST.in * Allow different auth providers via plugin system * Better handling of stale tokens (no more 401's) * change image list and network list data to be sorted by name rather than UUID * Relex prettytable depend to match glanceclient 2.7.0 ----- * Add call to get hypervisor statistics * Fix image-create --poll race-condition * set admin password during instance creation * Clarify usage of --insecure flag * Fix resize polling * Add support for modification of instance Security Group * Add support for hypervisor-uptime * Install test-requires in development venv * 'endpoints' and 'credentials' work with token caching * This should fix a problem with overly aggressive token caching * Flavor-list sort by ID numerically * Bring back the output from client.http\_log() 2.6.10 ------ * Add hypervisor information extension * More friendly keyring support when token caching is off * Whoops, the last changes to keyring introduced some problems with v1.1 auth tests * Auth token caching on by default. --no\_cache to disable. Better bypass support too * Add host-list command * Indicate unused variables and other misc. house cleaning * don't bash-complete the '-h' option * Add read\_versioninfo method * Turn multiple hints with the same key into a list * Cleanup of setup.py usage of openstack-common * Implement post-tag versioning numbering * Small doc cleanup round * Update Contributing blurb in the docs * Update for blueprint general-host-aggregates 2.6.1 ----- * Admin action to reset states * Filter out v1.0 endpoints * option to bypass managment endpoint and timings support * Removes NOVACLIENT\_DEBUG from client code * Fix spelling errors in aggregates section * Move docs to doc * Lock prettytable dep at v0.6 * Removed generate\_authors.sh since it's no longer used * nova show cmd displays unique flavor and image id * Use openstack-common for AUTHORS generation * Add .tox to .gitignore * Add start and stop to server actions * Adds flavor-show support * doc: fix and clarify the --meta option help * Lock pep8 at v1.1 * Turn on verbose test output * Align tox.ini with standards * make nova bash-complete faster and more accurate * refactored --service\_name to only work with compute calls and added --volume\_service\_name for volume calls * removed int requirement for volume\_id on snaps * Updated to new prettytable api. Fixes bug 995818 * Allow server name to be specified for actions and diagnostics * Don't force volume id to int and allow search by name * Fix LP #990667 - Keypair \_\_repr\_\_ referencesuuid * really output the description of an exception * Limit hint/nic parsing to one split on '=' * update README.rst,add args "service\_type" when getting endpoints * Rename NOVA\_VERSION to OS\_COMPUTE\_API\_VERSION * Raise exception on all 4xx and 5xx responses * Update unittests to be Python 2.6 compatible * Display the request id on error response * Make '--help' argument more useful * Fixed the subcommand error message for nova shell * Request ID when name is ambiguous * Set resources as loaded on get * Miscellaneous code cleanup * add packages using find\_packages() * set 'compute' as default endpoint bug fix for #970808 * Add -i/--identity option to 'nova ssh' * Improve 'nova ssh' error message * Fix spelling of curent in list sec groups * Set up the log handler only once * Remove serverId lookup in volume attachments * Handle server\_id and serverId in volume list * Added cloudpipe support. Fixes bug 962286 * Proposed HACKING guidelines for string encoding * Add missing tools and tox.ini to tarball * Fixes bug #959262 - Prevent a failure to create the cache directory from causing an exception * Improve the error message from the nova shell * Adds NOVACLIENT\_INSECURE option * Implement quota classes * Open Folsom * Adding Console Log to CLI * Change CLIAuth arg names * Add suport for instance locking/unlocking * Add --poll for instance snapshots * Add human-friendly ID support * Fixes lp#948685 proxy\_token and proxy\_tenant\_id behavior * Separate UUID caches for different endpoints * Remove trailing whitespaces in regular file * Adds --ipv6 and --port to ssh convience command * Add --poll for long running actions * Add support for volume types * Makes novaclient use the volumes endpoint * Fix for backward compatibility with stable/diablo flavors * Add support for ephemeral\_gb to novaclient * allow '=' inside value of --meta=key=value * bug 932408: python-novaclient miss OSAPI host operations * Add ssh convenience command * Allow UUID\_CACHE\_DIR overriding via env variable * Removes zones * Fixes bug 925644: move dotfiles into dir * add support for --config-drive 'boot' command * shell: Hook --debug up to more stuff * Properly handle KeyErrors * adding credentials and endpoints output for debugging * Fixes bug 924588: Remove proto-keystone client from novaclient * Fix bug 904364: Consistiently handle trailing '/' on URLs * Adding describe-resource subcommand * Add Accept: applicaton/json header to all service requests. Fixes bug 904436 * Blueprint cli-auth: common cli args * Add --all\_tenants option to 'nova list' * Adding live migration subcommand * Handle Ambiguous Endpoints Correctly * Implementing Scheduling Hints * Remove non-working --key\_path argument on boot * Fix datetime issue with usage\_data * blueprint host-aggregates: client bindings * moves the "help" in the usage information of a wrong command to the correct position * Implementing client for new x509 support in nova * Add flavor create/delete support * Add a 'usage' module and 'usage-list' cli command * Implement virtual interfaces servers api * Print adminPass when rescuing an instance * do not require NOVA\_VERSION in env, default to 1.1 * Match create\_image on server object and manager * Catch novaclient up with renaming and other nova changes * Add server.get\_vnc\_console functionality to python-novaclient * Fix bad api call, 'migrate' is an action * Adding rebuild/resize hooks * Implementing Floating Ip Pools * Get ImpLoader from ImpImporter for Py2.6 * Discover extensions via Python's import paths * PEP8 python-novaclient cleanup * show 409 responses * Added command-line interfaces for the floating ip DNS api to nova * Fix Quota ant SecurityGroup resources refreshing * Clean FloatingIPDNS resource * Install a good version of pip in the venv * Add tox.ini file * Add missing returns and remove superfluous ones * Fix typo in endpoint\_name help string * Add the python api for floating IP DNS * Abstract Client building into novaclient.client * Remove unused imports and fix NameError on exc * Improve the test framework to handle urls with args * Simplifying get\_console\_output client interface * Removing cache-busting query param (fresh) * Adding return statement to get\_console\_output * python-novaclient missing pep8 in pip-requires * utils.find\_resource fixes + fix for volumes * Add list() method to ManagerWithFind * Extensions can now modify resources * more work towards standardize config * Allow to not specify image if block\_device\_mapping is set * Adding support for the os-getConsoleOutput server action * Add 'discover' command for Keystone discovery and version listing * User friendly help message * Do no depends on argparse for Python >= 2.7 * standardize environmental settings for cli auth * Removed v1.0 support * Making contrib a Python package * Adding extension framework * Fix typo in README * Accept 1 and 2 as version choices * Add support for RAX authentication * Align run\_tests.sh with nova * Switch versioning to common Nova versioning * Fix PEP8 error * Add MANIFEST.in and setup.cfg back * Adding 'absolute-limits' and 'rate-limits' * Fixing all remaining pep8 errors * Clean up image-list cli command * Clean up image-show * Updated README.rst * Converting rxtx\_cap and rxtx\_quota to rxtx\_factor * Gracefully handle failure to cache UUID's. Bug #897885 * Change 'zone\_blob' key to 'blob' in create server. bug 893183 * Fix spacing errors in authentication exceptions * Adding UUID cache for bash autocompletion * Revert api\_key change in novaclient Client argument * Adds bash completion support and cleans up setup.py * Rewriting admin-only calls as server actions * Add rfc.sh * Add .gitreview config file for gerrit * pep8 * fix tests * trunk merge * Add support for specifying VIF networks while booting * Use a try/finally to ensure state restoration * Follow redirects when calling out to Keystone * Modified as per code-review comments: - Renaned snapshot to volume-snapshot - Created a new file for volume snapshots * few missing references to api\_key * tests working * started * added --endpoint\_name support * Add back display of adminPass to boot * Boot now works with limited info returned from server * fixed missing line continuation characters in shell.py * PEP8 cleanups of utils, and the v1\_?/shell.py files * minor pep8 tweaks * corrected argument order and replaced tabs with spaces * resolved merge conflict * added a space after url * Added the option --insecure. This disables SSL certificate validation * Updated the novaclient shell to display the parent server id that the image came from * Fixed description for block\_device\_mapping parameter * minor fixes * Added support for boot from volume (or snapshot) * version update * minor tweaks and long overdue pep8 * new service catalog semantics * Added support for listing/creating/deleting snapshots of nova volumes. Also implemented the supporting CLI commands. Requires the OS API extension, 'os-snapshots' * Updated volume-create command to accept an optional attribute, snapshot\_id. This enables the user to create a volume from a snapshot * Fixes #133 -- Keystone Client fetches correct service type and endpoint * fix tests * typo * merged and fixed pshkitin's keypair work * doc improvements * Added support to specify more boot options * Updated volume attach/detach commands to accept server name (in addition to server id). Code review comments: https://github.com/rackspace/python-novaclient/pull/125/files#r169829 * Booting server with specific key is implemented * Added commands to work with keypairs * make description consistent * remove extra space * add ability to create source group rules * don't expose ids to end user * work on formatting for secgroup rules * display floating ip on create * Add CLI for security groups and rules * raise exception if floating\_ip is not found in floating-ip-delete * Add cli for floating ips * Added support to specify more boot options * Don't filter endpoints when filter\_value is non-truthy * Added the following CLI commands to access nova volumes: volume-attach Attach a volume to a server. volume-create Add a new volume. volume-delete Remove a volume. volume-detach Detach a volume from a server. volume-list List all the volumes. volume-show Show details about a volume * now uses tenantName vs. tenantId to auth * version bump * removed unicode casts * cleaned up exception handling * new service catalog implementation * change auth cred format for keystone * Added methods to get, attach and detach volumes to/from running instances * Added support to access nova-volume api (v1.1 extension) - Only the basic functionality (create, delete, list) is implemented * add todo to update doc strings so that they reflect extension/optional-ness * update doc strings * add key\_name to servers.create * Make sure flavor is a type of int * removed debugging * token support * fixed unknown service * properly uses keystone admin endpoint for token lookup * proxy token support - no tests * readme fix * service catalog with multiple endpoints per service * Add ability to force debugging via os environ * merge fixup * version bump * readme * service catalog as auth parameter * service name support * Extend lazy loading support to Weighting * Fix unittests breakage in test\_shell * Fix #109 (nova show name not working) * Add userdata support * Remove extra NOVA\_PROJECT\_ID * Fix unittests breakage from merge 3507905 * Add 'meta' command to allow set/delete of metadata items on servers. Added ability to run multiple assert\_called tests from one test function * add build, dist, python-novaclient.egg-info to .gitignore * fixes odd \_\_get\_attr\_\_ behavior in 2.6.5 * conflict fixed * catch misssing id * Add body in debugging * Fix test installation exclude * Add support for image metadata to be viewed, added, updated, and deleted * Bump the release version * fixed up zone-add * Reducing v1\_1.base to just booting manager * tests working * in progress - adding zone name * ensure we have auth\_url and project\_id for !1.0 * Updated error message as suggested by bcwaldon * take auth token param * Do not assume default for image and flavor * expanding on concept of 'loaded' * limiting resource lazyloading to a single query * Fix extra # char as noticed by jk0 * Add piston service\_catalog * Add anotherjesse keystone here * Fix loop properly * Make sure we can do a get on the base class * Client changes for username and password in zone add * fix for chmouel's comment, and tweaks to tests * support for floating\_ips + D4 * make \_\_repr\_\_ more useful with default behavior, rather than juse displaying id * a few tweaks to get the client talking to nova * more cleanup * progress on security groups * updating version * updating for new rebuild format * adding tests * cleaning up find\_resource method to support str/int ids, uuids, and integer-like names * Fix #85 * fixing the shell tests * Fixed 1.0 and unit tests * Added support for 1.0 and added unit tests * Updated rescue/unrescue to use public API * removing extra space * updating quotas and tests with the format which recently landed in nova * fixing up a few pep8 issues, and pointing client to the new endpoint * Properly make image\_id a requirement to be int * Make sure the image id is an integer * really fixed * accidentally deleted a comment when fixing conflict * pep8 issues * update readme to talk about keystone with CLI and use 1.1 api * Switch API path to match http://bazaar.launchpad.net/~tpatil/nova/os-security-groups/revision/1369 * Fix API path * fix display\_name references that should have been instance\_name * removed fixed\_ip from v1.1 shell. Use --ip instead. Fixed up rest of other search options from last commit * start add of --image, --flavor, --status, and --host options to 'list' command. also fix up differences with --name and --display\_name compared to how nova implementation turned out * Security groups cleanups * Added redirect tests, changed wrong status in test\_authenticate\_success * Added self.auth\_url updating, WrongResponse exception * add note about keystone / auth 2.0 * Clean up id handling and pass basic tests * Add security group rules * Eradicate TABs, make tests run * missed a conflict * merge master * Initial security groups code * adding unittest * removing extra newline * adding email to .mailmap * catching authorization failure (x-server-management-url KeyError) * bring up-to-date with lp:~cloudbuilders/nova/os-keypairs * keypair api * add license headers * add support for quotas * pep8, again * Recursion handling * Added .mailmap file for AUTHORS * Updated authors and fixed tools/generate\_authors.sh * Fixes copyright notice and adds script to gen AUTHORS * keypair api * pep8 * Status code 305 fix, ClientExceptions if we can not handle response * whitespace cleanups * pep8 cleanups after the rebase * Adds run\_tests.sh and virtualenv support * pep8 in tests * pep8 in novaclient * Add Hacking and Authors to bring this into accordance with OpenStack coding standards * redirect * Redirection handling * cleaning up boot output; upping version * Added documentation for NOVA\_VERSION * Make it possible to authenticate against keystone * Removed the bodies again * Corrected docs * off by one * Missed a conflict 2.6.0 ----- * manual merge * Accidently had a reference to ipgroup still * Merged v1.0 functionality into v1.1 so we don't lose any features by...upgrading? * Fix for failing tests because boot response now requests additional information * formatting updates * novaclient -> nova in some documentation as per feedback * Removed unneeded print * Change create-image back to image-create, and increased version to 2.6.0 * Updated --version to default to NOVA\_VERSION, quick fix * Updated --version to default to NOVA\_VERSION * osc -> novaclient * Cleaned up v1.0 and v1.1 test setup to remove globals and encapsulate custom asserts. Still duplicate code, but closer to being able to remove. Now tests set up OpenStackClient much closer to how users will do it, minus the stubbing of the client * Wrong client was getting loaded * Grrrr, bad import * Tests now run correctly for v1.1 and v1.0 * Updated the default version back to 1.0, as there are some quirks with 1.1 * Tests working again...merged in some work we did earlier * Split everything down the middle into v1\_0 and v1\_1, including tests * bumping version and updating README * updating server backup action; pep8 fixes * removed server dump after add/remove fixed-ip * version bump * fixed public private ip list * added various search options to list command. will need a version bump as i changed the 'list' api that nova uses. after version bump, my search nova branch will need pip-requires updated to match * docs * added add/remove fixed\_ip actions to servers * Clarify description so usage doesn't imply name is the only valid value * Added support for request timeouts * Added migration functionality * Refactored backup rotation * Review feedback * Fixed unit tests * Implemented backup with rotation * for creating 'x' instances, min\_count > max\_count check was reversed make max/min\_instances a little more sane by making them 'int' types fix issue where only specifying --min\_instances didn't work * Due to how novaclient works, it tends to do a 'get' first on whatever ID you pass on command line. Then it does the real command, re-using the ID found in the 'get' call, instead of the initial ID that you specified (which may have been a UUID) * Cleaned up the query\_string generation for 'nova list' Made --recurse\_zones not need an '=argument' * Added --recurse\_zones option to 'list' Added --fixed\_ip option to 'list' to find a particular instance by IP Fixed issue with 'show' when --recurse\_zones=1 and specifying UUID * fixup * release note update * tests working again for weight\_scale/weight\_offset * fixed up tests after trunk merge and bumped version * version bump * fixed project\_id tests * Merged my 'create-num-instances' branch which adds support for --min\_instances and --max\_instances to zone-boot * version bumped * trunk merge * Typo fix * Added the missing files * Added a method to create instances on behalf of an account via the admin API methods for openstack * changed docs about using project id * bumped version # after project\_id update * Don't restrict ids to int * fix errors * support for project id header * Now that I understand how to build extensions, I understand how this extension will be built, and can fully implement add\_fixed\_ip() and remove\_fixed\_ip() * Add the basic calls for add\_fixed\_ip/remove\_fixed\_ip * defaults back to no detail * tweaks * griddynamics better logging * reservation\_id optional parameter added to GET /servers (aka 'list') * works properly with zone-boot * Improving tests * Added parameter detailed to list * zone select support and version bump to 2.4.3 * fix to reviewer comment: add check if logging disabled * improve perfomance on string concat in logging * add logging for http request-response * zone select * add undetailed flavor list * zone\_blob support added to server.create * fixed flavor-list columns * added support for missing flavors/images * up'ed version to suit pypi distribution update * fixed software license * version 2.4 * Added Jacob Kaplan-Moss copyright notices on older/untouched files * renamed to novaclient and fixed flavor tests * missing docstring quote * tweaked release notes * removed copyright/license notices from files not significantly changed * renamed cmdline tool from novatools to nova. Changed version to 2.1. Changed license to Apache. Added copyright notices. Cleaner exception reporting in non-debug scnario * fixed setup * longer zone list * Added full flavor detail support * zone info works * get this zone status * removed Username from zone info * NOVA\_TOOLS\_\* -> NOVA\_\*, --debug * removed zone name, renamed auth\_url to api\_url, added username/password * zone shell cmds & tests added * zone tests pass * zone tests pass * tests pass again * zones * Renamed all CloudServers to OpenStack and python-cloudservers to python-novatools * installer fixup * README update and rename cloudservers to novatools * Starting on child zone support * Initial commit from fork ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/HACKING.rst0000664000175000017500000000220100000000000017104 0ustar00zuulzuul00000000000000Nova Client Style Commandments ============================== - Step 1: Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest - Step 2: Read on Nova Client Specific Commandments --------------------------------- None so far Text encoding ------------- - Transitions between internal unicode and external strings should always be immediately and explicitly encoded or decoded. - All external text that is not explicitly encoded (database storage, commandline arguments, etc.) should be presumed to be encoded as utf-8. Wrong:: mystring = infile.readline() myreturnstring = do_some_magic_with(mystring) outfile.write(myreturnstring) Right:: mystring = infile.readline() mytext = s.decode('utf-8') returntext = do_some_magic_with(mytext) returnstring = returntext.encode('utf-8') outfile.write(returnstring) Running Tests ------------- The testing system is based on a combination of tox and stestr. If you just want to run the whole suite, run ``tox`` and all will be fine. However, if you'd like to dig in a bit more, you might want to learn some things about stestr itself. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/LICENSE0000664000175000017500000002707300000000000016331 0ustar00zuulzuul00000000000000Copyright (c) 2009 Jacob Kaplan-Moss - initial codebase (< v2.1) Copyright (c) 2011 Rackspace - OpenStack extensions (>= v2.1) All rights reserved. Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. --- License for python-novaclient versions prior to 2.1 --- All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of this project nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1725030944.8644686 python-novaclient-18.7.0/PKG-INFO0000664000175000017500000000600200000000000016406 0ustar00zuulzuul00000000000000Metadata-Version: 1.2 Name: python-novaclient Version: 18.7.0 Summary: Client library for OpenStack Compute API Home-page: https://docs.openstack.org/python-novaclient/latest Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: Apache License, Version 2.0 Description: ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/python-novaclient.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on ============================================ Python bindings to the OpenStack Compute API ============================================ .. image:: https://img.shields.io/pypi/v/python-novaclient.svg :target: https://pypi.org/project/python-novaclient/ :alt: Latest Version This is a client for the OpenStack Compute API. It provides a Python API (the ``novaclient`` module) and a deprecated command-line script (``nova``). The Python API implements 100% of the OpenStack Compute API. * License: Apache License, Version 2.0 * `PyPi`_ - package installation * `Online Documentation`_ * `Launchpad project`_ - release management * `Blueprints`_ - feature specifications * `Bugs`_ - issue tracking * `Source`_ * `Specs`_ * `How to Contribute`_ * `Release Notes`_ .. _PyPi: https://pypi.org/project/python-novaclient .. _Online Documentation: https://docs.openstack.org/python-novaclient/latest .. _Launchpad project: https://launchpad.net/python-novaclient .. _Blueprints: https://blueprints.launchpad.net/python-novaclient .. _Bugs: https://bugs.launchpad.net/python-novaclient .. _Source: https://opendev.org/openstack/python-novaclient .. _How to Contribute: https://docs.opendev.org/opendev/infra-manual/latest/developers.html .. _Specs: https://specs.openstack.org/openstack/nova-specs/ .. _Release Notes: https://docs.openstack.org/releasenotes/python-novaclient Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Environment :: OpenStack Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Information Technology Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: Implementation :: CPython Requires-Python: >=3.8 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/README.rst0000664000175000017500000000315100000000000017002 0ustar00zuulzuul00000000000000======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/python-novaclient.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on ============================================ Python bindings to the OpenStack Compute API ============================================ .. image:: https://img.shields.io/pypi/v/python-novaclient.svg :target: https://pypi.org/project/python-novaclient/ :alt: Latest Version This is a client for the OpenStack Compute API. It provides a Python API (the ``novaclient`` module) and a deprecated command-line script (``nova``). The Python API implements 100% of the OpenStack Compute API. * License: Apache License, Version 2.0 * `PyPi`_ - package installation * `Online Documentation`_ * `Launchpad project`_ - release management * `Blueprints`_ - feature specifications * `Bugs`_ - issue tracking * `Source`_ * `Specs`_ * `How to Contribute`_ * `Release Notes`_ .. _PyPi: https://pypi.org/project/python-novaclient .. _Online Documentation: https://docs.openstack.org/python-novaclient/latest .. _Launchpad project: https://launchpad.net/python-novaclient .. _Blueprints: https://blueprints.launchpad.net/python-novaclient .. _Bugs: https://bugs.launchpad.net/python-novaclient .. _Source: https://opendev.org/openstack/python-novaclient .. _How to Contribute: https://docs.opendev.org/opendev/infra-manual/latest/developers.html .. _Specs: https://specs.openstack.org/openstack/nova-specs/ .. _Release Notes: https://docs.openstack.org/releasenotes/python-novaclient ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/bindep.txt0000664000175000017500000000121300000000000017312 0ustar00zuulzuul00000000000000# This is a cross-platform list tracking distribution packages needed by tests; # see https://docs.opendev.org/opendev/bindep/latest/ for additional information. build-essential [platform:dpkg] dbus-devel [platform:rpm] dbus-glib-devel [platform:rpm] gettext language-pack-en [platform:ubuntu] libdbus-1-dev [platform:dpkg] libdbus-glib-1-dev [platform:dpkg] libffi-dev [platform:dpkg] libffi-devel [platform:rpm] libssl-dev [platform:ubuntu] libuuid-devel [platform:rpm] locales [platform:debian] openssl python3-all-dev [platform:ubuntu !platform:ubuntu-precise] python3-dev [platform:dpkg] python3-devel [platform:fedora] uuid-dev [platform:dpkg] ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1725030944.7844644 python-novaclient-18.7.0/doc/0000775000175000017500000000000000000000000016060 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/doc/requirements.txt0000664000175000017500000000025400000000000021345 0ustar00zuulzuul00000000000000sphinx>=2.1.1 # BSD openstackdocstheme>=2.2.0 # Apache-2.0 reno>=3.1.0 # Apache-2.0 sphinxcontrib-apidoc>=0.2.0 # BSD # redirect tests in docs whereto>=0.3.0 # Apache-2.0 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1725030944.7844644 python-novaclient-18.7.0/doc/source/0000775000175000017500000000000000000000000017360 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1725030944.7844644 python-novaclient-18.7.0/doc/source/_extra/0000775000175000017500000000000000000000000020642 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/doc/source/_extra/.htaccess0000664000175000017500000000115600000000000022443 0ustar00zuulzuul00000000000000# The following is generated with: # # git log --follow --name-status --format='%H' ac25ae6fee.. -- doc/source/ | \ # grep ^R | grep .rst | cut -f2- | \ # sed -e 's|doc/source/|redirectmatch 301 ^/python-novaclient/([^/]+)/|' -e 's|doc/source/|/python-novaclient/$1/|' -e 's/.rst/.html$/' -e 's/.rst/.html/' | \ # sort redirectmatch 301 ^/python-novaclient/([^/]+)/api.html$ /python-novaclient/$1/reference/api/index.html redirectmatch 301 ^/python-novaclient/([^/]+)/man/nova.html$ /python-novaclient/$1/cli/nova.html redirectmatch 301 ^/python-novaclient/([^/]+)/shell.html$ /python-novaclient/$1/user/shell.html ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1725030944.7844644 python-novaclient-18.7.0/doc/source/cli/0000775000175000017500000000000000000000000020127 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/doc/source/cli/index.rst0000664000175000017500000000012600000000000021767 0ustar00zuulzuul00000000000000=============== CLI Reference =============== .. toctree:: :maxdepth: 2 nova ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/doc/source/cli/nova.rst0000664000175000017500000026032700000000000021636 0ustar00zuulzuul00000000000000====== nova ====== The nova client is the command-line interface (CLI) for the Compute service (nova) API and its extensions. For help on a specific :command:`nova` command, enter: .. code-block:: console $ nova help COMMAND .. deprecated:: 17.8.0 The ``nova`` CLI has been deprecated in favour of the unified ``openstack`` CLI. For information on using the ``openstack`` CLI, see :python-openstackclient-doc:`OpenStackClient <>`. .. _nova_command_usage: nova usage ~~~~~~~~~~ .. code-block:: console usage: nova [--version] [--debug] [--os-cache] [--timings] [--os-region-name ] [--service-type ] [--service-name ] [--os-endpoint-type ] [--os-compute-api-version ] [--os-endpoint-override ] [--profile HMAC_KEY] [--insecure] [--os-cacert ] [--os-cert ] [--os-key ] [--timeout ] [--collect-timing] [--os-auth-type ] [--os-auth-url OS_AUTH_URL] [--os-system-scope OS_SYSTEM_SCOPE] [--os-domain-id OS_DOMAIN_ID] [--os-domain-name OS_DOMAIN_NAME] [--os-project-id OS_PROJECT_ID] [--os-project-name OS_PROJECT_NAME] [--os-project-domain-id OS_PROJECT_DOMAIN_ID] [--os-project-domain-name OS_PROJECT_DOMAIN_NAME] [--os-trust-id OS_TRUST_ID] [--os-default-domain-id OS_DEFAULT_DOMAIN_ID] [--os-default-domain-name OS_DEFAULT_DOMAIN_NAME] [--os-user-id OS_USER_ID] [--os-username OS_USERNAME] [--os-user-domain-id OS_USER_DOMAIN_ID] [--os-user-domain-name OS_USER_DOMAIN_NAME] [--os-password OS_PASSWORD] ... **Subcommands:** ``add-fixed-ip`` **DEPRECATED** Add new IP address on a network to server. ``add-secgroup`` Add a Security Group to a server. ``agent-create`` Create new agent build. ``agent-delete`` Delete existing agent build. ``agent-list`` List all builds. ``agent-modify`` Modify existing agent build. ``aggregate-add-host`` Add the host to the specified aggregate. ``aggregate-cache-images`` Request images be pre-cached on hosts within an aggregate. (Supported by API versions '2.81' - '2.latest') ``aggregate-create`` Create a new aggregate with the specified details. ``aggregate-delete`` Delete the aggregate. ``aggregate-list`` Print a list of all aggregates. ``aggregate-remove-host`` Remove the specified host from the specified aggregate. ``aggregate-set-metadata`` Update the metadata associated with the aggregate. ``aggregate-show`` Show details of the specified aggregate. ``aggregate-update`` Update the aggregate's name and optionally availability zone. ``availability-zone-list`` List all the availability zones. ``backup`` Backup a server by creating a 'backup' type snapshot. ``boot`` Boot a new server. ``clear-password`` Clear the admin password for a server from the metadata server. This action does not actually change the instance server password. ``cloudpipe-configure`` **DEPRECATED** Update the VPN IP/port of a cloudpipe instance. ``cloudpipe-create`` **DEPRECATED** Create a cloudpipe instance for the given project. ``cloudpipe-list`` **DEPRECATED** Print a list of all cloudpipe instances. ``console-log`` Get console log output of a server. ``delete`` Immediately shut down and delete specified server(s). ``diagnostics`` Retrieve server diagnostics. ``evacuate`` Evacuate server from failed host. ``flavor-access-add`` Add flavor access for the given tenant. ``flavor-access-list`` Print access information about the given flavor. ``flavor-access-remove`` Remove flavor access for the given tenant. ``flavor-create`` Create a new flavor. ``flavor-delete`` Delete a specific flavor ``flavor-key`` Set or unset extra_spec for a flavor. ``flavor-list`` Print a list of available 'flavors' (sizes of servers). ``flavor-show`` Show details about the given flavor. ``flavor-update`` Update the description of an existing flavor. (Supported by API versions '2.55' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] ``floating-ip-associate`` **DEPRECATED** Associate a floating IP address to a server. ``floating-ip-disassociate`` **DEPRECATED** Disassociate a floating IP address from a server. ``force-delete`` Force delete a server. ``get-mks-console`` Get an MKS console to a server. (Supported by API versions '2.8' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] ``get-password`` Get the admin password for a server. This operation calls the metadata service to query metadata information and does not read password information from the server itself. ``get-rdp-console`` Get a rdp console to a server. ``get-serial-console`` Get a serial console to a server. ``get-spice-console`` Get a spice console to a server. ``get-vnc-console`` Get a vnc console to a server. ``host-action`` **DEPRECATED** Perform a power action on a host. ``host-describe`` **DEPRECATED** Describe a specific host. ``host-evacuate`` Evacuate all instances from failed host. ``host-evacuate-live`` Live migrate all instances off the specified host to other available hosts. ``host-list`` **DEPRECATED** List all hosts by service. ``host-meta`` Set or Delete metadata on all instances of a host. ``host-servers-migrate`` Cold migrate all instances off the specified host to other available hosts. ``host-update`` **DEPRECATED** Update host settings. ``hypervisor-list`` List hypervisors. (Supported by API versions '2.0' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] ``hypervisor-servers`` List servers belonging to specific hypervisors. ``hypervisor-show`` Display the details of the specified hypervisor. ``hypervisor-stats`` Get hypervisor statistics over all compute nodes. ``hypervisor-uptime`` Display the uptime of the specified hypervisor. ``image-create`` Create a new image by taking a snapshot of a running server. ``instance-action`` Show an action. ``instance-action-list`` List actions on a server. ``instance-usage-audit-log`` List/Get server usage audits. ``interface-attach`` Attach a network interface to a server. ``interface-detach`` Detach a network interface from a server. ``interface-list`` List interfaces attached to a server. ``keypair-add`` Create a new key pair for use with servers. ``keypair-delete`` Delete keypair given by its name. (Supported by API versions '2.0' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] ``keypair-list`` Print a list of keypairs for a user (Supported by API versions '2.0' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] ``keypair-show`` Show details about the given keypair. (Supported by API versions '2.0' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] ``limits`` Print rate and absolute limits. ``list`` List servers. ``list-secgroup`` List Security Group(s) of a server. ``live-migration`` Migrate running server to a new machine. ``live-migration-abort`` Abort an on-going live migration. (Supported by API versions '2.24' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] ``live-migration-force-complete`` Force on-going live migration to complete. (Supported by API versions '2.22' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] ``lock`` Lock a server. A normal (non-admin) user will not be able to execute actions on a locked server. ``meta`` Set or delete metadata on a server. ``migrate`` Migrate a server. The new host will be selected by the scheduler. ``migration-list`` Print a list of migrations. ``pause`` Pause a server. ``quota-class-show`` List the quotas for a quota class. ``quota-class-update`` Update the quotas for a quota class. (Supported by API versions '2.0' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] ``quota-defaults`` List the default quotas for a tenant. ``quota-delete`` Delete quota for a tenant/user so their quota will Revert back to default. ``quota-show`` List the quotas for a tenant/user. ``quota-update`` Update the quotas for a tenant/user. (Supported by API versions '2.0' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] ``reboot`` Reboot a server. ``rebuild`` Shutdown, re-image, and re-boot a server. ``refresh-network`` Refresh server network information. ``remove-fixed-ip`` **DEPRECATED** Remove an IP address from a server. ``remove-secgroup`` Remove a Security Group from a server. ``rescue`` Reboots a server into rescue mode, which starts the machine from either the initial image or a specified image, attaching the current boot disk as secondary. ``reset-network`` Reset network of a server. ``reset-state`` Reset the state of a server. ``resize`` Resize a server. ``resize-confirm`` Confirm a previous resize. ``resize-revert`` Revert a previous resize (and return to the previous VM). ``restore`` Restore a soft-deleted server. ``resume`` Resume a server. ``server-group-create`` Create a new server group with the specified details. ``server-group-delete`` Delete specific server group(s). ``server-group-get`` Get a specific server group. ``server-group-list`` Print a list of all server groups. ``server-migration-list`` Get the migrations list of specified server. (Supported by API versions '2.23' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] ``server-migration-show`` Get the migration of specified server. (Supported by API versions '2.23' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] ``server-tag-add`` Add one or more tags to a server. (Supported by API versions '2.26' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] ``server-tag-delete`` Delete one or more tags from a server. (Supported by API versions '2.26' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] ``server-tag-delete-all`` Delete all tags from a server. (Supported by API versions '2.26' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] ``server-tag-list`` Get list of tags from a server. (Supported by API versions '2.26' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] ``server-tag-set`` Set list of tags to a server. (Supported by API versions '2.26' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] ``server-topology`` Retrieve NUMA topology of the given server. (Supported by API versions '2.78' - '2.latest') ``service-delete`` Delete the service. ``service-disable`` Disable the service. ``service-enable`` Enable the service. ``service-force-down`` Force service to down. (Supported by API versions '2.11' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] ``service-list`` Show a list of all running services. Filter by host & binary. ``set-password`` Change the admin password for a server. ``shelve`` Shelve a server. ``shelve-offload`` Remove a shelved server from the compute node. ``show`` Show details about the given server. ``ssh`` SSH into a server. ``start`` Start the server(s). ``stop`` Stop the server(s). ``suspend`` Suspend a server. ``trigger-crash-dump`` Trigger crash dump in an instance. (Supported by API versions '2.17' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] ``unlock`` Unlock a server. ``unpause`` Unpause a server. ``unrescue`` Restart the server from normal boot disk again. ``unshelve`` Unshelve a server. ``update`` Update the name or the description for a server. ``usage`` Show usage data for a single tenant. ``usage-list`` List usage data for all tenants. ``version-list`` List all API versions. ``virtual-interface-list`` **DEPRECATED** Show virtual interface info about the given server. ``volume-attach`` Attach a volume to a server. ``volume-attachments`` List all the volumes attached to a server. ``volume-detach`` Detach a volume from a server. ``volume-update`` Update the attachment on the server. Migrates the data from an attached volume to the specified available volume and swaps out the active attachment to the new volume. Since microversion 2.85, support for updating the ``delete_on_termination`` delete flag, which allows changing the behavior of volume deletion on instance deletion. ``x509-create-cert`` **DEPRECATED** Create x509 cert for a user in tenant. ``x509-get-root-cert`` **DEPRECATED** Fetch the x509 root cert. ``bash-completion`` Prints all of the commands and options to stdout so that the nova.bash_completion script doesn't have to hard code them. ``help`` Display help about this program or one of its subcommands. .. _nova_command_options: nova optional arguments ~~~~~~~~~~~~~~~~~~~~~~~ ``--version`` show program's version number and exit ``--debug`` Print debugging output. ``--os-cache`` Use the auth token cache. Defaults to False if ``env[OS_CACHE]`` is not set. ``--timings`` Print call timing info. ``--os-region-name `` Defaults to ``env[OS_REGION_NAME]``. ``--service-type `` Defaults to compute for most actions. ``--service-name `` Defaults to ``env[NOVA_SERVICE_NAME]``. ``--os-endpoint-type `` Defaults to ``env[NOVA_ENDPOINT_TYPE]``, ``env[OS_ENDPOINT_TYPE]`` or publicURL. ``--os-compute-api-version `` Accepts X, X.Y (where X is major and Y is minor part) or "X.latest", defaults to ``env[OS_COMPUTE_API_VERSION]``. ``--os-endpoint-override `` Use this API endpoint instead of the Service Catalog. Defaults to ``env[OS_ENDPOINT_OVERRIDE]``. ``--profile HMAC_KEY`` 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 nova; it is specified in the Nova configuration file at "/etc/nova/nova.conf". Without the key, profiling will not be triggered even if OSprofiler is enabled on the server side. ``--os-auth-type , --os-auth-plugin `` Authentication type to use .. _nova_add-secgroup: nova add-secgroup ----------------- .. code-block:: console usage: nova add-secgroup Add a Security Group to a server. **Positional arguments:** ```` Name or ID of server. ```` Name or ID of Security Group. .. _nova_agent-create: nova agent-create ----------------- .. code-block:: console usage: nova agent-create Create new agent build. **Positional arguments:** ```` Type of OS. ```` Type of architecture. ```` Version. ```` URL. ```` MD5 hash. ```` Type of hypervisor. .. _nova_agent-delete: nova agent-delete ----------------- .. code-block:: console usage: nova agent-delete Delete existing agent build. **Positional arguments:** ```` ID of the agent-build. .. _nova_agent-list: nova agent-list --------------- .. code-block:: console usage: nova agent-list [--hypervisor ] List all builds. **Optional arguments:** ``--hypervisor `` Type of hypervisor. .. _nova_agent-modify: nova agent-modify ----------------- .. code-block:: console usage: nova agent-modify Modify existing agent build. **Positional arguments:** ```` ID of the agent-build. ```` Version. ```` URL ```` MD5 hash. .. _nova_aggregate-add-host: nova aggregate-add-host ----------------------- .. code-block:: console usage: nova aggregate-add-host Add the host to the specified aggregate. **Positional arguments:** ```` Name or ID of aggregate. ```` The host to add to the aggregate. .. _nova_aggregate-cache-images: nova aggregate-cache-images --------------------------- .. code-block:: console usage: nova aggregate-cache-images [ ..] Request image(s) be pre-cached on hosts within the aggregate. (Supported by API versions '2.81' - '2.latest') .. versionadded:: 16.0.0 **Positional arguments:** ```` Name or ID of aggregate. ```` Name or ID of image(s) to cache. .. _nova_aggregate-create: nova aggregate-create --------------------- .. code-block:: console usage: nova aggregate-create [] Create a new aggregate with the specified details. **Positional arguments:** ```` Name of aggregate. ```` The availability zone of the aggregate (optional). .. _nova_aggregate-delete: nova aggregate-delete --------------------- .. code-block:: console usage: nova aggregate-delete Delete the aggregate. **Positional arguments:** ```` Name or ID of aggregate to delete. .. _nova_aggregate-list: nova aggregate-list ------------------- .. code-block:: console usage: nova aggregate-list Print a list of all aggregates. .. _nova_aggregate-remove-host: nova aggregate-remove-host -------------------------- .. code-block:: console usage: nova aggregate-remove-host Remove the specified host from the specified aggregate. **Positional arguments:** ```` Name or ID of aggregate. ```` The host to remove from the aggregate. .. _nova_aggregate-set-metadata: nova aggregate-set-metadata --------------------------- .. code-block:: console usage: nova aggregate-set-metadata [ ...] Update the metadata associated with the aggregate. **Positional arguments:** ```` Name or ID of aggregate to update. ```` Metadata to add/update to aggregate. Specify only the key to delete a metadata item. .. _nova_aggregate-show: nova aggregate-show ------------------- .. code-block:: console usage: nova aggregate-show Show details of the specified aggregate. **Positional arguments:** ```` Name or ID of aggregate. .. _nova_aggregate-update: nova aggregate-update --------------------- .. code-block:: console usage: nova aggregate-update [--name NAME] [--availability-zone ] Update the aggregate's name and optionally availability zone. **Positional arguments:** ```` Name or ID of aggregate to update. **Optional arguments:** ``--name NAME`` New name for aggregate. ``--availability-zone `` New availability zone for aggregate. .. _nova_availability-zone-list: nova availability-zone-list --------------------------- .. code-block:: console usage: nova availability-zone-list List all the availability zones. .. _nova_backup: nova backup ----------- .. code-block:: console usage: nova backup Backup a server by creating a 'backup' type snapshot. **Positional arguments:** ```` Name or ID of server. ```` Name of the backup image. ```` The backup type, like "daily" or "weekly". ```` Int parameter representing how many backups to keep around. .. _nova_boot: nova boot --------- .. code-block:: console usage: nova boot [--flavor ] [--image ] [--image-with ] [--boot-volume ] [--snapshot ] [--min-count ] [--max-count ] [--meta ] [--key-name ] [--user-data ] [--availability-zone ] [--security-groups ] [--block-device-mapping ] [--block-device key1=value1[,key2=value2...]] [--swap ] [--ephemeral size=[,format=]] [--hint ] [--nic ] [--config-drive ] [--poll] [--admin-pass ] [--access-ip-v4 ] [--access-ip-v6 ] [--description ] [--tags ] [--return-reservation-id] [--trusted-image-certificate-id ] [--host ] [--hypervisor-hostname ] [--hostname ] Boot a new server. In order to create a server with pre-existing ports that contain a ``resource_request`` value, such as for guaranteed minimum bandwidth quality of service support, microversion ``2.72`` is required. **Positional arguments:** ```` Name for the new server. **Optional arguments:** ``--flavor `` Name or ID of flavor (see 'nova flavor-list'). ``--image `` Name or ID of image (see 'glance image-list'). ``--image-with `` Image metadata property (see 'glance image-show'). ``--boot-volume `` Volume ID to boot from. ``--snapshot `` Snapshot ID to boot from (will create a volume). ``--min-count `` Boot at least servers (limited by quota). ``--max-count `` Boot up to servers (limited by quota). ``--meta `` Record arbitrary key/value metadata to /meta_data.json on the metadata server. Can be specified multiple times. ``--key-name `` Key name of keypair that should be created earlier with the command keypair-add. ``--user-data `` user data file to pass to be exposed by the metadata server. ``--availability-zone `` The availability zone for server placement. ``--security-groups `` Comma separated list of security group names. ``--block-device-mapping `` Block device mapping in the format =:::. ``--block-device`` key1=value1[,key2=value2...] Block device mapping with the keys: id=UUID (image_id, snapshot_id or volume_id only if using source image, snapshot or volume) source=source type (image, snapshot, volume or blank), dest=destination type of the block device (volume or local), bus=device's bus (e.g. uml, lxc, virtio, ...; if omitted, hypervisor driver chooses a suitable default, honoured only if device type is supplied) type=device type (e.g. disk, cdrom, ...; defaults to 'disk') device=name of the device (e.g. vda, xda, ...; if omitted, hypervisor driver chooses suitable device depending on selected bus; note the libvirt driver always uses default device names), size=size of the block device in MiB(for swap) and in GiB(for other formats) (if omitted, hypervisor driver calculates size), format=device will be formatted (e.g. swap, ntfs, ...; optional), bootindex=integer used for ordering the boot disks (for image backed instances it is equal to 0, for others need to be specified), shutdown=shutdown behaviour (either preserve or remove, for local destination set to remove), tag=device metadata tag (optional; supported by API versions '2.42' - '2.latest'), and volume_type=type of volume to create (either ID or name) when source is `blank`, `image` or `snapshot` and dest is `volume` (optional; supported by API versions '2.67' - '2.latest'). ``--swap `` Create and attach a local swap block device of MiB. ``--ephemeral`` size=[,format=] Create and attach a local ephemeral block device of GiB and format it to . ``--hint `` Send arbitrary key/value pairs to the scheduler for custom use. ``--nic `` Create a NIC on the server. Specify option multiple times to create multiple nics unless using the special 'auto' or 'none' values. auto: automatically allocate network resources if none are available. This cannot be specified with any other nic value and cannot be specified multiple times. none: do not attach a NIC at all. This cannot be specified with any other nic value and cannot be specified multiple times. net-id: attach NIC to network with a specific UUID. net-name: attach NIC to network with this name (either port-id or net-id or net-name must be provided), v4-fixed-ip: IPv4 fixed address for NIC (optional), v6-fixed-ip: IPv6 fixed address for NIC (optional), port-id: attach NIC to port with this UUID tag: interface metadata tag (optional) (either port-id or net-id must be provided). (Supported by API versions '2.42' - '2.latest') ``--config-drive `` Enable config drive. The value must be a boolean value. ``--poll`` Report the new server boot progress until it completes. ``--admin-pass `` Admin password for the instance. ``--access-ip-v4 `` Alternative access IPv4 of the instance. ``--access-ip-v6 `` Alternative access IPv6 of the instance. ``--description `` Description for the server. (Supported by API versions '2.19' - '2.latest') ``--tags `` Tags for the server.Tags must be separated by commas: --tags (Supported by API versions '2.52' - '2.latest') ``--return-reservation-id`` Return a reservation id bound to created servers. ``--trusted-image-certificate-id `` Trusted image certificate IDs used to validate certificates during the image signature verification process. Defaults to env[OS_TRUSTED_IMAGE_CERTIFICATE_IDS]. May be specified multiple times to pass multiple trusted image certificate IDs. (Supported by API versions '2.63' - '2.latest') ``--host `` Requested host to create servers. Admin only by default. (Supported by API versions '2.74' - '2.latest') ``--hypervisor-hostname `` Requested hypervisor hostname to create servers. Admin only by default. (Supported by API versions '2.74' - '2.latest') ``--hostname `` Hostname for the instance. This sets the hostname stored in the metadata server: a utility such as cloud-init running on the guest is required to propagate these changes to the guest. (Supported by API versions '2.90' - '2.latest') .. _nova_clear-password: nova clear-password ------------------- .. code-block:: console usage: nova clear-password Clear the admin password for a server from the metadata server. This action does not actually change the instance server password. **Positional arguments:** ```` Name or ID of server. .. _nova_console-log: nova console-log ---------------- .. code-block:: console usage: nova console-log [--length ] Get console log output of a server. **Locale encoding issues** If you encounter an error such as: .. code-block:: console UnicodeEncodeError: 'ascii' codec can't encode characters in position The solution to these problems is different depending on which locale your computer is running in. For instance, if you have a German Linux machine, you can fix the problem by exporting the locale to de_DE.utf-8: .. code-block:: console export LC_ALL=de_DE.utf-8 export LANG=de_DE.utf-8 If you are on a US machine, en_US.utf-8 is the encoding of choice. On some newer Linux systems, you could also try C.UTF-8 as the locale: .. code-block:: console export LC_ALL=C.UTF-8 export LANG=C.UTF-8 **Positional arguments:** ```` Name or ID of server. **Optional arguments:** ``--length `` Length in lines to tail. .. _nova_delete: nova delete ----------- .. code-block:: console usage: nova delete [--all-tenants] [ ...] Immediately shut down and delete specified server(s). **Positional arguments:** ```` Name or ID of server(s). **Optional arguments:** ``--all-tenants`` Delete server(s) in another tenant by name (Admin only). .. _nova_diagnostics: nova diagnostics ---------------- .. code-block:: console usage: nova diagnostics Retrieve server diagnostics. **Positional arguments:** ```` Name or ID of server. .. _nova_evacuate: nova evacuate ------------- .. code-block:: console usage: nova evacuate [--password ] [--on-shared-storage] [--force] [] Evacuate server from failed host. **Positional arguments:** ```` Name or ID of server. ```` Name or ID of the target host. If no host is specified, the scheduler will choose one. **Optional arguments:** ``--password `` Set the provided admin password on the evacuated server. Not applicable if the server is on shared storage. ``--on-shared-storage`` Specifies whether server files are located on shared storage. (Supported by API versions '2.0' - '2.13') ``--force`` Force an evacuation by not verifying the provided destination host by the scheduler. (Supported by API versions '2.29' - '2.67') .. warning:: This could result in failures to actually evacuate the server to the specified host. It is recommended to either not specify a host so that the scheduler will pick one, or specify a host without ``--force``. .. _nova_flavor-access-add: nova flavor-access-add ---------------------- .. code-block:: console usage: nova flavor-access-add Add flavor access for the given tenant. **Positional arguments:** ```` Flavor name or ID to add access for the given tenant. ```` Tenant ID to add flavor access for. .. _nova_flavor-access-list: nova flavor-access-list ----------------------- .. code-block:: console usage: nova flavor-access-list [--flavor ] Print access information about the given flavor. **Optional arguments:** ``--flavor `` Filter results by flavor name or ID. .. _nova_flavor-access-remove: nova flavor-access-remove ------------------------- .. code-block:: console usage: nova flavor-access-remove Remove flavor access for the given tenant. **Positional arguments:** ```` Flavor name or ID to remove access for the given tenant. ```` Tenant ID to remove flavor access for. .. _nova_flavor-create: nova flavor-create ------------------ .. code-block:: console usage: nova flavor-create [--ephemeral ] [--swap ] [--rxtx-factor ] [--is-public ] [--description ] Create a new flavor. **Positional arguments:** ```` Unique name of the new flavor. ```` Unique ID of the new flavor. Specifying 'auto' will generated a UUID for the ID. ```` Memory size in MiB. ```` Disk size in GiB. ```` Number of vcpus **Optional arguments:** ``--ephemeral `` Ephemeral space size in GiB (default 0). ``--swap `` Swap space size in MiB (default 0). ``--rxtx-factor `` RX/TX factor (default 1). ``--is-public `` Make flavor accessible to the public (default true). ``--description `` A free form description of the flavor. Limited to 65535 characters in length. Only printable characters are allowed. (Supported by API versions '2.55' - '2.latest') .. _nova_flavor-delete: nova flavor-delete ------------------ .. code-block:: console usage: nova flavor-delete Delete a specific flavor **Positional arguments:** ```` Name or ID of the flavor to delete. .. _nova_flavor-key: nova flavor-key --------------- .. code-block:: console usage: nova flavor-key [ ...] Set or unset extra_spec for a flavor. **Positional arguments:** ```` Name or ID of flavor. ```` Actions: 'set' or 'unset'. ```` Extra_specs to set/unset (only key is necessary on unset). .. _nova_flavor-list: nova flavor-list ---------------- .. code-block:: console usage: nova flavor-list [--extra-specs] [--all] [--marker ] [--min-disk ] [--min-ram ] [--limit ] [--sort-key ] [--sort-dir ] Print a list of available 'flavors' (sizes of servers). **Optional arguments:** ``--extra-specs`` Get extra-specs of each flavor. ``--all`` Display all flavors (Admin only). ``--marker `` The last flavor ID of the previous page; displays list of flavors after "marker". ``--min-disk `` Filters the flavors by a minimum disk space, in GiB. ``--min-ram `` Filters the flavors by a minimum RAM, in MiB. ``--limit `` Maximum number of flavors to display. If limit is bigger than 'CONF.api.max_limit' option of Nova API, limit 'CONF.api.max_limit' will be used instead. ``--sort-key `` Flavors list sort key. ``--sort-dir `` Flavors list sort direction. .. _nova_flavor-show: nova flavor-show ---------------- .. code-block:: console usage: nova flavor-show Show details about the given flavor. **Positional arguments:** ```` Name or ID of flavor. nova flavor-update ------------------ .. code-block:: console usage: nova flavor-update Update the description of an existing flavor. (Supported by API versions '2.55' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] .. versionadded:: 10.0.0 **Positional arguments** ```` Name or ID of the flavor to update. ```` A free form description of the flavor. Limited to 65535 characters in length. Only printable characters are allowed. .. _nova_force-delete: nova force-delete ----------------- .. code-block:: console usage: nova force-delete Force delete a server. **Positional arguments:** ```` Name or ID of server. .. _nova_get-mks-console: nova get-mks-console -------------------- .. code-block:: console usage: nova get-mks-console Get an MKS console to a server. (Supported by API versions '2.8' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] .. versionadded:: 3.0.0 **Positional arguments:** ```` Name or ID of server. .. _nova_get-password: nova get-password ----------------- .. code-block:: console usage: nova get-password [] Get the admin password for a server. This operation calls the metadata service to query metadata information and does not read password information from the server itself. **Positional arguments:** ```` Name or ID of server. ```` Private key (used locally to decrypt password) (Optional). When specified, the command displays the clear (decrypted) VM password. When not specified, the ciphered VM password is displayed. .. _nova_get-rdp-console: nova get-rdp-console -------------------- .. code-block:: console usage: nova get-rdp-console Get a rdp console to a server. **Positional arguments:** ```` Name or ID of server. ```` Type of rdp console ("rdp-html5"). .. _nova_get-serial-console: nova get-serial-console ----------------------- .. code-block:: console usage: nova get-serial-console [--console-type CONSOLE_TYPE] Get a serial console to a server. **Positional arguments:** ```` Name or ID of server. **Optional arguments:** ``--console-type CONSOLE_TYPE`` Type of serial console, default="serial". .. _nova_get-spice-console: nova get-spice-console ---------------------- .. code-block:: console usage: nova get-spice-console Get a spice console to a server. **Positional arguments:** ```` Name or ID of server. ```` Type of spice console ("spice-html5"). .. _nova_get-vnc-console: nova get-vnc-console -------------------- .. code-block:: console usage: nova get-vnc-console Get a vnc console to a server. **Positional arguments:** ```` Name or ID of server. ```` Type of vnc console ("novnc" or "xvpvnc"). .. _nova_host-evacuate: nova host-evacuate ------------------ .. code-block:: console usage: nova host-evacuate [--target_host ] [--force] [--strict] Evacuate all instances from failed host. **Positional arguments:** ```` The hypervisor hostname (or pattern) to search for. .. warning:: Use a fully qualified domain name if you only want to evacuate from a specific host. **Optional arguments:** ``--target_host `` Name of target host. If no host is specified the scheduler will select a target. ``--force`` Force an evacuation by not verifying the provided destination host by the scheduler. (Supported by API versions '2.29' - '2.67') .. warning:: This could result in failures to actually evacuate the server to the specified host. It is recommended to either not specify a host so that the scheduler will pick one, or specify a host without ``--force``. ``--strict`` Evacuate host with exact hypervisor hostname match .. _nova_host-evacuate-live: nova host-evacuate-live ----------------------- .. code-block:: console usage: nova host-evacuate-live [--target-host ] [--block-migrate] [--max-servers ] [--force] [--strict] Live migrate all instances off the specified host to other available hosts. **Positional arguments:** ```` Name of host. The hypervisor hostname (or pattern) to search for. .. warning:: Use a fully qualified domain name if you only want to live migrate from a specific host. **Optional arguments:** ``--target-host `` Name of target host. If no host is specified, the scheduler will choose one. ``--block-migrate`` Enable block migration. (Default=auto) (Supported by API versions '2.25' - '2.latest') ``--max-servers `` Maximum number of servers to live migrate simultaneously ``--force`` Force a live-migration by not verifying the provided destination host by the scheduler. (Supported by API versions '2.30' - '2.67') .. warning:: This could result in failures to actually live migrate the servers to the specified host. It is recommended to either not specify a host so that the scheduler will pick one, or specify a host without ``--force``. ``--strict`` live Evacuate host with exact hypervisor hostname match .. _nova_host-meta: nova host-meta -------------- .. code-block:: console usage: nova host-meta [--strict] [ ...] Set or Delete metadata on all instances of a host. **Positional arguments:** ```` The hypervisor hostname (or pattern) to search for. .. warning:: Use a fully qualified domain name if you only want to update metadata for servers on a specific host. ```` Actions: 'set' or 'delete' ```` Metadata to set or delete (only key is necessary on delete) **Optional arguments:** ``--strict`` Set host-meta to the hypervisor with exact hostname match .. _nova_host-servers-migrate: nova host-servers-migrate ------------------------- .. code-block:: console usage: nova host-servers-migrate [--strict] Cold migrate all instances off the specified host to other available hosts. **Positional arguments:** ```` Name of host. The hypervisor hostname (or pattern) to search for. .. warning:: Use a fully qualified domain name if you only want to cold migrate from a specific host. **Optional arguments:** ``--strict`` Migrate host with exact hypervisor hostname match .. _nova_hypervisor-list: nova hypervisor-list -------------------- .. code-block:: console usage: nova hypervisor-list [--matching ] [--marker ] [--limit ] List hypervisors. (Supported by API versions '2.0' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] **Optional arguments:** ``--matching `` List hypervisors matching the given . If matching is used limit and marker options will be ignored. ``--marker `` The last hypervisor of the previous page; displays list of hypervisors after "marker". (Supported by API versions '2.33' - '2.latest') ``--limit `` Maximum number of hypervisors to display. If limit is bigger than 'CONF.api.max_limit' option of Nova API, limit 'CONF.api.max_limit' will be used instead. (Supported by API versions '2.33' - '2.latest') .. _nova_hypervisor-servers: nova hypervisor-servers ----------------------- .. code-block:: console usage: nova hypervisor-servers List servers belonging to specific hypervisors. **Positional arguments:** ```` The hypervisor hostname (or pattern) to search for. .. _nova_hypervisor-show: nova hypervisor-show -------------------- .. code-block:: console usage: nova hypervisor-show [--wrap ] Display the details of the specified hypervisor. **Positional arguments:** ```` Name or ID of the hypervisor. Starting with microversion 2.53 the ID must be a UUID. **Optional arguments:** ``--wrap `` Wrap the output to a specified length. Default is 40 or 0 to disable .. _nova_hypervisor-stats: nova hypervisor-stats --------------------- .. code-block:: console usage: nova hypervisor-stats Get hypervisor statistics over all compute nodes. .. _nova_hypervisor-uptime: nova hypervisor-uptime ---------------------- .. code-block:: console usage: nova hypervisor-uptime Display the uptime of the specified hypervisor. **Positional arguments:** ```` Name or ID of the hypervisor. Starting with microversion 2.53 the ID must be a UUID. .. _nova_image-create: nova image-create ----------------- .. code-block:: console usage: nova image-create [--metadata ] [--show] [--poll] Create a new image by taking a snapshot of a running server. **Positional arguments:** ```` Name or ID of server. ```` Name of snapshot. **Optional arguments:** ``--metadata `` Record arbitrary key/value metadata to /meta_data.json on the metadata server. Can be specified multiple times. ``--show`` Print image info. ``--poll`` Report the snapshot progress and poll until image creation is complete. .. _nova_instance-action: nova instance-action -------------------- .. code-block:: console usage: nova instance-action Show an action. **Positional arguments:** ```` Name or UUID of the server to show actions for. Only UUID can be used to show actions for a deleted server. (Supported by API versions '2.21' - '2.latest') ```` Request ID of the action to get. .. _nova_instance-action-list: nova instance-action-list ------------------------- .. code-block:: console usage: nova instance-action-list [--marker ] [--limit ] [--changes-since ] [--changes-before ] List actions on a server. **Positional arguments:** ```` Name or UUID of the server to list actions for. Only UUID can be used to list actions on a deleted server. (Supported by API versions '2.21' - '2.latest') **Optional arguments:** ``--marker `` The last instance action of the previous page; displays list of actions after "marker". (Supported by API versions '2.58' - '2.latest') ``--limit `` Maximum number of instance actions to display. Note that there is a configurable max limit on the server, and the limit that is used will be the minimum of what is requested here and what is configured in the server. (Supported by API versions '2.58' - '2.latest') ``--changes-since `` List only instance actions changed later or equal to a certain point of time. The provided time should be an ISO 8061 formatted time. e.g. 2016-03-04T06:27:59Z. (Supported by API versions '2.58' - '2.latest') ``--changes-before `` List only instance actions changed earlier or equal to a certain point of time. The provided time should be an ISO 8061 formatted time. e.g. 2016-03-04T06:27:59Z. (Supported by API versions '2.66' - '2.latest') .. _nova_instance-usage-audit-log: nova instance-usage-audit-log ----------------------------- .. code-block:: console usage: nova instance-usage-audit-log [--before ] List/Get server usage audits. **Optional arguments:** ``--before `` Filters the response by the date and time before which to list usage audits. The date and time stamp format is as follows: CCYY-MM-DD hh:mm:ss.NNNNNN ex 2015-08-27 09:49:58 or 2015-08-27 09:49:58.123456. .. _nova_interface-attach: nova interface-attach --------------------- .. code-block:: console usage: nova interface-attach [--port-id ] [--net-id ] [--fixed-ip ] [--tag ] Attach a network interface to a server. **Positional arguments:** ```` Name or ID of server. **Optional arguments:** ``--port-id `` Port ID. ``--net-id `` Network ID ``--fixed-ip `` Requested fixed IP. ``--tag `` Tag for the attached interface. (Supported by API versions '2.49' - '2.latest') .. _nova_interface-detach: nova interface-detach --------------------- .. code-block:: console usage: nova interface-detach Detach a network interface from a server. **Positional arguments:** ```` Name or ID of server. ```` Port ID. .. _nova_interface-list: nova interface-list ------------------- .. code-block:: console usage: nova interface-list List interfaces attached to a server. **Positional arguments:** ```` Name or ID of server. .. _nova_keypair-add: nova keypair-add ---------------- .. code-block:: console usage: nova keypair-add [--pub-key ] [--key-type ] [--user ] Create a new key pair for use with servers. **Positional arguments:** ```` Name of key. **Optional arguments:** ``--pub-key `` Path to a public ssh key. ``--key-type `` Keypair type. Can be ssh or x509. (Supported by API versions '2.2' - '2.latest') ``--user `` ID of user to whom to add key-pair (Admin only). (Supported by API versions '2.10' - '2.latest') .. _nova_keypair-delete: nova keypair-delete ------------------- .. code-block:: console usage: nova keypair-delete [--user ] Delete keypair given by its name. (Supported by API versions '2.0' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] **Positional arguments:** ```` Keypair name to delete. **Optional arguments:** ``--user `` ID of key-pair owner (Admin only). .. _nova_keypair-list: nova keypair-list ----------------- .. code-block:: console usage: nova keypair-list [--user ] [--marker ] [--limit ] Print a list of keypairs for a user (Supported by API versions '2.0' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] **Optional arguments:** ``--user `` List key-pairs of specified user ID (Admin only). ``--marker `` The last keypair of the previous page; displays list of keypairs after "marker". ``--limit `` Maximum number of keypairs to display. If limit is bigger than 'CONF.api.max_limit' option of Nova API, limit 'CONF.api.max_limit' will be used instead. .. _nova_keypair-show: nova keypair-show ----------------- .. code-block:: console usage: nova keypair-show [--user ] Show details about the given keypair. (Supported by API versions '2.0' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] **Positional arguments:** ```` Name of keypair. **Optional arguments:** ``--user `` ID of key-pair owner (Admin only). .. _nova_limits: nova limits ----------- .. code-block:: console usage: nova limits [--tenant []] [--reserved] Print rate and absolute limits. **Optional arguments:** ``--tenant []`` Display information from single tenant (Admin only). ``--reserved`` Include reservations count. .. _nova_list: nova list --------- .. code-block:: console usage: nova list [--reservation-id ] [--ip ] [--ip6 ] [--name ] [--status ] [--flavor ] [--image ] [--host ] [--all-tenants [<0|1>]] [--tenant []] [--user []] [--deleted] [--fields ] [--minimal] [--sort [:]] [--marker ] [--limit ] [--availability-zone ] [--key-name ] [--[no-]config-drive] [--progress ] [--vm-state ] [--task-state ] [--power-state ] [--changes-since ] [--changes-before ] [--tags ] [--tags-any ] [--not-tags ] [--not-tags-any ] [--locked] List servers. Note that from microversion 2.69, during partial infrastructure failures in the deployment, the output of this command may return partial results for the servers present in the failure domain. **Optional arguments:** ``--reservation-id `` Only return servers that match reservation-id. ``--ip `` Search with regular expression match by IP address. ``--ip6 `` Search with regular expression match by IPv6 address. ``--name `` Search with regular expression match by name. ``--status `` Search by server status. ``--flavor `` Search by flavor name or ID. ``--image `` Search by image name or ID. ``--host `` Search servers by hostname to which they are assigned (Admin only). ``--all-tenants [<0|1>]`` Display information from all tenants (Admin only). ``--tenant []`` Display information from single tenant (Admin only). ``--user []`` Display information from single user (Admin only until microversion 2.82). ``--deleted`` Only display deleted servers (Admin only). ``--fields `` Comma-separated list of fields to display. Use the show command to see which fields are available. ``--minimal`` Get only UUID and name. ``--sort [:]`` Comma-separated list of sort keys and directions in the form of [:]. The direction defaults to descending if not specified. ``--marker `` The last server UUID of the previous page; displays list of servers after "marker". ``--limit `` Maximum number of servers to display. If limit == -1, all servers will be displayed. If limit is bigger than 'CONF.api.max_limit' option of Nova API, limit 'CONF.api.max_limit' will be used instead. ``--availability-zone `` Display servers based on their availability zone (Admin only until microversion 2.82). ``--key-name `` Display servers based on their keypair name (Admin only until microversion 2.82). ``--config-drive`` Display servers that have a config drive attached. It is mutually exclusive with '--no-config-drive'. (Admin only until microversion 2.82). ``--no-config-drive`` Display servers that do not have a config drive attached. It is mutually exclusive with '--config-drive'. (Admin only until microversion 2.82). ``--progress `` Display servers based on their progress value (Admin only until microversion 2.82). ``--vm-state `` Display servers based on their vm_state value (Admin only until microversion 2.82). ``--task-state `` Display servers based on their task_state value (Admin only until microversion 2.82). ``--power-state `` Display servers based on their power_state value (Admin only until microversion 2.82). ``--changes-since `` List only servers changed later or equal to a certain point of time. The provided time should be an ISO 8061 formatted time. e.g. 2016-03-04T06:27:59Z . ``--changes-before `` List only servers changed earlier or equal to a certain point of time. The provided time should be an ISO 8061 formatted time. e.g. 2016-03-05T06:27:59Z . (Supported by API versions '2.66' - '2.latest') ``--tags `` The given tags must all be present for a server to be included in the list result. Boolean expression in this case is 't1 AND t2'. Tags must be separated by commas: --tags (Supported by API versions '2.26' - '2.latest') ``--tags-any `` If one of the given tags is present the server will be included in the list result. Boolean expression in this case is 't1 OR t2'. Tags must be separated by commas: --tags-any (Supported by API versions '2.26' - '2.latest') ``--not-tags `` Only the servers that do not have any of the given tags will be included in the list results. Boolean expression in this case is 'NOT(t1 AND t2)'. Tags must be separated by commas: --not-tags (Supported by API versions '2.26' - '2.latest') ``--not-tags-any `` Only the servers that do not have at least one of the given tags will be included in the list result. Boolean expression in this case is 'NOT(t1 OR t2)'. Tags must be separated by commas: --not-tags-any (Supported by API versions '2.26' - '2.latest') ``--locked `` Display servers based on their locked value. A value must be specified; eg. 'true' will list only locked servers and 'false' will list only unlocked servers. (Supported by API versions '2.73' - '2.latest') .. _nova_list-secgroup: nova list-secgroup ------------------ .. code-block:: console usage: nova list-secgroup List Security Group(s) of a server. **Positional arguments:** ```` Name or ID of server. .. _nova_live-migration: nova live-migration ------------------- .. code-block:: console usage: nova live-migration [--block-migrate] [--force] [] Migrate running server to a new machine. **Positional arguments:** ```` Name or ID of server. ```` Destination host name. If no host is specified, the scheduler will choose one. **Optional arguments:** ``--block-migrate`` True in case of block_migration. (Default=auto:live_migration) (Supported by API versions '2.25' - '2.latest') ``--force`` Force a live-migration by not verifying the provided destination host by the scheduler. (Supported by API versions '2.30' - '2.67') .. warning:: This could result in failures to actually live migrate the server to the specified host. It is recommended to either not specify a host so that the scheduler will pick one, or specify a host without ``--force``. .. _nova_live-migration-abort: nova live-migration-abort ------------------------- .. code-block:: console usage: nova live-migration-abort Abort an on-going live migration. (Supported by API versions '2.24' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] For microversions from 2.24 to 2.64 the migration status must be ``running``; for microversion 2.65 and greater, the migration status can also be ``queued`` and ``preparing``. .. versionadded:: 3.3.0 **Positional arguments:** ```` Name or ID of server. ```` ID of migration. .. _nova_live-migration-force-complete: nova live-migration-force-complete ---------------------------------- .. code-block:: console usage: nova live-migration-force-complete Force on-going live migration to complete. (Supported by API versions '2.22' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] .. versionadded:: 3.3.0 **Positional arguments:** ```` Name or ID of server. ```` ID of migration. .. _nova_lock: nova lock --------- .. code-block:: console usage: nova lock [--reason ] Lock a server. A normal (non-admin) user will not be able to execute actions on a locked server. **Positional arguments:** ```` Name or ID of server. **Optional arguments:** ``--reason `` Reason for locking the server. (Supported by API versions '2.73' - '2.latest') .. _nova_meta: nova meta --------- .. code-block:: console usage: nova meta [ ...] Set or delete metadata on a server. **Positional arguments:** ```` Name or ID of server. ```` Actions: 'set' or 'delete'. ```` Metadata to set or delete (only key is necessary on delete). .. _nova_migrate: nova migrate ------------ .. code-block:: console usage: nova migrate [--host ] [--poll] Migrate a server. The new host will be selected by the scheduler. **Positional arguments:** ```` Name or ID of server. **Optional arguments:** ``--host `` Destination host name. (Supported by API versions '2.56' - '2.latest') ``--poll`` Report the server migration progress until it completes. .. _nova_migration-list: nova migration-list ------------------- .. code-block:: console usage: nova migration-list [--instance-uuid ] [--host ] [--status ] [--migration-type ] [--source-compute ] [--marker ] [--limit ] [--changes-since ] [--changes-before ] [--project-id ] [--user-id ] Print a list of migrations. **Examples** To see the list of evacuation operations *from* a compute service host: .. code-block:: console nova migration-list --migration-type evacuation --source-compute host.foo.bar **Optional arguments:** ``--instance-uuid `` Fetch migrations for the given instance. ``--host `` Fetch migrations for the given source or destination host. ``--status `` Fetch migrations for the given status. ``--migration-type `` Filter migrations by type. Valid values are: * evacuation * live-migration * migration .. note:: This is a cold migration. * resize ``--source-compute `` Filter migrations by source compute host name. ``--marker `` The last migration of the previous page; displays list of migrations after "marker". Note that the marker is the migration UUID. (Supported by API versions '2.59' - '2.latest') ``--limit `` Maximum number of migrations to display. Note that there is a configurable max limit on the server, and the limit that is used will be the minimum of what is requested here and what is configured in the server. (Supported by API versions '2.59' - '2.latest') ``--changes-since `` List only migrations changed later or equal to a certain point of time. The provided time should be an ISO 8061 formatted time. e.g. 2016-03-04T06:27:59Z . (Supported by API versions '2.59' - '2.latest') ``--changes-before `` List only migrations changed earlier or equal to a certain point of time. The provided time should be an ISO 8061 formatted time. e.g. 2016-03-04T06:27:59Z . (Supported by API versions '2.66' - '2.latest') ``--project-id `` Filter the migrations by the given project ID. (Supported by API versions '2.80' - '2.latest') ``--user-id `` Filter the migrations by the given user ID. (Supported by API versions '2.80' - '2.latest') .. _nova_pause: nova pause ---------- .. code-block:: console usage: nova pause Pause a server. **Positional arguments:** ```` Name or ID of server. .. _nova_quota-class-show: nova quota-class-show --------------------- .. code-block:: console usage: nova quota-class-show List the quotas for a quota class. **Positional arguments:** ```` Name of quota class to list the quotas for. .. _nova_quota-class-update: nova quota-class-update ----------------------- .. code-block:: console usage: nova quota-class-update [--instances ] [--cores ] [--ram ] [--metadata-items ] [--key-pairs ] [--server-groups ] [--server-group-members ] Update the quotas for a quota class. (Supported by API versions '2.0' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] **Positional arguments:** ```` Name of quota class to set the quotas for. **Optional arguments:** ``--instances `` New value for the "instances" quota. ``--cores `` New value for the "cores" quota. ``--ram `` New value for the "ram" quota. ``--metadata-items `` New value for the "metadata-items" quota. ``--key-pairs `` New value for the "key-pairs" quota. ``--server-groups `` New value for the "server-groups" quota. ``--server-group-members `` New value for the "server-group-members" quota. .. _nova_quota-defaults: nova quota-defaults ------------------- .. code-block:: console usage: nova quota-defaults [--tenant ] List the default quotas for a tenant. **Optional arguments:** ``--tenant `` ID of tenant to list the default quotas for. .. _nova_quota-delete: nova quota-delete ----------------- .. code-block:: console usage: nova quota-delete --tenant [--user ] Delete quota for a tenant/user so their quota will Revert back to default. **Optional arguments:** ``--tenant `` ID of tenant to delete quota for. ``--user `` ID of user to delete quota for. .. _nova_quota-show: nova quota-show --------------- .. code-block:: console usage: nova quota-show [--tenant ] [--user ] [--detail] List the quotas for a tenant/user. **Optional arguments:** ``--tenant `` ID of tenant to list the quotas for. ``--user `` ID of user to list the quotas for. ``--detail`` Show detailed info (limit, reserved, in-use). .. _nova_quota-update: nova quota-update ----------------- .. code-block:: console usage: nova quota-update [--user ] [--instances ] [--cores ] [--ram ] [--metadata-items ] [--key-pairs ] [--server-groups ] [--server-group-members ] [--force] Update the quotas for a tenant/user. (Supported by API versions '2.0' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] **Positional arguments:** ```` ID of tenant to set the quotas for. **Optional arguments:** ``--user `` ID of user to set the quotas for. ``--instances `` New value for the "instances" quota. ``--cores `` New value for the "cores" quota. ``--ram `` New value for the "ram" quota. ``--metadata-items `` New value for the "metadata-items" quota. ``--key-pairs `` New value for the "key-pairs" quota. ``--server-groups `` New value for the "server-groups" quota. ``--server-group-members `` New value for the "server-group-members" quota. ``--force`` Whether force update the quota even if the already used and reserved exceeds the new quota. .. _nova_reboot: nova reboot ----------- .. code-block:: console usage: nova reboot [--hard] [--poll] [ ...] Reboot a server. **Positional arguments:** ```` Name or ID of server(s). **Optional arguments:** ``--hard`` Perform a hard reboot (instead of a soft one). Note: Ironic does not currently support soft reboot; consequently, bare metal nodes will always do a hard reboot, regardless of the use of this option. ``--poll`` Poll until reboot is complete. .. _nova_rebuild: nova rebuild ------------ .. code-block:: console usage: nova rebuild [--rebuild-password ] [--poll] [--minimal] [--preserve-ephemeral] [--name ] [--description ] [--meta ] [--key-name ] [--key-unset] [--user-data ] [--user-data-unset] [--trusted-image-certificate-id ] [--trusted-image-certificates-unset] [--hostname ] Shutdown, re-image, and re-boot a server. **Positional arguments:** ```` Name or ID of server. ```` Name or ID of new image. **Optional arguments:** ``--rebuild-password `` Set the provided admin password on the rebuilt server. ``--poll`` Report the server rebuild progress until it completes. ``--minimal`` Skips flavor/image lookups when showing servers. ``--preserve-ephemeral`` Preserve the default ephemeral storage partition on rebuild. ``--name `` Name for the new server. ``--description `` New description for the server. (Supported by API versions '2.19' - '2.latest') ``--meta `` Record arbitrary key/value metadata to /meta_data.json on the metadata server. Can be specified multiple times. ``--key-name `` Keypair name to set in the server. Cannot be specified with the '--key-unset' option. (Supported by API versions '2.54' - '2.latest') ``--key-unset`` Unset keypair in the server. Cannot be specified with the '--key-name' option. (Supported by API versions '2.54' - '2.latest') ``--user-data `` User data file to pass to be exposed by the metadata server. (Supported by API versions '2.57' - '2.latest') ``--user-data-unset`` Unset user_data in the server. Cannot be specified with the '--user-data' option. (Supported by API versions '2.57' - '2.latest') ``--trusted-image-certificate-id `` Trusted image certificate IDs used to validate certificates during the image signature verification process. Defaults to env[OS_TRUSTED_IMAGE_CERTIFICATE_IDS]. May be specified multiple times to pass multiple trusted image certificate IDs. (Supported by API versions '2.63' - '2.latest') ``--trusted-image-certificates-unset`` Unset trusted_image_certificates in the server. Cannot be specified with the ``--trusted-image-certificate-id`` option. (Supported by API versions '2.63' - '2.latest') ``--hostname `` New hostname for the instance. This only updates the hostname stored in the metadata server: a utility running on the guest is required to propagate these changes to the guest. (Supported by API versions '2.90' - '2.latest') .. _nova_refresh-network: nova refresh-network -------------------- .. code-block:: console usage: nova refresh-network Refresh server network information. **Positional arguments:** ```` Name or ID of a server for which the network cache should be refreshed from neutron (Admin only). .. _nova_remove-secgroup: nova remove-secgroup -------------------- .. code-block:: console usage: nova remove-secgroup Remove a Security Group from a server. **Positional arguments:** ```` Name or ID of server. ```` Name of Security Group. .. _nova_rescue: nova rescue ----------- .. code-block:: console usage: nova rescue [--password ] [--image ] Reboots a server into rescue mode, which starts the machine from either the initial image or a specified image, attaching the current boot disk as secondary. **Positional arguments:** ```` Name or ID of server. **Optional arguments:** ``--password `` The admin password to be set in the rescue environment. ``--image `` The image to rescue with. .. _nova_reset-network: nova reset-network ------------------ .. code-block:: console usage: nova reset-network Reset network of a server. **Positional arguments:** ```` Name or ID of server. .. _nova_reset-state: nova reset-state ---------------- .. code-block:: console usage: nova reset-state [--all-tenants] [--active] [ ...] Reset the state of a server. **Positional arguments:** ```` Name or ID of server(s). **Optional arguments:** ``--all-tenants`` Reset state server(s) in another tenant by name (Admin only). ``--active`` Request the server be reset to "active" state instead of "error" state (the default). .. _nova_resize: nova resize ----------- .. code-block:: console usage: nova resize [--poll] Resize a server. **Positional arguments:** ```` Name or ID of server. ```` Name or ID of new flavor. **Optional arguments:** ``--poll`` Report the server resize progress until it completes. .. _nova_resize-confirm: nova resize-confirm ------------------- .. code-block:: console usage: nova resize-confirm Confirm a previous resize. **Positional arguments:** ```` Name or ID of server. .. _nova_resize-revert: nova resize-revert ------------------ .. code-block:: console usage: nova resize-revert Revert a previous resize (and return to the previous VM). **Positional arguments:** ```` Name or ID of server. .. _nova_restore: nova restore ------------ .. code-block:: console usage: nova restore Restore a soft-deleted server. **Positional arguments:** ```` Name or ID of server. .. _nova_resume: nova resume ----------- .. code-block:: console usage: nova resume Resume a server. **Positional arguments:** ```` Name or ID of server. .. _nova_server-group-create: nova server-group-create ------------------------ .. code-block:: console usage: nova server-group-create [--rules ] Create a new server group with the specified details. **Positional arguments:** ```` Server group name. ```` Policy for the server groups. **Optional arguments:** ``--rule`` Policy rules for the server groups. (Supported by API versions '2.64' - '2.latest'). Currently, only the ``max_server_per_host`` rule is supported for the ``anti-affinity`` policy. The ``max_server_per_host`` rule allows specifying how many members of the anti-affinity group can reside on the same compute host. If not specified, only one member from the same anti-affinity group can reside on a given host. .. _nova_server-group-delete: nova server-group-delete ------------------------ .. code-block:: console usage: nova server-group-delete [ ...] Delete specific server group(s). **Positional arguments:** ```` Unique ID(s) of the server group to delete. .. _nova_server-group-get: nova server-group-get --------------------- .. code-block:: console usage: nova server-group-get Get a specific server group. **Positional arguments:** ```` Unique ID of the server group to get. .. _nova_server-group-list: nova server-group-list ---------------------- .. code-block:: console usage: nova server-group-list [--limit ] [--offset ] [--all-projects] Print a list of all server groups. **Optional arguments:** ``--limit `` Maximum number of server groups to display. If limit is bigger than 'CONF.api.max_limit' option of Nova API, limit 'CONF.api.max_limit' will be used instead. ``--offset `` The offset of groups list to display; use with limit to return a slice of server groups. ``--all-projects`` Display server groups from all projects (Admin only). .. _nova_server-migration-list: nova server-migration-list -------------------------- .. code-block:: console usage: nova server-migration-list Get the migrations list of specified server. (Supported by API versions '2.23' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] .. versionadded:: 3.3.0 **Positional arguments:** ```` Name or ID of server. .. _nova_server-migration-show: nova server-migration-show -------------------------- .. code-block:: console usage: nova server-migration-show Get the migration of specified server. (Supported by API versions '2.23' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] .. versionadded:: 3.3.0 **Positional arguments:** ```` Name or ID of server. ```` ID of migration. .. _nova_server-tag-add: nova server-tag-add ------------------- .. code-block:: console usage: nova server-tag-add [ ...] Add one or more tags to a server. (Supported by API versions '2.26' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] .. versionadded:: 4.1.0 **Positional arguments:** ```` Name or ID of server. ```` Tag(s) to add. .. _nova_server-tag-delete: nova server-tag-delete ---------------------- .. code-block:: console usage: nova server-tag-delete [ ...] Delete one or more tags from a server. (Supported by API versions '2.26' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] .. versionadded:: 4.1.0 **Positional arguments:** ```` Name or ID of server. ```` Tag(s) to delete. .. _nova_server-tag-delete-all: nova server-tag-delete-all -------------------------- .. code-block:: console usage: nova server-tag-delete-all Delete all tags from a server. (Supported by API versions '2.26' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] .. versionadded:: 4.1.0 **Positional arguments:** ```` Name or ID of server. .. _nova_server-tag-list: nova server-tag-list -------------------- .. code-block:: console usage: nova server-tag-list Get list of tags from a server. (Supported by API versions '2.26' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] .. versionadded:: 4.1.0 **Positional arguments:** ```` Name or ID of server. .. _nova_server-tag-set: nova server-tag-set ------------------- .. code-block:: console usage: nova server-tag-set [ ...] Set list of tags to a server. (Supported by API versions '2.26' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] .. versionadded:: 4.1.0 **Positional arguments:** ```` Name or ID of server. ```` Tag(s) to set. .. _nova_server_topology: nova server-topology -------------------- .. code-block:: console usage: nova server-topology Retrieve server NUMA topology information. Host specific fields are only visible to users with the administrative role. (Supported by API versions '2.78' - '2.latest') .. versionadded:: 15.1.0 **Positional arguments:** ```` Name or ID of server. .. _nova_service-delete: nova service-delete ------------------- .. code-block:: console usage: nova service-delete Delete the service. .. important:: If deleting a nova-compute service, be sure to stop the actual ``nova-compute`` process on the physical host *before* deleting the service with this command. Failing to do so can lead to the running service re-creating orphaned **compute_nodes** table records in the database. **Positional arguments:** ```` ID of service as a UUID. (Supported by API versions '2.53' - '2.latest') .. _nova_service-disable: nova service-disable -------------------- .. code-block:: console usage: nova service-disable [--reason ] Disable the service. **Positional arguments:** ```` ID of the service as a UUID. (Supported by API versions '2.53' - '2.latest') **Optional arguments:** ``--reason `` Reason for disabling the service. .. _nova_service-enable: nova service-enable ------------------- .. code-block:: console usage: nova service-enable Enable the service. **Positional arguments:** ```` ID of the service as a UUID. (Supported by API versions '2.53' - '2.latest') .. _nova_service-force-down: nova service-force-down ----------------------- .. code-block:: console usage: nova service-force-down [--unset] Force service to down. (Supported by API versions '2.11' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] .. versionadded:: 2.27.0 **Positional arguments:** ```` ID of the service as a UUID. (Supported by API versions '2.53' - '2.latest') **Optional arguments:** ``--unset`` Unset the forced_down state of the service. .. _nova_service-list: nova service-list ----------------- .. code-block:: console usage: nova service-list [--host ] [--binary ] Show a list of all running services. Filter by host & binary. Note that from microversion 2.69, during partial infrastructure failures in the deployment, the output of this command may return partial results for the services present in the failure domain. **Optional arguments:** ``--host `` Name of host. ``--binary `` Service binary. .. _nova_set-password: nova set-password ----------------- .. code-block:: console usage: nova set-password Change the admin password for a server. **Positional arguments:** ```` Name or ID of server. .. _nova_shelve: nova shelve ----------- .. code-block:: console usage: nova shelve Shelve a server. **Positional arguments:** ```` Name or ID of server. .. _nova_shelve-offload: nova shelve-offload ------------------- .. code-block:: console usage: nova shelve-offload Remove a shelved server from the compute node. **Positional arguments:** ```` Name or ID of server. .. _nova_show: nova show --------- .. code-block:: console usage: nova show [--minimal] [--wrap ] Show details about the given server. Note that from microversion 2.69, during partial infrastructure failures in the deployment, the output of this command may return partial results for the server if it exists in the failure domain. **Positional arguments:** ```` Name or ID of server. **Optional arguments:** ``--minimal`` Skips flavor/image lookups when showing servers. ``--wrap `` Wrap the output to a specified length, or 0 to disable. .. _nova_ssh: nova ssh -------- .. code-block:: console usage: nova ssh [--port PORT] [--address-type ADDRESS_TYPE] [--network ] [--ipv6] [--login ] [-i IDENTITY] [--extra-opts EXTRA] SSH into a server. **Positional arguments:** ```` Name or ID of server. **Optional arguments:** ``--port PORT`` Optional flag to indicate which port to use for ssh. (Default=22) ``--address-type ADDRESS_TYPE`` Optional flag to indicate which IP type to use. Possible values includes fixed and floating (the Default). ``--network `` Network to use for the ssh. ``--ipv6`` Optional flag to indicate whether to use an IPv6 address attached to a server. (Defaults to IPv4 address) ``--login `` Login to use. ``-i IDENTITY, --identity IDENTITY`` Private key file, same as the -i option to the ssh command. ``--extra-opts EXTRA`` Extra options to pass to ssh. see: man ssh. .. _nova_start: nova start ---------- .. code-block:: console usage: nova start [--all-tenants] [ ...] Start the server(s). **Positional arguments:** ```` Name or ID of server(s). **Optional arguments:** ``--all-tenants`` Start server(s) in another tenant by name (Admin only). .. _nova_stop: nova stop --------- .. code-block:: console usage: nova stop [--all-tenants] [ ...] Stop the server(s). **Positional arguments:** ```` Name or ID of server(s). **Optional arguments:** ``--all-tenants`` Stop server(s) in another tenant by name (Admin only). .. _nova_suspend: nova suspend ------------ .. code-block:: console usage: nova suspend Suspend a server. **Positional arguments:** ```` Name or ID of server. .. _nova_trigger-crash-dump: nova trigger-crash-dump ----------------------- .. code-block:: console usage: nova trigger-crash-dump Trigger crash dump in an instance. (Supported by API versions '2.17' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] .. versionadded:: 3.3.0 **Positional arguments:** ```` Name or ID of server. .. _nova_unlock: nova unlock ----------- .. code-block:: console usage: nova unlock Unlock a server. **Positional arguments:** ```` Name or ID of server. .. _nova_unpause: nova unpause ------------ .. code-block:: console usage: nova unpause Unpause a server. **Positional arguments:** ```` Name or ID of server. .. _nova_unrescue: nova unrescue ------------- .. code-block:: console usage: nova unrescue Restart the server from normal boot disk again. **Positional arguments:** ```` Name or ID of server. .. _nova_unshelve: nova unshelve ------------- .. code-block:: console usage: nova unshelve [--availability-zone ] Unshelve a server. **Positional arguments:** ```` Name or ID of server. **Optional arguments:** ``--availability-zone `` Name of the availability zone in which to unshelve a ``SHELVED_OFFLOADED`` server. (Supported by API versions '2.77' - '2.latest') .. _nova_update: nova update ----------- .. code-block:: console usage: nova update [--name ] [--description ] [--hostname ] Update attributes of a server. **Positional arguments:** ```` Name (old name) or ID of server. **Optional arguments:** ``--name `` New name for the server. ``--description `` New description for the server. If it equals to empty string (i.g. ""), the server description will be removed. (Supported by API versions '2.19' - '2.latest') ``--hostname `` New hostname for the instance. This only updates the hostname stored in the metadata server: a utility running on the guest is required to propagate these changes to the guest. (Supported by API versions '2.90' - '2.latest') .. _nova_usage: nova usage ---------- .. code-block:: console usage: nova usage [--start ] [--end ] [--tenant ] Show usage data for a single tenant. **Optional arguments:** ``--start `` Usage range start date ex 2012-01-20. (default: 4 weeks ago) ``--end `` Usage range end date, ex 2012-01-20. (default: tomorrow) ``--tenant `` UUID of tenant to get usage for. .. _nova_usage-list: nova usage-list --------------- .. code-block:: console usage: nova usage-list [--start ] [--end ] List usage data for all tenants. **Optional arguments:** ``--start `` Usage range start date ex 2012-01-20. (default: 4 weeks ago) ``--end `` Usage range end date, ex 2012-01-20. (default: tomorrow) .. _nova_version-list: nova version-list ----------------- .. code-block:: console usage: nova version-list List all API versions. .. _nova_volume-attach: nova volume-attach ------------------ .. code-block:: console usage: nova volume-attach [--delete-on-termination] [--tag ] [] Attach a volume to a server. **Positional arguments:** ```` Name or ID of server. ```` ID of the volume to attach. ```` Name of the device e.g. /dev/vdb. Use "auto" for autoassign (if supported). Libvirt driver will use default device name. **Optional arguments:** ``--tag `` Tag for the attached volume. (Supported by API versions '2.49' - '2.latest') ``--delete-on-termination`` Specify if the attached volume should be deleted when the server is destroyed. By default the attached volume is not deleted when the server is destroyed. (Supported by API versions '2.79' - '2.latest') .. _nova_volume-attachments: nova volume-attachments ----------------------- .. code-block:: console usage: nova volume-attachments List all the volumes attached to a server. **Positional arguments:** ```` Name or ID of server. .. _nova_volume-detach: nova volume-detach ------------------ .. code-block:: console usage: nova volume-detach Detach a volume from a server. **Positional arguments:** ```` Name or ID of server. ```` ID of the volume to detach. .. _nova_volume-update: nova volume-update ------------------ .. code-block:: console usage: nova volume-update [--[no-]delete-on-termination] Update the attachment on the server. Migrates the data from an attached volume to the specified available volume and swaps out the active attachment to the new volume. **Positional arguments:** ```` Name or ID of server. ```` ID of the source (original) volume. ```` ID of the destination volume. **Optional arguments:** ``--delete-on-termination`` Specify that the volume should be deleted when the server is destroyed. It is mutually exclusive with '--no-delete-on-termination'. (Supported by API versions '2.85' - '2.latest') ``--no-delete-on-termination`` Specify that the attached volume should not be deleted when the server is destroyed. It is mutually exclusive with '--delete-on-termination'. (Supported by API versions '2.85' - '2.latest') .. _nova_bash-completion: nova bash-completion -------------------- .. code-block:: console usage: nova bash-completion Prints all of the commands and options to stdout so that the nova.bash_completion script doesn't have to hard code them. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/doc/source/conf.py0000664000175000017500000000522100000000000020657 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # # python-novaclient documentation build configuration file # -- 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 = [ 'openstackdocstheme', 'sphinx.ext.autodoc', 'sphinxcontrib.apidoc', ] # sphinxcontrib.apidoc options apidoc_module_dir = '../../novaclient' apidoc_output_dir = 'reference/api' apidoc_excluded_paths = [ 'tests/*'] apidoc_separate_modules = True # The content that will be inserted into the main body of an autoclass # directive. autoclass_content = 'both' # The master toctree document. master_doc = 'index' copyright = 'OpenStack Contributors' # -- 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 = 'openstackdocs' # Add any paths that contain "extra" files, such as .htaccess or # robots.txt. html_extra_path = ['_extra'] # -- Options for LaTeX output ------------------------------------------------- latex_documents = [ ('index', 'doc-python-novaclient.tex', 'python-novaclient Documentation', 'OpenStack Foundation', 'manual'), ] latex_elements = { 'extraclassoptions': 'openany,oneside', 'preamble': r'\setcounter{tocdepth}{4}', 'makeindex': '', 'printindex': '', } # -- Options for openstackdocstheme ------------------------------------------- openstackdocs_repo_name = 'openstack/python-novaclient' openstackdocs_bug_project = 'python-novaclient' openstackdocs_bug_tag = '' openstackdocs_pdf_link = True openstackdocs_projects = [ 'keystoneauth', 'nova', 'os-client-config', 'python-openstackclient', ] # -- Options for manual page output ------------------------------------------ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('cli/nova', 'nova', 'OpenStack Nova command line client', ['OpenStack Contributors'], 1), ] ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1725030944.7884645 python-novaclient-18.7.0/doc/source/contributor/0000775000175000017500000000000000000000000021732 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/doc/source/contributor/contributing.rst0000664000175000017500000000402500000000000025174 0ustar00zuulzuul00000000000000============================ So You Want to Contribute... ============================ For general information on contributing to OpenStack, please check out the `contributor guide `_ to get started. It covers all the basics that are common to all OpenStack projects: the accounts you need, the basics of interacting with our Gerrit review system, how we communicate as a community, etc. Below will cover the more project specific information you need to get started with python-novaclient. .. important:: The ``nova`` CLI has been deprecated in favour of the unified ``openstack`` CLI. Changes to the Python bindings are still welcome, however, no further changes should be made to the shell. Communication ~~~~~~~~~~~~~ Please refer `how-to-get-involved `_. Contacting the Core Team ~~~~~~~~~~~~~~~~~~~~~~~~ The easiest way to reach the core team is via IRC, using the ``openstack-nova`` OFTC IRC channel. New Feature Planning ~~~~~~~~~~~~~~~~~~~~ If you want to propose a new feature please read the `blueprints `_ page. Task Tracking ~~~~~~~~~~~~~ We track our tasks in `Launchpad `__. If you're looking for some smaller, easier work item to pick up and get started on, search for the 'low-hanging-fruit' tag. Reporting a Bug ~~~~~~~~~~~~~~~ You found an issue and want to make sure we are aware of it? You can do so on `Launchpad `__. More info about Launchpad usage can be found on `OpenStack docs page `_. Getting Your Patch Merged ~~~~~~~~~~~~~~~~~~~~~~~~~ All changes proposed to the python-novaclient requires two ``Code-Review +2`` votes from ``python-novaclient`` core reviewers before one of the core reviewers can approve patch by giving ``Workflow +1`` vote.. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/doc/source/contributor/deprecation-policy.rst0000664000175000017500000000271100000000000026257 0ustar00zuulzuul00000000000000Deprecating commands ==================== There are times when commands need to be deprecated due to rename or removal. The process for command deprecation is: 1. Push up a change for review which deprecates the command(s). - The change should print a deprecation warning to ``stderr`` each time a deprecated command is used. - That warning message should include a rough timeline for when the command will be removed and what should be used instead, if anything. - The description in the help text for the deprecated command should mark that it is deprecated. - The change should include a release note with the ``deprecations`` section filled out. - The deprecation cycle is typically the first client release *after* the next *full* nova server release so that there is at least six months of deprecation. 2. Once the change is approved, have a member of the `nova-release`_ team release a new version of `python-novaclient`. .. _nova-release: https://review.opendev.org/#/admin/groups/147,members 3. Example: ``_ This change was made while the nova 12.0.0 Liberty release was in development. The current version of `python-novaclient` at the time was 2.25.0. Once the change was merged, `python-novaclient` 2.26.0 was released. Since there was less than six months before 12.0.0 would be released, the deprecation cycle ran through the 13.0.0 nova server release. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/doc/source/contributor/index.rst0000664000175000017500000000040000000000000023565 0ustar00zuulzuul00000000000000=================== Contributor Guide =================== Basic Information ================= .. toctree:: :maxdepth: 2 contributing Developer Guide =============== .. toctree:: :maxdepth: 2 microversions testing deprecation-policy ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/doc/source/contributor/microversions.rst0000664000175000017500000000526000000000000025371 0ustar00zuulzuul00000000000000===================================== Adding support for a new microversion ===================================== If a new microversion is added on the nova side, then support must be added on the *python-novaclient* side also. The following procedure describes how to add support for a new microversion in *python-novaclient*. #. Update ``API_MAX_VERSION`` Set ``API_MAX_VERSION`` in ``novaclient/__init__.py`` to the version you are going to support. .. note:: Microversion support should be added one by one in order. For example, microversion 2.74 should be added right after microversion 2.73. Microversion 2.74 should not be added right after microversion 2.72 or earlier. #. Update CLI and Python API Update CLI (``novaclient/v2/shell.py``) and/or Python API (``novaclient/v2/*.py``) to support the microversion. #. Add tests Add unit tests for the change. Add unit tests for the previous microversion to check raising an error or an exception when new arguments or parameters are specified. Add functional tests if necessary. Add the microversion in the ``exclusions`` in the ``test_versions`` method of the ``novaclient.tests.unit.v2.test_shell.ShellTest`` class if there are no versioned wrapped method changes for the microversion. The versioned wrapped methods have ``@api_versions.wraps`` decorators. For example (microversion 2.72 example):: exclusions = set([ (snipped...) 72, # There are no version-wrapped shell method changes for this. ]) #. Update the CLI reference Update the CLI reference (``doc/source/cli/nova.rst``) if the CLI commands and/or arguments are modified. #. Add a release note Add a release note for the change. The release note should include a link to the description for the microversion in the :nova-doc:`Compute API Microversion History `. #. Commit message The description of the blueprint and dependency on the patch in nova side should be added in the commit message. For example:: Implements: blueprint remove-force-flag-from-live-migrate-and-evacuate Depends-On: https://review.opendev.org/#/c/634600/ See the following examples: - `Microversion 2.71 - show server group `_ - `API microversion 2.69: Handles Down Cells `_ - `Microversion 2.68: Remove 'forced' live migrations, evacuations `_ - `Add support changes-before for microversion 2.66 `_ - `Microversion 2.64 - Use new format policy in server group `_ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/doc/source/contributor/testing.rst0000664000175000017500000000161000000000000024137 0ustar00zuulzuul00000000000000========= Testing ========= The preferred way to run the unit tests is using ``tox``. There are multiple test targets that can be run to validate the code. ``tox -e pep8`` Style guidelines enforcement. ``tox -e py38`` Traditional unit testing (Python 3.8). ``tox -e functional`` Live functional testing against an existing OpenStack instance. (Python 3.8) ``tox -e cover`` Generate a coverage report on unit testing. Functional testing assumes the existence of a `clouds.yaml` file as supported by :os-client-config-doc:`os-client-config <>`. It assumes the existence of a cloud named `devstack` that behaves like a normal DevStack installation with a demo and an admin user/tenant - or clouds named `functional_admin` and `functional_nonadmin`. Refer to `Consistent Testing Interface`__ for more details. __ https://governance.openstack.org/tc/reference/project-testing-interface.html ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/doc/source/index.rst0000664000175000017500000000151600000000000021224 0ustar00zuulzuul00000000000000=========================================== Python bindings to the OpenStack Nova API =========================================== This is a client for OpenStack Nova API. There's a :doc:`Python API ` (the :mod:`novaclient` module), and a deprecated :doc:`command-line script ` (installed as :program:`nova`). Each implements the entire OpenStack Nova API. You'll need credentials for an OpenStack cloud that implements the Compute API in order to use the nova client. .. seealso:: You may want to read the `OpenStack Compute API Guide`__ to get an idea of the concepts. By understanding the concepts this library should make more sense. __ https://docs.openstack.org/api-guide/compute/index.html .. toctree:: :maxdepth: 2 user/index cli/index reference/index contributor/index ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1725030944.7884645 python-novaclient-18.7.0/doc/source/reference/0000775000175000017500000000000000000000000021316 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/doc/source/reference/index.rst0000664000175000017500000000011400000000000023153 0ustar00zuulzuul00000000000000========= Reference ========= .. toctree:: :maxdepth: 6 api/modules ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1725030944.7884645 python-novaclient-18.7.0/doc/source/user/0000775000175000017500000000000000000000000020336 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/doc/source/user/index.rst0000664000175000017500000000013400000000000022175 0ustar00zuulzuul00000000000000============ User Guide ============ .. toctree:: :maxdepth: 2 shell python-api ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/doc/source/user/python-api.rst0000664000175000017500000000711700000000000023166 0ustar00zuulzuul00000000000000================================== The :mod:`novaclient` Python API ================================== .. module:: novaclient :synopsis: A client for the OpenStack Nova API. :no-index: .. currentmodule:: novaclient Usage ----- First create a client instance with your credentials:: >>> from novaclient import client >>> nova = client.Client(VERSION, USERNAME, PASSWORD, PROJECT_ID, AUTH_URL) Here ``VERSION`` can be a string or ``novaclient.api_versions.APIVersion`` obj. If you prefer string value, you can use ``1.1`` (deprecated now), ``2`` or ``2.X`` (where X is a microversion). Alternatively, you can create a client instance using the keystoneauth session API:: >>> from keystoneauth1 import loading >>> from keystoneauth1 import session >>> from novaclient import client >>> loader = loading.get_plugin_loader('password') >>> auth = loader.load_from_options(auth_url=AUTH_URL, ... username=USERNAME, ... password=PASSWORD, ... project_id=PROJECT_ID) >>> sess = session.Session(auth=auth) >>> nova = client.Client(VERSION, session=sess) If you have PROJECT_NAME instead of a PROJECT_ID, use the project_name parameter. Similarly, if your cloud uses keystone v3 and you have a DOMAIN_NAME or DOMAIN_ID, provide it as `user_domain_(name|id)` and if you are using a PROJECT_NAME also provide the domain information as `project_domain_(name|id)`. novaclient adds 'python-novaclient' and its version to the user-agent string that keystoneauth produces. If you are creating an application using novaclient and want to register a name and version in the user-agent string, pass those to the Session:: >>> sess = session.Session( ... auth=auth, app_name'nodepool', app_version'1.2.3') If you are making a library that consumes novaclient but is not an end-user application, you can append a (name, version) tuple to the session's `additional_user_agent` property:: >>> sess = session.Session(auth=auth) >>> sess.additional_user_agent.append(('shade', '1.2.3')) For more information on this keystoneauth API, see :keystoneauth-doc:`Using Sessions `. It is also possible to use an instance as a context manager in which case there will be a session kept alive for the duration of the with statement:: >>> from novaclient import client >>> with client.Client(VERSION, USERNAME, PASSWORD, ... PROJECT_ID, AUTH_URL) as nova: ... nova.servers.list() ... nova.flavors.list() ... It is also possible to have a permanent (process-long) connection pool, by passing a connection_pool=True:: >>> from novaclient import client >>> nova = client.Client(VERSION, USERNAME, PASSWORD, PROJECT_ID, ... AUTH_URL, connection_pool=True) Then call methods on its managers:: >>> nova.servers.list() [] >>> nova.flavors.list() [, , , , , , ] >>> fl = nova.flavors.find(ram=512) >>> nova.servers.create("my-server", flavor=fl) .. warning:: Direct initialization of ``novaclient.v2.client.Client`` object can cause you to "shoot yourself in the foot". See launchpad bug-report `1493576`_ for more details. .. _1493576: https://launchpad.net/bugs/1493576 Reference --------- See :doc:`the module reference `. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/doc/source/user/shell.rst0000664000175000017500000000513400000000000022202 0ustar00zuulzuul00000000000000=================================== The :program:`nova` Shell Utility =================================== .. program:: nova .. highlight:: bash The :program:`nova` shell utility interacts with OpenStack Nova API from the command line. It supports the entirety of the OpenStack Nova API. You'll need to provide :program:`nova` with your OpenStack Keystone user information. You can do this with the `--os-username`, `--os-password`, `--os-project-name` (`--os-project-id`), `--os-project-domain-name` (`--os-project-domain-id`) and `--os-user-domain-name` (`--os-user-domain-id`) options, but it's easier to just set them as environment variables by setting some environment variables: .. deprecated:: 17.8.0 The ``nova`` CLI has been deprecated in favour of the unified ``openstack`` CLI. For information on using the ``openstack`` CLI, see :python-openstackclient-doc:`OpenStackClient <>`. .. envvar:: OS_USERNAME Your OpenStack Keystone user name. .. envvar:: OS_PASSWORD Your password. .. envvar:: OS_PROJECT_NAME The name of project for work. .. envvar:: OS_PROJECT_ID The ID of project for work. .. envvar:: OS_PROJECT_DOMAIN_NAME The name of domain containing the project. .. envvar:: OS_PROJECT_DOMAIN_ID The ID of domain containing the project. .. envvar:: OS_USER_DOMAIN_NAME The user's domain name. .. envvar:: OS_USER_DOMAIN_ID The user's domain ID. .. envvar:: OS_AUTH_URL The OpenStack Keystone endpoint URL. .. envvar:: OS_COMPUTE_API_VERSION The OpenStack Nova API version (microversion). .. envvar:: OS_REGION_NAME The Keystone region name. Defaults to the first region if multiple regions are available. .. envvar:: OS_TRUSTED_IMAGE_CERTIFICATE_IDS A comma-delimited list of trusted image certificate IDs. Only used with the ``nova boot`` and ``nova rebuild`` commands starting with the 2.63 microversion. For example:: export OS_TRUSTED_IMAGE_CERTIFICATE_IDS=trusted-cert-id1,trusted-cert-id2 For example, in Bash you'd use:: export OS_USERNAME=yourname export OS_PASSWORD=yadayadayada export OS_PROJECT_NAME=myproject export OS_PROJECT_DOMAIN_NAME=default export OS_USER_DOMAIN_NAME=default export OS_AUTH_URL=http:///identity export OS_COMPUTE_API_VERSION=2.1 From there, all shell commands take the form:: nova [arguments...] Run :program:`nova help` to get a full list of all possible commands, and run :program:`nova help ` to get detailed help for that command. For more information, see :doc:`the command reference `. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1725030944.7884645 python-novaclient-18.7.0/doc/test/0000775000175000017500000000000000000000000017037 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/doc/test/redirect-tests.txt0000664000175000017500000000040100000000000022534 0ustar00zuulzuul00000000000000/python-novaclient/latest/api.html 301 /python-novaclient/latest/reference/api/index.html /python-novaclient/latest/man/nova.html 301 /python-novaclient/latest/cli/nova.html /python-novaclient/latest/shell.html 301 /python-novaclient/latest/user/shell.html ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1725030944.7924647 python-novaclient-18.7.0/novaclient/0000775000175000017500000000000000000000000017455 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/__init__.py0000664000175000017500000000223000000000000021563 0ustar00zuulzuul00000000000000# Copyright 2012 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import pbr.version from novaclient import api_versions __version__ = pbr.version.VersionInfo('python-novaclient').version_string() API_MIN_VERSION = api_versions.APIVersion("2.1") # The max version should be the latest version that is supported in the client, # not necessarily the latest that the server can provide. This is only bumped # when client supported the max version, and bumped sequentially, otherwise # the client may break due to server side new version may include some # backward incompatible change. API_MAX_VERSION = api_versions.APIVersion("2.96") ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/api_versions.py0000664000175000017500000004045600000000000022541 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 functools import logging import os import pkgutil import re import traceback import warnings from oslo_utils import strutils import novaclient from novaclient import exceptions from novaclient.i18n import _ LOG = logging.getLogger(__name__) if not LOG.handlers: LOG.addHandler(logging.StreamHandler()) LEGACY_HEADER_NAME = "X-OpenStack-Nova-API-Version" HEADER_NAME = "OpenStack-API-Version" SERVICE_TYPE = "compute" _SUBSTITUTIONS = {} _type_error_msg = _("'%(other)s' should be an instance of '%(cls)s'") class APIVersion(object): """This class represents an API Version Request. This class provides convenience methods for manipulation and comparison of version numbers that we need to do to implement microversions. """ def __init__(self, version_str=None): """Create an API version object. :param version_str: String representation of APIVersionRequest. Correct format is 'X.Y', where 'X' and 'Y' are int values. None value should be used to create Null APIVersionRequest, which is equal to 0.0 """ self.ver_major = 0 self.ver_minor = 0 if version_str is not None: match = re.match(r"^([1-9]\d*)\.([1-9]\d*|0|latest)$", version_str) if match: self.ver_major = int(match.group(1)) if match.group(2) == "latest": # NOTE(andreykurilin): Infinity allows to easily determine # latest version and doesn't require any additional checks # in comparison methods. self.ver_minor = float("inf") else: self.ver_minor = int(match.group(2)) else: msg = _("Invalid format of client version '%s'. " "Expected format 'X.Y', where X is a major part and Y " "is a minor part of version.") % version_str raise exceptions.UnsupportedVersion(msg) def __str__(self): """Debug/Logging representation of object.""" if self.is_latest(): return "Latest API Version Major: %s" % self.ver_major return ("API Version Major: %s, Minor: %s" % (self.ver_major, self.ver_minor)) def __repr__(self): if self.is_null(): return "" else: return "" % self.get_string() def is_null(self): return self.ver_major == 0 and self.ver_minor == 0 def is_latest(self): return self.ver_minor == float("inf") def __lt__(self, other): if not isinstance(other, APIVersion): raise TypeError(_type_error_msg % {"other": other, "cls": self.__class__}) return ((self.ver_major, self.ver_minor) < (other.ver_major, other.ver_minor)) def __eq__(self, other): if not isinstance(other, APIVersion): raise TypeError(_type_error_msg % {"other": other, "cls": self.__class__}) return ((self.ver_major, self.ver_minor) == (other.ver_major, other.ver_minor)) def __gt__(self, other): if not isinstance(other, APIVersion): raise TypeError(_type_error_msg % {"other": other, "cls": self.__class__}) return ((self.ver_major, self.ver_minor) > (other.ver_major, other.ver_minor)) def __le__(self, other): return self < other or self == other def __ne__(self, other): return not self.__eq__(other) def __ge__(self, other): return self > other or self == other def matches(self, min_version, max_version): """Matches the version object. Returns whether the version object represents a version greater than or equal to the minimum version and less than or equal to the maximum version. :param min_version: Minimum acceptable version. :param max_version: Maximum acceptable version. :returns: boolean If min_version is null then there is no minimum limit. If max_version is null then there is no maximum limit. If self is null then raise ValueError """ if self.is_null(): raise ValueError(_("Null APIVersion doesn't support 'matches'.")) if max_version.is_null() and min_version.is_null(): return True elif max_version.is_null(): return min_version <= self elif min_version.is_null(): return self <= max_version else: return min_version <= self <= max_version def get_string(self): """Version string representation. Converts object to string representation which if used to create an APIVersion object results in the same version. """ if self.is_null(): raise ValueError( _("Null APIVersion cannot be converted to string.")) elif self.is_latest(): return "%s.%s" % (self.ver_major, "latest") return "%s.%s" % (self.ver_major, self.ver_minor) class VersionedMethod(object): def __init__(self, name, start_version, end_version, func): """Versioning information for a single method :param name: Name of the method :param start_version: Minimum acceptable version :param end_version: Maximum acceptable_version :param func: Method to call Minimum and maximums are inclusive """ self.name = name self.start_version = start_version self.end_version = end_version self.func = func def __str__(self): return ("Version Method %s: min: %s, max: %s" % (self.name, self.start_version, self.end_version)) def __repr__(self): return "" % self.name def get_available_major_versions(): # NOTE(andreykurilin): available clients version should not be # hardcoded, so let's discover them. matcher = re.compile(r"v[0-9]*$") submodules = pkgutil.iter_modules([os.path.dirname(__file__)]) available_versions = [name[1:] for loader, name, ispkg in submodules if matcher.search(name)] return available_versions def check_major_version(api_version): """Checks major part of ``APIVersion`` obj is supported. :raises novaclient.exceptions.UnsupportedVersion: if major part is not supported """ available_versions = get_available_major_versions() if (not api_version.is_null() and str(api_version.ver_major) not in available_versions): if len(available_versions) == 1: msg = _("Invalid client version '%(version)s'. " "Major part should be '%(major)s'") % { "version": api_version.get_string(), "major": available_versions[0]} else: msg = _("Invalid client version '%(version)s'. " "Major part must be one of: '%(major)s'") % { "version": api_version.get_string(), "major": ", ".join(available_versions)} raise exceptions.UnsupportedVersion(msg) def get_api_version(version_string): """Returns checked APIVersion object""" version_string = str(version_string) if strutils.is_int_like(version_string): version_string = "%s.0" % version_string api_version = APIVersion(version_string) check_major_version(api_version) return api_version def _get_server_version_range(client): version = client.versions.get_current() if not hasattr(version, 'version') or not version.version: return APIVersion(), APIVersion() return APIVersion(version.min_version), APIVersion(version.version) def discover_version(client, requested_version): """Discover most recent version supported by API and client. Checks ``requested_version`` and returns the most recent version supported by both the API and the client. :param client: client object :param requested_version: requested version represented by APIVersion obj :returns: APIVersion """ server_start_version, server_end_version = _get_server_version_range( client) if (not requested_version.is_latest() and requested_version != APIVersion('2.0')): if server_start_version.is_null() and server_end_version.is_null(): raise exceptions.UnsupportedVersion( _("Server doesn't support microversions")) if not requested_version.matches(server_start_version, server_end_version): raise exceptions.UnsupportedVersion( _("The specified version isn't supported by server. The valid " "version range is '%(min)s' to '%(max)s'") % { "min": server_start_version.get_string(), "max": server_end_version.get_string()}) return requested_version if requested_version == APIVersion('2.0'): if (server_start_version == APIVersion('2.1') or (server_start_version.is_null() and server_end_version.is_null())): return APIVersion('2.0') else: raise exceptions.UnsupportedVersion( _("The server isn't backward compatible with Nova V2 REST " "API")) if server_start_version.is_null() and server_end_version.is_null(): return APIVersion('2.0') elif novaclient.API_MIN_VERSION > server_end_version: raise exceptions.UnsupportedVersion( _("Server version is too old. The client valid version range is " "'%(client_min)s' to '%(client_max)s'. The server valid version " "range is '%(server_min)s' to '%(server_max)s'.") % { 'client_min': novaclient.API_MIN_VERSION.get_string(), 'client_max': novaclient.API_MAX_VERSION.get_string(), 'server_min': server_start_version.get_string(), 'server_max': server_end_version.get_string()}) elif novaclient.API_MAX_VERSION < server_start_version: raise exceptions.UnsupportedVersion( _("Server version is too new. The client valid version range is " "'%(client_min)s' to '%(client_max)s'. The server valid version " "range is '%(server_min)s' to '%(server_max)s'.") % { 'client_min': novaclient.API_MIN_VERSION.get_string(), 'client_max': novaclient.API_MAX_VERSION.get_string(), 'server_min': server_start_version.get_string(), 'server_max': server_end_version.get_string()}) elif novaclient.API_MAX_VERSION <= server_end_version: return novaclient.API_MAX_VERSION elif server_end_version < novaclient.API_MAX_VERSION: return server_end_version def update_headers(headers, api_version): """Set microversion headers if api_version is not null""" if not api_version.is_null(): version_string = api_version.get_string() if api_version.ver_minor != 0: headers[LEGACY_HEADER_NAME] = version_string if api_version.ver_minor >= 27: headers[HEADER_NAME] = '%s %s' % (SERVICE_TYPE, version_string) def check_headers(response, api_version): """Checks that microversion header is in response.""" if api_version.ver_minor > 0: if (api_version.ver_minor < 27 and LEGACY_HEADER_NAME not in response.headers): _warn_missing_microversion_header(LEGACY_HEADER_NAME) elif (api_version.ver_minor >= 27 and HEADER_NAME not in response.headers): _warn_missing_microversion_header(HEADER_NAME) def _add_substitution(versioned_method): _SUBSTITUTIONS.setdefault(versioned_method.name, []) _SUBSTITUTIONS[versioned_method.name].append(versioned_method) def _get_function_name(func): # NOTE(andreykurilin): Based on the facts: # - Python 2 does not have __qualname__ property as Python 3 has; # - we cannot use im_class here, since we need to obtain name of # function in `wraps` decorator during class initialization # ("im_class" property does not exist at that moment) # we need to write own logic to obtain the full function name which # include module name, owner name(optional) and just function name. filename, _lineno, _name, line = traceback.extract_stack()[-4] module, _file_extension = os.path.splitext(filename) module = module.replace("/", ".") if module.endswith(func.__module__): return "%s.[%s].%s" % (func.__module__, line, func.__name__) else: return "%s.%s" % (func.__module__, func.__name__) def get_substitutions(func_name, api_version=None): if hasattr(func_name, "__id__"): func_name = func_name.__id__ substitutions = _SUBSTITUTIONS.get(func_name, []) if api_version and not api_version.is_null(): return [m for m in substitutions if api_version.matches(m.start_version, m.end_version)] return sorted(substitutions, key=lambda m: m.start_version) # FIXME(mriedem): This breaks any ManagerWithFind.list method that has a # 'detailed' kwarg since the ManagerWithFind.findall won't find the correct # argspec from the wrapped list method. def wraps(start_version, end_version=None): start_version = APIVersion(start_version) if end_version: end_version = APIVersion(end_version) else: end_version = APIVersion("%s.latest" % start_version.ver_major) def decor(func): func.versioned = True name = _get_function_name(func) versioned_method = VersionedMethod(name, start_version, end_version, func) _add_substitution(versioned_method) @functools.wraps(func) def substitution(obj, *args, **kwargs): methods = get_substitutions(name, obj.api_version) if not methods: raise exceptions.VersionNotFoundForAPIMethod( obj.api_version.get_string(), name) return methods[-1].func(obj, *args, **kwargs) # Let's share "arguments" with original method and substitution to # allow put utils.arg and wraps decorators in any order if not hasattr(func, 'arguments'): func.arguments = [] substitution.arguments = func.arguments # NOTE(andreykurilin): The way to obtain function's name in Python 2 # bases on traceback(see _get_function_name for details). Since the # right versioned method is used in several places, one object # can have different names. Let's generate name of function one time # and use __id__ property in all other places. substitution.__id__ = name return substitution return decor def _warn_missing_microversion_header(header_name): """Log a warning about missing microversion response header.""" LOG.warning(_( "Your request was processed by a Nova API which does not support " "microversions (%s header is missing from response). " "Warning: Response may be incorrect."), header_name) def deprecated_after(version): decorator = wraps('2.0', version) def wrapper(fn): @functools.wraps(fn) def wrapped(*a, **k): decorated = decorator(fn) if hasattr(fn, '__module__'): mod = fn.__module__ else: mod = a[0].__module__ warnings.warn('The %s module is deprecated ' 'and will be removed.' % mod, DeprecationWarning) return decorated(*a, **k) return wrapped return wrapper ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/base.py0000664000175000017500000004620200000000000020745 0ustar00zuulzuul00000000000000# Copyright 2010 Jacob Kaplan-Moss # Copyright 2011 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Base utilities to build API operation managers and objects on top of. """ import abc import contextlib import copy import hashlib import os import threading from oslo_utils import reflection from oslo_utils import strutils import requests from novaclient import exceptions from novaclient import utils def getid(obj): """Get object's ID or object. Abstracts the common pattern of allowing both an object or an object's ID as a parameter when dealing with relationships. """ return getattr(obj, 'id', obj) # TODO(aababilov): call run_hooks() in HookableMixin's child classes class HookableMixin(object): """Mixin so classes can register and run hooks.""" _hooks_map = {} @classmethod def add_hook(cls, hook_type, hook_func): """Add a new hook of specified type. :param cls: class that registers hooks :param hook_type: hook type, e.g., '__pre_parse_args__' :param hook_func: hook function """ if hook_type not in cls._hooks_map: cls._hooks_map[hook_type] = [] cls._hooks_map[hook_type].append(hook_func) @classmethod def run_hooks(cls, hook_type, *args, **kwargs): """Run all hooks of specified type. :param cls: class that registers hooks :param hook_type: hook type, e.g., '__pre_parse_args__' :param args: args to be passed to every hook function :param kwargs: kwargs to be passed to every hook function """ hook_funcs = cls._hooks_map.get(hook_type) or [] for hook_func in hook_funcs: hook_func(*args, **kwargs) class RequestIdMixin(object): """Wrapper class to expose x-openstack-request-id to the caller. """ def request_ids_setup(self): self.x_openstack_request_ids = [] @property def request_ids(self): return self.x_openstack_request_ids def append_request_ids(self, resp): """Add request_ids as an attribute to the object :param resp: Response object or list of Response objects """ if isinstance(resp, list): # Add list of request_ids if response is of type list. for resp_obj in resp: self._append_request_id(resp_obj) elif resp is not None: # Add request_ids if response contains single object. self._append_request_id(resp) def _append_request_id(self, resp): if isinstance(resp, requests.Response): # Extract 'x-openstack-request-id' from headers if # response is a Response object. request_id = (resp.headers.get('x-openstack-request-id') or resp.headers.get('x-compute-request-id')) else: # If resp is of type string or None. request_id = resp if request_id not in self.x_openstack_request_ids: self.x_openstack_request_ids.append(request_id) class Resource(RequestIdMixin): """Base class for OpenStack resources (tenant, user, etc.). This is pretty much just a bag for attributes. """ HUMAN_ID = False NAME_ATTR = 'name' def __init__(self, manager, info, loaded=False, resp=None): """Populate and bind to a manager. :param manager: BaseManager object :param info: dictionary representing resource attributes :param loaded: prevent lazy-loading if set to True :param resp: Response or list of Response objects """ self.manager = manager self._info = info self._add_details(info) self._loaded = loaded self.request_ids_setup() self.append_request_ids(resp) def __repr__(self): reprkeys = sorted(k for k in self.__dict__.keys() if k[0] != '_' and k not in ['manager', 'x_openstack_request_ids']) info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys) return "<%s %s>" % (self.__class__.__name__, info) @property def api_version(self): return self.manager.api_version @property def human_id(self): """Human-readable ID which can be used for bash completion. """ if self.HUMAN_ID: name = getattr(self, self.NAME_ATTR, None) if name is not None: return strutils.to_slug(name) return None def _add_details(self, info): for (k, v) in info.items(): try: setattr(self, k, v) self._info[k] = v except AttributeError: # In this case we already defined the attribute on the class pass def __getattr__(self, k): if k 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) # The 'request_ids' attribute has been added, # so store the request id to it instead of _info self.append_request_ids(new.request_ids) 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): # Using not of '==' implementation because the not of # __eq__, when it returns NotImplemented, is returning False. return not self == other def is_loaded(self): return self._loaded def set_loaded(self, val): self._loaded = val def set_info(self, key, value): self._info[key] = value def to_dict(self): return copy.deepcopy(self._info) class Manager(HookableMixin): """Manager for API service. Managers interact with a particular type of API (servers, flavors, images, etc.) and provide CRUD operations for them. """ resource_class = None cache_lock = threading.RLock() def __init__(self, api): self.api = api @property def client(self): return self.api.client @property def api_version(self): return self.api.api_version def _list(self, url, response_key, obj_class=None, body=None, filters=None): if filters: url = utils.get_url_with_filter(url, filters) if body: resp, body = self.api.client.post(url, body=body) else: resp, body = self.api.client.get(url) if obj_class is None: obj_class = self.resource_class data = body[response_key] # NOTE(ja): keystone returns values as list as {'values': [ ... ]} # unlike other services which just return the list... if isinstance(data, dict): try: data = data['values'] except KeyError: pass with self.completion_cache('human_id', obj_class, mode="w"): with self.completion_cache('uuid', obj_class, mode="w"): items = [obj_class(self, res, loaded=True) for res in data if res] return ListWithMeta(items, resp) @contextlib.contextmanager def alternate_service_type(self, default, allowed_types=()): original_service_type = self.api.client.service_type if original_service_type in allowed_types: yield else: self.api.client.service_type = default try: yield finally: self.api.client.service_type = original_service_type @contextlib.contextmanager def completion_cache(self, cache_type, obj_class, mode): """The completion cache for bash autocompletion. The completion cache store items that can be used for bash autocompletion, like UUIDs or human-friendly IDs. A resource listing will clear and repopulate the cache. A resource create will append to the cache. Delete is not handled because listings are assumed to be performed often enough to keep the cache reasonably up-to-date. """ # NOTE(wryan): This lock protects read and write access to the # completion caches with self.cache_lock: base_dir = utils.env('NOVACLIENT_UUID_CACHE_DIR', default="~/.novaclient") # NOTE(sirp): Keep separate UUID caches for each username + # endpoint pair username = utils.env('OS_USERNAME', 'NOVA_USERNAME') url = utils.env('OS_URL', 'NOVA_URL') uniqifier = hashlib.sha256(username.encode('utf-8') + url.encode('utf-8')).hexdigest() cache_dir = os.path.expanduser(os.path.join(base_dir, uniqifier)) try: os.makedirs(cache_dir, 0o755) except OSError: # NOTE(kiall): This is typically either permission denied while # attempting to create the directory, or the # directory already exists. Either way, don't # fail. pass resource = obj_class.__name__.lower() filename = "%s-%s-cache" % (resource, cache_type.replace('_', '-')) path = os.path.join(cache_dir, filename) cache_attr = "_%s_cache" % cache_type try: setattr(self, cache_attr, open(path, mode)) except IOError: # NOTE(kiall): This is typically a permission denied while # attempting to write the cache file. pass try: yield finally: cache = getattr(self, cache_attr, None) if cache: cache.close() delattr(self, cache_attr) def write_to_completion_cache(self, cache_type, val): cache = getattr(self, "_%s_cache" % cache_type, None) if cache: cache.write("%s\n" % val) def _get(self, url, response_key, filters=None): if filters: url = utils.get_url_with_filter(url, filters) resp, body = self.api.client.get(url) if response_key is not None: content = body[response_key] else: content = body return self.resource_class(self, content, loaded=True, resp=resp) def _create(self, url, body, response_key, return_raw=False, obj_class=None, **kwargs): self.run_hooks('modify_body_for_create', body, **kwargs) resp, body = self.api.client.post(url, body=body) if return_raw: return self.convert_into_with_meta(body[response_key], resp) if obj_class is None: obj_class = self.resource_class with self.completion_cache('human_id', obj_class, mode="a"): with self.completion_cache('uuid', obj_class, mode="a"): return obj_class(self, body[response_key], resp=resp) def _delete(self, url): resp, body = self.api.client.delete(url) return self.convert_into_with_meta(body, resp) def _update(self, url, body, response_key=None, **kwargs): self.run_hooks('modify_body_for_update', body, **kwargs) resp, body = self.api.client.put(url, body=body) if body: if response_key: return self.resource_class(self, body[response_key], resp=resp) else: return self.resource_class(self, body, resp=resp) else: return StrWithMeta(body, resp) def convert_into_with_meta(self, item, resp): if isinstance(item, str): return StrWithMeta(item, resp) elif isinstance(item, bytes): return BytesWithMeta(item, resp) elif isinstance(item, list): return ListWithMeta(item, resp) elif isinstance(item, tuple): return TupleWithMeta(item, resp) elif item is None: return TupleWithMeta((), resp) else: return DictWithMeta(item, resp) class ManagerWithFind(Manager, metaclass=abc.ABCMeta): """Like a `Manager`, but with additional `find()`/`findall()` methods.""" @abc.abstractmethod def list(self): pass def find(self, **kwargs): """Find a single item with attributes matching ``**kwargs``.""" matches = self.findall(**kwargs) num_matches = len(matches) if num_matches == 0: msg = "No %s matching %s." % (self.resource_class.__name__, kwargs) raise exceptions.NotFound(404, msg) elif num_matches > 1: raise exceptions.NoUniqueMatch else: matches[0].append_request_ids(matches.request_ids) return matches[0] def findall(self, **kwargs): """Find all items with attributes matching ``**kwargs``.""" found = ListWithMeta([], None) searches = kwargs.items() detailed = True list_kwargs = {} list_argspec = reflection.get_callable_args(self.list) if 'detailed' in list_argspec: detailed = ("human_id" not in kwargs and "name" not in kwargs and "display_name" not in kwargs) list_kwargs['detailed'] = detailed if 'is_public' in list_argspec and 'is_public' in kwargs: is_public = kwargs['is_public'] list_kwargs['is_public'] = is_public if is_public is None: tmp_kwargs = kwargs.copy() del tmp_kwargs['is_public'] searches = tmp_kwargs.items() if 'search_opts' in list_argspec: # pass search_opts in to do server side based filtering. # TODO(jogo) not all search_opts support regex, find way to # identify when to use regex and when to use string matching. # volumes does not support regex while servers does. So when # doing findall on servers some client side filtering is still # needed. if "human_id" in kwargs: list_kwargs['search_opts'] = {"name": kwargs["human_id"]} elif "name" in kwargs: list_kwargs['search_opts'] = {"name": kwargs["name"]} elif "display_name" in kwargs: list_kwargs['search_opts'] = {"name": kwargs["display_name"]} if "all_tenants" in kwargs: all_tenants = kwargs['all_tenants'] list_kwargs['search_opts']['all_tenants'] = all_tenants searches = [(k, v) for k, v in searches if k != 'all_tenants'] if "deleted" in kwargs: deleted = kwargs['deleted'] list_kwargs['search_opts']['deleted'] = deleted searches = [(k, v) for k, v in searches if k != 'deleted'] listing = self.list(**list_kwargs) found.append_request_ids(listing.request_ids) for obj in listing: try: if all(getattr(obj, attr) == value for (attr, value) in searches): if detailed: found.append(obj) else: detail = self.get(obj.id) found.append(detail) found.append_request_ids(detail.request_ids) except AttributeError: continue return found class BootingManagerWithFind(ManagerWithFind): """Like a `ManagerWithFind`, but has the ability to boot servers.""" def _parse_block_device_mapping(self, block_device_mapping): """Parses legacy block device mapping.""" # FIXME(andreykurilin): make it work with block device mapping v2 bdm = [] for device_name, mapping in block_device_mapping.items(): # # The mapping is in the format: # :[]:[]:[] # bdm_dict = {'device_name': device_name} mapping_parts = mapping.split(':') source_id = mapping_parts[0] if len(mapping_parts) == 1: bdm_dict['volume_id'] = source_id elif len(mapping_parts) > 1: source_type = mapping_parts[1] if source_type.startswith('snap'): bdm_dict['snapshot_id'] = source_id else: bdm_dict['volume_id'] = source_id if len(mapping_parts) > 2 and mapping_parts[2]: bdm_dict['volume_size'] = str(int(mapping_parts[2])) if len(mapping_parts) > 3: bdm_dict['delete_on_termination'] = mapping_parts[3] bdm.append(bdm_dict) return bdm class ListWithMeta(list, RequestIdMixin): def __init__(self, values, resp): super(ListWithMeta, self).__init__(values) self.request_ids_setup() self.append_request_ids(resp) class DictWithMeta(dict, RequestIdMixin): def __init__(self, values, resp): super(DictWithMeta, self).__init__(values) self.request_ids_setup() self.append_request_ids(resp) class TupleWithMeta(tuple, RequestIdMixin): def __new__(cls, values, resp): return super(TupleWithMeta, cls).__new__(cls, values) def __init__(self, values, resp): self.request_ids_setup() self.append_request_ids(resp) class StrWithMeta(str, RequestIdMixin): def __new__(cls, value, resp): return super(StrWithMeta, cls).__new__(cls, value) def __init__(self, values, resp): self.request_ids_setup() self.append_request_ids(resp) class BytesWithMeta(bytes, RequestIdMixin): def __new__(cls, value, resp): return super(BytesWithMeta, cls).__new__(cls, value) def __init__(self, values, resp): self.request_ids_setup() self.append_request_ids(resp) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/client.py0000664000175000017500000003211300000000000021305 0ustar00zuulzuul00000000000000# Copyright 2010 Jacob Kaplan-Moss # Copyright 2011 OpenStack Foundation # Copyright 2011 Piston Cloud Computing, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ OpenStack Client interface. Handles the REST calls and responses. """ import itertools import pkgutil import warnings from keystoneauth1 import adapter from keystoneauth1 import identity from keystoneauth1 import session as ksession from oslo_utils import importutils import stevedore import novaclient from novaclient import api_versions from novaclient import exceptions from novaclient import extension as ext from novaclient.i18n import _ from novaclient import utils osprofiler_profiler = importutils.try_import("osprofiler.profiler") osprofiler_web = importutils.try_import("osprofiler.web") class SessionClient(adapter.LegacyJsonAdapter): client_name = 'python-novaclient' client_version = novaclient.__version__ def __init__(self, *args, **kwargs): self.times = [] self.timings = kwargs.pop('timings', False) self.api_version = kwargs.pop('api_version', None) self.api_version = self.api_version or api_versions.APIVersion() super(SessionClient, self).__init__(*args, **kwargs) def request(self, url, method, **kwargs): kwargs.setdefault('headers', kwargs.get('headers', {})) api_versions.update_headers(kwargs["headers"], self.api_version) # NOTE(dbelova): 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()) # NOTE(jamielennox): The standard call raises errors from # keystoneauth1, where we need to raise the novaclient errors. raise_exc = kwargs.pop('raise_exc', True) with utils.record_time(self.times, self.timings, method, url): resp, body = super(SessionClient, self).request(url, method, raise_exc=False, **kwargs) # TODO(andreykurilin): uncomment this line, when we will be able to # check only nova-related calls # api_versions.check_headers(resp, self.api_version) if raise_exc and resp.status_code >= 400: raise exceptions.from_response(resp, body, url, method) return resp, body def get_timings(self): return self.times def reset_timings(self): self.times = [] def _construct_http_client(api_version=None, auth=None, auth_token=None, auth_url=None, cacert=None, cert=None, endpoint_override=None, endpoint_type='publicURL', http_log_debug=False, insecure=False, logger=None, os_cache=False, password=None, project_domain_id=None, project_domain_name=None, project_id=None, project_name=None, region_name=None, service_name=None, service_type='compute', session=None, timeout=None, timings=False, user_agent='python-novaclient', user_domain_id=None, user_domain_name=None, user_id=None, username=None, **kwargs): if not session: if not auth and auth_token: auth = identity.Token(auth_url=auth_url, token=auth_token, project_id=project_id, project_name=project_name, project_domain_id=project_domain_id, project_domain_name=project_domain_name) elif not auth: auth = identity.Password(username=username, user_id=user_id, password=password, project_id=project_id, project_name=project_name, auth_url=auth_url, project_domain_id=project_domain_id, project_domain_name=project_domain_name, user_domain_id=user_domain_id, user_domain_name=user_domain_name) session = ksession.Session(auth=auth, verify=(cacert or not insecure), timeout=timeout, cert=cert, user_agent=user_agent) return SessionClient(api_version=api_version, auth=auth, endpoint_override=endpoint_override, interface=endpoint_type, logger=logger, region_name=region_name, service_name=service_name, service_type=service_type, session=session, timings=timings, user_agent=user_agent, **kwargs) def discover_extensions(*args, **kwargs): """Returns the list of extensions, which can be discovered by python path and by entry-point 'novaclient.extension'. """ chain = itertools.chain(_discover_via_python_path(), _discover_via_entry_points()) return [ext.Extension(name, module) for name, module in chain] def _discover_via_python_path(): for (module_loader, name, _ispkg) in pkgutil.iter_modules(): if name.endswith('_python_novaclient_ext'): # NOTE(sdague): needed for python 2.x compatibility. if not hasattr(module_loader, 'load_module'): module_loader = module_loader.find_module(name) module = module_loader.load_module(name) if hasattr(module, 'extension_name'): name = module.extension_name yield name, module def _make_discovery_manager(): # This function provides a place to mock out the entry point scan return stevedore.ExtensionManager('novaclient.extension') def _discover_via_entry_points(): mgr = _make_discovery_manager() for extension in mgr: yield extension.name, extension.plugin def _get_client_class_and_version(version): if not isinstance(version, api_versions.APIVersion): version = api_versions.get_api_version(version) else: api_versions.check_major_version(version) if version.is_latest(): raise exceptions.UnsupportedVersion( _("The version should be explicit, not latest.")) return version, importutils.import_class( "novaclient.v%s.client.Client" % version.ver_major) def _check_arguments(kwargs, release, deprecated_name, right_name=None): """Process deprecation of arguments. Checks presence of deprecated argument in kwargs, prints proper warning message, renames key to right one it needed. """ if deprecated_name in kwargs: if right_name: if right_name in kwargs: msg = _("The '%(old)s' argument is deprecated in " "%(release)s and its use may result in errors " "in future releases. As '%(new)s' is provided, " "the '%(old)s' argument will be ignored.") % { "old": deprecated_name, "release": release, "new": right_name} kwargs.pop(deprecated_name) else: msg = _("The '%(old)s' argument is deprecated in " "%(release)s and its use may result in errors in " "future releases. Use '%(right)s' instead.") % { "old": deprecated_name, "release": release, "right": right_name} kwargs[right_name] = kwargs.pop(deprecated_name) else: msg = _("The '%(old)s' argument is deprecated in %(release)s " "and its use may result in errors in future " "releases.") % { "old": deprecated_name, "release": release} # just ignore it kwargs.pop(deprecated_name) warnings.warn(msg) def Client(version, username=None, password=None, project_id=None, auth_url=None, **kwargs): """Initialize client object based on given version. HOW-TO: The simplest way to create a client instance is initialization with your credentials:: >>> from novaclient import client >>> nova = client.Client(VERSION, USERNAME, PASSWORD, ... PROJECT_ID, AUTH_URL) Here ``VERSION`` can be a string or ``novaclient.api_versions.APIVersion`` obj. If you prefer string value, you can use ``1.1`` (deprecated now), ``2`` or ``2.X`` (where X is a microversion). Alternatively, you can create a client instance using the keystoneauth session API. See "The novaclient Python API" page at python-novaclient's doc. """ if password: kwargs["password"] = password if project_id: kwargs["project_id"] = project_id _check_arguments(kwargs, "Ocata", "auth_plugin") _check_arguments(kwargs, "Ocata", "auth_system") if "no_cache" in kwargs: _check_arguments(kwargs, "Ocata", "no_cache", right_name="os_cache") # os_cache is not a fully compatible with no_cache, so we need to # apply this custom processing kwargs["os_cache"] = not kwargs["os_cache"] _check_arguments(kwargs, "Ocata", "bypass_url", right_name="endpoint_override") _check_arguments(kwargs, "Ocata", "api_key", right_name="password") # NOTE(andreykurilin): OpenStack projects use two variables with one # meaning: 'endpoint_type' and 'interface'. 'endpoint_type' is an old # name which was used by most OpenStack clients. Later it was replaced by # 'interface' in keystone and later some other clients switched to new # variable name too. In case of novaclient, there is no need to switch to # 'interface' variable name due too several reasons: # - novaclient uses 'endpoint_type' variable name long time ago and # there is no real reasons to switch to new name; # - 'interface' argument is used in several shell subcommands # (for example in `nova floating-ip-bulk-create`), so we will need to # modify these subcommands to not conflict with global flag # 'interface' # Actually, novaclient did not accept 'interface' before, but since we # allow additional arguments(kwargs), someone can use this variable name # and face issue about unexpected behavior. _check_arguments(kwargs, "Ocata", "interface", right_name="endpoint_type") _check_arguments(kwargs, "Ocata", "tenant_name", right_name="project_name") _check_arguments(kwargs, "Ocata", "tenant_id", right_name="project_id") _check_arguments(kwargs, "Ocata", "proxy_tenant_id") _check_arguments(kwargs, "Ocata", "proxy_token") _check_arguments(kwargs, "Ocata", "connection_pool") _check_arguments(kwargs, "Ocata", "volume_service_name") api_version, client_class = _get_client_class_and_version(version) kwargs.pop("direct_use", None) profile = kwargs.pop("profile", None) if osprofiler_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 # nova-api nova.conf, otherwise the latter will fail to check the # request signature and will skip initialization of osprofiler on # the server side. osprofiler_profiler.init(profile) return client_class(api_version=api_version, auth_url=auth_url, direct_use=False, username=username, **kwargs) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/crypto.py0000664000175000017500000000252000000000000021346 0ustar00zuulzuul00000000000000# Copyright 2013 Nebula, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import base64 import subprocess class DecryptionFailure(Exception): pass def decrypt_password(private_key, password): """Base64 decodes password and unencrypts it with private key. Requires openssl binary available in the path. """ unencoded = base64.b64decode(password) cmd = ['openssl', 'rsautl', '-decrypt', '-inkey', private_key] proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = proc.communicate(unencoded) proc.stdin.close() if proc.returncode: raise DecryptionFailure(err) if isinstance(out, bytes): return out.decode('utf-8') return out ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/exceptions.py0000664000175000017500000001776600000000000022231 0ustar00zuulzuul00000000000000# Copyright 2010 Jacob Kaplan-Moss # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Exception definitions. """ class UnsupportedVersion(Exception): """Indicates that the user is trying to use an unsupported version of the API. """ pass class UnsupportedConsoleType(Exception): """Indicates that the user is trying to use an unsupported console type when retrieving console urls of servers. """ def __init__(self, console_type): self.message = 'Unsupported console_type "%s"' % console_type class UnsupportedAttribute(AttributeError): """Indicates that the user is trying to transmit the argument to a method, which is not supported by selected version. """ def __init__(self, argument_name, start_version, end_version=None): if end_version: self.message = ( "'%(name)s' argument is only allowed for microversions " "%(start)s - %(end)s." % {"name": argument_name, "start": start_version, "end": end_version}) else: self.message = ( "'%(name)s' argument is only allowed since microversion " "%(start)s." % {"name": argument_name, "start": start_version}) super(UnsupportedAttribute, self).__init__(self.message) class CommandError(Exception): pass class NoUniqueMatch(Exception): pass class ResourceInErrorState(Exception): """Resource is in the error state.""" def __init__(self, obj): msg = "`%s` resource is in the error state" % obj.__class__.__name__ fault_msg = getattr(obj, "fault", {}).get("message") if fault_msg: msg += "due to '%s'" % fault_msg self.message = "%s." % msg class VersionNotFoundForAPIMethod(Exception): msg_fmt = "API version '%(vers)s' is not supported on '%(method)s' method." def __init__(self, version, method): self.version = version self.method = method def __str__(self): return self.msg_fmt % {"vers": self.version, "method": self.method} class InstanceInDeletedState(Exception): """Instance is in the deleted state.""" pass class ClientException(Exception): """ The base exception class for all exceptions this library raises. """ message = 'Unknown Error' def __init__(self, code, message=None, details=None, request_id=None, url=None, method=None): self.code = code self.message = message or self.__class__.message self.details = details self.request_id = request_id self.url = url self.method = method def __str__(self): formatted_string = "%s (HTTP %s)" % (self.message, self.code) if self.request_id: formatted_string += " (Request-ID: %s)" % self.request_id return formatted_string class RetryAfterException(ClientException): """ The base exception class for ClientExceptions that use Retry-After header. """ def __init__(self, *args, **kwargs): try: self.retry_after = int(kwargs.pop('retry_after')) except (KeyError, ValueError): self.retry_after = 0 super(RetryAfterException, self).__init__(*args, **kwargs) class BadRequest(ClientException): """ HTTP 400 - Bad request: you sent some malformed data. """ http_status = 400 message = "Bad request" class Unauthorized(ClientException): """ HTTP 401 - Unauthorized: bad credentials. """ http_status = 401 message = "Unauthorized" class Forbidden(ClientException): """ HTTP 403 - Forbidden: your credentials don't give you access to this resource. """ http_status = 403 message = "Forbidden" class NotFound(ClientException): """ HTTP 404 - Not found """ http_status = 404 message = "Not found" class MethodNotAllowed(ClientException): """ HTTP 405 - Method Not Allowed """ http_status = 405 message = "Method Not Allowed" class NotAcceptable(ClientException): """ HTTP 406 - Not Acceptable """ http_status = 406 message = "Not Acceptable" class Conflict(ClientException): """ HTTP 409 - Conflict """ http_status = 409 message = "Conflict" class OverLimit(RetryAfterException): """ HTTP 413 - Over limit: you're over the API limits for this time period. """ http_status = 413 message = "Over limit" class RateLimit(RetryAfterException): """ HTTP 429 - Rate limit: you've sent too many requests for this time period. """ http_status = 429 message = "Rate limit" # NotImplemented is a python keyword. class HTTPNotImplemented(ClientException): """ HTTP 501 - Not Implemented: the server does not support this operation. """ http_status = 501 message = "Not Implemented" # In Python 2.4 Exception is old-style and thus doesn't have a __subclasses__() # so we can do this: # _code_map = dict((c.http_status, c) # for c in ClientException.__subclasses__()) # # Instead, we have to hardcode it: _error_classes = [BadRequest, Unauthorized, Forbidden, NotFound, MethodNotAllowed, NotAcceptable, Conflict, OverLimit, RateLimit, HTTPNotImplemented] _code_map = dict((c.http_status, c) for c in _error_classes) class InvalidUsage(RuntimeError): """This function call is invalid in the way you are using this client. Due to the transition to using keystoneauth some function calls are no longer available. You should make a similar call to the session object instead. """ pass def from_response(response, body, url, method=None): """ Return an instance of an ClientException or subclass based on a requests response. Usage:: resp, body = requests.request(...) if resp.status_code != 200: raise exception_from_response(resp, rest.text) """ cls = _code_map.get(response.status_code, ClientException) kwargs = { 'code': response.status_code, 'method': method, 'url': url, 'request_id': None, } if response.headers: kwargs['request_id'] = response.headers.get('x-compute-request-id') if (issubclass(cls, RetryAfterException) and 'retry-after' in response.headers): kwargs['retry_after'] = response.headers.get('retry-after') if body: message = "n/a" details = "n/a" if hasattr(body, 'keys'): # NOTE(mriedem): WebOb<1.6.0 will return a nested dict structure # where the error keys to the message/details/code. WebOb>=1.6.0 # returns just a response body as a single dict, not nested, # so we have to handle both cases (since we can't trust what we're # given with content_type: application/json either way. if 'message' in body: # WebOb 1.6.0 case message = body.get('message') details = body.get('details') else: # WebOb<1.6.0 where we assume there is a single error message # key to the body that has the message and details. error = body[list(body)[0]] message = error.get('message') details = error.get('details') kwargs['message'] = message kwargs['details'] = details return cls(**kwargs) class ResourceNotFound(Exception): """Error in getting the resource.""" pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/extension.py0000664000175000017500000000256200000000000022050 0ustar00zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from novaclient import base from novaclient import utils class Extension(base.HookableMixin): """Extension descriptor.""" SUPPORTED_HOOKS = ('__pre_parse_args__', '__post_parse_args__') def __init__(self, name, module): self.name = name self.module = module self._parse_extension_module() def _parse_extension_module(self): self.manager_class = None for attr_name, attr_value in self.module.__dict__.items(): if attr_name in self.SUPPORTED_HOOKS: self.add_hook(attr_name, attr_value) elif utils.safe_issubclass(attr_value, base.Manager): self.manager_class = attr_value def __repr__(self): return "" % self.name ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/i18n.py0000664000175000017500000000151100000000000020604 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 novaclient. See https://docs.openstack.org/oslo.i18n/latest/user/usage.html . """ import oslo_i18n _translators = oslo_i18n.TranslatorFactory(domain='novaclient') # The primary translation function using the well-known name "_" _ = _translators.primary ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/shell.py0000664000175000017500000010516400000000000021145 0ustar00zuulzuul00000000000000# Copyright 2010 Jacob Kaplan-Moss # Copyright 2011 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Command-line interface to the OpenStack Nova API. """ import argparse import logging import os import sys from keystoneauth1 import loading from oslo_utils import encodeutils from oslo_utils import importutils from oslo_utils import strutils import novaclient from novaclient import api_versions from novaclient import client from novaclient import exceptions as exc import novaclient.extension from novaclient.i18n import _ from novaclient import utils osprofiler_profiler = importutils.try_import("osprofiler.profiler") DEFAULT_MAJOR_OS_COMPUTE_API_VERSION = "2.0" # The default behaviour of nova client CLI is that CLI negotiates with server # to find out the most recent version between client and server, and # '2.latest' means to that. This value never be changed until we decided to # change the default behaviour of nova client CLI. DEFAULT_OS_COMPUTE_API_VERSION = '2.latest' DEFAULT_NOVA_ENDPOINT_TYPE = 'publicURL' DEFAULT_NOVA_SERVICE_TYPE = "compute" HINT_HELP_MSG = (" [hint: use '--os-compute-api-version' flag to show help " "message for proper version]") logger = logging.getLogger(__name__) class DeprecatedAction(argparse.Action): """An argparse action for deprecated options. This class is an ``argparse.Action`` subclass that allows command line options to be explicitly deprecated. It modifies the help text for the option to indicate that it's deprecated (unless help has been suppressed using ``argparse.SUPPRESS``), and provides a means to specify an alternate option to use using the ``use`` keyword argument to ``argparse.ArgumentParser.add_argument()``. The original action may be specified with the ``real_action`` keyword argument, which has the same interpretation as the ``action`` argument to ``argparse.ArgumentParser.add_argument()``, with the addition of the special "nothing" action which completely ignores the option (other than emitting the deprecation warning). Note that the deprecation warning is only emitted once per specific option string. Note: If the ``real_action`` keyword argument specifies an unknown action, no warning will be emitted unless the action is used, due to limitations with the method used to resolve the action names. """ def __init__(self, option_strings, dest, help=None, real_action=None, use=None, **kwargs): """Initialize a ``DeprecatedAction`` instance. :param option_strings: The recognized option strings. :param dest: The attribute that will be set. :param help: Help text. This will be updated to indicate the deprecation, and if ``use`` is provided, that text will be included as well. :param real_action: The actual action to invoke. This is interpreted the same way as the ``action`` parameter. :param use: Text explaining which option to use instead. """ # Update the help text if not help: if use: help = _('Deprecated; %(use)s') % {'use': use} else: help = _('Deprecated') elif help != argparse.SUPPRESS: if use: help = _('%(help)s (Deprecated; %(use)s)') % { 'help': help, 'use': use, } else: help = _('%(help)s (Deprecated)') % {'help': help} # Initialize ourself appropriately super(DeprecatedAction, self).__init__( option_strings, dest, help=help, **kwargs) # 'emitted' tracks which warnings we've emitted self.emitted = set() self.use = use # Select the appropriate action if real_action == 'nothing': # NOTE(Vek): "nothing" is distinct from a real_action=None # argument. When real_action=None, the argparse default # action of "store" is used; when real_action='nothing', # however, we explicitly inhibit doing anything with the # option self.real_action_args = False self.real_action = None elif real_action is None or isinstance(real_action, str): # Specified by string (or None); we have to have a parser # to look up the actual action, so defer to later self.real_action_args = (option_strings, dest, help, kwargs) self.real_action = real_action else: self.real_action_args = False self.real_action = real_action( option_strings, dest, help=help, **kwargs) def _get_action(self, parser): """Retrieve the action callable. This internal method is used to retrieve the callable implementing the action. If ``real_action`` was specified as ``None`` or one of the standard string names, an internal method of the ``argparse.ArgumentParser`` instance is used to resolve it into an actual action class, which is then instantiated. This is cached, in case the action is called multiple times. :param parser: The ``argparse.ArgumentParser`` instance. :returns: The action callable. """ # If a lookup is needed, look up the action in the parser if self.real_action_args is not False: option_strings, dest, help, kwargs = self.real_action_args action_class = parser._registry_get('action', self.real_action) # Did we find the action class? if action_class is None: print(_('WARNING: Programming error: Unknown real action ' '"%s"') % self.real_action, file=sys.stderr) self.real_action = None else: # OK, instantiate the action class self.real_action = action_class( option_strings, dest, help=help, **kwargs) # It's been resolved, no further need to look it up self.real_action_args = False return self.real_action def __call__(self, parser, namespace, values, option_string): """Implement the action. Emits the deprecation warning message (only once for any given option string), then calls the real action (if any). :param parser: The ``argparse.ArgumentParser`` instance. :param namespace: The ``argparse.Namespace`` object which should have an attribute set. :param values: Any arguments provided to the option. :param option_string: The option string that was used. """ action = self._get_action(parser) # Only emit the deprecation warning once per option if option_string not in self.emitted: if self.use: print(_('WARNING: Option "%(option)s" is deprecated; ' '%(use)s') % { 'option': option_string, 'use': self.use, }, file=sys.stderr) else: print(_('WARNING: Option "%(option)s" is deprecated') % {'option': option_string}, file=sys.stderr) self.emitted.add(option_string) if action: action(parser, namespace, values, option_string) class NovaClientArgumentParser(argparse.ArgumentParser): def __init__(self, *args, **kwargs): super(NovaClientArgumentParser, self).__init__(*args, **kwargs) def error(self, message): """error(message: string) Prints a usage message incorporating the message to stderr and exits. """ self.print_usage(sys.stderr) # FIXME(lzyeval): if changes occur in argparse.ArgParser._check_value choose_from = ' (choose from' progparts = self.prog.partition(' ') self.exit(2, _("error: %(errmsg)s\nTry '%(mainp)s help %(subp)s'" " for more information.\n") % {'errmsg': message.split(choose_from)[0], 'mainp': progparts[0], 'subp': progparts[2]}) def _get_option_tuples(self, option_string): """returns (action, option, value) candidates for an option prefix Returns [first candidate] if all candidates refers to current and deprecated forms of the same options: "nova boot ... --key KEY" parsing succeed because --key could only match --key-name, --key_name which are current/deprecated forms of the same option. """ option_tuples = (super(NovaClientArgumentParser, self) ._get_option_tuples(option_string)) if len(option_tuples) > 1: # In Python < 3.12, this is a 3-part tuple: # action, option_string, explicit_arg # In Python >= 3.12, this is a 4 part tuple: # action, option_string, sep, explicit_arg normalizeds = [opt[1].replace('_', '-') for opt in option_tuples] if len(set(normalizeds)) == 1: return option_tuples[:1] return option_tuples class OpenStackComputeShell(object): times = [] def __init__(self): self.client_logger = None def _append_global_identity_args(self, parser, argv): # Register the CLI arguments that have moved to the session object. loading.register_session_argparse_arguments(parser) # Peek into argv to see if os-token was given, # in which case, the token auth plugin is what the user wants # else, we'll default to password default_auth_plugin = 'password' if "--os-token" in argv: default_auth_plugin = 'token' loading.register_auth_argparse_arguments( parser, argv, default=default_auth_plugin) parser.set_defaults(insecure=strutils.bool_from_string( utils.env('NOVACLIENT_INSECURE', default=False))) parser.set_defaults(os_auth_url=utils.env('OS_AUTH_URL', 'NOVA_URL')) parser.set_defaults(os_username=utils.env('OS_USERNAME', 'NOVA_USERNAME')) parser.set_defaults(os_password=utils.env('OS_PASSWORD', 'NOVA_PASSWORD')) parser.set_defaults(os_project_name=utils.env( 'OS_PROJECT_NAME', 'OS_TENANT_NAME', 'NOVA_PROJECT_ID')) parser.set_defaults(os_project_id=utils.env( 'OS_PROJECT_ID', 'OS_TENANT_ID')) parser.set_defaults( os_project_domain_id=utils.env('OS_PROJECT_DOMAIN_ID')) parser.set_defaults( os_project_domain_name=utils.env('OS_PROJECT_DOMAIN_NAME')) parser.set_defaults( os_user_domain_id=utils.env('OS_USER_DOMAIN_ID')) parser.set_defaults( os_user_domain_name=utils.env('OS_USER_DOMAIN_NAME')) def get_base_parser(self, argv): parser = NovaClientArgumentParser( prog='nova', description=__doc__.strip(), epilog='See "nova 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=novaclient.__version__) parser.add_argument( '--debug', default=False, action='store_true', help=_("Print debugging output.")) parser.add_argument( '--os-cache', default=strutils.bool_from_string( utils.env('OS_CACHE', default=False), True), action='store_true', help=_("Use the auth token cache. Defaults to False if " "env[OS_CACHE] is not set.")) parser.add_argument( '--timings', default=False, action='store_true', help=_("Print call timing info.")) parser.add_argument( '--os-region-name', metavar='', default=utils.env('OS_REGION_NAME', 'NOVA_REGION_NAME'), help=_('Defaults to env[OS_REGION_NAME].')) parser.add_argument( '--service-type', metavar='', help=_('Defaults to compute for most actions.')) parser.add_argument( '--service-name', metavar='', default=utils.env('NOVA_SERVICE_NAME'), help=_('Defaults to env[NOVA_SERVICE_NAME].')) parser.add_argument( '--os-endpoint-type', metavar='', dest='endpoint_type', default=utils.env( 'NOVA_ENDPOINT_TYPE', default=utils.env( 'OS_ENDPOINT_TYPE', default=DEFAULT_NOVA_ENDPOINT_TYPE)), help=_('Defaults to env[NOVA_ENDPOINT_TYPE], ' 'env[OS_ENDPOINT_TYPE] or ') + DEFAULT_NOVA_ENDPOINT_TYPE + '.') parser.add_argument( '--os-compute-api-version', metavar='', default=utils.env('OS_COMPUTE_API_VERSION', default=DEFAULT_OS_COMPUTE_API_VERSION), help=_('Accepts X, X.Y (where X is major and Y is minor part) or ' '"X.latest", defaults to env[OS_COMPUTE_API_VERSION].')) parser.add_argument( '--os-endpoint-override', metavar='', dest='endpoint_override', default=utils.env('OS_ENDPOINT_OVERRIDE', 'NOVACLIENT_ENDPOINT_OVERRIDE', 'NOVACLIENT_BYPASS_URL'), help=_("Use this API endpoint instead of the Service Catalog. " "Defaults to env[OS_ENDPOINT_OVERRIDE].")) parser.set_defaults(func=self.do_help) parser.set_defaults(command='') if osprofiler_profiler: parser.add_argument('--profile', metavar='HMAC_KEY', default=utils.env('OS_PROFILE'), 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 ' 'nova; it is specified in the Nova ' 'configuration file at "/etc/nova/nova.conf". ' 'Without the key, profiling will not be ' 'triggered even if OSprofiler is enabled on ' 'the server side.') self._append_global_identity_args(parser, argv) return parser def get_subcommand_parser(self, version, do_help=False, argv=None): parser = self.get_base_parser(argv) self.subcommands = {} subparsers = parser.add_subparsers(metavar='') actions_module = importutils.import_module( "novaclient.v%s.shell" % version.ver_major) self._find_actions(subparsers, actions_module, version, do_help) self._find_actions(subparsers, self, version, do_help) for extension in self.extensions: self._find_actions(subparsers, extension.module, version, do_help) 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, version, do_help): msg = _(" (Supported by API versions '%(start)s' - '%(end)s')") 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 '' if hasattr(callback, "versioned"): additional_msg = "" subs = api_versions.get_substitutions(callback) if do_help: additional_msg = msg % { 'start': subs[0].start_version.get_string(), 'end': subs[-1].end_version.get_string()} if version.is_latest(): additional_msg += HINT_HELP_MSG subs = [versioned_method for versioned_method in subs if version.matches(versioned_method.start_version, versioned_method.end_version)] if subs: # use the "latest" substitution callback = subs[-1].func else: # there is no proper versioned method continue desc = callback.__doc__ or desc desc += additional_msg action_help = desc.strip() arguments = getattr(callback, 'arguments', []) 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 (args, kwargs) in arguments: kwargs = kwargs.copy() start_version = kwargs.pop("start_version", None) end_version = kwargs.pop("end_version", None) group = kwargs.pop("group", None) if start_version: start_version = api_versions.APIVersion(start_version) if end_version: end_version = api_versions.APIVersion(end_version) else: end_version = api_versions.APIVersion( "%s.latest" % start_version.ver_major) if do_help: kwargs["help"] = kwargs.get("help", "") + (msg % { "start": start_version.get_string(), "end": end_version.get_string()}) if not version.matches(start_version, end_version): continue if group: if group not in groups: groups[group] = ( subparser.add_mutually_exclusive_group() ) kwargs['dest'] = kwargs.get('dest', group) groups[group].add_argument(*args, **kwargs) else: subparser.add_argument(*args, **kwargs) subparser.set_defaults(func=callback) def setup_debugging(self, debug): if not debug: return streamformat = "%(levelname)s (%(module)s:%(lineno)d) %(message)s" # Set up the root logger to debug so that the submodules can # print debug messages logging.basicConfig(level=logging.DEBUG, format=streamformat) logging.getLogger('iso8601').setLevel(logging.WARNING) self.client_logger = logging.getLogger(client.__name__) ch = logging.StreamHandler() self.client_logger.setLevel(logging.DEBUG) self.client_logger.addHandler(ch) def main(self, argv): # Parse args once to find version and debug settings parser = self.get_base_parser(argv) (args, args_list) = parser.parse_known_args(argv) self.setup_debugging(args.debug) self.extensions = [] do_help = args.help or not args_list or args_list[0] == 'help' # bash-completion should not require authentication skip_auth = do_help or ( 'bash-completion' in argv) if not args.os_compute_api_version: api_version = api_versions.get_api_version( DEFAULT_MAJOR_OS_COMPUTE_API_VERSION) else: api_version = api_versions.get_api_version( args.os_compute_api_version) auth_token = getattr(args, "os_token", None) os_username = getattr(args, "os_username", None) os_user_id = getattr(args, "os_user_id", None) os_password = None # Fetched and set later as needed os_project_name = getattr( args, 'os_project_name', getattr(args, 'os_tenant_name', None)) os_project_id = getattr( args, 'os_project_id', getattr(args, 'os_tenant_id', None)) os_auth_url = args.os_auth_url os_region_name = args.os_region_name if "v2.0" not in os_auth_url: # NOTE(andreykurilin): assume that keystone V3 is used and try to # be more user-friendly, i.e provide default values for domains if (not args.os_project_domain_id and not args.os_project_domain_name): setattr(args, "os_project_domain_id", "default") # os_user_domain_id is redundant in case of Token auth type if not auth_token and (not args.os_user_domain_id and not args.os_user_domain_name): setattr(args, "os_user_domain_id", "default") os_project_domain_id = args.os_project_domain_id os_project_domain_name = args.os_project_domain_name os_user_domain_id = getattr(args, "os_user_domain_id", None) os_user_domain_name = getattr(args, "os_user_domain_name", None) endpoint_type = args.endpoint_type insecure = args.insecure service_type = args.service_type service_name = args.service_name endpoint_override = args.endpoint_override os_cache = args.os_cache cacert = args.os_cacert cert = args.os_cert timeout = args.timeout keystone_session = None keystone_auth = None if not endpoint_type: endpoint_type = DEFAULT_NOVA_ENDPOINT_TYPE # This allow users to use endpoint_type as (internal, public or admin) # just like other openstack clients (glance, cinder etc) if endpoint_type in ['internal', 'public', 'admin']: endpoint_type += 'URL' if not service_type: # Note(alex_xu): We need discover version first, so if there isn't # service type specified, we use default nova service type. service_type = DEFAULT_NOVA_SERVICE_TYPE # We should always auth unless we have a token and we're passing a # specific endpoint # Expired tokens are handled by client.py:_cs_request must_auth = not (auth_token and endpoint_override) # FIXME(usrleon): Here should be restrict for project id same as # for os_username or os_password but for compatibility it is not. if must_auth and not skip_auth: if not any([auth_token, os_username, os_user_id]): raise exc.CommandError( _("You must provide a user name/id (via --os-username, " "--os-user-id, env[OS_USERNAME] or env[OS_USER_ID]) or " "an auth token (via --os-token).")) if not any([os_project_name, 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_ID]" " or env[OS_PROJECT_NAME]. You may" " use os-project and os-tenant" " interchangeably.")) if not os_auth_url: raise exc.CommandError( _("You must provide an auth url " "via either --os-auth-url or env[OS_AUTH_URL].")) # TODO(Shilpasd): need to provide support in python - novaclient # for required options for below default auth type plugins: # 1. v3oidcclientcredential # 2. v3oidcpassword # 3. v3oidcauthcode # 4. v3oidcaccesstoken # 5. v3oauth1 # 6. v3fedkerb # 7. v3adfspassword # 8. v3samlpassword # 9. v3applicationcredential # TODO(Shilpasd): need to provide support in python - novaclient # for below extra keystoneauth auth type plugins: # We will need to add code to support discovering of versions # supported by the keystone service based on the auth_url similar # to the one supported by glanceclient. # 1. v3password # 2. v3token # 3. v3kerberos # 4. v3totp with utils.record_time(self.times, args.timings, 'auth_url', args.os_auth_url): keystone_session = ( loading.load_session_from_argparse_arguments(args)) keystone_auth = ( loading.load_auth_from_argparse_arguments(args)) if (not skip_auth and not any([os_project_name, 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_ID]" " or env[OS_PROJECT_NAME]. You may" " use os-project and os-tenant" " interchangeably.")) if not os_auth_url and not skip_auth: raise exc.CommandError( _("You must provide an auth url " "via either --os-auth-url or env[OS_AUTH_URL]")) additional_kwargs = {} if osprofiler_profiler: additional_kwargs["profile"] = args.profile # This client is just used to discover api version. Version API needn't # microversion, so we just pass version 2 at here. self.cs = client.Client( api_versions.APIVersion("2.0"), os_username, os_password, project_id=os_project_id, project_name=os_project_name, user_id=os_user_id, auth_url=os_auth_url, insecure=insecure, region_name=os_region_name, endpoint_type=endpoint_type, extensions=self.extensions, service_type=service_type, service_name=service_name, auth_token=auth_token, timings=args.timings, endpoint_override=endpoint_override, os_cache=os_cache, http_log_debug=args.debug, cacert=cacert, cert=cert, timeout=timeout, session=keystone_session, auth=keystone_auth, logger=self.client_logger, project_domain_id=os_project_domain_id, project_domain_name=os_project_domain_name, user_domain_id=os_user_domain_id, user_domain_name=os_user_domain_name, **additional_kwargs) if not skip_auth: if not api_version.is_latest(): if api_version > api_versions.APIVersion("2.0"): if not api_version.matches(novaclient.API_MIN_VERSION, novaclient.API_MAX_VERSION): raise exc.CommandError( _("The specified version isn't supported by " "client. The valid version range is '%(min)s' " "to '%(max)s'") % { "min": novaclient.API_MIN_VERSION.get_string(), "max": novaclient.API_MAX_VERSION.get_string()} ) api_version = api_versions.discover_version(self.cs, api_version) # build available subcommands based on version self.extensions = client.discover_extensions(api_version) self._run_extension_hooks('__pre_parse_args__') subcommand_parser = self.get_subcommand_parser( api_version, do_help=do_help, argv=argv) self.parser = subcommand_parser if args.help or not argv: subcommand_parser.print_help() return 0 args = subcommand_parser.parse_args(argv) self._run_extension_hooks('__post_parse_args__', args) # Short-circuit and deal with help right away. if args.func == self.do_help: self.do_help(args) return 0 elif args.func == self.do_bash_completion: self.do_bash_completion(args) return 0 if not args.service_type: service_type = (utils.get_service_type(args.func) or DEFAULT_NOVA_SERVICE_TYPE) if utils.isunauthenticated(args.func): # NOTE(alex_xu): We need authentication for discover microversion. # But the subcommands may needn't it. If the subcommand needn't, # we clear the session arguments. keystone_session = None keystone_auth = None # Recreate client object with discovered version. self.cs = client.Client( api_version, os_username, os_password, project_id=os_project_id, project_name=os_project_name, user_id=os_user_id, auth_url=os_auth_url, insecure=insecure, region_name=os_region_name, endpoint_type=endpoint_type, extensions=self.extensions, service_type=service_type, service_name=service_name, auth_token=auth_token, timings=args.timings, endpoint_override=endpoint_override, os_cache=os_cache, http_log_debug=args.debug, cacert=cacert, cert=cert, timeout=timeout, session=keystone_session, auth=keystone_auth, project_domain_id=os_project_domain_id, project_domain_name=os_project_domain_name, user_domain_id=os_user_domain_id, user_domain_name=os_user_domain_name) args.func(self.cs, args) if osprofiler_profiler and args.profile: trace_id = osprofiler_profiler.get().get_base_id() print("To display trace use the command:\n\n" " osprofiler trace show --html %s " % trace_id) if args.timings: self._dump_timings(self.times + self.cs.get_timings()) 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)) utils.print_list(results, ["url", "seconds"], sortby_index=None) def _run_extension_hooks(self, hook_type, *args, **kwargs): """Run hooks for all registered extensions.""" for extension in self.extensions: extension.run_hooks(hook_type, *args, **kwargs) def do_bash_completion(self, _args): """ Prints all of the commands and options to stdout so that the nova.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)) @utils.arg( 'command', metavar='', nargs='?', help=_('Display help for .')) def do_help(self, args): """ Display help about this program or one of its subcommands. """ if args.command: if args.command in self.subcommands: self.subcommands[args.command].print_help() else: raise exc.CommandError(_("'%s' is not a valid subcommand") % args.command) else: self.parser.print_help() # I'm picky about my shell help. class OpenStackHelpFormatter(argparse.HelpFormatter): def __init__(self, prog, indent_increment=2, max_help_position=32, width=None): super(OpenStackHelpFormatter, self).__init__(prog, indent_increment, max_help_position, width) def start_section(self, heading): # Title-case the headings heading = '%s%s' % (heading[0].upper(), heading[1:]) super(OpenStackHelpFormatter, self).start_section(heading) def main(argv=sys.argv[1:]): try: # Special dansmith envvar to hide the warning. Don't rely on this # because we will eventually remove all this stuff. if os.environ.get("NOVACLIENT_ISHOULDNTBEDOINGTHIS") != "1": print( _( "nova CLI is deprecated and will be removed in a future " "release" ), file=sys.stderr, ) argv = [encodeutils.safe_decode(a) for a in argv] OpenStackComputeShell().main(argv) except Exception as exc: logger.debug(exc, exc_info=1) message = encodeutils.exception_to_unicode(exc) print("ERROR (%(type)s): %(msg)s" % { 'type': exc.__class__.__name__, 'msg': message}, file=sys.stderr) sys.exit(1) except KeyboardInterrupt: print(_("... terminating nova client"), file=sys.stderr) sys.exit(130) if __name__ == "__main__": main() ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1725030944.7924647 python-novaclient-18.7.0/novaclient/tests/0000775000175000017500000000000000000000000020617 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/__init__.py0000664000175000017500000000000000000000000022716 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1725030944.7924647 python-novaclient-18.7.0/novaclient/tests/functional/0000775000175000017500000000000000000000000022761 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/README.rst0000664000175000017500000000325600000000000024456 0ustar00zuulzuul00000000000000==================================== python-novaclient functional testing ==================================== Idea ---- Over time we have noticed two issues with novaclient unit tests. * Does not exercise the CLI * We can get the expected server behavior wrong, and test the wrong thing. We are using functional tests, run against a running cloud (primarily devstack), to address these two cases. Additionally these functional tests can be considered example uses of python-novaclient. These tests started out in tempest as read only nova CLI tests, to make sure the CLI didn't simply stacktrace when being used (which happened on multiple occasions). Testing Theory -------------- We are treating python-novaclient as legacy code, so we do not want to spend a lot of effort adding in missing features. In the future the CLI will move to python-openstackclient, and the python API will be based on the OpenStack SDK project. But until that happens we still need better functional testing, to prevent regressions etc. Since python-novaclient has two uses, CLI and python API, we should have two sets of functional tests. CLI and python API. The python API tests should never use the CLI. But the CLI tests can use the python API where adding native support to the CLI for the required functionality would involve a non trivial amount of work. Functional Test Guidelines -------------------------- * Consume credentials via standard client environmental variables:: OS_USERNAME OS_PASSWORD OS_TENANT_NAME OS_AUTH_URL * Usage of insecure SSL can be configured via the standard client environment variable:: OS_INSECURE * Try not to require an additional configuration file ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/__init__.py0000664000175000017500000000000000000000000025060 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1725030944.7924647 python-novaclient-18.7.0/novaclient/tests/functional/api/0000775000175000017500000000000000000000000023532 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/api/__init__.py0000664000175000017500000000000000000000000025631 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/api/test_servers.py0000664000175000017500000000241500000000000026636 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 time from novaclient.tests.functional import base class TestServersAPI(base.ClientTestBase): def test_server_ips(self): server_name = "test_server" initial_server = self.client.servers.create( server_name, self.image, self.flavor, nics=[{"net-id": self.network.id}]) self.addCleanup(initial_server.delete) for x in range(60): server = self.client.servers.get(initial_server) if server.status == "ACTIVE": break else: time.sleep(1) else: self.fail("Server %s did not go ACTIVE after 60s" % server) ips = self.client.servers.ips(server) self.assertIn(self.network.name, ips) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/base.py0000664000175000017500000005725400000000000024262 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import os import time from cinderclient.v3 import client as cinderclient import fixtures from glanceclient import client as glanceclient from keystoneauth1.exceptions import discovery as discovery_exc from keystoneauth1 import identity from keystoneauth1 import session as ksession from keystoneclient import client as keystoneclient from keystoneclient import discover as keystone_discover from neutronclient.v2_0 import client as neutronclient import openstack.config import openstack.config.exceptions from oslo_utils import uuidutils import tempest.lib.cli.base import testtools import novaclient import novaclient.api_versions from novaclient import base import novaclient.client from novaclient.v2 import networks import novaclient.v2.shell BOOT_IS_COMPLETE = ("login as 'cirros' user. default password: " "'gocubsgo'. use 'sudo' for root.") def is_keystone_version_available(session, version): """Given a (major, minor) pair, check if the API version is enabled.""" d = keystone_discover.Discover(session) try: d.create_client(version) except (discovery_exc.DiscoveryFailure, discovery_exc.VersionNotAvailable): return False else: return True # The following are simple filter functions that filter our available # image / flavor list so that they can be used in standard testing. def pick_flavor(flavors): """Given a flavor list pick a reasonable one.""" for flavor_priority in ('m1.nano', 'm1.micro', 'm1.tiny', 'm1.small'): for flavor in flavors: if flavor.name == flavor_priority: return flavor raise NoFlavorException() def pick_image(images): firstImage = None for image in images: firstImage = firstImage or image if image.name.startswith('cirros') and ( image.name.endswith('-uec') or image.name.endswith('-disk.img')): return image # We didn't find the specific cirros image we'd like to use, so just use # the first available. if firstImage: return firstImage raise NoImageException() def pick_network(networks): network_name = os.environ.get('OS_NOVACLIENT_NETWORK') if network_name: for network in networks: if network.name == network_name: return network raise NoNetworkException() return networks[0] class NoImageException(Exception): """We couldn't find an acceptable image.""" pass class NoFlavorException(Exception): """We couldn't find an acceptable flavor.""" pass class NoNetworkException(Exception): """We couldn't find an acceptable network.""" pass class NoCloudConfigException(Exception): """We couldn't find a cloud configuration.""" pass CACHE = {} class ClientTestBase(testtools.TestCase): """Base test class for read only python-novaclient commands. This is a first pass at a simple read only python-novaclient test. This only exercises client commands that are read only. This should test commands: * as a regular user * as a admin user * with and without optional parameters * initially just check return codes, and later test command outputs """ COMPUTE_API_VERSION = None log_format = ('%(asctime)s %(process)d %(levelname)-8s ' '[%(name)s] %(message)s') def setUp(self): super(ClientTestBase, self).setUp() test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0) try: test_timeout = int(test_timeout) except ValueError: test_timeout = 0 if test_timeout > 0: self.useFixture(fixtures.Timeout(test_timeout, gentle=True)) 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)) if (os.environ.get('OS_LOG_CAPTURE') != 'False' and os.environ.get('OS_LOG_CAPTURE') != '0'): self.useFixture(fixtures.LoggerFixture(nuke_handlers=False, format=self.log_format, level=None)) # Collecting of credentials: # # Grab the cloud config from a user's clouds.yaml file. # First look for a functional_admin cloud, as this is a cloud # that the user may have defined for functional testing that has # admin credentials. # If that is not found, get the devstack config and override the # username and project_name to be admin so that admin credentials # will be used. # # Finally, fall back to looking for environment variables to support # existing users running these the old way. We should deprecate that # as tox 2.0 blanks out environment. # # TODO(sdague): while we collect this information in # tempest-lib, we do it in a way that's not available for top # level tests. Long term this probably needs to be in the base # class. openstack_config = openstack.config.OpenStackConfig() try: cloud_config = openstack_config.get_one_cloud('functional_admin') except openstack.config.exceptions.OpenStackConfigException: try: cloud_config = openstack_config.get_one_cloud( 'devstack', auth=dict( username='admin', project_name='admin')) except openstack.config.exceptions.OpenStackConfigException: try: cloud_config = openstack_config.get_one_cloud('envvars') except openstack.config.exceptions.OpenStackConfigException: cloud_config = None if cloud_config is None: raise NoCloudConfigException( "Could not find a cloud named functional_admin or a cloud" " named devstack. Please check your clouds.yaml file and" " try again.") auth_info = cloud_config.config['auth'] user = auth_info['username'] passwd = auth_info['password'] self.project_name = auth_info['project_name'] auth_url = auth_info['auth_url'] user_domain_id = auth_info['user_domain_id'] self.project_domain_id = auth_info['project_domain_id'] if 'insecure' in cloud_config.config: self.insecure = cloud_config.config['insecure'] else: self.insecure = False self.cacert = cloud_config.config['cacert'] self.cert = cloud_config.config['cert'] auth = identity.Password(username=user, password=passwd, project_name=self.project_name, auth_url=auth_url, project_domain_id=self.project_domain_id, user_domain_id=user_domain_id) session = ksession.Session( cert=self.cert, auth=auth, verify=(self.cacert or not self.insecure) ) self.client = self._get_novaclient(session) self.glance = glanceclient.Client('2', session=session) # pick some reasonable flavor / image combo if "flavor" not in CACHE: CACHE["flavor"] = pick_flavor(self.client.flavors.list()) if "image" not in CACHE: CACHE["image"] = pick_image(self.glance.images.list()) self.flavor = CACHE["flavor"] self.image = CACHE["image"] if "network" not in CACHE: # Get the networks from neutron. neutron = neutronclient.Client(session=session) neutron_networks = neutron.list_networks()['networks'] # Convert the neutron dicts to Network objects. nets = [] for network in neutron_networks: nets.append(networks.Network( networks.NeutronManager, network)) # Keep track of whether or not there are multiple networks # available to the given tenant because if so, a specific # network ID has to be passed in on server create requests # otherwise the server POST will fail with a 409. CACHE['multiple_networks'] = len(nets) > 1 CACHE["network"] = pick_network(nets) self.network = CACHE["network"] self.multiple_networks = CACHE['multiple_networks'] # create a CLI client in case we'd like to do CLI # testing. tempest.lib does this really weird thing where it # builds a giant factory of all the CLIs that it knows # about. Eventually that should really be unwound into # something more sensible. cli_dir = os.environ.get( 'OS_NOVACLIENT_EXEC_DIR', os.path.join(os.environ['TOX_ENV_DIR'], 'bin')) self.cli_clients = tempest.lib.cli.base.CLIClient( username=user, password=passwd, tenant_name=self.project_name, uri=auth_url, cli_dir=cli_dir, insecure=self.insecure) self.keystone = keystoneclient.Client(session=session, username=user, password=passwd) self.cinder = cinderclient.Client(auth=auth, session=session) def _get_novaclient(self, session): nc = novaclient.client.Client("2", session=session) if self.COMPUTE_API_VERSION: if "min_api_version" not in CACHE: # Obtain supported versions by API side v = nc.versions.get_current() if not hasattr(v, 'version') or not v.version: # API doesn't support microversions CACHE["min_api_version"] = ( novaclient.api_versions.APIVersion("2.0")) CACHE["max_api_version"] = ( novaclient.api_versions.APIVersion("2.0")) else: CACHE["min_api_version"] = ( novaclient.api_versions.APIVersion(v.min_version)) CACHE["max_api_version"] = ( novaclient.api_versions.APIVersion(v.version)) if self.COMPUTE_API_VERSION == "2.latest": requested_version = min(novaclient.API_MAX_VERSION, CACHE["max_api_version"]) else: requested_version = novaclient.api_versions.APIVersion( self.COMPUTE_API_VERSION) if not requested_version.matches(CACHE["min_api_version"], CACHE["max_api_version"]): msg = ("%s is not supported by Nova-API. Supported version" % self.COMPUTE_API_VERSION) if CACHE["min_api_version"] == CACHE["max_api_version"]: msg += ": %s" % CACHE["min_api_version"].get_string() else: msg += "s: %s - %s" % ( CACHE["min_api_version"].get_string(), CACHE["max_api_version"].get_string()) self.skipTest(msg) nc.api_version = requested_version return nc def nova(self, action, flags='', params='', fail_ok=False, endpoint_type='publicURL', merge_stderr=False): if self.COMPUTE_API_VERSION: flags += " --os-compute-api-version %s " % self.COMPUTE_API_VERSION return self.cli_clients.nova(action, flags, params, fail_ok, endpoint_type, merge_stderr) def wait_for_volume_status(self, volume, status, timeout=60, poll_interval=1): """Wait until volume reaches given status. :param volume: volume resource :param status: expected status of volume :param timeout: timeout in seconds :param poll_interval: poll interval in seconds """ start_time = time.time() while time.time() - start_time < timeout: volume = self.cinder.volumes.get(volume.id) if volume.status == status: break time.sleep(poll_interval) else: self.fail("Volume %s did not reach status %s after %d s" % (volume.id, status, timeout)) def wait_for_server_os_boot(self, server_id, timeout=300, poll_interval=1): """Wait until instance's operating system is completely booted. :param server_id: uuid4 id of given instance :param timeout: timeout in seconds :param poll_interval: poll interval in seconds """ start_time = time.time() console = None while time.time() - start_time < timeout: console = self.nova('console-log %s ' % server_id) if BOOT_IS_COMPLETE in console: break time.sleep(poll_interval) else: self.fail("Server %s did not boot after %d s.\nConsole:\n%s" % (server_id, timeout, console)) def wait_for_resource_delete(self, resource, manager, timeout=60, poll_interval=1): """Wait until getting the resource raises NotFound exception. :param resource: Resource object. :param manager: Manager object with get method. :param timeout: timeout in seconds :param poll_interval: poll interval in seconds """ start_time = time.time() while time.time() - start_time < timeout: try: manager.get(resource) except Exception as e: if getattr(e, "http_status", None) == 404: break else: raise time.sleep(poll_interval) else: self.fail("The resource '%s' still exists." % base.getid(resource)) def name_generate(self): """Generate randomized name for some entity.""" # NOTE(andreykurilin): name_generator method is used for various # resources (servers, flavors, volumes, keystone users, etc). # Since the length of name has limits we cannot use the whole UUID, # so the first 8 chars is taken from it. # Based on the fact that the new name includes class and method # names, 8 chars of uuid should be enough to prevent any conflicts, # even if the single test will be launched in parallel thousand times return "%(prefix)s-%(test_cls)s-%(test_name)s" % { "prefix": uuidutils.generate_uuid()[:8], "test_cls": self.__class__.__name__, "test_name": self.id().rsplit(".", 1)[-1] } def _get_value_from_the_table(self, table, key): """Parses table to get desired value. EXAMPLE of the table: # +-------------+----------------------------------+ # | Property | Value | # +-------------+----------------------------------+ # | description | | # | enabled | True | # | id | 582df899eabc47018c96713c2f7196ba | # | name | admin | # +-------------+----------------------------------+ """ lines = table.split("\n") for line in lines: if "|" in line: l_property, l_value = line.split("|")[1:3] if l_property.strip() == key: return l_value.strip() raise ValueError("Property '%s' is missing from the table:\n%s" % (key, table)) def _get_column_value_from_single_row_table(self, table, column): """Get the value for the column in the single-row table Example table: +----------+-------------+----------+----------+ | address | cidr | hostname | host | +----------+-------------+----------+----------+ | 10.0.0.3 | 10.0.0.0/24 | test | myhost | +----------+-------------+----------+----------+ :param table: newline-separated table with |-separated cells :param column: name of the column to look for :raises: ValueError if the column value is not found """ lines = table.split("\n") # Determine the column header index first. column_index = -1 for line in lines: if "|" in line: if column_index == -1: headers = line.split("|")[1:-1] for index, header in enumerate(headers): if header.strip() == column: column_index = index break else: # We expect a single-row table so we should be able to get # the value now using the column index. return line.split("|")[1:-1][column_index].strip() raise ValueError("Unable to find value for column '%s'." % column) def _get_list_of_values_from_single_column_table(self, table, column): """Get the list of values for the column in the single-column table Example table: +------+ | Tags | +------+ | tag1 | | tag2 | +------+ :param table: newline-separated table with |-separated cells :param column: name of the column to look for :raises: ValueError if the single column has some other name """ lines = table.split("\n") column_name = None values = [] for line in lines: if "|" in line: if not column_name: column_name = line.split("|")[1].strip() if column_name != column: raise ValueError( "The table has no column %(expected)s " "but has column %(actual)s." % { 'expected': column, 'actual': column_name}) else: values.append(line.split("|")[1].strip()) return values def _create_server(self, name=None, flavor=None, with_network=True, add_cleanup=True, **kwargs): name = name or self.name_generate() if with_network: nics = [{"net-id": self.network.id}] else: nics = None flavor = flavor or self.flavor server = self.client.servers.create(name, self.image, flavor, nics=nics, **kwargs) if add_cleanup: self.addCleanup(server.delete) novaclient.v2.shell._poll_for_status( self.client.servers.get, server.id, 'building', ['active']) return server def _wait_for_state_change(self, server_id, status): novaclient.v2.shell._poll_for_status( self.client.servers.get, server_id, None, [status], show_progress=False, poll_period=1, silent=True) def _get_project_id(self, name): """Obtain project id by project name.""" if self.keystone.version == "v3": project = self.keystone.projects.find(name=name) else: project = self.keystone.tenants.find(name=name) return project.id def _cleanup_server(self, server_id): """Deletes a server and waits for it to be gone.""" self.client.servers.delete(server_id) self.wait_for_resource_delete(server_id, self.client.servers) def _get_absolute_limits(self): """Returns the absolute limits (quota usage) including reserved quota usage for the given tenant running the test. :return: A dict where the key is the limit (or usage) and value. """ # The absolute limits are returned in a generator so convert to a dict. return {limit.name: limit.value for limit in self.client.limits.get(reserved=True).absolute} def _pick_alternate_flavor(self): """Given the flavor picked in the base class setup, this finds the opposite flavor to use for a resize test. For example, if m1.nano is the flavor, then use m1.micro, but those are only available if Tempest is configured. If m1.tiny, then use m1.small. """ flavor_name = self.flavor.name if flavor_name == 'm1.nano': # This is an upsize test. return 'm1.micro' if flavor_name == 'm1.micro': # This is a downsize test. return 'm1.nano' if flavor_name == 'm1.tiny': # This is an upsize test. return 'm1.small' if flavor_name == 'm1.small': # This is a downsize test. return 'm1.tiny' self.fail('Unable to find alternate for flavor: %s' % flavor_name) class TenantTestBase(ClientTestBase): """Base test class for additional tenant and user creation which could be required in various test scenarios """ def setUp(self): super(TenantTestBase, self).setUp() user_name = uuidutils.generate_uuid() project_name = uuidutils.generate_uuid() password = 'password' if self.keystone.version == "v3": project = self.keystone.projects.create(project_name, self.project_domain_id) self.project_id = project.id self.addCleanup(self.keystone.projects.delete, self.project_id) self.user_id = self.keystone.users.create( name=user_name, password=password, default_project=self.project_id).id for role in self.keystone.roles.list(): if "member" in role.name.lower(): self.keystone.roles.grant(role.id, user=self.user_id, project=self.project_id) break else: project = self.keystone.tenants.create(project_name) self.project_id = project.id self.addCleanup(self.keystone.tenants.delete, self.project_id) self.user_id = self.keystone.users.create( user_name, password, tenant_id=self.project_id).id self.addCleanup(self.keystone.users.delete, self.user_id) self.cli_clients_2 = tempest.lib.cli.base.CLIClient( username=user_name, password=password, tenant_name=project_name, uri=self.cli_clients.uri, cli_dir=self.cli_clients.cli_dir, insecure=self.insecure) def another_nova(self, action, flags='', params='', fail_ok=False, endpoint_type='publicURL', merge_stderr=False): flags += " --os-compute-api-version %s " % self.COMPUTE_API_VERSION return self.cli_clients_2.nova(action, flags, params, fail_ok, endpoint_type, merge_stderr) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/clouds.yaml.sample0000664000175000017500000000032000000000000026411 0ustar00zuulzuul00000000000000clouds: devstack: auth: username: admin password: change_me project_name: admin auth_url: http://localhost:5000/v3 user_domain_id: default project_domain_id: default ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1725030944.7924647 python-novaclient-18.7.0/novaclient/tests/functional/hooks/0000775000175000017500000000000000000000000024104 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/hooks/check_resources.py0000664000175000017500000000163400000000000027631 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 novaclient.tests.functional import base class ResourceChecker(base.ClientTestBase): def runTest(self): pass def check(self): self.setUp() print("$ nova list --all-tenants") print(self.nova("list", params="--all-tenants")) print("\n") if __name__ == "__main__": ResourceChecker().check() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/test_auth.py0000664000175000017500000001076400000000000025343 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import os from urllib import parse import tempest.lib.cli.base from novaclient import client from novaclient.tests.functional import base class TestAuthentication(base.ClientTestBase): def _get_url(self, identity_api_version): url = parse.urlparse(self.cli_clients.uri) return parse.urlunparse((url.scheme, url.netloc, '/identity/v%s' % identity_api_version, url.params, url.query, url.fragment)) def nova_auth_with_password(self, action, identity_api_version): flags = ( f'--os-username {self.cli_clients.username} ' f'--os-tenant-name {self.cli_clients.tenant_name} ' f'--os-password {self.cli_clients.password} ' f'--os-auth-url {self._get_url(identity_api_version)} ' f'--os-endpoint-type publicURL' ) if self.cacert: flags = f'{flags} --os-cacert {self.cacert}' if self.cert: flags = f'{flags} --os-cert {self.cert}' if self.cli_clients.insecure: flags = f'{flags} --insecure' return tempest.lib.cli.base.execute( "nova", action, flags, cli_dir=self.cli_clients.cli_dir) def nova_auth_with_token(self, identity_api_version): auth_ref = self.client.client.session.auth.get_access( self.client.client.session) token = auth_ref.auth_token auth_url = self._get_url(identity_api_version) kw = {} if identity_api_version == "3": kw["project_domain_id"] = self.project_domain_id nova = client.Client("2", auth_token=token, auth_url=auth_url, project_name=self.project_name, cacert=self.cacert, cert=self.cert, **kw) nova.servers.list() # NOTE(andreykurilin): tempest.lib.cli.base.execute doesn't allow to # pass 'env' argument to subprocess.Popen for overriding the current # process' environment. # When one of OS_AUTH_TYPE or OS_AUTH_PLUGIN environment variables # presents, keystoneauth1 can load the wrong auth plugin with wrong # expected cli arguments. To avoid this case, we need to modify # current environment. # TODO(andreykurilin): tempest.lib.cli.base.execute is quite simple # method that can be replaced by subprocess.check_output direct call # with passing env argument to avoid modifying the current process # environment. or we probably can propose a change to tempest. os.environ.pop("OS_AUTH_TYPE", None) os.environ.pop("OS_AUTH_PLUGIN", None) flags = ( f'--os-tenant-name {self.project_name} ' f'--os-token {token} ' f'--os-auth-url {auth_url} ' f'--os-endpoint-type publicURL' ) if self.cacert: flags = f'{flags} --os-cacert {self.cacert}' if self.cert: flags = f'{flags} --os-cert {self.cert}' if self.cli_clients.insecure: flags = f'{flags} --insecure' tempest.lib.cli.base.execute( "nova", "list", flags, cli_dir=self.cli_clients.cli_dir) def test_auth_via_keystone_v2(self): session = self.keystone.session version = (2, 0) if not base.is_keystone_version_available(session, version): self.skipTest("Identity API version 2.0 is not available.") self.nova_auth_with_password("list", identity_api_version="2.0") self.nova_auth_with_token(identity_api_version="2.0") def test_auth_via_keystone_v3(self): session = self.keystone.session version = (3, 0) if not base.is_keystone_version_available(session, version): self.skipTest("Identity API version 3.0 is not available.") self.nova_auth_with_password("list", identity_api_version="3") self.nova_auth_with_token(identity_api_version="3") ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1725030944.800465 python-novaclient-18.7.0/novaclient/tests/functional/v2/0000775000175000017500000000000000000000000023310 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/__init__.py0000664000175000017500000000000000000000000025407 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/fake_crypto.py0000664000175000017500000000513700000000000026176 0ustar00zuulzuul00000000000000# Copyright 2015 Cloudbase Solutions # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. def get_x509_cert_and_fingerprint(): fingerprint = "a1:6f:6d:ea:a6:36:d0:3a:c6:eb:b6:ee:07:94:3e:2a:90:98:2b:c9" certif = ( "-----BEGIN CERTIFICATE-----\n" "MIIDIjCCAgqgAwIBAgIJAIE8EtWfZhhFMA0GCSqGSIb3DQEBCwUAMCQxIjAgBgNV\n" "BAMTGWNsb3VkYmFzZS1pbml0LXVzZXItMTM1NTkwHhcNMTUwMTI5MTgyMzE4WhcN\n" "MjUwMTI2MTgyMzE4WjAkMSIwIAYDVQQDExljbG91ZGJhc2UtaW5pdC11c2VyLTEz\n" "NTU5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv4lv95ofkXLIbALU\n" "UEb1f949TYNMUvMGNnLyLgGOY+D61TNG7RZn85cRg9GVJ7KDjSLN3e3LwH5rgv5q\n" "pU+nM/idSMhG0CQ1lZeExTsMEJVT3bG7LoU5uJ2fJSf5+hA0oih2M7/Kap5ggHgF\n" "h+h8MWvDC9Ih8x1aadkk/OEmJsTrziYm0C/V/FXPHEuXfZn8uDNKZ/tbyfI6hwEj\n" "nLz5Zjgg29n6tIPYMrnLNDHScCwtNZOcnixmWzsxCt1bxsAEA/y9gXUT7xWUf52t\n" "2+DGQbLYxo0PHjnPf3YnFXNavfTt+4c7ZdHhOQ6ZA8FGQ2LJHDHM1r2/8lK4ld2V\n" "qgNTcQIDAQABo1cwVTATBgNVHSUEDDAKBggrBgEFBQcDAjA+BgNVHREENzA1oDMG\n" "CisGAQQBgjcUAgOgJQwjY2xvdWRiYXNlLWluaXQtdXNlci0xMzU1OUBsb2NhbGhv\n" "c3QwDQYJKoZIhvcNAQELBQADggEBAHHX/ZUOMR0ZggQnfXuXLIHWlffVxxLOV/bE\n" "7JC/dtedHqi9iw6sRT5R6G1pJo0xKWr2yJVDH6nC7pfxCFkby0WgVuTjiu6iNRg2\n" "4zNJd8TGrTU+Mst+PPJFgsxrAY6vjwiaUtvZ/k8PsphHXu4ON+oLurtVDVgog7Vm\n" "fQCShx434OeJj1u8pb7o2WyYS5nDVrHBhlCAqVf2JPKu9zY+i9gOG2kimJwH7fJD\n" "xXpMIwAQ+flwlHR7OrE0L8TNcWwKPRAY4EPcXrT+cWo1k6aTqZDSK54ygW2iWtni\n" "ZBcstxwcB4GIwnp1DrPW9L2gw5eLe1Sl6wdz443TW8K/KPV9rWQ=\n" "-----END CERTIFICATE-----\n") return certif, fingerprint def get_ssh_pub_key_and_fingerprint(): fingerprint = "1e:2c:9b:56:79:4b:45:77:f9:ca:7a:98:2c:b0:d5:3c" public_key = ("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDx8nkQv/zgGg" "B4rMYmIf+6A4l6Rr+o/6lHBQdW5aYd44bd8JttDCE/F/pNRr0l" "RE+PiqSPO8nDPHw0010JeMH9gYgnnFlyY3/OcJ02RhIPyyxYpv" "9FhY+2YiUkpwFOcLImyrxEsYXpD/0d3ac30bNH6Sw9JD9UZHYc" "pSxsIbECHw== Generated-by-Nova") return public_key, fingerprint ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1725030944.800465 python-novaclient-18.7.0/novaclient/tests/functional/v2/legacy/0000775000175000017500000000000000000000000024554 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/legacy/__init__.py0000664000175000017500000000000000000000000026653 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/legacy/test_consoles.py0000664000175000017500000000421700000000000030016 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. from tempest.lib import exceptions from novaclient.tests.functional import base class TestConsolesNovaClient(base.ClientTestBase): """Consoles functional tests.""" COMPUTE_API_VERSION = "2.1" def _test_console_get(self, command, expected_response_type): server = self._create_server() completed_command = command % server.id try: output = self.nova(completed_command) # if we didn't fail, check that the expected response type is in # the output console_type = self._get_column_value_from_single_row_table( output, 'Type') self.assertEqual(expected_response_type, console_type, output) except exceptions.CommandFailed as cf: self.assertIn('HTTP 400', str(cf.stderr)) def _test_vnc_console_get(self): self._test_console_get('get-vnc-console %s novnc', 'novnc') def _test_spice_console_get(self): self._test_console_get('get-spice-console %s spice-html5', 'spice-html5') def _test_rdp_console_get(self): self._test_console_get('get-rdp-console %s rdp-html5', 'rdp-html5') def _test_serial_console_get(self): self._test_console_get('get-serial-console %s', 'serial') def test_vnc_console_get(self): self._test_vnc_console_get() def test_spice_console_get(self): self._test_spice_console_get() def test_rdp_console_get(self): self._test_rdp_console_get() def test_serial_console_get(self): self._test_serial_console_get() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/legacy/test_extended_attributes.py0000664000175000017500000000464000000000000032237 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_serialization import jsonutils from novaclient.tests.functional import base class TestExtAttrNovaClient(base.ClientTestBase): """Functional tests for os-extended-server-attributes""" COMPUTE_API_VERSION = "2.1" def _create_server_and_attach_volume(self): server = self._create_server() volume = self.cinder.volumes.create(1) self.addCleanup(volume.delete) self.wait_for_volume_status(volume, 'available') self.nova('volume-attach', params="%s %s" % (server.name, volume.id)) self.addCleanup(self._release_volume, server, volume) self.wait_for_volume_status(volume, 'in-use') return server, volume def _release_volume(self, server, volume): self.nova('volume-detach', params="%s %s" % (server.id, volume.id)) self.wait_for_volume_status(volume, 'available') def test_extended_server_attributes(self): server, volume = self._create_server_and_attach_volume() table = self.nova('show %s' % server.id) # Check that attributes listed below exist in 'nova show' table and # they are exactly Property attributes (not an instance's name, e.g.) # The _get_value_from_the_table() will raise an exception # if attr is not a key (first column) of the table dict for attr in ['OS-EXT-SRV-ATTR:host', 'OS-EXT-SRV-ATTR:hypervisor_hostname', 'OS-EXT-SRV-ATTR:instance_name']: self._get_value_from_the_table(table, attr) # Check that attribute given below also exists in 'nova show' table # as a key (first column) of table dict volume_attr = self._get_value_from_the_table( table, 'os-extended-volumes:volumes_attached') # Check that 'id' exists as a key of volume_attr dict self.assertIn('id', jsonutils.loads(volume_attr)[0]) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/legacy/test_flavor_access.py0000664000175000017500000000602700000000000031004 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 novaclient.tests.functional import base class TestFlvAccessNovaClient(base.TenantTestBase): """Functional tests for flavors with public and non-public access""" COMPUTE_API_VERSION = "2.1" def test_public_flavor_list(self): # Check that flavors with public access are available for both admin # and non-admin tenants flavor_list1 = self.nova('flavor-list') flavor_list2 = self.another_nova('flavor-list') self.assertEqual(flavor_list1, flavor_list2) def test_non_public_flavor_list(self): # Check that non-public flavor appears in flavor list # only for admin tenant and only with --all attribute # and doesn't appear for non-admin tenant flv_name = self.name_generate() self.nova('flavor-create --is-public false %s auto 512 1 1' % flv_name) self.addCleanup(self.nova, 'flavor-delete %s' % flv_name) flavor_list1 = self.nova('flavor-list') self.assertNotIn(flv_name, flavor_list1) flavor_list2 = self.nova('flavor-list --all') flavor_list3 = self.another_nova('flavor-list --all') self.assertIn(flv_name, flavor_list2) self.assertNotIn(flv_name, flavor_list3) def test_add_access_non_public_flavor(self): # Check that it's allowed to grant an access to non-public flavor for # the given tenant flv_name = self.name_generate() self.nova('flavor-create --is-public false %s auto 512 1 1' % flv_name) self.addCleanup(self.nova, 'flavor-delete %s' % flv_name) self.nova('flavor-access-add', params="%s %s" % (flv_name, self.project_id)) self.assertIn(self.project_id, self.nova('flavor-access-list --flavor %s' % flv_name)) def test_add_access_public_flavor(self): # For microversion < 2.7 the 'flavor-access-add' operation is executed # successfully for public flavor, but the next operation, # 'flavor-access-list --flavor %(name_of_public_flavor)' returns # a CommandError flv_name = self.name_generate() self.nova('flavor-create %s auto 512 1 1' % flv_name) self.addCleanup(self.nova, 'flavor-delete %s' % flv_name) self.nova('flavor-access-add %s %s' % (flv_name, self.project_id)) output = self.nova('flavor-access-list --flavor %s' % flv_name, fail_ok=True, merge_stderr=True) self.assertIn("CommandError", output) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/legacy/test_hypervisors.py0000664000175000017500000000344700000000000030572 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 novaclient.tests.functional import base from novaclient import utils class TestHypervisors(base.ClientTestBase): COMPUTE_API_VERSION = "2.1" def _test_list(self, cpu_info_type, uuid_as_id=False): hypervisors = self.client.hypervisors.list() if not len(hypervisors): self.fail("No hypervisors detected.") for hypervisor in hypervisors: self.assertIsInstance(hypervisor.cpu_info, cpu_info_type) if uuid_as_id: # microversion >= 2.53 returns a uuid for the id self.assertFalse(utils.is_integer_like(hypervisor.id), 'Expected hypervisor.id to be a UUID.') self.assertFalse( utils.is_integer_like(hypervisor.service['id']), 'Expected hypervisor.service.id to be a UUID.') else: self.assertTrue(utils.is_integer_like(hypervisor.id), 'Expected hypervisor.id to be an integer.') self.assertTrue( utils.is_integer_like(hypervisor.service['id']), 'Expected hypervisor.service.id to be an integer.') def test_list(self): self._test_list(str) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/legacy/test_instances.py0000664000175000017500000000537300000000000030164 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 novaclient.tests.functional import base class TestInstanceCLI(base.ClientTestBase): COMPUTE_API_VERSION = "2.1" def test_attach_volume(self): """Test we can attach a volume via the cli. This test was added after bug 1423695. That bug exposed inconsistencies in how to talk to API services from the CLI vs. API level. The volumes api calls that were designed to populate the completion cache were incorrectly routed to the Nova endpoint. Novaclient volumes support actually talks to Cinder endpoint directly. This would case volume-attach to return a bad error code, however it does this *after* the attach command is correctly dispatched. So the volume-attach still works, but the user is presented a 404 error. This test ensures we can do a through path test of: boot, create volume, attach volume, detach volume, delete volume, destroy. """ name = self.name_generate() # Boot via the cli, as we're primarily testing the cli in this test self.nova('boot', params="--flavor %s --image %s %s --nic net-id=%s --poll" % (self.flavor.name, self.image.name, name, self.network.id)) # Be nice about cleaning up, however, use the API for this to avoid # parsing text. servers = self.client.servers.list(search_opts={"name": name}) # the name is a random uuid, there better only be one self.assertEqual(1, len(servers), servers) server = servers[0] self.addCleanup(server.delete) # create a volume for attachment volume = self.cinder.volumes.create(1) self.addCleanup(volume.delete) # allow volume to become available self.wait_for_volume_status(volume, 'available') # attach the volume self.nova('volume-attach', params="%s %s" % (name, volume.id)) # volume needs to transition to 'in-use' to be attached self.wait_for_volume_status(volume, 'in-use') # clean up on success self.nova('volume-detach', params="%s %s" % (name, volume.id)) self.wait_for_volume_status(volume, 'available') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/legacy/test_keypairs.py0000664000175000017500000000625100000000000030020 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 tempfile from tempest.lib import exceptions from novaclient.tests.functional import base from novaclient.tests.functional.v2 import fake_crypto class TestKeypairsNovaClient(base.ClientTestBase): """Keypairs functional tests.""" COMPUTE_API_VERSION = "2.1" def _serialize_kwargs(self, kwargs): kwargs_pairs = ['--%(key)s %(val)s' % {'key': key.replace('_', '-'), 'val': val} for key, val in kwargs.items()] return " ".join(kwargs_pairs) def _create_keypair(self, **kwargs): key_name = self._raw_create_keypair(**kwargs) self.addCleanup(self.nova, 'keypair-delete %s' % key_name) return key_name def _raw_create_keypair(self, **kwargs): key_name = self.name_generate() kwargs_str = self._serialize_kwargs(kwargs) self.nova('keypair-add %s %s' % (kwargs_str, key_name)) return key_name def _show_keypair(self, key_name): return self.nova('keypair-show %s' % key_name) def _list_keypairs(self): return self.nova('keypair-list') def _delete_keypair(self, key_name): self.nova('keypair-delete %s' % key_name) def _create_public_key_file(self, public_key): pubfile = tempfile.mkstemp()[1] with open(pubfile, 'w') as f: f.write(public_key) return pubfile def test_create_keypair(self): key_name = self._create_keypair() keypair = self._show_keypair(key_name) self.assertIn(key_name, keypair) return keypair def _test_import_keypair(self, fingerprint, **create_kwargs): key_name = self._create_keypair(**create_kwargs) keypair = self._show_keypair(key_name) self.assertIn(key_name, keypair) self.assertIn(fingerprint, keypair) return keypair def test_import_keypair(self): pub_key, fingerprint = fake_crypto.get_ssh_pub_key_and_fingerprint() pub_key_file = self._create_public_key_file(pub_key) self._test_import_keypair(fingerprint, pub_key=pub_key_file) def test_list_keypair(self): key_name = self._create_keypair() keypairs = self._list_keypairs() self.assertIn(key_name, keypairs) def test_delete_keypair(self): key_name = self._raw_create_keypair() keypair = self._show_keypair(key_name) self.assertIsNotNone(keypair) self._delete_keypair(key_name) # keypair-show should fail if no keypair with given name is found. self.assertRaises(exceptions.CommandFailed, self._show_keypair, key_name) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/legacy/test_os_services.py0000664000175000017500000000744000000000000030516 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 novaclient.tests.functional import base class TestOsServicesNovaClient(base.ClientTestBase): """Functional tests for os-services attributes""" COMPUTE_API_VERSION = "2.1" def test_os_services_list(self): table = self.nova('service-list') for serv in self.client.services.list(): self.assertIn(serv.binary, table) def test_os_service_disable_enable(self): # Disable and enable Nova services in accordance with list of nova # services returned by client # NOTE(sdague): service disable has the chance in racing # with other tests. Now functional tests for novaclient are launched # in serial way (https://review.opendev.org/#/c/217768/), but # it's a potential issue for making these tests parallel in the future for serv in self.client.services.list(): # In Pike the os-services API was made multi-cell aware and it # looks up services by host, which uses the host mapping record # in the API DB which is only populated for nova-compute services, # effectively making it impossible to perform actions like enable # or disable non-nova-compute services since the API won't be able # to find them. So filter out anything that's not nova-compute. if serv.binary != 'nova-compute': continue host = self._get_column_value_from_single_row_table( self.nova('service-list --binary %s' % serv.binary), 'Host') service = self.nova('service-disable %s' % host) self.addCleanup(self.nova, 'service-enable', params=host) status = self._get_column_value_from_single_row_table( service, 'Status') self.assertEqual('disabled', status) service = self.nova('service-enable %s' % host) status = self._get_column_value_from_single_row_table( service, 'Status') self.assertEqual('enabled', status) def test_os_service_disable_log_reason(self): for serv in self.client.services.list(): # In Pike the os-services API was made multi-cell aware and it # looks up services by host, which uses the host mapping record # in the API DB which is only populated for nova-compute services, # effectively making it impossible to perform actions like enable # or disable non-nova-compute services since the API won't be able # to find them. So filter out anything that's not nova-compute. if serv.binary != 'nova-compute': continue host = self._get_column_value_from_single_row_table( self.nova('service-list --binary %s' % serv.binary), 'Host') service = self.nova( 'service-disable --reason test_disable %s' % host) self.addCleanup(self.nova, 'service-enable', params=host) status = self._get_column_value_from_single_row_table( service, 'Status') log_reason = self._get_column_value_from_single_row_table( service, 'Disabled Reason') self.assertEqual('disabled', status) self.assertEqual('test_disable', log_reason) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/legacy/test_quotas.py0000664000175000017500000000374400000000000027511 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 novaclient.tests.functional import base class TestQuotasNovaClient(base.ClientTestBase): """Nova quotas functional tests.""" COMPUTE_API_VERSION = "2.1" _quota_resources = ['instances', 'cores', 'ram', 'floating_ips', 'fixed_ips', 'metadata_items', 'injected_files', 'injected_file_content_bytes', 'injected_file_path_bytes', 'key_pairs', 'security_groups', 'security_group_rules', 'server_groups', 'server_group_members'] def test_quotas_update(self): # `nova quota-update` requires tenant-id. tenant_id = self._get_project_id(self.cli_clients.tenant_name) self.addCleanup(self.client.quotas.delete, tenant_id) original_quotas = self.client.quotas.get(tenant_id) difference = 10 params = [tenant_id] for quota_name in self._quota_resources: params.append("--%(name)s %(value)s" % { "name": quota_name.replace("_", "-"), "value": getattr(original_quotas, quota_name) + difference}) self.nova("quota-update", params=" ".join(params)) updated_quotas = self.client.quotas.get(tenant_id) for quota_name in self._quota_resources: self.assertEqual(getattr(original_quotas, quota_name), getattr(updated_quotas, quota_name) - difference) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/legacy/test_readonly_nova.py0000664000175000017500000001136600000000000031034 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 tempest.lib import decorators from tempest.lib import exceptions from novaclient.tests.functional import base class SimpleReadOnlyNovaClientTest(base.ClientTestBase): """Read only functional python-novaclient tests. This only exercises client commands that are read only. """ COMPUTE_API_VERSION = "2.1" def test_admin_fake_action(self): self.assertRaises(exceptions.CommandFailed, self.nova, 'this-does-nova-exist') # NOTE(jogo): Commands in order listed in 'nova help' def test_admin_aggregate_list(self): self.nova('aggregate-list') def test_admin_availability_zone_list(self): self.assertIn("internal", self.nova('availability-zone-list')) def test_admin_flavor_access_list(self): self.assertRaises(exceptions.CommandFailed, self.nova, 'flavor-access-list') # Failed to get access list for public flavor type self.assertRaises(exceptions.CommandFailed, self.nova, 'flavor-access-list', params='--flavor m1.tiny') def test_admin_flavor_list(self): self.assertIn("Memory_MiB", self.nova('flavor-list')) def test_admin_hypervisor_list(self): self.nova('hypervisor-list') @decorators.skip_because(bug="1157349") def test_admin_interface_list(self): self.nova('interface-list') def test_admin_keypair_list(self): self.nova('keypair-list') def test_admin_list(self): self.nova('list') self.nova('list', params='--all-tenants 1') self.nova('list', params='--all-tenants 0') self.assertRaises(exceptions.CommandFailed, self.nova, 'list', params='--all-tenants bad') def test_admin_server_group_list(self): self.nova('server-group-list') def test_admin_service_list(self): self.nova('service-list') def test_admin_usage(self): self.nova('usage') def test_admin_usage_list(self): self.nova('usage-list') def test_admin_help(self): self.nova('help') def test_agent_list(self): ex = self.assertRaises(exceptions.CommandFailed, self.nova, 'agent-list') self.assertIn( "This resource is no longer available. " "No forwarding address is given. (HTTP 410)", str(ex)) self.assertIn( "This command has been deprecated since 23.0.0 Wallaby Release " "and will be removed in the first major release " "after the Nova server 24.0.0 X release.", str(ex.stderr)) ex = self.assertRaises(exceptions.CommandFailed, self.nova, 'agent-list', flags='--debug') self.assertIn( "This resource is no longer available. " "No forwarding address is given. (HTTP 410)", str(ex)) self.assertIn( "This command has been deprecated since 23.0.0 Wallaby Release " "and will be removed in the first major release " "after the Nova server 24.0.0 X release.", str(ex.stderr)) def test_migration_list(self): self.nova('migration-list') self.nova('migration-list', flags='--debug') def test_version_list(self): self.nova('version-list', flags='--debug') def test_quota_defaults(self): self.nova('quota-defaults') self.nova('quota-defaults', flags='--debug') def test_bash_completion(self): self.nova('bash-completion') # Optional arguments: def test_admin_version(self): self.nova('', flags='--version') def test_admin_debug_list(self): self.nova('list', flags='--debug') def test_admin_timeout(self): self.nova('list', flags='--timeout %d' % 60) def test_admin_timing(self): self.nova('list', flags='--timing') def test_admin_invalid_bypass_url(self): self.assertRaises(exceptions.CommandFailed, self.nova, 'list', flags='--os-endpoint-override badurl') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/legacy/test_server_groups.py0000664000175000017500000000366000000000000031077 0ustar00zuulzuul00000000000000# Copyright 2015 Huawei Technology 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 novaclient.tests.functional import base class TestServerGroupClient(base.ClientTestBase): """Server groups v2.1 functional tests.""" COMPUTE_API_VERSION = "2.1" def _create_sg(self, policy): sg_name = self.name_generate() output = self.nova('server-group-create %s %s' % (sg_name, policy)) sg_id = self._get_column_value_from_single_row_table(output, "Id") return sg_id def test_create_server_group(self): sg_id = self._create_sg("affinity") self.addCleanup(self.nova, 'server-group-delete %s' % sg_id) sg = self.nova('server-group-get %s' % sg_id) result = self._get_column_value_from_single_row_table(sg, "Id") self.assertEqual(sg_id, result) def test_list_server_group(self): sg_id = self._create_sg("affinity") self.addCleanup(self.nova, 'server-group-delete %s' % sg_id) sg = self.nova('server-group-list') result = self._get_column_value_from_single_row_table(sg, "Id") self.assertEqual(sg_id, result) def test_delete_server_group(self): sg_id = self._create_sg("affinity") sg = self.nova('server-group-get %s' % sg_id) result = self._get_column_value_from_single_row_table(sg, "Id") self.assertIsNotNone(result) self.nova('server-group-delete %s' % sg_id) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/legacy/test_servers.py0000664000175000017500000001701700000000000027664 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 datetime from oslo_utils import timeutils from novaclient.tests.functional import base class TestServersBootNovaClient(base.ClientTestBase): """Servers boot functional tests.""" COMPUTE_API_VERSION = "2.1" def _boot_server_with_legacy_bdm(self, bdm_params=()): volume_size = 1 volume_name = self.name_generate() volume = self.cinder.volumes.create(size=volume_size, name=volume_name, imageRef=self.image.id) self.wait_for_volume_status(volume, "available") if (len(bdm_params) >= 3 and bdm_params[2] == '1'): delete_volume = False else: delete_volume = True bdm_params = ':'.join(bdm_params) if bdm_params: bdm_params = ''.join((':', bdm_params)) params = ( "%(name)s --flavor %(flavor)s --poll " "--block-device-mapping vda=%(volume_id)s%(bdm_params)s" % { "name": self.name_generate(), "flavor": self.flavor.id, "volume_id": volume.id, "bdm_params": bdm_params}) # check to see if we have to pass in a network id if self.multiple_networks: params += ' --nic net-id=%s' % self.network.id server_info = self.nova("boot", params=params) server_id = self._get_value_from_the_table(server_info, "id") self.client.servers.delete(server_id) self.wait_for_resource_delete(server_id, self.client.servers) if delete_volume: self.cinder.volumes.delete(volume.id) self.wait_for_resource_delete(volume.id, self.cinder.volumes) def test_boot_server_with_legacy_bdm(self): # bdm v1 format # ::: # params = (type, size, delete-on-terminate) params = ('', '', '1') self._boot_server_with_legacy_bdm(bdm_params=params) def test_boot_server_with_legacy_bdm_volume_id_only(self): self._boot_server_with_legacy_bdm() def test_boot_server_with_net_name(self): server_info = self.nova("boot", params=( "%(name)s --flavor %(flavor)s --image %(image)s --poll " "--nic net-name=%(net-name)s" % {"name": self.name_generate(), "image": self.image.id, "flavor": self.flavor.id, "net-name": self.network.name})) server_id = self._get_value_from_the_table(server_info, "id") self.client.servers.delete(server_id) self.wait_for_resource_delete(server_id, self.client.servers) def test_boot_server_using_image_with(self): """Scenario test which does the following: 1. Create a server. 2. Create a snapshot image of the server with a special meta key. 3. Create a second server using the --image-with option using the meta key stored in the snapshot image created in step 2. """ params = ( '--flavor %(flavor)s --image %(image)s --poll ' 'image-with-server-1' % {'image': self.image.id, 'flavor': self.flavor.id}) # check to see if we have to pass in a network id if self.multiple_networks: params += ' --nic net-id=%s' % self.network.id # create the first server and wait for it to be active server_info = self.nova('boot', params=params) server_id = self._get_value_from_the_table(server_info, 'id') self.addCleanup(self._cleanup_server, server_id) # create a snapshot of the server with an image metadata key snapshot_info = self.nova('image-create', params=( '--metadata image_with_meta=%(meta_value)s ' '--show --poll %(server_id)s image-with-snapshot' % { 'meta_value': server_id, 'server_id': server_id})) # get the snapshot image id out of the output table for the second # server create request snapshot_id = self._get_value_from_the_table(snapshot_info, 'id') self.addCleanup(self.glance.images.delete, snapshot_id) # verify the metadata was set on the snapshot image meta_value = self._get_value_from_the_table( snapshot_info, 'image_with_meta') self.assertEqual(server_id, meta_value) params = ( '--flavor %(flavor)s --image-with image_with_meta=%(meta_value)s ' '--poll image-with-server-2' % {'meta_value': server_id, 'flavor': self.flavor.id}) # check to see if we have to pass in a network id if self.multiple_networks: params += ' --nic net-id=%s' % self.network.id # create the second server using --image-with server_info = self.nova('boot', params=params) server_id = self._get_value_from_the_table(server_info, 'id') self.addCleanup(self._cleanup_server, server_id) class TestServersListNovaClient(base.ClientTestBase): """Servers list functional tests.""" COMPUTE_API_VERSION = "2.1" def _create_servers(self, name, number): return [self._create_server(name) for i in range(number)] def test_list_with_limit(self): name = self.name_generate() self._create_servers(name, 2) output = self.nova("list", params="--limit 1 --name %s" % name) # Cut header and footer of the table servers = output.split("\n")[3:-2] self.assertEqual(1, len(servers), output) def test_list_with_changes_since(self): now = datetime.datetime.isoformat(timeutils.utcnow()) name = self.name_generate() self._create_servers(name, 1) output = self.nova("list", params="--changes-since %s" % now) self.assertIn(name, output, output) now = datetime.datetime.isoformat(timeutils.utcnow()) output = self.nova("list", params="--changes-since %s" % now) self.assertNotIn(name, output, output) def test_list_all_servers(self): name = self.name_generate() precreated_servers = self._create_servers(name, 3) # there are no possibility to exceed the limit on API side, so just # check that "-1" limit processes by novaclient side output = self.nova("list", params="--limit -1 --name %s" % name) # Cut header and footer of the table for server in precreated_servers: self.assertIn(server.id, output) def test_list_minimal(self): server = self._create_server() server_output = self.nova("list --minimal") # The only fields output are "ID" and "Name" output_uuid = self._get_column_value_from_single_row_table( server_output, 'ID') output_name = self._get_column_value_from_single_row_table( server_output, 'Name') self.assertEqual(output_uuid, server.id) self.assertEqual(output_name, server.name) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/legacy/test_usage.py0000664000175000017500000000536500000000000027302 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 datetime from novaclient.tests.functional import base class TestUsageCLI(base.ClientTestBase): COMPUTE_API_VERSION = '2.1' def _get_num_servers_from_usage_output(self): output = self.nova('usage') servers = self._get_column_value_from_single_row_table( output, 'Servers') return int(servers) def _get_num_servers_by_tenant_from_usage_output(self): tenant_id = self._get_project_id(self.cli_clients.tenant_name) output = self.nova('usage --tenant=%s' % tenant_id) servers = self._get_column_value_from_single_row_table( output, 'Servers') return int(servers) def test_usage(self): before = self._get_num_servers_from_usage_output() self._create_server() after = self._get_num_servers_from_usage_output() self.assertGreater(after, before) def test_usage_tenant(self): before = self._get_num_servers_by_tenant_from_usage_output() self._create_server() after = self._get_num_servers_by_tenant_from_usage_output() self.assertGreater(after, before) class TestUsageClient(base.ClientTestBase): COMPUTE_API_VERSION = '2.1' def _create_servers_in_time_window(self): start = datetime.datetime.now() self._create_server() self._create_server() end = datetime.datetime.now() return start, end def test_get(self): start, end = self._create_servers_in_time_window() tenant_id = self._get_project_id(self.cli_clients.tenant_name) usage = self.client.usage.get(tenant_id, start=start, end=end) self.assertEqual(tenant_id, usage.tenant_id) self.assertGreaterEqual(len(usage.server_usages), 2) def test_list(self): start, end = self._create_servers_in_time_window() tenant_id = self._get_project_id(self.cli_clients.tenant_name) usages = self.client.usage.list(start=start, end=end, detailed=True) tenant_ids = [usage.tenant_id for usage in usages] self.assertIn(tenant_id, tenant_ids) for usage in usages: if usage.tenant_id == tenant_id: self.assertGreaterEqual(len(usage.server_usages), 2) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/test_aggregates.py0000664000175000017500000000350100000000000027031 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 novaclient.tests.functional import base class TestAggregatesNovaClient(base.ClientTestBase): COMPUTE_API_VERSION = '2.1' def setUp(self): super(TestAggregatesNovaClient, self).setUp() self.agg1 = self.name_generate() self.agg2 = self.name_generate() self.addCleanup(self._clean_aggregates) def _clean_aggregates(self): for a in (self.agg1, self.agg2): try: self.nova('aggregate-delete', params=a) except Exception: pass def test_aggregate_update_name(self): self.nova('aggregate-create', params=self.agg1) self.nova('aggregate-update', params='--name=%s %s' % (self.agg2, self.agg1)) output = self.nova('aggregate-show', params=self.agg2) self.assertIn(self.agg2, output) self.nova('aggregate-delete', params=self.agg2) def test_aggregate_update_az(self): self.nova('aggregate-create', params=self.agg2) self.nova('aggregate-update', params='--availability-zone=myaz %s' % self.agg2) output = self.nova('aggregate-show', params=self.agg2) self.assertIn('myaz', output) self.nova('aggregate-delete', params=self.agg2) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/test_consoles.py0000664000175000017500000000261200000000000026547 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. from novaclient.tests.functional.v2.legacy import test_consoles class TestConsolesNovaClientV26(test_consoles.TestConsolesNovaClient): """Consoles functional tests for >=v2.6 api microversions.""" COMPUTE_API_VERSION = "2.6" def test_vnc_console_get(self): self._test_vnc_console_get() def test_spice_console_get(self): self._test_spice_console_get() def test_rdp_console_get(self): self._test_rdp_console_get() def test_serial_console_get(self): self._test_serial_console_get() class TestConsolesNovaClientV28(test_consoles.TestConsolesNovaClient): """Consoles functional tests for >=v2.8 api microversions.""" COMPUTE_API_VERSION = "2.8" def test_webmks_console_get(self): self._test_console_get('get-mks-console %s ', 'webmks') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/test_device_tagging.py0000664000175000017500000001561500000000000027670 0ustar00zuulzuul00000000000000# Copyright (C) 2016, 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. from tempest.lib import exceptions from novaclient.tests.functional import base class TestBlockDeviceTaggingCLIError(base.ClientTestBase): """Negative test that asserts that creating a server with a tagged block device with a specific microversion will fail. """ COMPUTE_API_VERSION = "2.31" def test_boot_server_with_tagged_block_devices_with_error(self): try: output = self.nova('boot', params=( '%(name)s --flavor %(flavor)s --poll ' '--nic net-id=%(net-uuid)s ' '--block-device ' 'source=image,dest=volume,id=%(image)s,size=1,bootindex=0,' 'shutdown=remove,tag=bar' % {'name': self.name_generate(), 'flavor': self.flavor.id, 'net-uuid': self.network.id, 'image': self.image.id})) except exceptions.CommandFailed as e: self.assertIn("ERROR (CommandError): " "'tag' in block device mapping is not supported " "in API version %s." % self.COMPUTE_API_VERSION, str(e)) else: server_id = self._get_value_from_the_table(output, 'id') self.client.servers.delete(server_id) self.wait_for_resource_delete(server_id, self.client.servers) self.fail("Booting a server with block device tag is not failed.") class TestNICDeviceTaggingCLIError(base.ClientTestBase): """Negative test that asserts that creating a server with a tagged nic with a specific microversion will fail. """ COMPUTE_API_VERSION = "2.31" def test_boot_server_with_tagged_nic_devices_with_error(self): try: output = self.nova('boot', params=( '%(name)s --flavor %(flavor)s --poll ' '--nic net-id=%(net-uuid)s,tag=foo ' '--block-device ' 'source=image,dest=volume,id=%(image)s,size=1,bootindex=0,' 'shutdown=remove' % {'name': self.name_generate(), 'flavor': self.flavor.id, 'net-uuid': self.network.id, 'image': self.image.id})) except exceptions.CommandFailed as e: self.assertIn('Invalid nic argument', str(e)) else: server_id = self._get_value_from_the_table(output, 'id') self.client.servers.delete(server_id) self.wait_for_resource_delete(server_id, self.client.servers) self.fail("Booting a server with network interface tag " "is not failed.") class TestBlockDeviceTaggingCLI(base.ClientTestBase): """Tests that creating a server with a tagged block device will work with the 2.32 microversion, where the feature was originally added. """ COMPUTE_API_VERSION = "2.32" def test_boot_server_with_tagged_block_devices(self): server_info = self.nova('boot', params=( '%(name)s --flavor %(flavor)s --poll ' '--nic net-id=%(net-uuid)s ' '--block-device ' 'source=image,dest=volume,id=%(image)s,size=1,bootindex=0,' 'shutdown=remove,tag=bar' % {'name': self.name_generate(), 'flavor': self.flavor.id, 'net-uuid': self.network.id, 'image': self.image.id})) server_id = self._get_value_from_the_table(server_info, 'id') self.client.servers.delete(server_id) self.wait_for_resource_delete(server_id, self.client.servers) class TestNICDeviceTaggingCLI(base.ClientTestBase): """Tests that creating a server with a tagged nic will work with the 2.32 microversion, where the feature was originally added. """ COMPUTE_API_VERSION = "2.32" def test_boot_server_with_tagged_nic_devices(self): server_info = self.nova('boot', params=( '%(name)s --flavor %(flavor)s --poll ' '--nic net-id=%(net-uuid)s,tag=foo ' '--block-device ' 'source=image,dest=volume,id=%(image)s,size=1,bootindex=0,' 'shutdown=remove' % {'name': self.name_generate(), 'flavor': self.flavor.id, 'net-uuid': self.network.id, 'image': self.image.id})) server_id = self._get_value_from_the_table(server_info, 'id') self.client.servers.delete(server_id) self.wait_for_resource_delete(server_id, self.client.servers) class TestDeviceTaggingCLIV233(TestBlockDeviceTaggingCLIError, TestNICDeviceTaggingCLI): """Tests that in microversion 2.33, creating a server with a tagged block device will fail, but creating a server with a tagged nic will succeed. """ COMPUTE_API_VERSION = "2.33" class TestDeviceTaggingCLIV236(TestBlockDeviceTaggingCLIError, TestNICDeviceTaggingCLI): """Tests that in microversion 2.36, creating a server with a tagged block device will fail, but creating a server with a tagged nic will succeed. This is testing the boundary before 2.37 where nic tagging was broken. """ COMPUTE_API_VERSION = "2.36" class TestDeviceTaggingCLIV237(TestBlockDeviceTaggingCLIError, TestNICDeviceTaggingCLIError): """Tests that in microversion 2.37, creating a server with either a tagged block device or tagged nic would fail. """ COMPUTE_API_VERSION = "2.37" class TestDeviceTaggingCLIV241(TestBlockDeviceTaggingCLIError, TestNICDeviceTaggingCLIError): """Tests that in microversion 2.41, creating a server with either a tagged block device or tagged nic would fail. This is testing the boundary before 2.42 where block device tags and nic tags were fixed for server create requests. """ COMPUTE_API_VERSION = "2.41" class TestDeviceTaggingCLIV242(TestBlockDeviceTaggingCLI, TestNICDeviceTaggingCLI): """Tests that in microversion 2.42 you could once again create a server with a tagged block device or a tagged nic. """ COMPUTE_API_VERSION = "2.42" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/test_extended_attributes.py0000664000175000017500000000407600000000000030776 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_serialization import jsonutils from novaclient.tests.functional.v2.legacy import test_extended_attributes class TestExtAttrNovaClientV23(test_extended_attributes.TestExtAttrNovaClient): """Functional tests for os-extended-server-attributes, microversion 2.3""" COMPUTE_API_VERSION = "2.3" def test_extended_server_attributes(self): server, volume = self._create_server_and_attach_volume() table = self.nova('show %s' % server.id) # Check that attributes listed below exist in 'nova show' table and # they are exactly Property attributes (not an instance's name, e.g.) # The _get_value_from_the_table() will raise an exception # if attr is not a key of the table dict (first column) for attr in ['OS-EXT-SRV-ATTR:reservation_id', 'OS-EXT-SRV-ATTR:launch_index', 'OS-EXT-SRV-ATTR:ramdisk_id', 'OS-EXT-SRV-ATTR:kernel_id', 'OS-EXT-SRV-ATTR:hostname', 'OS-EXT-SRV-ATTR:root_device_name']: self._get_value_from_the_table(table, attr) # Check that attribute given below also exists in 'nova show' table # as a key (first column) of table dict volume_attr = self._get_value_from_the_table( table, 'os-extended-volumes:volumes_attached') # Check that 'delete_on_termination' exists as a key # of volume_attr dict self.assertIn('delete_on_termination', jsonutils.loads(volume_attr)[0]) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/test_flavor.py0000664000175000017500000000515600000000000026221 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 novaclient.tests.functional import base class TestFlavorNovaClientV274(base.ClientTestBase): """Functional tests for flavors""" COMPUTE_API_VERSION = "2.74" # NOTE(gmann): Before microversion 2.75, default value of 'swap' field is # returned as empty string. SWAP_DEFAULT = "" def _create_flavor(self, swap=None): flv_name = self.name_generate() cmd = 'flavor-create %s auto 512 1 1' if swap: cmd = cmd + (' --swap %s' % swap) out = self.nova(cmd % flv_name) self.addCleanup(self.nova, 'flavor-delete %s' % flv_name) return out, flv_name def test_create_flavor_with_no_swap(self): out, _ = self._create_flavor() self.assertEqual( self.SWAP_DEFAULT, self._get_column_value_from_single_row_table(out, "Swap")) def test_update_flavor_with_no_swap(self): _, flv_name = self._create_flavor() out = self.nova('flavor-update %s new-description' % flv_name) self.assertEqual( self.SWAP_DEFAULT, self._get_column_value_from_single_row_table(out, "Swap")) def test_show_flavor_with_no_swap(self): _, flv_name = self._create_flavor() out = self.nova('flavor-show %s' % flv_name) self.assertEqual(self.SWAP_DEFAULT, self._get_value_from_the_table(out, "swap")) def test_list_flavor_with_no_swap(self): self._create_flavor() out = self.nova('flavor-list') self.assertEqual( self.SWAP_DEFAULT, self._get_column_value_from_single_row_table(out, "Swap")) def test_create_flavor_with_swap(self): out, _ = self._create_flavor(swap=10) self.assertEqual( '10', self._get_column_value_from_single_row_table(out, "Swap")) class TestFlavorNovaClientV275(TestFlavorNovaClientV274): """Functional tests for flavors""" COMPUTE_API_VERSION = "2.75" # NOTE(gmann): Since microversion 2.75, default value of 'swap' field is # returned as 0. SWAP_DEFAULT = '0' ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/test_flavor_access.py0000664000175000017500000000270400000000000027536 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 novaclient.tests.functional.v2.legacy import test_flavor_access class TestFlvAccessNovaClientV27(test_flavor_access.TestFlvAccessNovaClient): """Check that an attempt to grant an access to a public flavor for the given tenant fails with Conflict error in accordance with 2.7 microversion REST API History """ COMPUTE_API_VERSION = "2.7" def test_add_access_public_flavor(self): flv_name = self.name_generate() self.nova('flavor-create %s auto 512 1 1' % flv_name) self.addCleanup(self.nova, 'flavor-delete %s' % flv_name) output = self.nova('flavor-access-add %s %s' % (flv_name, self.project_id), fail_ok=True, merge_stderr=True) self.assertIn("ERROR (Conflict): " "Can not add access to a public flavor. (HTTP 409) ", output) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/test_hypervisors.py0000664000175000017500000000307500000000000027323 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 novaclient.tests.functional.v2.legacy import test_hypervisors class TestHypervisorsV28(test_hypervisors.TestHypervisors): COMPUTE_API_VERSION = "2.28" def test_list(self): self._test_list(dict) class TestHypervisorsV2_53(TestHypervisorsV28): COMPUTE_API_VERSION = "2.53" def test_list(self): self._test_list(cpu_info_type=dict, uuid_as_id=True) def test_search_with_details(self): # First find a hypervisor from the list to search on. hypervisors = self.client.hypervisors.list() # Now search for that hypervisor with details. hypervisor = hypervisors[0] hypervisors = self.client.hypervisors.search( hypervisor.hypervisor_hostname, detailed=True) self.assertEqual(1, len(hypervisors)) hypervisor = hypervisors[0] # We know we got details if service is in the response. self.assertIsNotNone(hypervisor.service, 'Expected service in hypervisor: %s' % hypervisor) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/test_image_meta.py0000664000175000017500000000237100000000000027014 0ustar00zuulzuul00000000000000# Copyright 2016 Mirantis, 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 novaclient.tests.functional import base class TestImageMetaV239(base.ClientTestBase): """Functional tests for image-meta proxy API.""" # 'image-metadata' proxy API was deprecated in 2.39 but the CLI should # fallback to 2.35 and emit a warning. COMPUTE_API_VERSION = "2.39" def test_limits(self): """Tests that 2.39 won't return 'maxImageMeta' resource limit and the CLI output won't show it. """ output = self.nova('limits') # assert that MaxImageMeta isn't in the table output self.assertRaises(ValueError, self._get_value_from_the_table, output, 'maxImageMeta') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/test_instance_action.py0000664000175000017500000002230700000000000030066 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 time from oslo_utils import timeutils from oslo_utils import uuidutils from tempest.lib import exceptions from novaclient.tests.functional import base class TestInstanceActionCLI(base.ClientTestBase): COMPUTE_API_VERSION = "2.21" def _test_cmd_with_not_existing_instance(self, cmd, args): try: self.nova("%s %s" % (cmd, args)) except exceptions.CommandFailed as e: self.assertIn("ERROR (NotFound):", str(e)) else: self.fail("%s is not failed on non existing instance." % cmd) def test_show_action_with_not_existing_instance(self): name_or_uuid = uuidutils.generate_uuid() request_id = uuidutils.generate_uuid() self._test_cmd_with_not_existing_instance( "instance-action", "%s %s" % (name_or_uuid, request_id)) def test_list_actions_with_not_existing_instance(self): name_or_uuid = uuidutils.generate_uuid() self._test_cmd_with_not_existing_instance("instance-action-list", name_or_uuid) def test_show_and_list_actions_on_deleted_instance(self): server = self._create_server(add_cleanup=False) server.delete() self.wait_for_resource_delete(server, self.client.servers) output = self.nova("instance-action-list %s" % server.id) # NOTE(andreykurilin): output is not a single row table, so we can # obtain just "create" action. It should be enough for testing # "nova instance-action " command request_id = self._get_column_value_from_single_row_table( output, "Request_ID") output = self.nova("instance-action %s %s" % (server.id, request_id)) # ensure that obtained action is "create". self.assertEqual("create", self._get_value_from_the_table(output, "action")) class TestInstanceActionCLIV258(TestInstanceActionCLI): """Instance action functional tests for v2.58 nova-api microversion.""" COMPUTE_API_VERSION = "2.58" # Does this microversion return a hostId field in the event response? expect_event_hostId_field = False def test_list_instance_action_with_marker_and_limit(self): server = self._create_server() server.stop() # The actions are sorted by created_at in descending order, # and now we have two actions: create and stop. output = self.nova("instance-action-list %s --limit 1" % server.id) marker_req = self._get_column_value_from_single_row_table( output, "Request_ID") action = self._get_list_of_values_from_single_column_table( output, "Action") # The stop action was most recently created so it's what # we get back when limit=1. self.assertEqual(action, ['stop']) output = self.nova("instance-action-list %s --limit 1 " "--marker %s" % (server.id, marker_req)) action = self._get_list_of_values_from_single_column_table( output, "Action") self.assertEqual(action, ['create']) if not self.expect_event_hostId_field: # Make sure host and hostId are not in the response when # microversion is less than 2.62. output = self.nova("instance-action %s %s" % ( server.id, marker_req)) self.assertNotIn("'host'", output) self.assertNotIn("'hostId'", output) def test_list_instance_action_with_changes_since(self): # Ignore microseconds to make this a deterministic test. before_create = timeutils.utcnow().replace(microsecond=0).isoformat() server = self._create_server() time.sleep(2) before_stop = timeutils.utcnow().replace(microsecond=0).isoformat() server.stop() create_output = self.nova( "instance-action-list %s --changes-since %s" % (server.id, before_create)) action = self._get_list_of_values_from_single_column_table( create_output, "Action") # The actions are sorted by created_at in descending order. self.assertEqual(action, ['create', 'stop']) stop_output = self.nova("instance-action-list %s --changes-since %s" % (server.id, before_stop)) action = self._get_list_of_values_from_single_column_table( stop_output, "Action") # Provide detailed debug information if this fails. self.assertEqual(action, ['stop'], 'Expected to find the stop action with ' '--changes-since=%s but got: %s\n\n' 'First instance-action-list output: %s' % (before_stop, stop_output, create_output)) class TestInstanceActionCLIV262(TestInstanceActionCLIV258, base.TenantTestBase): """Instance action functional tests for v2.62 nova-api microversion.""" COMPUTE_API_VERSION = "2.62" expect_event_hostId_field = True def test_show_actions_with_host(self): name = self.name_generate() # Create server with non-admin user server = self.another_nova('boot --flavor %s --image %s --poll %s' % (self.flavor.name, self.image.name, name)) server_id = self._get_value_from_the_table(server, 'id') self.addCleanup(self.client.servers.delete, server_id) output = self.nova("instance-action-list %s" % server_id) request_id = self._get_column_value_from_single_row_table( output, "Request_ID") # Only the 'hostId' are exposed to non-admin output = self.another_nova( "instance-action %s %s" % (server_id, request_id)) self.assertNotIn("'host'", output) self.assertIn("'hostId'", output) # The 'host' and 'hostId' are exposed to admin output = self.nova("instance-action %s %s" % (server_id, request_id)) self.assertIn("'host'", output) self.assertIn("'hostId'", output) class TestInstanceActionCLIV266(TestInstanceActionCLIV258, base.TenantTestBase): """Instance action functional tests for v2.66 nova-api microversion.""" COMPUTE_API_VERSION = "2.66" expect_event_hostId_field = True def _wait_for_instance_actions(self, server, expected_num_of_actions): start_time = time.time() # Time out after 60 seconds while time.time() - start_time < 60: actions = self.client.instance_action.list(server) if len(actions) == expected_num_of_actions: break # Sleep 1 second time.sleep(1) else: self.fail("The number of instance actions for server %s " "was not %d after 60 s" % (server.id, expected_num_of_actions)) # NOTE(takashin): In some DBMSs (e.g. MySQL 5.7), fractions # (millisecond and microsecond) of DateTime column is not stored # by default. So sleep an extra second. time.sleep(1) # Return time return timeutils.utcnow().isoformat() def test_list_instance_action_with_changes_before(self): server = self._create_server() end_create = self._wait_for_instance_actions(server, 1) # NOTE(takashin): In some DBMSs (e.g. MySQL 5.7), fractions # (millisecond and microsecond) of DateTime column is not stored # by default. So sleep a second. time.sleep(1) server.stop() end_stop = self._wait_for_instance_actions(server, 2) stop_output = self.nova( "instance-action-list %s --changes-before %s" % (server.id, end_stop)) action = self._get_list_of_values_from_single_column_table( stop_output, "Action") # The actions are sorted by created_at in descending order. self.assertEqual(['create', 'stop'], action, 'Expected to find the create and stop actions with ' '--changes-before=%s but got: %s\n\n' % (end_stop, stop_output)) create_output = self.nova( "instance-action-list %s --changes-before %s" % (server.id, end_create)) action = self._get_list_of_values_from_single_column_table( create_output, "Action") # Provide detailed debug information if this fails. self.assertEqual(['create'], action, 'Expected to find the create action with ' '--changes-before=%s but got: %s\n\n' 'First instance-action-list output: %s' % (end_create, create_output, stop_output)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/test_instance_usage_audit_log.py0000664000175000017500000000563000000000000031744 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 datetime from oslo_utils import timeutils from novaclient.tests.functional import base class TestInstanceUsageAuditLogCLI(base.ClientTestBase): COMPUTE_API_VERSION = '2.1' # NOTE(takashin): By default, 'instance_usage_audit' is False in nova. # So the instance usage audit log is not recorded. # Therefore an empty result can be got. # But it is tested here to call APIs and get responses normally. @staticmethod def _get_begin_end_time(): current = timeutils.utcnow() end = datetime.datetime(day=1, month=current.month, year=current.year) year = end.year if current.month == 1: year -= 1 month = 12 else: month = current.month - 1 begin = datetime.datetime(day=1, month=month, year=year) return (begin, end) def test_get_os_instance_usage_audit_log(self): (begin, end) = self._get_begin_end_time() expected = { 'hosts_not_run': '[]', 'log': '{}', 'num_hosts': '0', 'num_hosts_done': '0', 'num_hosts_not_run': '0', 'num_hosts_running': '0', 'overall_status': 'ALL hosts done. 0 errors.', 'total_errors': '0', 'total_instances': '0', 'period_beginning': str(begin), 'period_ending': str(end) } output = self.nova('instance-usage-audit-log') for key in expected.keys(): self.assertEqual(expected[key], self._get_value_from_the_table(output, key)) def test_get_os_instance_usage_audit_log_with_before(self): expected = { 'hosts_not_run': '[]', 'log': '{}', 'num_hosts': '0', 'num_hosts_done': '0', 'num_hosts_not_run': '0', 'num_hosts_running': '0', 'overall_status': 'ALL hosts done. 0 errors.', 'total_errors': '0', 'total_instances': '0', 'period_beginning': '2016-11-01 00:00:00', 'period_ending': '2016-12-01 00:00:00' } output = self.nova( 'instance-usage-audit-log --before "2016-12-10 13:59:59.999999"') for key in expected.keys(): self.assertEqual(expected[key], self._get_value_from_the_table(output, key)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/test_instances.py0000664000175000017500000000133500000000000026712 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 novaclient.tests.functional.v2.legacy import test_instances class TestInstanceCLI(test_instances.TestInstanceCLI): COMPUTE_API_VERSION = "2.latest" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/test_keypairs.py0000664000175000017500000001205100000000000026547 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 novaclient.tests.functional import base from novaclient.tests.functional.v2 import fake_crypto from novaclient.tests.functional.v2.legacy import test_keypairs class TestKeypairsNovaClientV22(test_keypairs.TestKeypairsNovaClient): """Keypairs functional tests for v2.2 nova-api microversion.""" COMPUTE_API_VERSION = "2.2" def test_create_keypair(self): keypair = super(TestKeypairsNovaClientV22, self).test_create_keypair() self.assertIn('ssh', keypair) def test_create_keypair_x509(self): key_name = self._create_keypair(key_type='x509') keypair = self._show_keypair(key_name) self.assertIn(key_name, keypair) self.assertIn('x509', keypair) def test_import_keypair(self): pub_key, fingerprint = fake_crypto.get_ssh_pub_key_and_fingerprint() pub_key_file = self._create_public_key_file(pub_key) keypair = self._test_import_keypair(fingerprint, pub_key=pub_key_file) self.assertIn('ssh', keypair) def test_import_keypair_x509(self): certif, fingerprint = fake_crypto.get_x509_cert_and_fingerprint() pub_key_file = self._create_public_key_file(certif) keypair = self._test_import_keypair(fingerprint, key_type='x509', pub_key=pub_key_file) self.assertIn('x509', keypair) class TestKeypairsNovaClientV210(base.TenantTestBase): """Keypairs functional tests for v2.10 nova-api microversion.""" COMPUTE_API_VERSION = "2.10" def test_create_and_list_keypair(self): name = self.name_generate() self.nova("keypair-add %s --user %s" % (name, self.user_id)) self.addCleanup(self.another_nova, "keypair-delete %s" % name) output = self.nova("keypair-list") self.assertRaises(ValueError, self._get_value_from_the_table, output, name) output_1 = self.another_nova("keypair-list") output_2 = self.nova("keypair-list --user %s" % self.user_id) self.assertEqual(output_1, output_2) # it should be table with one key-pair self.assertEqual(name, self._get_column_value_from_single_row_table( output_1, "Name")) output_1 = self.another_nova("keypair-show %s " % name) output_2 = self.nova("keypair-show --user %s %s" % (self.user_id, name)) self.assertEqual(output_1, output_2) self.assertEqual(self.user_id, self._get_value_from_the_table(output_1, "user_id")) def test_create_and_delete(self): name = self.name_generate() def cleanup(): # We should check keypair existence and remove it from correct user # if keypair is presented o = self.another_nova("keypair-list") if name in o: self.another_nova("keypair-delete %s" % name) self.nova("keypair-add %s --user %s" % (name, self.user_id)) self.addCleanup(cleanup) output = self.another_nova("keypair-list") self.assertEqual(name, self._get_column_value_from_single_row_table( output, "Name")) self.nova("keypair-delete %s --user %s " % (name, self.user_id)) output = self.another_nova("keypair-list") self.assertRaises( ValueError, self._get_column_value_from_single_row_table, output, "Name") class TestKeypairsNovaClientV235(base.TenantTestBase): """Keypairs functional tests for v2.35 nova-api microversion.""" COMPUTE_API_VERSION = "2.35" def test_create_and_list_keypair_with_marker_and_limit(self): names = [] for i in range(3): names.append(self.name_generate()) self.nova("keypair-add %s --user %s" % (names[i], self.user_id)) self.addCleanup(self.another_nova, "keypair-delete %s" % names[i]) # sort keypairs before pagination names = sorted(names) # list only one keypair after the first output_1 = self.another_nova("keypair-list --limit 1 --marker %s" % names[0]) output_2 = self.nova("keypair-list --limit 1 --marker %s --user %s" % (names[0], self.user_id)) self.assertEqual(output_1, output_2) # it should be table with only one second key-pair self.assertEqual( names[1], self._get_column_value_from_single_row_table(output_1, "Name")) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/test_migrations.py0000664000175000017500000001322200000000000027075 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_utils import uuidutils from novaclient.tests.functional import base class TestMigrationList(base.ClientTestBase): """Tests the "nova migration-list" command.""" def _filter_migrations( self, version, migration_type, source_compute): """ Filters migrations by --migration-type and --source-compute. :param version: The --os-compute-api-version to use. :param migration_type: The type of migrations to filter. :param source_compute: The source compute service hostname to filter. :return: output of the nova migration-list command with filters applied """ return self.nova('migration-list', flags='--os-compute-api-version %s' % version, params='--migration-type %s --source-compute %s' % ( migration_type, source_compute)) def test_migration_list(self): """Tests creating a server, resizing it and then listing and filtering migrations using various microversion milestones. """ server_id = self._create_server(flavor=self.flavor.id).id # Find the source compute by getting OS-EXT-SRV-ATTR:host from the # nova show output. server = self.nova('show', params='%s' % server_id) server_user_id = self._get_value_from_the_table(server, 'user_id') tenant_id = self._get_value_from_the_table(server, 'tenant_id') source_compute = self._get_value_from_the_table( server, 'OS-EXT-SRV-ATTR:host') # now resize up alternate_flavor = self._pick_alternate_flavor() self.nova('resize', params='%s %s --poll' % (server_id, alternate_flavor)) # now confirm the resize self.nova('resize-confirm', params='%s' % server_id) # wait for the server to be active and then check the migration list self._wait_for_state_change(server_id, 'active') # First, list migrations with v2.1 and our server id should be in the # output. There should only be the one migration. migrations = self.nova('migration-list', flags='--os-compute-api-version 2.1') instance_uuid = self._get_column_value_from_single_row_table( migrations, 'Instance UUID') self.assertEqual(server_id, instance_uuid) # A successfully confirmed resize should have the migration status # of "confirmed". migration_status = self._get_column_value_from_single_row_table( migrations, 'Status') self.assertEqual('confirmed', migration_status) # Now listing migrations with 2.23 should give us the Type column which # should have a value of "resize". migrations = self.nova('migration-list', flags='--os-compute-api-version 2.23') migration_type = self._get_column_value_from_single_row_table( migrations, 'Type') self.assertEqual('resize', migration_type) # Filter migrations with v2.1. migrations = self._filter_migrations('2.1', 'resize', source_compute) # Make sure we got something back. src_compute = self._get_column_value_from_single_row_table( migrations, 'Source Compute') self.assertEqual(source_compute, src_compute) # Filter migrations with v2.59 and make sure there is a migration UUID # value in the output. migrations = self._filter_migrations('2.59', 'resize', source_compute) # _get_column_value_from_single_row_table will raise ValueError if a # value is not found for the given column. We don't actually care what # the migration UUID value is just that the filter works and the UUID # is shown. self._get_column_value_from_single_row_table(migrations, 'UUID') # Filter migrations with v2.66, same as 2.59. migrations = self._filter_migrations('2.66', 'resize', source_compute) self._get_column_value_from_single_row_table(migrations, 'UUID') # Now do a negative test to show that filtering on a migration type # that we don't have a migration for will not return anything. migrations = self._filter_migrations( '2.1', 'evacuation', source_compute) self.assertNotIn(server_id, migrations) # Similarly, make sure we don't get anything back when filtering on # a --source-compute that doesn't exist. migrations = self._filter_migrations( '2.66', 'resize', uuidutils.generate_uuid()) self.assertNotIn(server_id, migrations) # Listing migrations with v2.80 and make sure there are the User ID # and Project ID values in the output. migrations = self.nova('migration-list', flags='--os-compute-api-version 2.80') user_id = self._get_column_value_from_single_row_table( migrations, 'User ID') self.assertEqual(server_user_id, user_id) project_id = self._get_column_value_from_single_row_table( migrations, 'Project ID') self.assertEqual(tenant_id, project_id) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/test_networks.py0000664000175000017500000000317600000000000026604 0ustar00zuulzuul00000000000000# Copyright 2016 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. from novaclient.tests.functional import base class TestNetworkCommandsV2_36(base.ClientTestBase): """Deprecated network command functional tests.""" # Proxy APIs were deprecated in 2.36 but the CLI should fallback to 2.35 # and emit a warning. COMPUTE_API_VERSION = "2.36" def test_limits(self): """Tests that 2.36 won't return network-related resource limits and the CLI output won't show them. """ output = self.nova('limits') # assert that SecurityGroups isn't in the table output self.assertRaises(ValueError, self._get_value_from_the_table, output, 'SecurityGroups') def test_quota_show(self): """Tests that 2.36 won't return network-related resource quotas and the CLI output won't show them. """ output = self.nova('quota-show') # assert that security_groups isn't in the table output self.assertRaises(ValueError, self._get_value_from_the_table, output, 'security_groups') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/test_os_services.py0000664000175000017500000001714600000000000027256 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 novaclient.tests.functional import base from novaclient.tests.functional.v2.legacy import test_os_services from novaclient import utils class TestOsServicesNovaClientV211(test_os_services.TestOsServicesNovaClient): """Functional tests for os-services attributes, microversion 2.11""" COMPUTE_API_VERSION = "2.11" def test_os_services_force_down_force_up(self): for serv in self.client.services.list(): # In Pike the os-services API was made multi-cell aware and it # looks up services by host, which uses the host mapping record # in the API DB which is only populated for nova-compute services, # effectively making it impossible to perform actions like enable # or disable non-nova-compute services since the API won't be able # to find them. So filter out anything that's not nova-compute. if serv.binary != 'nova-compute': continue service_list = self.nova('service-list --binary %s' % serv.binary) # Check the 'service-list' table has the 'Forced down' column status = self._get_column_value_from_single_row_table( service_list, 'Forced down') self.assertEqual('False', status) host = self._get_column_value_from_single_row_table(service_list, 'Host') service = self.nova('service-force-down %s' % host) self.addCleanup(self.nova, 'service-force-down --unset', params=host) status = self._get_column_value_from_single_row_table( service, 'Forced down') self.assertEqual('True', status) service = self.nova('service-force-down --unset %s' % host) status = self._get_column_value_from_single_row_table( service, 'Forced down') self.assertEqual('False', status) class TestOsServicesNovaClientV2_53(base.ClientTestBase): """Tests the nova service-* commands using the 2.53 microversion. The main difference with the 2.53 microversion in these commands is the host/binary combination is replaced with the service.id as the unique identifier for a service. """ COMPUTE_API_VERSION = "2.53" def test_os_services_list(self): table = self.nova('service-list') for serv in self.client.services.list(): self.assertIn(serv.binary, table) # the id should not be an integer and should be in the table self.assertFalse(utils.is_integer_like(serv.id)) self.assertIn(serv.id, table) def test_os_service_disable_enable(self): # Disable and enable Nova services in accordance with list of nova # services returned by client # NOTE(sdague): service disable has the chance in racing # with other tests. Now functional tests for novaclient are launched # in serial way (https://review.opendev.org/#/c/217768/), but # it's a potential issue for making these tests parallel in the future for serv in self.client.services.list(): # In Pike the os-services API was made multi-cell aware and it # looks up services by host, which uses the host mapping record # in the API DB which is only populated for nova-compute services, # effectively making it impossible to perform actions like enable # or disable non-nova-compute services since the API won't be able # to find them. So filter out anything that's not nova-compute. if serv.binary != 'nova-compute': continue service = self.nova('service-disable %s' % serv.id) self.addCleanup(self.nova, 'service-enable', params="%s" % serv.id) service_id = self._get_column_value_from_single_row_table( service, 'ID') self.assertEqual(serv.id, service_id) status = self._get_column_value_from_single_row_table( service, 'Status') self.assertEqual('disabled', status) service = self.nova('service-enable %s' % serv.id) service_id = self._get_column_value_from_single_row_table( service, 'ID') self.assertEqual(serv.id, service_id) status = self._get_column_value_from_single_row_table( service, 'Status') self.assertEqual('enabled', status) def test_os_service_disable_log_reason(self): for serv in self.client.services.list(): # In Pike the os-services API was made multi-cell aware and it # looks up services by host, which uses the host mapping record # in the API DB which is only populated for nova-compute services, # effectively making it impossible to perform actions like enable # or disable non-nova-compute services since the API won't be able # to find them. So filter out anything that's not nova-compute. if serv.binary != 'nova-compute': continue service = self.nova('service-disable --reason test_disable %s' % serv.id) self.addCleanup(self.nova, 'service-enable', params="%s" % serv.id) service_id = self._get_column_value_from_single_row_table( service, 'ID') self.assertEqual(serv.id, service_id) status = self._get_column_value_from_single_row_table( service, 'Status') log_reason = self._get_column_value_from_single_row_table( service, 'Disabled Reason') self.assertEqual('disabled', status) self.assertEqual('test_disable', log_reason) def test_os_services_force_down_force_up(self): for serv in self.client.services.list(): # In Pike the os-services API was made multi-cell aware and it # looks up services by host, which uses the host mapping record # in the API DB which is only populated for nova-compute services, # effectively making it impossible to perform actions like enable # or disable non-nova-compute services since the API won't be able # to find them. So filter out anything that's not nova-compute. if serv.binary != 'nova-compute': continue service = self.nova('service-force-down %s' % serv.id) self.addCleanup(self.nova, 'service-force-down --unset', params="%s" % serv.id) service_id = self._get_column_value_from_single_row_table( service, 'ID') self.assertEqual(serv.id, service_id) forced_down = self._get_column_value_from_single_row_table( service, 'Forced down') self.assertEqual('True', forced_down) service = self.nova('service-force-down --unset %s' % serv.id) forced_down = self._get_column_value_from_single_row_table( service, 'Forced down') self.assertEqual('False', forced_down) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/test_quota_classes.py0000664000175000017500000001427500000000000027600 0ustar00zuulzuul00000000000000# Copyright 2017 Huawei Technologies Co.,LTD. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.lib import exceptions from novaclient.tests.functional import base class TestQuotaClassesNovaClient(base.ClientTestBase): """Nova quota classes functional tests for the v2.1 microversion.""" COMPUTE_API_VERSION = '2.1' # The list of quota class resources we expect in the output table. _included_resources = ['instances', 'cores', 'ram', 'floating_ips', 'fixed_ips', 'metadata_items', 'injected_files', 'injected_file_content_bytes', 'injected_file_path_bytes', 'key_pairs', 'security_groups', 'security_group_rules'] # The list of quota class resources we do not expect in the output table. _excluded_resources = ['server_groups', 'server_group_members'] # Any resources that are not shown but can be updated. For example, before # microversion 2.50 you can update server_groups and server_groups_members # quota class values but they are not shown in the GET response. _extra_update_resources = _excluded_resources # The list of resources which are blocked from being updated. _blocked_update_resources = [] def _get_quota_class_name(self): """Returns a fake quota class name specific to this test class.""" return 'fake-class-%s' % self.COMPUTE_API_VERSION.replace('.', '-') def _verify_quota_class_show_output(self, output, expected_values): # Assert that the expected key/value pairs are in the output table for quota_name in self._included_resources: # First make sure the resource is actually in expected quota. self.assertIn(quota_name, expected_values) expected_value = expected_values[quota_name] actual_value = self._get_value_from_the_table(output, quota_name) self.assertEqual(expected_value, actual_value) # Now make sure anything that we don't expect in the output table is # actually not showing up. for quota_name in self._excluded_resources: # ValueError is raised when the key isn't found in the table. self.assertRaises(ValueError, self._get_value_from_the_table, output, quota_name) def test_quota_class_show(self): """Tests showing quota class values for a fake non-existing quota class. The API will return the defaults if the quota class does not actually exist. We use a fake class to avoid any interaction with the real default quota class values. """ default_quota_class_set = self.client.quota_classes.get('default') default_values = { quota_name: str(getattr(default_quota_class_set, quota_name)) for quota_name in self._included_resources } output = self.nova('quota-class-show %s' % self._get_quota_class_name()) self._verify_quota_class_show_output(output, default_values) def test_quota_class_update(self): """Tests updating a fake quota class. The way this works in the API is that if the quota class is not found, it is created. So in this test we can use a fake quota class with fake values and they will all get set. We don't use the default quota class because it is global and we don't want to interfere with other tests. """ class_name = self._get_quota_class_name() params = [class_name] expected_values = {} for quota_name in ( self._included_resources + self._extra_update_resources): params.append("--%s 99" % quota_name.replace("_", "-")) expected_values[quota_name] = '99' # Note that the quota-class-update CLI doesn't actually output any # information from the response. self.nova("quota-class-update", params=" ".join(params)) # Assert the results using the quota-class-show output. output = self.nova('quota-class-show %s' % class_name) self._verify_quota_class_show_output(output, expected_values) # Assert that attempting to update resources that are blocked will # result in a failure. for quota_name in self._blocked_update_resources: self.assertRaises( exceptions.CommandFailed, self.nova, "quota-class-update %s --%s 99" % (class_name, quota_name.replace("_", "-"))) class TestQuotasNovaClient2_50(TestQuotaClassesNovaClient): """Nova quota classes functional tests for the v2.50 microversion.""" COMPUTE_API_VERSION = '2.50' # The 2.50 microversion added the server_groups and server_group_members # to the response, and filtered out floating_ips, fixed_ips, # security_groups and security_group_members, similar to the 2.36 # microversion in the os-quota-sets API. _included_resources = ['instances', 'cores', 'ram', 'metadata_items', 'injected_files', 'injected_file_content_bytes', 'injected_file_path_bytes', 'key_pairs', 'server_groups', 'server_group_members'] # The list of quota class resources we do not expect in the output table. _excluded_resources = ['floating_ips', 'fixed_ips', 'security_groups', 'security_group_rules'] # In 2.50, server_groups and server_group_members can be both updated # in a PUT request and shown in a GET response. _extra_update_resources = [] # In 2.50, you can't update the network-related resources. _blocked_update_resources = _excluded_resources ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/test_quotas.py0000664000175000017500000000565200000000000026245 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 novaclient.tests.functional.v2.legacy import test_quotas class TestQuotasNovaClient2_35(test_quotas.TestQuotasNovaClient): """Nova quotas functional tests.""" COMPUTE_API_VERSION = "2.35" _quota_resources = ['instances', 'cores', 'ram', 'floating_ips', 'fixed_ips', 'metadata_items', 'injected_files', 'injected_file_content_bytes', 'injected_file_path_bytes', 'key_pairs', 'security_groups', 'security_group_rules', 'server_groups', 'server_group_members'] def test_quotas_update(self): # `nova quota-update` requires tenant-id. tenant_id = self._get_project_id(self.cli_clients.tenant_name) self.addCleanup(self.client.quotas.delete, tenant_id) original_quotas = self.client.quotas.get(tenant_id) difference = 10 params = [tenant_id] for quota_name in self._quota_resources: params.append("--%(name)s %(value)s" % { "name": quota_name.replace("_", "-"), "value": getattr(original_quotas, quota_name) + difference}) self.nova("quota-update", params=" ".join(params)) updated_quotas = self.client.quotas.get(tenant_id) for quota_name in self._quota_resources: self.assertEqual(getattr(original_quotas, quota_name), getattr(updated_quotas, quota_name) - difference) class TestQuotasNovaClient2_36(TestQuotasNovaClient2_35): """Nova quotas functional tests.""" COMPUTE_API_VERSION = "2.36" # The 2.36 microversion stops proxying network quota resources like # floating/fixed IPs and security groups/rules. _quota_resources = ['instances', 'cores', 'ram', 'metadata_items', 'injected_files', 'injected_file_content_bytes', 'injected_file_path_bytes', 'key_pairs', 'server_groups', 'server_group_members'] class TestQuotasNovaClient2_57(TestQuotasNovaClient2_35): """Nova quotas functional tests.""" COMPUTE_API_VERSION = "2.latest" # The 2.57 microversion deprecates injected_file* quotas. _quota_resources = ['instances', 'cores', 'ram', 'metadata_items', 'key_pairs', 'server_groups', 'server_group_members'] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/test_resize.py0000664000175000017500000001305400000000000026225 0ustar00zuulzuul00000000000000# Copyright 2017 Huawei Technologies Co.,LTD. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from novaclient.tests.functional import base class TestServersResize(base.ClientTestBase): """Servers resize functional tests.""" COMPUTE_API_VERSION = '2.1' def _compare_quota_usage(self, old_usage, new_usage, expect_diff=True): """Compares the quota usage in the provided AbsoluteLimits.""" # For a resize, instance usage shouldn't change. self.assertEqual(old_usage['totalInstancesUsed'], new_usage['totalInstancesUsed'], 'totalInstancesUsed does not match') # For the resize we're doing, those flavors have the same vcpus so we # don't expect any quota change. self.assertEqual(old_usage['totalCoresUsed'], new_usage['totalCoresUsed'], 'totalCoresUsed does not match') # RAM is the only thing that will change for these flavors in a resize. if expect_diff: self.assertNotEqual(old_usage['totalRAMUsed'], new_usage['totalRAMUsed'], 'totalRAMUsed should have changed') else: self.assertEqual(old_usage['totalRAMUsed'], new_usage['totalRAMUsed'], 'totalRAMUsed does not match') def test_resize_up_confirm(self): """Tests creating a server and resizes up and confirms the resize. Compares quota before, during and after the resize. """ server_id = self._create_server(flavor=self.flavor.id).id # get the starting quota now that we've created a server starting_usage = self._get_absolute_limits() # now resize up alternate_flavor = self._pick_alternate_flavor() self.nova('resize', params='%s %s --poll' % (server_id, alternate_flavor)) resize_usage = self._get_absolute_limits() # compare the starting usage against the resize usage self._compare_quota_usage(starting_usage, resize_usage) # now confirm the resize self.nova('resize-confirm', params='%s' % server_id) # we have to wait for the server to be ACTIVE before we can check quota self._wait_for_state_change(server_id, 'active') # get the final quota usage which should be the same as the resize # usage before confirm confirm_usage = self._get_absolute_limits() self._compare_quota_usage( resize_usage, confirm_usage, expect_diff=False) def _create_resize_down_flavors(self): """Creates two flavors with different size ram but same size vcpus and disk. :returns: tuple of 2 IDs which represents larger_flavor for resize and smaller flavor. """ output = self.nova('flavor-create', params='%s auto 128 0 1' % self.name_generate()) larger_id = self._get_column_value_from_single_row_table(output, "ID") self.addCleanup(self.nova, 'flavor-delete', params=larger_id) output = self.nova('flavor-create', params='%s auto 64 0 1' % self.name_generate()) smaller_id = self._get_column_value_from_single_row_table(output, "ID") self.addCleanup(self.nova, 'flavor-delete', params=smaller_id) return larger_id, smaller_id def test_resize_down_revert(self): """Tests creating a server and resizes down and reverts the resize. Compares quota before, during and after the resize. """ # devstack's m1.tiny and m1.small have different size disks so we # can't use those as you can't resize down the disk. So we have to # create our own flavors. larger_flavor, smaller_flavor = self._create_resize_down_flavors() # Now create the server with the larger flavor. server_id = self._create_server(flavor=larger_flavor).id # get the starting quota now that we've created a server starting_usage = self._get_absolute_limits() # now resize down self.nova('resize', params='%s %s --poll' % (server_id, smaller_flavor)) resize_usage = self._get_absolute_limits() # compare the starting usage against the resize usage; with counting # quotas in the server there are no reservations, so the # usage changes after the resize happens before it's confirmed. self._compare_quota_usage(starting_usage, resize_usage) # now revert the resize self.nova('resize-revert', params='%s' % server_id) # we have to wait for the server to be ACTIVE before we can check quota self._wait_for_state_change(server_id, 'active') # get the final quota usage which will be different from the resize # usage since we've reverted back *up* to the original flavor; the API # code checks quota again if we revert up in size revert_usage = self._get_absolute_limits() self._compare_quota_usage(resize_usage, revert_usage) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/test_server_groups.py0000664000175000017500000001170400000000000027631 0ustar00zuulzuul00000000000000# Copyright 2015 Huawei Technology 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 novaclient.tests.functional.v2.legacy import test_server_groups class TestServerGroupClientV213(test_server_groups.TestServerGroupClient): """Server groups v2.13 functional tests.""" COMPUTE_API_VERSION = "2.13" expected_metadata = True expected_policy_rules = False def test_create_server_group(self): sg_id = self._create_sg("affinity") self.addCleanup(self.nova, 'server-group-delete %s' % sg_id) sg = self.nova('server-group-get %s' % sg_id) result = self._get_column_value_from_single_row_table(sg, "Id") self._get_column_value_from_single_row_table( sg, "User Id") self._get_column_value_from_single_row_table( sg, "Project Id") self.assertEqual(sg_id, result) self._get_column_value_from_single_row_table(sg, "Metadata") self.assertIn( 'affinity', self._get_column_value_from_single_row_table(sg, 'Policies')) self.assertNotIn('Rules', sg) def test_list_server_groups(self): sg_id = self._create_sg("affinity") self.addCleanup(self.nova, 'server-group-delete %s' % sg_id) sg = self.nova("server-group-list") result = self._get_column_value_from_single_row_table(sg, "Id") self._get_column_value_from_single_row_table( sg, "User Id") self._get_column_value_from_single_row_table( sg, "Project Id") self.assertEqual(sg_id, result) if self.expected_metadata: self._get_column_value_from_single_row_table(sg, "Metadata") else: self.assertNotIn(sg, 'Metadata') if self.expected_policy_rules: self.assertEqual( 'affinity', self._get_column_value_from_single_row_table(sg, "Policy")) self.assertEqual( '{}', self._get_column_value_from_single_row_table(sg, "Rules")) else: self.assertIn( 'affinity', self._get_column_value_from_single_row_table(sg, 'Policies')) self.assertNotIn('Rules', sg) def test_get_server_group(self): sg_id = self._create_sg("affinity") self.addCleanup(self.nova, 'server-group-delete %s' % sg_id) sg = self.nova('server-group-get %s' % sg_id) result = self._get_column_value_from_single_row_table(sg, "Id") self._get_column_value_from_single_row_table( sg, "User Id") self._get_column_value_from_single_row_table( sg, "Project Id") self.assertEqual(sg_id, result) if self.expected_metadata: self._get_column_value_from_single_row_table(sg, "Metadata") else: self.assertNotIn(sg, 'Metadata') if self.expected_policy_rules: self.assertEqual( 'affinity', self._get_column_value_from_single_row_table(sg, "Policy")) self.assertEqual( '{}', self._get_column_value_from_single_row_table(sg, "Rules")) else: self.assertIn( 'affinity', self._get_column_value_from_single_row_table(sg, 'Policies')) self.assertNotIn('Rules', sg) class TestServerGroupClientV264(TestServerGroupClientV213): """Server groups v2.64 functional tests.""" COMPUTE_API_VERSION = "2.64" expected_metadata = False expected_policy_rules = True def test_create_server_group(self): output = self.nova('server-group-create complex-anti-affinity-group ' 'anti-affinity --rule max_server_per_host=3') sg_id = self._get_column_value_from_single_row_table(output, "Id") self.addCleanup(self.nova, 'server-group-delete %s' % sg_id) sg = self.nova('server-group-get %s' % sg_id) result = self._get_column_value_from_single_row_table(sg, "Id") self.assertEqual(sg_id, result) self._get_column_value_from_single_row_table( sg, "User Id") self._get_column_value_from_single_row_table( sg, "Project Id") self.assertNotIn('Metadata', sg) self.assertEqual( 'anti-affinity', self._get_column_value_from_single_row_table(sg, "Policy")) self.assertIn( 'max_server_per_host', self._get_column_value_from_single_row_table(sg, "Rules")) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/test_servers.py0000664000175000017500000003766400000000000026432 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 random import string from tempest.lib import decorators from novaclient.tests.functional import base from novaclient.tests.functional.v2.legacy import test_servers from novaclient.v2 import shell class TestServersBootNovaClient(test_servers.TestServersBootNovaClient): """Servers boot functional tests.""" COMPUTE_API_VERSION = "2.latest" class TestServersListNovaClient(test_servers.TestServersListNovaClient): """Servers list functional tests.""" COMPUTE_API_VERSION = "2.latest" class TestServerLockV29(base.ClientTestBase): COMPUTE_API_VERSION = "2.9" def _show_server_and_check_lock_attr(self, server, value): output = self.nova("show %s" % server.id) self.assertEqual(str(value), self._get_value_from_the_table(output, "locked")) def test_attribute_presented(self): # prepare server = self._create_server() # testing self._show_server_and_check_lock_attr(server, False) self.nova("lock %s" % server.id) self._show_server_and_check_lock_attr(server, True) self.nova("unlock %s" % server.id) self._show_server_and_check_lock_attr(server, False) class TestServersDescription(base.ClientTestBase): COMPUTE_API_VERSION = "2.19" def _boot_server_with_description(self): descr = "Some words about this test VM." server = self._create_server(description=descr) self.assertEqual(descr, server.description) return server, descr def test_create(self): # Add a description to the tests that create a server server, descr = self._boot_server_with_description() output = self.nova("show %s" % server.id) self.assertEqual(descr, self._get_value_from_the_table(output, "description")) def test_list_servers_with_description(self): # Check that the description is returned as part of server details # for a server list server, descr = self._boot_server_with_description() output = self.nova("list --fields description") self.assertEqual(server.id, self._get_column_value_from_single_row_table( output, "ID")) self.assertEqual(descr, self._get_column_value_from_single_row_table( output, "Description")) @decorators.skip_because(bug="1694371") def test_rebuild(self): # Add a description to the tests that rebuild a server server, descr = self._boot_server_with_description() descr = "New description for rebuilt VM." self.nova("rebuild --description '%s' %s %s" % (descr, server.id, self.image.name)) shell._poll_for_status( self.client.servers.get, server.id, 'rebuild', ['active']) output = self.nova("show %s" % server.id) self.assertEqual(descr, self._get_value_from_the_table(output, "description")) def test_remove_description(self): # Remove description from server booted with it server, descr = self._boot_server_with_description() self.nova("update %s --description ''" % server.id) output = self.nova("show %s" % server.id) self.assertEqual("-", self._get_value_from_the_table(output, "description")) def test_add_remove_description_on_existing_server(self): # Set and remove the description on an existing server server = self._create_server() descr = "Add a description for previously-booted VM." self.nova("update %s --description '%s'" % (server.id, descr)) output = self.nova("show %s" % server.id) self.assertEqual(descr, self._get_value_from_the_table(output, "description")) self.nova("update %s --description ''" % server.id) output = self.nova("show %s" % server.id) self.assertEqual("-", self._get_value_from_the_table(output, "description")) def test_update_with_description_longer_than_255_symbols(self): # Negative case for description longer than 255 characters server = self._create_server() descr = ''.join(random.choice(string.ascii_letters) for i in range(256)) output = self.nova("update %s --description '%s'" % (server.id, descr), fail_ok=True, merge_stderr=True) self.assertIn("ERROR (BadRequest): Invalid input for field/attribute" " description. Value: %s. '%s' is too long (HTTP 400)" % (descr, descr), output) class TestServersTagsV226(base.ClientTestBase): COMPUTE_API_VERSION = "2.26" def _boot_server_with_tags(self, tags=["t1", "t2"]): uuid = self._create_server().id self.client.servers.set_tags(uuid, tags) return uuid def test_show(self): uuid = self._boot_server_with_tags() output = self.nova("show %s" % uuid) self.assertEqual('["t1", "t2"]', self._get_value_from_the_table( output, "tags")) def test_unicode_tag_correctly_displayed(self): """Regression test for bug #1669683. List and dict fields with unicode cannot be correctly displayed. Ensure that once we fix this it doesn't regress. """ # create an instance with chinese tag uuid = self._boot_server_with_tags(tags=["中文标签"]) output = self.nova("show %s" % uuid) self.assertEqual('["中文标签"]', self._get_value_from_the_table( output, "tags")) def test_list(self): uuid = self._boot_server_with_tags() output = self.nova("server-tag-list %s" % uuid) tags = self._get_list_of_values_from_single_column_table( output, "Tag") self.assertEqual(["t1", "t2"], tags) def test_add(self): uuid = self._boot_server_with_tags() self.nova("server-tag-add %s t3" % uuid) self.assertEqual(["t1", "t2", "t3"], self.client.servers.tag_list(uuid)) def test_add_many(self): uuid = self._boot_server_with_tags() self.nova("server-tag-add %s t3 t4" % uuid) self.assertEqual(["t1", "t2", "t3", "t4"], self.client.servers.tag_list(uuid)) def test_set(self): uuid = self._boot_server_with_tags() self.nova("server-tag-set %s t3 t4" % uuid) self.assertEqual(["t3", "t4"], self.client.servers.tag_list(uuid)) def test_delete(self): uuid = self._boot_server_with_tags() self.nova("server-tag-delete %s t2" % uuid) self.assertEqual(["t1"], self.client.servers.tag_list(uuid)) def test_delete_many(self): uuid = self._boot_server_with_tags() self.nova("server-tag-delete %s t1 t2" % uuid) self.assertEqual([], self.client.servers.tag_list(uuid)) def test_delete_all(self): uuid = self._boot_server_with_tags() self.nova("server-tag-delete-all %s" % uuid) self.assertEqual([], self.client.servers.tag_list(uuid)) class TestServersAutoAllocateNetworkCLI(base.ClientTestBase): COMPUTE_API_VERSION = '2.37' def _find_network_in_table(self, table): # Example: # +-----------------+-----------------------------------+ # | Property | Value | # +-----------------+-----------------------------------+ # | private network | 192.168.154.128 | # +-----------------+-----------------------------------+ for line in table.split('\n'): if '|' in line: l_property, l_value = line.split('|')[1:3] if ' network' in l_property.strip(): return ' '.join(l_property.strip().split()[:-1]) def test_boot_server_with_auto_network(self): """Tests that the CLI defaults to 'auto' when --nic isn't specified. """ # check to see if multiple networks are available because if so we # have to skip this test as auto will fail with a 409 conflict as it's # an ambiguous request and nova won't know which network to pick if self.multiple_networks: # we could potentially get around this by extending TenantTestBase self.skipTest('multiple networks available') server_info = self.nova('boot', params=( '%(name)s --flavor %(flavor)s --poll ' '--image %(image)s ' % {'name': self.name_generate(), 'flavor': self.flavor.id, 'image': self.image.id})) server_id = self._get_value_from_the_table(server_info, 'id') self.addCleanup(self.wait_for_resource_delete, server_id, self.client.servers) self.addCleanup(self.client.servers.delete, server_id) # get the server details to verify there is a network, we don't care # what the network name is, we just want to see an entry show up server_info = self.nova('show', params=server_id) network = self._find_network_in_table(server_info) self.assertIsNotNone( network, 'Auto-allocated network not found: %s' % server_info) def test_boot_server_with_no_network(self): """Tests that '--nic none' is honored. """ server_info = self.nova('boot', params=( '%(name)s --flavor %(flavor)s --poll ' '--image %(image)s --nic none' % {'name': self.name_generate(), 'flavor': self.flavor.id, 'image': self.image.id})) server_id = self._get_value_from_the_table(server_info, 'id') self.addCleanup(self.wait_for_resource_delete, server_id, self.client.servers) self.addCleanup(self.client.servers.delete, server_id) # get the server details to verify there is not a network server_info = self.nova('show', params=server_id) network = self._find_network_in_table(server_info) self.assertIsNone( network, 'Unexpected network allocation: %s' % server_info) class TestServersDetailsFlavorInfo(base.ClientTestBase): COMPUTE_API_VERSION = '2.47' def _validate_flavor_details(self, flavor_details, server_details): # This is a mapping between the keys used in the flavor GET response # and the keys used for the flavor information embedded in the server # details. flavor_key_mapping = { "OS-FLV-EXT-DATA:ephemeral": "flavor:ephemeral", "disk": "flavor:disk", "extra_specs": "flavor:extra_specs", "name": "flavor:original_name", "ram": "flavor:ram", "swap": "flavor:swap", "vcpus": "flavor:vcpus", } for key in flavor_key_mapping: flavor_val = self._get_value_from_the_table( flavor_details, key) server_flavor_val = self._get_value_from_the_table( server_details, flavor_key_mapping[key]) if key == "swap" and flavor_val == "": # "flavor-show" displays zero swap as empty string. flavor_val = '0' self.assertEqual(flavor_val, server_flavor_val) def _setup_extra_specs(self, flavor_id): extra_spec_key = "dummykey" self.nova('flavor-key', params=('%(flavor)s set %(key)s=dummyval' % {'flavor': flavor_id, 'key': extra_spec_key})) unset_params = ('%(flavor)s unset %(key)s' % {'flavor': flavor_id, 'key': extra_spec_key}) self.addCleanup(self.nova, 'flavor-key', params=unset_params) def test_show(self): self._setup_extra_specs(self.flavor.id) uuid = self._create_server().id server_output = self.nova("show %s" % uuid) flavor_output = self.nova("flavor-show %s" % self.flavor.id) self._validate_flavor_details(flavor_output, server_output) def test_show_minimal(self): uuid = self._create_server().id server_output = self.nova("show --minimal %s" % uuid) server_output_flavor = self._get_value_from_the_table( server_output, 'flavor') self.assertEqual(self.flavor.name, server_output_flavor) def test_list(self): self._setup_extra_specs(self.flavor.id) self._create_server() server_output = self.nova("list --fields flavor:disk") # namespaced fields get reformatted slightly as column names server_flavor_val = self._get_column_value_from_single_row_table( server_output, 'flavor: Disk') flavor_output = self.nova("flavor-show %s" % self.flavor.id) flavor_val = self._get_value_from_the_table(flavor_output, 'disk') self.assertEqual(flavor_val, server_flavor_val) class TestInterfaceAttach(base.ClientTestBase): COMPUTE_API_VERSION = '2.latest' def test_interface_attach(self): server = self._create_server() output = self.nova("interface-attach --net-id %s %s" % (self.network.id, server.id)) for key in ('ip_address', 'mac_addr', 'port_id', 'port_state'): self._get_value_from_the_table(output, key) self.assertEqual( self.network.id, self._get_value_from_the_table(output, 'net_id')) class TestServeRebuildV274(base.ClientTestBase): COMPUTE_API_VERSION = '2.74' REBUILD_FIELDS = ["OS-DCF:diskConfig", "accessIPv4", "accessIPv6", "adminPass", "created", "description", "flavor", "hostId", "id", "image", "key_name", "locked", "locked_reason", "metadata", "name", "progress", "server_groups", "status", "tags", "tenant_id", "trusted_image_certificates", "updated", "user_data", "user_id"] def test_rebuild(self): server = self._create_server() output = self.nova("rebuild %s %s" % (server.id, self.image.name)) for field in self.REBUILD_FIELDS: self.assertIn(field, output) class TestServeRebuildV275(TestServeRebuildV274): COMPUTE_API_VERSION = '2.75' REBUILD_FIELDS_V275 = ['OS-EXT-AZ:availability_zone', 'config_drive', 'OS-EXT-SRV-ATTR:host', 'OS-EXT-SRV-ATTR:hypervisor_hostname', 'OS-EXT-SRV-ATTR:instance_name', 'OS-EXT-SRV-ATTR:hostname', 'OS-EXT-SRV-ATTR:kernel_id', 'OS-EXT-SRV-ATTR:launch_index', 'OS-EXT-SRV-ATTR:ramdisk_id', 'OS-EXT-SRV-ATTR:reservation_id', 'OS-EXT-SRV-ATTR:root_device_name', 'host_status', 'OS-SRV-USG:launched_at', 'OS-SRV-USG:terminated_at', 'OS-EXT-STS:task_state', 'OS-EXT-STS:vm_state', 'OS-EXT-STS:power_state', 'security_groups', 'os-extended-volumes:volumes_attached'] REBUILD_FIELDS = TestServeRebuildV274.REBUILD_FIELDS + REBUILD_FIELDS_V275 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/test_trigger_crash_dump.py0000664000175000017500000001355400000000000030601 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 time from tempest.lib import decorators from novaclient.tests.functional import base from novaclient.v2 import shell @decorators.skip_because(bug="1675526") class TestTriggerCrashDumpNovaClientV217(base.TenantTestBase): """Functional tests for trigger crash dump""" COMPUTE_API_VERSION = "2.17" # It's a resource-consuming task to implement full-flow (up to getting # and reading a dump file) functional test for trigger-crash-dump. # We need to upload Ubuntu image for booting an instance based on it, # and to install kdump with its further configuring on this instance. # Here, the "light" version of functional test is proposed. # It's based on knowledge that trigger-crash-dump uses a NMI injection, # and when the 'trigger-crash-dump' operation is executed, # instance's kernel receives the NMI signal, and an appropriate # message will appear in the instance's log. # The server status must be ACTIVE, PAUSED, RESCUED, RESIZED or ERROR. # If not, the conflictingRequest(409) code is returned def _assert_nmi(self, server_id, timeout=60, poll_interval=1): start_time = time.time() while time.time() - start_time < timeout: if 'trigger_crash_dump' in self.nova('instance-action-list %s ' % server_id): break time.sleep(poll_interval) else: self.fail("Trigger crash dump hasn't been executed for server %s" % server_id) def test_trigger_crash_dump_in_active_state(self): server = self._create_server() self.wait_for_server_os_boot(server.id) self.nova('trigger-crash-dump %s ' % server.id) self._assert_nmi(server.id) def test_trigger_crash_dump_in_error_state(self): server = self._create_server() self.wait_for_server_os_boot(server.id) self.nova('reset-state %s ' % server.id) shell._poll_for_status( self.client.servers.get, server.id, 'active', ['error']) self.nova('trigger-crash-dump %s ' % server.id) self._assert_nmi(server.id) def test_trigger_crash_dump_in_paused_state(self): server = self._create_server() self.wait_for_server_os_boot(server.id) self.nova('pause %s ' % server.id) shell._poll_for_status( self.client.servers.get, server.id, 'active', ['paused']) self.nova('trigger-crash-dump %s ' % server.id) self._assert_nmi(server.id) def test_trigger_crash_dump_in_rescued_state(self): server = self._create_server() self.wait_for_server_os_boot(server.id) self.nova('rescue %s ' % server.id) shell._poll_for_status( self.client.servers.get, server.id, 'active', ['rescue']) self.wait_for_server_os_boot(server.id) self.nova('trigger-crash-dump %s ' % server.id) self._assert_nmi(server.id) def test_trigger_crash_dump_in_resized_state(self): server = self._create_server() self.wait_for_server_os_boot(server.id) self.nova('resize %s %s' % (server.id, 'm1.small')) shell._poll_for_status( self.client.servers.get, server.id, 'active', ['verify_resize']) self.nova('trigger-crash-dump %s ' % server.id) self._assert_nmi(server.id) def test_trigger_crash_dump_in_shutoff_state(self): server = self._create_server() self.wait_for_server_os_boot(server.id) self.nova('stop %s ' % server.id) shell._poll_for_status( self.client.servers.get, server.id, 'active', ['shutoff']) output = self.nova('trigger-crash-dump %s ' % server.id, fail_ok=True, merge_stderr=True) self.assertIn("ERROR (Conflict): " "Cannot 'trigger_crash_dump' instance %s " "while it is in vm_state stopped (HTTP 409) " % server.id, output) # If the specified server is locked, the conflictingRequest(409) code # is returned to a user without administrator privileges. def test_trigger_crash_dump_in_locked_state_admin(self): server = self._create_server() self.wait_for_server_os_boot(server.id) self.nova('lock %s ' % server.id) self.nova('trigger-crash-dump %s ' % server.id) self._assert_nmi(server.id) def test_trigger_crash_dump_in_locked_state_nonadmin(self): name = self.name_generate() server = self.another_nova('boot --flavor %s --image %s --poll %s' % (self.flavor.name, self.image.name, name)) self.addCleanup(self.another_nova, 'delete', params=name) server_id = self._get_value_from_the_table( server, 'id') self.wait_for_server_os_boot(server_id) self.another_nova('lock %s ' % server_id) self.addCleanup(self.another_nova, 'unlock', params=name) output = self.another_nova('trigger-crash-dump %s ' % server_id, fail_ok=True, merge_stderr=True) # NOTE(mriedem): Depending on the version of the server you can get # different error messages back from this, so just assert that it's a # 409 either way. self.assertIn("ERROR (Conflict)", output) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/functional/v2/test_usage.py0000664000175000017500000000265200000000000026032 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 novaclient.tests.functional.v2.legacy import test_usage class TestUsageCLI_V240(test_usage.TestUsageCLI): COMPUTE_API_VERSION = '2.40' class TestUsageClient_V240(test_usage.TestUsageClient): COMPUTE_API_VERSION = '2.40' def test_get(self): start, end = self._create_servers_in_time_window() tenant_id = self._get_project_id(self.cli_clients.tenant_name) usage = self.client.usage.get( tenant_id, start=start, end=end, limit=1) self.assertEqual(tenant_id, usage.tenant_id) self.assertEqual(1, len(usage.server_usages)) def test_list(self): start, end = self._create_servers_in_time_window() usages = self.client.usage.list( start=start, end=end, detailed=True, limit=1) self.assertEqual(1, len(usages)) self.assertEqual(1, len(usages[0].server_usages)) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1725030944.8044653 python-novaclient-18.7.0/novaclient/tests/unit/0000775000175000017500000000000000000000000021576 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/__init__.py0000664000175000017500000000000000000000000023675 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/fake_actions_module.py0000664000175000017500000000244000000000000026143 0ustar00zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from novaclient import api_versions from novaclient import utils @api_versions.wraps("2.10", "2.20") def do_fake_action(): return 1 @api_versions.wraps("2.21", "2.30") def do_fake_action(): return 2 @api_versions.wraps("2.0") def do_another_fake_action(): return 0 @utils.arg( '--foo', start_version='2.1', end_version='2.2') @utils.arg( '--bar', start_version='2.3', end_version='2.4') def do_fake_action2(): return 3 @utils.arg( '--foo', help='first foo', start_version='2.10', end_version='2.20') @utils.arg( '--foo', help='second foo', start_version='2.21') def do_fake_action3(): return 3 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/fakes.py0000664000175000017500000001347700000000000023255 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. """ A fake server that "responds" to API methods with pre-canned responses. All of these responses come from the spec, so if for some reason the spec's wrong the tests might raise AssertionError. I've indicated in comments the places where actual behavior differs from the spec. """ from novaclient import base # fake request id FAKE_REQUEST_ID = 'req-3fdea7c2-e3e3-48b5-a656-6b12504c49a1' FAKE_REQUEST_ID_LIST = [FAKE_REQUEST_ID] def assert_has_keys(dict, required=None, optional=None): required = required or [] optional = optional or [] keys = dict.keys() for k in required: try: assert k in keys except AssertionError: extra_keys = set(keys).difference(set(required + optional)) raise AssertionError("found unexpected keys: %s" % list(extra_keys)) class FakeClient(object): def assert_called(self, method, url, body=None, pos=-1): """Assert that an HTTP method was called at given order/position. :param method: HTTP method name which is expected to be called :param url: Expected request url to be called with given method :param body: Expected request body to be called with given method and url. Default is None. :param pos: Order of the expected method call. If multiple methods calls are made in single API request, then, order of each method call can be checked by passing expected order to this arg. Default is -1 which means most recent call. Usage:: 1. self.run_command('flavor-list --extra-specs') self.assert_called('GET', '/flavors/aa1/os-extra_specs') 2. self.run_command(["boot", "--image", "1", "--flavor", "512 MiB Server", "--max-count", "3", "server"]) self.assert_called('GET', '/images/1', pos=0) self.assert_called('GET', '/flavors/512 MiB Server', pos=1) self.assert_called('GET', '/flavors?is_public=None', pos=2) self.assert_called('GET', '/flavors/2', pos=3) self.assert_called( 'POST', '/servers', { 'server': { 'flavorRef': '2', 'name': 'server', 'imageRef': '1', 'min_count': 1, 'max_count': 3, } }, pos=4) """ expected = (method, url) assert self.client.callstack, \ "Expected %s %s but no calls were made." % expected called = self.client.callstack[pos][0:2] assert expected == called, \ ('\nExpected: %(expected)s' '\nActual: %(called)s' '\nCall position: %(pos)s' '\nCalls:\n%(calls)s' % {'expected': expected, 'called': called, 'pos': pos, 'calls': '\n'.join(str(c) for c in self.client.callstack)}) if body is not None: if self.client.callstack[pos][2] != body: raise AssertionError('%r != %r' % (self.client.callstack[pos][2], body)) def assert_called_anytime(self, method, url, body=None): """Assert that an HTTP method was called anytime in the test. :param method: HTTP method name which is expected to be called :param url: Expected request url to be called with given method :param body: Expected request body to be called with given method and url. Default is None. Usage:: self.run_command('flavor-list --extra-specs') self.assert_called_anytime('GET', '/flavors/detail') """ expected = (method, url) assert self.client.callstack, \ "Expected %s %s but no calls were made." % expected found = False for entry in self.client.callstack: if expected == entry[0:2]: found = True break assert found, 'Expected %s; got %s' % (expected, self.client.callstack) if body is not None: try: assert entry[2] == body except AssertionError: print(entry[2]) print("!=") print(body) raise self.client.callstack = [] def assert_not_called(self, method, url, body=None): """Assert that an HTTP method was not called in the test. :param method: HTTP method name which is expected not to be called :param url: Expected request url not to be called with given method :param body: Expected request body not to be called with given method and url. Default is None. """ not_expected = (method, url, body) for entry in self.client.callstack: assert not_expected != entry[0:3], ( 'API %s %s body=%s was called.' % not_expected) def clear_callstack(self): self.client.callstack = [] def authenticate(self): pass # Fake class that will be used as an extension class FakeManager(base.Manager): pass ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1725030944.8084655 python-novaclient-18.7.0/novaclient/tests/unit/fixture_data/0000775000175000017500000000000000000000000024255 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/fixture_data/__init__.py0000664000175000017500000000000000000000000026354 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/fixture_data/agents.py0000664000175000017500000000334700000000000026117 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 novaclient.tests.unit.fixture_data import base class Fixture(base.Fixture): base_url = 'os-agents' def setUp(self): super(Fixture, self).setUp() post_os_agents = { 'agent': { 'url': '/xxx/xxx/xxx', 'hypervisor': 'kvm', 'md5hash': 'add6bb58e139be103324d04d82d8f546', 'version': '7.0', 'architecture': 'x86', 'os': 'win', 'id': 1 } } self.requests_mock.post(self.url(), json=post_os_agents, headers=self.json_headers) put_os_agents_1 = { "agent": { "url": "/yyy/yyyy/yyyy", "version": "8.0", "md5hash": "add6bb58e139be103324d04d82d8f546", 'id': 1 } } self.requests_mock.put(self.url(1), json=put_os_agents_1, headers=self.json_headers) self.requests_mock.delete(self.url(1), headers=self.json_headers, status_code=202) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/fixture_data/aggregates.py0000664000175000017500000000440100000000000026737 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 novaclient.tests.unit.fixture_data import base class Fixture(base.Fixture): base_url = 'os-aggregates' def setUp(self): super(Fixture, self).setUp() get_os_aggregates = {"aggregates": [ {'id': '1', 'name': 'test', 'availability_zone': 'nova1'}, {'id': '2', 'name': 'test2', 'availability_zone': 'nova1'}, ]} self.requests_mock.get(self.url(), json=get_os_aggregates, headers=self.json_headers) get_aggregates_1 = {'aggregate': get_os_aggregates['aggregates'][0]} self.requests_mock.post(self.url(), json=get_aggregates_1, headers=self.json_headers) for agg_id in (1, 2): for method in ('GET', 'PUT'): self.requests_mock.register_uri(method, self.url(agg_id), json=get_aggregates_1, headers=self.json_headers) self.requests_mock.post(self.url(agg_id, 'action'), json=get_aggregates_1, headers=self.json_headers) self.requests_mock.delete(self.url(1), status_code=202, headers=self.json_headers) self.requests_mock.register_uri('POST', self.url(1), json={}, headers=self.json_headers) self.requests_mock.post(self.url(1, 'images'), json={}, headers=self.json_headers) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/fixture_data/availability_zones.py0000664000175000017500000000623200000000000030522 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 novaclient.tests.unit.fixture_data import base class V1(base.Fixture): base_url = 'os-availability-zone' zone_info_key = 'availabilityZoneInfo' zone_name_key = 'zoneName' zone_state_key = 'zoneState' def setUp(self): super(V1, self).setUp() get_os_availability_zone = { self.zone_info_key: [ { self.zone_name_key: "zone-1", self.zone_state_key: {"available": True}, "hosts": None }, { self.zone_name_key: "zone-2", self.zone_state_key: {"available": False}, "hosts": None } ] } self.requests_mock.get(self.url(), json=get_os_availability_zone, headers=self.json_headers) get_os_zone_detail = { self.zone_info_key: [ { self.zone_name_key: "zone-1", self.zone_state_key: {"available": True}, "hosts": { "fake_host-1": { "nova-compute": { "active": True, "available": True, "updated_at": '2012-12-26 14:45:25' } } } }, { self.zone_name_key: "internal", self.zone_state_key: {"available": True}, "hosts": { "fake_host-1": { "nova-sched": { "active": True, "available": True, "updated_at": '2012-12-26 14:45:25' } }, "fake_host-2": { "nova-network": { "active": True, "available": False, "updated_at": '2012-12-26 14:45:24' } } } }, { self.zone_name_key: "zone-2", self.zone_state_key: {"available": False}, "hosts": None } ] } self.requests_mock.get(self.url('detail'), json=get_os_zone_detail, headers=self.json_headers) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/fixture_data/base.py0000664000175000017500000000252000000000000025540 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 urllib import parse import fixtures from novaclient.tests.unit.v2 import fakes COMPUTE_URL = 'http://compute.host' class Fixture(fixtures.Fixture): base_url = None json_headers = {'Content-Type': 'application/json', 'x-openstack-request-id': fakes.FAKE_REQUEST_ID} def __init__(self, requests_mock, compute_url=COMPUTE_URL): super(Fixture, self).__init__() self.requests_mock = requests_mock self.compute_url = compute_url def url(self, *args, **kwargs): url_args = [self.compute_url] if self.base_url: url_args.append(self.base_url) url = '/'.join(str(a).strip('/') for a in tuple(url_args) + args) if kwargs: url += '?%s' % parse.urlencode(kwargs, doseq=True) return url ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/fixture_data/client.py0000664000175000017500000000510400000000000026105 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 fixtures from keystoneauth1 import fixture from keystoneauth1 import loading from keystoneauth1 import session from novaclient import client IDENTITY_URL = 'http://identityserver:5000/v2.0' COMPUTE_URL = 'http://compute.host' class V1(fixtures.Fixture): def __init__(self, requests_mock, compute_url=COMPUTE_URL, identity_url=IDENTITY_URL, **client_kwargs): super(V1, self).__init__() self.identity_url = identity_url self.compute_url = compute_url self.client = None self.requests_mock = requests_mock self.token = fixture.V2Token() self.token.set_scope() self.discovery = fixture.V2Discovery(href=self.identity_url) s = self.token.add_service('compute') s.add_endpoint(self.compute_url) s = self.token.add_service('computev3') s.add_endpoint(self.compute_url) self._client_kwargs = client_kwargs def setUp(self): super(V1, self).setUp() auth_url = '%s/tokens' % self.identity_url headers = {'X-Content-Type': 'application/json'} self.requests_mock.post(auth_url, json=self.token, headers=headers) self.requests_mock.get(self.identity_url, json=self.discovery, headers=headers) self.client = self.new_client(**self._client_kwargs) def new_client(self, **client_kwargs): return client.Client("2", username='xx', password='xx', project_id='xx', auth_url=self.identity_url, **client_kwargs) class SessionV1(V1): def new_client(self): self.session = session.Session() loader = loading.get_plugin_loader('password') self.session.auth = loader.load_from_options( auth_url=self.identity_url, username='xx', password='xx') return client.Client("2", session=self.session) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/fixture_data/floatingips.py0000664000175000017500000000355500000000000027156 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 novaclient.tests.unit.fixture_data import base class FloatingFixture(base.Fixture): base_url = 'os-floating-ips' def setUp(self): super(FloatingFixture, self).setUp() floating_ips = [{'id': 1, 'fixed_ip': '10.0.0.1', 'ip': '11.0.0.1'}, {'id': 2, 'fixed_ip': '10.0.0.2', 'ip': '11.0.0.2'}] get_os_floating_ips = {'floating_ips': floating_ips} self.requests_mock.get(self.url(), json=get_os_floating_ips, headers=self.json_headers) for ip in floating_ips: get_os_floating_ip = {'floating_ip': ip} self.requests_mock.get(self.url(ip['id']), json=get_os_floating_ip, headers=self.json_headers) self.requests_mock.delete(self.url(ip['id']), headers=self.json_headers, status_code=204) def post_os_floating_ips(request, context): ip = floating_ips[0].copy() ip['pool'] = request.json().get('pool') return {'floating_ip': ip} self.requests_mock.post(self.url(), json=post_os_floating_ips, headers=self.json_headers) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/fixture_data/hypervisors.py0000664000175000017500000002505500000000000027233 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 urllib import parse from oslo_utils import encodeutils from novaclient import api_versions from novaclient.tests.unit.fixture_data import base class V1(base.Fixture): base_url = 'os-hypervisors' api_version = '2.1' hyper_id_1 = 1234 hyper_id_2 = 5678 service_id_1 = 1 service_id_2 = 2 @staticmethod def _transform_hypervisor_details(hypervisor): """Transform a detailed hypervisor view from 2.53 to 2.88.""" del hypervisor['current_workload'] del hypervisor['disk_available_least'] del hypervisor['free_ram_mb'] del hypervisor['free_disk_gb'] del hypervisor['local_gb'] del hypervisor['local_gb_used'] del hypervisor['memory_mb'] del hypervisor['memory_mb_used'] del hypervisor['running_vms'] del hypervisor['vcpus'] del hypervisor['vcpus_used'] hypervisor['uptime'] = 'fake uptime' def setUp(self): super(V1, self).setUp() api_version = api_versions.APIVersion(self.api_version) get_os_hypervisors = { 'hypervisors': [ { 'id': self.hyper_id_1, 'hypervisor_hostname': 'hyper1', 'state': 'up', 'status': 'enabled', }, { 'id': self.hyper_id_2, 'hypervisor_hostname': 'hyper2', 'state': 'up', 'status': 'enabled', }, ] } self.headers = self.json_headers self.requests_mock.get(self.url(), json=get_os_hypervisors, headers=self.headers) get_os_hypervisors_detail = { 'hypervisors': [ { 'id': self.hyper_id_1, 'service': { 'id': self.service_id_1, 'host': 'compute1', }, 'vcpus': 4, 'memory_mb': 10 * 1024, 'local_gb': 250, 'vcpus_used': 2, 'memory_mb_used': 5 * 1024, 'local_gb_used': 125, 'hypervisor_type': 'xen', 'hypervisor_version': 3, 'hypervisor_hostname': 'hyper1', 'free_ram_mb': 5 * 1024, 'free_disk_gb': 125, 'current_workload': 2, 'running_vms': 2, 'cpu_info': 'cpu_info', 'disk_available_least': 100, 'state': 'up', 'status': 'enabled', }, { 'id': self.hyper_id_2, 'service': { 'id': self.service_id_2, 'host': 'compute2', }, 'vcpus': 4, 'memory_mb': 10 * 1024, 'local_gb': 250, 'vcpus_used': 2, 'memory_mb_used': 5 * 1024, 'local_gb_used': 125, 'hypervisor_type': 'xen', 'hypervisor_version': 3, 'hypervisor_hostname': 'hyper2', 'free_ram_mb': 5 * 1024, 'free_disk_gb': 125, 'current_workload': 2, 'running_vms': 2, 'cpu_info': 'cpu_info', 'disk_available_least': 100, 'state': 'up', 'status': 'enabled', } ] } if api_version >= api_versions.APIVersion('2.88'): for hypervisor in get_os_hypervisors_detail['hypervisors']: self._transform_hypervisor_details(hypervisor) self.requests_mock.get(self.url('detail'), json=get_os_hypervisors_detail, headers=self.headers) get_os_hypervisors_stats = { 'hypervisor_statistics': { 'count': 2, 'vcpus': 8, 'memory_mb': 20 * 1024, 'local_gb': 500, 'vcpus_used': 4, 'memory_mb_used': 10 * 1024, 'local_gb_used': 250, 'free_ram_mb': 10 * 1024, 'free_disk_gb': 250, 'current_workload': 4, 'running_vms': 4, 'disk_available_least': 200, } } self.requests_mock.get(self.url('statistics'), json=get_os_hypervisors_stats, headers=self.headers) get_os_hypervisors_search = { 'hypervisors': [ { 'id': self.hyper_id_1, 'hypervisor_hostname': 'hyper1', 'state': 'up', 'status': 'enabled', }, { 'id': self.hyper_id_2, 'hypervisor_hostname': 'hyper2', 'state': 'up', 'status': 'enabled', }, ] } if api_version >= api_versions.APIVersion('2.53'): url = self.url(hypervisor_hostname_pattern='hyper') else: url = self.url('hyper', 'search') self.requests_mock.get(url, json=get_os_hypervisors_search, headers=self.headers) if api_version >= api_versions.APIVersion('2.53'): get_os_hypervisors_search_u_v2_53 = { 'error_name': 'BadRequest', 'message': 'Invalid input for query parameters ' 'hypervisor_hostname_pattern.', 'code': 400} # hypervisor_hostname_pattern is encoded in the url method url = self.url(hypervisor_hostname_pattern='\\u5de5\\u4f5c') self.requests_mock.get(url, json=get_os_hypervisors_search_u_v2_53, headers=self.headers, status_code=400) else: get_os_hypervisors_search_unicode = { 'error_name': 'NotFound', 'message': "No hypervisor matching " "'\\u5de5\\u4f5c' could be found.", 'code': 404 } hypervisor_hostname_pattern = parse.quote(encodeutils.safe_encode( '\\u5de5\\u4f5c')) url = self.url(hypervisor_hostname_pattern, 'search') self.requests_mock.get(url, json=get_os_hypervisors_search_unicode, headers=self.headers, status_code=404) get_hyper_server = { 'hypervisors': [ { 'id': self.hyper_id_1, 'hypervisor_hostname': 'hyper1', 'state': 'up', 'status': 'enabled', 'servers': [ {'name': 'inst1', 'uuid': 'uuid1'}, {'name': 'inst2', 'uuid': 'uuid2'} ] }, { 'id': self.hyper_id_2, 'hypervisor_hostname': 'hyper2', 'state': 'up', 'status': 'enabled', 'servers': [ {'name': 'inst3', 'uuid': 'uuid3'}, {'name': 'inst4', 'uuid': 'uuid4'} ] } ] } if api_version >= api_versions.APIVersion('2.53'): url = self.url(hypervisor_hostname_pattern='hyper', with_servers=True) else: url = self.url('hyper', 'servers') self.requests_mock.get(url, json=get_hyper_server, headers=self.headers) get_os_hypervisors_hyper1 = { 'hypervisor': { 'id': self.hyper_id_1, 'service': {'id': self.service_id_1, 'host': 'compute1'}, 'vcpus': 4, 'memory_mb': 10 * 1024, 'local_gb': 250, 'vcpus_used': 2, 'memory_mb_used': 5 * 1024, 'local_gb_used': 125, 'hypervisor_type': 'xen', 'hypervisor_version': 3, 'hypervisor_hostname': 'hyper1', 'free_ram_mb': 5 * 1024, 'free_disk_gb': 125, 'current_workload': 2, 'running_vms': 2, 'cpu_info': 'cpu_info', 'disk_available_least': 100, 'state': 'up', 'status': 'enabled', } } if api_version >= api_versions.APIVersion('2.88'): self._transform_hypervisor_details( get_os_hypervisors_hyper1['hypervisor']) self.requests_mock.get(self.url(self.hyper_id_1), json=get_os_hypervisors_hyper1, headers=self.headers) get_os_hypervisors_uptime = { 'hypervisor': { 'id': self.hyper_id_1, 'hypervisor_hostname': 'hyper1', 'uptime': 'fake uptime', 'state': 'up', 'status': 'enabled', } } self.requests_mock.get(self.url(self.hyper_id_1, 'uptime'), json=get_os_hypervisors_uptime, headers=self.headers) class V253(V1): """Fixture data for the os-hypervisors 2.53 API.""" api_version = '2.53' hyper_id_1 = 'd480b1b6-2255-43c2-b2c2-d60d42c2c074' hyper_id_2 = '43a8214d-f36a-4fc0-a25c-3cf35c17522d' service_id_1 = 'a87743ff-9c29-42ff-805d-2444659b5fc0' service_id_2 = '0486ab8b-1cfc-4ccb-9d94-9f22ec8bbd6b' class V288(V253): """Fixture data for the os-hypervisors 2.88 API.""" api_version = '2.88' ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/fixture_data/images.py0000664000175000017500000000511400000000000026075 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 novaclient.tests.unit import fakes from novaclient.tests.unit.fixture_data import base class V1(base.Fixture): base_url = 'v2/images' def setUp(self): super(V1, self).setUp() get_images = { 'images': [ {'id': 1, 'name': 'CentOS 5.2'}, {'id': 2, 'name': 'My Server Backup'} ] } headers = self.json_headers self.requests_mock.get(self.url(), json=get_images, headers=headers) image_1 = { 'id': 1, 'name': 'CentOS 5.2', "updated": "2010-10-10T12:00:00Z", "created": "2010-08-10T12:00:00Z", "status": "ACTIVE", "metadata": { "test_key": "test_value", }, "links": {}, } image_2 = { "id": 2, "name": "My Server Backup", "serverId": 1234, "updated": "2010-10-10T12:00:00Z", "created": "2010-08-10T12:00:00Z", "status": "SAVING", "progress": 80, "links": {}, } self.requests_mock.get(self.url('detail'), json={'images': [image_1, image_2]}, headers=headers) self.requests_mock.get(self.url(1), json={'image': image_1}, headers=headers) def post_images_1_metadata(request, context): body = request.json() assert list(body) == ['metadata'] fakes.assert_has_keys(body['metadata'], required=['test_key']) return {'metadata': image_1['metadata']} self.requests_mock.post(self.url(1, 'metadata'), json=post_images_1_metadata, headers=headers) for u in (1, '1/metadata/test_key'): self.requests_mock.delete(self.url(u), status_code=204, headers=headers) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/fixture_data/keypairs.py0000664000175000017500000000404400000000000026460 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 novaclient import api_versions from novaclient.tests.unit import fakes from novaclient.tests.unit.fixture_data import base class V1(base.Fixture): api_version = '2.1' base_url = 'os-keypairs' def setUp(self): super(V1, self).setUp() api_version = api_versions.APIVersion(self.api_version) keypair = {'fingerprint': 'FAKE_KEYPAIR', 'name': 'test'} headers = self.json_headers self.requests_mock.get(self.url(), json={'keypairs': [keypair]}, headers=headers) self.requests_mock.get(self.url('test'), json={'keypair': keypair}, headers=headers) self.requests_mock.delete(self.url('test'), status_code=202, headers=headers) def post_os_keypairs(request, context): body = request.json() assert list(body) == ['keypair'] if api_version >= api_versions.APIVersion("2.92"): # In 2.92, public_key becomes mandatory required = ['name', 'public_key'] else: required = ['name'] fakes.assert_has_keys(body['keypair'], required=required) return {'keypair': keypair} self.requests_mock.post(self.url(), json=post_os_keypairs, headers=headers) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/fixture_data/limits.py0000664000175000017500000000605300000000000026134 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 novaclient.tests.unit.fixture_data import base class Fixture(base.Fixture): base_url = 'limits' absolute = { "maxTotalRAMSize": 51200, "maxServerMeta": 5, "maxImageMeta": 5, "maxPersonality": 5, "maxPersonalitySize": 10240 } def setUp(self): super(Fixture, self).setUp() get_limits = { "limits": { "rate": [ { "uri": "*", "regex": ".*", "limit": [ { "value": 10, "verb": "POST", "remaining": 2, "unit": "MINUTE", "next-available": "2011-12-15T22:42:45Z" }, { "value": 10, "verb": "PUT", "remaining": 2, "unit": "MINUTE", "next-available": "2011-12-15T22:42:45Z" }, { "value": 100, "verb": "DELETE", "remaining": 100, "unit": "MINUTE", "next-available": "2011-12-15T22:42:45Z" } ] }, { "uri": "*/servers", "regex": "^/servers", "limit": [ { "verb": "POST", "value": 25, "remaining": 24, "unit": "DAY", "next-available": "2011-12-15T22:42:45Z" } ] } ], "absolute": self.absolute, }, } headers = self.json_headers self.requests_mock.get(self.url(), json=get_limits, headers=headers) class Fixture2_57(Fixture): """Fixture data for the 2.57 microversion where personality files are deprecated. """ absolute = { "maxTotalRAMSize": 51200, "maxServerMeta": 5 } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/fixture_data/quotas.py0000664000175000017500000000572600000000000026155 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 novaclient.tests.unit.fixture_data import base class V1(base.Fixture): base_url = 'os-quota-sets' def setUp(self): super(V1, self).setUp() uuid = '97f4c221-bff4-4578-b030-0df4ef119353' uuid2 = '97f4c221bff44578b0300df4ef119353' test_json = {'quota_set': self.test_quota('test')} self.headers = self.json_headers for u in ('test', 'tenant-id', 'tenant-id/defaults', '%s/defaults' % uuid2, 'test/detail'): self.requests_mock.get(self.url(u), json=test_json, headers=self.headers) self.requests_mock.put(self.url(uuid), json={'quota_set': self.test_quota(uuid)}, headers=self.headers) self.requests_mock.get(self.url(uuid), json={'quota_set': self.test_quota(uuid)}, headers=self.headers) self.requests_mock.put(self.url(uuid2), json={'quota_set': self.test_quota(uuid2)}, headers=self.headers) self.requests_mock.get(self.url(uuid2), json={'quota_set': self.test_quota(uuid2)}, headers=self.headers) for u in ('test', uuid2): self.requests_mock.delete(self.url(u), status_code=202, headers=self.headers) def test_quota(self, tenant_id='test'): return { 'id': tenant_id, 'metadata_items': 1, 'injected_file_content_bytes': 1, 'injected_file_path_bytes': 1, 'ram': 1, 'fixed_ips': -1, 'floating_ips': 1, 'instances': 1, 'injected_files': 1, 'cores': 1, 'key_pairs': 1, 'security_groups': 1, 'security_group_rules': 1, 'server_groups': 1, 'server_group_members': 1 } class V2_57(V1): """2.57 fixture data where there are no injected file or network resources """ def test_quota(self, tenant_id='test'): return { 'id': tenant_id, 'metadata_items': 1, 'ram': 1, 'instances': 1, 'cores': 1, 'key_pairs': 1, 'server_groups': 1, 'server_group_members': 1 } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/fixture_data/server_groups.py0000664000175000017500000000761200000000000027542 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 novaclient.tests.unit.fixture_data import base class Fixture(base.Fixture): base_url = 'os-server-groups' def setUp(self): super(Fixture, self).setUp() server_groups = [ { "members": [], "metadata": {}, "id": "2cbd51f4-fafe-4cdb-801b-cf913a6f288b", "policies": [], "name": "ig1" }, { "members": [], "metadata": {}, "id": "4473bb03-4370-4bfb-80d3-dc8cffc47d94", "policies": ["anti-affinity"], "name": "ig2" }, { "members": [], "metadata": {"key": "value"}, "id": "31ab9bdb-55e1-4ac3-b094-97eeb1b65cc4", "policies": [], "name": "ig3" }, { "members": ["2dccb4a1-02b9-482a-aa23-5799490d6f5d"], "metadata": {}, "id": "4890bb03-7070-45fb-8453-d34556c87d94", "policies": ["anti-affinity"], "name": "ig2" } ] other_project_server_groups = [ { "members": [], "metadata": {}, "id": "11111111-1111-1111-1111-111111111111", "policies": [], "name": "ig4" }, { "members": [], "metadata": {}, "id": "22222222-2222-2222-2222-222222222222", "policies": ["anti-affinity"], "name": "ig5" }, { "members": [], "metadata": {"key": "value"}, "id": "33333333-3333-3333-3333-333333333333", "policies": [], "name": "ig6" }, { "members": ["2dccb4a1-02b9-482a-aa23-5799490d6f5d"], "metadata": {}, "id": "44444444-4444-4444-4444-444444444444", "policies": ["anti-affinity"], "name": "ig5" } ] headers = self.json_headers self.requests_mock.get(self.url(), json={'server_groups': server_groups}, headers=headers) self.requests_mock.get(self.url(all_projects=True), json={'server_groups': server_groups + other_project_server_groups}, headers=headers) self.requests_mock.get(self.url(limit=2, offset=1), json={'server_groups': server_groups[1:3]}, headers=headers) server = server_groups[0] def _register(method, *args): self.requests_mock.register_uri(method, self.url(*args), json={'server_group': server}, headers=headers) _register('POST') _register('POST', server['id']) _register('GET', server['id']) _register('PUT', server['id']) _register('POST', server['id'], '/action') self.requests_mock.delete(self.url(server['id']), status_code=202, headers=headers) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/fixture_data/server_migrations.py0000664000175000017500000000611200000000000030371 0ustar00zuulzuul00000000000000# Copyright 2016 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from novaclient.tests.unit.fixture_data import base class Fixture(base.Fixture): base_url = 'servers' def setUp(self): super(Fixture, self).setUp() url = self.url('1234', 'migrations', '1', 'action') self.requests_mock.post(url, status_code=202, headers=self.json_headers) get_migrations = {'migrations': [ { "created_at": "2016-01-29T13:42:02.000000", "dest_compute": "compute2", "dest_host": "1.2.3.4", "dest_node": "node2", "id": 1, "server_uuid": "4cfba335-03d8-49b2-8c52-e69043d1e8fe", "source_compute": "compute1", "source_node": "node1", "status": "running", "memory_total_bytes": 123456, "memory_processed_bytes": 12345, "memory_remaining_bytes": 120000, "disk_total_bytes": 234567, "disk_processed_bytes": 23456, "disk_remaining_bytes": 230000, "updated_at": "2016-01-29T13:42:02.000000" }]} url = self.url('1234', 'migrations') self.requests_mock.get(url, json=get_migrations, headers=self.json_headers) get_migration = {'migration': { "created_at": "2016-01-29T13:42:02.000000", "dest_compute": "compute2", "dest_host": "1.2.3.4", "dest_node": "node2", "id": 1, "server_uuid": "4cfba335-03d8-49b2-8c52-e69043d1e8fe", "source_compute": "compute1", "source_node": "node1", "status": "running", "memory_total_bytes": 123456, "memory_processed_bytes": 12345, "memory_remaining_bytes": 120000, "disk_total_bytes": 234567, "disk_processed_bytes": 23456, "disk_remaining_bytes": 230000, "updated_at": "2016-01-29T13:42:02.000000" }} url = self.url('1234', 'migrations', '1') self.requests_mock.get(url, json=get_migration, headers=self.json_headers) url = self.url('1234', 'migrations', '1') self.requests_mock.delete(url, status_code=202, headers=self.json_headers) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/fixture_data/servers.py0000664000175000017500000004663300000000000026334 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 novaclient import api_versions from novaclient.tests.unit import fakes from novaclient.tests.unit.fixture_data import base from novaclient.tests.unit.v2 import fakes as v2_fakes class Base(base.Fixture): base_url = 'servers' def setUp(self): super(Base, self).setUp() get_servers = { "servers": [ {'id': 1234, 'name': 'sample-server'}, {'id': 5678, 'name': 'sample-server2'} ] } self.requests_mock.get(self.url(), json=get_servers, headers=self.json_headers) self.server_1234 = { "id": 1234, "name": "sample-server", "image": { "id": 2, "name": "sample image", }, "flavor": { "id": 1, "name": "256 MiB Server", }, "hostId": "e4d909c290d0fb1ca068ffaddf22cbd0", "status": "BUILD", "progress": 60, "addresses": { "public": [ { "version": 4, "addr": "1.2.3.4", }, { "version": 4, "addr": "5.6.7.8", }], "private": [{ "version": 4, "addr": "10.11.12.13", }], }, "metadata": { "Server Label": "Web Head 1", "Image Version": "2.1" }, "OS-EXT-SRV-ATTR:host": "computenode1", "security_groups": [{ 'id': 1, 'name': 'securitygroup1', 'description': 'FAKE_SECURITY_GROUP', 'tenant_id': '4ffc664c198e435e9853f2538fbcd7a7' }], "OS-EXT-MOD:some_thing": "mod_some_thing_value", } self.server_5678 = { "id": 5678, "name": "sample-server2", "image": { "id": 2, "name": "sample image", }, "flavor": { "id": 1, "name": "256 MiB Server", }, "hostId": "9e107d9d372bb6826bd81d3542a419d6", "status": "ACTIVE", "addresses": { "public": [ { "version": 4, "addr": "4.5.6.7", }, { "version": 4, "addr": "5.6.9.8", }], "private": [{ "version": 4, "addr": "10.13.12.13", }], }, "metadata": { "Server Label": "DB 1" }, "OS-EXT-SRV-ATTR:host": "computenode2", "security_groups": [ { 'id': 1, 'name': 'securitygroup1', 'description': 'FAKE_SECURITY_GROUP', 'tenant_id': '4ffc664c198e435e9853f2538fbcd7a7' }, { 'id': 2, 'name': 'securitygroup2', 'description': 'ANOTHER_FAKE_SECURITY_GROUP', 'tenant_id': '4ffc664c198e435e9853f2538fbcd7a7' }], } self.server_9012 = { "id": 9012, "name": "sample-server3", "image": "", "flavor": { "id": 1, "name": "256 MiB Server", }, "hostId": "9e107d9d372bb6826bd81d3542a419d6", "status": "ACTIVE", "addresses": { "public": [ { "version": 4, "addr": "4.5.6.7", }, { "version": 4, "addr": "5.6.9.8", }], "private": [{ "version": 4, "addr": "10.13.12.13", }], }, "metadata": { "Server Label": "DB 1" } } servers = [self.server_1234, self.server_5678, self.server_9012] get_servers_detail = {"servers": servers} self.requests_mock.get(self.url('detail'), json=get_servers_detail, headers=self.json_headers) self.requests_mock.get( self.url('detail', marker=self.server_1234["id"]), json={"servers": [self.server_1234, self.server_5678]}, headers=self.json_headers, complete_qs=True) self.requests_mock.get( self.url('detail', marker=self.server_5678["id"]), json={"servers": []}, headers=self.json_headers, complete_qs=True) self.server_1235 = self.server_1234.copy() self.server_1235['id'] = 1235 self.server_1235['status'] = 'error' self.server_1235['fault'] = {'message': 'something went wrong!'} for s in servers + [self.server_1235]: self.requests_mock.get(self.url(s['id']), json={'server': s}, headers=self.json_headers) for s in (1234, 5678): self.requests_mock.delete(self.url(s), status_code=202, headers=self.json_headers) for k in ('test_key', 'key1', 'key2'): self.requests_mock.delete(self.url(1234, 'metadata', k), status_code=204, headers=self.json_headers) metadata1 = {'metadata': {'test_key': 'test_value'}} self.requests_mock.post(self.url(1234, 'metadata'), json=metadata1, headers=self.json_headers) self.requests_mock.put(self.url(1234, 'metadata', 'test_key'), json=metadata1, headers=self.json_headers) self.diagnostic = {'data': 'Fake diagnostics'} metadata2 = {'metadata': {'key1': 'val1'}} for u in ('uuid1', 'uuid2', 'uuid3', 'uuid4'): self.requests_mock.post(self.url(u, 'metadata'), json=metadata2, status_code=204) self.requests_mock.delete(self.url(u, 'metadata', 'key1'), json=self.diagnostic, headers=self.json_headers) metadata3 = {'meta': { 'Server Label': 'Web Head 1' }} self.requests_mock.get(self.url(1234, 'metadata', 'Server Label'), json=metadata3, headers=self.json_headers) metadata4 = {'metadata': { 'Server Label': 'Web Head 1', 'Image Version': '2.1' }} self.requests_mock.get(self.url(1234, 'metadata'), json=metadata4, headers=self.json_headers) get_security_groups = { "security_groups": [{ 'id': 1, 'name': 'securitygroup1', 'description': 'FAKE_SECURITY_GROUP', 'tenant_id': '4ffc664c198e435e9853f2538fbcd7a7', 'rules': []}] } self.requests_mock.get(self.url('1234', 'os-security-groups'), json=get_security_groups, headers=self.json_headers) self.requests_mock.post(self.url(), json=self.post_servers, headers=self.json_headers) self.requests_mock.post(self.url('1234', 'remote-consoles'), json=self.post_servers_1234_remote_consoles, headers=self.json_headers) self.requests_mock.post(self.url('1234', 'action'), json=self.post_servers_1234_action, headers=self.json_headers) get_os_interface = { "interfaceAttachments": [ { "port_state": "ACTIVE", "net_id": "net-id-1", "port_id": "port-id-1", "mac_address": "aa:bb:cc:dd:ee:ff", "fixed_ips": [{"ip_address": "1.2.3.4"}], }, { "port_state": "ACTIVE", "net_id": "net-id-1", "port_id": "port-id-1", "mac_address": "aa:bb:cc:dd:ee:ff", "fixed_ips": [{"ip_address": "1.2.3.4"}], } ] } self.requests_mock.get(self.url('1234', 'os-interface'), json=get_os_interface, headers=self.json_headers) interface_data = {'interfaceAttachment': {}} self.requests_mock.post(self.url('1234', 'os-interface'), json=interface_data, headers=self.json_headers) def put_servers_1234(request, context): body = request.json() assert list(body) == ['server'] fakes.assert_has_keys(body['server'], optional=['name', 'adminPass']) return request.body self.requests_mock.put(self.url(1234), text=put_servers_1234, status_code=204, headers=self.json_headers) # # Server password # self.requests_mock.delete(self.url(1234, 'os-server-password'), status_code=202, headers=self.json_headers) # # Server tags # self.requests_mock.get(self.url(1234, 'tags'), json={'tags': ['tag1', 'tag2']}, headers=self.json_headers) self.requests_mock.get(self.url(1234, 'tags', 'tag'), status_code=204, headers=self.json_headers) self.requests_mock.delete(self.url(1234, 'tags', 'tag'), status_code=204, headers=self.json_headers) self.requests_mock.delete(self.url(1234, 'tags'), status_code=204, headers=self.json_headers) def put_server_tag(request, context): assert request.text is None context.status_code = 201 return None self.requests_mock.put(self.url(1234, 'tags', 'tag'), json=put_server_tag, headers=self.json_headers) def put_server_tags(request, context): body = request.json() assert list(body) == ['tags'] return body self.requests_mock.put(self.url(1234, 'tags'), json=put_server_tags, headers=self.json_headers) class V1(Base): def setUp(self): super(V1, self).setUp() # # Server Addresses # add = self.server_1234['addresses'] self.requests_mock.get(self.url(1234, 'ips'), json={'addresses': add}, headers=self.json_headers) self.requests_mock.get(self.url(1234, 'ips', 'public'), json={'public': add['public']}, headers=self.json_headers) self.requests_mock.get(self.url(1234, 'ips', 'private'), json={'private': add['private']}, headers=self.json_headers) self.requests_mock.delete(self.url(1234, 'ips', 'public', '1.2.3.4'), status_code=202) self.requests_mock.get(self.url('1234', 'diagnostics'), json=self.diagnostic, headers=self.json_headers) self.requests_mock.delete(self.url('1234', 'os-interface', 'port-id'), headers=self.json_headers) self.requests_mock.get(self.url('1234', 'topology'), json=v2_fakes.SERVER_TOPOLOGY, headers=self.json_headers) # Testing with the following password and key # # Clear password: FooBar123 # # RSA Private Key: novaclient/tests/unit/idfake.pem # # Encrypted password # OIuEuQttO8Rk93BcKlwHQsziDAnkAm/V6V8VPToA8ZeUaUBWwS0gwo2K6Y61Z96r # qG447iRz0uTEEYq3RAYJk1mh3mMIRVl27t8MtIecR5ggVVbz1S9AwXJQypDKl0ho # QFvhCBcMWPohyGewDJOhDbtuN1IoFI9G55ZvFwCm5y7m7B2aVcoLeIsJZE4PLsIw # /y5a6Z3/AoJZYGG7IH5WN88UROU3B9JZGFB2qtPLQTOvDMZLUhoPRIJeHiVSlo1N # tI2/++UsXVg3ow6ItqCJGgdNuGG5JB+bslDHWPxROpesEIHdczk46HCpHQN8f1sk # Hi/fmZZNQQqj1Ijq0caOIw== get_server_password = { 'password': 'OIuEuQttO8Rk93BcKlwHQsziDAnkAm/V6V8VPToA8ZeUaUBWwS0gwo2K6Y61Z96r' 'qG447iRz0uTEEYq3RAYJk1mh3mMIRVl27t8MtIecR5ggVVbz1S9AwXJQypDKl0ho' 'QFvhCBcMWPohyGewDJOhDbtuN1IoFI9G55ZvFwCm5y7m7B2aVcoLeIsJZE4PLsIw' '/y5a6Z3/AoJZYGG7IH5WN88UROU3B9JZGFB2qtPLQTOvDMZLUhoPRIJeHiVSlo1N' 'tI2/++UsXVg3ow6ItqCJGgdNuGG5JB+bslDHWPxROpesEIHdczk46HCpHQN8f1sk' 'Hi/fmZZNQQqj1Ijq0caOIw=='} self.requests_mock.get(self.url(1234, 'os-server-password'), json=get_server_password, headers=self.json_headers) def post_servers(self, request, context): body = request.json() context.status_code = 202 assert (set(body.keys()) <= set(['server', 'os:scheduler_hints'])) fakes.assert_has_keys(body['server'], required=['name', 'imageRef', 'flavorRef'], optional=['metadata', 'personality']) if 'personality' in body['server']: for pfile in body['server']['personality']: fakes.assert_has_keys(pfile, required=['path', 'contents']) if ('return_reservation_id' in body['server'].keys() and body['server']['return_reservation_id']): return {'reservation_id': 'r-3fhpjulh'} if body['server']['name'] == 'some-bad-server': body = self.server_1235 else: body = self.server_1234 return {'server': body} def post_servers_1234_remote_consoles(self, request, context): _body = '' body = request.json() context.status_code = 202 assert len(body.keys()) == 1 assert 'remote_console' in body.keys() assert 'protocol' in body['remote_console'].keys() protocol = body['remote_console']['protocol'] _body = {'protocol': protocol, 'type': 'novnc', 'url': 'http://example.com:6080/vnc_auto.html?token=XYZ'} return {'remote_console': _body} def post_servers_1234_action(self, request, context): _body = '' body = request.json() context.status_code = 202 assert len(body.keys()) == 1 action = list(body)[0] api_version = api_versions.APIVersion( request.headers.get('X-OpenStack-Nova-API-Version', '2.1')) if v2_fakes.FakeSessionClient.check_server_actions(body): # NOTE(snikitin): No need to do any operations here. This 'pass' # is needed to avoid AssertionError in the last 'else' statement # if we found 'action' in method check_server_actions and # raise AssertionError if we didn't find 'action' at all. pass elif action == 'os-migrateLive': # Fixme(eliqiao): body of os-migrateLive changes from v2.25 # but we can not specify version in data_fixture now and this is # V1 data, so just let it pass pass elif action == 'migrate': return None elif action == 'lock': return None elif action == 'unshelve': if api_version >= api_versions.APIVersion("2.91"): # In 2.91 and above, we allow body to be one of these: # {'unshelve': None} # {'unshelve': {'availability_zone': }} # {'unshelve': {'availability_zone': None}} (Unpin az) # {'unshelve': {'host': }} # {'unshelve': {'availability_zone': , 'host': }} # {'unshelve': {'availability_zone': None, 'host': }} if body[action] is not None: for key in body[action].keys(): key in ['availability_zone', 'host'] return None elif action == 'rebuild': body = body[action] adminPass = body.get('adminPass', 'randompassword') assert 'imageRef' in body _body = self.server_1234.copy() _body['adminPass'] = adminPass elif action == 'confirmResize': assert body[action] is None # This one method returns a different response code context.status_code = 204 return None elif action == 'rescue': if body[action]: keys = set(body[action].keys()) assert not (keys - set(['adminPass', 'rescue_image_ref'])) else: assert body[action] is None _body = {'adminPass': 'RescuePassword'} elif action == 'createImage': assert set(body[action].keys()) == set(['name', 'metadata']) if api_version >= api_versions.APIVersion('2.45'): return {'image_id': '456'} context.headers['location'] = "http://blah/images/456" elif action == 'createBackup': assert set(body[action].keys()) == set(['name', 'backup_type', 'rotation']) if api_version >= api_versions.APIVersion('2.45'): return {'image_id': '456'} context.headers['location'] = "http://blah/images/456" elif action == 'os-getConsoleOutput': assert list(body[action]) == ['length'] context.status_code = 202 return {'output': 'foo'} elif action == 'os-getSerialConsole': assert list(body[action]) == ['type'] elif action == 'evacuate': keys = list(body[action]) if 'adminPass' in keys: keys.remove('adminPass') assert 'host' in keys else: raise AssertionError("Unexpected server action: %s" % action) return {'server': _body} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/idfake.pem0000664000175000017500000000321300000000000023523 0ustar00zuulzuul00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEA9QstF/7prDY7a9La7GS9TpMX+MWWXQgK6pHRLakDFp1WX1Q3 Vly7rWitaZUGirUPMm181oJXBwkKlAxFD7hKjyHYaSswNszPYIAsVkc1+AO5epXz g9kUBNtfg44Pg72UecwLrZ8JpmNZpJlKQOx6vF+yi7JmHrrIf6il/grIGUPzoT2L yReimpyPoBrGtXhJYaCJ/XbKg1idRZiQdmwh1F/OmZWn9p0wunnsv08a0+qIywuw WhG9/Zy9fjnEByfusS6gI0GIxDRL4RWzOqphd3PZzunwIBgEKFhgiki9+2DgcRVO 9I5wnDvfwQREJRZWh1uJa5ZTcfPa1EzZryVeOQIDAQABAoIBABxO3Te/cBk/7p9n LXlPrfrszUEk+ljm+/PbQpIGy1+Kb5b1sKrebaP7ysS+vZG6lvXZZimVxx398mXm APhu7tYYL9r+bUR3ZqGcTQLumRJ8w6mgtxANPN3Oxfr5p1stxIBJjTPSgpfhNFLq joRvjUJDv+mZg2ibZVwyDHMLpdAdKp+3XMdyTLZcH9esqwii+natix7rHd1RuF85 L1dfpxjkItwhgHsfdYS++5X3fRByFOhQ+Nhabh/kPQbQMcteRn1bN6zeCWBSglNb Ka/ZrXb6ApRUc22Ji62mNO2ZPPekLJeCHk2h2E7ezYX+sGDNvvd/jHVDJJ20FjD1 Z9KXuK0CgYEA/2vniy9yWd925QQtWbmrxgy6yj89feMH/LTv4qP298rGZ2nqxsyd 9pdBdb4NMsi4HmV5PG1hp3VRNBHl53DNh5eqzT8WEXnIF+sbrIU3KzrCVAx1kZTl +OWKA6aVUsvvO3y85SOvInnsV+IsOGmU4/WBSjYoe39Bo7mq/YuZB9MCgYEA9ZlB KBm6PjFdHQGNgedXahWzRcwC+ALCYqequPYqJolNzhrK4Uc2sWPSGdnldcHZ4XCQ wbfCxUSwrMpA1oyuIQ0U4aowmOw5DjIueBWI8XBYEVRBlwvJwbXpBZ/DspGzTUDx MBrrEwEaMadQvxhRnAzhp0rQAepatcz6Fgb1JkMCgYBMwDLiew5kfSav6JJsDMPW DksurNQgeNEUmZYfx19V1EPMHWKj/CZXS9oqtEIpCXFyCNHmW4PlmvYcrGgmJJpN 7UAwzo0mES8UKNy2+Yy7W7u7H8dQSKrWILtZH3xtVcR8Xp4wSIm+1V40hkz9YpSP 71y7XQzLF1E1DnyYFZOVawKBgAFrmHfd5jjT2kD/sEzPBK9lXrsJmf7LLUqaw578 NXQxmRSXDRNOcR+Hf0CNBQmwTE1EdGHaaTLw2cC2Drfu6lbgl31SmaNYwl+1pJUn MrqKtseq4BI6jDkljypsKRqQQyQwOvTXQwLCH9+nowzn3Bj17hwkj51jOJESlWOp OKO3AoGBALm+jjqyqX7gSnqK3FAumB8mlhv3yI1Wr1ctwe18mKfKbz17HxXRu9pF K/6e7WMCA1p+jhoE8gj1h2WBcH0nV2qt8Ye8gJBbCi4dhI08o4AfrIV47oZx1RlO qYcA1U9lyaODY5SL8+6PHOy5J/aYtuA+wvfEnWiCIdKQrhWetcn3 -----END RSA PRIVATE KEY----- ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/test_api_versions.py0000664000175000017500000004245000000000000025715 0ustar00zuulzuul00000000000000# Copyright 2016 Mirantis # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock import novaclient from novaclient import api_versions from novaclient import exceptions from novaclient.tests.unit import utils from novaclient import utils as nutils from novaclient.v2 import versions class APIVersionTestCase(utils.TestCase): def test_valid_version_strings(self): def _test_string(version, exp_major, exp_minor): v = api_versions.APIVersion(version) self.assertEqual(v.ver_major, exp_major) self.assertEqual(v.ver_minor, exp_minor) _test_string("1.1", 1, 1) _test_string("2.10", 2, 10) _test_string("5.234", 5, 234) _test_string("12.5", 12, 5) _test_string("2.0", 2, 0) _test_string("2.200", 2, 200) def test_null_version(self): v = api_versions.APIVersion() self.assertTrue(v.is_null()) def test_invalid_version_strings(self): self.assertRaises(exceptions.UnsupportedVersion, api_versions.APIVersion, "2") self.assertRaises(exceptions.UnsupportedVersion, api_versions.APIVersion, "200") self.assertRaises(exceptions.UnsupportedVersion, api_versions.APIVersion, "2.1.4") self.assertRaises(exceptions.UnsupportedVersion, api_versions.APIVersion, "200.23.66.3") self.assertRaises(exceptions.UnsupportedVersion, api_versions.APIVersion, "5 .3") self.assertRaises(exceptions.UnsupportedVersion, api_versions.APIVersion, "5. 3") self.assertRaises(exceptions.UnsupportedVersion, api_versions.APIVersion, "5.03") self.assertRaises(exceptions.UnsupportedVersion, api_versions.APIVersion, "02.1") self.assertRaises(exceptions.UnsupportedVersion, api_versions.APIVersion, "2.001") self.assertRaises(exceptions.UnsupportedVersion, api_versions.APIVersion, "") self.assertRaises(exceptions.UnsupportedVersion, api_versions.APIVersion, " 2.1") self.assertRaises(exceptions.UnsupportedVersion, api_versions.APIVersion, "2.1 ") def test_version_comparisons(self): v1 = api_versions.APIVersion("2.0") v2 = api_versions.APIVersion("2.5") v3 = api_versions.APIVersion("5.23") v4 = api_versions.APIVersion("2.0") v_null = api_versions.APIVersion() self.assertTrue(v1.__lt__(v2)) self.assertTrue(v3.__gt__(v2)) self.assertTrue(v1.__ne__(v2)) self.assertTrue(v1.__eq__(v4)) self.assertTrue(v1.__ne__(v_null)) self.assertTrue(v_null.__eq__(v_null)) self.assertRaises(TypeError, v1.__le__, "2.1") def test_version_matches(self): v1 = api_versions.APIVersion("2.0") v2 = api_versions.APIVersion("2.5") v3 = api_versions.APIVersion("2.45") v4 = api_versions.APIVersion("3.3") v5 = api_versions.APIVersion("3.23") v6 = api_versions.APIVersion("2.0") v7 = api_versions.APIVersion("3.3") v8 = api_versions.APIVersion("4.0") v_null = api_versions.APIVersion() self.assertTrue(v2.matches(v1, v3)) self.assertTrue(v2.matches(v1, v_null)) self.assertTrue(v1.matches(v6, v2)) self.assertTrue(v4.matches(v2, v7)) self.assertTrue(v4.matches(v_null, v7)) self.assertTrue(v4.matches(v_null, v8)) self.assertFalse(v1.matches(v2, v3)) self.assertFalse(v5.matches(v2, v4)) self.assertFalse(v2.matches(v3, v1)) self.assertRaises(ValueError, v_null.matches, v1, v3) def test_get_string(self): v1_string = "3.23" v1 = api_versions.APIVersion(v1_string) self.assertEqual(v1_string, v1.get_string()) self.assertRaises(ValueError, api_versions.APIVersion().get_string) class UpdateHeadersTestCase(utils.TestCase): def test_api_version_is_null(self): headers = {} api_versions.update_headers(headers, api_versions.APIVersion()) self.assertEqual({}, headers) def test_api_version_is_major(self): headers = {} api_versions.update_headers(headers, api_versions.APIVersion("7.0")) self.assertEqual({}, headers) def test_api_version_is_not_null(self): api_version = api_versions.APIVersion("2.3") headers = {} api_versions.update_headers(headers, api_version) self.assertEqual( {"X-OpenStack-Nova-API-Version": api_version.get_string()}, headers) def test_api_version_is_gte_27(self): api_version = api_versions.APIVersion("2.27") headers = {} api_versions.update_headers(headers, api_version) self.assertIn('X-OpenStack-Nova-API-Version', headers) self.assertIn('OpenStack-API-Version', headers) self.assertEqual(api_version.get_string(), headers['X-OpenStack-Nova-API-Version']) self.assertEqual('%s %s' % (api_versions.SERVICE_TYPE, api_version.get_string()), headers['OpenStack-API-Version']) class CheckHeadersTestCase(utils.TestCase): def setUp(self): super(CheckHeadersTestCase, self).setUp() mock_log_patch = mock.patch("novaclient.api_versions.LOG") self.mock_log = mock_log_patch.start() self.addCleanup(mock_log_patch.stop) def test_legacy_microversion_is_specified(self): response = mock.MagicMock( headers={api_versions.LEGACY_HEADER_NAME: ""}) api_versions.check_headers(response, api_versions.APIVersion("2.2")) self.assertFalse(self.mock_log.warning.called) response = mock.MagicMock(headers={}) api_versions.check_headers(response, api_versions.APIVersion("2.2")) self.assertTrue(self.mock_log.warning.called) def test_generic_microversion_is_specified(self): response = mock.MagicMock( headers={api_versions.HEADER_NAME: ""}) api_versions.check_headers(response, api_versions.APIVersion("2.27")) self.assertFalse(self.mock_log.warning.called) response = mock.MagicMock(headers={}) api_versions.check_headers(response, api_versions.APIVersion("2.27")) self.assertTrue(self.mock_log.warning.called) def test_microversion_is_not_specified(self): response = mock.MagicMock( headers={api_versions.LEGACY_HEADER_NAME: ""}) api_versions.check_headers(response, api_versions.APIVersion("2.2")) self.assertFalse(self.mock_log.warning.called) response = mock.MagicMock(headers={}) api_versions.check_headers(response, api_versions.APIVersion("2.0")) self.assertFalse(self.mock_log.warning.called) class GetAPIVersionTestCase(utils.TestCase): def test_get_available_client_versions(self): output = api_versions.get_available_major_versions() self.assertNotEqual([], output) def test_wrong_format(self): self.assertRaises(exceptions.UnsupportedVersion, api_versions.get_api_version, "something_wrong") def test_wrong_major_version(self): self.assertRaises(exceptions.UnsupportedVersion, api_versions.get_api_version, "1") @mock.patch("novaclient.api_versions.APIVersion") def test_only_major_part_is_presented(self, mock_apiversion): version = 7 self.assertEqual(mock_apiversion.return_value, api_versions.get_api_version(version)) mock_apiversion.assert_called_once_with("%s.0" % str(version)) @mock.patch("novaclient.api_versions.APIVersion") def test_major_and_minor_parts_is_presented(self, mock_apiversion): version = "2.7" self.assertEqual(mock_apiversion.return_value, api_versions.get_api_version(version)) mock_apiversion.assert_called_once_with(version) class WrapsTestCase(utils.TestCase): def _get_obj_with_vers(self, vers): return mock.MagicMock(api_version=api_versions.APIVersion(vers)) def _side_effect_of_vers_method(self, *args, **kwargs): m = mock.MagicMock(start_version=args[1], end_version=args[2]) m.name = args[0] return m @mock.patch("novaclient.api_versions._get_function_name") @mock.patch("novaclient.api_versions.VersionedMethod") def test_end_version_is_none(self, mock_versioned_method, mock_name): mock_versioned_method.side_effect = self._side_effect_of_vers_method @api_versions.wraps("2.2") def foo(*args, **kwargs): pass foo(self._get_obj_with_vers("2.4")) mock_versioned_method.assert_called_once_with( mock_name.return_value, api_versions.APIVersion("2.2"), api_versions.APIVersion("2.latest"), mock.ANY) @mock.patch("novaclient.api_versions._get_function_name") @mock.patch("novaclient.api_versions.VersionedMethod") def test_start_and_end_version_are_presented(self, mock_versioned_method, mock_name): mock_versioned_method.side_effect = self._side_effect_of_vers_method @api_versions.wraps("2.2", "2.6") def foo(*args, **kwargs): pass foo(self._get_obj_with_vers("2.4")) mock_versioned_method.assert_called_once_with( mock_name.return_value, api_versions.APIVersion("2.2"), api_versions.APIVersion("2.6"), mock.ANY) @mock.patch("novaclient.api_versions._get_function_name") @mock.patch("novaclient.api_versions.VersionedMethod") def test_api_version_doesnt_match(self, mock_versioned_method, mock_name): mock_versioned_method.side_effect = self._side_effect_of_vers_method @api_versions.wraps("2.2", "2.6") def foo(*args, **kwargs): pass self.assertRaises(exceptions.VersionNotFoundForAPIMethod, foo, self._get_obj_with_vers("2.1")) mock_versioned_method.assert_called_once_with( mock_name.return_value, api_versions.APIVersion("2.2"), api_versions.APIVersion("2.6"), mock.ANY) def test_define_method_is_actually_called(self): checker = mock.MagicMock() @api_versions.wraps("2.2", "2.6") def some_func(*args, **kwargs): checker(*args, **kwargs) obj = self._get_obj_with_vers("2.4") some_args = ("arg_1", "arg_2") some_kwargs = {"key1": "value1", "key2": "value2"} some_func(obj, *some_args, **some_kwargs) checker.assert_called_once_with(*((obj,) + some_args), **some_kwargs) @mock.patch("novaclient.api_versions._get_function_name") def test_arguments_property_is_copied(self, mock_name): @nutils.arg("argument_1") @api_versions.wraps("2.666", "2.777") @nutils.arg("argument_2") def some_func(): pass versioned_method = api_versions.get_substitutions( mock_name.return_value, api_versions.APIVersion("2.700"))[0] self.assertEqual(some_func.arguments, versioned_method.func.arguments) self.assertIn((("argument_1",), {}), versioned_method.func.arguments) self.assertIn((("argument_2",), {}), versioned_method.func.arguments) def test_several_methods_with_same_name_in_one_module(self): class A(object): api_version = api_versions.APIVersion("777.777") @api_versions.wraps("777.777") def f(self): return 1 class B(object): api_version = api_versions.APIVersion("777.777") @api_versions.wraps("777.777") def f(self): return 2 self.assertEqual(1, A().f()) self.assertEqual(2, B().f()) def test_generate_function_name(self): expected_name = "novaclient.tests.unit.test_api_versions.fake_func" self.assertNotIn(expected_name, api_versions._SUBSTITUTIONS) @api_versions.wraps("7777777.7777777") def fake_func(): pass self.assertIn(expected_name, api_versions._SUBSTITUTIONS) self.assertEqual(expected_name, fake_func.__id__) class DiscoverVersionTestCase(utils.TestCase): def setUp(self): super(DiscoverVersionTestCase, self).setUp() self.orig_max = novaclient.API_MAX_VERSION self.orig_min = novaclient.API_MIN_VERSION self.addCleanup(self._clear_fake_version) def _clear_fake_version(self): novaclient.API_MAX_VERSION = self.orig_max novaclient.API_MIN_VERSION = self.orig_min def test_server_is_too_new(self): fake_client = mock.MagicMock() fake_client.versions.get_current.return_value = mock.MagicMock( version="2.7", min_version="2.4") novaclient.API_MAX_VERSION = api_versions.APIVersion("2.3") novaclient.API_MIN_VERSION = api_versions.APIVersion("2.1") self.assertRaises(exceptions.UnsupportedVersion, api_versions.discover_version, fake_client, api_versions.APIVersion('2.latest')) def test_server_is_too_old(self): fake_client = mock.MagicMock() fake_client.versions.get_current.return_value = mock.MagicMock( version="2.7", min_version="2.4") novaclient.API_MAX_VERSION = api_versions.APIVersion("2.10") novaclient.API_MIN_VERSION = api_versions.APIVersion("2.9") self.assertRaises(exceptions.UnsupportedVersion, api_versions.discover_version, fake_client, api_versions.APIVersion('2.latest')) def test_server_end_version_is_the_latest_one(self): fake_client = mock.MagicMock() fake_client.versions.get_current.return_value = mock.MagicMock( version="2.7", min_version="2.4") novaclient.API_MAX_VERSION = api_versions.APIVersion("2.11") novaclient.API_MIN_VERSION = api_versions.APIVersion("2.1") self.assertEqual( "2.7", api_versions.discover_version( fake_client, api_versions.APIVersion('2.latest')).get_string()) def test_client_end_version_is_the_latest_one(self): fake_client = mock.MagicMock() fake_client.versions.get_current.return_value = mock.MagicMock( version="2.16", min_version="2.4") novaclient.API_MAX_VERSION = api_versions.APIVersion("2.11") novaclient.API_MIN_VERSION = api_versions.APIVersion("2.1") self.assertEqual( "2.11", api_versions.discover_version( fake_client, api_versions.APIVersion('2.latest')).get_string()) def test_server_without_microversion(self): fake_client = mock.MagicMock() fake_client.versions.get_current.return_value = mock.MagicMock( version='', min_version='') novaclient.API_MAX_VERSION = api_versions.APIVersion("2.11") novaclient.API_MIN_VERSION = api_versions.APIVersion("2.1") self.assertEqual( "2.0", api_versions.discover_version( fake_client, api_versions.APIVersion('2.latest')).get_string()) def test_server_without_microversion_and_no_version_field(self): fake_client = mock.MagicMock() fake_client.versions.get_current.return_value = versions.Version( None, {}) novaclient.API_MAX_VERSION = api_versions.APIVersion("2.11") novaclient.API_MIN_VERSION = api_versions.APIVersion("2.1") self.assertEqual( "2.0", api_versions.discover_version( fake_client, api_versions.APIVersion('2.latest')).get_string()) def test_server_without_microversion_rax_workaround(self): fake_client = mock.MagicMock() fake_client.versions.get_current.return_value = None novaclient.API_MAX_VERSION = api_versions.APIVersion("2.11") novaclient.API_MIN_VERSION = api_versions.APIVersion("2.1") self.assertEqual( "2.0", api_versions.discover_version( fake_client, api_versions.APIVersion('2.latest')).get_string()) class DecoratedAfterTestCase(utils.TestCase): def test_decorated_after(self): class Fake(object): api_version = api_versions.APIVersion('2.123') @api_versions.deprecated_after('2.123') def foo(self): pass with mock.patch('warnings.warn') as mock_warn: Fake().foo() msg = ('The novaclient.tests.unit.test_api_versions module ' 'is deprecated and will be removed.') mock_warn.assert_called_once_with(msg, mock.ANY) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/test_base.py0000664000175000017500000001303600000000000024124 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 requests from novaclient import api_versions from novaclient import base from novaclient import exceptions from novaclient.tests.unit import utils from novaclient.tests.unit.v2 import fakes from novaclient.v2 import flavors def create_response_obj_with_header(): resp = requests.Response() resp.headers['x-openstack-request-id'] = fakes.FAKE_REQUEST_ID return resp def create_response_obj_with_compute_header(): resp = requests.Response() resp.headers['x-compute-request-id'] = fakes.FAKE_REQUEST_ID return resp class BaseTest(utils.TestCase): def test_resource_repr(self): r = base.Resource(None, dict(foo="bar", baz="spam")) self.assertEqual("", repr(r)) def test_getid(self): self.assertEqual(4, base.getid(4)) class TmpObject(object): id = 4 self.assertEqual(4, base.getid(TmpObject)) def test_resource_lazy_getattr(self): cs = fakes.FakeClient(api_versions.APIVersion("2.0")) f = flavors.Flavor(cs.flavors, {'id': 1}) self.assertEqual('256 MiB Server', f.name) cs.assert_called('GET', '/flavors/1') # Missing stuff still fails after a second get self.assertRaises(AttributeError, getattr, f, 'blahblah') def test_eq(self): # Two resources of the same type with the same id: equal r1 = base.Resource(None, {'id': 1, 'name': 'hi'}) r2 = base.Resource(None, {'id': 1, 'name': 'hello'}) self.assertEqual(r1, r2) # Two resources of different types: never equal r1 = base.Resource(None, {'id': 1}) r2 = flavors.Flavor(None, {'id': 1}) self.assertNotEqual(r1, r2) # Two resources with no ID: equal if their info is equal r1 = base.Resource(None, {'name': 'joe', 'age': 12}) r2 = base.Resource(None, {'name': 'joe', 'age': 12}) self.assertEqual(r1, r2) def test_ne(self): # Two resources of different types: never equal r1 = base.Resource(None, {'id': 1, 'name': 'test'}) r2 = object() self.assertNotEqual(r1, r2) def test_findall_invalid_attribute(self): cs = fakes.FakeClient(api_versions.APIVersion("2.0")) # Make sure findall with an invalid attribute doesn't cause errors. # The following should not raise an exception. cs.flavors.findall(vegetable='carrot') # However, find() should raise an error self.assertRaises(exceptions.NotFound, cs.flavors.find, vegetable='carrot') def test_resource_object_with_request_ids(self): resp_obj = create_response_obj_with_header() r = base.Resource(None, {"name": "1"}, resp=resp_obj) self.assertEqual(fakes.FAKE_REQUEST_ID_LIST, r.request_ids) def test_resource_object_with_compute_request_ids(self): resp_obj = create_response_obj_with_compute_header() r = base.Resource(None, {"name": "1"}, resp=resp_obj) self.assertEqual(fakes.FAKE_REQUEST_ID_LIST, r.request_ids) class ListWithMetaTest(utils.TestCase): def test_list_with_meta(self): resp = create_response_obj_with_header() obj = base.ListWithMeta([], resp) self.assertEqual([], obj) # Check request_ids attribute is added to obj self.assertTrue(hasattr(obj, 'request_ids')) self.assertEqual(fakes.FAKE_REQUEST_ID_LIST, obj.request_ids) class DictWithMetaTest(utils.TestCase): def test_dict_with_meta(self): resp = create_response_obj_with_header() obj = base.DictWithMeta({}, resp) self.assertEqual({}, obj) # Check request_ids attribute is added to obj self.assertTrue(hasattr(obj, 'request_ids')) self.assertEqual(fakes.FAKE_REQUEST_ID_LIST, obj.request_ids) class TupleWithMetaTest(utils.TestCase): def test_tuple_with_meta(self): resp = create_response_obj_with_header() expected_tuple = (1, 2) obj = base.TupleWithMeta(expected_tuple, resp) self.assertEqual(expected_tuple, obj) # Check request_ids attribute is added to obj self.assertTrue(hasattr(obj, 'request_ids')) self.assertEqual(fakes.FAKE_REQUEST_ID_LIST, obj.request_ids) class StrWithMetaTest(utils.TestCase): def test_str_with_meta(self): resp = create_response_obj_with_header() obj = base.StrWithMeta("test-str", resp) self.assertEqual("test-str", obj) # Check request_ids attribute is added to obj self.assertTrue(hasattr(obj, 'request_ids')) self.assertEqual(fakes.FAKE_REQUEST_ID_LIST, obj.request_ids) class BytesWithMetaTest(utils.TestCase): def test_bytes_with_meta(self): resp = create_response_obj_with_header() obj = base.BytesWithMeta(b'test-bytes', resp) self.assertEqual(b'test-bytes', obj) # Check request_ids attribute is added to obj self.assertTrue(hasattr(obj, 'request_ids')) self.assertEqual(fakes.FAKE_REQUEST_ID_LIST, obj.request_ids) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/test_client.py0000664000175000017500000001312400000000000024466 0ustar00zuulzuul00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy from unittest import mock from keystoneauth1 import session from oslo_utils import uuidutils import novaclient.api_versions import novaclient.client import novaclient.extension from novaclient.tests.unit import utils import novaclient.v2.client class SessionClientTest(utils.TestCase): def test_timings(self): self.requests_mock.get('http://no.where') client = novaclient.client.SessionClient(session=session.Session()) client.request("http://no.where", 'GET') self.assertEqual(0, len(client.times)) client = novaclient.client.SessionClient(session=session.Session(), timings=True) client.request("http://no.where", 'GET') self.assertEqual(1, len(client.times)) self.assertEqual('GET http://no.where', client.times[0][0]) def test_client_get_reset_timings_v2(self): cs = novaclient.client.SessionClient(session=session.Session()) self.assertEqual(0, len(cs.get_timings())) cs.times.append("somevalue") self.assertEqual(1, len(cs.get_timings())) self.assertEqual("somevalue", cs.get_timings()[0]) cs.reset_timings() self.assertEqual(0, len(cs.get_timings())) def test_global_id(self): global_id = "req-%s" % uuidutils.generate_uuid() self.requests_mock.get('http://no.where') client = novaclient.client.SessionClient(session=session.Session(), global_request_id=global_id) client.request("http://no.where", 'GET') headers = self.requests_mock.last_request.headers self.assertEqual(headers['X-OpenStack-Request-ID'], global_id) class ClientsUtilsTest(utils.TestCase): @mock.patch("novaclient.client._discover_via_entry_points") @mock.patch("novaclient.client._discover_via_python_path") @mock.patch("novaclient.extension.Extension") def test_discover_extensions_all(self, mock_extension, mock_discover_via_python_path, mock_discover_via_entry_points): def make_gen(start, end): def f(*args, **kwargs): for i in range(start, end): yield "name-%s" % i, i return f mock_discover_via_python_path.side_effect = make_gen(0, 3) mock_discover_via_entry_points.side_effect = make_gen(3, 4) version = novaclient.api_versions.APIVersion("2.0") result = novaclient.client.discover_extensions(version) self.assertEqual([mock.call("name-%s" % i, i) for i in range(0, 4)], mock_extension.call_args_list) mock_discover_via_python_path.assert_called_once_with() mock_discover_via_entry_points.assert_called_once_with() self.assertEqual([mock_extension()] * 4, result) @mock.patch("novaclient.client.warnings") def test__check_arguments(self, mock_warnings): release = "Coolest" # no reference novaclient.client._check_arguments({}, release=release, deprecated_name="foo") self.assertFalse(mock_warnings.warn.called) novaclient.client._check_arguments({}, release=release, deprecated_name="foo", right_name="bar") self.assertFalse(mock_warnings.warn.called) # with alternative original_kwargs = {"foo": "text"} actual_kwargs = copy.copy(original_kwargs) self.assertEqual(original_kwargs, actual_kwargs) novaclient.client._check_arguments(actual_kwargs, release=release, deprecated_name="foo", right_name="bar") self.assertNotEqual(original_kwargs, actual_kwargs) self.assertEqual({"bar": original_kwargs["foo"]}, actual_kwargs) self.assertTrue(mock_warnings.warn.called) mock_warnings.warn.reset_mock() # without alternative original_kwargs = {"foo": "text"} actual_kwargs = copy.copy(original_kwargs) self.assertEqual(original_kwargs, actual_kwargs) novaclient.client._check_arguments(actual_kwargs, release=release, deprecated_name="foo") self.assertNotEqual(original_kwargs, actual_kwargs) self.assertEqual({}, actual_kwargs) self.assertTrue(mock_warnings.warn.called) class ClientTest(utils.TestCase): def test_logger(self): client = novaclient.client.Client('2.1', logger=mock.sentinel.logger) self.assertEqual(mock.sentinel.logger, client.logger) self.assertEqual(mock.sentinel.logger, client.client.logger) client = novaclient.client.Client('2.1') self.assertEqual('novaclient.v2.client', client.logger.name) self.assertIsNotNone(client.client.logger) self.assertEqual('novaclient.v2.client', client.client.logger.name) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/test_crypto.py0000664000175000017500000000554700000000000024542 0ustar00zuulzuul00000000000000# Copyright 2018 NTT Corporation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import base64 import subprocess from unittest import mock from novaclient import crypto from novaclient.tests.unit import utils class CryptoTest(utils.TestCase): def setUp(self): super(CryptoTest, self).setUp() # The password string that passed as the method argument self.password_string = 'Test Password' # The return value of Popen.communicate self.decrypt_password = b'Decrypt Password' self.private_key = 'Test Private Key' @mock.patch('subprocess.Popen') def test_decrypt_password(self, mock_open): mocked_proc = mock.Mock() mock_open.return_value = mocked_proc mocked_proc.returncode = 0 mocked_proc.communicate.return_value = (self.decrypt_password, '') decrypt_password = crypto.decrypt_password(self.private_key, self.password_string) # The return value is 'str' in both python 2 and python 3 self.assertIsInstance(decrypt_password, str) self.assertEqual('Decrypt Password', decrypt_password) mock_open.assert_called_once_with( ['openssl', 'rsautl', '-decrypt', '-inkey', self.private_key], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) mocked_proc.communicate.assert_called_once_with( base64.b64decode(self.password_string)) mocked_proc.stdin.close.assert_called_once_with() @mock.patch('subprocess.Popen') def test_decrypt_password_failure(self, mock_open): mocked_proc = mock.Mock() mock_open.return_value = mocked_proc mocked_proc.returncode = 1 # Error case mocked_proc.communicate.return_value = (self.decrypt_password, '') self.assertRaises(crypto.DecryptionFailure, crypto.decrypt_password, self.private_key, self.password_string) mock_open.assert_called_once_with( ['openssl', 'rsautl', '-decrypt', '-inkey', self.private_key], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) mocked_proc.communicate.assert_called_once_with( base64.b64decode(self.password_string)) mocked_proc.stdin.close.assert_called_once_with() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/test_discover.py0000664000175000017500000000566700000000000025043 0ustar00zuulzuul00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import importlib import inspect from unittest import mock import stevedore from stevedore import extension from novaclient import client from novaclient.tests.unit import utils class DiscoverTest(utils.TestCase): def test_discover_via_entry_points(self): def mock_mgr(): fake_ep = mock.Mock() fake_ep.name = 'foo' module_spec = importlib.machinery.ModuleSpec('foo', None) fake_ep.module = importlib.util.module_from_spec(module_spec) fake_ep.load.return_value = fake_ep.module fake_ext = extension.Extension( name='foo', entry_point=fake_ep, plugin=fake_ep.module, obj=None, ) return stevedore.ExtensionManager.make_test_instance([fake_ext]) @mock.patch.object(client, '_make_discovery_manager', mock_mgr) def test(): for name, module in client._discover_via_entry_points(): self.assertEqual('foo', name) self.assertTrue(inspect.ismodule(module)) test() def test_discover_extensions(self): def mock_discover_via_python_path(): module_spec = importlib.machinery.ModuleSpec('foo', None) module = importlib.util.module_from_spec(module_spec) yield 'foo', module def mock_discover_via_entry_points(): module_spec = importlib.machinery.ModuleSpec('baz', None) module = importlib.util.module_from_spec(module_spec) yield 'baz', module @mock.patch.object(client, '_discover_via_python_path', mock_discover_via_python_path) @mock.patch.object(client, '_discover_via_entry_points', mock_discover_via_entry_points) def test(): extensions = client.discover_extensions('1.1') self.assertEqual(2, len(extensions)) names = sorted(['foo', 'baz']) sorted_extensions = sorted(extensions, key=lambda ext: ext.name) for i in range(len(names)): ext = sorted_extensions[i] name = names[i] self.assertEqual(ext.name, name) self.assertTrue(inspect.ismodule(ext.module)) test() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/test_exceptions.py0000664000175000017500000000371200000000000025373 0ustar00zuulzuul00000000000000# Copyright 2016 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 novaclient import exceptions from novaclient.tests.unit import utils as test_utils class ExceptionsTestCase(test_utils.TestCase): def _test_from_response(self, body, expected_message): data = { 'status_code': 404, 'headers': { 'content-type': 'application/json', 'x-openstack-request-id': ( 'req-d9df03b0-4150-4b53-8157-7560ccf39f75'), } } response = test_utils.TestResponse(data) fake_url = 'http://localhost:8774/v2.1/fake/flavors/test' error = exceptions.from_response(response, body, fake_url, 'GET') self.assertIsInstance(error, exceptions.NotFound) self.assertEqual(expected_message, error.message) def test_from_response_webob_pre_1_6_0(self): # Tests error responses before webob 1.6.0 where the error details # are nested in the response body. message = "Flavor test could not be found." self._test_from_response( {"itemNotFound": {"message": message, "code": 404}}, message) def test_from_response_webob_post_1_6_0(self): # Tests error responses from webob 1.6.0 where the error details # are in the response body. message = "Flavor test could not be found." self._test_from_response({"message": message, "code": 404}, message) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/test_shell.py0000664000175000017500000012012100000000000024313 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 io import re import sys from unittest import mock import ddt import fixtures from keystoneauth1 import fixture import requests_mock from testtools import matchers from novaclient import api_versions import novaclient.client from novaclient import exceptions import novaclient.shell from novaclient.tests.unit import fake_actions_module from novaclient.tests.unit import utils FAKE_ENV = {'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_TENANT_NAME': 'tenant_name', 'OS_AUTH_URL': 'http://no.where/v2.0', 'OS_COMPUTE_API_VERSION': '2', 'OS_PROJECT_DOMAIN_ID': 'default', 'OS_PROJECT_DOMAIN_NAME': 'default', 'OS_USER_DOMAIN_ID': 'default', 'OS_USER_DOMAIN_NAME': 'default'} FAKE_ENV2 = {'OS_USER_ID': 'user_id', 'OS_PASSWORD': 'password', 'OS_TENANT_ID': 'tenant_id', 'OS_AUTH_URL': 'http://no.where/v2.0', 'OS_COMPUTE_API_VERSION': '2'} FAKE_ENV3 = {'OS_USER_ID': 'user_id', 'OS_PASSWORD': 'password', 'OS_TENANT_ID': 'tenant_id', 'OS_AUTH_URL': 'http://no.where/v2.0', 'NOVA_ENDPOINT_TYPE': 'novaURL', 'OS_ENDPOINT_TYPE': 'osURL', 'OS_COMPUTE_API_VERSION': '2'} FAKE_ENV4 = {'OS_USER_ID': 'user_id', 'OS_PASSWORD': 'password', 'OS_TENANT_ID': 'tenant_id', 'OS_AUTH_URL': 'http://no.where/v2.0', 'NOVA_ENDPOINT_TYPE': 'internal', 'OS_ENDPOINT_TYPE': 'osURL', 'OS_COMPUTE_API_VERSION': '2'} FAKE_ENV5 = {'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_TENANT_NAME': 'tenant_name', 'OS_AUTH_URL': 'http://no.where/v2.0'} def _create_ver_list(versions): return {'versions': {'values': versions}} class DeprecatedActionTest(utils.TestCase): @mock.patch.object(argparse.Action, '__init__', return_value=None) def test_init_emptyhelp_nouse(self, mock_init): result = novaclient.shell.DeprecatedAction( 'option_strings', 'dest', a=1, b=2, c=3) self.assertEqual(result.emitted, set()) self.assertIsNone(result.use) self.assertEqual(result.real_action_args, ('option_strings', 'dest', 'Deprecated', {'a': 1, 'b': 2, 'c': 3})) self.assertIsNone(result.real_action) mock_init.assert_called_once_with( 'option_strings', 'dest', help='Deprecated', a=1, b=2, c=3) @mock.patch.object(novaclient.shell.argparse.Action, '__init__', return_value=None) def test_init_emptyhelp_withuse(self, mock_init): result = novaclient.shell.DeprecatedAction( 'option_strings', 'dest', use='use this instead', a=1, b=2, c=3) self.assertEqual(result.emitted, set()) self.assertEqual(result.use, 'use this instead') self.assertEqual(result.real_action_args, ('option_strings', 'dest', 'Deprecated; use this instead', {'a': 1, 'b': 2, 'c': 3})) self.assertIsNone(result.real_action) mock_init.assert_called_once_with( 'option_strings', 'dest', help='Deprecated; use this instead', a=1, b=2, c=3) @mock.patch.object(argparse.Action, '__init__', return_value=None) def test_init_withhelp_nouse(self, mock_init): result = novaclient.shell.DeprecatedAction( 'option_strings', 'dest', help='some help', a=1, b=2, c=3) self.assertEqual(result.emitted, set()) self.assertIsNone(result.use) self.assertEqual(result.real_action_args, ('option_strings', 'dest', 'some help (Deprecated)', {'a': 1, 'b': 2, 'c': 3})) self.assertIsNone(result.real_action) mock_init.assert_called_once_with( 'option_strings', 'dest', help='some help (Deprecated)', a=1, b=2, c=3) @mock.patch.object(novaclient.shell.argparse.Action, '__init__', return_value=None) def test_init_withhelp_withuse(self, mock_init): result = novaclient.shell.DeprecatedAction( 'option_strings', 'dest', help='some help', use='use this instead', a=1, b=2, c=3) self.assertEqual(result.emitted, set()) self.assertEqual(result.use, 'use this instead') self.assertEqual(result.real_action_args, ('option_strings', 'dest', 'some help (Deprecated; use this instead)', {'a': 1, 'b': 2, 'c': 3})) self.assertIsNone(result.real_action) mock_init.assert_called_once_with( 'option_strings', 'dest', help='some help (Deprecated; use this instead)', a=1, b=2, c=3) @mock.patch.object(argparse.Action, '__init__', return_value=None) def test_init_suppresshelp_nouse(self, mock_init): result = novaclient.shell.DeprecatedAction( 'option_strings', 'dest', help=argparse.SUPPRESS, a=1, b=2, c=3) self.assertEqual(result.emitted, set()) self.assertIsNone(result.use) self.assertEqual(result.real_action_args, ('option_strings', 'dest', argparse.SUPPRESS, {'a': 1, 'b': 2, 'c': 3})) self.assertIsNone(result.real_action) mock_init.assert_called_once_with( 'option_strings', 'dest', help=argparse.SUPPRESS, a=1, b=2, c=3) @mock.patch.object(novaclient.shell.argparse.Action, '__init__', return_value=None) def test_init_suppresshelp_withuse(self, mock_init): result = novaclient.shell.DeprecatedAction( 'option_strings', 'dest', help=argparse.SUPPRESS, use='use this instead', a=1, b=2, c=3) self.assertEqual(result.emitted, set()) self.assertEqual(result.use, 'use this instead') self.assertEqual(result.real_action_args, ('option_strings', 'dest', argparse.SUPPRESS, {'a': 1, 'b': 2, 'c': 3})) self.assertIsNone(result.real_action) mock_init.assert_called_once_with( 'option_strings', 'dest', help=argparse.SUPPRESS, a=1, b=2, c=3) @mock.patch.object(argparse.Action, '__init__', return_value=None) def test_init_action_nothing(self, mock_init): result = novaclient.shell.DeprecatedAction( 'option_strings', 'dest', real_action='nothing', a=1, b=2, c=3) self.assertEqual(result.emitted, set()) self.assertIsNone(result.use) self.assertIs(result.real_action_args, False) self.assertIsNone(result.real_action) mock_init.assert_called_once_with( 'option_strings', 'dest', help='Deprecated', a=1, b=2, c=3) @mock.patch.object(argparse.Action, '__init__', return_value=None) def test_init_action_string(self, mock_init): result = novaclient.shell.DeprecatedAction( 'option_strings', 'dest', real_action='store', a=1, b=2, c=3) self.assertEqual(result.emitted, set()) self.assertIsNone(result.use) self.assertEqual(result.real_action_args, ('option_strings', 'dest', 'Deprecated', {'a': 1, 'b': 2, 'c': 3})) self.assertEqual(result.real_action, 'store') mock_init.assert_called_once_with( 'option_strings', 'dest', help='Deprecated', a=1, b=2, c=3) @mock.patch.object(argparse.Action, '__init__', return_value=None) def test_init_action_other(self, mock_init): action = mock.Mock() result = novaclient.shell.DeprecatedAction( 'option_strings', 'dest', real_action=action, a=1, b=2, c=3) self.assertEqual(result.emitted, set()) self.assertIsNone(result.use) self.assertIs(result.real_action_args, False) self.assertEqual(result.real_action, action.return_value) mock_init.assert_called_once_with( 'option_strings', 'dest', help='Deprecated', a=1, b=2, c=3) action.assert_called_once_with( 'option_strings', 'dest', help='Deprecated', a=1, b=2, c=3) @mock.patch.object(sys, 'stderr', io.StringIO()) def test_get_action_nolookup(self): action_class = mock.Mock() parser = mock.Mock(**{ '_registry_get.return_value': action_class, }) obj = novaclient.shell.DeprecatedAction( 'option_strings', 'dest', real_action='nothing', const=1) obj.real_action = 'action' result = obj._get_action(parser) self.assertEqual(result, 'action') self.assertEqual(obj.real_action, 'action') self.assertFalse(parser._registry_get.called) self.assertFalse(action_class.called) self.assertEqual(sys.stderr.getvalue(), '') @mock.patch.object(sys, 'stderr', io.StringIO()) def test_get_action_lookup_noresult(self): parser = mock.Mock(**{ '_registry_get.return_value': None, }) obj = novaclient.shell.DeprecatedAction( 'option_strings', 'dest', real_action='store', const=1) result = obj._get_action(parser) self.assertIsNone(result) self.assertIsNone(obj.real_action) parser._registry_get.assert_called_once_with( 'action', 'store') self.assertEqual(sys.stderr.getvalue(), 'WARNING: Programming error: Unknown real action ' '"store"\n') @mock.patch.object(sys, 'stderr', io.StringIO()) def test_get_action_lookup_withresult(self): action_class = mock.Mock() parser = mock.Mock(**{ '_registry_get.return_value': action_class, }) obj = novaclient.shell.DeprecatedAction( 'option_strings', 'dest', real_action='store', const=1) result = obj._get_action(parser) self.assertEqual(result, action_class.return_value) self.assertEqual(obj.real_action, action_class.return_value) parser._registry_get.assert_called_once_with( 'action', 'store') action_class.assert_called_once_with( 'option_strings', 'dest', help='Deprecated', const=1) self.assertEqual(sys.stderr.getvalue(), '') @mock.patch.object(sys, 'stderr', io.StringIO()) @mock.patch.object(novaclient.shell.DeprecatedAction, '_get_action') def test_call_unemitted_nouse(self, mock_get_action): obj = novaclient.shell.DeprecatedAction( 'option_strings', 'dest') obj('parser', 'namespace', 'values', 'option_string') self.assertEqual(obj.emitted, set(['option_string'])) mock_get_action.assert_called_once_with('parser') mock_get_action.return_value.assert_called_once_with( 'parser', 'namespace', 'values', 'option_string') self.assertEqual(sys.stderr.getvalue(), 'WARNING: Option "option_string" is deprecated\n') @mock.patch.object(sys, 'stderr', io.StringIO()) @mock.patch.object(novaclient.shell.DeprecatedAction, '_get_action') def test_call_unemitted_withuse(self, mock_get_action): obj = novaclient.shell.DeprecatedAction( 'option_strings', 'dest', use='use this instead') obj('parser', 'namespace', 'values', 'option_string') self.assertEqual(obj.emitted, set(['option_string'])) mock_get_action.assert_called_once_with('parser') mock_get_action.return_value.assert_called_once_with( 'parser', 'namespace', 'values', 'option_string') self.assertEqual(sys.stderr.getvalue(), 'WARNING: Option "option_string" is deprecated; ' 'use this instead\n') @mock.patch.object(sys, 'stderr', io.StringIO()) @mock.patch.object(novaclient.shell.DeprecatedAction, '_get_action') def test_call_emitted_nouse(self, mock_get_action): obj = novaclient.shell.DeprecatedAction( 'option_strings', 'dest') obj.emitted.add('option_string') obj('parser', 'namespace', 'values', 'option_string') self.assertEqual(obj.emitted, set(['option_string'])) mock_get_action.assert_called_once_with('parser') mock_get_action.return_value.assert_called_once_with( 'parser', 'namespace', 'values', 'option_string') self.assertEqual(sys.stderr.getvalue(), '') @mock.patch.object(sys, 'stderr', io.StringIO()) @mock.patch.object(novaclient.shell.DeprecatedAction, '_get_action') def test_call_emitted_withuse(self, mock_get_action): obj = novaclient.shell.DeprecatedAction( 'option_strings', 'dest', use='use this instead') obj.emitted.add('option_string') obj('parser', 'namespace', 'values', 'option_string') self.assertEqual(obj.emitted, set(['option_string'])) mock_get_action.assert_called_once_with('parser') mock_get_action.return_value.assert_called_once_with( 'parser', 'namespace', 'values', 'option_string') self.assertEqual(sys.stderr.getvalue(), '') class ParserTest(utils.TestCase): def setUp(self): super(ParserTest, self).setUp() self.parser = novaclient.shell.NovaClientArgumentParser() def test_ambiguous_option(self): self.parser.add_argument('--tic') self.parser.add_argument('--tac') try: self.parser.parse_args(['--t']) except SystemExit as err: self.assertEqual(2, err.code) else: self.fail('SystemExit not raised') def test_not_really_ambiguous_option(self): # current/deprecated forms of the same option self.parser.add_argument('--tic-tac', action="store_true") self.parser.add_argument('--tic_tac', action="store_true") args = self.parser.parse_args(['--tic']) self.assertTrue(args.tic_tac) @ddt.ddt class ShellTest(utils.TestCase): _msg_no_tenant_project = ("You must provide a project name or project" " ID via --os-project-name, --os-project-id," " env[OS_PROJECT_ID] or env[OS_PROJECT_NAME]." " You may use os-project and os-tenant" " interchangeably.") 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 setUp(self): super(ShellTest, self).setUp() self.mock_client = mock.MagicMock() self.mock_client.return_value.api_version = novaclient.API_MIN_VERSION self.useFixture(fixtures.MonkeyPatch('novaclient.client.Client', self.mock_client)) self.nc_util = mock.patch('novaclient.utils.isunauthenticated').start() self.nc_util.return_value = False self.mock_server_version_range = mock.patch( 'novaclient.api_versions._get_server_version_range').start() self.mock_server_version_range.return_value = ( novaclient.API_MIN_VERSION, novaclient.API_MIN_VERSION) self.orig_max_ver = novaclient.API_MAX_VERSION self.orig_min_ver = novaclient.API_MIN_VERSION self.addCleanup(self._clear_fake_version) self.addCleanup(mock.patch.stopall) def _clear_fake_version(self): novaclient.API_MAX_VERSION = self.orig_max_ver novaclient.API_MIN_VERSION = self.orig_min_ver def shell(self, argstr, exitcodes=(0,)): orig = sys.stdout orig_stderr = sys.stderr try: sys.stdout = io.StringIO() sys.stderr = io.StringIO() _shell = novaclient.shell.OpenStackComputeShell() _shell.main(argstr.split()) except SystemExit: exc_type, exc_value, exc_traceback = sys.exc_info() self.assertIn(exc_value.code, exitcodes) finally: stdout = sys.stdout.getvalue() sys.stdout.close() sys.stdout = orig stderr = sys.stderr.getvalue() sys.stderr.close() sys.stderr = orig_stderr return (stdout, stderr) def register_keystone_discovery_fixture(self, mreq): v2_url = "http://no.where/v2.0" v2_version = fixture.V2Discovery(v2_url) mreq.register_uri( 'GET', v2_url, json=_create_ver_list([v2_version]), status_code=200) def test_help_unknown_command(self): self.assertRaises(exceptions.CommandError, self.shell, 'help foofoo') def test_invalid_timeout(self): for f in [0, -1, -10]: cmd_text = '--timeout %s' % (f) stdout, stderr = self.shell(cmd_text, exitcodes=[0, 2]) required = [ 'argument --timeout: %s must be greater than 0' % (f), ] for r in required: self.assertIn(r, stderr) def _test_help(self, command, required=None): if required is None: required = [ '.*?^usage: ', '.*?^\\s+set-password\\s+Change the admin password', '.*?^See "nova help COMMAND" for help on a specific command', ] stdout, stderr = self.shell(command) for r in required: self.assertThat((stdout + stderr), matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE)) def test_help(self): self._test_help('help') def test_help_option(self): self._test_help('--help') self._test_help('-h') def test_help_no_options(self): self._test_help('') def test_help_no_subcommand(self): self._test_help('--os-compute-api-version 2.87') def test_help_on_subcommand(self): required = [ '.*?^usage: nova set-password', '.*?^Change the admin password', '.*?^Positional arguments:', ] self._test_help('help set-password', required=required) def test_bash_completion(self): stdout, stderr = self.shell('bash-completion') # just check we have some output required = [ '.*--matching', '.*--wrap', '.*help', '.*server-group-delete', '.*--image-with'] for r in required: self.assertThat((stdout + stderr), matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE)) def test_no_username(self): required = ('You must provide a user name/id (via --os-username, ' '--os-user-id, env[OS_USERNAME] or env[OS_USER_ID]) or ' 'an auth token (via --os-token).') self.make_env(exclude='OS_USERNAME') try: self.shell('list') except exceptions.CommandError as message: self.assertEqual(required, message.args[0]) else: self.fail('CommandError not raised') def test_no_user_id(self): required = ('You must provide a user name/id (via --os-username, ' '--os-user-id, env[OS_USERNAME] or env[OS_USER_ID]) or ' 'an auth token (via --os-token).') self.make_env(exclude='OS_USER_ID', fake_env=FAKE_ENV2) try: self.shell('list') except exceptions.CommandError as message: self.assertEqual(required, message.args[0]) else: self.fail('CommandError not raised') def test_no_tenant_name(self): required = self._msg_no_tenant_project self.make_env(exclude='OS_TENANT_NAME') try: self.shell('list') except exceptions.CommandError as message: self.assertEqual(required, message.args[0]) else: self.fail('CommandError not raised') def test_no_tenant_id(self): required = self._msg_no_tenant_project self.make_env(exclude='OS_TENANT_ID', fake_env=FAKE_ENV2) try: self.shell('list') except exceptions.CommandError as message: self.assertEqual(required, message.args[0]) else: self.fail('CommandError not raised') def test_no_auth_url(self): required = ('You must provide an auth url' ' via either --os-auth-url or env[OS_AUTH_URL].',) self.make_env(exclude='OS_AUTH_URL') try: self.shell('list') except exceptions.CommandError as message: self.assertEqual(required, message.args) else: self.fail('CommandError not raised') @ddt.data( (None, 'project_domain_id', FAKE_ENV['OS_PROJECT_DOMAIN_ID']), ('OS_PROJECT_DOMAIN_ID', 'project_domain_id', ''), (None, 'project_domain_name', FAKE_ENV['OS_PROJECT_DOMAIN_NAME']), ('OS_PROJECT_DOMAIN_NAME', 'project_domain_name', ''), (None, 'user_domain_id', FAKE_ENV['OS_USER_DOMAIN_ID']), ('OS_USER_DOMAIN_ID', 'user_domain_id', ''), (None, 'user_domain_name', FAKE_ENV['OS_USER_DOMAIN_NAME']), ('OS_USER_DOMAIN_NAME', 'user_domain_name', '') ) @ddt.unpack def test_basic_attributes(self, exclude, client_arg, env_var): self.make_env(exclude=exclude, fake_env=FAKE_ENV) self.shell('list') client_kwargs = self.mock_client.call_args_list[0][1] self.assertEqual(env_var, client_kwargs[client_arg]) @requests_mock.Mocker() def test_nova_endpoint_type(self, m_requests): self.make_env(fake_env=FAKE_ENV3) self.register_keystone_discovery_fixture(m_requests) self.shell('list') client_kwargs = self.mock_client.call_args_list[0][1] self.assertEqual(client_kwargs['endpoint_type'], 'novaURL') @requests_mock.Mocker() def test_endpoint_type_like_other_clients(self, m_requests): self.make_env(fake_env=FAKE_ENV4) self.register_keystone_discovery_fixture(m_requests) self.shell('list') client_kwargs = self.mock_client.call_args_list[0][1] self.assertEqual(client_kwargs['endpoint_type'], 'internalURL') @requests_mock.Mocker() def test_os_endpoint_type(self, m_requests): self.make_env(exclude='NOVA_ENDPOINT_TYPE', fake_env=FAKE_ENV3) self.register_keystone_discovery_fixture(m_requests) self.shell('list') client_kwargs = self.mock_client.call_args_list[0][1] self.assertEqual(client_kwargs['endpoint_type'], 'osURL') def test_default_endpoint_type(self): self.make_env() self.shell('list') client_kwargs = self.mock_client.call_args_list[0][1] self.assertEqual(client_kwargs['endpoint_type'], 'publicURL') @mock.patch('sys.stdin', side_effect=mock.MagicMock) @mock.patch('getpass.getpass', return_value='password') @requests_mock.Mocker() def test_password(self, mock_getpass, mock_stdin, m_requests): mock_stdin.encoding = "utf-8" ex = '\n'.join([ '+----+------+--------+------------+-------------+----------+', '| ID | Name | Status | Task State | Power State | Networks |', '+----+------+--------+------------+-------------+----------+', '+----+------+--------+------------+-------------+----------+', '' ]) self.make_env(exclude='OS_PASSWORD') self.register_keystone_discovery_fixture(m_requests) stdout, stderr = self.shell('list') self.assertEqual((stdout + stderr), ex) def _test_service_type(self, version, service_type, mock_client): if version is None: cmd = 'list' else: cmd = ('--service-type %s --os-compute-api-version %s list' % (service_type, version)) self.make_env() self.shell(cmd) _client_args, client_kwargs = mock_client.call_args_list[0] self.assertEqual(service_type, client_kwargs['service_type']) def test_default_service_type(self): self._test_service_type(None, 'compute', self.mock_client) def test_v2_service_type(self): self._test_service_type('2', 'compute', self.mock_client) def test_v_unknown_service_type(self): self.assertRaises(exceptions.UnsupportedVersion, self._test_service_type, 'unknown', 'compute', self.mock_client) @mock.patch('sys.stdout', io.StringIO()) @mock.patch('sys.stderr', io.StringIO()) def test_main_noargs(self): # Ensure that main works with no command-line arguments try: novaclient.shell.main([]) except SystemExit: self.fail('Unexpected SystemExit') # We expect the normal usage as a result self.assertIn( 'Command-line interface to the OpenStack Nova API', sys.stdout.getvalue(), ) # We also expect to see the deprecation warning self.assertIn( 'nova CLI is deprecated and will be removed in a future release', sys.stderr.getvalue(), ) @mock.patch.object(novaclient.shell.OpenStackComputeShell, 'main') def test_main_keyboard_interrupt(self, mock_compute_shell): # Ensure that exit code is 130 for KeyboardInterrupt mock_compute_shell.side_effect = KeyboardInterrupt() try: novaclient.shell.main([]) except SystemExit as ex: self.assertEqual(ex.code, 130) @mock.patch.object(novaclient.shell.OpenStackComputeShell, 'times') @requests_mock.Mocker() def test_timing(self, m_times, m_requests): m_times.append.side_effect = RuntimeError('Boom!') self.make_env() self.register_keystone_discovery_fixture(m_requests) self.shell('list') exc = self.assertRaises(RuntimeError, self.shell, '--timings list') self.assertEqual('Boom!', str(exc)) @requests_mock.Mocker() def test_osprofiler(self, m_requests): self.make_env() def client(*args, **kwargs): self.assertEqual('swordfish', kwargs['profile']) with mock.patch('novaclient.client.Client', client): # we are only interested in the fact Client is initialized properly self.shell('list --profile swordfish', (0, 2)) @requests_mock.Mocker() def test_osprofiler_not_installed(self, m_requests): self.make_env() # NOTE(rpodolyaka): osprofiler is in test-requirements, so we have to # simulate its absence here with mock.patch('novaclient.shell.osprofiler_profiler', None): _, stderr = self.shell('list --profile swordfish', (0, 2)) self.assertIn('unrecognized arguments: --profile swordfish', stderr) def test_microversion_with_default_behaviour(self): self.make_env(fake_env=FAKE_ENV5) self.mock_server_version_range.return_value = ( api_versions.APIVersion("2.1"), api_versions.APIVersion("2.3")) self.shell('list') client_args = self.mock_client.call_args_list[1][0] self.assertEqual(api_versions.APIVersion("2.3"), client_args[0]) def test_microversion_with_default_behaviour_with_legacy_server(self): self.make_env(fake_env=FAKE_ENV5) self.mock_server_version_range.return_value = ( api_versions.APIVersion(), api_versions.APIVersion()) self.shell('list') client_args = self.mock_client.call_args_list[1][0] self.assertEqual(api_versions.APIVersion("2.0"), client_args[0]) def test_microversion_with_latest(self): self.make_env() novaclient.API_MAX_VERSION = api_versions.APIVersion('2.3') self.mock_server_version_range.return_value = ( api_versions.APIVersion("2.1"), api_versions.APIVersion("2.3")) self.shell('--os-compute-api-version 2.latest list') client_args = self.mock_client.call_args_list[1][0] self.assertEqual(api_versions.APIVersion("2.3"), client_args[0]) def test_microversion_with_specified_version(self): self.make_env() self.mock_server_version_range.return_value = ( api_versions.APIVersion("2.10"), api_versions.APIVersion("2.100")) novaclient.API_MAX_VERSION = api_versions.APIVersion("2.100") novaclient.API_MIN_VERSION = api_versions.APIVersion("2.90") self.shell('--os-compute-api-version 2.99 list') client_args = self.mock_client.call_args_list[1][0] self.assertEqual(api_versions.APIVersion("2.99"), client_args[0]) def test_microversion_with_specified_version_out_of_range(self): novaclient.API_MAX_VERSION = api_versions.APIVersion("2.100") novaclient.API_MIN_VERSION = api_versions.APIVersion("2.90") self.assertRaises(exceptions.CommandError, self.shell, '--os-compute-api-version 2.199 list') def test_microversion_with_v2_and_v2_1_server(self): self.make_env() self.mock_server_version_range.return_value = ( api_versions.APIVersion('2.1'), api_versions.APIVersion('2.3')) novaclient.API_MAX_VERSION = api_versions.APIVersion("2.100") novaclient.API_MIN_VERSION = api_versions.APIVersion("2.1") self.shell('--os-compute-api-version 2 list') client_args = self.mock_client.call_args_list[1][0] self.assertEqual(api_versions.APIVersion("2.0"), client_args[0]) def test_microversion_with_v2_and_v2_server(self): self.make_env() self.mock_server_version_range.return_value = ( api_versions.APIVersion(), api_versions.APIVersion()) novaclient.API_MAX_VERSION = api_versions.APIVersion("2.100") novaclient.API_MIN_VERSION = api_versions.APIVersion("2.1") self.shell('--os-compute-api-version 2 list') client_args = self.mock_client.call_args_list[1][0] self.assertEqual(api_versions.APIVersion("2.0"), client_args[0]) def test_microversion_with_v2_without_server_compatible(self): self.make_env() self.mock_server_version_range.return_value = ( api_versions.APIVersion('2.2'), api_versions.APIVersion('2.3')) novaclient.API_MAX_VERSION = api_versions.APIVersion("2.100") novaclient.API_MIN_VERSION = api_versions.APIVersion("2.1") self.assertRaises( exceptions.UnsupportedVersion, self.shell, '--os-compute-api-version 2 list') def test_microversion_with_specific_version_without_microversions(self): self.make_env() self.mock_server_version_range.return_value = ( api_versions.APIVersion(), api_versions.APIVersion()) novaclient.API_MAX_VERSION = api_versions.APIVersion("2.100") novaclient.API_MIN_VERSION = api_versions.APIVersion("2.1") self.assertRaises( exceptions.UnsupportedVersion, self.shell, '--os-compute-api-version 2.3 list') @mock.patch.object(novaclient.shell.OpenStackComputeShell, 'main') def test_main_error_handling(self, mock_compute_shell): class MyException(Exception): pass with mock.patch('sys.stderr', io.StringIO()): mock_compute_shell.side_effect = MyException('message') self.assertRaises(SystemExit, novaclient.shell.main, []) err = sys.stderr.getvalue() # We expect to see the error propagated self.assertIn('ERROR (MyException): message\n', err) # We also expect to see the deprecation warning self.assertIn( 'nova CLI is deprecated and will be removed in a future release', err, ) class TestLoadVersionedActions(utils.TestCase): def test_load_versioned_actions(self): # first load with API version 2.15, ensuring we use the 2.15 version of # the underlying function (which returns 1) parser = novaclient.shell.NovaClientArgumentParser() subparsers = parser.add_subparsers(metavar='') shell = novaclient.shell.OpenStackComputeShell() shell.subcommands = {} shell._find_actions(subparsers, fake_actions_module, api_versions.APIVersion("2.15"), False) self.assertIn('fake-action', shell.subcommands.keys()) self.assertEqual( 1, shell.subcommands['fake-action'].get_default('func')()) # now load with API version 2.25, ensuring we now use the # correspponding version of the underlying function (which now returns # 2) parser = novaclient.shell.NovaClientArgumentParser() subparsers = parser.add_subparsers(metavar='') shell = novaclient.shell.OpenStackComputeShell() shell.subcommands = {} shell._find_actions(subparsers, fake_actions_module, api_versions.APIVersion("2.25"), False) self.assertIn('fake-action', shell.subcommands.keys()) self.assertEqual( 2, shell.subcommands['fake-action'].get_default('func')()) def test_load_versioned_actions_not_in_version_range(self): parser = novaclient.shell.NovaClientArgumentParser() subparsers = parser.add_subparsers(metavar='') shell = novaclient.shell.OpenStackComputeShell() shell.subcommands = {} shell._find_actions(subparsers, fake_actions_module, api_versions.APIVersion("2.10000"), False) self.assertNotIn('fake-action', shell.subcommands.keys()) self.assertIn('fake-action2', shell.subcommands.keys()) def test_load_versioned_actions_with_help(self): parser = novaclient.shell.NovaClientArgumentParser() subparsers = parser.add_subparsers(metavar='') shell = novaclient.shell.OpenStackComputeShell() shell.subcommands = {} shell._find_actions(subparsers, fake_actions_module, api_versions.APIVersion("2.15"), True) self.assertIn('fake-action', shell.subcommands.keys()) expected_desc = (" (Supported by API versions '%(start)s' - " "'%(end)s')") % {'start': '2.10', 'end': '2.30'} self.assertEqual(expected_desc, shell.subcommands['fake-action'].description) def test_load_versioned_actions_with_help_on_latest(self): parser = novaclient.shell.NovaClientArgumentParser() subparsers = parser.add_subparsers(metavar='') shell = novaclient.shell.OpenStackComputeShell() shell.subcommands = {} shell._find_actions(subparsers, fake_actions_module, api_versions.APIVersion("2.latest"), True) self.assertIn('another-fake-action', shell.subcommands.keys()) expected_desc = (" (Supported by API versions '%(start)s' - " "'%(end)s')%(hint)s") % { 'start': '2.0', 'end': '2.latest', 'hint': novaclient.shell.HINT_HELP_MSG} self.assertEqual(expected_desc, shell.subcommands['another-fake-action'].description) @mock.patch.object(novaclient.shell.NovaClientArgumentParser, 'add_argument') def test_load_versioned_actions_with_args(self, mock_add_arg): parser = novaclient.shell.NovaClientArgumentParser(add_help=False) subparsers = parser.add_subparsers(metavar='') shell = novaclient.shell.OpenStackComputeShell() shell.subcommands = {} shell._find_actions(subparsers, fake_actions_module, api_versions.APIVersion("2.1"), False) self.assertIn('fake-action2', shell.subcommands.keys()) mock_add_arg.assert_has_calls([ mock.call('-h', '--help', action='help', help='==SUPPRESS=='), mock.call('--foo')]) @mock.patch.object(novaclient.shell.NovaClientArgumentParser, 'add_argument') def test_load_versioned_actions_with_args2(self, mock_add_arg): parser = novaclient.shell.NovaClientArgumentParser(add_help=False) subparsers = parser.add_subparsers(metavar='') shell = novaclient.shell.OpenStackComputeShell() shell.subcommands = {} shell._find_actions(subparsers, fake_actions_module, api_versions.APIVersion("2.4"), False) self.assertIn('fake-action2', shell.subcommands.keys()) mock_add_arg.assert_has_calls([ mock.call('-h', '--help', action='help', help='==SUPPRESS=='), mock.call('--bar')]) @mock.patch.object(novaclient.shell.NovaClientArgumentParser, 'add_argument') def test_load_versioned_actions_with_args_not_in_version_range( self, mock_add_arg): parser = novaclient.shell.NovaClientArgumentParser(add_help=False) subparsers = parser.add_subparsers(metavar='') shell = novaclient.shell.OpenStackComputeShell() shell.subcommands = {} shell._find_actions(subparsers, fake_actions_module, api_versions.APIVersion("2.10000"), False) self.assertIn('fake-action2', shell.subcommands.keys()) mock_add_arg.assert_has_calls([ mock.call('-h', '--help', action='help', help='==SUPPRESS==')]) @mock.patch.object(novaclient.shell.NovaClientArgumentParser, 'add_argument') def test_load_versioned_actions_with_args_and_help(self, mock_add_arg): parser = novaclient.shell.NovaClientArgumentParser(add_help=False) subparsers = parser.add_subparsers(metavar='') shell = novaclient.shell.OpenStackComputeShell() shell.subcommands = {} shell._find_actions(subparsers, fake_actions_module, api_versions.APIVersion("2.4"), True) mock_add_arg.assert_has_calls([ mock.call('-h', '--help', action='help', help='==SUPPRESS=='), mock.call('--bar', help=" (Supported by API versions '2.3' - '2.4')")]) @mock.patch.object(novaclient.shell.NovaClientArgumentParser, 'add_argument') def test_load_actions_with_versioned_args(self, mock_add_arg): parser = novaclient.shell.NovaClientArgumentParser(add_help=False) subparsers = parser.add_subparsers(metavar='') shell = novaclient.shell.OpenStackComputeShell() shell.subcommands = {} shell._find_actions(subparsers, fake_actions_module, api_versions.APIVersion("2.20"), False) self.assertIn(mock.call('--foo', help="first foo"), mock_add_arg.call_args_list) self.assertNotIn(mock.call('--foo', help="second foo"), mock_add_arg.call_args_list) mock_add_arg.reset_mock() parser = novaclient.shell.NovaClientArgumentParser(add_help=False) subparsers = parser.add_subparsers(metavar='') shell = novaclient.shell.OpenStackComputeShell() shell.subcommands = {} shell._find_actions(subparsers, fake_actions_module, api_versions.APIVersion("2.21"), False) self.assertNotIn(mock.call('--foo', help="first foo"), mock_add_arg.call_args_list) self.assertIn(mock.call('--foo', help="second foo"), mock_add_arg.call_args_list) class ShellTestKeystoneV3(ShellTest): def make_env(self, exclude=None, fake_env=FAKE_ENV): if 'OS_AUTH_URL' in fake_env: fake_env.update({'OS_AUTH_URL': 'http://no.where/v3'}) env = dict((k, v) for k, v in fake_env.items() if k != exclude) self.useFixture(fixtures.MonkeyPatch('os.environ', env)) def register_keystone_discovery_fixture(self, mreq): v3_url = "http://no.where/v3" v3_version = fixture.V3Discovery(v3_url) mreq.register_uri( 'GET', v3_url, json=_create_ver_list([v3_version]), status_code=200) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/test_utils.py0000664000175000017500000004207100000000000024353 0ustar00zuulzuul00000000000000# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import io import sys from unittest import mock from urllib import parse from novaclient import base from novaclient import exceptions from novaclient.tests.unit import fakes from novaclient.tests.unit import utils as test_utils from novaclient import utils from novaclient.v2 import servers UUID = '8e8ec658-c7b0-4243-bdf8-6f7f2952c0d0' class FakeResource(object): NAME_ATTR = 'name' request_ids = fakes.FAKE_REQUEST_ID_LIST def __init__(self, _id, properties): self.id = _id try: self.name = properties['name'] except KeyError: pass def append_request_ids(self, resp): pass class FakeManager(base.ManagerWithFind): resource_class = FakeResource resources = [ FakeResource('1234', {'name': 'entity_one'}), FakeResource('12345', {'name': 'UPPER'}), FakeResource('123456', {'name': 'lower'}), FakeResource('1234567', {'name': 'Mixed'}), FakeResource('12345678', {'name': 'mixed'}), FakeResource(UUID, {'name': 'entity_two'}), FakeResource('5678', {'name': '9876'}), FakeResource('01234', {'name': 'entity_three'}) ] is_alphanum_id_allowed = None def __init__(self, alphanum_id_allowed=False): self.is_alphanum_id_allowed = alphanum_id_allowed def get(self, resource_id): for resource in self.resources: if resource.id == str(resource_id): return resource raise exceptions.NotFound(resource_id) def list(self): return base.ListWithMeta(self.resources, fakes.FAKE_REQUEST_ID_LIST) class FakeDisplayResource(object): NAME_ATTR = 'display_name' def __init__(self, _id, properties): self.id = _id try: self.display_name = properties['display_name'] except KeyError: pass def append_request_ids(self, resp): pass class FakeDisplayManager(FakeManager): resource_class = FakeDisplayResource resources = [ FakeDisplayResource('4242', {'display_name': 'entity_three'}), ] class FindResourceTestCase(test_utils.TestCase): def setUp(self): super(FindResourceTestCase, self).setUp() self.manager = FakeManager(None) def test_find_none(self): """Test a few non-valid inputs.""" self.assertRaises(exceptions.CommandError, utils.find_resource, self.manager, 'asdf') self.assertRaises(exceptions.CommandError, utils.find_resource, self.manager, None) self.assertRaises(exceptions.CommandError, utils.find_resource, self.manager, {}) def test_find_by_integer_id(self): output = utils.find_resource(self.manager, 1234) self.assertEqual(output, self.manager.get('1234')) def test_find_by_str_id(self): output = utils.find_resource(self.manager, '1234') self.assertEqual(output, self.manager.get('1234')) def test_find_by_uuid(self): output = utils.find_resource(self.manager, UUID) self.assertEqual(output, self.manager.get(UUID)) def test_find_by_str_name(self): output = utils.find_resource(self.manager, 'entity_one') self.assertEqual(output, self.manager.get('1234')) def test_find_by_str_upper_name(self): output = utils.find_resource(self.manager, 'UPPER') self.assertEqual(output, self.manager.get('12345')) def test_find_by_str_lower_name(self): output = utils.find_resource(self.manager, 'lower') self.assertEqual(output, self.manager.get('123456')) def test_find_by_str_mix_name(self): output = utils.find_resource(self.manager, 'Mixed') self.assertEqual(output, self.manager.get('1234567')) def test_find_by_str_lower_name_mixed(self): output = utils.find_resource(self.manager, 'mixed') self.assertEqual(output, self.manager.get('12345678')) def test_find_by_str_display_name(self): display_manager = FakeDisplayManager(None) output = utils.find_resource(display_manager, 'entity_three') self.assertEqual(output, display_manager.get('4242')) def test_find_in_alphanum_allowed_manager_by_str_id_(self): alphanum_manager = FakeManager(True) output = utils.find_resource(alphanum_manager, '01234') self.assertEqual(output, alphanum_manager.get('01234')) def test_find_without_wrapping_exception(self): alphanum_manager = FakeManager(True) self.assertRaises(exceptions.NotFound, utils.find_resource, alphanum_manager, 'not_exist', wrap_exception=False) res = alphanum_manager.resources[0] alphanum_manager.resources.append(res) self.assertRaises(exceptions.NoUniqueMatch, utils.find_resource, alphanum_manager, res.name, wrap_exception=False) class _FakeResult(object): def __init__(self, name, value): self.name = name self.value = value class PrintResultTestCase(test_utils.TestCase): @mock.patch('sys.stdout', io.StringIO()) def test_print_dict(self): dict = {'key': 'value'} utils.print_dict(dict) self.assertEqual('+----------+-------+\n' '| Property | Value |\n' '+----------+-------+\n' '| key | value |\n' '+----------+-------+\n', sys.stdout.getvalue()) @mock.patch('sys.stdout', io.StringIO()) def test_print_dict_wrap(self): dict = {'key1': 'not wrapped', 'key2': 'this will be wrapped'} utils.print_dict(dict, wrap=16) self.assertEqual('+----------+--------------+\n' '| Property | Value |\n' '+----------+--------------+\n' '| key1 | not wrapped |\n' '| key2 | this will be |\n' '| | wrapped |\n' '+----------+--------------+\n', sys.stdout.getvalue()) @mock.patch('sys.stdout', io.StringIO()) def test_print_list_sort_by_str(self): objs = [_FakeResult("k1", 1), _FakeResult("k3", 2), _FakeResult("k2", 3)] utils.print_list(objs, ["Name", "Value"], sortby_index=0) self.assertEqual('+------+-------+\n' '| Name | Value |\n' '+------+-------+\n' '| k1 | 1 |\n' '| k2 | 3 |\n' '| k3 | 2 |\n' '+------+-------+\n', sys.stdout.getvalue()) @mock.patch('sys.stdout', io.StringIO()) def test_print_list_sort_by_integer(self): objs = [_FakeResult("k1", 1), _FakeResult("k3", 2), _FakeResult("k2", 3)] utils.print_list(objs, ["Name", "Value"], sortby_index=1) self.assertEqual('+------+-------+\n' '| Name | Value |\n' '+------+-------+\n' '| k1 | 1 |\n' '| k3 | 2 |\n' '| k2 | 3 |\n' '+------+-------+\n', sys.stdout.getvalue()) @mock.patch('sys.stdout', io.StringIO()) def test_print_unicode_list(self): objs = [_FakeResult("k", '\u2026')] utils.print_list(objs, ["Name", "Value"]) s = '\u2026' self.assertEqual('+------+-------+\n' '| Name | Value |\n' '+------+-------+\n' '| k | %s |\n' '+------+-------+\n' % s, sys.stdout.getvalue()) # without sorting @mock.patch('sys.stdout', io.StringIO()) def test_print_list_sort_by_none(self): objs = [_FakeResult("k1", 1), _FakeResult("k3", 3), _FakeResult("k2", 2)] utils.print_list(objs, ["Name", "Value"], sortby_index=None) self.assertEqual('+------+-------+\n' '| Name | Value |\n' '+------+-------+\n' '| k1 | 1 |\n' '| k3 | 3 |\n' '| k2 | 2 |\n' '+------+-------+\n', sys.stdout.getvalue()) @mock.patch('sys.stdout', io.StringIO()) def test_print_dict_dictionary(self): dict = {'k': {'foo': 'bar'}} utils.print_dict(dict) self.assertEqual('+----------+----------------+\n' '| Property | Value |\n' '+----------+----------------+\n' '| k | {"foo": "bar"} |\n' '+----------+----------------+\n', sys.stdout.getvalue()) @mock.patch('sys.stdout', io.StringIO()) def test_print_dict_list_dictionary(self): dict = {'k': [{'foo': 'bar'}]} utils.print_dict(dict) self.assertEqual('+----------+------------------+\n' '| Property | Value |\n' '+----------+------------------+\n' '| k | [{"foo": "bar"}] |\n' '+----------+------------------+\n', sys.stdout.getvalue()) @mock.patch('sys.stdout', io.StringIO()) def test_print_dict_list(self): dict = {'k': ['foo', 'bar']} utils.print_dict(dict) self.assertEqual('+----------+----------------+\n' '| Property | Value |\n' '+----------+----------------+\n' '| k | ["foo", "bar"] |\n' '+----------+----------------+\n', sys.stdout.getvalue()) @mock.patch('sys.stdout', io.StringIO()) def test_print_large_dict_list(self): dict = {'k': ['foo1', 'bar1', 'foo2', 'bar2', 'foo3', 'bar3', 'foo4', 'bar4']} utils.print_dict(dict, wrap=40) self.assertEqual( '+----------+------------------------------------------+\n' '| Property | Value |\n' '+----------+------------------------------------------+\n' '| k | ["foo1", "bar1", "foo2", "bar2", "foo3", |\n' '| | "bar3", "foo4", "bar4"] |\n' '+----------+------------------------------------------+\n', sys.stdout.getvalue()) @mock.patch('sys.stdout', io.StringIO()) def test_print_unicode_dict(self): dict = {'k': '\u2026'} utils.print_dict(dict) s = '\u2026' self.assertEqual('+----------+-------+\n' '| Property | Value |\n' '+----------+-------+\n' '| k | %s |\n' '+----------+-------+\n' % s, sys.stdout.getvalue()) class FlattenTestCase(test_utils.TestCase): def test_flattening(self): squashed = utils.flatten_dict( {'a1': {'b1': 1234, 'b2': 'string', 'b3': set((1, 2, 3)), 'b4': {'c1': ['l', 'l', ['l']], 'c2': 'string'}}, 'a2': ['l'], 'a3': ('t',), 'a4': {}}) self.assertEqual({'a1_b1': 1234, 'a1_b2': 'string', 'a1_b3': set([1, 2, 3]), 'a1_b4_c1': ['l', 'l', ['l']], 'a1_b4_c2': 'string', 'a2': ['l'], 'a3': ('t',), 'a4': {}}, squashed) def test_pretty_choice_dict(self): d = {} r = utils.pretty_choice_dict(d) self.assertEqual("", r) d = {"k1": "v1", "k2": "v2", "k3": "v3"} r = utils.pretty_choice_dict(d) self.assertEqual("'k1=v1', 'k2=v2', 'k3=v3'", r) class ValidationsTestCase(test_utils.TestCase): def test_validate_flavor_metadata_keys_with_valid_keys(self): valid_keys = ['key1', 'month.price', 'I-Am:AK-ey.01-', 'spaces and _'] utils.validate_flavor_metadata_keys(valid_keys) def test_validate_flavor_metadata_keys_with_invalid_keys(self): invalid_keys = ['/1', '?1', '%1', '<', '>', '\1'] for key in invalid_keys: try: utils.validate_flavor_metadata_keys([key]) self.fail("Invalid key passed validation: %s" % key) except exceptions.CommandError as ce: self.assertIn(key, str(ce)) class DoActionOnManyTestCase(test_utils.TestCase): def _test_do_action_on_many(self, side_effect, fail): action = mock.Mock(side_effect=side_effect) if fail: self.assertRaises(exceptions.CommandError, utils.do_action_on_many, action, [1, 2], 'success with %s', 'error') else: utils.do_action_on_many(action, [1, 2], 'success with %s', 'error') action.assert_has_calls([mock.call(1), mock.call(2)]) def test_do_action_on_many_success(self): self._test_do_action_on_many([None, None], fail=False) def test_do_action_on_many_first_fails(self): self._test_do_action_on_many([Exception(), None], fail=True) def test_do_action_on_many_last_fails(self): self._test_do_action_on_many([None, Exception()], fail=True) @mock.patch('sys.stdout', new_callable=io.StringIO) def _test_do_action_on_many_resource_string( self, resource, expected_string, mock_stdout): utils.do_action_on_many(mock.Mock(), [resource], 'success with %s', 'error') self.assertIn('success with %s' % expected_string, mock_stdout.getvalue()) def test_do_action_on_many_resource_string_with_str(self): self._test_do_action_on_many_resource_string('resource1', 'resource1') def test_do_action_on_many_resource_string_with_human_id(self): resource = servers.Server(None, {'name': 'resource1'}) self._test_do_action_on_many_resource_string(resource, 'resource1') def test_do_action_on_many_resource_string_with_id(self): resource = servers.Server(None, {'id': UUID}) self._test_do_action_on_many_resource_string(resource, UUID) def test_do_action_on_many_resource_string_with_id_and_human_id(self): resource = servers.Server(None, {'name': 'resource1', 'id': UUID}) self._test_do_action_on_many_resource_string(resource, 'resource1 (%s)' % UUID) class RecordTimeTestCase(test_utils.TestCase): def test_record_time(self): times = [] with utils.record_time(times, True, 'a', 'b'): pass self.assertEqual(1, len(times)) self.assertEqual(3, len(times[0])) self.assertEqual('a b', times[0][0]) self.assertIsInstance(times[0][1], float) self.assertIsInstance(times[0][2], float) times = [] with utils.record_time(times, False, 'x'): pass self.assertEqual(0, len(times)) class PrepareQueryStringTestCase(test_utils.TestCase): def setUp(self): super(PrepareQueryStringTestCase, self).setUp() self.ustr = b'?\xd0\xbf=1&\xd1\x80=2' # in py3 real unicode symbols will be urlencoded self.ustr = self.ustr.decode('utf8') self.cases = ( ({}, ''), (None, ''), ({'2': 2, '10': 1}, '?10=1&2=2'), ({'abc': 1, 'abc1': 2}, '?abc=1&abc1=2'), ({b'\xd0\xbf': 1, b'\xd1\x80': 2}, self.ustr), ({(1, 2): '1', (3, 4): '2'}, '?(1, 2)=1&(3, 4)=2') ) def test_convert_dict_to_string(self): for case in self.cases: self.assertEqual( case[1], parse.unquote_plus(utils.prepare_query_string(case[0]))) def test_get_url_with_filter(self): url = '/fake' for case in self.cases: self.assertEqual( '%s%s' % (url, case[1]), parse.unquote_plus(utils.get_url_with_filter(url, case[0]))) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/utils.py0000664000175000017500000001076000000000000023314 0ustar00zuulzuul00000000000000# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import os from unittest import mock import fixtures from oslo_serialization import jsonutils import requests from requests_mock.contrib import fixture as requests_mock_fixture import testscenarios import testtools AUTH_URL = "http://localhost:5002/auth_url" AUTH_URL_V1 = "http://localhost:5002/auth_url/v1.0" AUTH_URL_V2 = "http://localhost:5002/auth_url/v2.0" def _patch_mock_to_raise_for_invalid_assert_calls(): def raise_for_invalid_assert_calls(wrapped): def wrapper(_self, name): valid_asserts = [ 'assert_called_with', 'assert_called_once_with', 'assert_has_calls', 'assert_any_calls'] if name.startswith('assert') and name not in valid_asserts: raise AttributeError('%s is not a valid mock assert method' % name) return wrapped(_self, name) return wrapper mock.Mock.__getattr__ = raise_for_invalid_assert_calls( mock.Mock.__getattr__) # NOTE(gibi): needs to be called only once at import time # to patch the mock lib _patch_mock_to_raise_for_invalid_assert_calls() class TestCase(testtools.TestCase): TEST_REQUEST_BASE = { 'verify': True, } def setUp(self): super(TestCase, self).setUp() if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or os.environ.get('OS_STDOUT_CAPTURE') == '1'): stdout = self.useFixture(fixtures.StringStream('stdout')).stream self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout)) if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or os.environ.get('OS_STDERR_CAPTURE') == '1'): stderr = self.useFixture(fixtures.StringStream('stderr')).stream self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr)) self.requests_mock = self.useFixture(requests_mock_fixture.Fixture()) def assert_request_id(self, request_id_mixin, request_id_list): self.assertEqual(request_id_list, request_id_mixin.request_ids) class FixturedTestCase(testscenarios.TestWithScenarios, TestCase): client_fixture_class = None data_fixture_class = None def setUp(self): super(FixturedTestCase, self).setUp() self.data_fixture = None self.client_fixture = None self.cs = None if self.client_fixture_class: fix = self.client_fixture_class(self.requests_mock) self.client_fixture = self.useFixture(fix) self.cs = self.client_fixture.client if self.data_fixture_class: fix = self.data_fixture_class(self.requests_mock) self.data_fixture = self.useFixture(fix) def assert_called(self, method, path, body=None): self.assertEqual(self.requests_mock.last_request.method, method) self.assertEqual(self.requests_mock.last_request.path_url, path) if body: req_data = self.requests_mock.last_request.body if isinstance(req_data, bytes): req_data = req_data.decode('utf-8') if not isinstance(body, str): # json load if the input body to match against is not a string req_data = jsonutils.loads(req_data) self.assertEqual(body, req_data) class TestResponse(requests.Response): """Class used to wrap requests.Response. Provide some convenience to initialize with a dict. """ def __init__(self, data): super(TestResponse, self).__init__() self._text = None if isinstance(data, dict): self.status_code = data.get('status_code') self.headers = data.get('headers') # Fake the text attribute to streamline Response creation self._text = data.get('text') else: self.status_code = data def __eq__(self, other): return self.__dict__ == other.__dict__ @property def text(self): return self._text ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1725030944.8164659 python-novaclient-18.7.0/novaclient/tests/unit/v2/0000775000175000017500000000000000000000000022125 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/v2/__init__.py0000664000175000017500000000000000000000000024224 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/v2/fakes.py0000664000175000017500000026377100000000000023610 0ustar00zuulzuul00000000000000# Copyright (c) 2011 X.commerce, a business unit of eBay Inc. # Copyright 2011 OpenStack Foundation # Copyright 2013 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import copy import datetime import re from unittest import mock from urllib import parse from oslo_utils import strutils import novaclient from novaclient import api_versions from novaclient import client as base_client from novaclient import exceptions from novaclient.tests.unit import fakes from novaclient.tests.unit import utils from novaclient.v2 import client # regex to compare callback to result of get_endpoint() # checks version number (vX or vX.X where X is a number) # and also checks if the id is on the end ENDPOINT_RE = re.compile( r"^get_http:__nova_api:8774_v\d(_\d)?_\w{32}$") # accepts formats like v2 or v2.1 ENDPOINT_TYPE_RE = re.compile(r"^v\d(\.\d)?$") # accepts formats like v2 or v2_1 CALLBACK_RE = re.compile(r"^get_http:__nova_api:8774_v\d(_\d)?$") # fake image uuids FAKE_IMAGE_UUID_1 = 'c99d7632-bd66-4be9-aed5-3dd14b223a76' FAKE_IMAGE_UUID_2 = 'f27f479a-ddda-419a-9bbc-d6b56b210161' FAKE_IMAGE_UUID_SNAPSHOT = '555cae93-fb41-4145-9c52-f5b923538a26' FAKE_IMAGE_UUID_SNAP_DEL = '55bb23af-97a4-4068-bdf8-f10c62880ddf' FAKE_IMAGE_UUID_BACKUP = '2f87e889-41a4-4778-8553-83f5eea68c5d' # fake request id FAKE_REQUEST_ID = fakes.FAKE_REQUEST_ID FAKE_REQUEST_ID_LIST = fakes.FAKE_REQUEST_ID_LIST FAKE_RESPONSE_HEADERS = {'x-openstack-request-id': FAKE_REQUEST_ID} FAKE_SERVICE_UUID_1 = '75e9eabc-ed3b-4f11-8bba-add1e7e7e2de' FAKE_SERVICE_UUID_2 = '1f140183-c914-4ddf-8757-6df73028aa86' SERVER_TOPOLOGY = { "nodes": [ { "cpu_pinning": { "0": 0, "1": 5 }, "host_node": 0, "memory_mb": 1024, "siblings": [ [ 0, 1 ] ], "vcpu_set": [ 0, 1 ] }, { "cpu_pinning": { "2": 1, "3": 8 }, "host_node": 1, "memory_mb": 2048, "siblings": [ [ 2, 3 ] ], "vcpu_set": [ 2, 3 ] } ], "pagesize_kb": 4 } class FakeClient(fakes.FakeClient, client.Client): def __init__(self, api_version, *args, **kwargs): client.Client.__init__(self, username='username', password='password', project_id='project_id', auth_url='auth_url', extensions=kwargs.get('extensions'), direct_use=False, api_version=api_version) self.client = FakeSessionClient(api_version=api_version, **kwargs) class FakeSessionClient(base_client.SessionClient): def __init__(self, *args, **kwargs): self.callstack = [] self.visited = [] self.auth = mock.Mock() self.session = mock.Mock() self.service_type = 'service_type' self.service_name = None self.endpoint_override = None self.interface = None self.region_name = None self.version = None self.api_version = kwargs.get('api_version') self.auth.get_auth_ref.return_value.project_id = 'tenant_id' # determines which endpoint to return in get_endpoint() # NOTE(augustina): this is a hacky workaround, ultimately # we need to fix our whole mocking architecture (fixtures?) if 'endpoint_type' in kwargs: self.endpoint_type = kwargs['endpoint_type'] else: self.endpoint_type = 'endpoint_type' self.logger = mock.MagicMock() def get_endpoint(self, **kwargs): # check if endpoint matches expected format (eg, v2.1) if (hasattr(self, 'endpoint_type') and ENDPOINT_TYPE_RE.search(self.endpoint_type)): return "http://nova-api:8774/%s/" % self.endpoint_type else: return ( "http://nova-api:8774/v2.1/190a755eef2e4aac9f06aa6be9786385") def request(self, url, method, **kwargs): return self._cs_request(url, method, **kwargs) def _cs_request(self, url, method, **kwargs): # Check that certain things are called correctly if method in ['GET', 'DELETE']: assert 'body' not in kwargs elif method == 'PUT': assert 'body' in kwargs if url is not None: # Call the method args = parse.parse_qsl(parse.urlparse(url)[4]) kwargs.update(args) munged_url = url.rsplit('?', 1)[0] munged_url = munged_url.strip('/').replace('/', '_') munged_url = munged_url.replace('.', '_') munged_url = munged_url.replace('-', '_') munged_url = munged_url.replace(' ', '_') munged_url = munged_url.replace('!', '_') munged_url = munged_url.replace('@', '_') munged_url = munged_url.replace('%20', '_') munged_url = munged_url.replace('%3A', '_') munged_url = munged_url.replace('%', '_') callback = "%s_%s" % (method.lower(), munged_url) if url is None or callback == "get_http:__nova_api:8774": # To get API version information, it is necessary to GET # a nova endpoint directly without "v2/". callback = "get_versions" elif CALLBACK_RE.search(callback): callback = "get_current_version" elif ENDPOINT_RE.search(callback): # compare callback to result of get_endpoint() # NOTE(sdague): if we try to call a thing that doesn't # exist, just return a 404. This allows the stack to act # more like we'd expect when making REST calls. raise exceptions.NotFound('404') # Handle fake glance v2 requests v2_image = False if callback.startswith('get_v2_images'): v2_image = True callback = callback.replace('get_v2_', 'get_') simulate_pagination_next_links = [ 'get_os_simple_tenant_usage', 'get_os_simple_tenant_usage_tenant_id', ] if callback in simulate_pagination_next_links: while callback in self.visited: callback += '_next' if not hasattr(self, callback): raise AssertionError('Called unknown API method: %s %s, ' 'expected fakes method name: %s' % (method, url, callback)) # Note the call self.visited.append(callback) self.callstack.append((method, url, kwargs.get('body'))) status, headers, body = getattr(self, callback)(**kwargs) # If we're dealing with a glance v2 image response, the response # isn't wrapped like the compute images API proxy is, so handle that. if body and v2_image and 'image' in body: body = body['image'] r = utils.TestResponse({ "status_code": status, "text": body, "headers": headers, }) if status >= 400: raise exceptions.from_response(r, body, url, method) return r, body def get_versions(self): return (200, FAKE_RESPONSE_HEADERS, { "versions": [ {"status": "SUPPORTED", "updated": "2011-01-21T11:33:21Z", "links": [{"href": "http://nova-api:8774/v2/", "rel": "self"}], "min_version": "", "version": "", "id": "v2.0"}, {"status": "CURRENT", "updated": "2013-07-23T11:33:21Z", "links": [{"href": "http://nova-api:8774/v2.1/", "rel": "self"}], "min_version": novaclient.API_MIN_VERSION.get_string(), "version": novaclient.API_MAX_VERSION.get_string(), "id": "v2.1"} ]}) def get_current_version(self): versions = { # v2 doesn't contain version or min_version fields "v2": { "version": { "status": "SUPPORTED", "updated": "2011-01-21T11:33:21Z", "links": [{ "href": "http://nova-api:8774/v2/", "rel": "self" }], "id": "v2.0" } }, "v2.1": { "version": { "status": "CURRENT", "updated": "2013-07-23T11:33:21Z", "links": [{ "href": "http://nova-api:8774/v2.1/", "rel": "self" }], "min_version": novaclient.API_MIN_VERSION.get_string(), "version": novaclient.API_MAX_VERSION.get_string(), "id": "v2.1" } } } # if an endpoint_type that matches a version wasn't initialized, # default to v2.1 endpoint = 'v2.1' if hasattr(self, 'endpoint_type'): if ENDPOINT_TYPE_RE.search(self.endpoint_type): if self.endpoint_type in versions: endpoint = self.endpoint_type else: raise AssertionError( "Unknown endpoint_type:%s" % self.endpoint_type) return (200, FAKE_RESPONSE_HEADERS, versions[endpoint]) # # agents # def get_os_agents(self, **kw): hypervisor = kw.get('hypervisor', 'kvm') return (200, {}, { 'agents': [{'hypervisor': hypervisor, 'os': 'win', 'architecture': 'x86', 'version': '7.0', 'url': 'xxx://xxxx/xxx/xxx', 'md5hash': 'add6bb58e139be103324d04d82d8f545', 'id': 1}, {'hypervisor': hypervisor, 'os': 'linux', 'architecture': 'x86', 'version': '16.0', 'url': 'xxx://xxxx/xxx/xxx1', 'md5hash': 'add6bb58e139be103324d04d82d8f546', 'id': 2}]}) def post_os_agents(self, body): return (200, {}, {'agent': { 'url': '/xxx/xxx/xxx', 'hypervisor': body['agent']['hypervisor'], 'md5hash': 'add6bb58e139be103324d04d82d8f546', 'version': '7.0', 'architecture': 'x86', 'os': 'win', 'id': 1}}) def delete_os_agents_1(self, **kw): return (202, {}, None) def put_os_agents_1(self, body, **kw): return (200, {}, { "agent": {"url": "/yyy/yyyy/yyyy", "version": "8.0", "md5hash": "add6bb58e139be103324d04d82d8f546", 'id': 1}}) # # Limits # def get_limits(self, **kw): absolute = { "maxTotalRAMSize": 51200, "maxServerMeta": 5, "maxImageMeta": 5 } # 2.57 removes injected_file* entries from the response. if self.api_version < api_versions.APIVersion('2.57'): absolute.update({"maxPersonality": 5, "maxPersonalitySize": 10240}) return (200, {}, {"limits": { "rate": [ { "uri": "*", "regex": ".*", "limit": [ { "value": 10, "verb": "POST", "remaining": 2, "unit": "MINUTE", "next-available": "2011-12-15T22:42:45Z" }, { "value": 10, "verb": "PUT", "remaining": 2, "unit": "MINUTE", "next-available": "2011-12-15T22:42:45Z" }, { "value": 100, "verb": "DELETE", "remaining": 100, "unit": "MINUTE", "next-available": "2011-12-15T22:42:45Z" } ] }, { "uri": "*/servers", "regex": "^/servers", "limit": [ { "verb": "POST", "value": 25, "remaining": 24, "unit": "DAY", "next-available": "2011-12-15T22:42:45Z" } ] } ], "absolute": absolute, }}) # # Servers # def get_servers(self, **kw): servers = {"servers": [ {'id': '1234', 'name': 'sample-server'}, {'id': '5678', 'name': 'sample-server2'}, {'id': '9014', 'name': 'help'} ]} if self.api_version >= api_versions.APIVersion('2.69'): # include "partial results" from non-responsive part of # infrastructure. servers['servers'].append( {'id': '9015', 'status': "UNKNOWN", "links": [ { "href": "http://fake/v2.1/", "rel": "self" }, { "href": "http://fake", "rel": "bookmark" } ]} ) return (200, {}, servers) def get_servers_detail(self, **kw): servers = {"servers": [ { "id": '1234', "name": "sample-server", "image": { "id": FAKE_IMAGE_UUID_2, "name": "sample image", }, "flavor": { "id": 1, "name": "256 MiB Server", }, "hostId": "e4d909c290d0fb1ca068ffaddf22cbd0", "status": "BUILD", "progress": 60, "addresses": { "public": [ { "version": 4, "addr": "1.2.3.4", }, { "version": 4, "addr": "5.6.7.8", }], "private": [{ "version": 4, "addr": "10.11.12.13", }], }, "metadata": { "Server Label": "Web Head 1", "Image Version": "2.1" }, "OS-EXT-SRV-ATTR:host": "computenode1", "security_groups": [{ 'id': 1, 'name': 'securitygroup1', 'description': 'FAKE_SECURITY_GROUP', 'tenant_id': '4ffc664c198e435e9853f2538fbcd7a7' }], "OS-EXT-MOD:some_thing": "mod_some_thing_value", }, { "id": '5678', "name": "sample-server2", "image": { "id": FAKE_IMAGE_UUID_1, "name": "sample image", }, "flavor": { "id": 1, "name": "256 MiB Server", }, "hostId": "9e107d9d372bb6826bd81d3542a419d6", "status": "ACTIVE", "addresses": { "public": [ { "version": 4, "addr": "4.5.6.7", }, { "version": 4, "addr": "5.6.9.8", }], "private": [{ "version": 4, "addr": "10.13.12.13", }], }, "metadata": { "Server Label": "DB 1" }, "OS-EXT-SRV-ATTR:host": "computenode2", "security_groups": [ { 'id': 1, 'name': 'securitygroup1', 'description': 'FAKE_SECURITY_GROUP', 'tenant_id': '4ffc664c198e435e9853f2538fbcd7a7' }, { 'id': 2, 'name': 'securitygroup2', 'description': 'ANOTHER_FAKE_SECURITY_GROUP', 'tenant_id': '4ffc664c198e435e9853f2538fbcd7a7' }], }, { "id": '9012', "name": "sample-server3", "image": "", "flavor": { "id": 1, "name": "256 MiB Server", }, "hostId": "9e107d9d372bb6826bd81d3542a419d6", "status": "ACTIVE", "addresses": { "public": [ { "version": 4, "addr": "4.5.6.7", }, { "version": 4, "addr": "5.6.9.8", }], "private": [{ "version": 4, "addr": "10.13.12.13", }], }, "metadata": { "Server Label": "DB 1" } }, { "id": '9013', "name": "sample-server4", "flavor": { "id": '80645cf4-6ad3-410a-bbc8-6f3e1e291f51', }, "image": { "id": '3e861307-73a6-4d1f-8d68-f68b03223032', }, "hostId": "9e107d9d372bb6826bd81d3542a419d6", "status": "ACTIVE", }, { "id": '9014', "name": "help", "flavor": { "id": '80645cf4-6ad3-410a-bbc8-6f3e1e291f51', }, "image": { "id": '3e861307-73a6-4d1f-8d68-f68b03223032', }, "hostId": "9e107d9d372bb6826bd81d3542a419d6", "status": "ACTIVE", }, ]} if self.api_version >= api_versions.APIVersion('2.69'): # include "partial results" from non-responsive part of # infrastructure. servers['servers'].append( { "id": "9015", "status": "UNKNOWN", "tenant_id": "6f70656e737461636b20342065766572", "created": "2018-12-03T21:06:18Z", "links": [ { "href": "http://fake/v2.1/", "rel": "self" }, { "href": "http://fake", "rel": "bookmark" } ] } ) return (200, {}, servers) def post_servers(self, body, **kw): assert set(body.keys()) <= set(['server', 'os:scheduler_hints']) fakes.assert_has_keys( body['server'], required=['name', 'imageRef', 'flavorRef'], optional=['metadata', 'personality']) if 'personality' in body['server']: for pfile in body['server']['personality']: fakes.assert_has_keys(pfile, required=['path', 'contents']) if body['server']['name'] == 'some-bad-server': return (202, {}, self.get_servers_1235()[2]) else: return (202, {}, self.get_servers_1234()[2]) def get_servers_1234(self, **kw): server = self.get_servers_detail()[2]['servers'][0] if self.api_version >= api_versions.APIVersion('2.71'): server.update( {'server_groups': ['a67359fb-d397-4697-88f1-f55e3ee7c499']}) return (200, {}, {'server': server}) def get_servers_1235(self, **kw): r = {'server': self.get_servers_detail()[2]['servers'][0]} r['server']['id'] = '1235' r['server']['status'] = 'error' r['server']['fault'] = {'message': 'something went wrong!'} return (200, {}, r) def get_servers_5678(self, **kw): r = {'server': self.get_servers_detail()[2]['servers'][1]} return (200, {}, r) def get_servers_9012(self, **kw): r = {'server': self.get_servers_detail()[2]['servers'][2]} return (200, {}, r) def get_servers_9013(self, **kw): r = {'server': self.get_servers_detail()[2]['servers'][3]} return (200, {}, r) def get_servers_9014(self, **kw): r = {'server': self.get_servers_detail()[2]['servers'][4]} return (200, {}, r) def get_servers_9015(self, **kw): r = {'server': self.get_servers_detail()[2]['servers'][5]} r['server']["OS-EXT-AZ:availability_zone"] = 'geneva' r['server']["OS-EXT-STS:power_state"] = 0 flavor = { "disk": 1, "ephemeral": 0, "original_name": "m1.tiny", "ram": 512, "swap": 0, "vcpus": 1, "extra_specs": {} } image = { "id": "c99d7632-bd66-4be9-aed5-3dd14b223a76", } r['server']['image'] = image r['server']['flavor'] = flavor r['server']['user_id'] = "fake" return (200, {}, r) def delete_os_server_groups_12345(self, **kw): return (202, {}, None) def delete_os_server_groups_56789(self, **kw): return (202, {}, None) def delete_servers_1234(self, **kw): return (202, {}, None) def delete_servers_5678(self, **kw): return (202, {}, None) def delete_servers_1234_metadata_key1(self, **kw): return (204, {}, None) def delete_servers_1234_metadata_key2(self, **kw): return (204, {}, None) def post_servers_1234_metadata(self, **kw): return (204, {}, {'metadata': {'test_key': 'test_value'}}) def get_servers_1234_diagnostics(self, **kw): return (200, {}, {'data': 'Fake diagnostics'}) def post_servers_uuid1_metadata(self, **kw): return (204, {}, {'metadata': {'key1': 'val1'}}) def post_servers_uuid2_metadata(self, **kw): return (204, {}, {'metadata': {'key1': 'val1'}}) def post_servers_uuid3_metadata(self, **kw): return (204, {}, {'metadata': {'key1': 'val1'}}) def post_servers_uuid4_metadata(self, **kw): return (204, {}, {'metadata': {'key1': 'val1'}}) def post_servers_uuid5_metadata(self, **kw): return (204, {}, {'metadata': {'key1': 'val1'}}) def post_servers_uuid6_metadata(self, **kw): return (204, {}, {'metadata': {'key1': 'val1'}}) def delete_servers_uuid1_metadata_key1(self, **kw): return (200, {}, {'data': 'Fake diagnostics'}) def delete_servers_uuid2_metadata_key1(self, **kw): return (200, {}, {'data': 'Fake diagnostics'}) def delete_servers_uuid3_metadata_key1(self, **kw): return (200, {}, {'data': 'Fake diagnostics'}) def delete_servers_uuid4_metadata_key1(self, **kw): return (200, {}, {'data': 'Fake diagnostics'}) def delete_servers_uuid5_metadata_key1(self, **kw): return (200, {}, {'data': 'Fake diagnostics'}) def delete_servers_uuid6_metadata_key1(self, **kw): return (200, {}, {'data': 'Fake diagnostics'}) def get_servers_1234_os_security_groups(self, **kw): return (200, {}, { "security_groups": [{ 'id': 1, 'name': 'securitygroup1', 'description': 'FAKE_SECURITY_GROUP', 'tenant_id': '4ffc664c198e435e9853f2538fbcd7a7', 'rules': []}] }) def get_servers_1234_topology(self, **kw): return 200, {}, SERVER_TOPOLOGY # # Server password # # Testing with the following password and key # # Clear password: FooBar123 # # RSA Private Key: novaclient/tests/unit/idfake.pem # # Encrypted password # OIuEuQttO8Rk93BcKlwHQsziDAnkAm/V6V8VPToA8ZeUaUBWwS0gwo2K6Y61Z96r # qG447iRz0uTEEYq3RAYJk1mh3mMIRVl27t8MtIecR5ggVVbz1S9AwXJQypDKl0ho # QFvhCBcMWPohyGewDJOhDbtuN1IoFI9G55ZvFwCm5y7m7B2aVcoLeIsJZE4PLsIw # /y5a6Z3/AoJZYGG7IH5WN88UROU3B9JZGFB2qtPLQTOvDMZLUhoPRIJeHiVSlo1N # tI2/++UsXVg3ow6ItqCJGgdNuGG5JB+bslDHWPxROpesEIHdczk46HCpHQN8f1sk # Hi/fmZZNQQqj1Ijq0caOIw== def get_servers_1234_os_server_password(self, **kw): return (200, {}, { 'password': 'OIuEuQttO8Rk93BcKlwHQsziDAnkAm/V6V8VPToA8ZeUaUBWwS0gwo2K6Y61Z96r' 'qG447iRz0uTEEYq3RAYJk1mh3mMIRVl27t8MtIecR5ggVVbz1S9AwXJQypDKl0ho' 'QFvhCBcMWPohyGewDJOhDbtuN1IoFI9G55ZvFwCm5y7m7B2aVcoLeIsJZE4PLsIw' '/y5a6Z3/AoJZYGG7IH5WN88UROU3B9JZGFB2qtPLQTOvDMZLUhoPRIJeHiVSlo1N' 'tI2/++UsXVg3ow6ItqCJGgdNuGG5JB+bslDHWPxROpesEIHdczk46HCpHQN8f1sk' 'Hi/fmZZNQQqj1Ijq0caOIw=='}) def delete_servers_1234_os_server_password(self, **kw): return (202, {}, None) # # Server actions # none_actions = ['revertResize', 'os-stop', 'os-start', 'forceDelete', 'restore', 'pause', 'unpause', 'unlock', 'unrescue', 'resume', 'suspend', 'shelve', 'shelveOffload', 'resetNetwork'] type_actions = ['os-getVNCConsole', 'os-getSPICEConsole', 'os-getRDPConsole'] @classmethod def check_server_actions(cls, body): action = list(body)[0] if action == 'reboot': assert list(body[action]) == ['type'] assert body[action]['type'] in ['HARD', 'SOFT'] elif action == 'resize': assert 'flavorRef' in body[action] elif action in cls.none_actions: assert body[action] is None elif action == 'changePassword': assert list(body[action]) == ['adminPass'] elif action in cls.type_actions: assert list(body[action]) == ['type'] elif action == 'os-resetState': assert list(body[action]) == ['state'] elif action == 'resetNetwork': assert body[action] is None elif action in ['addSecurityGroup', 'removeSecurityGroup']: assert list(body[action]) == ['name'] elif action == 'trigger_crash_dump': assert body[action] is None else: return False return True def post_servers_1234_action(self, body, **kw): _headers = dict() _body = None resp = 202 assert len(body.keys()) == 1 action = list(body)[0] if self.check_server_actions(body): # NOTE(snikitin): No need to do any operations here. This 'pass' # is needed to avoid AssertionError in the last 'else' statement # if we found 'action' in method check_server_actions and # raise AssertionError if we didn't find 'action' at all. pass elif action == 'os-migrateLive': expected = set(['host', 'block_migration']) if self.api_version >= api_versions.APIVersion("2.30"): if 'force' in body[action].keys(): # force can be optional expected.add('force') if self.api_version < api_versions.APIVersion("2.25"): expected.add('disk_over_commit') assert set(body[action].keys()) == expected elif action == 'migrate': if self.api_version < api_versions.APIVersion("2.56"): assert body[action] is None else: expected = set() if 'host' in body[action].keys(): # host can be optional expected.add('host') assert set(body[action].keys()) == expected elif action == 'lock': if self.api_version < api_versions.APIVersion("2.73"): assert body[action] is None else: # In 2.73 and above, we allow body to be one of these: # a) {'lock': None} # b) {'lock': {}} # c) {'lock': {locked_reason': 'blah'}} if body[action] is not None: expected = set() if 'locked_reason' in body[action].keys(): # reason can be optional expected.add('locked_reason') assert set(body[action].keys()) == expected else: assert body[action] is None elif action == 'unshelve': if self.api_version < api_versions.APIVersion("2.77"): assert body[action] is None else: # In 2.77 to 2.91, we allow body to be one of these: # {'unshelve': None} # {'unshelve': {'availability_zone': 'foo-az'}} if body[action] is not None: assert set(body[action].keys()) == set( ['availability_zone']) elif action == 'rebuild': body = body[action] adminPass = body.get('adminPass', 'randompassword') assert 'imageRef' in body _body = self.get_servers_1234()[2] _body['server']['adminPass'] = adminPass elif action == 'confirmResize': assert body[action] is None # This one method returns a different response code return (204, {}, None) elif action == 'rescue': if body[action]: keys = set(body[action].keys()) assert not (keys - set(['adminPass', 'rescue_image_ref'])) else: assert body[action] is None _body = {'adminPass': 'RescuePassword'} elif action == 'createImage': assert set(body[action].keys()) == set(['name', 'metadata']) if self.api_version < api_versions.APIVersion('2.45'): _headers = dict(location="http://blah/images/%s" % FAKE_IMAGE_UUID_SNAPSHOT) else: _body = {'image_id': FAKE_IMAGE_UUID_SNAPSHOT} if body[action]['name'] == 'mysnapshot_deleted': _headers = dict(location="http://blah/images/%s" % FAKE_IMAGE_UUID_SNAP_DEL) elif action == 'createBackup': assert set(body[action].keys()) == set(['name', 'backup_type', 'rotation']) if self.api_version < api_versions.APIVersion('2.45'): _headers = dict(location="http://blah/images/%s" % FAKE_IMAGE_UUID_BACKUP) else: _body = {'image_id': FAKE_IMAGE_UUID_BACKUP} elif action == 'os-getConsoleOutput': assert list(body[action]) == ['length'] return (202, {}, {'output': 'foo'}) elif action == 'evacuate': keys = list(body[action]) if 'adminPass' in keys: keys.remove('adminPass') if 'host' in keys: keys.remove('host') if 'onSharedStorage' in keys: keys.remove('onSharedStorage') if 'force' in keys: keys.remove('force') assert set(keys) == set() else: raise AssertionError("Unexpected server action: %s" % action) _headers.update(FAKE_RESPONSE_HEADERS) return (resp, _headers, _body) def post_servers_5678_action(self, body, **kw): return self.post_servers_1234_action(body, **kw) # # Flavors # def get_flavors(self, **kw): status, header, flavors = self.get_flavors_detail(**kw) included_fields = ['id', 'name'] if self.api_version >= api_versions.APIVersion('2.55'): included_fields.append('description') if self.api_version >= api_versions.APIVersion('2.61'): included_fields.append('extra_specs') for flavor in flavors['flavors']: for k in list(flavor): if k not in included_fields: del flavor[k] return (200, FAKE_RESPONSE_HEADERS, flavors) def get_flavors_detail(self, **kw): flavors = {'flavors': [ {'id': 1, 'name': '256 MiB Server', 'ram': 256, 'disk': 10, 'OS-FLV-EXT-DATA:ephemeral': 10, 'os-flavor-access:is_public': True, 'links': {}}, {'id': 2, 'name': '512 MiB Server', 'ram': 512, 'disk': 20, 'OS-FLV-EXT-DATA:ephemeral': 20, 'os-flavor-access:is_public': False, 'links': {}}, {'id': 4, 'name': '1024 MiB Server', 'ram': 1024, 'disk': 10, 'OS-FLV-EXT-DATA:ephemeral': 10, 'os-flavor-access:is_public': True, 'links': {}}, {'id': 'aa1', 'name': '128 MiB Server', 'ram': 128, 'disk': 0, 'OS-FLV-EXT-DATA:ephemeral': 0, 'os-flavor-access:is_public': True, 'links': {}} ]} if 'is_public' not in kw: filter_is_public = True else: if kw['is_public'].lower() == 'none': filter_is_public = None else: filter_is_public = strutils.bool_from_string(kw['is_public'], True) if filter_is_public is not None: if filter_is_public: flavors['flavors'] = [ v for v in flavors['flavors'] if v['os-flavor-access:is_public'] ] else: flavors['flavors'] = [ v for v in flavors['flavors'] if not v['os-flavor-access:is_public'] ] # Add description in the response for all flavors. if self.api_version >= api_versions.APIVersion('2.55'): for flavor in flavors['flavors']: flavor['description'] = None # Add a new flavor that is a copy of the first but with a different # name, flavorid and a description set. new_flavor = copy.deepcopy(flavors['flavors'][0]) new_flavor['id'] = 'with-description' new_flavor['name'] = 'with-description' new_flavor['description'] = 'test description' flavors['flavors'].append(new_flavor) # Add extra_specs in the response for all flavors. if self.api_version >= api_versions.APIVersion('2.61'): for flavor in flavors['flavors']: flavor['extra_specs'] = {'test': 'value'} return (200, FAKE_RESPONSE_HEADERS, flavors) def get_flavors_1(self, **kw): return ( 200, FAKE_RESPONSE_HEADERS, {'flavor': self.get_flavors_detail(is_public='None')[2]['flavors'][0]} ) def get_flavors_2(self, **kw): return ( 200, {}, {'flavor': self.get_flavors_detail(is_public='None')[2]['flavors'][1]} ) def get_flavors_3(self, **kw): # Diablo has no ephemeral return ( 200, FAKE_RESPONSE_HEADERS, {'flavor': { 'id': 3, 'name': '256 MiB Server', 'ram': 256, 'disk': 10, }}, ) def get_flavors_512_MiB_Server(self, **kw): raise exceptions.NotFound('404') def get_flavors_128_MiB_Server(self, **kw): raise exceptions.NotFound('404') def get_flavors_80645cf4_6ad3_410a_bbc8_6f3e1e291f51(self, **kw): raise exceptions.NotFound('404') def get_flavors_aa1(self, **kw): # Alphanumeric flavor id are allowed. return ( 200, FAKE_RESPONSE_HEADERS, {'flavor': self.get_flavors_detail(is_public='None')[2]['flavors'][3]} ) def get_flavors_4(self, **kw): return ( 200, {}, {'flavor': self.get_flavors_detail(is_public='None')[2]['flavors'][2]} ) def get_flavors_with_description(self, **kw): return ( 200, {}, {'flavor': self.get_flavors_detail(is_public='None')[2]['flavors'][-1]} ) def delete_flavors_flavordelete(self, **kw): return (202, FAKE_RESPONSE_HEADERS, None) def delete_flavors_2(self, **kw): return (202, FAKE_RESPONSE_HEADERS, None) def post_flavors(self, body, **kw): return ( 202, FAKE_RESPONSE_HEADERS, {'flavor': self.get_flavors_detail(is_public='None')[2]['flavors'][0]} ) def put_flavors_with_description(self, body, **kw): assert 'flavor' in body assert 'description' in body['flavor'] flavor = self.get_flavors_with_description(**kw)[2] # Fake out the actual update of the flavor description for the response flavor['description'] = body['flavor']['description'] return (200, {}, {'flavor': flavor}) def get_flavors_1_os_extra_specs(self, **kw): return ( 200, {}, {'extra_specs': {"k1": "v1"}}) def get_flavors_2_os_extra_specs(self, **kw): return ( 200, {}, {'extra_specs': {"k2": "v2"}}) def get_flavors_aa1_os_extra_specs(self, **kw): return ( 200, {}, {'extra_specs': {"k3": "v3"}}) def get_flavors_4_os_extra_specs(self, **kw): return ( 200, {}, {'extra_specs': {"k4": "v4"}}) def post_flavors_1_os_extra_specs(self, body, **kw): assert list(body) == ['extra_specs'] fakes.assert_has_keys(body['extra_specs'], required=['k1']) return ( 200, FAKE_RESPONSE_HEADERS, {'extra_specs': {"k1": "v1"}}) def post_flavors_4_os_extra_specs(self, body, **kw): assert list(body) == ['extra_specs'] return ( 200, FAKE_RESPONSE_HEADERS, body) def delete_flavors_1_os_extra_specs_k1(self, **kw): return (204, {}, None) # # Flavor access # def get_flavors_2_os_flavor_access(self, **kw): return ( 200, FAKE_RESPONSE_HEADERS, {'flavor_access': [{'flavor_id': '2', 'tenant_id': 'proj1'}, {'flavor_id': '2', 'tenant_id': 'proj2'}]}) def post_flavors_2_action(self, body, **kw): return (202, FAKE_RESPONSE_HEADERS, self.get_flavors_2_os_flavor_access()[2]) # # Images # def get_images(self, **kw): images = [ { "id": FAKE_IMAGE_UUID_SNAPSHOT, "name": "My Server Backup", "serverId": '1234', "updated": "2010-10-10T12:00:00Z", "created": "2010-08-10T12:00:00Z", "status": "SAVING", "progress": 80, "links": {}, }, { "id": FAKE_IMAGE_UUID_SNAP_DEL, "name": "My Server Backup Deleted", "serverId": '1234', "updated": "2010-10-10T12:00:00Z", "created": "2010-08-10T12:00:00Z", "status": "DELETED", "fault": {'message': 'Image has been deleted.'}, "links": {}, }, { 'id': FAKE_IMAGE_UUID_1, 'name': 'CentOS 5.2', "updated": "2010-10-10T12:00:00Z", "created": "2010-08-10T12:00:00Z", "status": "ACTIVE", "test_key": "test_value", "links": {}, }, { "id": FAKE_IMAGE_UUID_2, "name": "My Server Backup", "serverId": '1234', "updated": "2010-10-10T12:00:00Z", "created": "2010-08-10T12:00:00Z", "status": "SAVING", "progress": 80, "links": {}, }, { "id": FAKE_IMAGE_UUID_BACKUP, "name": "back1", "serverId": '1234', "updated": "2010-10-10T12:00:00Z", "created": "2010-08-10T12:00:00Z", "status": "SAVING", "progress": 80, "links": {}, }, ] if 'id' in kw: requested = kw['id'].replace('in:', '').split(',') images = [i for i in images if i['id'] in requested] if 'names' in kw: requested = kw['names'].replace('in:', '').split(',') images = [i for i in images if i['name'] in requested] return (200, {}, {'images': images}) def get_images_555cae93_fb41_4145_9c52_f5b923538a26(self, **kw): return (200, {}, {'image': self.get_images()[2]['images'][0]}) def get_images_55bb23af_97a4_4068_bdf8_f10c62880ddf(self, **kw): return (200, {}, {'image': self.get_images()[2]['images'][1]}) def get_images_c99d7632_bd66_4be9_aed5_3dd14b223a76(self, **kw): return (200, {}, {'image': self.get_images()[2]['images'][2]}) def get_images_f27f479a_ddda_419a_9bbc_d6b56b210161(self, **kw): return (200, {}, {'image': self.get_images()[2]['images'][3]}) def get_images_2f87e889_41a4_4778_8553_83f5eea68c5d(self, **kw): return (200, {}, {'image': self.get_images()[2]['images'][4]}) def get_images_3e861307_73a6_4d1f_8d68_f68b03223032(self): raise exceptions.NotFound('404') # # Keypairs # def get_os_keypairs_test(self, *kw): return (200, {}, {'keypair': self.get_os_keypairs()[2]['keypairs'][0]['keypair']}) def get_os_keypairs(self, user_id=None, limit=None, marker=None, *kw): return (200, {}, { "keypairs": [{"keypair": { "public_key": "FAKE_SSH_RSA", "private_key": "FAKE_PRIVATE_KEY", "user_id": "81e373b596d6466e99c4896826abaa46", "name": "test", "deleted": False, "created_at": "2014-04-19T02:16:44.000000", "updated_at": "2014-04-19T10:12:3.000000", "fingerprint": "FAKE_KEYPAIR", "deleted_at": None, "id": 4}} ]}) def delete_os_keypairs_test(self, **kw): return (202, {}, None) def post_os_keypairs(self, body, **kw): assert list(body) == ['keypair'] fakes.assert_has_keys(body['keypair'], required=['name']) r = {'keypair': self.get_os_keypairs()[2]['keypairs'][0]['keypair']} return (202, {}, r) # # Quotas # def get_os_quota_sets_tenant_id(self, **kw): return (200, {}, { 'quota_set': { 'tenant_id': 'test', 'metadata_items': [], 'injected_file_content_bytes': 1, 'injected_file_path_bytes': 1, 'ram': 1, 'floating_ips': 1, 'instances': 1, 'injected_files': 1, 'cores': 1, 'keypairs': 1, 'security_groups': 1, 'security_group_rules': 1}}) def get_os_quota_sets_97f4c221bff44578b0300df4ef119353(self, **kw): return (200, {}, { 'quota_set': { 'tenant_id': '97f4c221bff44578b0300df4ef119353', 'metadata_items': [], 'injected_file_content_bytes': 1, 'injected_file_path_bytes': 1, 'ram': 1, 'floating_ips': 1, 'instances': 1, 'injected_files': 1, 'cores': 1, 'keypairs': 1, 'security_groups': 1, 'security_group_rules': 1}}) def get_os_quota_sets_97f4c221bff44578b0300df4ef119353_detail(self, **kw): return (200, {}, { 'quota_set': { 'tenant_id': '97f4c221bff44578b0300df4ef119353', 'cores': { 'in_use': 0, 'limit': 20, 'reserved': 0 }, 'fixed_ips': { 'in_use': 0, 'limit': -1, 'reserved': 0 }, 'floating_ips': { 'in_use': 0, 'limit': 10, 'reserved': 0 }, 'injected_file_content_bytes': { 'in_use': 0, 'limit': 10240, 'reserved': 0 }, 'injected_file_path_bytes': { 'in_use': 0, 'limit': 255, 'reserved': 0 }, 'injected_files': { 'in_use': 0, 'limit': 5, 'reserved': 0 }, 'instances': { 'in_use': 0, 'limit': 10, 'reserved': 0 }, 'key_pairs': { 'in_use': 0, 'limit': 100, 'reserved': 0 }, 'metadata_items': { 'in_use': 0, 'limit': 128, 'reserved': 0 }, 'ram': { 'in_use': 0, 'limit': 51200, 'reserved': 0 }, 'security_group_rules': { 'in_use': 0, 'limit': 20, 'reserved': 0 }, 'security_groups': { 'in_use': 0, 'limit': 10, 'reserved': 0 }, 'server_group_members': { 'in_use': 0, 'limit': 10, 'reserved': 0 }, 'server_groups': { 'in_use': 0, 'limit': 10, 'reserved': 0 } }}) def get_os_quota_sets_97f4c221bff44578b0300df4ef119353_defaults(self): return (200, {}, { 'quota_set': { 'tenant_id': 'test', 'metadata_items': [], 'injected_file_content_bytes': 1, 'injected_file_path_bytes': 1, 'ram': 1, 'floating_ips': 1, 'instances': 1, 'injected_files': 1, 'cores': 1, 'keypairs': 1, 'security_groups': 1, 'security_group_rules': 1}}) def get_os_quota_sets_tenant_id_defaults(self): return (200, {}, { 'quota_set': { 'tenant_id': 'test', 'metadata_items': [], 'injected_file_content_bytes': 1, 'injected_file_path_bytes': 1, 'ram': 1, 'floating_ips': 1, 'instances': 1, 'injected_files': 1, 'cores': 1, 'keypairs': 1, 'security_groups': 1, 'security_group_rules': 1}}) def put_os_quota_sets_97f4c221bff44578b0300df4ef119353(self, body, **kw): assert list(body) == ['quota_set'] fakes.assert_has_keys(body['quota_set']) return (200, {}, { 'quota_set': { 'tenant_id': '97f4c221bff44578b0300df4ef119353', 'metadata_items': [], 'injected_file_content_bytes': 1, 'injected_file_path_bytes': 1, 'ram': 1, 'floating_ips': 1, 'instances': 1, 'injected_files': 1, 'cores': 1, 'keypairs': 1, 'security_groups': 1, 'security_group_rules': 1}}) def delete_os_quota_sets_97f4c221bff44578b0300df4ef119353(self, **kw): return (202, {}, {}) # # Quota Classes # def get_os_quota_class_sets_test(self, **kw): # 2.57 removes injected_file* entries from the response. if self.api_version >= api_versions.APIVersion('2.57'): return (200, FAKE_RESPONSE_HEADERS, { 'quota_class_set': { 'id': 'test', 'metadata_items': 1, 'ram': 1, 'instances': 1, 'cores': 1, 'key_pairs': 1, 'server_groups': 1, 'server_group_members': 1}}) if self.api_version >= api_versions.APIVersion('2.50'): return (200, FAKE_RESPONSE_HEADERS, { 'quota_class_set': { 'id': 'test', 'metadata_items': 1, 'injected_file_content_bytes': 1, 'injected_file_path_bytes': 1, 'ram': 1, 'instances': 1, 'injected_files': 1, 'cores': 1, 'key_pairs': 1, 'server_groups': 1, 'server_group_members': 1}}) return (200, FAKE_RESPONSE_HEADERS, { 'quota_class_set': { 'id': 'test', 'metadata_items': 1, 'injected_file_content_bytes': 1, 'injected_file_path_bytes': 1, 'ram': 1, 'floating_ips': 1, 'fixed_ips': -1, 'instances': 1, 'injected_files': 1, 'cores': 1, 'key_pairs': 1, 'security_groups': 1, 'security_group_rules': 1}}) def put_os_quota_class_sets_test(self, body, **kw): assert list(body) == ['quota_class_set'] # 2.57 removes injected_file* entries from the response. if self.api_version >= api_versions.APIVersion('2.57'): return (200, {}, { 'quota_class_set': { 'metadata_items': 1, 'ram': 1, 'instances': 1, 'cores': 1, 'key_pairs': 1, 'server_groups': 1, 'server_group_members': 1}}) if self.api_version >= api_versions.APIVersion('2.50'): return (200, {}, { 'quota_class_set': { 'metadata_items': 1, 'injected_file_content_bytes': 1, 'injected_file_path_bytes': 1, 'ram': 1, 'instances': 1, 'injected_files': 1, 'cores': 1, 'key_pairs': 1, 'server_groups': 1, 'server_group_members': 1}}) return (200, {}, { 'quota_class_set': { 'metadata_items': 1, 'injected_file_content_bytes': 1, 'injected_file_path_bytes': 1, 'ram': 1, 'floating_ips': 1, 'fixed_ips': -1, 'instances': 1, 'injected_files': 1, 'cores': 1, 'key_pairs': 1, 'security_groups': 1, 'security_group_rules': 1}}) def put_os_quota_class_sets_97f4c221bff44578b0300df4ef119353(self, body, **kw): assert list(body) == ['quota_class_set'] return (200, {}, { 'quota_class_set': { 'metadata_items': 1, 'injected_file_content_bytes': 1, 'injected_file_path_bytes': 1, 'ram': 1, 'floating_ips': 1, 'instances': 1, 'injected_files': 1, 'cores': 1, 'key_pairs': 1, 'security_groups': 1, 'security_group_rules': 1}}) # # Tenant Usage # def get_os_simple_tenant_usage(self, **kw): return (200, FAKE_RESPONSE_HEADERS, {'tenant_usages': [{ 'total_memory_mb_usage': 25451.762807466665, 'total_vcpus_usage': 49.71047423333333, 'total_hours': 49.71047423333333, 'tenant_id': '7b0a1d73f8fb41718f3343c207597869', 'stop': '2012-01-22 19:48:41.750722', 'server_usages': [{ 'hours': 49.71047423333333, 'uptime': 27035, 'local_gb': 0, 'ended_at': None, 'name': 'f15image1', 'tenant_id': '7b0a1d73f8fb41718f3343c207597869', 'instance_id': 'f079e394-1111-457b-b350-bb5ecc685cdd', 'vcpus': 1, 'memory_mb': 512, 'state': 'active', 'flavor': 'm1.tiny', 'started_at': '2012-01-20 18:06:06.479998', }], 'start': '2011-12-25 19:48:41.750687', 'total_local_gb_usage': 0.0}]}) def get_os_simple_tenant_usage_next(self, **kw): return (200, FAKE_RESPONSE_HEADERS, {'tenant_usages': [{ 'total_memory_mb_usage': 25451.762807466665, 'total_vcpus_usage': 49.71047423333333, 'total_hours': 49.71047423333333, 'tenant_id': '7b0a1d73f8fb41718f3343c207597869', 'stop': '2012-01-22 19:48:41.750722', 'server_usages': [{ 'hours': 49.71047423333333, 'uptime': 27035, 'local_gb': 0, 'ended_at': None, 'name': 'f15image1', 'tenant_id': '7b0a1d73f8fb41718f3343c207597869', 'instance_id': 'f079e394-2222-457b-b350-bb5ecc685cdd', 'vcpus': 1, 'memory_mb': 512, 'state': 'active', 'flavor': 'm1.tiny', 'started_at': '2012-01-20 18:06:06.479998', }], 'start': '2011-12-25 19:48:41.750687', 'total_local_gb_usage': 0.0}]}) def get_os_simple_tenant_usage_next_next(self, **kw): return (200, FAKE_RESPONSE_HEADERS, {'tenant_usages': []}) def get_os_simple_tenant_usage_tenantfoo(self, **kw): return (200, FAKE_RESPONSE_HEADERS, {'tenant_usage': { 'total_memory_mb_usage': 25451.762807466665, 'total_vcpus_usage': 49.71047423333333, 'total_hours': 49.71047423333333, 'tenant_id': '7b0a1d73f8fb41718f3343c207597869', 'stop': '2012-01-22 19:48:41.750722', 'server_usages': [{ 'hours': 49.71047423333333, 'uptime': 27035, 'local_gb': 0, 'ended_at': None, 'name': 'f15image1', 'tenant_id': '7b0a1d73f8fb41718f3343c207597869', 'instance_id': 'f079e394-1111-457b-b350-bb5ecc685cdd', 'vcpus': 1, 'memory_mb': 512, 'state': 'active', 'flavor': 'm1.tiny', 'started_at': '2012-01-20 18:06:06.479998', }], 'start': '2011-12-25 19:48:41.750687', 'total_local_gb_usage': 0.0}}) def get_os_simple_tenant_usage_test(self, **kw): return (200, {}, {'tenant_usage': { 'total_memory_mb_usage': 25451.762807466665, 'total_vcpus_usage': 49.71047423333333, 'total_hours': 49.71047423333333, 'tenant_id': '7b0a1d73f8fb41718f3343c207597869', 'stop': '2012-01-22 19:48:41.750722', 'server_usages': [{ 'hours': 49.71047423333333, 'uptime': 27035, 'local_gb': 0, 'ended_at': None, 'name': 'f15image1', 'tenant_id': '7b0a1d73f8fb41718f3343c207597869', 'instance_id': 'f079e394-1111-457b-b350-bb5ecc685cdd', 'vcpus': 1, 'memory_mb': 512, 'state': 'active', 'flavor': 'm1.tiny', 'started_at': '2012-01-20 18:06:06.479998', }], 'start': '2011-12-25 19:48:41.750687', 'total_local_gb_usage': 0.0}}) def get_os_simple_tenant_usage_tenant_id(self, **kw): return (200, {}, {'tenant_usage': { 'total_memory_mb_usage': 25451.762807466665, 'total_vcpus_usage': 49.71047423333333, 'total_hours': 49.71047423333333, 'tenant_id': '7b0a1d73f8fb41718f3343c207597869', 'stop': '2012-01-22 19:48:41.750722', 'server_usages': [{ 'hours': 49.71047423333333, 'uptime': 27035, 'local_gb': 0, 'ended_at': None, 'name': 'f15image1', 'tenant_id': '7b0a1d73f8fb41718f3343c207597869', 'instance_id': 'f079e394-1111-457b-b350-bb5ecc685cdd', 'vcpus': 1, 'memory_mb': 512, 'state': 'active', 'flavor': 'm1.tiny', 'started_at': '2012-01-20 18:06:06.479998', }], 'start': '2011-12-25 19:48:41.750687', 'total_local_gb_usage': 0.0}}) def get_os_simple_tenant_usage_tenant_id_next(self, **kw): return (200, {}, {'tenant_usage': { 'total_memory_mb_usage': 25451.762807466665, 'total_vcpus_usage': 49.71047423333333, 'total_hours': 49.71047423333333, 'tenant_id': '7b0a1d73f8fb41718f3343c207597869', 'stop': '2012-01-22 19:48:41.750722', 'server_usages': [{ 'hours': 49.71047423333333, 'uptime': 27035, 'local_gb': 0, 'ended_at': None, 'name': 'f15image1', 'tenant_id': '7b0a1d73f8fb41718f3343c207597869', 'instance_id': 'f079e394-2222-457b-b350-bb5ecc685cdd', 'vcpus': 1, 'memory_mb': 512, 'state': 'active', 'flavor': 'm1.tiny', 'started_at': '2012-01-20 18:06:06.479998', }], 'start': '2011-12-25 19:48:41.750687', 'total_local_gb_usage': 0.0}}) def get_os_simple_tenant_usage_tenant_id_next_next(self, **kw): return (200, {}, {'tenant_usage': {}}) # # Aggregates # def get_os_aggregates(self, *kw): response = (200, {}, {"aggregates": [ {'id': '1', 'name': 'test', 'availability_zone': 'nova1'}, {'id': '2', 'name': 'test2', 'availability_zone': 'nova1'}, {'id': '3', 'name': 'test3', 'metadata': {'test': "dup", "none_key": "Nine"}}, ]}) # microversion >= 2.41 returns the uuid in the response if self.api_version >= api_versions.APIVersion('2.41'): aggregates = response[2]['aggregates'] aggregates[0]['uuid'] = '80785864-087b-45a5-a433-b20eac9b58aa' aggregates[1]['uuid'] = '30827713-5957-4b68-8fd3-ccaddb568c24' aggregates[2]['uuid'] = '9a651b22-ce3f-4a87-acd7-98446ef591c4' return response def _return_aggregate(self): r = {'aggregate': self.get_os_aggregates()[2]['aggregates'][0]} return (200, {}, r) def _return_aggregate_3(self): r = {'aggregate': self.get_os_aggregates()[2]['aggregates'][2]} return (200, {}, r) def get_os_aggregates_1(self, **kw): return self._return_aggregate() def get_os_aggregates_3(self, **kw): return self._return_aggregate_3() def post_os_aggregates(self, body, **kw): return self._return_aggregate() def put_os_aggregates_1(self, body, **kw): return self._return_aggregate() def post_os_aggregates_1_action(self, body, **kw): return self._return_aggregate() def post_os_aggregates_3_action(self, body, **kw): return self._return_aggregate_3() def delete_os_aggregates_1(self, **kw): return (202, {}, None) def post_os_aggregates_1_images(self, body, **kw): return (202, {}, None) # # Services # def get_os_services(self, **kw): host = kw.get('host', 'host1') binary = kw.get('binary', 'nova-compute') if self.api_version >= api_versions.APIVersion('2.53'): service_id_1 = FAKE_SERVICE_UUID_1 service_id_2 = FAKE_SERVICE_UUID_2 else: service_id_1 = 1 service_id_2 = 2 services = { 'services': [ {'binary': binary, 'host': host, 'zone': 'nova', 'status': 'enabled', 'state': 'up', 'updated_at': datetime.datetime( 2012, 10, 29, 13, 42, 2), 'id': service_id_1}, {'binary': binary, 'host': host, 'zone': 'nova', 'status': 'disabled', 'state': 'down', 'updated_at': datetime.datetime( 2012, 9, 18, 8, 3, 38), 'id': service_id_2}, ] } if self.api_version >= api_versions.APIVersion('2.69'): services['services'].append( { "binary": "nova-compute", "host": "host-down", "status": "UNKNOWN" } ) return (200, FAKE_RESPONSE_HEADERS, services) def put_os_services_enable(self, body, **kw): return (200, FAKE_RESPONSE_HEADERS, {'service': {'host': body['host'], 'binary': body['binary'], 'status': 'enabled'}}) def put_os_services_disable(self, body, **kw): return (200, FAKE_RESPONSE_HEADERS, {'service': {'host': body['host'], 'binary': body['binary'], 'status': 'disabled'}}) def put_os_services_disable_log_reason(self, body, **kw): return (200, FAKE_RESPONSE_HEADERS, {'service': { 'host': body['host'], 'binary': body['binary'], 'status': 'disabled', 'disabled_reason': body['disabled_reason']}}) def put_os_services_75e9eabc_ed3b_4f11_8bba_add1e7e7e2de( self, body, **kw): """This should only be called with microversion >= 2.53.""" return (200, FAKE_RESPONSE_HEADERS, {'service': { 'host': 'host1', 'binary': 'nova-compute', 'status': body.get('status', 'enabled'), 'disabled_reason': body.get('disabled_reason'), 'forced_down': body.get('forced_down', False)}}) def delete_os_services_1(self, **kw): return (204, FAKE_RESPONSE_HEADERS, None) def delete_os_services_75e9eabc_ed3b_4f11_8bba_add1e7e7e2de(self, **kwarg): return (204, FAKE_RESPONSE_HEADERS, None) def put_os_services_force_down(self, body, **kw): return (200, FAKE_RESPONSE_HEADERS, {'service': { 'host': body['host'], 'binary': body['binary'], 'forced_down': False}}) # # Hypervisors # def get_os_hypervisors(self, **kw): return (200, {}, { "hypervisors": [ {'id': 1234, 'hypervisor_hostname': 'hyper1'}, {'id': 5678, 'hypervisor_hostname': 'hyper2'}]}) def get_os_hypervisors_statistics(self, **kw): return (200, {}, { "hypervisor_statistics": { 'count': 2, 'vcpus': 8, 'memory_mb': 20 * 1024, 'local_gb': 500, 'vcpus_used': 4, 'memory_mb_used': 10 * 1024, 'local_gb_used': 250, 'free_ram_mb': 10 * 1024, 'free_disk_gb': 250, 'current_workload': 4, 'running_vms': 4, 'disk_available_least': 200} }) def get_os_hypervisors_hyper1(self, **kw): return (200, {}, { 'hypervisor': {'id': 1234, 'service': {'id': 1, 'host': 'compute1'}, 'vcpus': 4, 'memory_mb': 10 * 1024, 'local_gb': 250, 'vcpus_used': 2, 'memory_mb_used': 5 * 1024, 'local_gb_used': 125, 'hypervisor_type': "xen", 'hypervisor_version': 3, 'hypervisor_hostname': "hyper1", 'free_ram_mb': 5 * 1024, 'free_disk_gb': 125, 'current_workload': 2, 'running_vms': 2, 'cpu_info': 'cpu_info', 'disk_available_least': 100}}) def get_os_hypervisors_region_child_1(self, **kw): return (200, {}, { 'hypervisor': {'id': 'region!child@1', 'service': {'id': 1, 'host': 'compute1'}, 'vcpus': 4, 'memory_mb': 10 * 1024, 'local_gb': 250, 'vcpus_used': 2, 'memory_mb_used': 5 * 1024, 'local_gb_used': 125, 'hypervisor_type': "xen", 'hypervisor_version': 3, 'hypervisor_hostname': "hyper1", 'free_ram_mb': 5 * 1024, 'free_disk_gb': 125, 'current_workload': 2, 'running_vms': 2, 'cpu_info': 'cpu_info', 'disk_available_least': 100}}) def get_os_hypervisors_hyper_search(self, **kw): return (200, {}, { 'hypervisors': [ {'id': 1234, 'hypervisor_hostname': 'hyper1'}, {'id': 5678, 'hypervisor_hostname': 'hyper2'}]}) def get_os_hypervisors_hyper_servers(self, **kw): return (200, {}, { 'hypervisors': [ {'id': 1234, 'hypervisor_hostname': 'hyper1', 'servers': [ {'name': 'inst1', 'uuid': 'uuid1'}, {'name': 'inst2', 'uuid': 'uuid2'}]}, {'id': 5678, 'hypervisor_hostname': 'hyper2', 'servers': [ {'name': 'inst3', 'uuid': 'uuid3'}, {'name': 'inst4', 'uuid': 'uuid4'}]}] }) def get_os_hypervisors_hyper1_servers(self, **kw): return (200, {}, { 'hypervisors': [ {'id': 1234, 'hypervisor_hostname': 'hyper1', 'servers': [ {'name': 'inst1', 'uuid': 'uuid1'}, {'name': 'inst2', 'uuid': 'uuid2'}]}] }) def get_os_hypervisors_hyper2_servers(self, **kw): return (200, {}, { 'hypervisors': [ {'id': 5678, 'hypervisor_hostname': 'hyper2', 'servers': [ {'name': 'inst3', 'uuid': 'uuid3'}, {'name': 'inst4', 'uuid': 'uuid4'}]}] }) def get_os_hypervisors_hyper_no_servers_servers(self, **kw): return (200, {}, {'hypervisors': [{'id': 1234, 'hypervisor_hostname': 'hyper1'}]}) def get_os_hypervisors_1234(self, **kw): return (200, {}, { 'hypervisor': {'id': 1234, 'service': {'id': 1, 'host': 'compute1'}, 'vcpus': 4, 'memory_mb': 10 * 1024, 'local_gb': 250, 'vcpus_used': 2, 'memory_mb_used': 5 * 1024, 'local_gb_used': 125, 'hypervisor_type': "xen", 'hypervisor_version': 3, 'hypervisor_hostname': "hyper1", 'free_ram_mb': 5 * 1024, 'free_disk_gb': 125, 'current_workload': 2, 'running_vms': 2, 'cpu_info': 'cpu_info', 'disk_available_least': 100}}) def get_os_hypervisors_1234_uptime(self, **kw): return (200, {}, { 'hypervisor': {'id': 1234, 'hypervisor_hostname': "hyper1", 'uptime': "fake uptime"}}) def get_os_hypervisors_region_child_1_uptime(self, **kw): return (200, {}, { 'hypervisor': {'id': 'region!child@1', 'hypervisor_hostname': "hyper1", 'uptime': "fake uptime"}}) def get_v2_0_networks(self, **kw): """Return neutron proxied networks. We establish a few different possible networks that we can get by name, which we can then call in tests. The only usage of this API should be for name -> id translation, however a full valid neutron block is provided for the private network to see the kinds of things that will be in that payload. """ name = kw.get('name', "blank") networks_by_name = { 'private': [ {"status": "ACTIVE", "router:external": False, "availability_zone_hints": [], "availability_zones": ["nova"], "description": "", "name": "private", "subnets": ["64706c26-336c-4048-ab3c-23e3283bca2c", "18512740-c760-4d5f-921f-668105c9ee44"], "shared": False, "tenant_id": "abd42f270bca43ea80fe4a166bc3536c", "created_at": "2016-08-15T17:34:49", "tags": [], "ipv6_address_scope": None, "updated_at": "2016-08-15T17:34:49", "admin_state_up": True, "ipv4_address_scope": None, "port_security_enabled": True, "mtu": 1450, "id": "e43a56c7-11d4-45c9-8681-ddc8171b5850", "revision": 2}], 'duplicate': [ {"status": "ACTIVE", "id": "e43a56c7-11d4-45c9-8681-ddc8171b5850"}, {"status": "ACTIVE", "id": "f43a56c7-11d4-45c9-8681-ddc8171b5850"}], 'blank': [] } return (200, {}, {"networks": networks_by_name[name]}) def get_os_availability_zone_detail(self, **kw): return (200, {}, { "availabilityZoneInfo": [ {"zoneName": "zone-1", "zoneState": {"available": True}, "hosts": { "fake_host-1": { "nova-compute": { "active": True, "available": True, "updated_at": datetime.datetime( 2012, 12, 26, 14, 45, 25, 0)}}}}, {"zoneName": "internal", "zoneState": {"available": True}, "hosts": { "fake_host-1": { "nova-sched": { "active": True, "available": True, "updated_at": datetime.datetime( 2012, 12, 26, 14, 45, 25, 0)}}, "fake_host-2": { "nova-network": { "active": True, "available": False, "updated_at": datetime.datetime( 2012, 12, 26, 14, 45, 24, 0)}}}}, {"zoneName": "zone-2", "zoneState": {"available": False}, "hosts": None}]}) def get_servers_1234_os_interface(self, **kw): attachments = { "interfaceAttachments": [ {"port_state": "ACTIVE", "net_id": "net-id-1", "port_id": "port-id-1", "mac_address": "aa:bb:cc:dd:ee:ff", "fixed_ips": [{"ip_address": "1.2.3.4"}], }, {"port_state": "ACTIVE", "net_id": "net-id-1", "port_id": "port-id-1", "mac_address": "aa:bb:cc:dd:ee:ff", "fixed_ips": [{"ip_address": "1.2.3.4"}], } ] } if self.api_version >= api_versions.APIVersion('2.70'): # Include the "tag" field in each attachment. for attachment in attachments['interfaceAttachments']: attachment['tag'] = 'test-tag' return (200, {}, attachments) def post_servers_1234_os_interface(self, **kw): attachment = {} if self.api_version >= api_versions.APIVersion('2.70'): # Include the "tag" field in the response. attachment['tag'] = 'test-tag' return (200, {}, {'interfaceAttachment': attachment}) def delete_servers_1234_os_interface_port_id(self, **kw): return (200, {}, None) def post_servers_1234_os_volume_attachments(self, **kw): attachment = {"device": "/dev/vdb", "volumeId": 2} if self.api_version >= api_versions.APIVersion('2.70'): # Include the "tag" field in the response. attachment['tag'] = 'test-tag' if self.api_version >= api_versions.APIVersion('2.79'): # Include the "delete_on_termination" field in the # response. attachment['delete_on_termination'] = True return (200, FAKE_RESPONSE_HEADERS, {"volumeAttachment": attachment}) def put_servers_1234_os_volume_attachments_Work(self, **kw): return (200, FAKE_RESPONSE_HEADERS, {"volumeAttachment": {"volumeId": 2}}) def get_servers_1234_os_volume_attachments(self, **kw): attachments = { "volumeAttachments": [ {"display_name": "Work", "display_description": "volume for work", "status": "ATTACHED", "id": "15e59938-07d5-11e1-90e3-e3dffe0c5983", "created_at": "2011-09-09T00:00:00Z", "attached": "2011-11-11T00:00:00Z", "size": 1024, "attachments": [{"id": "3333", "links": ''}], "metadata": {}} ] } if self.api_version >= api_versions.APIVersion('2.70'): # Include the "tag" field in each attachment. for attachment in attachments['volumeAttachments']: attachment['tag'] = 'test-tag' if self.api_version >= api_versions.APIVersion('2.79'): # Include the "delete_on_termination" field in each # attachment. for attachment in attachments['volumeAttachments']: attachment['delete_on_termination'] = True return (200, FAKE_RESPONSE_HEADERS, attachments) def get_servers_1234_os_volume_attachments_Work(self, **kw): return (200, FAKE_RESPONSE_HEADERS, { "volumeAttachment": {"display_name": "Work", "display_description": "volume for work", "status": "ATTACHED", "id": "15e59938-07d5-11e1-90e3-e3dffe0c5983", "created_at": "2011-09-09T00:00:00Z", "attached": "2011-11-11T00:00:00Z", "size": 1024, "attachments": [{"id": "3333", "links": ''}], "metadata": {}}}) def delete_servers_1234_os_volume_attachments_Work(self, **kw): return (200, FAKE_RESPONSE_HEADERS, {}) def get_servers_1234_os_instance_actions(self, **kw): action = {"instance_uuid": "1234", "user_id": "b968c25e04ab405f9fe4e6ca54cce9a5", "start_time": "2013-03-25T13:45:09.000000", "request_id": "req-abcde12345", "action": "create", "message": None, "project_id": "04019601fe3648c0abd4f4abfb9e6106"} if self.api_version >= api_versions.APIVersion('2.58'): # This is intentionally different from the start_time. action['updated_at'] = '2013-03-25T13:50:09.000000' return (200, FAKE_RESPONSE_HEADERS, { "instanceActions": [action]}) def get_servers_1234_os_instance_actions_req_abcde12345(self, **kw): action = {"instance_uuid": "1234", "user_id": "b968c25e04ab405f9fe4e6ca54cce9a5", "start_time": "2013-03-25T13:45:09.000000", "request_id": "req-abcde12345", "action": "create", "message": None, "project_id": "04019601fe3648c0abd4f4abfb9e6106"} if self.api_version >= api_versions.APIVersion('2.58'): action['updated_at'] = '2013-03-25T13:45:09.000000' return (200, FAKE_RESPONSE_HEADERS, { "instanceAction": action}) def get_os_instance_usage_audit_log(self, **kw): return (200, FAKE_RESPONSE_HEADERS, { "instance_usage_audit_logs": { "hosts_not_run": ["samplehost3"], "log": { "samplehost0": { "errors": 1, "instances": 1, "message": ("Instance usage audit ran for host " "samplehost0, 1 instances in 0.01 " "seconds."), "state": "DONE" }, "samplehost1": { "errors": 1, "instances": 2, "message": ("Instance usage audit ran for host " "samplehost1, 2 instances in 0.01 " "seconds."), "state": "DONE" }, "samplehost2": { "errors": 1, "instances": 3, "message": ("Instance usage audit ran for host " "samplehost2, 3 instances in 0.01 " "seconds."), "state": "DONE" }, }, "num_hosts": 4, "num_hosts_done": 3, "num_hosts_not_run": 1, "num_hosts_running": 0, "overall_status": "3 of 4 hosts done. 3 errors.", "period_beginning": "2012-06-01 00:00:00", "period_ending": "2012-07-01 00:00:00", "total_errors": 3, "total_instances": 6}}) def get_os_instance_usage_audit_log_2016_12_10_13_59_59_999999(self, **kw): return (200, FAKE_RESPONSE_HEADERS, { "instance_usage_audit_log": { "hosts_not_run": ["samplehost3"], "log": { "samplehost0": { "errors": 1, "instances": 1, "message": ("Instance usage audit ran for host " "samplehost0, 1 instances in 0.01 " "seconds."), "state": "DONE" }, "samplehost1": { "errors": 1, "instances": 2, "message": ("Instance usage audit ran for host " "samplehost1, 2 instances in 0.01 " "seconds."), "state": "DONE" }, "samplehost2": { "errors": 1, "instances": 3, "message": ("Instance usage audit ran for host " "samplehost2, 3 instances in 0.01 " "seconds."), "state": "DONE" }, }, "num_hosts": 4, "num_hosts_done": 3, "num_hosts_not_run": 1, "num_hosts_running": 0, "overall_status": "3 of 4 hosts done. 3 errors.", "period_beginning": "2012-06-01 00:00:00", "period_ending": "2012-07-01 00:00:00", "total_errors": 3, "total_instances": 6}}) def get_os_instance_usage_audit_log__5Cu5de5_5Cu4f5c(self, **kw): return (400, {}, {"Invalid timestamp for date \u6f22\u5b57"}) def post_servers_uuid1_action(self, **kw): return 202, {}, {} def post_servers_uuid2_action(self, **kw): return 202, {}, {} def post_servers_uuid3_action(self, **kw): return 202, {}, {} def post_servers_uuid4_action(self, **kw): return 202, {}, {} def post_servers_uuid5_action(self, **kw): return 202, {}, {} def post_servers_uuid6_action(self, **kw): return 202, {}, {} def get_os_migrations(self, **kw): migration1 = { "created_at": "2012-10-29T13:42:02.000000", "dest_compute": "compute2", "dest_host": "1.2.3.4", "dest_node": "node2", "id": '1234', "instance_uuid": "instance_id_123", "new_instance_type_id": 2, "old_instance_type_id": 1, "source_compute": "compute1", "source_node": "node1", "status": "Done", "updated_at": "2012-10-29T13:42:02.000000" } migration2 = { "created_at": "2012-10-29T13:42:02.000000", "dest_compute": "compute2", "dest_host": "1.2.3.4", "dest_node": "node2", "id": '1234', "instance_uuid": "instance_id_456", "new_instance_type_id": 2, "old_instance_type_id": 1, "source_compute": "compute1", "source_node": "node1", "status": "Done", "updated_at": "2013-11-50T13:42:02.000000" } if self.api_version >= api_versions.APIVersion("2.23"): migration1.update({"migration_type": "live-migration"}) migration2.update({"migration_type": "live-migration"}) if self.api_version >= api_versions.APIVersion("2.59"): migration1.update({"uuid": "11111111-07d5-11e1-90e3-e3dffe0c5983"}) migration2.update({"uuid": "22222222-07d5-11e1-90e3-e3dffe0c5983"}) if self.api_version >= api_versions.APIVersion("2.80"): migration1.update({ "project_id": "b59c18e5fa284fd384987c5cb25a1853", "user_id": "13cc0930d27c4be0acc14d7c47a3e1f7"}) migration2.update({ "project_id": "b59c18e5fa284fd384987c5cb25a1853", "user_id": "13cc0930d27c4be0acc14d7c47a3e1f7"}) migration_list = [] instance_uuid = kw.get('instance_uuid', None) if instance_uuid == migration1['instance_uuid']: migration_list.append(migration1) elif instance_uuid == migration2['instance_uuid']: migration_list.append(migration2) elif instance_uuid is None: migration_list.extend([migration1, migration2]) migrations = {'migrations': migration_list} return (200, FAKE_RESPONSE_HEADERS, migrations) # # Server Groups # def get_os_server_groups(self, **kw): server_groups = [ {"members": [], "metadata": {}, "id": "2cbd51f4-fafe-4cdb-801b-cf913a6f288b", "policies": [], "name": "ig1"}, {"members": [], "metadata": {}, "id": "4473bb03-4370-4bfb-80d3-dc8cffc47d94", "policies": ["anti-affinity"], "name": "ig2"}, {"members": [], "metadata": {"key": "value"}, "id": "31ab9bdb-55e1-4ac3-b094-97eeb1b65cc4", "policies": [], "name": "ig3"}, {"members": ["2dccb4a1-02b9-482a-aa23-5799490d6f5d"], "metadata": {}, "id": "4890bb03-7070-45fb-8453-d34556c87d94", "policies": ["anti-affinity"], "name": "ig2"}] other_project_server_groups = [ {"members": [], "metadata": {}, "id": "11111111-1111-1111-1111-111111111111", "policies": [], "name": "ig4"}, {"members": [], "metadata": {}, "id": "22222222-2222-2222-2222-222222222222", "policies": ["anti-affinity"], "name": "ig5"}, {"members": [], "metadata": {"key": "value"}, "id": "31ab9bdb-55e1-4ac3-b094-97eeb1b65cc4", "policies": [], "name": "ig6"}, {"members": ["33333333-3333-3333-3333-333333333333"], "metadata": {}, "id": "44444444-4444-4444-4444-444444444444", "policies": ["anti-affinity"], "name": "ig5"}] if kw.get("all_projects", False): server_groups.extend(other_project_server_groups) limit = int(kw.get("limit", 1000)) offset = int(kw.get("offset", 0)) server_groups = server_groups[offset:limit + 1] return (200, {}, {"server_groups": server_groups}) def _return_server_group(self): if self.api_version < api_versions.APIVersion("2.64"): r = {'server_group': self.get_os_server_groups()[2]['server_groups'][0]} else: r = {"members": [], "id": "2cbd51f4-fafe-4cdb-801b-cf913a6f288b", 'server_group': {'name': 'ig1', 'policy': 'anti-affinity', 'rules': {'max_server_per_host': 3}}} return (200, {}, r) def post_os_server_groups(self, body, **kw): return self._return_server_group() def post_servers_1234_migrations_1_action(self, body): return (202, {}, None) @api_versions.wraps(start_version="2.23") def get_servers_1234_migrations_1(self, **kw): migration = {"migration": { "created_at": "2016-01-29T13:42:02.000000", "dest_compute": "compute2", "dest_host": "1.2.3.4", "dest_node": "node2", "id": 1, "server_uuid": "4cfba335-03d8-49b2-8c52-e69043d1e8fe", "source_compute": "compute1", "source_node": "node1", "status": "running", "memory_total_bytes": 123456, "memory_processed_bytes": 12345, "memory_remaining_bytes": 120000, "disk_total_bytes": 234567, "disk_processed_bytes": 23456, "disk_remaining_bytes": 230000, "updated_at": "2016-01-29T13:42:02.000000" }} if self.api_version >= api_versions.APIVersion("2.80"): migration['migration'].update({ "project_id": "b59c18e5fa284fd384987c5cb25a1853", "user_id": "13cc0930d27c4be0acc14d7c47a3e1f7"}) return (200, FAKE_RESPONSE_HEADERS, migration) @api_versions.wraps(start_version="2.23") def get_servers_1234_migrations(self, **kw): migrations = {'migrations': [ { "created_at": "2016-01-29T13:42:02.000000", "dest_compute": "compute2", "dest_host": "1.2.3.4", "dest_node": "node2", "id": 1, "server_uuid": "4cfba335-03d8-49b2-8c52-e69043d1e8fe", "source_compute": "compute1", "source_node": "node1", "status": "running", "memory_total_bytes": 123456, "memory_processed_bytes": 12345, "memory_remaining_bytes": 120000, "disk_total_bytes": 234567, "disk_processed_bytes": 23456, "disk_remaining_bytes": 230000, "updated_at": "2016-01-29T13:42:02.000000" }]} if self.api_version >= api_versions.APIVersion("2.80"): migrations['migrations'][0].update({ "project_id": "b59c18e5fa284fd384987c5cb25a1853", "user_id": "13cc0930d27c4be0acc14d7c47a3e1f7"}) return (200, FAKE_RESPONSE_HEADERS, migrations) def delete_servers_1234_migrations_1(self): return (202, {}, None) def put_servers_1234(self, **kw): return (201, {}, None) def put_servers_1234_tags_tag(self, **kw): return (201, {}, None) def put_servers_1234_tags_tag1(self, **kw): return (201, {}, None) def put_servers_1234_tags_tag2(self, **kw): return (201, {}, None) def put_servers_1234_tags_tag3(self, **kw): return (201, {}, None) def put_servers_1234_tags(self, **kw): return (201, {}, None) def get_servers_1234_tags(self, **kw): return (200, {}, {'tags': ['tag1', 'tag2']}) def delete_servers_1234_tags_tag(self, **kw): return (204, {}, None) def delete_servers_1234_tags_tag1(self, **kw): return (204, {}, None) def delete_servers_1234_tags_tag2(self, **kw): return (204, {}, None) def delete_servers_1234_tags_tag3(self, **kw): return (204, {}, None) def delete_servers_1234_tags(self, **kw): return (204, {}, None) def post_os_assisted_volume_snapshots(self, **kw): return (202, FAKE_RESPONSE_HEADERS, {'snapshot': {'id': 'blah', 'volumeId': '1'}}) def delete_os_assisted_volume_snapshots_x(self, **kw): return (202, FAKE_RESPONSE_HEADERS, {}) def post_os_server_external_events(self, **kw): return (200, FAKE_RESPONSE_HEADERS, { 'events': [ {'name': 'test-event', 'status': 'completed', 'tag': 'tag', 'server_uuid': 'fake-uuid1'}, {'name': 'test-event', 'status': 'completed', 'tag': 'tag', 'server_uuid': 'fake-uuid2'}]}) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/v2/test_agents.py0000664000175000017500000001047200000000000025023 0ustar00zuulzuul00000000000000# Copyright 2012 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from novaclient.tests.unit.fixture_data import agents as data from novaclient.tests.unit.fixture_data import client from novaclient.tests.unit import utils from novaclient.tests.unit.v2 import fakes from novaclient.v2 import agents class AgentsTest(utils.FixturedTestCase): data_fixture_class = data.Fixture scenarios = [('original', {'client_fixture_class': client.V1}), ('session', {'client_fixture_class': client.SessionV1})] def stub_hypervisors(self, hypervisor='kvm'): get_os_agents = { 'agents': [ { 'hypervisor': hypervisor, 'os': 'win', 'architecture': 'x86', 'version': '7.0', 'url': 'xxx://xxxx/xxx/xxx', 'md5hash': 'add6bb58e139be103324d04d82d8f545', 'id': 1 }, { 'hypervisor': hypervisor, 'os': 'linux', 'architecture': 'x86', 'version': '16.0', 'url': 'xxx://xxxx/xxx/xxx1', 'md5hash': 'add6bb58e139be103324d04d82d8f546', 'id': 2 }, ] } headers = {'Content-Type': 'application/json', 'x-openstack-request-id': fakes.FAKE_REQUEST_ID} self.requests_mock.get(self.data_fixture.url(), json=get_os_agents, headers=headers) def test_list_agents(self): self.stub_hypervisors() ags = self.cs.agents.list() self.assert_called('GET', '/os-agents') self.assert_request_id(ags, fakes.FAKE_REQUEST_ID_LIST) for a in ags: self.assertIsInstance(a, agents.Agent) self.assertEqual('kvm', a.hypervisor) def test_list_agents_with_hypervisor(self): self.stub_hypervisors('xen') ags = self.cs.agents.list('xen') self.assert_called('GET', '/os-agents?hypervisor=xen') self.assert_request_id(ags, fakes.FAKE_REQUEST_ID_LIST) for a in ags: self.assertIsInstance(a, agents.Agent) self.assertEqual('xen', a.hypervisor) def test_agents_create(self): ag = self.cs.agents.create('win', 'x86', '7.0', '/xxx/xxx/xxx', 'add6bb58e139be103324d04d82d8f546', 'xen') self.assert_request_id(ag, fakes.FAKE_REQUEST_ID_LIST) body = {'agent': {'url': '/xxx/xxx/xxx', 'hypervisor': 'xen', 'md5hash': 'add6bb58e139be103324d04d82d8f546', 'version': '7.0', 'architecture': 'x86', 'os': 'win'}} self.assert_called('POST', '/os-agents', body) self.assertEqual(1, ag._info.copy()['id']) def test_agents_delete(self): ret = self.cs.agents.delete('1') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('DELETE', '/os-agents/1') def _build_example_update_body(self): return {"para": { "url": "/yyy/yyyy/yyyy", "version": "8.0", "md5hash": "add6bb58e139be103324d04d82d8f546"}} def test_agents_modify(self): ag = self.cs.agents.update('1', '8.0', '/yyy/yyyy/yyyy', 'add6bb58e139be103324d04d82d8f546') self.assert_request_id(ag, fakes.FAKE_REQUEST_ID_LIST) body = self._build_example_update_body() self.assert_called('PUT', '/os-agents/1', body) self.assertEqual(1, ag.id) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/v2/test_aggregates.py0000664000175000017500000002127500000000000025656 0ustar00zuulzuul00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from novaclient import api_versions from novaclient import exceptions from novaclient.tests.unit.fixture_data import aggregates as data from novaclient.tests.unit.fixture_data import client from novaclient.tests.unit import utils from novaclient.tests.unit.v2 import fakes from novaclient.v2 import aggregates from novaclient.v2 import images class AggregatesTest(utils.FixturedTestCase): data_fixture_class = data.Fixture scenarios = [('original', {'client_fixture_class': client.V1}), ('session', {'client_fixture_class': client.SessionV1})] def test_list_aggregates(self): result = self.cs.aggregates.list() self.assert_request_id(result, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/os-aggregates') for aggregate in result: self.assertIsInstance(aggregate, aggregates.Aggregate) def test_create_aggregate(self): body = {"aggregate": {"name": "test", "availability_zone": "nova1"}} aggregate = self.cs.aggregates.create("test", "nova1") self.assert_request_id(aggregate, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/os-aggregates', body) self.assertIsInstance(aggregate, aggregates.Aggregate) def test_get(self): aggregate = self.cs.aggregates.get("1") self.assert_request_id(aggregate, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/os-aggregates/1') self.assertIsInstance(aggregate, aggregates.Aggregate) aggregate2 = self.cs.aggregates.get(aggregate) self.assert_request_id(aggregate2, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/os-aggregates/1') self.assertIsInstance(aggregate2, aggregates.Aggregate) def test_get_details(self): aggregate = self.cs.aggregates.get_details("1") self.assert_request_id(aggregate, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/os-aggregates/1') self.assertIsInstance(aggregate, aggregates.Aggregate) aggregate2 = self.cs.aggregates.get_details(aggregate) self.assert_request_id(aggregate2, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/os-aggregates/1') self.assertIsInstance(aggregate2, aggregates.Aggregate) def test_update(self): aggregate = self.cs.aggregates.get("1") values = {"name": "foo"} body = {"aggregate": values} result1 = aggregate.update(values) self.assert_request_id(result1, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('PUT', '/os-aggregates/1', body) self.assertIsInstance(result1, aggregates.Aggregate) result2 = self.cs.aggregates.update(2, values) self.assert_request_id(result2, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('PUT', '/os-aggregates/2', body) self.assertIsInstance(result2, aggregates.Aggregate) def test_update_with_availability_zone(self): aggregate = self.cs.aggregates.get("1") values = {"name": "foo", "availability_zone": "new_zone"} body = {"aggregate": values} result3 = self.cs.aggregates.update(aggregate, values) self.assert_request_id(result3, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('PUT', '/os-aggregates/1', body) self.assertIsInstance(result3, aggregates.Aggregate) def test_add_host(self): aggregate = self.cs.aggregates.get("1") host = "host1" body = {"add_host": {"host": "host1"}} result1 = aggregate.add_host(host) self.assert_request_id(result1, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/os-aggregates/1/action', body) self.assertIsInstance(result1, aggregates.Aggregate) result2 = self.cs.aggregates.add_host("2", host) self.assert_request_id(result2, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/os-aggregates/2/action', body) self.assertIsInstance(result2, aggregates.Aggregate) result3 = self.cs.aggregates.add_host(aggregate, host) self.assert_request_id(result3, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/os-aggregates/1/action', body) self.assertIsInstance(result3, aggregates.Aggregate) def test_remove_host(self): aggregate = self.cs.aggregates.get("1") host = "host1" body = {"remove_host": {"host": "host1"}} result1 = aggregate.remove_host(host) self.assert_request_id(result1, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/os-aggregates/1/action', body) self.assertIsInstance(result1, aggregates.Aggregate) result2 = self.cs.aggregates.remove_host("2", host) self.assert_request_id(result2, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/os-aggregates/2/action', body) self.assertIsInstance(result2, aggregates.Aggregate) result3 = self.cs.aggregates.remove_host(aggregate, host) self.assert_request_id(result3, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/os-aggregates/1/action', body) self.assertIsInstance(result3, aggregates.Aggregate) def test_set_metadata(self): aggregate = self.cs.aggregates.get("1") metadata = {"foo": "bar"} body = {"set_metadata": {"metadata": metadata}} result1 = aggregate.set_metadata(metadata) self.assert_request_id(result1, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/os-aggregates/1/action', body) self.assertIsInstance(result1, aggregates.Aggregate) result2 = self.cs.aggregates.set_metadata(2, metadata) self.assert_request_id(result2, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/os-aggregates/2/action', body) self.assertIsInstance(result2, aggregates.Aggregate) result3 = self.cs.aggregates.set_metadata(aggregate, metadata) self.assert_request_id(result3, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/os-aggregates/1/action', body) self.assertIsInstance(result3, aggregates.Aggregate) def test_delete_aggregate(self): aggregate = self.cs.aggregates.list()[0] result1 = aggregate.delete() self.assert_request_id(result1, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('DELETE', '/os-aggregates/1') result2 = self.cs.aggregates.delete('1') self.assert_request_id(result2, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('DELETE', '/os-aggregates/1') result3 = self.cs.aggregates.delete(aggregate) self.assert_request_id(result3, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('DELETE', '/os-aggregates/1') class AggregatesV281Test(utils.FixturedTestCase): api_version = "2.81" data_fixture_class = data.Fixture scenarios = [('original', {'client_fixture_class': client.V1}), ('session', {'client_fixture_class': client.SessionV1})] def setUp(self): super(AggregatesV281Test, self).setUp() self.cs.api_version = api_versions.APIVersion(self.api_version) def test_cache_images(self): aggregate = self.cs.aggregates.list()[0] _images = [images.Image(self.cs.aggregates, {'id': '1'}), images.Image(self.cs.aggregates, {'id': '2'})] aggregate.cache_images(_images) expected_body = {'cache': [{'id': image.id} for image in _images]} self.assert_called('POST', '/os-aggregates/1/images', expected_body) def test_cache_images_just_ids(self): aggregate = self.cs.aggregates.list()[0] _images = ['1'] aggregate.cache_images(_images) expected_body = {'cache': [{'id': '1'}]} self.assert_called('POST', '/os-aggregates/1/images', expected_body) def test_cache_images_pre281(self): self.cs.api_version = api_versions.APIVersion('2.80') aggregate = self.cs.aggregates.list()[0] _images = [images.Image(self.cs.aggregates, {'id': '1'})] self.assertRaises(exceptions.VersionNotFoundForAPIMethod, aggregate.cache_images, _images) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/v2/test_assisted_volume_snapshots.py0000664000175000017500000000275500000000000031057 0ustar00zuulzuul00000000000000# Copyright (C) 2013, 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. """ Assisted volume snapshots - to be used by Cinder and not end users. """ from novaclient import api_versions from novaclient.tests.unit import utils from novaclient.tests.unit.v2 import fakes class AssistedVolumeSnapshotsTestCase(utils.TestCase): def setUp(self): super(AssistedVolumeSnapshotsTestCase, self).setUp() self.cs = fakes.FakeClient(api_versions.APIVersion("2.1")) def test_create_snap(self): vs = self.cs.assisted_volume_snapshots.create('1', {}) self.assert_request_id(vs, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('POST', '/os-assisted-volume-snapshots') def test_delete_snap(self): vs = self.cs.assisted_volume_snapshots.delete('x', {}) self.assert_request_id(vs, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called( 'DELETE', '/os-assisted-volume-snapshots/x?delete_info={}') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/v2/test_availability_zone.py0000664000175000017500000000745000000000000027251 0ustar00zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from novaclient.tests.unit.fixture_data import availability_zones as data from novaclient.tests.unit.fixture_data import client from novaclient.tests.unit import utils from novaclient.tests.unit.v2 import fakes from novaclient.v2 import availability_zones class AvailabilityZoneTest(utils.FixturedTestCase): # NOTE(cyeoh): import shell here so the V3 version of # this class can inherit off the v3 version of shell from novaclient.v2 import shell # noqa data_fixture_class = data.V1 scenarios = [('original', {'client_fixture_class': client.V1}), ('session', {'client_fixture_class': client.SessionV1})] def setUp(self): super(AvailabilityZoneTest, self).setUp() self.availability_zone_type = self._get_availability_zone_type() def _get_availability_zone_type(self): return availability_zones.AvailabilityZone def _assertZone(self, zone, name, status): self.assertEqual(zone.zoneName, name) self.assertEqual(zone.zoneState, status) def test_list_availability_zone(self): zones = self.cs.availability_zones.list(detailed=False) self.assert_request_id(zones, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/os-availability-zone') for zone in zones: self.assertIsInstance(zone, self.availability_zone_type) self.assertEqual(2, len(zones)) l0 = ['zone-1', 'available'] l1 = ['zone-2', 'not available'] z0 = self.shell._treeizeAvailabilityZone(zones[0]) z1 = self.shell._treeizeAvailabilityZone(zones[1]) self.assertEqual((1, 1), (len(z0), len(z1))) self._assertZone(z0[0], l0[0], l0[1]) self._assertZone(z1[0], l1[0], l1[1]) def test_detail_availability_zone(self): zones = self.cs.availability_zones.list(detailed=True) self.assert_request_id(zones, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/os-availability-zone/detail') for zone in zones: self.assertIsInstance(zone, self.availability_zone_type) self.assertEqual(3, len(zones)) l0 = ['zone-1', 'available'] l1 = ['|- fake_host-1', ''] l2 = ['| |- nova-compute', 'enabled :-) 2012-12-26 14:45:25'] l3 = ['internal', 'available'] l4 = ['|- fake_host-1', ''] l5 = ['| |- nova-sched', 'enabled :-) 2012-12-26 14:45:25'] l6 = ['|- fake_host-2', ''] l7 = ['| |- nova-network', 'enabled XXX 2012-12-26 14:45:24'] l8 = ['zone-2', 'not available'] z0 = self.shell._treeizeAvailabilityZone(zones[0]) z1 = self.shell._treeizeAvailabilityZone(zones[1]) z2 = self.shell._treeizeAvailabilityZone(zones[2]) self.assertEqual((3, 5, 1), (len(z0), len(z1), len(z2))) self._assertZone(z0[0], l0[0], l0[1]) self._assertZone(z0[1], l1[0], l1[1]) self._assertZone(z0[2], l2[0], l2[1]) self._assertZone(z1[0], l3[0], l3[1]) self._assertZone(z1[1], l4[0], l4[1]) self._assertZone(z1[2], l5[0], l5[1]) self._assertZone(z1[3], l6[0], l6[1]) self._assertZone(z1[4], l7[0], l7[1]) self._assertZone(z2[0], l8[0], l8[1]) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/v2/test_client.py0000664000175000017500000000330600000000000025016 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 keystoneauth1 import session from oslo_utils import uuidutils from novaclient import api_versions from novaclient.tests.unit import utils from novaclient.v2 import client class ClientTest(utils.TestCase): def test_adapter_properties(self): # sample of properties, there are many more user_agent = uuidutils.generate_uuid(dashed=False) endpoint_override = uuidutils.generate_uuid(dashed=False) s = session.Session() c = client.Client(session=s, api_version=api_versions.APIVersion("2.0"), user_agent=user_agent, endpoint_override=endpoint_override, direct_use=False) self.assertEqual(user_agent, c.client.user_agent) self.assertEqual(endpoint_override, c.client.endpoint_override) def test_passing_endpoint_type(self): endpoint_type = uuidutils.generate_uuid(dashed=False) s = session.Session() c = client.Client(session=s, endpoint_type=endpoint_type, direct_use=False) self.assertEqual(endpoint_type, c.client.interface) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/v2/test_flavor_access.py0000664000175000017500000000531600000000000026355 0ustar00zuulzuul00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from novaclient import api_versions from novaclient.tests.unit import utils from novaclient.tests.unit.v2 import fakes from novaclient.v2 import flavor_access class FlavorAccessTest(utils.TestCase): def setUp(self): super(FlavorAccessTest, self).setUp() self.cs = fakes.FakeClient(api_versions.APIVersion("2.0")) def test_list_access_by_flavor_private(self): kwargs = {'flavor': self.cs.flavors.get(2)} r = self.cs.flavor_access.list(**kwargs) self.assert_request_id(r, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('GET', '/flavors/2/os-flavor-access') for a in r: self.assertIsInstance(a, flavor_access.FlavorAccess) def test_add_tenant_access(self): flavor = self.cs.flavors.get(2) tenant = 'proj2' r = self.cs.flavor_access.add_tenant_access(flavor, tenant) self.assert_request_id(r, fakes.FAKE_REQUEST_ID_LIST) body = { "addTenantAccess": { "tenant": "proj2" } } self.cs.assert_called('POST', '/flavors/2/action', body) for a in r: self.assertIsInstance(a, flavor_access.FlavorAccess) def test_remove_tenant_access(self): flavor = self.cs.flavors.get(2) tenant = 'proj2' r = self.cs.flavor_access.remove_tenant_access(flavor, tenant) self.assert_request_id(r, fakes.FAKE_REQUEST_ID_LIST) body = { "removeTenantAccess": { "tenant": "proj2" } } self.cs.assert_called('POST', '/flavors/2/action', body) for a in r: self.assertIsInstance(a, flavor_access.FlavorAccess) def test_repr_flavor_access(self): flavor = self.cs.flavors.get(2) tenant = 'proj3' r = self.cs.flavor_access.add_tenant_access(flavor, tenant) def get_expected(flavor_access): return ("" % (flavor_access.flavor_id, flavor_access.tenant_id)) for a in r: self.assertEqual(get_expected(a), repr(a)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/v2/test_flavors.py0000664000175000017500000003265300000000000025223 0ustar00zuulzuul00000000000000# Copyright (c) 2013, OpenStack # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 novaclient import api_versions from novaclient import base from novaclient import exceptions from novaclient.tests.unit import utils from novaclient.tests.unit.v2 import fakes from novaclient.v2 import flavors class FlavorsTest(utils.TestCase): def setUp(self): super(FlavorsTest, self).setUp() self.cs = fakes.FakeClient(api_versions.APIVersion("2.0")) self.flavor_type = self._get_flavor_type() def _get_flavor_type(self): return flavors.Flavor def test_list_flavors(self): fl = self.cs.flavors.list() self.assert_request_id(fl, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('GET', '/flavors/detail') for flavor in fl: self.assertIsInstance(flavor, self.flavor_type) def test_list_flavors_undetailed(self): fl = self.cs.flavors.list(detailed=False) self.assert_request_id(fl, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('GET', '/flavors') for flavor in fl: self.assertIsInstance(flavor, self.flavor_type) def test_list_flavors_with_marker_limit(self): fl = self.cs.flavors.list(marker=1234, limit=4) self.assert_request_id(fl, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('GET', '/flavors/detail?limit=4&marker=1234') def test_list_flavors_with_min_disk(self): fl = self.cs.flavors.list(min_disk=20) self.assert_request_id(fl, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('GET', '/flavors/detail?minDisk=20') def test_list_flavors_with_min_ram(self): fl = self.cs.flavors.list(min_ram=512) self.assert_request_id(fl, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('GET', '/flavors/detail?minRam=512') def test_list_flavors_with_sort_key_dir(self): fl = self.cs.flavors.list(sort_key='id', sort_dir='asc') self.assert_request_id(fl, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('GET', '/flavors/detail?sort_dir=asc&sort_key=id') def test_list_flavors_is_public_none(self): fl = self.cs.flavors.list(is_public=None) self.assert_request_id(fl, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('GET', '/flavors/detail?is_public=None') for flavor in fl: self.assertIsInstance(flavor, self.flavor_type) def test_list_flavors_is_public_false(self): fl = self.cs.flavors.list(is_public=False) self.assert_request_id(fl, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('GET', '/flavors/detail?is_public=False') for flavor in fl: self.assertIsInstance(flavor, self.flavor_type) def test_list_flavors_is_public_true(self): fl = self.cs.flavors.list(is_public=True) self.assert_request_id(fl, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('GET', '/flavors/detail') for flavor in fl: self.assertIsInstance(flavor, self.flavor_type) def test_get_flavor_details(self): f = self.cs.flavors.get(1) self.assert_request_id(f, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('GET', '/flavors/1') self.assertIsInstance(f, self.flavor_type) self.assertEqual(256, f.ram) self.assertEqual(10, f.disk) self.assertEqual(10, f.ephemeral) self.assertTrue(f.is_public) def test_get_flavor_details_alphanum_id(self): f = self.cs.flavors.get('aa1') self.assert_request_id(f, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('GET', '/flavors/aa1') self.assertIsInstance(f, self.flavor_type) self.assertEqual(128, f.ram) self.assertEqual(0, f.disk) self.assertEqual(0, f.ephemeral) self.assertTrue(f.is_public) def test_get_flavor_details_diablo(self): f = self.cs.flavors.get(3) self.assert_request_id(f, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('GET', '/flavors/3') self.assertIsInstance(f, self.flavor_type) self.assertEqual(256, f.ram) self.assertEqual(10, f.disk) self.assertEqual('N/A', f.ephemeral) self.assertEqual('N/A', f.is_public) def test_find(self): f = self.cs.flavors.find(ram=256) self.assert_request_id(f, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('GET', '/flavors/detail') self.assertEqual('256 MiB Server', f.name) f = self.cs.flavors.find(disk=0) self.assert_request_id(f, fakes.FAKE_REQUEST_ID_LIST) self.assertEqual('128 MiB Server', f.name) self.assertRaises(exceptions.NotFound, self.cs.flavors.find, disk=12345) @staticmethod def _create_body(name, ram, vcpus, disk, ephemeral, id, swap, rxtx_factor, is_public): return { "flavor": { "name": name, "ram": ram, "vcpus": vcpus, "disk": disk, "OS-FLV-EXT-DATA:ephemeral": ephemeral, "id": id, "swap": swap, "rxtx_factor": rxtx_factor, "os-flavor-access:is_public": is_public, } } def test_create(self): f = self.cs.flavors.create("flavorcreate", 512, 1, 10, 1234, ephemeral=10, is_public=False) self.assert_request_id(f, fakes.FAKE_REQUEST_ID_LIST) body = self._create_body("flavorcreate", 512, 1, 10, 10, 1234, 0, 1.0, False) self.cs.assert_called('POST', '/flavors', body) self.assertIsInstance(f, self.flavor_type) def test_create_with_id_as_string(self): flavor_id = 'foobar' f = self.cs.flavors.create("flavorcreate", 512, 1, 10, flavor_id, ephemeral=10, is_public=False) self.assert_request_id(f, fakes.FAKE_REQUEST_ID_LIST) body = self._create_body("flavorcreate", 512, 1, 10, 10, flavor_id, 0, 1.0, False) self.cs.assert_called('POST', '/flavors', body) self.assertIsInstance(f, self.flavor_type) def test_create_ephemeral_ispublic_defaults(self): f = self.cs.flavors.create("flavorcreate", 512, 1, 10, 1234) self.assert_request_id(f, fakes.FAKE_REQUEST_ID_LIST) body = self._create_body("flavorcreate", 512, 1, 10, 0, 1234, 0, 1.0, True) self.cs.assert_called('POST', '/flavors', body) self.assertIsInstance(f, self.flavor_type) def test_invalid_parameters_create(self): self.assertRaises(exceptions.CommandError, self.cs.flavors.create, "flavorcreate", "invalid", 1, 10, 1234, swap=0, ephemeral=0, rxtx_factor=1.0, is_public=True) self.assertRaises(exceptions.CommandError, self.cs.flavors.create, "flavorcreate", 512, "invalid", 10, 1234, swap=0, ephemeral=0, rxtx_factor=1.0, is_public=True) self.assertRaises(exceptions.CommandError, self.cs.flavors.create, "flavorcreate", 512, 1, "invalid", 1234, swap=0, ephemeral=0, rxtx_factor=1.0, is_public=True) self.assertRaises(exceptions.CommandError, self.cs.flavors.create, "flavorcreate", 512, 1, 10, 1234, swap="invalid", ephemeral=0, rxtx_factor=1.0, is_public=True) self.assertRaises(exceptions.CommandError, self.cs.flavors.create, "flavorcreate", 512, 1, 10, 1234, swap=0, ephemeral="invalid", rxtx_factor=1.0, is_public=True) self.assertRaises(exceptions.CommandError, self.cs.flavors.create, "flavorcreate", 512, 1, 10, 1234, swap=0, ephemeral=0, rxtx_factor="invalid", is_public=True) self.assertRaises(exceptions.CommandError, self.cs.flavors.create, "flavorcreate", 512, 1, 10, 1234, swap=0, ephemeral=0, rxtx_factor=1.0, is_public='invalid') def test_delete(self): ret = self.cs.flavors.delete("flavordelete") self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('DELETE', '/flavors/flavordelete') def test_delete_with_flavor_instance(self): f = self.cs.flavors.get(2) ret = self.cs.flavors.delete(f) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('DELETE', '/flavors/2') def test_delete_with_flavor_instance_method(self): f = self.cs.flavors.get(2) ret = f.delete() self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('DELETE', '/flavors/2') def test_set_keys(self): f = self.cs.flavors.get(1) fk = f.set_keys({'k1': 'v1'}) self.assert_request_id(fk, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('POST', '/flavors/1/os-extra_specs', {"extra_specs": {'k1': 'v1'}}) def test_set_with_valid_keys(self): valid_keys = ['key4', 'month.price', 'I-Am:AK-ey.44-', 'key with spaces and _'] f = self.cs.flavors.get(4) for key in valid_keys: fk = f.set_keys({key: 'v4'}) self.assert_request_id(fk, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('POST', '/flavors/4/os-extra_specs', {"extra_specs": {key: 'v4'}}) def test_set_with_invalid_keys(self): invalid_keys = ['/1', '?1', '%1', '<', '>'] f = self.cs.flavors.get(1) for key in invalid_keys: self.assertRaises(exceptions.CommandError, f.set_keys, {key: 'v1'}) @mock.patch.object(flavors.FlavorManager, '_delete') def test_unset_keys(self, mock_delete): f = self.cs.flavors.get(1) keys = ['k1', 'k2'] mock_delete.return_value = base.TupleWithMeta( (), fakes.FAKE_REQUEST_ID_LIST) fu = f.unset_keys(keys) self.assert_request_id(fu, fakes.FAKE_REQUEST_ID_LIST) mock_delete.assert_has_calls([ mock.call("/flavors/1/os-extra_specs/k1"), mock.call("/flavors/1/os-extra_specs/k2") ]) class FlavorsTest_v2_55(utils.TestCase): """Tests creating/showing/updating a flavor with a description.""" def setUp(self): super(FlavorsTest_v2_55, self).setUp() self.cs = fakes.FakeClient(api_versions.APIVersion('2.55')) def test_list_flavors(self): fl = self.cs.flavors.list() self.cs.assert_called('GET', '/flavors/detail') for flavor in fl: self.assertTrue(hasattr(flavor, 'description'), "%s does not have a description set." % flavor) def test_list_flavors_undetailed(self): fl = self.cs.flavors.list(detailed=False) self.cs.assert_called('GET', '/flavors') for flavor in fl: self.assertTrue(hasattr(flavor, 'description'), "%s does not have a description set." % flavor) def test_get_flavor_details(self): f = self.cs.flavors.get('with-description') self.cs.assert_called('GET', '/flavors/with-description') self.assertEqual('test description', f.description) def test_create(self): self.cs.flavors.create( 'with-description', 512, 1, 10, 'with-description', ephemeral=10, is_public=False, description='test description') body = FlavorsTest._create_body( "with-description", 512, 1, 10, 10, 'with-description', 0, 1.0, False) body['flavor']['description'] = 'test description' self.cs.assert_called('POST', '/flavors', body) def test_create_bad_version(self): """Tests trying to create a flavor with a description before 2.55.""" self.cs.api_version = api_versions.APIVersion('2.54') self.assertRaises(exceptions.UnsupportedAttribute, self.cs.flavors.create, 'with-description', 512, 1, 10, 'with-description', description='test description') def test_update(self): updated_flavor = self.cs.flavors.update( 'with-description', 'new description') body = { 'flavor': { 'description': 'new description' } } self.cs.assert_called('PUT', '/flavors/with-description', body) self.assertEqual('new description', updated_flavor.description) def test_update_bad_version(self): """Tests trying to update a flavor with a description before 2.55.""" self.cs.api_version = api_versions.APIVersion('2.54') self.assertRaises(exceptions.VersionNotFoundForAPIMethod, self.cs.flavors.update, 'foo', 'bar') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/v2/test_hypervisors.py0000664000175000017500000003062600000000000026142 0ustar00zuulzuul00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from novaclient import api_versions from novaclient import exceptions from novaclient.tests.unit.fixture_data import client from novaclient.tests.unit.fixture_data import hypervisors as data from novaclient.tests.unit import utils from novaclient.tests.unit.v2 import fakes class HypervisorsTest(utils.FixturedTestCase): client_fixture_class = client.V1 data_fixture_class = data.V1 def compare_to_expected(self, expected, hyper): for key, value in expected.items(): self.assertEqual(getattr(hyper, key), value) def test_hypervisor_index(self): expected = [ dict(id=self.data_fixture.hyper_id_1, hypervisor_hostname='hyper1'), dict(id=self.data_fixture.hyper_id_2, hypervisor_hostname='hyper2')] result = self.cs.hypervisors.list(False) self.assert_request_id(result, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/os-hypervisors') for idx, hyper in enumerate(result): self.compare_to_expected(expected[idx], hyper) def test_hypervisor_detail(self): expected = [ dict(id=self.data_fixture.hyper_id_1, service=dict(id=self.data_fixture.service_id_1, host='compute1'), vcpus=4, memory_mb=10 * 1024, local_gb=250, vcpus_used=2, memory_mb_used=5 * 1024, local_gb_used=125, hypervisor_type="xen", hypervisor_version=3, hypervisor_hostname="hyper1", free_ram_mb=5 * 1024, free_disk_gb=125, current_workload=2, running_vms=2, cpu_info='cpu_info', disk_available_least=100, state='up', status='enabled'), dict(id=self.data_fixture.hyper_id_2, service=dict(id=self.data_fixture.service_id_2, host="compute2"), vcpus=4, memory_mb=10 * 1024, local_gb=250, vcpus_used=2, memory_mb_used=5 * 1024, local_gb_used=125, hypervisor_type="xen", hypervisor_version=3, hypervisor_hostname="hyper2", free_ram_mb=5 * 1024, free_disk_gb=125, current_workload=2, running_vms=2, cpu_info='cpu_info', disk_available_least=100, state='up', status='enabled')] if self.cs.api_version >= api_versions.APIVersion('2.88'): for hypervisor in expected: del hypervisor['current_workload'] del hypervisor['disk_available_least'] del hypervisor['free_ram_mb'] del hypervisor['free_disk_gb'] del hypervisor['local_gb'] del hypervisor['local_gb_used'] del hypervisor['memory_mb'] del hypervisor['memory_mb_used'] del hypervisor['running_vms'] del hypervisor['vcpus'] del hypervisor['vcpus_used'] hypervisor['uptime'] = 'fake uptime' result = self.cs.hypervisors.list() self.assert_request_id(result, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/os-hypervisors/detail') for idx, hyper in enumerate(result): self.compare_to_expected(expected[idx], hyper) def test_hypervisor_search(self): expected = [ dict(id=self.data_fixture.hyper_id_1, hypervisor_hostname='hyper1', state='up', status='enabled'), dict(id=self.data_fixture.hyper_id_2, hypervisor_hostname='hyper2', state='up', status='enabled')] result = self.cs.hypervisors.search('hyper') self.assert_request_id(result, fakes.FAKE_REQUEST_ID_LIST) if self.cs.api_version >= api_versions.APIVersion('2.53'): self.assert_called( 'GET', '/os-hypervisors?hypervisor_hostname_pattern=hyper') else: self.assert_called('GET', '/os-hypervisors/hyper/search') for idx, hyper in enumerate(result): self.compare_to_expected(expected[idx], hyper) def test_hypervisor_search_unicode(self): hypervisor_match = '\\u5de5\\u4f5c' if self.cs.api_version >= api_versions.APIVersion('2.53'): self.assertRaises(exceptions.BadRequest, self.cs.hypervisors.search, hypervisor_match) else: self.assertRaises(exceptions.NotFound, self.cs.hypervisors.search, hypervisor_match) def test_hypervisor_search_detailed(self): # detailed=True is not supported before 2.53 ex = self.assertRaises(exceptions.UnsupportedVersion, self.cs.hypervisors.search, 'hyper', detailed=True) self.assertIn('Parameter "detailed" requires API version 2.53 or ' 'greater.', str(ex)) def test_hypervisor_servers(self): expected = [ dict(id=self.data_fixture.hyper_id_1, hypervisor_hostname='hyper1', state='up', status='enabled', servers=[ dict(name='inst1', uuid='uuid1'), dict(name='inst2', uuid='uuid2')]), dict(id=self.data_fixture.hyper_id_2, hypervisor_hostname='hyper2', state='up', status='enabled', servers=[ dict(name='inst3', uuid='uuid3'), dict(name='inst4', uuid='uuid4')]), ] result = self.cs.hypervisors.search('hyper', True) self.assert_request_id(result, fakes.FAKE_REQUEST_ID_LIST) if self.cs.api_version >= api_versions.APIVersion('2.53'): self.assert_called( 'GET', '/os-hypervisors?hypervisor_hostname_pattern=hyper&' 'with_servers=True') else: self.assert_called('GET', '/os-hypervisors/hyper/servers') for idx, hyper in enumerate(result): self.compare_to_expected(expected[idx], hyper) def test_hypervisor_get(self): expected = dict( id=self.data_fixture.hyper_id_1, service=dict(id=self.data_fixture.service_id_1, host='compute1'), vcpus=4, memory_mb=10 * 1024, local_gb=250, vcpus_used=2, memory_mb_used=5 * 1024, local_gb_used=125, hypervisor_type="xen", hypervisor_version=3, hypervisor_hostname="hyper1", free_ram_mb=5 * 1024, free_disk_gb=125, current_workload=2, running_vms=2, cpu_info='cpu_info', disk_available_least=100, state='up', status='enabled') if self.cs.api_version >= api_versions.APIVersion('2.88'): del expected['current_workload'] del expected['disk_available_least'] del expected['free_ram_mb'] del expected['free_disk_gb'] del expected['local_gb'] del expected['local_gb_used'] del expected['memory_mb'] del expected['memory_mb_used'] del expected['running_vms'] del expected['vcpus'] del expected['vcpus_used'] expected['uptime'] = 'fake uptime' result = self.cs.hypervisors.get(self.data_fixture.hyper_id_1) self.assert_request_id(result, fakes.FAKE_REQUEST_ID_LIST) self.assert_called( 'GET', '/os-hypervisors/%s' % self.data_fixture.hyper_id_1) self.compare_to_expected(expected, result) def test_hypervisor_uptime(self): expected = dict( id=self.data_fixture.hyper_id_1, hypervisor_hostname="hyper1", uptime="fake uptime", state='up', status='enabled') result = self.cs.hypervisors.uptime(self.data_fixture.hyper_id_1) self.assert_request_id(result, fakes.FAKE_REQUEST_ID_LIST) self.assert_called( 'GET', '/os-hypervisors/%s/uptime' % self.data_fixture.hyper_id_1) self.compare_to_expected(expected, result) def test_hypervisor_statistics(self): expected = dict( count=2, vcpus=8, memory_mb=20 * 1024, local_gb=500, vcpus_used=4, memory_mb_used=10 * 1024, local_gb_used=250, free_ram_mb=10 * 1024, free_disk_gb=250, current_workload=4, running_vms=4, disk_available_least=200, ) result = self.cs.hypervisors.statistics() self.assert_request_id(result, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/os-hypervisors/statistics') self.compare_to_expected(expected, result) # Test for Bug #1370415, the line below used to raise AttributeError self.assertEqual("", result.__repr__()) class HypervisorsV233Test(HypervisorsTest): def setUp(self): super(HypervisorsV233Test, self).setUp() self.cs.api_version = api_versions.APIVersion("2.33") def test_use_limit_marker_params(self): params = {'limit': '10', 'marker': 'fake-marker'} self.cs.hypervisors.list(**params) for k, v in params.items(): self.assertEqual([v], self.requests_mock.last_request.qs[k]) class HypervisorsV253Test(HypervisorsV233Test): """Tests the os-hypervisors 2.53 API bindings.""" data_fixture_class = data.V253 def setUp(self): super(HypervisorsV253Test, self).setUp() self.cs.api_version = api_versions.APIVersion("2.53") def test_hypervisor_search_detailed(self): expected = [ dict(id=self.data_fixture.hyper_id_1, state='up', status='enabled', hypervisor_hostname='hyper1'), dict(id=self.data_fixture.hyper_id_2, state='up', status='enabled', hypervisor_hostname='hyper2')] result = self.cs.hypervisors.search('hyper', detailed=True) self.assert_request_id(result, fakes.FAKE_REQUEST_ID_LIST) self.assert_called( 'GET', '/os-hypervisors/detail?hypervisor_hostname_pattern=hyper') for idx, hyper in enumerate(result): self.compare_to_expected(expected[idx], hyper) class HypervisorsV288Test(HypervisorsV253Test): data_fixture_class = data.V288 def setUp(self): super().setUp() self.cs.api_version = api_versions.APIVersion('2.88') def test_hypervisor_uptime(self): expected = { 'id': self.data_fixture.hyper_id_1, 'hypervisor_hostname': 'hyper1', 'uptime': 'fake uptime', 'state': 'up', 'status': 'enabled', } result = self.cs.hypervisors.uptime(self.data_fixture.hyper_id_1) self.assert_request_id(result, fakes.FAKE_REQUEST_ID_LIST) self.assert_called( 'GET', '/os-hypervisors/%s' % self.data_fixture.hyper_id_1) self.compare_to_expected(expected, result) def test_hypervisor_statistics(self): exc = self.assertRaises( exceptions.UnsupportedVersion, self.cs.hypervisor_stats.statistics) self.assertIn( "The 'statistics' API is removed in API version 2.88 or later.", str(exc)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/v2/test_images.py0000664000175000017500000000264000000000000025005 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 novaclient.tests.unit.fixture_data import client from novaclient.tests.unit.fixture_data import images as data from novaclient.tests.unit import utils from novaclient.tests.unit.v2 import fakes from novaclient.v2 import images class ImagesTest(utils.FixturedTestCase): client_fixture_class = client.V1 data_fixture_class = data.V1 @mock.patch('novaclient.base.Manager.alternate_service_type') def test_list_images(self, mock_alternate_service_type): il = self.cs.glance.list() self.assert_request_id(il, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/v2/images') for i in il: self.assertIsInstance(i, images.Image) self.assertEqual(2, len(il)) mock_alternate_service_type.assert_called_once_with( 'image', allowed_types=('image',)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/v2/test_instance_actions.py0000664000175000017500000000641100000000000027064 0ustar00zuulzuul00000000000000# Copyright 2013 Rackspace Hosting # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from novaclient import api_versions from novaclient.tests.unit import utils from novaclient.tests.unit.v2 import fakes from novaclient.v2 import instance_action class InstanceActionExtensionTests(utils.TestCase): def setUp(self): super(InstanceActionExtensionTests, self).setUp() self.cs = fakes.FakeClient(api_versions.APIVersion("2.1")) def test_list_instance_actions(self): server_uuid = '1234' ial = self.cs.instance_action.list(server_uuid) self.assert_request_id(ial, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called( 'GET', '/servers/%s/os-instance-actions' % server_uuid) def test_get_instance_action(self): server_uuid = '1234' request_id = 'req-abcde12345' ia = self.cs.instance_action.get(server_uuid, request_id) self.assert_request_id(ia, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called( 'GET', '/servers/%s/os-instance-actions/%s' % (server_uuid, request_id)) class InstanceActionExtensionV258Tests(InstanceActionExtensionTests): def setUp(self): super(InstanceActionExtensionV258Tests, self).setUp() self.cs.api_version = api_versions.APIVersion("2.58") def test_list_instance_actions_with_limit_marker_params(self): server_uuid = '1234' marker = '12140183-c814-4ddf-8453-6df43028aa67' ias = self.cs.instance_action.list( server_uuid, marker=marker, limit=10, changes_since='2016-02-29T06:23:22') self.assert_request_id(ias, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called( 'GET', '/servers/%s/os-instance-actions?changes-since=%s&limit=10&' 'marker=%s' % (server_uuid, '2016-02-29T06%3A23%3A22', marker)) for ia in ias: self.assertIsInstance(ia, instance_action.InstanceAction) class InstanceActionExtensionV266Tests(InstanceActionExtensionV258Tests): def setUp(self): super(InstanceActionExtensionV266Tests, self).setUp() self.cs.api_version = api_versions.APIVersion("2.66") def test_list_instance_actions_with_changes_before(self): server_uuid = '1234' ias = self.cs.instance_action.list( server_uuid, marker=None, limit=None, changes_since=None, changes_before='2016-02-29T06:23:22') self.assert_request_id(ias, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called( 'GET', '/servers/%s/os-instance-actions?changes-before=%s' % (server_uuid, '2016-02-29T06%3A23%3A22')) for ia in ias: self.assertIsInstance(ia, instance_action.InstanceAction) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/v2/test_instance_usage_audit_log.py0000664000175000017500000000342100000000000030555 0ustar00zuulzuul00000000000000# Copyright 2013 Rackspace Hosting # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from novaclient import api_versions from novaclient import exceptions from novaclient.tests.unit import utils from novaclient.tests.unit.v2 import fakes class InstanceUsageAuditLogTests(utils.TestCase): def setUp(self): super(InstanceUsageAuditLogTests, self).setUp() self.cs = fakes.FakeClient(api_versions.APIVersion("2.1")) def test_instance_usage_audit_log(self): audit_log = self.cs.instance_usage_audit_log.get() self.assert_request_id(audit_log, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('GET', '/os-instance_usage_audit_log') def test_instance_usage_audit_log_with_before(self): audit_log = self.cs.instance_usage_audit_log.get( before='2016-12-10 13:59:59.999999') self.assert_request_id(audit_log, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called( 'GET', '/os-instance_usage_audit_log/2016-12-10%2013%3A59%3A59.999999') def test_instance_usage_audit_log_with_before_unicode(self): before = '\\u5de5\\u4f5c' self.assertRaises(exceptions.BadRequest, self.cs.instance_usage_audit_log.get, before) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/v2/test_keypairs.py0000664000175000017500000001411200000000000025364 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 novaclient import api_versions from novaclient.tests.unit.fixture_data import client from novaclient.tests.unit.fixture_data import keypairs as data from novaclient.tests.unit import utils from novaclient.tests.unit.v2 import fakes from novaclient.v2 import keypairs class KeypairsTest(utils.FixturedTestCase): client_fixture_class = client.V1 data_fixture_class = data.V1 def setUp(self): super(KeypairsTest, self).setUp() self.keypair_type = self._get_keypair_type() self.keypair_prefix = self._get_keypair_prefix() def _get_keypair_type(self): return keypairs.Keypair def _get_keypair_prefix(self): return keypairs.KeypairManager.keypair_prefix def test_get_keypair(self): kp = self.cs.keypairs.get('test') self.assert_request_id(kp, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/%s/test' % self.keypair_prefix) self.assertIsInstance(kp, keypairs.Keypair) self.assertEqual('test', kp.name) def test_list_keypairs(self): kps = self.cs.keypairs.list() self.assert_request_id(kps, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/%s' % self.keypair_prefix) for kp in kps: self.assertIsInstance(kp, keypairs.Keypair) def test_delete_keypair(self): kp = self.cs.keypairs.list()[0] ret = kp.delete() self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('DELETE', '/%s/test' % self.keypair_prefix) ret = self.cs.keypairs.delete('test') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('DELETE', '/%s/test' % self.keypair_prefix) ret = self.cs.keypairs.delete(kp) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('DELETE', '/%s/test' % self.keypair_prefix) class KeypairsV2TestCase(KeypairsTest): def setUp(self): super(KeypairsV2TestCase, self).setUp() self.cs.api_version = api_versions.APIVersion("2.0") def test_create_keypair(self): name = "foo" kp = self.cs.keypairs.create(name) self.assert_request_id(kp, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/%s' % self.keypair_prefix, body={'keypair': {'name': name}}) self.assertIsInstance(kp, keypairs.Keypair) def test_import_keypair(self): name = "foo" pub_key = "fake-public-key" kp = self.cs.keypairs.create(name, pub_key) self.assert_request_id(kp, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/%s' % self.keypair_prefix, body={'keypair': {'name': name, 'public_key': pub_key}}) self.assertIsInstance(kp, keypairs.Keypair) class KeypairsV22TestCase(KeypairsTest): def setUp(self): super(KeypairsV22TestCase, self).setUp() self.cs.api_version = api_versions.APIVersion("2.2") def test_create_keypair(self): name = "foo" key_type = "some_type" kp = self.cs.keypairs.create(name, key_type=key_type) self.assert_request_id(kp, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/%s' % self.keypair_prefix, body={'keypair': {'name': name, 'type': key_type}}) self.assertIsInstance(kp, keypairs.Keypair) def test_import_keypair(self): name = "foo" pub_key = "fake-public-key" kp = self.cs.keypairs.create(name, pub_key) self.assert_request_id(kp, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/%s' % self.keypair_prefix, body={'keypair': {'name': name, 'public_key': pub_key, 'type': 'ssh'}}) self.assertIsInstance(kp, keypairs.Keypair) class KeypairsV35TestCase(KeypairsTest): def setUp(self): super(KeypairsV35TestCase, self).setUp() self.cs.api_version = api_versions.APIVersion("2.35") def test_list_keypairs(self): kps = self.cs.keypairs.list(user_id='test_user', marker='test_kp', limit=3) self.assert_request_id(kps, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/%s?limit=3&marker=test_kp&user_id=test_user' % self.keypair_prefix) for kp in kps: self.assertIsInstance(kp, keypairs.Keypair) class KeypairsV92TestCase(KeypairsTest): def setUp(self): super(KeypairsV92TestCase, self).setUp() self.cs.api_version = api_versions.APIVersion("2.92") def test_create_keypair(self): name = "foo" key_type = "some_type" public_key = "fake-public-key" kp = self.cs.keypairs.create(name, public_key=public_key, key_type=key_type) self.assert_request_id(kp, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/%s' % self.keypair_prefix, body={'keypair': {'name': name, 'public_key': public_key, 'type': key_type}}) self.assertIsInstance(kp, keypairs.Keypair) def test_create_keypair_without_pubkey(self): name = "foo" key_type = "some_type" self.assertRaises(TypeError, self.cs.keypairs.create, name, key_type=key_type) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/v2/test_limits.py0000664000175000017500000001007300000000000025040 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 novaclient import api_versions from novaclient.tests.unit.fixture_data import client from novaclient.tests.unit.fixture_data import limits as data from novaclient.tests.unit import utils from novaclient.tests.unit.v2 import fakes from novaclient.v2 import limits class LimitsTest(utils.FixturedTestCase): client_fixture_class = client.V1 data_fixture_class = data.Fixture supports_image_meta = True # 2.39 deprecates maxImageMeta supports_personality = True # 2.57 deprecates maxPersonality* def test_get_limits(self): obj = self.cs.limits.get() self.assert_request_id(obj, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/limits') self.assertIsInstance(obj, limits.Limits) def test_get_limits_for_a_tenant(self): obj = self.cs.limits.get(tenant_id=1234) self.assert_request_id(obj, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/limits?tenant_id=1234') self.assertIsInstance(obj, limits.Limits) def test_absolute_limits_reserved(self): obj = self.cs.limits.get(reserved=True) self.assert_request_id(obj, fakes.FAKE_REQUEST_ID_LIST) expected = [ limits.AbsoluteLimit("maxTotalRAMSize", 51200), limits.AbsoluteLimit("maxServerMeta", 5) ] if self.supports_image_meta: expected.append(limits.AbsoluteLimit("maxImageMeta", 5)) if self.supports_personality: expected.extend([ limits.AbsoluteLimit("maxPersonality", 5), limits.AbsoluteLimit("maxPersonalitySize", 10240)]) self.assert_called('GET', '/limits?reserved=1') abs_limits = list(obj.absolute) self.assertEqual(len(abs_limits), len(expected)) for limit in abs_limits: self.assertIn(limit, expected) def test_rate_absolute_limits(self): obj = self.cs.limits.get() self.assert_request_id(obj, fakes.FAKE_REQUEST_ID_LIST) expected = ( limits.RateLimit('POST', '*', '.*', 10, 2, 'MINUTE', '2011-12-15T22:42:45Z'), limits.RateLimit('PUT', '*', '.*', 10, 2, 'MINUTE', '2011-12-15T22:42:45Z'), limits.RateLimit('DELETE', '*', '.*', 100, 100, 'MINUTE', '2011-12-15T22:42:45Z'), limits.RateLimit('POST', '*/servers', '^/servers', 25, 24, 'DAY', '2011-12-15T22:42:45Z'), ) rate_limits = list(obj.rate) self.assertEqual(len(rate_limits), len(expected)) for limit in rate_limits: self.assertIn(limit, expected) expected = [ limits.AbsoluteLimit("maxTotalRAMSize", 51200), limits.AbsoluteLimit("maxServerMeta", 5) ] if self.supports_image_meta: expected.append(limits.AbsoluteLimit("maxImageMeta", 5)) if self.supports_personality: expected.extend([ limits.AbsoluteLimit("maxPersonality", 5), limits.AbsoluteLimit("maxPersonalitySize", 10240)]) abs_limits = list(obj.absolute) self.assertEqual(len(abs_limits), len(expected)) for limit in abs_limits: self.assertIn(limit, expected) class LimitsTest2_57(LimitsTest): data_fixture_class = data.Fixture2_57 supports_image_meta = False supports_personality = False def setUp(self): super(LimitsTest2_57, self).setUp() self.cs.api_version = api_versions.APIVersion('2.57') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/v2/test_migrations.py0000664000175000017500000001560700000000000025723 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 novaclient import api_versions from novaclient.tests.unit import utils from novaclient.tests.unit.v2 import fakes from novaclient.v2 import migrations class MigrationsTest(utils.TestCase): def setUp(self): super(MigrationsTest, self).setUp() self.cs = fakes.FakeClient(api_versions.APIVersion("2.1")) def test_list_migrations(self): ml = self.cs.migrations.list() self.assert_request_id(ml, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('GET', '/os-migrations') for m in ml: self.assertIsInstance(m, migrations.Migration) self.assertRaises(AttributeError, getattr, m, "migration_type") self.assertRaises(AttributeError, getattr, m, "uuid") def test_list_migrations_with_filters(self): ml = self.cs.migrations.list('host1', 'finished') self.assert_request_id(ml, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called( 'GET', '/os-migrations?host=host1&status=finished') for m in ml: self.assertIsInstance(m, migrations.Migration) def test_list_migrations_with_instance_uuid_filter(self): ml = self.cs.migrations.list('host1', 'finished', 'instance_id_456') self.assert_request_id(ml, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called( 'GET', ('/os-migrations?host=host1&' 'instance_uuid=instance_id_456&status=finished')) self.assertEqual(1, len(ml)) self.assertEqual('instance_id_456', ml[0].instance_uuid) class MigrationsV223Test(MigrationsTest): def setUp(self): super(MigrationsV223Test, self).setUp() self.cs.api_version = api_versions.APIVersion("2.23") def test_list_migrations(self): ml = self.cs.migrations.list() self.assert_request_id(ml, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('GET', '/os-migrations') for m in ml: self.assertIsInstance(m, migrations.Migration) self.assertEqual(m.migration_type, 'live-migration') self.assertRaises(AttributeError, getattr, m, "uuid") class MigrationsV259Test(MigrationsV223Test): def setUp(self): super(MigrationsV259Test, self).setUp() self.cs.api_version = api_versions.APIVersion("2.59") def test_list_migrations(self): ml = self.cs.migrations.list() self.assert_request_id(ml, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('GET', '/os-migrations') for m in ml: self.assertIsInstance(m, migrations.Migration) self.assertEqual(m.migration_type, 'live-migration') self.assertTrue(hasattr(m, 'uuid')) def test_list_migrations_with_limit_marker_params(self): marker = '12140183-c814-4ddf-8453-6df43028aa67' params = {'limit': 10, 'marker': marker, 'changes_since': '2012-02-29T06:23:22'} ms = self.cs.migrations.list(**params) self.assert_request_id(ms, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('GET', '/os-migrations?' 'changes-since=%s&limit=10&marker=%s' % ('2012-02-29T06%3A23%3A22', marker)) for m in ms: self.assertIsInstance(m, migrations.Migration) class MigrationsV266Test(MigrationsV259Test): def setUp(self): super(MigrationsV266Test, self).setUp() self.cs.api_version = api_versions.APIVersion("2.66") def test_list_migrations_with_changes_before(self): params = {'changes_before': '2012-02-29T06:23:22'} ms = self.cs.migrations.list(**params) self.assert_request_id(ms, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('GET', '/os-migrations?' 'changes-before=%s' % '2012-02-29T06%3A23%3A22') for m in ms: self.assertIsInstance(m, migrations.Migration) class MigrationsV280Test(MigrationsV266Test): def setUp(self): super(MigrationsV280Test, self).setUp() self.cs.api_version = api_versions.APIVersion("2.80") def test_list_migrations_with_user_id(self): user_id = '13cc0930d27c4be0acc14d7c47a3e1f7' params = {'user_id': user_id} ms = self.cs.migrations.list(**params) self.assert_request_id(ms, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('GET', '/os-migrations?user_id=%s' % user_id) for m in ms: self.assertIsInstance(m, migrations.Migration) def test_list_migrations_with_project_id(self): project_id = 'b59c18e5fa284fd384987c5cb25a1853' params = {'project_id': project_id} ms = self.cs.migrations.list(**params) self.assert_request_id(ms, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('GET', '/os-migrations?project_id=%s' % project_id) for m in ms: self.assertIsInstance(m, migrations.Migration) def test_list_migrations_with_user_and_project_id(self): user_id = '13cc0930d27c4be0acc14d7c47a3e1f7' project_id = 'b59c18e5fa284fd384987c5cb25a1853' params = {'user_id': user_id, 'project_id': project_id} ms = self.cs.migrations.list(**params) self.assert_request_id(ms, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('GET', '/os-migrations?project_id=%s&user_id=%s' % (project_id, user_id)) for m in ms: self.assertIsInstance(m, migrations.Migration) def test_list_migrations_with_user_id_pre_v280(self): self.cs.api_version = api_versions.APIVersion('2.79') user_id = '13cc0930d27c4be0acc14d7c47a3e1f7' ex = self.assertRaises(TypeError, self.cs.migrations.list, user_id=user_id) self.assertIn("unexpected keyword argument 'user_id'", str(ex)) def test_list_migrations_with_project_id_pre_v280(self): self.cs.api_version = api_versions.APIVersion('2.79') project_id = '23cc0930d27c4be0acc14d7c47a3e1f7' ex = self.assertRaises(TypeError, self.cs.migrations.list, project_id=project_id) self.assertIn("unexpected keyword argument 'project_id'", str(ex)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/v2/test_quota_classes.py0000664000175000017500000001227400000000000026412 0ustar00zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from novaclient import api_versions from novaclient.tests.unit import utils from novaclient.tests.unit.v2 import fakes class QuotaClassSetsTest(utils.TestCase): def setUp(self): super(QuotaClassSetsTest, self).setUp() self.cs = fakes.FakeClient(api_versions.APIVersion("2.0")) def test_class_quotas_get(self): class_name = 'test' q = self.cs.quota_classes.get(class_name) self.assert_request_id(q, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('GET', '/os-quota-class-sets/%s' % class_name) return q def test_update_quota(self): q = self.cs.quota_classes.get('test') self.assert_request_id(q, fakes.FAKE_REQUEST_ID_LIST) q.update(cores=2) self.cs.assert_called('PUT', '/os-quota-class-sets/test') return q def test_refresh_quota(self): q = self.cs.quota_classes.get('test') q2 = self.cs.quota_classes.get('test') self.assertEqual(q.cores, q2.cores) q2.cores = 0 self.assertNotEqual(q.cores, q2.cores) q2.get() self.assertEqual(q.cores, q2.cores) class QuotaClassSetsTest2_50(QuotaClassSetsTest): """Tests the quota classes API binding using the 2.50 microversion.""" api_version = '2.50' invalid_resources = ['floating_ips', 'fixed_ips', 'networks', 'security_groups', 'security_group_rules'] def setUp(self): super(QuotaClassSetsTest2_50, self).setUp() self.cs = fakes.FakeClient(api_versions.APIVersion(self.api_version)) def test_class_quotas_get(self): """Tests that network-related resources aren't in a 2.50 response and server group related resources are in the response. """ q = super(QuotaClassSetsTest2_50, self).test_class_quotas_get() for invalid_resource in self.invalid_resources: self.assertFalse(hasattr(q, invalid_resource), '%s should not be in %s' % (invalid_resource, q)) # Also make sure server_groups and server_group_members are in the # response. for valid_resource in ('server_groups', 'server_group_members'): self.assertTrue(hasattr(q, valid_resource), '%s should be in %s' % (invalid_resource, q)) def test_update_quota(self): """Tests that network-related resources aren't in a 2.50 response and server group related resources are in the response. """ q = super(QuotaClassSetsTest2_50, self).test_update_quota() for invalid_resource in self.invalid_resources: self.assertFalse(hasattr(q, invalid_resource), '%s should not be in %s' % (invalid_resource, q)) # Also make sure server_groups and server_group_members are in the # response. for valid_resource in ('server_groups', 'server_group_members'): self.assertTrue(hasattr(q, valid_resource), '%s should be in %s' % (invalid_resource, q)) def test_update_quota_invalid_resources(self): """Tests trying to update quota class values for invalid resources. This will fail with TypeError because the network-related resource kwargs aren't defined. """ q = self.cs.quota_classes.get('test') self.assertRaises(TypeError, q.update, floating_ips=1) self.assertRaises(TypeError, q.update, fixed_ips=1) self.assertRaises(TypeError, q.update, security_groups=1) self.assertRaises(TypeError, q.update, security_group_rules=1) self.assertRaises(TypeError, q.update, networks=1) return q class QuotaClassSetsTest2_57(QuotaClassSetsTest2_50): """Tests the quota classes API binding using the 2.57 microversion.""" api_version = '2.57' def setUp(self): super(QuotaClassSetsTest2_57, self).setUp() self.invalid_resources.extend(['injected_files', 'injected_file_content_bytes', 'injected_file_path_bytes']) def test_update_quota_invalid_resources(self): """Tests trying to update quota class values for invalid resources. This will fail with TypeError because the file-related resource kwargs aren't defined. """ q = super( QuotaClassSetsTest2_57, self).test_update_quota_invalid_resources() self.assertRaises(TypeError, q.update, injected_files=1) self.assertRaises(TypeError, q.update, injected_file_content_bytes=1) self.assertRaises(TypeError, q.update, injected_file_path_bytes=1) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/v2/test_quotas.py0000664000175000017500000001222300000000000025052 0ustar00zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from novaclient import api_versions from novaclient.tests.unit.fixture_data import client from novaclient.tests.unit.fixture_data import quotas as data from novaclient.tests.unit import utils from novaclient.tests.unit.v2 import fakes class QuotaSetsTest(utils.FixturedTestCase): client_fixture_class = client.V1 data_fixture_class = data.V1 def test_tenant_quotas_get(self): tenant_id = 'test' q = self.cs.quotas.get(tenant_id) self.assert_request_id(q, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/os-quota-sets/%s' % tenant_id) return q def test_user_quotas_get(self): tenant_id = 'test' user_id = 'fake_user' q = self.cs.quotas.get(tenant_id, user_id=user_id) self.assert_request_id(q, fakes.FAKE_REQUEST_ID_LIST) url = '/os-quota-sets/%s?user_id=%s' % (tenant_id, user_id) self.assert_called('GET', url) def test_tenant_quotas_get_detail(self): tenant_id = 'test' q = self.cs.quotas.get(tenant_id, detail=True) self.assert_request_id(q, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/os-quota-sets/%s/detail' % tenant_id) def test_user_quotas_get_detail(self): tenant_id = 'test' user_id = 'fake_user' q = self.cs.quotas.get(tenant_id, user_id=user_id, detail=True) self.assert_request_id(q, fakes.FAKE_REQUEST_ID_LIST) url = '/os-quota-sets/%s/detail?user_id=%s' % (tenant_id, user_id) self.assert_called('GET', url) def test_tenant_quotas_defaults(self): tenant_id = '97f4c221bff44578b0300df4ef119353' q = self.cs.quotas.defaults(tenant_id) self.assert_request_id(q, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/os-quota-sets/%s/defaults' % tenant_id) def test_force_update_quota(self): q = self.cs.quotas.get('97f4c221bff44578b0300df4ef119353') qu = q.update(cores=2, force=True) self.assert_request_id(qu, fakes.FAKE_REQUEST_ID_LIST) self.assert_called( 'PUT', '/os-quota-sets/97f4c221bff44578b0300df4ef119353', {'quota_set': {'force': True, 'cores': 2}}) return q def test_quotas_delete(self): tenant_id = 'test' ret = self.cs.quotas.delete(tenant_id) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('DELETE', '/os-quota-sets/%s' % tenant_id) def test_user_quotas_delete(self): tenant_id = 'test' user_id = 'fake_user' ret = self.cs.quotas.delete(tenant_id, user_id=user_id) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) url = '/os-quota-sets/%s?user_id=%s' % (tenant_id, user_id) self.assert_called('DELETE', url) class QuotaSetsTest2_57(QuotaSetsTest): """Tests the quotas API binding using the 2.57 microversion.""" data_fixture_class = data.V2_57 invalid_resources = ['floating_ips', 'fixed_ips', 'networks', 'security_groups', 'security_group_rules', 'injected_files', 'injected_file_content_bytes', 'injected_file_path_bytes'] def setUp(self): super(QuotaSetsTest2_57, self).setUp() self.cs.api_version = api_versions.APIVersion('2.57') def test_tenant_quotas_get(self): q = super(QuotaSetsTest2_57, self).test_tenant_quotas_get() for invalid_resource in self.invalid_resources: self.assertFalse(hasattr(q, invalid_resource), '%s should not be in %s' % (invalid_resource, q)) def test_force_update_quota(self): q = super(QuotaSetsTest2_57, self).test_force_update_quota() for invalid_resource in self.invalid_resources: self.assertFalse(hasattr(q, invalid_resource), '%s should not be in %s' % (invalid_resource, q)) def test_update_quota_invalid_resources(self): """Tests trying to update quota values for invalid resources.""" q = self.cs.quotas.get('test') self.assertRaises(TypeError, q.update, floating_ips=1) self.assertRaises(TypeError, q.update, fixed_ips=1) self.assertRaises(TypeError, q.update, security_groups=1) self.assertRaises(TypeError, q.update, security_group_rules=1) self.assertRaises(TypeError, q.update, networks=1) self.assertRaises(TypeError, q.update, injected_files=1) self.assertRaises(TypeError, q.update, injected_file_content_bytes=1) self.assertRaises(TypeError, q.update, injected_file_path_bytes=1) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/v2/test_server_external_events.py0000664000175000017500000000311000000000000030325 0ustar00zuulzuul00000000000000# Copyright (C) 2014, 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. """ External event triggering for servers, not to be used by users. """ from novaclient import api_versions from novaclient.tests.unit import utils from novaclient.tests.unit.v2 import fakes class ServerExternalEventsTestCase(utils.TestCase): def setUp(self): super(ServerExternalEventsTestCase, self).setUp() self.cs = fakes.FakeClient(api_versions.APIVersion("2.1")) def test_external_event(self): events = [{'server_uuid': 'fake-uuid1', 'name': 'test-event', 'status': 'completed', 'tag': 'tag'}, {'server_uuid': 'fake-uuid2', 'name': 'test-event', 'status': 'completed', 'tag': 'tag'}] result = self.cs.server_external_events.create(events) self.assert_request_id(result, fakes.FAKE_REQUEST_ID_LIST) self.assertEqual(events, result) self.cs.assert_called('POST', '/os-server-external-events') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/v2/test_server_groups.py0000664000175000017500000001411000000000000026440 0ustar00zuulzuul00000000000000# Copyright (c) 2014 VMware, 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 novaclient import api_versions from novaclient import exceptions from novaclient.tests.unit.fixture_data import client from novaclient.tests.unit.fixture_data import server_groups as data from novaclient.tests.unit import utils from novaclient.tests.unit.v2 import fakes from novaclient.v2 import server_groups class ServerGroupsTest(utils.FixturedTestCase): client_fixture_class = client.V1 data_fixture_class = data.Fixture def test_list_server_groups(self): result = self.cs.server_groups.list() self.assert_request_id(result, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/os-server-groups') self.assertEqual(4, len(result)) for server_group in result: self.assertIsInstance(server_group, server_groups.ServerGroup) def test_list_server_groups_with_all_projects(self): result = self.cs.server_groups.list(all_projects=True) self.assert_request_id(result, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/os-server-groups?all_projects=True') self.assertEqual(8, len(result)) for server_group in result: self.assertIsInstance(server_group, server_groups.ServerGroup) def test_list_server_groups_with_limit_and_offset(self): all_groups = self.cs.server_groups.list() result = self.cs.server_groups.list(limit=2, offset=1) self.assert_request_id(result, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/os-server-groups?limit=2&offset=1') self.assertEqual(2, len(result)) for server_group in result: self.assertIsInstance(server_group, server_groups.ServerGroup) self.assertEqual(all_groups[1:3], result) def test_create_server_group(self): kwargs = {'name': 'ig1', 'policies': ['anti-affinity']} server_group = self.cs.server_groups.create(**kwargs) self.assert_request_id(server_group, fakes.FAKE_REQUEST_ID_LIST) body = {'server_group': kwargs} self.assert_called('POST', '/os-server-groups', body) self.assertIsInstance(server_group, server_groups.ServerGroup) def test_get_server_group(self): id = '2cbd51f4-fafe-4cdb-801b-cf913a6f288b' server_group = self.cs.server_groups.get(id) self.assert_request_id(server_group, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/os-server-groups/%s' % id) self.assertIsInstance(server_group, server_groups.ServerGroup) def test_delete_server_group(self): id = '2cbd51f4-fafe-4cdb-801b-cf913a6f288b' ret = self.cs.server_groups.delete(id) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('DELETE', '/os-server-groups/%s' % id) def test_delete_server_group_object(self): id = '2cbd51f4-fafe-4cdb-801b-cf913a6f288b' server_group = self.cs.server_groups.get(id) ret = server_group.delete() self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('DELETE', '/os-server-groups/%s' % id) def test_find_server_groups_by_name(self): expected_name = 'ig1' kwargs = {self.cs.server_groups.resource_class.NAME_ATTR: expected_name} server_group = self.cs.server_groups.find(**kwargs) self.assert_request_id(server_group, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/os-server-groups') self.assertIsInstance(server_group, server_groups.ServerGroup) actual_name = getattr(server_group, self.cs.server_groups.resource_class.NAME_ATTR) self.assertEqual(expected_name, actual_name) def test_find_no_existing_server_groups_by_name(self): expected_name = 'no-exist' kwargs = {self.cs.server_groups.resource_class.NAME_ATTR: expected_name} self.assertRaises(exceptions.NotFound, self.cs.server_groups.find, **kwargs) self.assert_called('GET', '/os-server-groups') class ServerGroupsTestV264(ServerGroupsTest): def setUp(self): super(ServerGroupsTestV264, self).setUp() self.cs.api_version = api_versions.APIVersion("2.64") def test_create_server_group(self): name = 'ig1' policy = 'anti-affinity' server_group = self.cs.server_groups.create(name, policy) self.assert_request_id(server_group, fakes.FAKE_REQUEST_ID_LIST) body = {'server_group': {'name': name, 'policy': policy}} self.assert_called('POST', '/os-server-groups', body) self.assertIsInstance(server_group, server_groups.ServerGroup) def test_create_server_group_with_rules(self): kwargs = {'name': 'ig1', 'policy': 'anti-affinity', 'rules': {'max_server_per_host': 3}} server_group = self.cs.server_groups.create(**kwargs) self.assert_request_id(server_group, fakes.FAKE_REQUEST_ID_LIST) body = { 'server_group': { 'name': 'ig1', 'policy': 'anti-affinity', 'rules': {'max_server_per_host': 3} } } self.assert_called('POST', '/os-server-groups', body) self.assertIsInstance(server_group, server_groups.ServerGroup) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/v2/test_server_migrations.py0000664000175000017500000000655100000000000027307 0ustar00zuulzuul00000000000000# Copyright 2016 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from novaclient import api_versions from novaclient import base from novaclient.tests.unit.fixture_data import client from novaclient.tests.unit.fixture_data import server_migrations as data from novaclient.tests.unit import utils from novaclient.tests.unit.v2 import fakes from novaclient.v2 import server_migrations class ServerMigrationsTest(utils.FixturedTestCase): client_fixture_class = client.V1 data_fixture_class = data.Fixture def setUp(self): super(ServerMigrationsTest, self).setUp() self.cs.api_version = api_versions.APIVersion("2.22") def test_live_migration_force_complete(self): body = {'force_complete': None} self.cs.server_migrations.live_migrate_force_complete(1234, 1) self.assert_called('POST', '/servers/1234/migrations/1/action', body) class ServerMigrationsTestV223(ServerMigrationsTest): migration = { "created_at": "2016-01-29T13:42:02.000000", "dest_compute": "compute2", "dest_host": "1.2.3.4", "dest_node": "node2", "id": 1, "server_uuid": "4cfba335-03d8-49b2-8c52-e69043d1e8fe", "source_compute": "compute1", "source_node": "node1", "status": "running", "memory_total_bytes": 123456, "memory_processed_bytes": 12345, "memory_remaining_bytes": 120000, "disk_total_bytes": 234567, "disk_processed_bytes": 23456, "disk_remaining_bytes": 230000, "updated_at": "2016-01-29T13:42:02.000000" } def setUp(self): super(ServerMigrationsTestV223, self).setUp() self.cs.api_version = api_versions.APIVersion("2.23") def test_list_migrations(self): ml = self.cs.server_migrations.list(1234) self.assertIsInstance(ml, base.ListWithMeta) self.assert_request_id(ml, fakes.FAKE_REQUEST_ID_LIST) for k in self.migration: self.assertEqual(self.migration[k], getattr(ml[0], k)) self.assert_called('GET', '/servers/1234/migrations') def test_get_migration(self): migration = self.cs.server_migrations.get(1234, 1) self.assertIsInstance(migration, server_migrations.ServerMigration) for k in migration._info: self.assertEqual(self.migration[k], migration._info[k]) self.assert_request_id(migration, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/servers/1234/migrations/1') class ServerMigrationsTestV224(ServerMigrationsTest): def setUp(self): super(ServerMigrationsTestV224, self).setUp() self.cs.api_version = api_versions.APIVersion("2.24") def test_live_migration_abort(self): self.cs.server_migrations.live_migration_abort(1234, 1) self.assert_called('DELETE', '/servers/1234/migrations/1') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/v2/test_servers.py0000664000175000017500000025135700000000000025244 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 base64 import io import os import tempfile from unittest import mock from novaclient import api_versions from novaclient import exceptions from novaclient.tests.unit.fixture_data import client from novaclient.tests.unit.fixture_data import floatingips from novaclient.tests.unit.fixture_data import servers as data from novaclient.tests.unit import utils from novaclient.tests.unit.v2 import fakes from novaclient.v2 import servers class ServersTest(utils.FixturedTestCase): client_fixture_class = client.V1 data_fixture_class = data.V1 api_version = None supports_files = True def setUp(self): super(ServersTest, self).setUp() self.useFixture(floatingips.FloatingFixture(self.requests_mock)) if self.api_version: self.cs.api_version = api_versions.APIVersion(self.api_version) def _get_server_create_default_nics(self): """Callback for default nics kwarg when creating a server. """ return None def test_list_servers(self): sl = self.cs.servers.list() self.assert_request_id(sl, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/servers/detail') for s in sl: self.assertIsInstance(s, servers.Server) def test_filter_servers_unicode(self): sl = self.cs.servers.list(search_opts={'name': 't€sting'}) self.assert_request_id(sl, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/servers/detail?name=t%E2%82%ACsting') for s in sl: self.assertIsInstance(s, servers.Server) def test_list_all_servers(self): # use marker just to identify this call in fixtures sl = self.cs.servers.list(limit=-1, marker=1234) self.assert_request_id(sl, fakes.FAKE_REQUEST_ID_LIST) self.assertEqual(2, len(sl)) self.assertEqual(self.requests_mock.request_history[-2].method, 'GET') self.assertEqual(self.requests_mock.request_history[-2].path_url, '/servers/detail?marker=1234') self.assert_called('GET', '/servers/detail?marker=5678') for s in sl: self.assertIsInstance(s, servers.Server) def test_filter_servers_unlocked(self): # calling the cs.servers.list python binding # will fail before 2.73 microversion. e = self.assertRaises(exceptions.UnsupportedAttribute, self.cs.servers.list, search_opts={'locked': False}) self.assertIn("'locked' argument is only allowed since " "microversion 2.73.", str(e)) def test_filter_without_config_drive(self): sl = self.cs.servers.list(search_opts={'config_drive': None}) self.assert_request_id(sl, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/servers/detail') for s in sl: self.assertIsInstance(s, servers.Server) def test_list_servers_undetailed(self): sl = self.cs.servers.list(detailed=False) self.assert_request_id(sl, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/servers') for s in sl: self.assertIsInstance(s, servers.Server) def test_list_servers_with_marker_limit(self): sl = self.cs.servers.list(marker=1234, limit=2) self.assert_request_id(sl, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/servers/detail?limit=2&marker=1234') for s in sl: self.assertIsInstance(s, servers.Server) def test_list_servers_sort_single(self): sl = self.cs.servers.list(sort_keys=['display_name'], sort_dirs=['asc']) self.assert_request_id(sl, fakes.FAKE_REQUEST_ID_LIST) self.assert_called( 'GET', '/servers/detail?sort_dir=asc&sort_key=display_name') for s in sl: self.assertIsInstance(s, servers.Server) def test_list_servers_sort_multiple(self): sl = self.cs.servers.list(sort_keys=['display_name', 'id'], sort_dirs=['asc', 'desc']) self.assert_request_id(sl, fakes.FAKE_REQUEST_ID_LIST) self.assert_called( 'GET', ('/servers/detail?sort_dir=asc&sort_dir=desc&' 'sort_key=display_name&sort_key=id')) for s in sl: self.assertIsInstance(s, servers.Server) def test_get_server_details(self): s = self.cs.servers.get(1234) self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/servers/1234') self.assertIsInstance(s, servers.Server) self.assertEqual(1234, s.id) self.assertEqual('BUILD', s.status) def test_get_server_promote_details(self): s1 = self.cs.servers.list(detailed=False)[0] s2 = self.cs.servers.list(detailed=True)[0] self.assertNotEqual(s1._info, s2._info) s1.get() self.assertEqual(s1._info, s2._info) def test_create_server(self): kwargs = {} if self.supports_files: kwargs['files'] = { '/etc/passwd': 'some data', # a file '/tmp/foo.txt': io.StringIO('data'), # a stream } s = self.cs.servers.create( name="My server", image=1, flavor=1, meta={'foo': 'bar'}, userdata="hello moto", key_name="fakekey", nics=self._get_server_create_default_nics(), **kwargs ) self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers') self.assertIsInstance(s, servers.Server) def test_create_server_boot_from_volume_with_nics(self): old_boot = self.cs.servers._boot nics = [{'net-id': '11111111-1111-1111-1111-111111111111', 'v4-fixed-ip': '10.0.0.7'}] bdm = {"volume_size": "1", "volume_id": "11111111-1111-1111-1111-111111111111", "delete_on_termination": "0", "device_name": "vda"} def wrapped_boot(url, key, *boot_args, **boot_kwargs): self.assertEqual(boot_kwargs['block_device_mapping'], bdm) self.assertEqual(boot_kwargs['nics'], nics) return old_boot(url, key, *boot_args, **boot_kwargs) @mock.patch.object(self.cs.servers, '_boot', wrapped_boot) def test_create_server_from_volume(): s = self.cs.servers.create( name="My server", image=1, flavor=1, meta={'foo': 'bar'}, userdata="hello moto", key_name="fakekey", block_device_mapping=bdm, nics=nics ) self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers') self.assertIsInstance(s, servers.Server) test_create_server_from_volume() def test_create_server_boot_from_volume_bdm_v2(self): old_boot = self.cs.servers._boot bdm = [{"volume_size": "1", "volume_id": "11111111-1111-1111-1111-111111111111", "delete_on_termination": "0", "device_name": "vda"}] def wrapped_boot(url, key, *boot_args, **boot_kwargs): self.assertEqual(boot_kwargs['block_device_mapping_v2'], bdm) return old_boot(url, key, *boot_args, **boot_kwargs) with mock.patch.object(self.cs.servers, '_boot', wrapped_boot): s = self.cs.servers.create( name="My server", image=1, flavor=1, meta={'foo': 'bar'}, userdata="hello moto", key_name="fakekey", block_device_mapping_v2=bdm, nics=self._get_server_create_default_nics() ) self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers') self.assertIsInstance(s, servers.Server) def test_create_server_boot_with_nics_ipv6(self): old_boot = self.cs.servers._boot nics = [{'net-id': '11111111-1111-1111-1111-111111111111', 'v6-fixed-ip': '2001:db9:0:1::10'}] def wrapped_boot(url, key, *boot_args, **boot_kwargs): self.assertEqual(boot_kwargs['nics'], nics) return old_boot(url, key, *boot_args, **boot_kwargs) with mock.patch.object(self.cs.servers, '_boot', wrapped_boot): s = self.cs.servers.create( name="My server", image=1, flavor=1, meta={'foo': 'bar'}, userdata="hello moto", key_name="fakekey", nics=nics ) self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers') self.assertIsInstance(s, servers.Server) def test_create_server_boot_with_address(self): old_boot = self.cs.servers._boot access_ip_v6 = '::1' access_ip_v4 = '10.10.10.10' def wrapped_boot(url, key, *boot_args, **boot_kwargs): self.assertEqual(boot_kwargs['access_ip_v6'], access_ip_v6) self.assertEqual(boot_kwargs['access_ip_v4'], access_ip_v4) return old_boot(url, key, *boot_args, **boot_kwargs) with mock.patch.object(self.cs.servers, '_boot', wrapped_boot): s = self.cs.servers.create( name="My server", image=1, flavor=1, meta={'foo': 'bar'}, userdata="hello moto", key_name="fakekey", access_ip_v6=access_ip_v6, access_ip_v4=access_ip_v4, nics=self._get_server_create_default_nics() ) self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers') self.assertIsInstance(s, servers.Server) def test_create_server_userdata_file_object(self): kwargs = {} if self.supports_files: kwargs['files'] = { '/etc/passwd': 'some data', # a file '/tmp/foo.txt': io.StringIO('data'), # a stream } s = self.cs.servers.create( name="My server", image=1, flavor=1, meta={'foo': 'bar'}, userdata=io.StringIO('hello moto'), nics=self._get_server_create_default_nics(), **kwargs ) self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers') self.assertIsInstance(s, servers.Server) def test_create_server_userdata_unicode(self): kwargs = {} if self.supports_files: kwargs['files'] = { '/etc/passwd': 'some data', # a file '/tmp/foo.txt': io.StringIO('data'), # a stream } s = self.cs.servers.create( name="My server", image=1, flavor=1, meta={'foo': 'bar'}, userdata='こんにちは', key_name="fakekey", nics=self._get_server_create_default_nics(), **kwargs ) self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers') self.assertIsInstance(s, servers.Server) def test_create_server_userdata_utf8(self): kwargs = {} if self.supports_files: kwargs['files'] = { '/etc/passwd': 'some data', # a file '/tmp/foo.txt': io.StringIO('data'), # a stream } s = self.cs.servers.create( name="My server", image=1, flavor=1, meta={'foo': 'bar'}, userdata='こんにちは', key_name="fakekey", nics=self._get_server_create_default_nics(), **kwargs ) self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers') self.assertIsInstance(s, servers.Server) def test_create_server_admin_pass(self): test_password = "test-pass" test_key = "fakekey" s = self.cs.servers.create( name="My server", image=1, flavor=1, admin_pass=test_password, key_name=test_key, nics=self._get_server_create_default_nics() ) self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers') self.assertIsInstance(s, servers.Server) body = self.requests_mock.last_request.json() self.assertEqual(test_password, body['server']['adminPass']) def test_create_server_userdata_bin(self): kwargs = {} if self.supports_files: kwargs['files'] = { '/etc/passwd': 'some data', # a file '/tmp/foo.txt': io.StringIO('data'), # a stream } with tempfile.TemporaryFile(mode='wb+') as bin_file: original_data = os.urandom(1024) bin_file.write(original_data) bin_file.flush() bin_file.seek(0) s = self.cs.servers.create( name="My server", image=1, flavor=1, meta={'foo': 'bar'}, userdata=bin_file, key_name="fakekey", nics=self._get_server_create_default_nics(), **kwargs ) self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers') self.assertIsInstance(s, servers.Server) # verify userdata matches original body = self.requests_mock.last_request.json() transferred_data = body['server']['user_data'] transferred_data = base64.b64decode(transferred_data) self.assertEqual(original_data, transferred_data) def _create_disk_config(self, disk_config): s = self.cs.servers.create( name="My server", image=1, flavor=1, disk_config=disk_config, nics=self._get_server_create_default_nics(), ) self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers') self.assertIsInstance(s, servers.Server) # verify disk config param was used in the request: server = self.requests_mock.last_request.json()['server'] self.assertIn('OS-DCF:diskConfig', server) self.assertEqual(disk_config, server['OS-DCF:diskConfig']) def test_create_server_disk_config_auto(self): self._create_disk_config('AUTO') def test_create_server_disk_config_manual(self): self._create_disk_config('MANUAL') def test_create_server_return_reservation_id(self): s = self.cs.servers.create( name="My server", image=1, flavor=1, reservation_id=True, nics=self._get_server_create_default_nics() ) self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers') def test_update_server(self): s = self.cs.servers.get(1234) # Update via instance s.update(name='hi') self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('PUT', '/servers/1234') s.update(name='hi') self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('PUT', '/servers/1234') # Silly, but not an error s.update() # Update via manager self.cs.servers.update(s, name='hi') self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('PUT', '/servers/1234') def test_delete_server(self): s = self.cs.servers.get(1234) ret = s.delete() self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('DELETE', '/servers/1234') ret = self.cs.servers.delete(1234) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('DELETE', '/servers/1234') ret = self.cs.servers.delete(s) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('DELETE', '/servers/1234') def test_delete_server_meta(self): ret = self.cs.servers.delete_meta(1234, ['test_key']) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('DELETE', '/servers/1234/metadata/test_key') def test_set_server_meta(self): m = self.cs.servers.set_meta(1234, {'test_key': 'test_value'}) self.assert_request_id(m, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/metadata', {'metadata': {'test_key': 'test_value'}}) def test_set_server_meta_item(self): m = self.cs.servers.set_meta_item(1234, 'test_key', 'test_value') self.assert_request_id(m, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('PUT', '/servers/1234/metadata/test_key', {'meta': {'test_key': 'test_value'}}) def test_get_server_meta(self): m = self.cs.servers.get_meta(1234, 'Server Label') self.assert_request_id(m, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/servers/1234/metadata/Server%20Label') self.assertEqual(m, {'meta': { 'Server Label': 'Web Head 1' }}) def test_list_server_meta(self): m = self.cs.servers.list_meta(1234) self.assert_request_id(m, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/servers/1234/metadata') self.assertEqual(m, {'metadata': { 'Server Label': 'Web Head 1', 'Image Version': '2.1' }}) def test_find(self): server = self.cs.servers.find(name='sample-server') self.assert_request_id(server, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/servers/1234') self.assertEqual('sample-server', server.name) # The networks should be sorted. networks = server.networks self.assertEqual(2, len(networks)) labels = list(networks) # returns the dict keys self.assertEqual('private', labels[0]) self.assertEqual('public', labels[1]) self.assertRaises(exceptions.NoUniqueMatch, self.cs.servers.find, flavor={"id": 1, "name": "256 MiB Server"}) sl = self.cs.servers.findall(flavor={"id": 1, "name": "256 MiB Server"}) self.assert_request_id(sl, fakes.FAKE_REQUEST_ID_LIST) self.assertEqual([1234, 5678, 9012], [s.id for s in sl]) def test_reboot_server(self): s = self.cs.servers.get(1234) ret = s.reboot() self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') ret = self.cs.servers.reboot(s, reboot_type='HARD') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') def test_rebuild_server(self): s = self.cs.servers.get(1234) ret = s.rebuild(image=1) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') ret = self.cs.servers.rebuild(s, image=1) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') ret = s.rebuild(image=1, password='5678') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') ret = self.cs.servers.rebuild(s, image=1, password='5678') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') def _rebuild_resize_disk_config(self, disk_config, operation="rebuild"): s = self.cs.servers.get(1234) if operation == "rebuild": ret = s.rebuild(image=1, disk_config=disk_config) elif operation == "resize": ret = s.resize(flavor=1, disk_config=disk_config) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') # verify disk config param was used in the request: d = self.requests_mock.last_request.json()[operation] self.assertIn('OS-DCF:diskConfig', d) self.assertEqual(disk_config, d['OS-DCF:diskConfig']) def test_rebuild_server_disk_config_auto(self): self._rebuild_resize_disk_config('AUTO') def test_rebuild_server_disk_config_manual(self): self._rebuild_resize_disk_config('MANUAL') def test_rebuild_server_preserve_ephemeral(self): s = self.cs.servers.get(1234) ret = s.rebuild(image=1, preserve_ephemeral=True) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') d = self.requests_mock.last_request.json()['rebuild'] self.assertIn('preserve_ephemeral', d) self.assertTrue(d['preserve_ephemeral']) def test_rebuild_server_name_meta_files(self): files = {'/etc/passwd': 'some data'} s = self.cs.servers.get(1234) ret = s.rebuild(image=1, name='new', meta={'foo': 'bar'}, files=files) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) d = self.requests_mock.last_request.json()['rebuild'] self.assertEqual('new', d['name']) self.assertEqual({'foo': 'bar'}, d['metadata']) self.assertEqual('/etc/passwd', d['personality'][0]['path']) def test_resize_server(self): s = self.cs.servers.get(1234) ret = s.resize(flavor=1) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') self.cs.servers.resize(s, flavor=1) self.assert_called('POST', '/servers/1234/action') def test_resize_server_disk_config_auto(self): self._rebuild_resize_disk_config('AUTO', 'resize') def test_resize_server_disk_config_manual(self): self._rebuild_resize_disk_config('MANUAL', 'resize') def test_confirm_resized_server(self): s = self.cs.servers.get(1234) ret = s.confirm_resize() self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') self.cs.servers.confirm_resize(s) self.assert_called('POST', '/servers/1234/action') def test_revert_resized_server(self): s = self.cs.servers.get(1234) ret = s.revert_resize() self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') ret = self.cs.servers.revert_resize(s) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') def test_migrate_server(self): s = self.cs.servers.get(1234) ret = s.migrate() self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') ret = self.cs.servers.migrate(s) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') def test_stop(self): s = self.cs.servers.get(1234) ret = s.stop() self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') ret = self.cs.servers.stop(s) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') def test_force_delete(self): s = self.cs.servers.get(1234) ret = s.force_delete() self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') ret = self.cs.servers.force_delete(s) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') def test_restore(self): s = self.cs.servers.get(1234) ret = s.restore() self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') ret = self.cs.servers.restore(s) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') def test_start(self): s = self.cs.servers.get(1234) ret = s.start() self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') ret = self.cs.servers.start(s) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') def test_rescue(self): s = self.cs.servers.get(1234) ret = s.rescue() self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') ret = self.cs.servers.rescue(s) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') def test_rescue_password(self): s = self.cs.servers.get(1234) ret = s.rescue(password='asdf') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action', {'rescue': {'adminPass': 'asdf'}}) ret = self.cs.servers.rescue(s, password='asdf') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action', {'rescue': {'adminPass': 'asdf'}}) def test_rescue_image(self): s = self.cs.servers.get(1234) ret = s.rescue(image=1) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action', {'rescue': {'rescue_image_ref': 1}}) ret = self.cs.servers.rescue(s, image=1) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action', {'rescue': {'rescue_image_ref': 1}}) def test_unrescue(self): s = self.cs.servers.get(1234) ret = s.unrescue() self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') ret = self.cs.servers.unrescue(s) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') def test_lock(self): s = self.cs.servers.get(1234) ret = s.lock() self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') ret = self.cs.servers.lock(s) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') def test_unlock(self): s = self.cs.servers.get(1234) ret = s.unlock() self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') ret = self.cs.servers.unlock(s) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') def test_backup(self): s = self.cs.servers.get(1234) sb = s.backup('back1', 'daily', 1) self.assert_request_id(sb, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') sb = self.cs.servers.backup(s, 'back1', 'daily', 2) self.assert_request_id(sb, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') def test_get_console_output_without_length(self): success = 'foo' s = self.cs.servers.get(1234) co = s.get_console_output() self.assert_request_id(co, fakes.FAKE_REQUEST_ID_LIST) self.assertEqual(success, s.get_console_output()) self.assert_called('POST', '/servers/1234/action') co = self.cs.servers.get_console_output(s) self.assert_request_id(co, fakes.FAKE_REQUEST_ID_LIST) self.assertEqual(success, self.cs.servers.get_console_output(s)) self.assert_called('POST', '/servers/1234/action') def test_get_console_output_with_length(self): success = 'foo' s = self.cs.servers.get(1234) co = s.get_console_output(length=50) self.assert_request_id(co, fakes.FAKE_REQUEST_ID_LIST) self.assertEqual(success, s.get_console_output(length=50)) self.assert_called('POST', '/servers/1234/action') co = self.cs.servers.get_console_output(s, length=50) self.assert_request_id(co, fakes.FAKE_REQUEST_ID_LIST) self.assertEqual(success, self.cs.servers.get_console_output(s, length=50)) self.assert_called('POST', '/servers/1234/action') # Testing password methods with the following password and key # # Clear password: FooBar123 # # RSA Private Key: novaclient/tests/unit/idfake.pem # # Encrypted password # OIuEuQttO8Rk93BcKlwHQsziDAnkAm/V6V8VPToA8ZeUaUBWwS0gwo2K6Y61Z96r # qG447iRz0uTEEYq3RAYJk1mh3mMIRVl27t8MtIecR5ggVVbz1S9AwXJQypDKl0ho # QFvhCBcMWPohyGewDJOhDbtuN1IoFI9G55ZvFwCm5y7m7B2aVcoLeIsJZE4PLsIw # /y5a6Z3/AoJZYGG7IH5WN88UROU3B9JZGFB2qtPLQTOvDMZLUhoPRIJeHiVSlo1N # tI2/++UsXVg3ow6ItqCJGgdNuGG5JB+bslDHWPxROpesEIHdczk46HCpHQN8f1sk # Hi/fmZZNQQqj1Ijq0caOIw== def test_get_password(self): s = self.cs.servers.get(1234) password = s.get_password('novaclient/tests/unit/idfake.pem') self.assert_request_id(password, fakes.FAKE_REQUEST_ID_LIST) self.assertEqual('FooBar123', password) self.assert_called('GET', '/servers/1234/os-server-password') def test_get_password_without_key(self): s = self.cs.servers.get(1234) password = s.get_password() self.assert_request_id(password, fakes.FAKE_REQUEST_ID_LIST) self.assertEqual( 'OIuEuQttO8Rk93BcKlwHQsziDAnkAm/V6V8VPToA8ZeUaUBWwS0gwo2K6Y61Z96r' 'qG447iRz0uTEEYq3RAYJk1mh3mMIRVl27t8MtIecR5ggVVbz1S9AwXJQypDKl0ho' 'QFvhCBcMWPohyGewDJOhDbtuN1IoFI9G55ZvFwCm5y7m7B2aVcoLeIsJZE4PLsIw' '/y5a6Z3/AoJZYGG7IH5WN88UROU3B9JZGFB2qtPLQTOvDMZLUhoPRIJeHiVSlo1N' 'tI2/++UsXVg3ow6ItqCJGgdNuGG5JB+bslDHWPxROpesEIHdczk46HCpHQN8f1sk' 'Hi/fmZZNQQqj1Ijq0caOIw==', password) self.assert_called('GET', '/servers/1234/os-server-password') def test_clear_password(self): s = self.cs.servers.get(1234) ret = s.clear_password() self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('DELETE', '/servers/1234/os-server-password') def test_get_server_diagnostics(self): s = self.cs.servers.get(1234) diagnostics = s.diagnostics() self.assert_request_id(diagnostics, fakes.FAKE_REQUEST_ID_LIST) self.assertIsNotNone(diagnostics) self.assert_called('GET', '/servers/1234/diagnostics') diagnostics_from_manager = self.cs.servers.diagnostics(1234) self.assert_request_id(diagnostics_from_manager, fakes.FAKE_REQUEST_ID_LIST) self.assertIsNotNone(diagnostics_from_manager) self.assert_called('GET', '/servers/1234/diagnostics') self.assertEqual(diagnostics[1], diagnostics_from_manager[1]) def test_get_vnc_console(self): s = self.cs.servers.get(1234) vc = s.get_vnc_console('novnc') self.assert_request_id(vc, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') vc = self.cs.servers.get_vnc_console(s, 'novnc') self.assert_request_id(vc, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') def test_get_spice_console(self): s = self.cs.servers.get(1234) sc = s.get_spice_console('spice-html5') self.assert_request_id(sc, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') sc = self.cs.servers.get_spice_console(s, 'spice-html5') self.assert_request_id(sc, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') def test_get_serial_console(self): s = self.cs.servers.get(1234) sc = s.get_serial_console('serial') self.assert_request_id(sc, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') sc = self.cs.servers.get_serial_console(s, 'serial') self.assert_request_id(sc, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') def test_get_rdp_console(self): s = self.cs.servers.get(1234) rc = s.get_rdp_console('rdp-html5') self.assert_request_id(rc, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') rc = self.cs.servers.get_rdp_console(s, 'rdp-html5') self.assert_request_id(rc, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') def test_get_console_url(self): s = self.cs.servers.get(1234) rc = s.get_console_url('novnc') self.assert_request_id(rc, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') rc = self.cs.servers.get_console_url(s, 'novnc') self.assert_request_id(rc, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') # test the case with invalid console type self.assertRaises(exceptions.UnsupportedConsoleType, s.get_console_url, 'invalid') def test_create_image(self): s = self.cs.servers.get(1234) im = s.create_image('123') self.assert_request_id(im, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') im = s.create_image('123', {}) self.assert_request_id(im, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') im = self.cs.servers.create_image(s, '123') self.assert_request_id(im, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') im = self.cs.servers.create_image(s, '123', {}) self.assert_request_id(im, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') def test_live_migrate_server(self): s = self.cs.servers.get(1234) ret = s.live_migrate(host='hostname', block_migration=False, disk_over_commit=False) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action', {'os-migrateLive': {'host': 'hostname', 'block_migration': False, 'disk_over_commit': False}}) ret = self.cs.servers.live_migrate(s, host='hostname', block_migration=False, disk_over_commit=False) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action', {'os-migrateLive': {'host': 'hostname', 'block_migration': False, 'disk_over_commit': False}}) def test_live_migrate_server_block_migration_none(self): s = self.cs.servers.get(1234) ret = s.live_migrate(host='hostname', block_migration=None, disk_over_commit=None) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action', {'os-migrateLive': {'host': 'hostname', 'block_migration': False, 'disk_over_commit': False}}) def test_reset_state(self): s = self.cs.servers.get(1234) ret = s.reset_state('newstate') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') ret = self.cs.servers.reset_state(s, 'newstate') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') def test_reset_network(self): s = self.cs.servers.get(1234) ret = s.reset_network() self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') ret = self.cs.servers.reset_network(s) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') def test_add_security_group(self): s = self.cs.servers.get(1234) sg = s.add_security_group('newsg') self.assert_request_id(sg, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') sg = self.cs.servers.add_security_group(s, 'newsg') self.assert_request_id(sg, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') def test_remove_security_group(self): s = self.cs.servers.get(1234) ret = s.remove_security_group('oldsg') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') ret = self.cs.servers.remove_security_group(s, 'oldsg') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') def test_list_security_group(self): s = self.cs.servers.get(1234) sgs = s.list_security_group() self.assert_request_id(sgs, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/servers/1234/os-security-groups') def test_evacuate(self): s = self.cs.servers.get(1234) ret = s.evacuate('fake_target_host', 'True') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') ret = self.cs.servers.evacuate(s, 'fake_target_host', 'False', 'NewAdminPassword') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') def test_interface_list(self): s = self.cs.servers.get(1234) il = s.interface_list() self.assert_request_id(il, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/servers/1234/os-interface') def test_interface_list_result_string_representable(self): """Test for bugs.launchpad.net/python-novaclient/+bug/1280453.""" # According to https://github.com/openstack/nova/blob/master/ # nova/api/openstack/compute/contrib/attach_interfaces.py#L33, # the attach_interface extension get method will return a json # object partly like this: interface_list = [{ 'net_id': 'd7745cf5-63f9-4883-b0ae-983f061e4f23', 'port_id': 'f35079da-36d5-4513-8ec1-0298d703f70e', 'mac_addr': 'fa:16:3e:4c:37:c8', 'port_state': 'ACTIVE', 'fixed_ips': [ { 'subnet_id': 'f1ad93ad-2967-46ba-b403-e8cbbe65f7fa', 'ip_address': '10.2.0.96' }] }] # If server is not string representable, it will raise an exception, # because attribute named 'name' cannot be found. # Parameter 'loaded' must be True or it will try to get attribute # 'id' then fails (lazy load detail), this is exactly same as # novaclient.base.Manager._list() s = servers.Server(servers.ServerManager, interface_list[0], loaded=True) # Trigger the __repr__ magic method self.assertEqual('', '%r' % s) def test_interface_attach(self): s = self.cs.servers.get(1234) ret = s.interface_attach(None, None, None) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/os-interface') self.assertIsInstance(ret, servers.NetworkInterface) def test_interface_detach(self): s = self.cs.servers.get(1234) ret = s.interface_detach('port-id') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('DELETE', '/servers/1234/os-interface/port-id') def test_create_server_with_description(self): self.assertRaises(exceptions.UnsupportedAttribute, self.cs.servers.create, name="My server", description="descr", image=1, flavor=1, meta={'foo': 'bar'}, userdata="hello moto", key_name="fakekey" ) def test_create_server_with_nics_auto(self): """Negative test for specifying nics='auto' before 2.37 """ self.assertRaises(ValueError, self.cs.servers.create, name='test', image='d9d8d53c-4b4a-4144-a5e5-b30d9f1fe46a', flavor='1', nics='auto') def test__validate_create_nics(self): if self.cs.api_version > api_versions.APIVersion('2.36'): self.assertRaises(ValueError, self.cs.servers._validate_create_nics, None) else: self.cs.servers._validate_create_nics(None) self.assertRaises(ValueError, self.cs.servers._validate_create_nics, mock.Mock()) self.cs.servers._validate_create_nics(["foo", "bar"]) self.cs.servers._validate_create_nics(("foo", "bar")) class ServersV26Test(ServersTest): api_version = "2.6" def test_get_vnc_console(self): s = self.cs.servers.get(1234) vc = s.get_vnc_console('novnc') self.assert_request_id(vc, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/remote-consoles') vc = self.cs.servers.get_vnc_console(s, 'novnc') self.assert_request_id(vc, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/remote-consoles') # test the case with invalid console type self.assertRaises(exceptions.UnsupportedConsoleType, s.get_vnc_console, 'invalid') def test_get_spice_console(self): s = self.cs.servers.get(1234) sc = s.get_spice_console('spice-html5') self.assert_request_id(sc, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/remote-consoles') sc = self.cs.servers.get_spice_console(s, 'spice-html5') self.assert_request_id(sc, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/remote-consoles') # test the case with invalid console type self.assertRaises(exceptions.UnsupportedConsoleType, s.get_spice_console, 'invalid') def test_get_serial_console(self): s = self.cs.servers.get(1234) sc = s.get_serial_console('serial') self.assert_request_id(sc, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/remote-consoles') sc = self.cs.servers.get_serial_console(s, 'serial') self.assert_request_id(sc, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/remote-consoles') # test the case with invalid console type self.assertRaises(exceptions.UnsupportedConsoleType, s.get_serial_console, 'invalid') def test_get_rdp_console(self): s = self.cs.servers.get(1234) rc = s.get_rdp_console('rdp-html5') self.assert_request_id(rc, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/remote-consoles') rc = self.cs.servers.get_rdp_console(s, 'rdp-html5') self.assert_request_id(rc, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/remote-consoles') # test the case with invalid console type self.assertRaises(exceptions.UnsupportedConsoleType, s.get_rdp_console, 'invalid') def test_get_console_url(self): s = self.cs.servers.get(1234) vc = s.get_console_url('novnc') self.assert_request_id(vc, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/remote-consoles') vc = self.cs.servers.get_console_url(s, 'novnc') self.assert_request_id(vc, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/remote-consoles') # test the case with invalid console type self.assertRaises(exceptions.UnsupportedConsoleType, s.get_console_url, 'invalid') # console type webmks is supported since api version 2.8 self.assertRaises(exceptions.UnsupportedConsoleType, s.get_console_url, 'webmks') class ServersV28Test(ServersV26Test): api_version = "2.8" def test_get_mks_console(self): s = self.cs.servers.get(1234) mksc = s.get_mks_console() self.assert_request_id(mksc, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/remote-consoles') mksc = self.cs.servers.get_mks_console(s) self.assert_request_id(mksc, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/remote-consoles') def test_get_console_url(self): s = self.cs.servers.get(1234) mksc = s.get_console_url('novnc') self.assert_request_id(mksc, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/remote-consoles') mksc = self.cs.servers.get_console_url(s, 'novnc') self.assert_request_id(mksc, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/remote-consoles') # test the case with invalid console type self.assertRaises(exceptions.UnsupportedConsoleType, s.get_console_url, 'invalid') class ServersV214Test(ServersV28Test): api_version = "2.14" def test_evacuate(self): s = self.cs.servers.get(1234) s.evacuate('fake_target_host') self.assert_called('POST', '/servers/1234/action') self.cs.servers.evacuate(s, 'fake_target_host', password='NewAdminPassword') self.assert_called('POST', '/servers/1234/action') class ServersV217Test(ServersV214Test): api_version = "2.17" def test_trigger_crash_dump(self): s = self.cs.servers.get(1234) s.trigger_crash_dump() self.assert_called('POST', '/servers/1234/action') self.cs.servers.trigger_crash_dump(s) self.assert_called('POST', '/servers/1234/action') class ServersV219Test(ServersV217Test): api_version = "2.19" def test_create_server_with_description(self): self.cs.servers.create( name="My server", description="descr", image=1, flavor=1, meta={'foo': 'bar'}, userdata="hello moto", key_name="fakekey", nics=self._get_server_create_default_nics() ) self.assert_called('POST', '/servers') def test_update_server_with_description(self): s = self.cs.servers.get(1234) s.update(description='hi') s.update(name='hi', description='hi') self.assert_called('PUT', '/servers/1234') def test_rebuild_with_description(self): s = self.cs.servers.get(1234) ret = s.rebuild(image="1", description="descr") self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') class ServersV225Test(ServersV219Test): api_version = "2.25" def test_live_migrate_server(self): s = self.cs.servers.get(1234) ret = s.live_migrate(host='hostname', block_migration='auto') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action', {'os-migrateLive': {'host': 'hostname', 'block_migration': 'auto'}}) ret = self.cs.servers.live_migrate(s, host='hostname', block_migration='auto') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action', {'os-migrateLive': {'host': 'hostname', 'block_migration': 'auto'}}) def test_live_migrate_server_block_migration_true(self): s = self.cs.servers.get(1234) ret = s.live_migrate(host='hostname', block_migration=True) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action', {'os-migrateLive': {'host': 'hostname', 'block_migration': True}}) ret = self.cs.servers.live_migrate(s, host='hostname', block_migration=True) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action', {'os-migrateLive': {'host': 'hostname', 'block_migration': True}}) def test_live_migrate_server_block_migration_none(self): s = self.cs.servers.get(1234) ret = s.live_migrate(host='hostname', block_migration=None) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action', {'os-migrateLive': {'host': 'hostname', 'block_migration': 'auto'}}) ret = self.cs.servers.live_migrate(s, host='hostname', block_migration='auto') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action', {'os-migrateLive': {'host': 'hostname', 'block_migration': 'auto'}}) class ServersV226Test(ServersV225Test): api_version = "2.26" def test_tag_list(self): s = self.cs.servers.get(1234) ret = s.tag_list() self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/servers/1234/tags') def test_tag_delete(self): s = self.cs.servers.get(1234) ret = s.delete_tag('tag') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('DELETE', '/servers/1234/tags/tag') def test_tag_delete_all(self): s = self.cs.servers.get(1234) ret = s.delete_all_tags() self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('DELETE', '/servers/1234/tags') def test_tag_add(self): s = self.cs.servers.get(1234) ret = s.add_tag('tag') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('PUT', '/servers/1234/tags/tag') def test_tags_set(self): s = self.cs.servers.get(1234) ret = s.set_tags(['tag1', 'tag2']) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('PUT', '/servers/1234/tags') class ServersV229Test(ServersV226Test): api_version = "2.29" def test_evacuate(self): s = self.cs.servers.get(1234) s.evacuate('fake_target_host') self.assert_called('POST', '/servers/1234/action', {'evacuate': {'host': 'fake_target_host'}}) self.cs.servers.evacuate(s, 'fake_target_host', force=True) self.assert_called('POST', '/servers/1234/action', {'evacuate': {'host': 'fake_target_host', 'force': True}}) class ServersV230Test(ServersV229Test): api_version = "2.30" def test_live_migrate_server(self): s = self.cs.servers.get(1234) ret = s.live_migrate(host='hostname', block_migration='auto') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action', {'os-migrateLive': {'host': 'hostname', 'block_migration': 'auto'}}) ret = self.cs.servers.live_migrate(s, host='hostname', block_migration='auto', force=True) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action', {'os-migrateLive': {'host': 'hostname', 'block_migration': 'auto', 'force': True}}) class ServersV232Test(ServersV226Test): api_version = "2.32" def test_create_server_boot_with_tagged_nics(self): nics = [{'net-id': '11111111-1111-1111-1111-111111111111', 'tag': 'one'}, {'net-id': '22222222-2222-2222-2222-222222222222', 'tag': 'two'}] self.cs.servers.create(name="Server with tagged nics", image=1, flavor=1, nics=nics) self.assert_called('POST', '/servers') def test_create_server_boot_with_tagged_nics_pre232(self): self.cs.api_version = api_versions.APIVersion("2.31") nics = [{'net-id': '11111111-1111-1111-1111-111111111111', 'tag': 'one'}, {'net-id': '22222222-2222-2222-2222-222222222222', 'tag': 'two'}] self.assertRaises(ValueError, self.cs.servers.create, name="Server with tagged nics", image=1, flavor=1, nics=nics) def test_create_server_boot_from_volume_tagged_bdm_v2(self): bdm = [{"volume_size": "1", "volume_id": "11111111-1111-1111-1111-111111111111", "delete_on_termination": "0", "device_name": "vda", "tag": "foo"}] s = self.cs.servers.create(name="My server", image=1, flavor=1, meta={'foo': 'bar'}, userdata="hello moto", key_name="fakekey", block_device_mapping_v2=bdm) self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers') def test_create_server_boot_from_volume_tagged_bdm_v2_pre232(self): self.cs.api_version = api_versions.APIVersion("2.31") bdm = [{"volume_size": "1", "volume_id": "11111111-1111-1111-1111-111111111111", "delete_on_termination": "0", "device_name": "vda", "tag": "foo"}] self.assertRaises(ValueError, self.cs.servers.create, name="My server", image=1, flavor=1, meta={'foo': 'bar'}, userdata="hello moto", key_name="fakekey", block_device_mapping_v2=bdm) class ServersV2_37Test(ServersV226Test): api_version = "2.37" def _get_server_create_default_nics(self): return 'auto' def test_create_server_no_nics(self): """Tests that nics are required in microversion 2.37+ """ self.assertRaises(ValueError, self.cs.servers.create, name='test', image='d9d8d53c-4b4a-4144-a5e5-b30d9f1fe46a', flavor='1') def test_create_server_with_nics_auto(self): s = self.cs.servers.create( name='test', image='d9d8d53c-4b4a-4144-a5e5-b30d9f1fe46a', flavor='1', nics=self._get_server_create_default_nics()) self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers') self.assertIsInstance(s, servers.Server) class ServersCreateImageBackupV2_45Test(utils.FixturedTestCase): """Tests the 2.45 microversion for createImage and createBackup server actions. """ client_fixture_class = client.V1 data_fixture_class = data.V1 api_version = '2.45' def setUp(self): super(ServersCreateImageBackupV2_45Test, self).setUp() self.cs.api_version = api_versions.APIVersion(self.api_version) def test_create_image(self): """Tests the createImage API with the 2.45 microversion which does not return the Location header, it returns a json dict in the response body with an image_id key. """ s = self.cs.servers.get(1234) im = s.create_image('123') self.assertEqual('456', im) self.assert_request_id(im, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') im = s.create_image('123', {}) self.assert_request_id(im, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') im = self.cs.servers.create_image(s, '123') self.assert_request_id(im, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') im = self.cs.servers.create_image(s, '123', {}) self.assert_request_id(im, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') def test_backup(self): s = self.cs.servers.get(1234) # Test backup on the Server object. sb = s.backup('back1', 'daily', 1) self.assertIn('image_id', sb) self.assertEqual('456', sb['image_id']) self.assert_request_id(sb, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') # Test backup on the ServerManager. sb = self.cs.servers.backup(s, 'back1', 'daily', 2) self.assertEqual('456', sb['image_id']) self.assert_request_id(sb, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action') class ServersV249Test(ServersV2_37Test): api_version = "2.49" def test_interface_attach_with_tag(self): s = self.cs.servers.get(1234) ret = s.interface_attach('7f42712e-63fe-484c-a6df-30ae4867ff66', None, None, 'test_tag') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called( 'POST', '/servers/1234/os-interface', {'interfaceAttachment': {'port_id': '7f42712e-63fe-484c-a6df-30ae4867ff66', 'tag': 'test_tag'}}) self.assertIsInstance(ret, servers.NetworkInterface) def test_add_fixed_ip(self): # novaclient.v2.servers.Server.add_fixed_ip() # is not available after 2.44 pass def test_remove_fixed_ip(self): # novaclient.v2.servers.Server.remove_fixed_ip() # is not available after 2.44 pass class ServersV252Test(ServersV249Test): api_version = "2.52" def test_create_server_with_tags(self): self.cs.servers.create( name="My server", image=1, flavor=1, meta={'foo': 'bar'}, userdata="hello moto", key_name="fakekey", nics=self._get_server_create_default_nics(), tags=['tag1', 'tag2'] ) self.assert_called('POST', '/servers', {'server': { 'flavorRef': '1', 'imageRef': '1', 'key_name': 'fakekey', 'max_count': 1, 'metadata': {'foo': 'bar'}, 'min_count': 1, 'name': 'My server', 'networks': 'auto', 'tags': ['tag1', 'tag2'], 'user_data': 'aGVsbG8gbW90bw==' }} ) def test_create_server_with_tags_pre_252_fails(self): self.cs.api_version = api_versions.APIVersion('2.51') self.assertRaises(exceptions.UnsupportedAttribute, self.cs.servers.create, name="My server", image=1, flavor=1, meta={'foo': 'bar'}, userdata="hello moto", key_name="fakekey", nics=self._get_server_create_default_nics(), tags=['tag1', 'tag2']) class ServersV254Test(ServersV252Test): api_version = "2.54" def test_rebuild_with_key_name(self): s = self.cs.servers.get(1234) ret = s.rebuild(image="1", key_name="test_keypair") self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action', {'rebuild': { 'imageRef': '1', 'key_name': 'test_keypair'}}) def test_rebuild_with_key_name_none(self): s = self.cs.servers.get(1234) ret = s.rebuild(image="1", key_name=None) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action', {'rebuild': { 'key_name': None, 'imageRef': '1'}}) def test_rebuild_with_key_name_pre_254_fails(self): self.cs.api_version = api_versions.APIVersion('2.53') ex = self.assertRaises(exceptions.UnsupportedAttribute, self.cs.servers.rebuild, '1234', fakes.FAKE_IMAGE_UUID_1, key_name='test_keypair') self.assertIn('key_name', str(ex.message)) class ServersV256Test(ServersV254Test): api_version = "2.56" def test_migrate_server(self): s = self.cs.servers.get(1234) ret = s.migrate() self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action', {'migrate': {}}) ret = s.migrate(host='target-host') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action', {'migrate': {'host': 'target-host'}}) def test_migrate_server_pre_256_fails(self): self.cs.api_version = api_versions.APIVersion('2.55') s = self.cs.servers.get(1234) ex = self.assertRaises(TypeError, s.migrate, host='target-host') self.assertIn('host', str(ex)) class ServersV257Test(ServersV256Test): """Tests the servers python API bindings with microversion 2.57 where personality files are deprecated. """ api_version = "2.57" supports_files = False def test_create_server_with_files_fails(self): ex = self.assertRaises( exceptions.UnsupportedAttribute, self.cs.servers.create, name="My server", image=1, flavor=1, files={ '/etc/passwd': 'some data', # a file '/tmp/foo.txt': io.StringIO('data'), # a stream }, nics='auto') self.assertIn('files', str(ex)) def test_rebuild_server_name_meta_files(self): files = {'/etc/passwd': 'some data'} s = self.cs.servers.get(1234) ex = self.assertRaises( exceptions.UnsupportedAttribute, s.rebuild, image=1, name='new', meta={'foo': 'bar'}, files=files) self.assertIn('files', str(ex)) class ServersV263Test(ServersV257Test): api_version = "2.63" def test_create_server_with_trusted_image_certificates(self): self.cs.servers.create( name="My server", image=1, flavor=1, meta={'foo': 'bar'}, userdata="hello moto", key_name="fakekey", nics=self._get_server_create_default_nics(), trusted_image_certificates=['id1', 'id2'], ) self.assert_called('POST', '/servers', {'server': { 'flavorRef': '1', 'imageRef': '1', 'key_name': 'fakekey', 'max_count': 1, 'metadata': {'foo': 'bar'}, 'min_count': 1, 'name': 'My server', 'networks': 'auto', 'trusted_image_certificates': ['id1', 'id2'], 'user_data': 'aGVsbG8gbW90bw==' }} ) def test_create_server_with_trusted_image_certificates_pre_263_fails(self): self.cs.api_version = api_versions.APIVersion('2.62') ex = self.assertRaises( exceptions.UnsupportedAttribute, self.cs.servers.create, name="My server", image=1, flavor=1, meta={'foo': 'bar'}, userdata="hello moto", key_name="fakekey", nics=self._get_server_create_default_nics(), trusted_image_certificates=['id1', 'id2']) self.assertIn('trusted_image_certificates', str(ex)) def test_rebuild_server_with_trusted_image_certificates(self): s = self.cs.servers.get(1234) ret = s.rebuild(image="1", trusted_image_certificates=['id1', 'id2']) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action', {'rebuild': { 'imageRef': '1', 'trusted_image_certificates': ['id1', 'id2']}}) def test_rebuild_server_with_trusted_image_certificates_none(self): s = self.cs.servers.get(1234) ret = s.rebuild(image="1", trusted_image_certificates=None) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action', {'rebuild': { 'imageRef': '1', 'trusted_image_certificates': None}}) def test_rebuild_with_trusted_image_certificates_pre_263_fails(self): self.cs.api_version = api_versions.APIVersion('2.62') ex = self.assertRaises(exceptions.UnsupportedAttribute, self.cs.servers.rebuild, '1234', fakes.FAKE_IMAGE_UUID_1, trusted_image_certificates=['id1', 'id2']) self.assertIn('trusted_image_certificates', str(ex)) class ServersV267Test(ServersV263Test): """Tests for creating a server with a block_device_mapping_v2 entry using volume_type for microversion 2.67. """ api_version = '2.67' def test_create_server_boot_from_volume_with_volume_type(self): bdm = [{"volume_size": 1, "uuid": "11111111-1111-1111-1111-111111111111", "delete_on_termination": True, "source_type": "snapshot", "destination_type": "volume", "boot_index": 0, "volume_type": "rbd"}] s = self.cs.servers.create( name="bfv server", image='', flavor=1, nics='auto', block_device_mapping_v2=bdm) self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers', { 'server': { 'flavorRef': '1', 'imageRef': '', 'name': 'bfv server', 'networks': 'auto', 'block_device_mapping_v2': bdm, 'min_count': 1, 'max_count': 1, }}) def test_create_server_boot_from_volume_with_volume_type_pre_267(self): self.cs.api_version = api_versions.APIVersion('2.66') bdm = [{"volume_size": 1, "uuid": "11111111-1111-1111-1111-111111111111", "delete_on_termination": True, "source_type": "snapshot", "destination_type": "volume", "boot_index": 0, "volume_type": "rbd"}] ex = self.assertRaises(ValueError, self.cs.servers.create, name="bfv server", image='', flavor=1, nics='none', block_device_mapping_v2=bdm) self.assertIn("Block device volume_type is not supported before " "microversion 2.67", str(ex)) class ServersV268Test(ServersV267Test): api_version = "2.68" def test_evacuate(self): s = self.cs.servers.get(1234) ret = s.evacuate('fake_target_host') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action', {'evacuate': {'host': 'fake_target_host'}}) ret = self.cs.servers.evacuate(s, 'fake_target_host') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action', {'evacuate': {'host': 'fake_target_host'}}) ex = self.assertRaises(TypeError, self.cs.servers.evacuate, 'fake_target_host', force=True) self.assertIn('force', str(ex)) def test_live_migrate_server(self): s = self.cs.servers.get(1234) ret = s.live_migrate(host='hostname', block_migration='auto') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action', {'os-migrateLive': {'host': 'hostname', 'block_migration': 'auto'}}) ret = self.cs.servers.live_migrate(s, host='hostname', block_migration='auto') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action', {'os-migrateLive': {'host': 'hostname', 'block_migration': 'auto'}}) ex = self.assertRaises(TypeError, self.cs.servers.live_migrate, host='hostname', force=True) self.assertIn('force', str(ex)) class ServersV273Test(ServersV268Test): api_version = "2.73" def test_lock_server(self): s = self.cs.servers.get(1234) ret = s.lock() self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action', {'lock': None}) ret = s.lock(reason='zombie-apocalypse') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action', {'lock': {'locked_reason': 'zombie-apocalypse'}}) def test_lock_server_pre_273_fails_with_reason(self): self.cs.api_version = api_versions.APIVersion('2.72') s = self.cs.servers.get(1234) e = self.assertRaises(TypeError, s.lock, reason='blah') self.assertIn("unexpected keyword argument 'reason'", str(e)) def test_filter_servers_unlocked(self): # support locked=False sl = self.cs.servers.list(search_opts={'locked': False}) self.assert_request_id(sl, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('GET', '/servers/detail?locked=False') for s in sl: self.assertIsInstance(s, servers.Server) class ServersV274Test(ServersV273Test): api_version = "2.74" def test_create_server_with_host(self): self.cs.servers.create( name="My server", image=1, flavor=1, nics="auto", host="new-host" ) self.assert_called('POST', '/servers', {'server': { 'flavorRef': '1', 'imageRef': '1', 'max_count': 1, 'min_count': 1, 'name': 'My server', 'networks': 'auto', 'host': 'new-host' }} ) def test_create_server_with_hypervisor_hostname(self): self.cs.servers.create( name="My server", image=1, flavor=1, nics="auto", hypervisor_hostname="new-host" ) self.assert_called('POST', '/servers', {'server': { 'flavorRef': '1', 'imageRef': '1', 'max_count': 1, 'min_count': 1, 'name': 'My server', 'networks': 'auto', 'hypervisor_hostname': 'new-host' }} ) def test_create_server_with_host_and_hypervisor_hostname(self): self.cs.servers.create( name="My server", image=1, flavor=1, nics="auto", host="new-host", hypervisor_hostname="new-host" ) self.assert_called('POST', '/servers', {'server': { 'flavorRef': '1', 'imageRef': '1', 'max_count': 1, 'min_count': 1, 'name': 'My server', 'networks': 'auto', 'host': 'new-host', 'hypervisor_hostname': 'new-host' }} ) def test_create_server_with_host_pre_274_fails(self): self.cs.api_version = api_versions.APIVersion('2.73') ex = self.assertRaises(exceptions.UnsupportedAttribute, self.cs.servers.create, name="My server", image=1, flavor=1, nics='auto', host="new-host") self.assertIn("'host' argument is only allowed since microversion " "2.74", str(ex)) def test_create_server_with_hypervisor_hostname_pre_274_fails(self): self.cs.api_version = api_versions.APIVersion('2.73') ex = self.assertRaises(exceptions.UnsupportedAttribute, self.cs.servers.create, name="My server", image=1, flavor=1, nics='auto', hypervisor_hostname="new-host") self.assertIn("'hypervisor_hostname' argument is only allowed since " "microversion 2.74", str(ex)) def test_create_server_with_host_and_hypervisor_hostname_pre_274_fails( self): self.cs.api_version = api_versions.APIVersion('2.73') ex = self.assertRaises(exceptions.UnsupportedAttribute, self.cs.servers.create, name="My server", image=1, flavor=1, nics='auto', host="new-host", hypervisor_hostname="new-host") self.assertIn("'host' argument is only allowed since microversion " "2.74", str(ex)) class ServersV277Test(ServersV274Test): api_version = "2.77" def test_unshelve(self): s = self.cs.servers.get(1234) # Test going through the Server object. ret = s.unshelve() self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called( 'POST', '/servers/1234/action', {'unshelve': None}) # Test going through the ServerManager directly. ret = self.cs.servers.unshelve(s) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called( 'POST', '/servers/1234/action', {'unshelve': None}) def test_unshelve_with_az(self): s = self.cs.servers.get(1234) # Test going through the Server object. ret = s.unshelve(availability_zone='foo-az') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action', {'unshelve': { 'availability_zone': 'foo-az'}}) # Test going through the ServerManager directly. ret = self.cs.servers.unshelve(s, availability_zone='foo-az') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('POST', '/servers/1234/action', {'unshelve': { 'availability_zone': 'foo-az'}}) def test_unshelve_server_pre_277_fails_with_specified_az(self): self.cs.api_version = api_versions.APIVersion('2.76') s = self.cs.servers.get(1234) # Test going through the Server object. ex = self.assertRaises(TypeError, s.unshelve, availability_zone='foo-az') self.assertIn("unexpected keyword argument 'availability_zone'", str(ex)) # Test going through the ServerManager directly. ex = self.assertRaises(TypeError, self.cs.servers.unshelve, s, availability_zone='foo-az') self.assertIn("unexpected keyword argument 'availability_zone'", str(ex)) class ServersV278Test(ServersV277Test): api_version = "2.78" def test_get_server_topology(self): s = self.cs.servers.get(1234) topology = s.topology() self.assert_request_id(topology, fakes.FAKE_REQUEST_ID_LIST) self.assertIsNotNone(topology) self.assert_called('GET', '/servers/1234/topology') topology_from_manager = self.cs.servers.topology(1234) self.assert_request_id(topology, fakes.FAKE_REQUEST_ID_LIST) self.assertIsNotNone(topology_from_manager) self.assert_called('GET', '/servers/1234/topology') self.assertEqual(topology, topology_from_manager) def test_get_server_topology_pre278(self): self.cs.api_version = api_versions.APIVersion('2.77') s = self.cs.servers.get(1234) self.assertRaises(exceptions.VersionNotFoundForAPIMethod, s.topology) class ServersV290Test(ServersV278Test): api_version = '2.90' def test_create_server_with_hostname(self): self.cs.servers.create( name='My server', image=1, flavor=1, nics='auto', hostname='new-hostname', ) self.assert_called( 'POST', '/servers', { 'server': { 'flavorRef': '1', 'imageRef': '1', 'max_count': 1, 'min_count': 1, 'name': 'My server', 'networks': 'auto', 'hostname': 'new-hostname' }, } ) def test_create_server_with_hostname_pre_290_fails(self): self.cs.api_version = api_versions.APIVersion('2.89') ex = self.assertRaises( exceptions.UnsupportedAttribute, self.cs.servers.create, name='My server', image=1, flavor=1, nics='auto', hostname='new-hostname') self.assertIn( "'hostname' argument is only allowed since microversion 2.90", str(ex)) def test_rebuild_server_with_hostname(self): s = self.cs.servers.get(1234) ret = s.rebuild(image="1", hostname='new-hostname') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called( 'POST', '/servers/1234/action', { 'rebuild': { 'imageRef': '1', 'hostname': 'new-hostname', }, }, ) def test_rebuild_server_with_hostname_pre_290_fails(self): self.cs.api_version = api_versions.APIVersion('2.89') ex = self.assertRaises( exceptions.UnsupportedAttribute, self.cs.servers.rebuild, '1234', fakes.FAKE_IMAGE_UUID_1, hostname='new-hostname') self.assertIn('hostname', str(ex)) def test_update_server_with_hostname(self): s = self.cs.servers.get(1234) s.update(hostname='new-hostname') self.assert_called( 'PUT', '/servers/1234', { 'server': { 'hostname': 'new-hostname', }, }, ) def test_update_with_hostname_pre_290_fails(self): self.cs.api_version = api_versions.APIVersion('2.89') s = self.cs.servers.get(1234) ex = self.assertRaises( TypeError, s.update, hostname='new-hostname') self.assertIn('hostname', str(ex)) class ServersV291Test(ServersV290Test): api_version = "2.91" def test_unshelve_with_host(self): s = self.cs.servers.get(1234) # Test going through the Server object. ret = s.unshelve(host='server1') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called( 'POST', '/servers/1234/action', {'unshelve': {'host': 'server1'}}) # Test going through the ServerManager directly. ret = self.cs.servers.unshelve(s, host='server1') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called( 'POST', '/servers/1234/action', {'unshelve': {'host': 'server1'}}) def test_unshelve_server_with_az_and_host(self): s = self.cs.servers.get(1234) # Test going through the Server object. ret = s.unshelve(host='server1', availability_zone='foo-az') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called( 'POST', '/servers/1234/action', {'unshelve': {'host': 'server1', 'availability_zone': 'foo-az'}}) # Test going through the ServerManager directly. ret = self.cs.servers.unshelve( s, host='server1', availability_zone='foo-az') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called( 'POST', '/servers/1234/action', {'unshelve': {'host': 'server1', 'availability_zone': 'foo-az'}}) def test_unshelve_unpin_az(self): s = self.cs.servers.get(1234) # Test going through the Server object. ret = s.unshelve(availability_zone=None) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called( 'POST', '/servers/1234/action', {'unshelve': {'availability_zone': None}}) # Test going through the ServerManager directly. ret = self.cs.servers.unshelve(s, availability_zone=None) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called( 'POST', '/servers/1234/action', {'unshelve': {'availability_zone': None}}) def test_unshelve_server_with_host_and_unpin(self): s = self.cs.servers.get(1234) # Test going through the Server object. ret = s.unshelve(availability_zone=None, host='server1') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called( 'POST', '/servers/1234/action', {'unshelve': {'host': 'server1', 'availability_zone': None}}) # Test going through the ServerManager directly. ret = self.cs.servers.unshelve( s, availability_zone=None, host='server1') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called( 'POST', '/servers/1234/action', {'unshelve': {'host': 'server1', 'availability_zone': None}}) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/v2/test_services.py0000664000175000017500000002023200000000000025360 0ustar00zuulzuul00000000000000# Copyright 2012 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from novaclient import api_versions from novaclient.tests.unit import utils from novaclient.tests.unit.v2 import fakes from novaclient.v2 import services class ServicesTest(utils.TestCase): api_version = "2.0" def setUp(self): super(ServicesTest, self).setUp() self.cs = fakes.FakeClient(api_versions.APIVersion(self.api_version)) self.service_type = self._get_service_type() def _get_service_type(self): return services.Service def test_list_services(self): svs = self.cs.services.list() self.assert_request_id(svs, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('GET', '/os-services') for s in svs: self.assertIsInstance(s, self._get_service_type()) self.assertEqual('nova-compute', s.binary) self.assertEqual('host1', s.host) self.assertEqual('' % s.id, str(s)) def test_list_services_with_hostname(self): svs = self.cs.services.list(host='host2') self.assert_request_id(svs, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('GET', '/os-services?host=host2') for s in svs: self.assertIsInstance(s, self._get_service_type()) self.assertEqual('nova-compute', s.binary) self.assertEqual('host2', s.host) def test_list_services_with_binary(self): svs = self.cs.services.list(binary='nova-cert') self.assert_request_id(svs, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('GET', '/os-services?binary=nova-cert') for s in svs: self.assertIsInstance(s, self._get_service_type()) self.assertEqual('nova-cert', s.binary) self.assertEqual('host1', s.host) def test_list_services_with_host_binary(self): svs = self.cs.services.list(host='host2', binary='nova-cert') self.assert_request_id(svs, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('GET', '/os-services?host=host2&binary=nova-cert') for s in svs: self.assertIsInstance(s, self._get_service_type()) self.assertEqual('nova-cert', s.binary) self.assertEqual('host2', s.host) def _update_body(self, host, binary, disabled_reason=None): body = {"host": host, "binary": binary} if disabled_reason is not None: body["disabled_reason"] = disabled_reason return body def test_services_enable(self): service = self.cs.services.enable('host1', 'nova-cert') self.assert_request_id(service, fakes.FAKE_REQUEST_ID_LIST) values = self._update_body("host1", "nova-cert") self.cs.assert_called('PUT', '/os-services/enable', values) self.assertIsInstance(service, self._get_service_type()) self.assertEqual('enabled', service.status) def test_services_delete(self): ret = self.cs.services.delete('1') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('DELETE', '/os-services/1') def test_services_disable(self): service = self.cs.services.disable('host1', 'nova-cert') self.assert_request_id(service, fakes.FAKE_REQUEST_ID_LIST) values = self._update_body("host1", "nova-cert") self.cs.assert_called('PUT', '/os-services/disable', values) self.assertIsInstance(service, self._get_service_type()) self.assertEqual('disabled', service.status) def test_services_disable_log_reason(self): service = self.cs.services.disable_log_reason( 'compute1', 'nova-compute', 'disable bad host') self.assert_request_id(service, fakes.FAKE_REQUEST_ID_LIST) values = self._update_body("compute1", "nova-compute", "disable bad host") self.cs.assert_called('PUT', '/os-services/disable-log-reason', values) self.assertIsInstance(service, self._get_service_type()) self.assertEqual('disabled', service.status) class ServicesV211TestCase(ServicesTest): api_version = "2.11" def _update_body(self, host, binary, disabled_reason=None, force_down=None): body = {"host": host, "binary": binary} if disabled_reason is not None: body["disabled_reason"] = disabled_reason if force_down is not None: body["forced_down"] = force_down return body def test_services_force_down(self): service = self.cs.services.force_down( 'compute1', 'nova-compute', False) self.assert_request_id(service, fakes.FAKE_REQUEST_ID_LIST) values = self._update_body("compute1", "nova-compute", force_down=False) self.cs.assert_called('PUT', '/os-services/force-down', values) self.assertIsInstance(service, self._get_service_type()) self.assertFalse(service.forced_down) class ServicesV2_53TestCase(ServicesV211TestCase): api_version = "2.53" def _update_body(self, status=None, disabled_reason=None, force_down=None): body = {} if status is not None: body['status'] = status if disabled_reason is not None: body['disabled_reason'] = disabled_reason if force_down is not None: body['forced_down'] = force_down return body def test_services_enable(self): service = self.cs.services.enable(fakes.FAKE_SERVICE_UUID_1) self.assert_request_id(service, fakes.FAKE_REQUEST_ID_LIST) values = self._update_body(status='enabled') self.cs.assert_called( 'PUT', '/os-services/%s' % fakes.FAKE_SERVICE_UUID_1, values) self.assertIsInstance(service, self._get_service_type()) self.assertEqual('enabled', service.status) def test_services_delete(self): ret = self.cs.services.delete(fakes.FAKE_SERVICE_UUID_1) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('DELETE', '/os-services/%s' % fakes.FAKE_SERVICE_UUID_1) def test_services_disable(self): service = self.cs.services.disable(fakes.FAKE_SERVICE_UUID_1) self.assert_request_id(service, fakes.FAKE_REQUEST_ID_LIST) values = self._update_body(status='disabled') self.cs.assert_called( 'PUT', '/os-services/%s' % fakes.FAKE_SERVICE_UUID_1, values) self.assertIsInstance(service, self._get_service_type()) self.assertEqual('disabled', service.status) def test_services_disable_log_reason(self): service = self.cs.services.disable_log_reason( fakes.FAKE_SERVICE_UUID_1, 'disable bad host') self.assert_request_id(service, fakes.FAKE_REQUEST_ID_LIST) values = self._update_body(status='disabled', disabled_reason='disable bad host') self.cs.assert_called( 'PUT', '/os-services/%s' % fakes.FAKE_SERVICE_UUID_1, values) self.assertIsInstance(service, self._get_service_type()) self.assertEqual('disabled', service.status) self.assertEqual('disable bad host', service.disabled_reason) def test_services_force_down(self): service = self.cs.services.force_down( fakes.FAKE_SERVICE_UUID_1, False) self.assert_request_id(service, fakes.FAKE_REQUEST_ID_LIST) values = self._update_body(force_down=False) self.cs.assert_called( 'PUT', '/os-services/%s' % fakes.FAKE_SERVICE_UUID_1, values) self.assertIsInstance(service, self._get_service_type()) self.assertFalse(service.forced_down) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/v2/test_shell.py0000664000175000017500000067114200000000000024660 0ustar00zuulzuul00000000000000# Copyright 2010 Jacob Kaplan-Moss # Copyright 2011 OpenStack Foundation # Copyright 2012 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import argparse import base64 import builtins import collections import datetime import io import os from unittest import mock import fixtures from oslo_utils import timeutils import testtools import novaclient from novaclient import api_versions from novaclient import base import novaclient.client from novaclient import exceptions import novaclient.shell from novaclient.tests.unit import utils from novaclient.tests.unit.v2 import fakes from novaclient.v2 import servers import novaclient.v2.shell FAKE_UUID_1 = fakes.FAKE_IMAGE_UUID_1 FAKE_UUID_2 = fakes.FAKE_IMAGE_UUID_2 # Converting dictionary to object TestAbsoluteLimits = collections.namedtuple("TestAbsoluteLimits", ["name", "value"]) class ShellFixture(fixtures.Fixture): def setUp(self): super(ShellFixture, self).setUp() self.shell = novaclient.shell.OpenStackComputeShell() def tearDown(self): # For some method like test_image_meta_bad_action we are # testing a SystemExit to be thrown and object self.shell has # no time to get instantiated which is OK in this case, so # we make sure the method is there before launching it. if hasattr(self.shell, 'cs'): self.shell.cs.clear_callstack() super(ShellFixture, self).tearDown() class ShellTest(utils.TestCase): FAKE_ENV = { 'NOVA_USERNAME': 'username', 'NOVA_PASSWORD': 'password', 'NOVA_PROJECT_ID': 'project_id', 'OS_COMPUTE_API_VERSION': '2', 'NOVA_URL': 'http://no.where', 'OS_AUTH_URL': 'http://no.where/v2.0', } def setUp(self): """Run before each test.""" super(ShellTest, self).setUp() for var in self.FAKE_ENV: self.useFixture(fixtures.EnvironmentVariable(var, self.FAKE_ENV[var])) self.shell = self.useFixture(ShellFixture()).shell self.useFixture(fixtures.MonkeyPatch( 'novaclient.client.Client', fakes.FakeClient)) # TODO(stephenfin): We should migrate most of the existing assertRaises # calls to simply pass expected_error to this instead so we can easily # capture and compare output @mock.patch('sys.stdout', new_callable=io.StringIO) @mock.patch('sys.stderr', new_callable=io.StringIO) def run_command(self, cmd, mock_stderr, mock_stdout, api_version=None, expected_error=None): version_options = [] if api_version: version_options.extend(["--os-compute-api-version", api_version, "--service-type", "computev21"]) if not isinstance(cmd, list): cmd = cmd.split() if expected_error: self.assertRaises(expected_error, self.shell.main, version_options + cmd) else: self.shell.main(version_options + cmd) return mock_stdout.getvalue(), mock_stderr.getvalue() def assert_called(self, method, url, body=None, **kwargs): return self.shell.cs.assert_called(method, url, body, **kwargs) def assert_called_anytime(self, method, url, body=None): return self.shell.cs.assert_called_anytime(method, url, body) def assert_not_called(self, method, url, body=None): return self.shell.cs.assert_not_called(method, url, body) def test_agents_list_with_hypervisor(self): _, err = self.run_command('agent-list --hypervisor xen') self.assert_called('GET', '/os-agents?hypervisor=xen') self.assertIn( 'This command has been deprecated since 23.0.0 Wallaby Release ' 'and will be removed in the first major release ' 'after the Nova server 24.0.0 X release.', err) def test_agents_create(self): _, err = self.run_command('agent-create win x86 7.0 ' '/xxx/xxx/xxx ' 'add6bb58e139be103324d04d82d8f546 ' 'kvm') self.assert_called( 'POST', '/os-agents', {'agent': { 'hypervisor': 'kvm', 'os': 'win', 'architecture': 'x86', 'version': '7.0', 'url': '/xxx/xxx/xxx', 'md5hash': 'add6bb58e139be103324d04d82d8f546'}}) self.assertIn( 'This command has been deprecated since 23.0.0 Wallaby Release ' 'and will be removed in the first major release ' 'after the Nova server 24.0.0 X release.', err) def test_agents_delete(self): _, err = self.run_command('agent-delete 1') self.assert_called('DELETE', '/os-agents/1') self.assertIn( 'This command has been deprecated since 23.0.0 Wallaby Release ' 'and will be removed in the first major release ' 'after the Nova server 24.0.0 X release.', err) def test_agents_modify(self): _, err = self.run_command('agent-modify 1 8.0 /yyy/yyyy/yyyy ' 'add6bb58e139be103324d04d82d8f546') self.assert_called('PUT', '/os-agents/1', {"para": { "url": "/yyy/yyyy/yyyy", "version": "8.0", "md5hash": "add6bb58e139be103324d04d82d8f546"}}) self.assertIn( 'This command has been deprecated since 23.0.0 Wallaby Release ' 'and will be removed in the first major release ' 'after the Nova server 24.0.0 X release.', err) def test_boot(self): self.run_command('boot --flavor 1 --image %s ' 'some-server' % FAKE_UUID_1) self.assert_called_anytime( 'POST', '/servers', {'server': { 'flavorRef': '1', 'name': 'some-server', 'imageRef': FAKE_UUID_1, 'min_count': 1, 'max_count': 1, }}, ) def test_boot_image_with(self): self.run_command("boot --flavor 1" " --image-with test_key=test_value some-server") self.assert_called_anytime( 'POST', '/servers', {'server': { 'flavorRef': '1', 'name': 'some-server', 'imageRef': FAKE_UUID_1, 'min_count': 1, 'max_count': 1, }}, ) def test_boot_image_with_error_out_no_match(self): cmd = ("boot --flavor 1" " --image-with fake_key=fake_value some-server") self.assertRaises(exceptions.CommandError, self.run_command, cmd) def test_boot_key(self): self.run_command('boot --flavor 1 --image %s --key-name 1 some-server' % FAKE_UUID_1) self.assert_called_anytime( 'POST', '/servers', {'server': { 'flavorRef': '1', 'name': 'some-server', 'imageRef': FAKE_UUID_1, 'key_name': '1', 'min_count': 1, 'max_count': 1, }}, ) def test_boot_user_data(self): testfile = os.path.join(os.path.dirname(__file__), 'testfile.txt') with open(testfile) as testfile_fd: data = testfile_fd.read().encode('utf-8') expected_file_data = base64.b64encode(data).decode('utf-8') self.run_command( 'boot --flavor 1 --image %s --user-data %s some-server' % (FAKE_UUID_1, testfile)) self.assert_called_anytime( 'POST', '/servers', {'server': { 'flavorRef': '1', 'name': 'some-server', 'imageRef': FAKE_UUID_1, 'min_count': 1, 'max_count': 1, 'user_data': expected_file_data }}, ) def test_boot_avzone(self): self.run_command( 'boot --flavor 1 --image %s --availability-zone avzone ' 'some-server' % FAKE_UUID_1) self.assert_called_anytime( 'POST', '/servers', {'server': { 'flavorRef': '1', 'name': 'some-server', 'imageRef': FAKE_UUID_1, 'availability_zone': 'avzone', 'min_count': 1, 'max_count': 1 }}, ) def test_boot_secgroup(self): self.run_command( 'boot --flavor 1 --image %s --security-groups secgroup1,' 'secgroup2 some-server' % FAKE_UUID_1) self.assert_called_anytime( 'POST', '/servers', {'server': { 'security_groups': [{'name': 'secgroup1'}, {'name': 'secgroup2'}], 'flavorRef': '1', 'name': 'some-server', 'imageRef': FAKE_UUID_1, 'min_count': 1, 'max_count': 1, }}, ) def test_boot_access_ip(self): self.run_command( 'boot --flavor 1 --image %s --access-ip-v4 10.10.10.10 ' '--access-ip-v6 ::1 some-server' % FAKE_UUID_1) self.assert_called_anytime( 'POST', '/servers', {'server': { 'flavorRef': '1', 'name': 'some-server', 'imageRef': FAKE_UUID_1, 'accessIPv4': '10.10.10.10', 'accessIPv6': '::1', 'max_count': 1, 'min_count': 1 }}, ) def test_boot_config_drive(self): self.run_command( 'boot --flavor 1 --image %s --config-drive 1 some-server' % FAKE_UUID_1) self.assert_called_anytime( 'POST', '/servers', {'server': { 'flavorRef': '1', 'name': 'some-server', 'imageRef': FAKE_UUID_1, 'min_count': 1, 'max_count': 1, 'config_drive': True }}, ) def test_boot_config_drive_false(self): self.run_command( 'boot --flavor 1 --image %s --config-drive false some-server' % FAKE_UUID_1) self.assert_called_anytime( 'POST', '/servers', {'server': { 'flavorRef': '1', 'name': 'some-server', 'imageRef': FAKE_UUID_1, 'min_count': 1, 'max_count': 1, }}, ) def test_boot_config_drive_invalid_value(self): ex = self.assertRaises( exceptions.CommandError, self.run_command, 'boot --flavor 1 --image %s --config-drive /dev/hda some-server' % FAKE_UUID_1) self.assertIn("The value of the '--config-drive' option must be " "a boolean value.", str(ex)) def test_boot_invalid_user_data(self): invalid_file = os.path.join(os.path.dirname(__file__), 'no_such_file') cmd = ('boot some-server --flavor 1 --image %s' ' --user-data %s' % (FAKE_UUID_1, invalid_file)) self.assertRaises(exceptions.CommandError, self.run_command, cmd) def test_boot_no_image_no_bdms(self): cmd = 'boot --flavor 1 some-server' self.assertRaises(exceptions.CommandError, self.run_command, cmd) def test_boot_no_flavor(self): cmd = 'boot --image %s some-server' % FAKE_UUID_1 self.assertRaises(exceptions.CommandError, self.run_command, cmd) def test_boot_not_key_value_bdm(self): cmd = ('boot --flavor 1 --image %s --block-device %s,tag=foo ' 'test-server' % (FAKE_UUID_1, FAKE_UUID_2)) self.assertRaises(argparse.ArgumentTypeError, self.run_command, cmd) def test_boot_not_key_value_ephemeral(self): cmd = ('boot --flavor 1 --image %s --ephemeral %s,tag=foo ' 'test-server' % (FAKE_UUID_1, FAKE_UUID_2)) self.assertRaises(argparse.ArgumentTypeError, self.run_command, cmd) def test_boot_no_image_bdms(self): self.run_command( 'boot --flavor 1 --block-device-mapping vda=blah:::0 some-server' ) self.assert_called_anytime( 'POST', '/servers', {'server': { 'flavorRef': '1', 'name': 'some-server', 'block_device_mapping': [ { 'volume_id': 'blah', 'delete_on_termination': '0', 'device_name': 'vda' } ], 'imageRef': '', 'min_count': 1, 'max_count': 1, }}, ) def test_boot_image_bdms_v2(self): self.run_command( 'boot --flavor 1 --image %s --block-device id=fake-id,' 'source=volume,dest=volume,device=vda,size=1,format=ext4,' 'type=disk,shutdown=preserve some-server' % FAKE_UUID_1 ) self.assert_called_anytime( 'POST', '/servers', {'server': { 'flavorRef': '1', 'name': 'some-server', 'block_device_mapping_v2': [ { 'uuid': FAKE_UUID_1, 'source_type': 'image', 'destination_type': 'local', 'boot_index': 0, 'delete_on_termination': True, }, { 'uuid': 'fake-id', 'source_type': 'volume', 'destination_type': 'volume', 'device_name': 'vda', 'volume_size': '1', 'guest_format': 'ext4', 'device_type': 'disk', 'delete_on_termination': False, }, ], 'imageRef': FAKE_UUID_1, 'min_count': 1, 'max_count': 1, }}, ) def test_boot_image_bdms_v2_wrong_source_type(self): self.assertRaises( exceptions.CommandError, self.run_command, 'boot --flavor 1 --image %s --block-device id=fake-id,' 'source=fake,device=vda,size=1,format=ext4,' 'type=disk,shutdown=preserve some-server' % FAKE_UUID_1) def test_boot_image_bdms_v2_no_source_type_no_destination_type(self): self.run_command( 'boot --flavor 1 --image %s --block-device id=fake-id,' 'device=vda,size=1,format=ext4,' 'type=disk,shutdown=preserve some-server' % FAKE_UUID_1 ) self.assert_called_anytime( 'POST', '/servers', {'server': { 'flavorRef': '1', 'name': 'some-server', 'block_device_mapping_v2': [ { 'uuid': FAKE_UUID_1, 'source_type': 'image', 'destination_type': 'local', 'boot_index': 0, 'delete_on_termination': True, }, { 'uuid': 'fake-id', 'source_type': 'blank', 'destination_type': 'local', 'device_name': 'vda', 'volume_size': '1', 'guest_format': 'ext4', 'device_type': 'disk', 'delete_on_termination': False, }, ], 'imageRef': FAKE_UUID_1, 'min_count': 1, 'max_count': 1, }}, ) def test_boot_image_bdms_v2_no_destination_type(self): self.run_command( 'boot --flavor 1 --image %s --block-device id=fake-id,' 'source=volume,device=vda,size=1,format=ext4,' 'type=disk,shutdown=preserve some-server' % FAKE_UUID_1 ) self.assert_called_anytime( 'POST', '/servers', {'server': { 'flavorRef': '1', 'name': 'some-server', 'block_device_mapping_v2': [ { 'uuid': FAKE_UUID_1, 'source_type': 'image', 'destination_type': 'local', 'boot_index': 0, 'delete_on_termination': True, }, { 'uuid': 'fake-id', 'source_type': 'volume', 'destination_type': 'volume', 'device_name': 'vda', 'volume_size': '1', 'guest_format': 'ext4', 'device_type': 'disk', 'delete_on_termination': False, }, ], 'imageRef': FAKE_UUID_1, 'min_count': 1, 'max_count': 1, }}, ) def test_boot_image_bdms_v2_wrong_destination_type(self): self.assertRaises( exceptions.CommandError, self.run_command, 'boot --flavor 1 --image %s --block-device id=fake-id,' 'source=volume,dest=dest1,device=vda,size=1,format=ext4,' 'type=disk,shutdown=preserve some-server' % FAKE_UUID_1) def test_boot_image_bdms_v2_with_tag(self): self.run_command( 'boot --flavor 1 --image %s --block-device id=fake-id,' 'source=volume,dest=volume,device=vda,size=1,format=ext4,' 'type=disk,shutdown=preserve,tag=foo some-server' % FAKE_UUID_1, api_version='2.32' ) self.assert_called_anytime( 'POST', '/servers', {'server': { 'flavorRef': '1', 'name': 'some-server', 'block_device_mapping_v2': [ { 'uuid': FAKE_UUID_1, 'source_type': 'image', 'destination_type': 'local', 'boot_index': 0, 'delete_on_termination': True, }, { 'uuid': 'fake-id', 'source_type': 'volume', 'destination_type': 'volume', 'device_name': 'vda', 'volume_size': '1', 'guest_format': 'ext4', 'device_type': 'disk', 'delete_on_termination': False, 'tag': 'foo', }, ], 'imageRef': FAKE_UUID_1, 'min_count': 1, 'max_count': 1, }}, ) def test_boot_no_image_bdms_v2(self): self.run_command( 'boot --flavor 1 --block-device id=fake-id,source=volume,' 'dest=volume,bus=virtio,device=vda,size=1,format=ext4,bootindex=0,' 'type=disk,shutdown=preserve some-server' ) self.assert_called_anytime( 'POST', '/servers', {'server': { 'flavorRef': '1', 'name': 'some-server', 'block_device_mapping_v2': [ { 'uuid': 'fake-id', 'source_type': 'volume', 'destination_type': 'volume', 'disk_bus': 'virtio', 'device_name': 'vda', 'volume_size': '1', 'guest_format': 'ext4', 'boot_index': '0', 'device_type': 'disk', 'delete_on_termination': False, } ], 'imageRef': '', 'min_count': 1, 'max_count': 1, }}, ) cmd = 'boot --flavor 1 --boot-volume fake-id some-server' self.run_command(cmd) self.assert_called_anytime( 'POST', '/servers', {'server': { 'flavorRef': '1', 'name': 'some-server', 'block_device_mapping_v2': [ { 'uuid': 'fake-id', 'source_type': 'volume', 'destination_type': 'volume', 'boot_index': 0, 'delete_on_termination': False, } ], 'imageRef': '', 'min_count': 1, 'max_count': 1, }}, ) cmd = 'boot --flavor 1 --snapshot fake-id some-server' self.run_command(cmd) self.assert_called_anytime( 'POST', '/servers', {'server': { 'flavorRef': '1', 'name': 'some-server', 'block_device_mapping_v2': [ { 'uuid': 'fake-id', 'source_type': 'snapshot', 'destination_type': 'volume', 'boot_index': 0, 'delete_on_termination': False, } ], 'imageRef': '', 'min_count': 1, 'max_count': 1, }}, ) self.run_command('boot --flavor 1 --swap 1 some-server') self.assert_called_anytime( 'POST', '/servers', {'server': { 'flavorRef': '1', 'name': 'some-server', 'block_device_mapping_v2': [ { 'source_type': 'blank', 'destination_type': 'local', 'boot_index': -1, 'guest_format': 'swap', 'volume_size': '1', 'delete_on_termination': True, } ], 'imageRef': '', 'min_count': 1, 'max_count': 1, }}, ) self.run_command( 'boot --flavor 1 --ephemeral size=1,format=ext4 some-server' ) self.assert_called_anytime( 'POST', '/servers', {'server': { 'flavorRef': '1', 'name': 'some-server', 'block_device_mapping_v2': [ { 'source_type': 'blank', 'destination_type': 'local', 'boot_index': -1, 'guest_format': 'ext4', 'volume_size': '1', 'delete_on_termination': True, } ], 'imageRef': '', 'min_count': 1, 'max_count': 1, }}, ) def test_boot_bdms_v2_invalid_shutdown_value(self): self.assertRaises(exceptions.CommandError, self.run_command, ('boot --flavor 1 --image %s --block-device ' 'id=fake-id,source=volume,dest=volume,device=vda,' 'size=1,format=ext4,type=disk,shutdown=foobar ' 'some-server' % FAKE_UUID_1)) def test_boot_from_volume_with_volume_type_latest_microversion(self): self.run_command( 'boot --flavor 1 --block-device id=%s,source=image,dest=volume,' 'size=1,bootindex=0,shutdown=remove,tag=foo,volume_type=lvm ' 'bfv-server' % FAKE_UUID_1, api_version='2.latest') self.assert_called_anytime( 'POST', '/servers', {'server': { 'flavorRef': '1', 'name': 'bfv-server', 'block_device_mapping_v2': [ { 'uuid': FAKE_UUID_1, 'source_type': 'image', 'destination_type': 'volume', 'volume_size': '1', 'delete_on_termination': True, 'tag': 'foo', 'boot_index': '0', 'volume_type': 'lvm' }, ], 'networks': 'auto', 'imageRef': '', 'min_count': 1, 'max_count': 1, }}) def test_boot_from_volume_with_volume_type_old_microversion(self): ex = self.assertRaises( exceptions.CommandError, self.run_command, 'boot --flavor 1 --block-device id=%s,source=image,dest=volume,' 'size=1,bootindex=0,shutdown=remove,tag=foo,volume_type=lvm ' 'bfv-server' % FAKE_UUID_1, api_version='2.66') self.assertIn("'volume_type' in block device mapping is not supported " "in API version", str(ex)) def test_boot_from_volume_with_volume_type(self): """Tests creating a volume-backed server from a source image and specifying the type of volume to create with microversion 2.67. """ self.run_command( 'boot --flavor 1 --block-device id=%s,source=image,dest=volume,' 'size=1,bootindex=0,shutdown=remove,tag=foo,volume_type=lvm ' 'bfv-server' % FAKE_UUID_1, api_version='2.67') self.assert_called_anytime( 'POST', '/servers', {'server': { 'flavorRef': '1', 'name': 'bfv-server', 'block_device_mapping_v2': [ { 'uuid': FAKE_UUID_1, 'source_type': 'image', 'destination_type': 'volume', 'volume_size': '1', 'delete_on_termination': True, 'tag': 'foo', 'boot_index': '0', 'volume_type': 'lvm' }, ], 'networks': 'auto', 'imageRef': '', 'min_count': 1, 'max_count': 1, }}) def test_boot_from_volume_without_volume_type_2_67(self): """Tests creating a volume-backed server from a source image but without specifying the type of volume to create with microversion 2.67. The volume_type parameter should be omitted in the request to the API server. """ self.run_command( 'boot --flavor 1 --block-device id=%s,source=image,dest=volume,' 'size=1,bootindex=0,shutdown=remove,tag=foo bfv-server' % FAKE_UUID_1, api_version='2.67') self.assert_called_anytime( 'POST', '/servers', {'server': { 'flavorRef': '1', 'name': 'bfv-server', 'block_device_mapping_v2': [ { 'uuid': FAKE_UUID_1, 'source_type': 'image', 'destination_type': 'volume', 'volume_size': '1', 'delete_on_termination': True, 'tag': 'foo', 'boot_index': '0', }, ], 'networks': 'auto', 'imageRef': '', 'min_count': 1, 'max_count': 1, }}) def test_boot_metadata(self): self.run_command('boot --image %s --flavor 1 --meta foo=bar=pants' ' --meta spam=eggs some-server ' % FAKE_UUID_1) self.assert_called_anytime( 'POST', '/servers', {'server': { 'flavorRef': '1', 'name': 'some-server', 'imageRef': FAKE_UUID_1, 'metadata': {'foo': 'bar=pants', 'spam': 'eggs'}, 'min_count': 1, 'max_count': 1, }}, ) def test_boot_with_incorrect_metadata(self): cmd = ('boot --image %s --flavor 1 --meta foo ' 'some-server ' % FAKE_UUID_1) result = self.assertRaises(argparse.ArgumentTypeError, self.run_command, cmd) expected = "'['foo']' is not in the format of 'key=value'" self.assertEqual(expected, result.args[0]) def test_boot_hints(self): cmd = ('boot --image %s --flavor 1 ' '--hint same_host=a0cf03a5-d921-4877-bb5c-86d26cf818e1 ' '--hint same_host=8c19174f-4220-44f0-824a-cd1eeef10287 ' '--hint query=[>=,$free_ram_mb,1024] ' 'some-server' % FAKE_UUID_1) self.run_command(cmd) self.assert_called_anytime( 'POST', '/servers', { 'server': { 'flavorRef': '1', 'name': 'some-server', 'imageRef': FAKE_UUID_1, 'min_count': 1, 'max_count': 1, }, 'os:scheduler_hints': { 'same_host': [ 'a0cf03a5-d921-4877-bb5c-86d26cf818e1', '8c19174f-4220-44f0-824a-cd1eeef10287', ], 'query': '[>=,$free_ram_mb,1024]', }, }, ) def test_boot_hints_invalid(self): cmd = ('boot --image %s --flavor 1 ' '--hint a0cf03a5-d921-4877-bb5c-86d26cf818e1 ' 'some-server' % FAKE_UUID_1) _, err = self.run_command(cmd, expected_error=SystemExit) self.assertIn("'a0cf03a5-d921-4877-bb5c-86d26cf818e1' is not in " "the format of 'key=value'", err) def test_boot_nic_auto_not_alone_after(self): cmd = ('boot --image %s --flavor 1 ' '--nic auto,tag=foo some-server' % FAKE_UUID_1) self.assertRaises(exceptions.CommandError, self.run_command, cmd) def test_boot_nic_auto_not_alone_before(self): cmd = ('boot --image %s --flavor 1 ' '--nic tag=foo,auto some-server' % FAKE_UUID_1) self.assertRaises(exceptions.CommandError, self.run_command, cmd) def test_boot_nic_none_not_alone_before(self): cmd = ('boot --image %s --flavor 1 ' '--nic none,tag=foo some-server' % FAKE_UUID_1) self.assertRaises(exceptions.CommandError, self.run_command, cmd) def test_boot_nic_none_not_alone_after(self): cmd = ('boot --image %s --flavor 1 ' '--nic tag=foo,none some-server' % FAKE_UUID_1) self.assertRaises(exceptions.CommandError, self.run_command, cmd) def test_boot_nics(self): cmd = ('boot --image %s --flavor 1 ' '--nic net-id=a=c,v4-fixed-ip=10.0.0.1 some-server' % FAKE_UUID_1) self.run_command(cmd) self.assert_called_anytime( 'POST', '/servers', { 'server': { 'flavorRef': '1', 'name': 'some-server', 'imageRef': FAKE_UUID_1, 'min_count': 1, 'max_count': 1, 'networks': [ {'uuid': 'a=c', 'fixed_ip': '10.0.0.1'}, ], }, }, ) def test_boot_with_multiple_nics(self): cmd = ('boot --image %s --flavor 1 ' '--nic net-id=net_a,v4-fixed-ip=10.0.0.1 ' '--nic net-id=net_b some-server' % FAKE_UUID_1) self.run_command(cmd) self.assert_called_anytime( 'POST', '/servers', { 'server': { 'flavorRef': '1', 'name': 'some-server', 'imageRef': FAKE_UUID_1, 'min_count': 1, 'max_count': 1, 'networks': [ {'uuid': 'net_a', 'fixed_ip': '10.0.0.1'}, {'uuid': 'net_b'} ], }, }, ) def test_boot_nics_with_tag(self): cmd = ('boot --image %s --flavor 1 ' '--nic net-id=a=c,v4-fixed-ip=10.0.0.1,tag=foo some-server' % FAKE_UUID_1) self.run_command(cmd, api_version='2.32') self.assert_called_anytime( 'POST', '/servers', { 'server': { 'flavorRef': '1', 'name': 'some-server', 'imageRef': FAKE_UUID_1, 'min_count': 1, 'max_count': 1, 'networks': [ {'uuid': 'a=c', 'fixed_ip': '10.0.0.1', 'tag': 'foo'}, ], }, }, ) def test_boot_invalid_nics_pre_v2_32(self): # This is a negative test to make sure we fail with the correct message cmd = ('boot --image %s --flavor 1 ' '--nic net-id=1,port-id=2 some-server' % FAKE_UUID_1) ex = self.assertRaises(exceptions.CommandError, self.run_command, cmd, api_version='2.1') self.assertNotIn('tag=tag', str(ex)) def test_boot_invalid_nics_v2_32(self): # This is a negative test to make sure we fail with the correct message cmd = ('boot --image %s --flavor 1 ' '--nic net-id=1,port-id=2 some-server' % FAKE_UUID_1) ex = self.assertRaises(exceptions.CommandError, self.run_command, cmd, api_version='2.32') self.assertIn('tag=tag', str(ex)) def test_boot_invalid_nics_v2_36_auto(self): """This is a negative test to make sure we fail with the correct message. --nic auto isn't allowed before 2.37. """ cmd = ('boot --image %s --flavor 1 --nic auto test' % FAKE_UUID_1) ex = self.assertRaises(exceptions.CommandError, self.run_command, cmd, api_version='2.36') self.assertNotIn('auto,none', str(ex)) def test_boot_invalid_nics_v2_37(self): """This is a negative test to make sure we fail with the correct message. """ cmd = ('boot --image %s --flavor 1 ' '--nic net-id=1 --nic auto some-server' % FAKE_UUID_1) ex = self.assertRaises(exceptions.CommandError, self.run_command, cmd, api_version='2.37') self.assertIn('auto,none', str(ex)) def test_boot_nics_auto_allocate_default(self): """Tests that if microversion>=2.37 is specified and no --nics are specified that a single --nic with net-id=auto is used. """ cmd = 'boot --image %s --flavor 1 some-server' % FAKE_UUID_1 self.run_command(cmd, api_version='2.37') self.assert_called_anytime( 'POST', '/servers', { 'server': { 'flavorRef': '1', 'name': 'some-server', 'imageRef': FAKE_UUID_1, 'min_count': 1, 'max_count': 1, 'networks': 'auto', }, }, ) def test_boot_nics_auto_allocate_none(self): """Tests specifying '--nic none' with microversion 2.37 """ cmd = 'boot --image %s --flavor 1 --nic none some-server' % FAKE_UUID_1 self.run_command(cmd, api_version='2.37') self.assert_called_anytime( 'POST', '/servers', { 'server': { 'flavorRef': '1', 'name': 'some-server', 'imageRef': FAKE_UUID_1, 'min_count': 1, 'max_count': 1, 'networks': 'none', }, }, ) def test_boot_nics_ipv6(self): cmd = ('boot --image %s --flavor 1 ' '--nic net-id=a=c,v6-fixed-ip=2001:db9:0:1::10 some-server' % FAKE_UUID_1) self.run_command(cmd) self.assert_called_anytime( 'POST', '/servers', { 'server': { 'flavorRef': '1', 'name': 'some-server', 'imageRef': FAKE_UUID_1, 'min_count': 1, 'max_count': 1, 'networks': [ {'uuid': 'a=c', 'fixed_ip': '2001:db9:0:1::10'}, ], }, }, ) def test_boot_nics_both_ipv4_and_ipv6(self): cmd = ('boot --image %s --flavor 1 ' '--nic net-id=a=c,v4-fixed-ip=10.0.0.1,' 'v6-fixed-ip=2001:db9:0:1::10 some-server' % FAKE_UUID_1) self.assertRaises(exceptions.CommandError, self.run_command, cmd) def test_boot_nics_no_value(self): cmd = ('boot --image %s --flavor 1 ' '--nic net-id some-server' % FAKE_UUID_1) self.assertRaises(exceptions.CommandError, self.run_command, cmd) def test_boot_nics_random_key(self): cmd = ('boot --image %s --flavor 1 ' '--nic net-id=a=c,v4-fixed-ip=10.0.0.1,foo=bar some-server' % FAKE_UUID_1) self.assertRaises(exceptions.CommandError, self.run_command, cmd) def test_boot_nics_no_netid_or_portid(self): cmd = ('boot --image %s --flavor 1 ' '--nic v4-fixed-ip=10.0.0.1 some-server' % FAKE_UUID_1) self.assertRaises(exceptions.CommandError, self.run_command, cmd) def test_boot_nics_netid_and_portid(self): cmd = ('boot --image %s --flavor 1 ' '--nic port-id=some=port,net-id=some=net some-server' % FAKE_UUID_1) self.assertRaises(exceptions.CommandError, self.run_command, cmd) def test_boot_nics_invalid_ipv4(self): cmd = ('boot --image %s --flavor 1 ' '--nic net-id=a=c,v4-fixed-ip=2001:db9:0:1::10 some-server' % FAKE_UUID_1) self.assertRaises(exceptions.CommandError, self.run_command, cmd) def test_boot_nics_invalid_ipv6(self): cmd = ('boot --image %s --flavor 1 ' '--nic net-id=a=c,v6-fixed-ip=10.0.0.1 some-server' % FAKE_UUID_1) self.assertRaises(exceptions.CommandError, self.run_command, cmd) def test_boot_nics_net_id_twice(self): cmd = ('boot --image %s --flavor 1 ' '--nic net-id=net-id1,net-id=net-id2 some-server' % FAKE_UUID_1) self.assertRaises(exceptions.CommandError, self.run_command, cmd) def test_boot_nics_net_name_neutron(self): cmd = ('boot --image %s --flavor 1 ' '--nic net-name=private some-server' % FAKE_UUID_1) self.run_command(cmd) self.assert_called_anytime( 'POST', '/servers', { 'server': { 'flavorRef': '1', 'name': 'some-server', 'imageRef': FAKE_UUID_1, 'min_count': 1, 'max_count': 1, 'networks': [ {'uuid': 'e43a56c7-11d4-45c9-8681-ddc8171b5850'}, ], }, }, ) def test_boot_nics_net_name_neutron_dup(self): cmd = ('boot --image %s --flavor 1 ' '--nic net-name=duplicate some-server' % FAKE_UUID_1) # this should raise a multiple matches error msg = ("Multiple network matches found for 'duplicate', " "use an ID to be more specific.") with testtools.ExpectedException(exceptions.CommandError, msg): self.run_command(cmd) def test_boot_nics_net_name_neutron_blank(self): cmd = ('boot --image %s --flavor 1 ' '--nic net-name=blank some-server' % FAKE_UUID_1) # this should raise a multiple matches error msg = 'No Network matching blank\\..*' with testtools.ExpectedException(exceptions.CommandError, msg): self.run_command(cmd) # TODO(sdague): the following tests should really avoid mocking # out other tests, and they should check the string in the # CommandError, because it's not really enough to distinguish # between various errors. @mock.patch('novaclient.v2.shell._find_network_id', return_value='net-id') def test_boot_nics_net_name_and_net_id(self, mock_find_network_id): cmd = ('boot --image %s --flavor 1 ' '--nic net-name=some-net,net-id=some-id some-server' % FAKE_UUID_1) self.assertRaises(exceptions.CommandError, self.run_command, cmd) @mock.patch('novaclient.v2.shell._find_network_id', return_value='net-id') def test_boot_nics_net_name_and_port_id(self, mock_find_network_id): cmd = ('boot --image %s --flavor 1 ' '--nic net-name=some-net,port-id=some-id some-server' % FAKE_UUID_1) self.assertRaises(exceptions.CommandError, self.run_command, cmd) def test_boot_files(self): testfile = os.path.join(os.path.dirname(__file__), 'testfile.txt') with open(testfile) as testfile_fd: data = testfile_fd.read() expected = base64.b64encode(data.encode('utf-8')).decode('utf-8') cmd = ('boot some-server --flavor 1 --image %s' ' --file /tmp/foo=%s --file /tmp/bar=%s') self.run_command(cmd % (FAKE_UUID_1, testfile, testfile)) self.assert_called_anytime( 'POST', '/servers', {'server': { 'flavorRef': '1', 'name': 'some-server', 'imageRef': FAKE_UUID_1, 'min_count': 1, 'max_count': 1, 'personality': [ {'path': '/tmp/bar', 'contents': expected}, {'path': '/tmp/foo', 'contents': expected}, ] }}, ) def test_boot_invalid_files(self): invalid_file = os.path.join(os.path.dirname(__file__), 'asdfasdfasdfasdf') cmd = ('boot some-server --flavor 1 --image %s' ' --file /foo=%s' % (FAKE_UUID_1, invalid_file)) self.assertRaises(exceptions.CommandError, self.run_command, cmd) def test_boot_files_2_57(self): """Tests that trying to run the boot command with the --file option after microversion 2.56 fails. """ testfile = os.path.join(os.path.dirname(__file__), 'testfile.txt') cmd = ('boot some-server --flavor 1 --image %s' ' --file /tmp/foo=%s') self.assertRaises(SystemExit, self.run_command, cmd % (FAKE_UUID_1, testfile), api_version='2.57') def test_boot_max_min_count(self): self.run_command('boot --image %s --flavor 1 --min-count 1' ' --max-count 3 server' % FAKE_UUID_1) self.assert_called_anytime( 'POST', '/servers', { 'server': { 'flavorRef': '1', 'name': 'server', 'imageRef': FAKE_UUID_1, 'min_count': 1, 'max_count': 3, } }) def test_boot_invalid_min_count(self): cmd = 'boot --image %s --flavor 1 --min-count 0 server' % FAKE_UUID_1 self.assertRaises(exceptions.CommandError, self.run_command, cmd) def test_boot_min_max_count(self): self.run_command('boot --image %s --flavor 1 --max-count 3 server' % FAKE_UUID_1) self.assert_called_anytime( 'POST', '/servers', { 'server': { 'flavorRef': '1', 'name': 'server', 'imageRef': FAKE_UUID_1, 'min_count': 1, 'max_count': 3, } }) self.run_command('boot --image %s --flavor 1 --min-count 3 server' % FAKE_UUID_1) self.assert_called_anytime( 'POST', '/servers', { 'server': { 'flavorRef': '1', 'name': 'server', 'imageRef': FAKE_UUID_1, 'min_count': 3, 'max_count': 3, } }) self.run_command('boot --image %s --flavor 1 ' '--min-count 3 --max-count 3 server' % FAKE_UUID_1) self.assert_called_anytime( 'POST', '/servers', { 'server': { 'flavorRef': '1', 'name': 'server', 'imageRef': FAKE_UUID_1, 'min_count': 3, 'max_count': 3, } }) self.run_command('boot --image %s --flavor 1 ' '--min-count 3 --max-count 5 server' % FAKE_UUID_1) self.assert_called_anytime( 'POST', '/servers', { 'server': { 'flavorRef': '1', 'name': 'server', 'imageRef': FAKE_UUID_1, 'min_count': 3, 'max_count': 5, } }) cmd = ('boot --image %s --flavor 1 --min-count 3 --max-count 1 serv' % FAKE_UUID_1) self.assertRaises(exceptions.CommandError, self.run_command, cmd) @mock.patch('novaclient.v2.shell._poll_for_status') def test_boot_with_poll(self, poll_method): self.run_command('boot --flavor 1 --image %s some-server --poll' % FAKE_UUID_1) self.assert_called_anytime( 'POST', '/servers', {'server': { 'flavorRef': '1', 'name': 'some-server', 'imageRef': FAKE_UUID_1, 'min_count': 1, 'max_count': 1, }}, ) self.assertEqual(1, poll_method.call_count) poll_method.assert_has_calls( [mock.call(self.shell.cs.servers.get, '1234', 'building', ['active'])]) def test_boot_with_poll_to_check_VM_state_error(self): self.assertRaises(exceptions.ResourceInErrorState, self.run_command, 'boot --flavor 1 --image %s some-bad-server --poll' % FAKE_UUID_1) def test_boot_named_flavor(self): self.run_command(["boot", "--image", FAKE_UUID_1, "--flavor", "512 MiB Server", "--max-count", "3", "server"]) self.assert_called('GET', '/v2/images/' + FAKE_UUID_1, pos=0) self.assert_called('GET', '/flavors/512 MiB Server', pos=1) self.assert_called('GET', '/flavors?is_public=None', pos=2) self.assert_called('GET', '/flavors/2', pos=3) self.assert_called( 'POST', '/servers', { 'server': { 'flavorRef': '2', 'name': 'server', 'imageRef': FAKE_UUID_1, 'min_count': 1, 'max_count': 3, } }, pos=4) def test_boot_invalid_ephemeral_data_format(self): cmd = ('boot --flavor 1 --image %s --ephemeral 1 some-server' % FAKE_UUID_1) self.assertRaises(argparse.ArgumentTypeError, self.run_command, cmd) def test_boot_with_tags(self): self.run_command('boot --flavor 1 --image %s --nic auto ' 'some-server --tags tag1,tag2' % FAKE_UUID_1, api_version='2.52') self.assert_called_anytime( 'POST', '/servers', {'server': { 'flavorRef': '1', 'name': 'some-server', 'imageRef': FAKE_UUID_1, 'min_count': 1, 'max_count': 1, 'networks': 'auto', 'tags': ['tag1', 'tag2'] }}, ) def test_boot_without_tags_v252(self): self.run_command('boot --flavor 1 --image %s --nic auto ' 'some-server' % FAKE_UUID_1, api_version='2.52') self.assert_called_anytime( 'POST', '/servers', {'server': { 'flavorRef': '1', 'name': 'some-server', 'imageRef': FAKE_UUID_1, 'min_count': 1, 'max_count': 1, 'networks': 'auto', }}, ) def test_boot_with_tags_pre_v2_52(self): cmd = ('boot --flavor 1 --image %s some-server ' '--tags tag1,tag2' % FAKE_UUID_1) self.assertRaises(SystemExit, self.run_command, cmd, api_version='2.51') def test_boot_with_single_trusted_image_certificates(self): self.run_command('boot --flavor 1 --image %s --nic auto some-server ' '--trusted-image-certificate-id id1' % FAKE_UUID_1, api_version='2.63') self.assert_called_anytime( 'POST', '/servers', {'server': { 'flavorRef': '1', 'name': 'some-server', 'imageRef': FAKE_UUID_1, 'min_count': 1, 'max_count': 1, 'networks': 'auto', 'trusted_image_certificates': ['id1'] }}, ) def test_boot_with_multiple_trusted_image_certificates(self): self.run_command('boot --flavor 1 --image %s --nic auto some-server ' '--trusted-image-certificate-id id1 ' '--trusted-image-certificate-id id2' % FAKE_UUID_1, api_version='2.63') self.assert_called_anytime( 'POST', '/servers', {'server': { 'flavorRef': '1', 'name': 'some-server', 'imageRef': FAKE_UUID_1, 'min_count': 1, 'max_count': 1, 'networks': 'auto', 'trusted_image_certificates': ['id1', 'id2'] }}, ) def test_boot_with_trusted_image_certificates_envar(self): self.useFixture(fixtures.EnvironmentVariable( 'OS_TRUSTED_IMAGE_CERTIFICATE_IDS', 'var_id1,var_id2')) self.run_command('boot --flavor 1 --image %s --nic auto some-server' % FAKE_UUID_1, api_version='2.63') self.assert_called_anytime( 'POST', '/servers', {'server': { 'flavorRef': '1', 'name': 'some-server', 'imageRef': FAKE_UUID_1, 'min_count': 1, 'max_count': 1, 'networks': 'auto', 'trusted_image_certificates': ['var_id1', 'var_id2'] }}, ) def test_boot_without_trusted_image_certificates_v263(self): self.run_command('boot --flavor 1 --image %s --nic auto some-server' % FAKE_UUID_1, api_version='2.63') self.assert_called_anytime( 'POST', '/servers', {'server': { 'flavorRef': '1', 'name': 'some-server', 'imageRef': FAKE_UUID_1, 'min_count': 1, 'max_count': 1, 'networks': 'auto', }}, ) def test_boot_with_trusted_image_certificates_pre_v263(self): cmd = ('boot --flavor 1 --image %s some-server ' '--trusted-image-certificate-id id1 ' '--trusted-image-certificate-id id2' % FAKE_UUID_1) self.assertRaises(SystemExit, self.run_command, cmd, api_version='2.62') # OS_TRUSTED_IMAGE_CERTIFICATE_IDS environment variable is not supported in # microversions < 2.63 (should result in an UnsupportedAttribute exception) def test_boot_with_trusted_image_certificates_envar_pre_v263(self): self.useFixture(fixtures.EnvironmentVariable( 'OS_TRUSTED_IMAGE_CERTIFICATE_IDS', 'var_id1,var_id2')) cmd = ('boot --flavor 1 --image %s --nic auto some-server ' % FAKE_UUID_1) self.assertRaises(exceptions.UnsupportedAttribute, self.run_command, cmd, api_version='2.62') def test_boot_with_trusted_image_certificates_arg_and_envvar(self): """Tests that if both the environment variable and argument are specified, the argument takes precedence. """ self.useFixture(fixtures.EnvironmentVariable( 'OS_TRUSTED_IMAGE_CERTIFICATE_IDS', 'cert1')) self.run_command('boot --flavor 1 --image %s --nic auto ' '--trusted-image-certificate-id cert2 some-server' % FAKE_UUID_1, api_version='2.63') self.assert_called_anytime( 'POST', '/servers', {'server': { 'flavorRef': '1', 'name': 'some-server', 'imageRef': FAKE_UUID_1, 'min_count': 1, 'max_count': 1, 'networks': 'auto', 'trusted_image_certificates': ['cert2'] }}, ) @mock.patch.object(servers.Server, 'networks', new_callable=mock.PropertyMock) def test_boot_with_not_found_when_accessing_addresses_attribute( self, mock_networks): mock_networks.side_effect = exceptions.NotFound( 404, 'Instance %s could not be found.' % FAKE_UUID_1) ex = self.assertRaises( exceptions.CommandError, self.run_command, 'boot --flavor 1 --image %s some-server' % FAKE_UUID_2) self.assertIn('Instance %s could not be found.' % FAKE_UUID_1, str(ex)) def test_boot_with_host_v274(self): self.run_command('boot --flavor 1 --image %s ' '--host new-host --nic auto ' 'some-server' % FAKE_UUID_1, api_version='2.74') self.assert_called_anytime( 'POST', '/servers', {'server': { 'flavorRef': '1', 'name': 'some-server', 'imageRef': FAKE_UUID_1, 'min_count': 1, 'max_count': 1, 'networks': 'auto', 'host': 'new-host', }}, ) def test_boot_with_hypervisor_hostname_v274(self): self.run_command('boot --flavor 1 --image %s --nic auto ' '--hypervisor-hostname new-host ' 'some-server' % FAKE_UUID_1, api_version='2.74') self.assert_called_anytime( 'POST', '/servers', {'server': { 'flavorRef': '1', 'name': 'some-server', 'imageRef': FAKE_UUID_1, 'min_count': 1, 'max_count': 1, 'networks': 'auto', 'hypervisor_hostname': 'new-host', }}, ) def test_boot_with_host_and_hypervisor_hostname_v274(self): self.run_command('boot --flavor 1 --image %s ' '--host new-host --nic auto ' '--hypervisor-hostname new-host ' 'some-server' % FAKE_UUID_1, api_version='2.74') self.assert_called_anytime( 'POST', '/servers', {'server': { 'flavorRef': '1', 'name': 'some-server', 'imageRef': FAKE_UUID_1, 'min_count': 1, 'max_count': 1, 'networks': 'auto', 'host': 'new-host', 'hypervisor_hostname': 'new-host', }}, ) def test_boot_with_host_pre_v274(self): cmd = ('boot --flavor 1 --image %s --nic auto ' '--host new-host some-server' % FAKE_UUID_1) self.assertRaises(SystemExit, self.run_command, cmd, api_version='2.73') def test_boot_with_hypervisor_hostname_pre_v274(self): cmd = ('boot --flavor 1 --image %s --nic auto ' '--hypervisor-hostname new-host some-server' % FAKE_UUID_1) self.assertRaises(SystemExit, self.run_command, cmd, api_version='2.73') def test_boot_with_host_and_hypervisor_hostname_pre_v274(self): cmd = ('boot --flavor 1 --image %s --nic auto ' '--host new-host --hypervisor-hostname new-host some-server' % FAKE_UUID_1) self.assertRaises(SystemExit, self.run_command, cmd, api_version='2.73') def test_boot_with_hostname(self): self.run_command( 'boot --flavor 1 --image %s ' '--hostname my-hostname --nic auto ' 'some-server' % FAKE_UUID_1, api_version='2.90') self.assert_called_anytime( 'POST', '/servers', {'server': { 'flavorRef': '1', 'name': 'some-server', 'imageRef': FAKE_UUID_1, 'min_count': 1, 'max_count': 1, 'networks': 'auto', 'hostname': 'my-hostname', }}, ) def test_boot_with_hostname_pre_v290(self): cmd = ( 'boot --flavor 1 --image %s --nic auto ' '--hostname my-hostname some-server' % FAKE_UUID_1 ) self.assertRaises( SystemExit, self.run_command, cmd, api_version='2.89') def test_flavor_list(self): out, _ = self.run_command('flavor-list') self.assert_called_anytime('GET', '/flavors/detail') self.assertNotIn('Description', out) def test_flavor_list_with_description(self): """Tests that the description column is added for version >= 2.55.""" out, _ = self.run_command('flavor-list', api_version='2.55') self.assert_called_anytime('GET', '/flavors/detail') self.assertIn('Description', out) def test_flavor_list_with_extra_specs(self): self.run_command('flavor-list --extra-specs') self.assert_called('GET', '/flavors/aa1/os-extra_specs') self.assert_called_anytime('GET', '/flavors/detail') def test_flavor_list_with_extra_specs_2_61_or_later(self): """Tests that the 'os-extra_specs' API is not called when the '--extra-specs' option is specified since microversion 2.61. """ out, _ = self.run_command('flavor-list --extra-specs', api_version='2.61') self.assert_not_called('GET', '/flavors/aa1/os-extra_specs') self.assert_called_anytime('GET', '/flavors/detail') self.assertIn('extra_specs', out) def test_flavor_list_with_all(self): self.run_command('flavor-list --all') self.assert_called('GET', '/flavors/detail?is_public=None') def test_flavor_list_with_limit_and_marker(self): self.run_command('flavor-list --marker 1 --limit 2') self.assert_called('GET', '/flavors/detail?limit=2&marker=1') def test_flavor_list_with_min_disk(self): self.run_command('flavor-list --min-disk 20') self.assert_called('GET', '/flavors/detail?minDisk=20') def test_flavor_list_with_min_ram(self): self.run_command('flavor-list --min-ram 512') self.assert_called('GET', '/flavors/detail?minRam=512') def test_flavor_list_with_sort_key_dir(self): self.run_command('flavor-list --sort-key id --sort-dir asc') self.assert_called('GET', '/flavors/detail?sort_dir=asc&sort_key=id') def test_flavor_show(self): out, _ = self.run_command('flavor-show 1') self.assert_called_anytime('GET', '/flavors/1') self.assertNotIn('description', out) def test_flavor_show_with_description(self): """Tests that the description is shown in version >= 2.55.""" out, _ = self.run_command('flavor-show 1', api_version='2.55') self.assert_called('GET', '/flavors/1', pos=-2) self.assert_called('GET', '/flavors/1/os-extra_specs', pos=-1) self.assertIn('description', out) def test_flavor_show_2_61_or_later(self): """Tests that the 'os-extra_specs' is not called in version >= 2.61.""" out, _ = self.run_command('flavor-show 1', api_version='2.61') self.assert_not_called('GET', '/flavors/1/os-extra_specs') self.assert_called_anytime('GET', '/flavors/1') self.assertIn('extra_specs', out) def test_flavor_show_with_alphanum_id(self): self.run_command('flavor-show aa1') self.assert_called_anytime('GET', '/flavors/aa1') def test_flavor_show_by_name(self): self.run_command(['flavor-show', '128 MiB Server']) self.assert_called('GET', '/flavors/128 MiB Server', pos=0) self.assert_called('GET', '/flavors?is_public=None', pos=1) self.assert_called('GET', '/flavors/aa1', pos=2) self.assert_called('GET', '/flavors/aa1/os-extra_specs', pos=3) def test_flavor_show_by_name_priv(self): self.run_command(['flavor-show', '512 MiB Server']) self.assert_called('GET', '/flavors/512 MiB Server', pos=0) self.assert_called('GET', '/flavors?is_public=None', pos=1) self.assert_called('GET', '/flavors/2', pos=2) self.assert_called('GET', '/flavors/2/os-extra_specs', pos=3) def test_flavor_key_set(self): self.run_command('flavor-key 1 set k1=v1') self.assert_called('POST', '/flavors/1/os-extra_specs', {'extra_specs': {'k1': 'v1'}}) def test_flavor_key_unset(self): self.run_command('flavor-key 1 unset k1') self.assert_called('DELETE', '/flavors/1/os-extra_specs/k1') def test_flavor_access_list_flavor(self): self.run_command('flavor-access-list --flavor 2') self.assert_called('GET', '/flavors/2/os-flavor-access') def test_flavor_access_list_no_filter(self): cmd = 'flavor-access-list' self.assertRaises(exceptions.CommandError, self.run_command, cmd) def test_flavor_access_list_public(self): cmd = 'flavor-access-list --flavor 1' self.assertRaises(exceptions.CommandError, self.run_command, cmd) def test_flavor_access_add_by_id(self): self.run_command('flavor-access-add 2 proj2') self.assert_called('POST', '/flavors/2/action', {'addTenantAccess': {'tenant': 'proj2'}}) def test_flavor_access_add_by_name(self): self.run_command(['flavor-access-add', '512 MiB Server', 'proj2']) self.assert_called('POST', '/flavors/2/action', {'addTenantAccess': {'tenant': 'proj2'}}) def test_flavor_access_remove_by_id(self): self.run_command('flavor-access-remove 2 proj2') self.assert_called('POST', '/flavors/2/action', {'removeTenantAccess': {'tenant': 'proj2'}}) def test_flavor_access_remove_by_name(self): self.run_command(['flavor-access-remove', '512 MiB Server', 'proj2']) self.assert_called('POST', '/flavors/2/action', {'removeTenantAccess': {'tenant': 'proj2'}}) def test_create_image(self): self.run_command('image-create sample-server mysnapshot') self.assert_called( 'POST', '/servers/1234/action', {'createImage': {'name': 'mysnapshot', 'metadata': {}}}, ) def test_create_image_2_45(self): """Tests the image-create command with microversion 2.45 which does not change the output of the command, just how the response from the server is processed. """ self.run_command('image-create sample-server mysnapshot', api_version='2.45') self.assert_called( 'POST', '/servers/1234/action', {'createImage': {'name': 'mysnapshot', 'metadata': {}}}, ) def test_create_image_with_incorrect_metadata(self): cmd = 'image-create sample-server mysnapshot --metadata foo' result = self.assertRaises(argparse.ArgumentTypeError, self.run_command, cmd) expected = "'['foo']' is not in the format of 'key=value'" self.assertEqual(expected, result.args[0]) def test_create_image_with_metadata(self): self.run_command( 'image-create sample-server mysnapshot --metadata mykey=123') self.assert_called( 'POST', '/servers/1234/action', {'createImage': {'name': 'mysnapshot', 'metadata': {'mykey': '123'}}}, ) def test_create_image_show(self): output, _err = self.run_command( 'image-create sample-server mysnapshot --show') self.assert_called_anytime( 'POST', '/servers/1234/action', {'createImage': {'name': 'mysnapshot', 'metadata': {}}}, ) self.assertIn('My Server Backup', output) self.assertIn('SAVING', output) @mock.patch('novaclient.v2.shell._poll_for_status') def test_create_image_with_poll(self, poll_method): self.run_command( 'image-create sample-server mysnapshot --poll') self.assert_called_anytime( 'POST', '/servers/1234/action', {'createImage': {'name': 'mysnapshot', 'metadata': {}}}, ) self.assertEqual(1, poll_method.call_count) poll_method.assert_has_calls( [mock.call(self.shell.cs.glance.find_image, fakes.FAKE_IMAGE_UUID_SNAPSHOT, 'snapshotting', ['active'])]) def test_create_image_with_poll_to_check_image_state_deleted(self): self.assertRaises( exceptions.InstanceInDeletedState, self.run_command, 'image-create sample-server mysnapshot_deleted --poll') def test_list(self): self.run_command('list') self.assert_called('GET', '/servers/detail') def test_list_minimal(self): self.run_command('list --minimal') self.assert_called('GET', '/servers') def test_list_deleted(self): self.run_command('list --deleted') self.assert_called('GET', '/servers/detail?deleted=True') def test_list_with_images(self): self.run_command('list --image %s' % FAKE_UUID_1) self.assert_called('GET', '/servers/detail?image=%s' % FAKE_UUID_1) def test_list_with_flavors(self): self.run_command('list --flavor 1') self.assert_called('GET', '/servers/detail?flavor=1') def test_list_by_tenant(self): self.run_command('list --tenant fake_tenant') self.assert_called( 'GET', '/servers/detail?all_tenants=1&tenant_id=fake_tenant') def test_list_by_user(self): self.run_command('list --user fake_user') self.assert_called( 'GET', '/servers/detail?user_id=fake_user') def test_list_with_single_sort_key_no_dir(self): self.run_command('list --sort 1') self.assert_called( 'GET', ('/servers/detail?sort_dir=desc&sort_key=1')) def test_list_with_single_sort_key_and_dir(self): self.run_command('list --sort 1:asc') self.assert_called( 'GET', ('/servers/detail?sort_dir=asc&sort_key=1')) def test_list_with_sort_keys_no_dir(self): self.run_command('list --sort 1,2') self.assert_called( 'GET', ('/servers/detail?sort_dir=desc&sort_dir=desc&' 'sort_key=1&sort_key=2')) def test_list_with_sort_keys_and_dirs(self): self.run_command('list --sort 1:asc,2:desc') self.assert_called( 'GET', ('/servers/detail?sort_dir=asc&sort_dir=desc&' 'sort_key=1&sort_key=2')) def test_list_with_sort_keys_and_some_dirs(self): self.run_command('list --sort 1,2:asc') self.assert_called( 'GET', ('/servers/detail?sort_dir=desc&sort_dir=asc&' 'sort_key=1&sort_key=2')) def test_list_with_invalid_sort_dir_one(self): cmd = 'list --sort 1:foo' self.assertRaises(exceptions.CommandError, self.run_command, cmd) def test_list_with_invalid_sort_dir_two(self): cmd = 'list --sort 1:asc,2:foo' self.assertRaises(exceptions.CommandError, self.run_command, cmd) def test_list_sortby_index_with_sort(self): # sortby_index is None if there is sort information for cmd in ['list --sort key', 'list --sort key:desc', 'list --sort key1,key2:asc']: with mock.patch('novaclient.utils.print_list') as mock_print_list: self.run_command(cmd) mock_print_list.assert_called_once_with( mock.ANY, mock.ANY, mock.ANY, sortby_index=None) def test_list_sortby_index_without_sort(self): # sortby_index is 1 without sort information for cmd in ['list', 'list --minimal', 'list --deleted']: with mock.patch('novaclient.utils.print_list') as mock_print_list: self.run_command(cmd) mock_print_list.assert_called_once_with( mock.ANY, mock.ANY, mock.ANY, sortby_index=1) def test_list_fields(self): output, _err = self.run_command( 'list --fields ' 'host,security_groups,OS-EXT-MOD:some_thing') self.assert_called('GET', '/servers/detail') self.assertIn('computenode1', output) self.assertIn('securitygroup1', output) self.assertIn('OS-EXT-MOD: Some Thing', output) self.assertIn('mod_some_thing_value', output) # Testing the 'networks' field that is explicitly added to the # existing fields list. output, _err = self.run_command('list --fields networks') self.assertIn('Networks', output) self.assertIn('10.11.12.13', output) self.assertIn('5.6.7.8', output) @mock.patch( 'novaclient.tests.unit.v2.fakes.FakeSessionClient.get_servers_detail') def test_list_fields_no_instances(self, mock_get_servers_detail): mock_get_servers_detail.return_value = (200, {}, {"servers": []}) stdout, _stderr = self.run_command('list --fields metadata,networks') # Because there are no instances, you just get the default columns # rather than the ones you actually asked for (Metadata, Networks). defaults = 'ID | Name | Status | Task State | Power State | Networks' self.assertIn(defaults, stdout) def test_list_invalid_fields(self): self.assertRaises(exceptions.CommandError, self.run_command, 'list --fields host,security_groups,' 'OS-EXT-MOD:some_thing,invalid') self.assertRaises(exceptions.CommandError, self.run_command, 'list --fields __dict__') self.assertRaises(exceptions.CommandError, self.run_command, 'list --fields update') self.assertRaises(exceptions.CommandError, self.run_command, 'list --fields __init__') self.assertRaises(exceptions.CommandError, self.run_command, 'list --fields __module__,updated') def test_list_with_marker(self): self.run_command('list --marker some-uuid') self.assert_called('GET', '/servers/detail?marker=some-uuid') def test_list_with_limit(self): self.run_command('list --limit 3') self.assert_called('GET', '/servers/detail?limit=3') def test_list_with_changes_since(self): self.run_command('list --changes-since 2016-02-29T06:23:22') self.assert_called( 'GET', '/servers/detail?changes-since=2016-02-29T06%3A23%3A22') def test_list_with_changes_since_invalid_value(self): self.assertRaises(exceptions.CommandError, self.run_command, 'list --changes-since 0123456789') def test_list_with_changes_before(self): self.run_command('list --changes-before 2016-02-29T06:23:22', api_version='2.66') self.assert_called( 'GET', '/servers/detail?changes-before=2016-02-29T06%3A23%3A22') def test_list_with_changes_before_invalid_value(self): ex = self.assertRaises(exceptions.CommandError, self.run_command, 'list --changes-before 0123456789', api_version='2.66') self.assertIn('Invalid changes-before value', str(ex)) def test_list_with_changes_before_pre_v266_not_allowed(self): self.assertRaises(SystemExit, self.run_command, 'list --changes-before 2016-02-29T06:23:22', api_version='2.65') def test_list_with_availability_zone(self): self.run_command('list --availability-zone nova') self.assert_called('GET', '/servers/detail?availability_zone=nova') def test_list_with_key_name(self): self.run_command('list --key-name my_key') self.assert_called('GET', '/servers/detail?key_name=my_key') def test_list_with_config_drive(self): self.run_command('list --config-drive') self.assert_called('GET', '/servers/detail?config_drive=True') def test_list_with_no_config_drive(self): self.run_command('list --no-config-drive') self.assert_called('GET', '/servers/detail?config_drive=False') def test_list_with_conflicting_config_drive(self): self.assertRaises(SystemExit, self.run_command, 'list --config-drive --no-config-drive') def test_list_with_progress(self): self.run_command('list --progress 100') self.assert_called('GET', '/servers/detail?progress=100') def test_list_with_0_progress(self): self.run_command('list --progress 0') self.assert_called('GET', '/servers/detail?progress=0') def test_list_with_vm_state(self): self.run_command('list --vm-state active') self.assert_called('GET', '/servers/detail?vm_state=active') def test_list_with_task_state(self): self.run_command('list --task-state reboot_started') self.assert_called('GET', '/servers/detail?task_state=reboot_started') def test_list_with_power_state(self): self.run_command('list --power-state 1') self.assert_called('GET', '/servers/detail?power_state=1') def test_list_with_power_state_filter_for_0_state(self): self.run_command('list --power-state 0') self.assert_called('GET', '/servers/detail?power_state=0') def test_list_fields_redundant(self): output, _err = self.run_command('list --fields id,status,status') header = output.splitlines()[1] self.assertEqual(1, header.count('ID')) self.assertEqual(0, header.count('Id')) self.assertEqual(1, header.count('Status')) def test_meta_parsing(self): meta = ['key1=meta1', 'key2=meta2'] ref = {'key1': 'meta1', 'key2': 'meta2'} parsed_meta = novaclient.v2.shell._meta_parsing(meta) self.assertEqual(ref, parsed_meta) def test_reboot(self): self.run_command('reboot sample-server') self.assert_called('POST', '/servers/1234/action', {'reboot': {'type': 'SOFT'}}) self.run_command('reboot sample-server --hard') self.assert_called('POST', '/servers/1234/action', {'reboot': {'type': 'HARD'}}) def test_reboot_many(self): self.run_command('reboot sample-server sample-server2') self.assert_called('POST', '/servers/1234/action', {'reboot': {'type': 'SOFT'}}, pos=-2) self.assert_called('POST', '/servers/5678/action', {'reboot': {'type': 'SOFT'}}, pos=-1) def test_rebuild(self): output, _err = self.run_command('rebuild sample-server %s' % FAKE_UUID_1) self.assert_called('GET', '/servers?name=sample-server', pos=0) self.assert_called('GET', '/servers/1234', pos=1) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1, pos=2) self.assert_called('POST', '/servers/1234/action', {'rebuild': {'imageRef': FAKE_UUID_1}}, pos=3) self.assert_called('GET', '/flavors/1', pos=4) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=5) self.assertIn('adminPass', output) def test_rebuild_password(self): output, _err = self.run_command('rebuild sample-server %s' ' --rebuild-password asdf' % FAKE_UUID_1) self.assert_called('GET', '/servers?name=sample-server', pos=0) self.assert_called('GET', '/servers/1234', pos=1) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1, pos=2) self.assert_called('POST', '/servers/1234/action', {'rebuild': {'imageRef': FAKE_UUID_1, 'adminPass': 'asdf'}}, pos=3) self.assert_called('GET', '/flavors/1', pos=4) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=5) self.assertIn('adminPass', output) def test_rebuild_preserve_ephemeral(self): self.run_command('rebuild sample-server %s --preserve-ephemeral' % FAKE_UUID_1) self.assert_called('GET', '/servers?name=sample-server', pos=0) self.assert_called('GET', '/servers/1234', pos=1) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1, pos=2) self.assert_called('POST', '/servers/1234/action', {'rebuild': {'imageRef': FAKE_UUID_1, 'preserve_ephemeral': True}}, pos=3) self.assert_called('GET', '/flavors/1', pos=4) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=5) def test_rebuild_name_meta(self): self.run_command('rebuild sample-server %s --name asdf --meta ' 'foo=bar' % FAKE_UUID_1) self.assert_called('GET', '/servers?name=sample-server', pos=0) self.assert_called('GET', '/servers/1234', pos=1) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1, pos=2) self.assert_called('POST', '/servers/1234/action', {'rebuild': {'imageRef': FAKE_UUID_1, 'name': 'asdf', 'metadata': {'foo': 'bar'}}}, pos=3) self.assert_called('GET', '/flavors/1', pos=4) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=5) def test_rebuild_reset_keypair(self): self.run_command('rebuild sample-server %s --key-name test_keypair' % FAKE_UUID_1, api_version='2.54') self.assert_called('GET', '/servers?name=sample-server', pos=0) self.assert_called('GET', '/servers/1234', pos=1) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1, pos=2) self.assert_called('POST', '/servers/1234/action', {'rebuild': {'imageRef': FAKE_UUID_1, 'key_name': 'test_keypair', 'description': None}}, pos=3) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=4) def test_rebuild_unset_keypair(self): self.run_command('rebuild sample-server %s --key-unset' % FAKE_UUID_1, api_version='2.54') self.assert_called('GET', '/servers?name=sample-server', pos=0) self.assert_called('GET', '/servers/1234', pos=1) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1, pos=2) self.assert_called('POST', '/servers/1234/action', {'rebuild': {'imageRef': FAKE_UUID_1, 'key_name': None, 'description': None}}, pos=3) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=4) def test_rebuild_unset_keypair_with_key_name(self): ex = self.assertRaises( exceptions.CommandError, self.run_command, 'rebuild sample-server %s --key-unset --key-name test_keypair' % FAKE_UUID_1, api_version='2.54') self.assertIn("Cannot specify '--key-unset' with '--key-name'.", str(ex)) def test_rebuild_with_incorrect_metadata(self): cmd = 'rebuild sample-server %s --name asdf --meta foo' % FAKE_UUID_1 result = self.assertRaises(argparse.ArgumentTypeError, self.run_command, cmd) expected = "'['foo']' is not in the format of 'key=value'" self.assertEqual(expected, result.args[0]) def test_rebuild_user_data_2_56(self): """Tests that trying to run the rebuild command with the --user-data* options before microversion 2.57 fails. """ cmd = 'rebuild sample-server %s --user-data test' % FAKE_UUID_1 self.assertRaises(SystemExit, self.run_command, cmd, api_version='2.56') cmd = 'rebuild sample-server %s --user-data-unset' % FAKE_UUID_1 self.assertRaises(SystemExit, self.run_command, cmd, api_version='2.56') def test_rebuild_files_2_57(self): """Tests that trying to run the rebuild command with the --file option after microversion 2.56 fails. """ testfile = os.path.join(os.path.dirname(__file__), 'testfile.txt') cmd = 'rebuild sample-server %s --file /tmp/foo=%s' self.assertRaises(SystemExit, self.run_command, cmd % (FAKE_UUID_1, testfile), api_version='2.57') def test_rebuild_change_user_data(self): testfile = os.path.join(os.path.dirname(__file__), 'testfile.txt') with open(testfile) as testfile_fd: data = testfile_fd.read().encode('utf-8') expected_file_data = servers.ServerManager.transform_userdata(data) self.run_command('rebuild sample-server %s --user-data %s' % (FAKE_UUID_1, testfile), api_version='2.57') self.assert_called('GET', '/servers?name=sample-server', pos=0) self.assert_called('GET', '/servers/1234', pos=1) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1, pos=2) self.assert_called('POST', '/servers/1234/action', {'rebuild': {'imageRef': FAKE_UUID_1, 'user_data': expected_file_data, 'description': None}}, pos=3) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=4) def test_rebuild_invalid_user_data(self): invalid_file = os.path.join(os.path.dirname(__file__), 'no_such_file') cmd = ('rebuild sample-server %s --user-data %s' % (FAKE_UUID_1, invalid_file)) ex = self.assertRaises(exceptions.CommandError, self.run_command, cmd, api_version='2.57') self.assertIn("Can't open '%(user_data)s': " "[Errno 2] No such file or directory: '%(user_data)s'" % {'user_data': invalid_file}, str(ex)) def test_rebuild_unset_user_data(self): self.run_command('rebuild sample-server %s --user-data-unset' % FAKE_UUID_1, api_version='2.57') self.assert_called('GET', '/servers?name=sample-server', pos=0) self.assert_called('GET', '/servers/1234', pos=1) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1, pos=2) self.assert_called('POST', '/servers/1234/action', {'rebuild': {'imageRef': FAKE_UUID_1, 'user_data': None, 'description': None}}, pos=3) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=4) def test_rebuild_user_data_and_unset_user_data(self): """Tests that trying to set --user-data and --unset-user-data in the same rebuild call fails. """ cmd = ('rebuild sample-server %s --user-data x --user-data-unset' % FAKE_UUID_1) ex = self.assertRaises(exceptions.CommandError, self.run_command, cmd, api_version='2.57') self.assertIn("Cannot specify '--user-data-unset' with " "'--user-data'.", str(ex)) def test_rebuild_with_single_trusted_image_certificates(self): self.run_command('rebuild sample-server %s ' '--trusted-image-certificate-id id1' % FAKE_UUID_1, api_version='2.63') self.assert_called('GET', '/servers?name=sample-server', pos=0) self.assert_called('GET', '/servers/1234', pos=1) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1, pos=2) self.assert_called('POST', '/servers/1234/action', {'rebuild': {'imageRef': FAKE_UUID_1, 'description': None, 'trusted_image_certificates': ['id1'] } }, pos=3) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=4) def test_rebuild_with_multiple_trusted_image_certificate_ids(self): self.run_command('rebuild sample-server %s ' '--trusted-image-certificate-id id1 ' '--trusted-image-certificate-id id2' % FAKE_UUID_1, api_version='2.63') self.assert_called('GET', '/servers?name=sample-server', pos=0) self.assert_called('GET', '/servers/1234', pos=1) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1, pos=2) self.assert_called('POST', '/servers/1234/action', {'rebuild': {'imageRef': FAKE_UUID_1, 'description': None, 'trusted_image_certificates': ['id1', 'id2'] } }, pos=3) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=4) def test_rebuild_with_trusted_image_certificates_envar(self): self.useFixture(fixtures.EnvironmentVariable( 'OS_TRUSTED_IMAGE_CERTIFICATE_IDS', 'var_id1,var_id2')) self.run_command('rebuild sample-server %s' % FAKE_UUID_1, api_version='2.63') self.assert_called('GET', '/servers?name=sample-server', pos=0) self.assert_called('GET', '/servers/1234', pos=1) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1, pos=2) self.assert_called('POST', '/servers/1234/action', {'rebuild': {'imageRef': FAKE_UUID_1, 'description': None, 'trusted_image_certificates': ['var_id1', 'var_id2']} }, pos=3) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=4) def test_rebuild_without_trusted_image_certificates_v263(self): self.run_command('rebuild sample-server %s' % FAKE_UUID_1, api_version='2.63') self.assert_called('GET', '/servers?name=sample-server', pos=0) self.assert_called('GET', '/servers/1234', pos=1) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1, pos=2) self.assert_called('POST', '/servers/1234/action', {'rebuild': {'imageRef': FAKE_UUID_1, 'description': None, } }, pos=3) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=4) def test_rebuild_with_trusted_image_certificates_pre_v263(self): cmd = ('rebuild sample-server %s' '--trusted-image-certificate-id id1 ' '--trusted-image-certificate-id id2' % FAKE_UUID_1) self.assertRaises(SystemExit, self.run_command, cmd, api_version='2.62') # OS_TRUSTED_IMAGE_CERTIFICATE_IDS environment variable is not supported in # microversions < 2.63 (should result in an UnsupportedAttribute exception) def test_rebuild_with_trusted_image_certificates_envar_pre_v263(self): self.useFixture(fixtures.EnvironmentVariable( 'OS_TRUSTED_IMAGE_CERTIFICATE_IDS', 'var_id1,var_id2')) cmd = ('rebuild sample-server %s' % FAKE_UUID_1) self.assertRaises(exceptions.UnsupportedAttribute, self.run_command, cmd, api_version='2.62') def test_rebuild_with_trusted_image_certificates_unset(self): """Tests explicitly unsetting the existing server trusted image certificate IDs. """ self.run_command('rebuild sample-server %s ' '--trusted-image-certificates-unset' % FAKE_UUID_1, api_version='2.63') self.assert_called('GET', '/servers?name=sample-server', pos=0) self.assert_called('GET', '/servers/1234', pos=1) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1, pos=2) self.assert_called('POST', '/servers/1234/action', {'rebuild': {'imageRef': FAKE_UUID_1, 'description': None, 'trusted_image_certificates': None } }, pos=3) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=4) def test_rebuild_with_trusted_image_certificates_unset_arg_conflict(self): """Tests the error condition that trusted image certs are both unset and set via argument during rebuild. """ ex = self.assertRaises( exceptions.CommandError, self.run_command, 'rebuild sample-server %s --trusted-image-certificate-id id1 ' '--trusted-image-certificates-unset' % FAKE_UUID_1, api_version='2.63') self.assertIn("Cannot specify '--trusted-image-certificates-unset' " "with '--trusted-image-certificate-id'", str(ex)) def test_rebuild_with_trusted_image_certificates_unset_env_conflict(self): """Tests the error condition that trusted image certs are both unset and set via environment variable during rebuild. """ self.useFixture(fixtures.EnvironmentVariable( 'OS_TRUSTED_IMAGE_CERTIFICATE_IDS', 'var_id1')) ex = self.assertRaises( exceptions.CommandError, self.run_command, 'rebuild sample-server %s --trusted-image-certificates-unset' % FAKE_UUID_1, api_version='2.63') self.assertIn("Cannot specify '--trusted-image-certificates-unset' " "with '--trusted-image-certificate-id'", str(ex)) def test_rebuild_with_trusted_image_certificates_arg_and_envar(self): """Tests that if both the environment variable and argument are specified, the argument takes precedence. """ self.useFixture(fixtures.EnvironmentVariable( 'OS_TRUSTED_IMAGE_CERTIFICATE_IDS', 'cert1')) self.run_command('rebuild sample-server ' '--trusted-image-certificate-id cert2 %s' % FAKE_UUID_1, api_version='2.63') self.assert_called('GET', '/servers?name=sample-server', pos=0) self.assert_called('GET', '/servers/1234', pos=1) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1, pos=2) self.assert_called('POST', '/servers/1234/action', {'rebuild': {'imageRef': FAKE_UUID_1, 'description': None, 'trusted_image_certificates': ['cert2']} }, pos=3) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=4) def test_rebuild_with_server_groups_in_response(self): out = self.run_command('rebuild sample-server %s' % FAKE_UUID_1, api_version='2.71')[0] self.assert_called('GET', '/servers?name=sample-server', pos=0) self.assert_called('GET', '/servers/1234', pos=1) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1, pos=2) self.assert_called('POST', '/servers/1234/action', {'rebuild': {'imageRef': FAKE_UUID_1, 'description': None, } }, pos=3) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=4) self.assertIn('server_groups', out) self.assertIn('a67359fb-d397-4697-88f1-f55e3ee7c499', out) def test_rebuild_without_server_groups_in_response(self): out = self.run_command('rebuild sample-server %s' % FAKE_UUID_1, api_version='2.70')[0] self.assert_called('GET', '/servers?name=sample-server', pos=0) self.assert_called('GET', '/servers/1234', pos=1) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1, pos=2) self.assert_called('POST', '/servers/1234/action', {'rebuild': {'imageRef': FAKE_UUID_1, 'description': None, } }, pos=3) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=4) self.assertNotIn('server_groups', out) self.assertNotIn('a67359fb-d397-4697-88f1-f55e3ee7c499', out) def test_rebuild_with_hostname(self): self.run_command( 'rebuild sample-server %s --hostname new-hostname' % FAKE_UUID_1, api_version='2.90') self.assert_called('GET', '/servers?name=sample-server', pos=0) self.assert_called('GET', '/servers/1234', pos=1) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1, pos=2) self.assert_called( 'POST', '/servers/1234/action', { 'rebuild': { 'imageRef': FAKE_UUID_1, 'description': None, 'hostname': 'new-hostname', }, }, pos=3) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=4) def test_rebuild_with_hostname_pre_v290(self): self.assertRaises( SystemExit, self.run_command, 'rebuild sample-server %s --hostname hostname' % FAKE_UUID_1, api_version='2.89') def test_start(self): self.run_command('start sample-server') self.assert_called('POST', '/servers/1234/action', {'os-start': None}) def test_start_with_all_tenants(self): self.run_command('start sample-server --all-tenants') self.assert_called('GET', '/servers?all_tenants=1&name=sample-server', pos=0) self.assert_called('GET', '/servers/1234', pos=1) self.assert_called('POST', '/servers/1234/action', {'os-start': None}) def test_stop(self): self.run_command('stop sample-server') self.assert_called('POST', '/servers/1234/action', {'os-stop': None}) def test_stop_with_all_tenants(self): self.run_command('stop sample-server --all-tenants') self.assert_called('GET', '/servers?all_tenants=1&name=sample-server', pos=0) self.assert_called('GET', '/servers/1234', pos=1) self.assert_called('POST', '/servers/1234/action', {'os-stop': None}) def test_pause(self): self.run_command('pause sample-server') self.assert_called('POST', '/servers/1234/action', {'pause': None}) def test_unpause(self): self.run_command('unpause sample-server') self.assert_called('POST', '/servers/1234/action', {'unpause': None}) def test_lock(self): self.run_command('lock sample-server') self.assert_called('POST', '/servers/1234/action', {'lock': None}) def test_lock_pre_v273(self): exp = self.assertRaises(SystemExit, self.run_command, 'lock sample-server --reason zombies', api_version='2.72') self.assertIn('2', str(exp)) def test_lock_v273(self): self.run_command('lock sample-server', api_version='2.73') self.assert_called('POST', '/servers/1234/action', {'lock': None}) self.run_command('lock sample-server --reason zombies', api_version='2.73') self.assert_called('POST', '/servers/1234/action', {'lock': {'locked_reason': 'zombies'}}) def test_unlock(self): self.run_command('unlock sample-server') self.assert_called('POST', '/servers/1234/action', {'unlock': None}) def test_suspend(self): self.run_command('suspend sample-server') self.assert_called('POST', '/servers/1234/action', {'suspend': None}) def test_resume(self): self.run_command('resume sample-server') self.assert_called('POST', '/servers/1234/action', {'resume': None}) def test_rescue(self): self.run_command('rescue sample-server') self.assert_called('POST', '/servers/1234/action', {'rescue': None}) def test_rescue_password(self): self.run_command('rescue sample-server --password asdf') self.assert_called('POST', '/servers/1234/action', {'rescue': {'adminPass': 'asdf'}}) def test_rescue_image(self): self.run_command('rescue sample-server --image %s' % FAKE_UUID_1) self.assert_called('POST', '/servers/1234/action', {'rescue': {'rescue_image_ref': FAKE_UUID_1}}) def test_unrescue(self): self.run_command('unrescue sample-server') self.assert_called('POST', '/servers/1234/action', {'unrescue': None}) def test_shelve(self): self.run_command('shelve sample-server') self.assert_called('POST', '/servers/1234/action', {'shelve': None}) def test_shelve_offload(self): self.run_command('shelve-offload sample-server') self.assert_called('POST', '/servers/1234/action', {'shelveOffload': None}) def test_unshelve(self): self.run_command('unshelve sample-server') self.assert_called('POST', '/servers/1234/action', {'unshelve': None}) def test_unshelve_pre_v277_with_az_fails(self): """Tests that trying to unshelve with an --availability-zone before 2.77 is an error. """ self.assertRaises(SystemExit, self.run_command, 'unshelve --availability-zone foo-az sample-server', api_version='2.76') def test_unshelve_v277(self): # Test backward compat without an AZ specified. self.run_command('unshelve sample-server', api_version='2.77') self.assert_called('POST', '/servers/1234/action', {'unshelve': None}) # Test with specifying an AZ. self.run_command('unshelve --availability-zone foo-az sample-server', api_version='2.77') self.assert_called('POST', '/servers/1234/action', {'unshelve': {'availability_zone': 'foo-az'}}) def test_migrate(self): self.run_command('migrate sample-server') self.assert_called('POST', '/servers/1234/action', {'migrate': None}) def test_migrate_pre_v256(self): self.assertRaises(SystemExit, self.run_command, 'migrate --host target-host sample-server', api_version='2.55') def test_migrate_v256(self): self.run_command('migrate sample-server', api_version='2.56') self.assert_called('POST', '/servers/1234/action', {'migrate': {}}) self.run_command('migrate --host target-host sample-server', api_version='2.56') self.assert_called('POST', '/servers/1234/action', {'migrate': {'host': 'target-host'}}) def test_update(self): self.run_command('update --name new-name sample-server') expected_put_body = { "server": { "name": "new-name" } } self.assert_called('GET', '/servers/1234', pos=-2) self.assert_called('PUT', '/servers/1234', expected_put_body, pos=-1) def test_update_with_description(self): self.run_command( 'update --description new-description sample-server', api_version='2.19') expected_put_body = { "server": { "description": "new-description" } } self.assert_called('GET', '/servers/1234', pos=-2) self.assert_called('PUT', '/servers/1234', expected_put_body, pos=-1) def test_update_with_description_pre_v219(self): self.assertRaises( SystemExit, self.run_command, 'update --description new-description sample-server', api_version='2.18') def test_update_with_hostname(self): self.run_command( 'update --hostname new-hostname sample-server', api_version='2.90') expected_put_body = { "server": { "hostname": "new-hostname" } } self.assert_called('GET', '/servers/1234', pos=-2) self.assert_called('PUT', '/servers/1234', expected_put_body, pos=-1) def test_update_with_hostname_pre_v290(self): self.assertRaises( SystemExit, self.run_command, 'update --hostname new-hostname sample-server', api_version='2.89') def test_resize(self): self.run_command('resize sample-server 1') self.assert_called('POST', '/servers/1234/action', {'resize': {'flavorRef': 1}}) def test_resize_confirm(self): self.run_command('resize-confirm sample-server') self.assert_called('POST', '/servers/1234/action', {'confirmResize': None}) def test_resize_revert(self): self.run_command('resize-revert sample-server') self.assert_called('POST', '/servers/1234/action', {'revertResize': None}) @mock.patch('getpass.getpass', mock.Mock(return_value='p')) def test_set_password(self): self.run_command('set-password sample-server') self.assert_called('POST', '/servers/1234/action', {'changePassword': {'adminPass': 'p'}}) def test_show(self): self.run_command('show 1234') self.assert_called('GET', '/servers?name=1234', pos=0) self.assert_called('GET', '/servers?name=1234', pos=1) self.assert_called('GET', '/servers/1234', pos=2) self.assert_called('GET', '/flavors/1', pos=3) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=4) def test_show_no_image(self): self.run_command('show 9012') self.assert_called('GET', '/servers/9012', pos=-2) self.assert_called('GET', '/flavors/1', pos=-1) def test_show_bad_id(self): self.assertRaises(exceptions.CommandError, self.run_command, 'show xxx') def test_show_unavailable_image_and_flavor(self): output, _ = self.run_command('show 9013') self.assert_called('GET', '/servers/9013', pos=-6) self.assert_called('GET', '/flavors/80645cf4-6ad3-410a-bbc8-6f3e1e291f51', pos=-5) self.assert_called('GET', '/v2/images/3e861307-73a6-4d1f-8d68-f68b03223032', pos=-1) self.assertIn('Image not found', output) self.assertIn('Flavor not found', output) def test_show_with_name_help(self): output, _ = self.run_command('show help') self.assert_called('GET', '/servers/9014', pos=-6) def test_show_with_server_groups_in_response(self): # Starting microversion 2.71, the 'server_groups' is included # in the output (the response). out = self.run_command('show 1234', api_version='2.71')[0] self.assert_called('GET', '/servers?name=1234', pos=0) self.assert_called('GET', '/servers?name=1234', pos=1) self.assert_called('GET', '/servers/1234', pos=2) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=3) self.assertIn('server_groups', out) self.assertIn('a67359fb-d397-4697-88f1-f55e3ee7c499', out) def test_show_without_server_groups_in_response(self): out = self.run_command('show 1234', api_version='2.70')[0] self.assert_called('GET', '/servers?name=1234', pos=0) self.assert_called('GET', '/servers?name=1234', pos=1) self.assert_called('GET', '/servers/1234', pos=2) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=3) self.assertNotIn('server_groups', out) self.assertNotIn('a67359fb-d397-4697-88f1-f55e3ee7c499', out) @mock.patch('novaclient.v2.shell.utils.print_dict') def test_print_server(self, mock_print_dict): self.run_command('show 5678') args, kwargs = mock_print_dict.call_args parsed_server = args[0] self.assertEqual('securitygroup1, securitygroup2', parsed_server['security_groups']) def test_delete(self): self.run_command('delete 1234') self.assert_called('DELETE', '/servers/1234') self.run_command('delete sample-server') self.assert_called('DELETE', '/servers/1234') def test_force_delete(self): self.run_command('force-delete 1234') self.assert_called('POST', '/servers/1234/action', {'forceDelete': None}) self.run_command('force-delete sample-server') self.assert_called('POST', '/servers/1234/action', {'forceDelete': None}) def test_restore(self): self.run_command('restore 1234') self.assert_called('POST', '/servers/1234/action', {'restore': None}) self.run_command('restore sample-server') self.assert_called('POST', '/servers/1234/action', {'restore': None}) def test_restore_withname(self): self.run_command('restore sample-server') self.assert_called('GET', '/servers?deleted=True&name=sample-server', pos=0) self.assert_called('GET', '/servers/1234', pos=1) self.assert_called('POST', '/servers/1234/action', {'restore': None}, pos=2) def test_delete_two_with_two_existent(self): self.run_command('delete 1234 5678') self.assert_called('DELETE', '/servers/1234', pos=-5) self.assert_called('DELETE', '/servers/5678', pos=-1) self.run_command('delete sample-server sample-server2') self.assert_called('GET', '/servers?name=sample-server', pos=-6) self.assert_called('GET', '/servers/1234', pos=-5) self.assert_called('DELETE', '/servers/1234', pos=-4) self.assert_called('GET', '/servers?name=sample-server2', pos=-3) self.assert_called('GET', '/servers/5678', pos=-2) self.assert_called('DELETE', '/servers/5678', pos=-1) def test_delete_two_with_two_existent_all_tenants(self): self.run_command('delete sample-server sample-server2 --all-tenants') self.assert_called('GET', '/servers?all_tenants=1&name=sample-server', pos=0) self.assert_called('GET', '/servers/1234', pos=1) self.assert_called('DELETE', '/servers/1234', pos=2) self.assert_called('GET', '/servers?all_tenants=1&name=sample-server2', pos=3) self.assert_called('GET', '/servers/5678', pos=4) self.assert_called('DELETE', '/servers/5678', pos=5) def test_delete_two_with_one_nonexistent(self): cmd = 'delete 1234 123456789' self.assertRaises(exceptions.CommandError, self.run_command, cmd) self.assert_called_anytime('DELETE', '/servers/1234') cmd = 'delete sample-server nonexistentserver' self.assertRaises(exceptions.CommandError, self.run_command, cmd) self.assert_called_anytime('DELETE', '/servers/1234') def test_delete_one_with_one_nonexistent(self): cmd = 'delete 123456789' self.assertRaises(exceptions.CommandError, self.run_command, cmd) cmd = 'delete nonexistent-server1' self.assertRaises(exceptions.CommandError, self.run_command, cmd) def test_delete_two_with_two_nonexistent(self): cmd = 'delete 123456789 987654321' self.assertRaises(exceptions.CommandError, self.run_command, cmd) cmd = 'delete nonexistent-server1 nonexistent-server2' self.assertRaises(exceptions.CommandError, self.run_command, cmd) def test_diagnostics(self): self.run_command('diagnostics 1234') self.assert_called('GET', '/servers/1234/diagnostics') self.run_command('diagnostics sample-server') self.assert_called('GET', '/servers/1234/diagnostics') def test_server_topology(self): self.run_command('server-topology 1234', api_version='2.78') self.assert_called('GET', '/servers/1234/topology') self.run_command('server-topology sample-server', api_version='2.78') self.assert_called('GET', '/servers/1234/topology') def test_server_topology_pre278(self): exp = self.assertRaises(SystemExit, self.run_command, 'server-topology 1234', api_version='2.77') self.assertIn('2', str(exp)) def test_refresh_network(self): self.run_command('refresh-network 1234') self.assert_called('POST', '/os-server-external-events', {'events': [{'name': 'network-changed', 'server_uuid': '1234'}]}) def test_set_meta_set(self): self.run_command('meta 1234 set key1=val1 key2=val2') self.assert_called('POST', '/servers/1234/metadata', {'metadata': {'key1': 'val1', 'key2': 'val2'}}) def test_set_meta_delete_dict(self): self.run_command('meta 1234 delete key1=val1 key2=val2') self.assert_called('DELETE', '/servers/1234/metadata/key1') self.assert_called('DELETE', '/servers/1234/metadata/key2', pos=-2) def test_set_meta_delete_keys(self): self.run_command('meta 1234 delete key1 key2') self.assert_called('DELETE', '/servers/1234/metadata/key1') self.assert_called('DELETE', '/servers/1234/metadata/key2', pos=-2) def test_set_host_meta(self): self.run_command('host-meta hyper set key1=val1 key2=val2') self.assert_called('GET', '/os-hypervisors/hyper/servers', pos=0) self.assert_called('POST', '/servers/uuid1/metadata', {'metadata': {'key1': 'val1', 'key2': 'val2'}}, pos=1) self.assert_called('POST', '/servers/uuid2/metadata', {'metadata': {'key1': 'val1', 'key2': 'val2'}}, pos=2) self.assert_called('POST', '/servers/uuid3/metadata', {'metadata': {'key1': 'val1', 'key2': 'val2'}}, pos=3) self.assert_called('POST', '/servers/uuid4/metadata', {'metadata': {'key1': 'val1', 'key2': 'val2'}}, pos=4) def test_set_host_meta_strict(self): self.run_command('host-meta hyper1 --strict set key1=val1 key2=val2') self.assert_called('GET', '/os-hypervisors/hyper1/servers', pos=0) self.assert_called('POST', '/servers/uuid1/metadata', {'metadata': {'key1': 'val1', 'key2': 'val2'}}, pos=1) self.assert_called('POST', '/servers/uuid2/metadata', {'metadata': {'key1': 'val1', 'key2': 'val2'}}, pos=2) def test_set_host_meta_no_match(self): cmd = 'host-meta hyper --strict set key1=val1 key2=val2' self.assertRaises(exceptions.NotFound, self.run_command, cmd) def test_set_host_meta_with_no_servers(self): self.run_command('host-meta hyper_no_servers set key1=val1 key2=val2') self.assert_called('GET', '/os-hypervisors/hyper_no_servers/servers') def test_set_host_meta_with_no_servers_strict(self): cmd = 'host-meta hyper_no_servers --strict set key1=val1 key2=val2' self.assertRaises(exceptions.NotFound, self.run_command, cmd) def test_delete_host_meta(self): self.run_command('host-meta hyper delete key1') self.assert_called('GET', '/os-hypervisors/hyper/servers', pos=0) self.assert_called('DELETE', '/servers/uuid1/metadata/key1', pos=1) self.assert_called('DELETE', '/servers/uuid2/metadata/key1', pos=2) def test_delete_host_meta_strict(self): self.run_command('host-meta hyper1 --strict delete key1') self.assert_called('GET', '/os-hypervisors/hyper1/servers', pos=0) self.assert_called('DELETE', '/servers/uuid1/metadata/key1', pos=1) self.assert_called('DELETE', '/servers/uuid2/metadata/key1', pos=2) def test_usage_list(self): cmd = 'usage-list --start 2000-01-20 --end 2005-02-01' stdout, _stderr = self.run_command(cmd) self.assert_called('GET', '/os-simple-tenant-usage?' + 'start=2000-01-20T00:00:00&' + 'end=2005-02-01T00:00:00&' + 'detailed=1') # Servers, RAM MiB-Hours, CPU Hours, Disk GiB-Hours self.assertIn('1 | 25451.76 | 49.71 | 0.00', stdout) def test_usage_list_stitch_together_next_results(self): cmd = 'usage-list --start 2000-01-20 --end 2005-02-01' stdout, _stderr = self.run_command(cmd, api_version='2.40') self.assert_called('GET', '/os-simple-tenant-usage?' 'start=2000-01-20T00:00:00&' 'end=2005-02-01T00:00:00&' 'detailed=1', pos=0) markers = [ 'f079e394-1111-457b-b350-bb5ecc685cdd', 'f079e394-2222-457b-b350-bb5ecc685cdd', ] for pos, marker in enumerate(markers): self.assert_called('GET', '/os-simple-tenant-usage?' 'start=2000-01-20T00:00:00&' 'end=2005-02-01T00:00:00&' 'marker=%s&detailed=1' % (marker), pos=pos + 1) # Servers, RAM MiB-Hours, CPU Hours, Disk GiB-Hours self.assertIn('2 | 50903.53 | 99.42 | 0.00', stdout) def test_usage_list_no_args(self): timeutils.set_time_override(datetime.datetime(2005, 2, 1, 0, 0)) self.addCleanup(timeutils.clear_time_override) self.run_command('usage-list') self.assert_called('GET', '/os-simple-tenant-usage?' + 'start=2005-01-04T00:00:00&' + 'end=2005-02-02T00:00:00&' + 'detailed=1') def test_usage(self): cmd = 'usage --start 2000-01-20 --end 2005-02-01 --tenant test' stdout, _stderr = self.run_command(cmd) self.assert_called('GET', '/os-simple-tenant-usage/test?' + 'start=2000-01-20T00:00:00&' + 'end=2005-02-01T00:00:00') # Servers, RAM MiB-Hours, CPU Hours, Disk GiB-Hours self.assertIn('1 | 25451.76 | 49.71 | 0.00', stdout) def test_usage_stitch_together_next_results(self): cmd = 'usage --start 2000-01-20 --end 2005-02-01' stdout, _stderr = self.run_command(cmd, api_version='2.40') self.assert_called('GET', '/os-simple-tenant-usage/tenant_id?' 'start=2000-01-20T00:00:00&' 'end=2005-02-01T00:00:00', pos=0) markers = [ 'f079e394-1111-457b-b350-bb5ecc685cdd', 'f079e394-2222-457b-b350-bb5ecc685cdd', ] for pos, marker in enumerate(markers): self.assert_called('GET', '/os-simple-tenant-usage/tenant_id?' 'start=2000-01-20T00:00:00&' 'end=2005-02-01T00:00:00&' 'marker=%s' % (marker), pos=pos + 1) # Servers, RAM MiB-Hours, CPU Hours, Disk GiB-Hours self.assertIn('2 | 50903.53 | 99.42 | 0.00', stdout) def test_usage_no_tenant(self): self.run_command('usage --start 2000-01-20 --end 2005-02-01') self.assert_called('GET', '/os-simple-tenant-usage/tenant_id?' + 'start=2000-01-20T00:00:00&' + 'end=2005-02-01T00:00:00') def test_flavor_delete(self): self.run_command("flavor-delete 2") self.assert_called('DELETE', '/flavors/2') def test_flavor_create(self): self.run_command("flavor-create flavorcreate " "1234 512 10 1 --swap 1024 --ephemeral 10 " "--is-public true") self.assert_called('POST', '/flavors', pos=-2) self.assert_called('GET', '/flavors/1', pos=-1) def test_flavor_create_with_description(self): """Tests creating a flavor with a description.""" self.run_command("flavor-create description " "1234 512 10 1 --description foo", api_version='2.55') expected_post_body = { "flavor": { "name": "description", "ram": 512, "vcpus": 1, "disk": 10, "id": "1234", "swap": 0, "OS-FLV-EXT-DATA:ephemeral": 0, "rxtx_factor": 1.0, "os-flavor-access:is_public": True, "description": "foo" } } self.assert_called('POST', '/flavors', expected_post_body, pos=-2) def test_flavor_update(self): """Tests creating a flavor with a description.""" out, _ = self.run_command( "flavor-update with-description new-description", api_version='2.55') expected_put_body = { "flavor": { "description": "new-description" } } self.assert_called('GET', '/flavors/with-description', pos=-2) self.assert_called('PUT', '/flavors/with-description', expected_put_body, pos=-1) self.assertIn('new-description', out) def test_aggregate_list(self): out, err = self.run_command('aggregate-list') self.assert_called('GET', '/os-aggregates') self.assertNotIn('UUID', out) def test_aggregate_list_v2_41(self): out, err = self.run_command('aggregate-list', api_version='2.41') self.assert_called('GET', '/os-aggregates') self.assertIn('UUID', out) self.assertIn('80785864-087b-45a5-a433-b20eac9b58aa', out) self.assertIn('30827713-5957-4b68-8fd3-ccaddb568c24', out) self.assertIn('9a651b22-ce3f-4a87-acd7-98446ef591c4', out) def test_aggregate_create(self): out, err = self.run_command('aggregate-create test_name nova1') body = {"aggregate": {"name": "test_name", "availability_zone": "nova1"}} self.assert_called('POST', '/os-aggregates', body, pos=-2) self.assert_called('GET', '/os-aggregates/1', pos=-1) self.assertNotIn('UUID', out) def test_aggregate_create_v2_41(self): out, err = self.run_command('aggregate-create test_name nova1', api_version='2.41') body = {"aggregate": {"name": "test_name", "availability_zone": "nova1"}} self.assert_called('POST', '/os-aggregates', body, pos=-2) self.assert_called('GET', '/os-aggregates/1', pos=-1) self.assertIn('UUID', out) self.assertIn('80785864-087b-45a5-a433-b20eac9b58aa', out) def test_aggregate_delete_by_id(self): self.run_command('aggregate-delete 1') self.assert_called('DELETE', '/os-aggregates/1') def test_aggregate_delete_by_name(self): self.run_command('aggregate-delete test') self.assert_called('DELETE', '/os-aggregates/1') def test_aggregate_update_by_id(self): out, err = self.run_command('aggregate-update 1 --name new_name') body = {"aggregate": {"name": "new_name"}} self.assert_called('PUT', '/os-aggregates/1', body, pos=-2) self.assert_called('GET', '/os-aggregates/1', pos=-1) self.assertNotIn('UUID', out) def test_aggregate_update_by_id_v2_41(self): out, err = self.run_command('aggregate-update 1 --name new_name', api_version='2.41') body = {"aggregate": {"name": "new_name"}} self.assert_called('PUT', '/os-aggregates/1', body, pos=-2) self.assert_called('GET', '/os-aggregates/1', pos=-1) self.assertIn('UUID', out) self.assertIn('80785864-087b-45a5-a433-b20eac9b58aa', out) def test_aggregate_update_by_name(self): self.run_command('aggregate-update test --name new_name ') body = {"aggregate": {"name": "new_name"}} self.assert_called('PUT', '/os-aggregates/1', body, pos=-2) self.assert_called('GET', '/os-aggregates/1', pos=-1) def test_aggregate_update_with_availability_zone_by_id(self): self.run_command('aggregate-update 1 --name foo ' '--availability-zone new_zone') body = {"aggregate": {"name": "foo", "availability_zone": "new_zone"}} self.assert_called('PUT', '/os-aggregates/1', body, pos=-2) self.assert_called('GET', '/os-aggregates/1', pos=-1) def test_aggregate_update_with_availability_zone_by_name(self): self.run_command('aggregate-update test --name foo ' '--availability-zone new_zone') body = {"aggregate": {"name": "foo", "availability_zone": "new_zone"}} self.assert_called('PUT', '/os-aggregates/1', body, pos=-2) self.assert_called('GET', '/os-aggregates/1', pos=-1) def test_aggregate_update_without_availability_zone_and_name(self): ex = self.assertRaises(exceptions.CommandError, self.run_command, 'aggregate-update test') self.assertIn("Either '--name ' or '--availability-zone " "' must be specified.", str(ex)) def test_aggregate_set_metadata_add_by_id(self): out, err = self.run_command('aggregate-set-metadata 3 foo=bar') body = {"set_metadata": {"metadata": {"foo": "bar"}}} self.assert_called('POST', '/os-aggregates/3/action', body, pos=-2) self.assert_called('GET', '/os-aggregates/3', pos=-1) self.assertNotIn('UUID', out) def test_aggregate_set_metadata_add_by_id_v2_41(self): out, err = self.run_command('aggregate-set-metadata 3 foo=bar', api_version='2.41') body = {"set_metadata": {"metadata": {"foo": "bar"}}} self.assert_called('POST', '/os-aggregates/3/action', body, pos=-2) self.assert_called('GET', '/os-aggregates/3', pos=-1) self.assertIn('UUID', out) self.assertIn('9a651b22-ce3f-4a87-acd7-98446ef591c4', out) def test_aggregate_set_metadata_add_duplicate_by_id(self): cmd = 'aggregate-set-metadata 3 test=dup' self.assertRaises(exceptions.CommandError, self.run_command, cmd) def test_aggregate_set_metadata_delete_by_id(self): self.run_command('aggregate-set-metadata 3 none_key') body = {"set_metadata": {"metadata": {"none_key": None}}} self.assert_called('POST', '/os-aggregates/3/action', body, pos=-2) self.assert_called('GET', '/os-aggregates/3', pos=-1) def test_aggregate_set_metadata_delete_missing_by_id(self): cmd = 'aggregate-set-metadata 3 delete_key2' self.assertRaises(exceptions.CommandError, self.run_command, cmd) def test_aggregate_set_metadata_by_name(self): self.run_command('aggregate-set-metadata test foo=bar') body = {"set_metadata": {"metadata": {"foo": "bar"}}} self.assert_called('POST', '/os-aggregates/1/action', body, pos=-2) self.assert_called('GET', '/os-aggregates/1', pos=-1) def test_aggregate_add_host_by_id(self): out, err = self.run_command('aggregate-add-host 1 host1') body = {"add_host": {"host": "host1"}} self.assert_called('POST', '/os-aggregates/1/action', body, pos=-2) self.assert_called('GET', '/os-aggregates/1', pos=-1) self.assertNotIn('UUID', out) def test_aggregate_add_host_by_id_v2_41(self): out, err = self.run_command('aggregate-add-host 1 host1', api_version='2.41') body = {"add_host": {"host": "host1"}} self.assert_called('POST', '/os-aggregates/1/action', body, pos=-2) self.assert_called('GET', '/os-aggregates/1', pos=-1) self.assertIn('UUID', out) self.assertIn('80785864-087b-45a5-a433-b20eac9b58aa', out) def test_aggregate_add_host_by_name(self): self.run_command('aggregate-add-host test host1') body = {"add_host": {"host": "host1"}} self.assert_called('POST', '/os-aggregates/1/action', body, pos=-2) self.assert_called('GET', '/os-aggregates/1', pos=-1) def test_aggregate_remove_host_by_id(self): out, err = self.run_command('aggregate-remove-host 1 host1') body = {"remove_host": {"host": "host1"}} self.assert_called('POST', '/os-aggregates/1/action', body, pos=-2) self.assert_called('GET', '/os-aggregates/1', pos=-1) self.assertNotIn('UUID', out) def test_aggregate_remove_host_by_id_v2_41(self): out, err = self.run_command('aggregate-remove-host 1 host1', api_version='2.41') body = {"remove_host": {"host": "host1"}} self.assert_called('POST', '/os-aggregates/1/action', body, pos=-2) self.assert_called('GET', '/os-aggregates/1', pos=-1) self.assertIn('UUID', out) self.assertIn('80785864-087b-45a5-a433-b20eac9b58aa', out) def test_aggregate_remove_host_by_name(self): self.run_command('aggregate-remove-host test host1') body = {"remove_host": {"host": "host1"}} self.assert_called('POST', '/os-aggregates/1/action', body, pos=-2) self.assert_called('GET', '/os-aggregates/1', pos=-1) def test_aggregate_show_by_id(self): out, err = self.run_command('aggregate-show 1') self.assert_called('GET', '/os-aggregates/1') self.assertNotIn('UUID', out) def test_aggregate_show_by_id_v2_41(self): out, err = self.run_command('aggregate-show 1', api_version='2.41') self.assert_called('GET', '/os-aggregates/1') self.assertIn('UUID', out) self.assertIn('80785864-087b-45a5-a433-b20eac9b58aa', out) def test_aggregate_show_by_name(self): self.run_command('aggregate-show test') self.assert_called('GET', '/os-aggregates') def test_aggregate_cache_images(self): self.run_command( 'aggregate-cache-images 1 %s %s' % ( FAKE_UUID_1, FAKE_UUID_2), api_version='2.81') body = { 'cache': [{'id': FAKE_UUID_1}, {'id': FAKE_UUID_2}], } self.assert_called('POST', '/os-aggregates/1/images', body) def test_aggregate_cache_images_no_images(self): self.assertRaises(SystemExit, self.run_command, 'aggregate-cache-images 1', api_version='2.81') def test_aggregate_cache_images_pre281(self): self.assertRaises(SystemExit, self.run_command, 'aggregate-cache-images 1 %s %s' % ( FAKE_UUID_1, FAKE_UUID_2), api_version='2.80') def test_live_migration(self): self.run_command('live-migration sample-server hostname') self.assert_called('POST', '/servers/1234/action', {'os-migrateLive': {'host': 'hostname', 'block_migration': False, 'disk_over_commit': False}}) self.run_command('live-migration sample-server hostname' ' --block-migrate') self.assert_called('POST', '/servers/1234/action', {'os-migrateLive': {'host': 'hostname', 'block_migration': True, 'disk_over_commit': False}}) self.run_command('live-migration sample-server hostname' ' --block-migrate --disk-over-commit') self.assert_called('POST', '/servers/1234/action', {'os-migrateLive': {'host': 'hostname', 'block_migration': True, 'disk_over_commit': True}}) def test_live_migration_v225(self): self.run_command('live-migration sample-server hostname', api_version='2.25') self.assert_called('POST', '/servers/1234/action', {'os-migrateLive': {'host': 'hostname', 'block_migration': 'auto'}}) self.run_command('live-migration sample-server hostname' ' --block-migrate', api_version='2.25') self.assert_called('POST', '/servers/1234/action', {'os-migrateLive': {'host': 'hostname', 'block_migration': True}}) self.run_command('live-migration sample-server', api_version='2.25') self.assert_called('POST', '/servers/1234/action', {'os-migrateLive': {'host': None, 'block_migration': 'auto'}}) def test_live_migration_v2_30(self): self.run_command('live-migration sample-server hostname', api_version='2.30') self.assert_called('POST', '/servers/1234/action', {'os-migrateLive': {'host': 'hostname', 'block_migration': 'auto'}}) self.run_command('live-migration --force sample-server hostname', api_version='2.30') self.assert_called('POST', '/servers/1234/action', {'os-migrateLive': {'host': 'hostname', 'block_migration': 'auto', 'force': True}}) def test_live_migration_v2_68(self): self.run_command('live-migration sample-server hostname', api_version='2.68') self.assert_called('POST', '/servers/1234/action', {'os-migrateLive': {'host': 'hostname', 'block_migration': 'auto'}}) self.assertRaises( SystemExit, self.run_command, 'live-migration --force sample-server hostname', api_version='2.68') def test_live_migration_force_complete(self): self.run_command('live-migration-force-complete sample-server 1', api_version='2.22') self.assert_called('POST', '/servers/1234/migrations/1/action', {'force_complete': None}) def test_list_migrations(self): self.run_command('server-migration-list sample-server', api_version='2.23') self.assert_called('GET', '/servers/1234/migrations') def test_list_migrations_pre_v280(self): out = self.run_command('server-migration-list sample-server', api_version='2.79')[0] self.assert_called('GET', '/servers/1234/migrations') self.assertNotIn('User ID', out) self.assertNotIn('Project ID', out) def test_list_migrations_v280(self): out = self.run_command('server-migration-list sample-server', api_version='2.80')[0] self.assert_called('GET', '/servers/1234/migrations') self.assertIn('User ID', out) self.assertIn('Project ID', out) def test_get_migration(self): self.run_command('server-migration-show sample-server 1', api_version='2.23') self.assert_called('GET', '/servers/1234/migrations/1') def test_get_migration_pre_v280(self): out = self.run_command('server-migration-show sample-server 1', api_version='2.79')[0] self.assert_called('GET', '/servers/1234/migrations/1') self.assertNotIn('user_id', out) self.assertNotIn('project_id', out) def test_get_migration_v280(self): out = self.run_command('server-migration-show sample-server 1', api_version='2.80')[0] self.assert_called('GET', '/servers/1234/migrations/1') self.assertIn('user_id', out) self.assertIn('project_id', out) def test_live_migration_abort(self): self.run_command('live-migration-abort sample-server 1', api_version='2.24') self.assert_called('DELETE', '/servers/1234/migrations/1') def test_host_evacuate_live_with_no_target_host(self): self.run_command('host-evacuate-live hyper') self.assert_called('GET', '/os-hypervisors/hyper/servers', pos=0) body = {'os-migrateLive': {'host': None, 'block_migration': False, 'disk_over_commit': False}} self.assert_called('POST', '/servers/uuid1/action', body, pos=1) self.assert_called('POST', '/servers/uuid2/action', body, pos=2) self.assert_called('POST', '/servers/uuid3/action', body, pos=3) self.assert_called('POST', '/servers/uuid4/action', body, pos=4) def test_host_evacuate_live_with_no_target_host_strict(self): self.run_command('host-evacuate-live hyper1 --strict') self.assert_called('GET', '/os-hypervisors/hyper1/servers', pos=0) body = {'os-migrateLive': {'host': None, 'block_migration': False, 'disk_over_commit': False}} self.assert_called('POST', '/servers/uuid1/action', body, pos=1) self.assert_called('POST', '/servers/uuid2/action', body, pos=2) def test_host_evacuate_live_no_match(self): cmd = 'host-evacuate-live hyper --strict' self.assertRaises(exceptions.NotFound, self.run_command, cmd) def test_host_evacuate_live_2_25(self): self.run_command('host-evacuate-live hyper', api_version='2.25') self.assert_called('GET', '/os-hypervisors/hyper/servers', pos=0) body = {'os-migrateLive': {'host': None, 'block_migration': 'auto'}} self.assert_called('POST', '/servers/uuid1/action', body, pos=1) self.assert_called('POST', '/servers/uuid2/action', body, pos=2) self.assert_called('POST', '/servers/uuid3/action', body, pos=3) self.assert_called('POST', '/servers/uuid4/action', body, pos=4) def test_host_evacuate_live_2_25_strict(self): self.run_command('host-evacuate-live hyper1 --strict', api_version='2.25') self.assert_called('GET', '/os-hypervisors/hyper1/servers', pos=0) body = {'os-migrateLive': {'host': None, 'block_migration': 'auto'}} self.assert_called('POST', '/servers/uuid1/action', body, pos=1) self.assert_called('POST', '/servers/uuid2/action', body, pos=2) def test_host_evacuate_live_with_target_host(self): self.run_command('host-evacuate-live hyper ' '--target-host hostname') self.assert_called('GET', '/os-hypervisors/hyper/servers', pos=0) body = {'os-migrateLive': {'host': 'hostname', 'block_migration': False, 'disk_over_commit': False}} self.assert_called('POST', '/servers/uuid1/action', body, pos=1) self.assert_called('POST', '/servers/uuid2/action', body, pos=2) self.assert_called('POST', '/servers/uuid3/action', body, pos=3) self.assert_called('POST', '/servers/uuid4/action', body, pos=4) def test_host_evacuate_live_with_target_host_strict(self): self.run_command('host-evacuate-live hyper1 ' '--target-host hostname --strict') self.assert_called('GET', '/os-hypervisors/hyper1/servers', pos=0) body = {'os-migrateLive': {'host': 'hostname', 'block_migration': False, 'disk_over_commit': False}} self.assert_called('POST', '/servers/uuid1/action', body, pos=1) self.assert_called('POST', '/servers/uuid2/action', body, pos=2) def test_host_evacuate_live_2_30(self): self.run_command('host-evacuate-live --force hyper ' '--target-host hostname', api_version='2.30') self.assert_called('GET', '/os-hypervisors/hyper/servers', pos=0) body = {'os-migrateLive': {'host': 'hostname', 'block_migration': 'auto', 'force': True}} self.assert_called('POST', '/servers/uuid1/action', body, pos=1) self.assert_called('POST', '/servers/uuid2/action', body, pos=2) self.assert_called('POST', '/servers/uuid3/action', body, pos=3) self.assert_called('POST', '/servers/uuid4/action', body, pos=4) def test_host_evacuate_live_2_30_strict(self): self.run_command('host-evacuate-live --force hyper1 ' '--target-host hostname --strict', api_version='2.30') self.assert_called('GET', '/os-hypervisors/hyper1/servers', pos=0) body = {'os-migrateLive': {'host': 'hostname', 'block_migration': 'auto', 'force': True}} self.assert_called('POST', '/servers/uuid1/action', body, pos=1) self.assert_called('POST', '/servers/uuid2/action', body, pos=2) def test_host_evacuate_live_with_block_migration(self): self.run_command('host-evacuate-live --block-migrate hyper') self.assert_called('GET', '/os-hypervisors/hyper/servers', pos=0) body = {'os-migrateLive': {'host': None, 'block_migration': True, 'disk_over_commit': False}} self.assert_called('POST', '/servers/uuid1/action', body, pos=1) self.assert_called('POST', '/servers/uuid2/action', body, pos=2) self.assert_called('POST', '/servers/uuid3/action', body, pos=3) self.assert_called('POST', '/servers/uuid4/action', body, pos=4) def test_host_evacuate_live_with_block_migration_strict(self): self.run_command('host-evacuate-live --block-migrate hyper2 --strict') self.assert_called('GET', '/os-hypervisors/hyper2/servers', pos=0) body = {'os-migrateLive': {'host': None, 'block_migration': True, 'disk_over_commit': False}} self.assert_called('POST', '/servers/uuid3/action', body, pos=1) self.assert_called('POST', '/servers/uuid4/action', body, pos=2) def test_host_evacuate_live_with_block_migration_2_25(self): self.run_command('host-evacuate-live --block-migrate hyper', api_version='2.25') self.assert_called('GET', '/os-hypervisors/hyper/servers', pos=0) body = {'os-migrateLive': {'host': None, 'block_migration': True}} self.assert_called('POST', '/servers/uuid1/action', body, pos=1) self.assert_called('POST', '/servers/uuid2/action', body, pos=2) self.assert_called('POST', '/servers/uuid3/action', body, pos=3) self.assert_called('POST', '/servers/uuid4/action', body, pos=4) def test_host_evacuate_live_with_block_migration_2_25_strict(self): self.run_command('host-evacuate-live --block-migrate hyper2 --strict', api_version='2.25') self.assert_called('GET', '/os-hypervisors/hyper2/servers', pos=0) body = {'os-migrateLive': {'host': None, 'block_migration': True}} self.assert_called('POST', '/servers/uuid3/action', body, pos=1) self.assert_called('POST', '/servers/uuid4/action', body, pos=2) def test_host_evacuate_live_with_disk_over_commit(self): self.run_command('host-evacuate-live --disk-over-commit hyper') self.assert_called('GET', '/os-hypervisors/hyper/servers', pos=0) body = {'os-migrateLive': {'host': None, 'block_migration': False, 'disk_over_commit': True}} self.assert_called('POST', '/servers/uuid1/action', body, pos=1) self.assert_called('POST', '/servers/uuid2/action', body, pos=2) self.assert_called('POST', '/servers/uuid3/action', body, pos=3) self.assert_called('POST', '/servers/uuid4/action', body, pos=4) def test_host_evacuate_live_with_disk_over_commit_strict(self): self.run_command('host-evacuate-live --disk-over-commit hyper2 ' '--strict') self.assert_called('GET', '/os-hypervisors/hyper2/servers', pos=0) body = {'os-migrateLive': {'host': None, 'block_migration': False, 'disk_over_commit': True}} self.assert_called('POST', '/servers/uuid3/action', body, pos=1) self.assert_called('POST', '/servers/uuid4/action', body, pos=2) def test_host_evacuate_live_with_disk_over_commit_2_25(self): self.assertRaises(SystemExit, self.run_command, 'host-evacuate-live --disk-over-commit hyper', api_version='2.25') def test_host_evacuate_live_with_disk_over_commit_2_25_strict(self): self.assertRaises(SystemExit, self.run_command, 'host-evacuate-live --disk-over-commit hyper2 ' '--strict', api_version='2.25') def test_host_evacuate_list_with_max_servers(self): self.run_command('host-evacuate-live --max-servers 1 hyper') self.assert_called('GET', '/os-hypervisors/hyper/servers', pos=0) body = {'os-migrateLive': {'host': None, 'block_migration': False, 'disk_over_commit': False}} self.assert_called('POST', '/servers/uuid1/action', body, pos=1) def test_host_evacuate_list_with_max_servers_strict(self): self.run_command('host-evacuate-live --max-servers 1 hyper1 --strict') self.assert_called('GET', '/os-hypervisors/hyper1/servers', pos=0) body = {'os-migrateLive': {'host': None, 'block_migration': False, 'disk_over_commit': False}} self.assert_called('POST', '/servers/uuid1/action', body, pos=1) def test_reset_state(self): self.run_command('reset-state sample-server') self.assert_called('POST', '/servers/1234/action', {'os-resetState': {'state': 'error'}}) self.run_command('reset-state sample-server --active') self.assert_called('POST', '/servers/1234/action', {'os-resetState': {'state': 'active'}}) def test_reset_state_with_all_tenants(self): self.run_command('reset-state sample-server --all-tenants') self.assert_called('GET', '/servers?all_tenants=1&name=sample-server', pos=0) self.assert_called('GET', '/servers/1234', pos=1) self.assert_called('POST', '/servers/1234/action', {'os-resetState': {'state': 'error'}}) def test_reset_state_multiple(self): self.run_command('reset-state sample-server sample-server2') self.assert_called('POST', '/servers/1234/action', {'os-resetState': {'state': 'error'}}, pos=-4) self.assert_called('POST', '/servers/5678/action', {'os-resetState': {'state': 'error'}}, pos=-1) def test_reset_state_active_multiple(self): self.run_command('reset-state --active sample-server sample-server2') self.assert_called('POST', '/servers/1234/action', {'os-resetState': {'state': 'active'}}, pos=-4) self.assert_called('POST', '/servers/5678/action', {'os-resetState': {'state': 'active'}}, pos=-1) def test_reset_network(self): self.run_command('reset-network sample-server') self.assert_called('POST', '/servers/1234/action', {'resetNetwork': None}) def test_services_list(self): self.run_command('service-list') self.assert_called('GET', '/os-services') def test_services_list_v2_53(self): """Tests nova service-list at the 2.53 microversion.""" self.run_command('service-list', api_version='2.53') self.assert_called('GET', '/os-services') def test_services_list_v269_with_down_cells(self): """Tests nova service-list at the 2.69 microversion.""" stdout, _stderr = self.run_command('service-list', api_version='2.69') self.assertEqual( '''\ +--------------------------------------+--------------+-----------+------+----------+-------+---------------------+-----------------+-------------+ | Id | Binary | Host | Zone | Status | State | Updated_at | Disabled Reason | Forced down | +--------------------------------------+--------------+-----------+------+----------+-------+---------------------+-----------------+-------------+ | 75e9eabc-ed3b-4f11-8bba-add1e7e7e2de | nova-compute | host1 | nova | enabled | up | 2012-10-29 13:42:02 | | | | 1f140183-c914-4ddf-8757-6df73028aa86 | nova-compute | host1 | nova | disabled | down | 2012-09-18 08:03:38 | | | | | nova-compute | host-down | | UNKNOWN | | | | | +--------------------------------------+--------------+-----------+------+----------+-------+---------------------+-----------------+-------------+ ''', # noqa stdout, ) self.assert_called('GET', '/os-services') def test_services_list_with_host(self): self.run_command('service-list --host host1') self.assert_called('GET', '/os-services?host=host1') def test_services_list_with_binary(self): self.run_command('service-list --binary nova-cert') self.assert_called('GET', '/os-services?binary=nova-cert') def test_services_list_with_host_binary(self): self.run_command('service-list --host host1 --binary nova-cert') self.assert_called('GET', '/os-services?host=host1&binary=nova-cert') def test_services_enable(self): self.run_command('service-enable host1') body = {'host': 'host1', 'binary': 'nova-compute'} self.assert_called('PUT', '/os-services/enable', body) def test_services_enable_v2_53(self): """Tests nova service-enable at the 2.53 microversion.""" self.run_command('service-enable %s' % fakes.FAKE_SERVICE_UUID_1, api_version='2.53') body = {'status': 'enabled'} self.assert_called( 'PUT', '/os-services/%s' % fakes.FAKE_SERVICE_UUID_1, body) def test_services_disable(self): self.run_command('service-disable host1') body = {'host': 'host1', 'binary': 'nova-compute'} self.assert_called('PUT', '/os-services/disable', body) def test_services_disable_v2_53(self): """Tests nova service-disable at the 2.53 microversion.""" self.run_command('service-disable %s' % fakes.FAKE_SERVICE_UUID_1, api_version='2.53') body = {'status': 'disabled'} self.assert_called( 'PUT', '/os-services/%s' % fakes.FAKE_SERVICE_UUID_1, body) def test_services_disable_with_reason(self): self.run_command('service-disable host1 --reason no_reason') body = {'host': 'host1', 'binary': 'nova-compute', 'disabled_reason': 'no_reason'} self.assert_called('PUT', '/os-services/disable-log-reason', body) def test_services_disable_with_reason_v2_53(self): """Tests nova service-disable --reason at microversion 2.53.""" self.run_command('service-disable %s --reason no_reason' % fakes.FAKE_SERVICE_UUID_1, api_version='2.53') body = {'status': 'disabled', 'disabled_reason': 'no_reason'} self.assert_called( 'PUT', '/os-services/%s' % fakes.FAKE_SERVICE_UUID_1, body) def test_service_force_down_v2_53(self): """Tests nova service-force-down at the 2.53 microversion.""" self.run_command('service-force-down %s' % fakes.FAKE_SERVICE_UUID_1, api_version='2.53') body = {'forced_down': True} self.assert_called( 'PUT', '/os-services/%s' % fakes.FAKE_SERVICE_UUID_1, body) def test_services_delete(self): self.run_command('service-delete 1') self.assert_called('DELETE', '/os-services/1') def test_services_delete_v2_53(self): """Tests nova service-delete at the 2.53 microversion.""" self.run_command('service-delete %s' % fakes.FAKE_SERVICE_UUID_1) self.assert_called( 'DELETE', '/os-services/%s' % fakes.FAKE_SERVICE_UUID_1) def test_host_evacuate_v2_14(self): self.run_command('host-evacuate hyper --target target_hyper', api_version='2.14') self.assert_called('GET', '/os-hypervisors/hyper/servers', pos=0) self.assert_called('POST', '/servers/uuid1/action', {'evacuate': {'host': 'target_hyper'}}, pos=1) self.assert_called('POST', '/servers/uuid2/action', {'evacuate': {'host': 'target_hyper'}}, pos=2) self.assert_called('POST', '/servers/uuid3/action', {'evacuate': {'host': 'target_hyper'}}, pos=3) self.assert_called('POST', '/servers/uuid4/action', {'evacuate': {'host': 'target_hyper'}}, pos=4) def test_host_evacuate_v2_14_strict(self): self.run_command('host-evacuate hyper1 --target target_hyper --strict', api_version='2.14') self.assert_called('GET', '/os-hypervisors/hyper1/servers', pos=0) self.assert_called('POST', '/servers/uuid1/action', {'evacuate': {'host': 'target_hyper'}}, pos=1) self.assert_called('POST', '/servers/uuid2/action', {'evacuate': {'host': 'target_hyper'}}, pos=2) def test_host_evacuate(self): self.run_command('host-evacuate hyper --target target_hyper') self.assert_called('GET', '/os-hypervisors/hyper/servers', pos=0) self.assert_called('POST', '/servers/uuid1/action', {'evacuate': {'host': 'target_hyper', 'onSharedStorage': False}}, pos=1) self.assert_called('POST', '/servers/uuid2/action', {'evacuate': {'host': 'target_hyper', 'onSharedStorage': False}}, pos=2) self.assert_called('POST', '/servers/uuid3/action', {'evacuate': {'host': 'target_hyper', 'onSharedStorage': False}}, pos=3) self.assert_called('POST', '/servers/uuid4/action', {'evacuate': {'host': 'target_hyper', 'onSharedStorage': False}}, pos=4) def test_host_evacuate_strict(self): self.run_command('host-evacuate hyper1 --target target_hyper --strict') self.assert_called('GET', '/os-hypervisors/hyper1/servers', pos=0) self.assert_called('POST', '/servers/uuid1/action', {'evacuate': {'host': 'target_hyper', 'onSharedStorage': False}}, pos=1) self.assert_called('POST', '/servers/uuid2/action', {'evacuate': {'host': 'target_hyper', 'onSharedStorage': False}}, pos=2) def test_host_evacuate_no_match(self): cmd = 'host-evacuate hyper --target target_hyper --strict' self.assertRaises(exceptions.NotFound, self.run_command, cmd) def test_host_evacuate_v2_29(self): self.run_command('host-evacuate hyper --target target_hyper --force', api_version='2.29') self.assert_called('GET', '/os-hypervisors/hyper/servers', pos=0) self.assert_called('POST', '/servers/uuid1/action', {'evacuate': {'host': 'target_hyper', 'force': True} }, pos=1) self.assert_called('POST', '/servers/uuid2/action', {'evacuate': {'host': 'target_hyper', 'force': True} }, pos=2) self.assert_called('POST', '/servers/uuid3/action', {'evacuate': {'host': 'target_hyper', 'force': True} }, pos=3) self.assert_called('POST', '/servers/uuid4/action', {'evacuate': {'host': 'target_hyper', 'force': True} }, pos=4) def test_host_evacuate_v2_29_strict(self): self.run_command('host-evacuate hyper1 --target target_hyper' ' --force --strict', api_version='2.29') self.assert_called('GET', '/os-hypervisors/hyper1/servers', pos=0) self.assert_called('POST', '/servers/uuid1/action', {'evacuate': {'host': 'target_hyper', 'force': True} }, pos=1) self.assert_called('POST', '/servers/uuid2/action', {'evacuate': {'host': 'target_hyper', 'force': True} }, pos=2) def test_host_evacuate_with_shared_storage(self): self.run_command( 'host-evacuate --on-shared-storage hyper --target target_hyper') self.assert_called('GET', '/os-hypervisors/hyper/servers', pos=0) self.assert_called('POST', '/servers/uuid1/action', {'evacuate': {'host': 'target_hyper', 'onSharedStorage': True}}, pos=1) self.assert_called('POST', '/servers/uuid2/action', {'evacuate': {'host': 'target_hyper', 'onSharedStorage': True}}, pos=2) self.assert_called('POST', '/servers/uuid3/action', {'evacuate': {'host': 'target_hyper', 'onSharedStorage': True}}, pos=3) self.assert_called('POST', '/servers/uuid4/action', {'evacuate': {'host': 'target_hyper', 'onSharedStorage': True}}, pos=4) def test_host_evacuate_with_shared_storage_strict(self): self.run_command('host-evacuate --on-shared-storage hyper1' ' --target target_hyper --strict') self.assert_called('GET', '/os-hypervisors/hyper1/servers', pos=0) self.assert_called('POST', '/servers/uuid1/action', {'evacuate': {'host': 'target_hyper', 'onSharedStorage': True}}, pos=1) self.assert_called('POST', '/servers/uuid2/action', {'evacuate': {'host': 'target_hyper', 'onSharedStorage': True}}, pos=2) def test_host_evacuate_with_no_target_host(self): self.run_command('host-evacuate --on-shared-storage hyper') self.assert_called('GET', '/os-hypervisors/hyper/servers', pos=0) self.assert_called('POST', '/servers/uuid1/action', {'evacuate': {'onSharedStorage': True}}, pos=1) self.assert_called('POST', '/servers/uuid2/action', {'evacuate': {'onSharedStorage': True}}, pos=2) self.assert_called('POST', '/servers/uuid3/action', {'evacuate': {'onSharedStorage': True}}, pos=3) self.assert_called('POST', '/servers/uuid4/action', {'evacuate': {'onSharedStorage': True}}, pos=4) def test_host_evacuate_with_no_target_host_strict(self): self.run_command('host-evacuate --on-shared-storage hyper1 --strict') self.assert_called('GET', '/os-hypervisors/hyper1/servers', pos=0) self.assert_called('POST', '/servers/uuid1/action', {'evacuate': {'onSharedStorage': True}}, pos=1) self.assert_called('POST', '/servers/uuid2/action', {'evacuate': {'onSharedStorage': True}}, pos=2) def test_host_servers_migrate(self): self.run_command('host-servers-migrate hyper') self.assert_called('GET', '/os-hypervisors/hyper/servers', pos=0) self.assert_called('POST', '/servers/uuid1/action', {'migrate': None}, pos=1) self.assert_called('POST', '/servers/uuid2/action', {'migrate': None}, pos=2) self.assert_called('POST', '/servers/uuid3/action', {'migrate': None}, pos=3) self.assert_called('POST', '/servers/uuid4/action', {'migrate': None}, pos=4) def test_host_servers_migrate_strict(self): self.run_command('host-servers-migrate hyper1 --strict') self.assert_called('GET', '/os-hypervisors/hyper1/servers', pos=0) self.assert_called('POST', '/servers/uuid1/action', {'migrate': None}, pos=1) self.assert_called('POST', '/servers/uuid2/action', {'migrate': None}, pos=2) def test_host_servers_migrate_no_match(self): cmd = 'host-servers-migrate hyper --strict' self.assertRaises(exceptions.NotFound, self.run_command, cmd) def test_hypervisor_list(self): self.run_command('hypervisor-list') self.assert_called('GET', '/os-hypervisors') def test_hypervisor_list_matching(self): self.run_command('hypervisor-list --matching hyper') self.assert_called('GET', '/os-hypervisors/hyper/search') def test_hypervisor_list_limit_marker(self): self.run_command('hypervisor-list --limit 10 --marker hyper1', api_version='2.33') self.assert_called('GET', '/os-hypervisors?limit=10&marker=hyper1') def test_hypervisor_servers(self): self.run_command('hypervisor-servers hyper') self.assert_called('GET', '/os-hypervisors/hyper/servers') def test_hypervisor_show_by_id(self): self.run_command('hypervisor-show 1234') self.assert_called('GET', '/os-hypervisors/1234') def test_hypervisor_list_show_by_cell_id(self): self.run_command('hypervisor-show region!child@1') self.assert_called('GET', '/os-hypervisors/region!child@1') def test_hypervisor_show_by_name(self): self.run_command('hypervisor-show hyper1') self.assert_called('GET', '/os-hypervisors/hyper1') def test_hypervisor_uptime_by_id(self): self.run_command('hypervisor-uptime 1234') self.assert_called('GET', '/os-hypervisors/1234/uptime') def test_hypervisor_uptime_by_cell_id(self): self.run_command('hypervisor-uptime region!child@1') self.assert_called('GET', '/os-hypervisors/region!child@1/uptime') def test_hypervisor_uptime_by_name(self): self.run_command('hypervisor-uptime hyper1') self.assert_called('GET', '/os-hypervisors/1234/uptime') def test_hypervisor_stats(self): self.run_command('hypervisor-stats') self.assert_called('GET', '/os-hypervisors/statistics') def test_hypervisor_stats_v2_88(self): """Tests nova hypervisor-stats at the 2.88 microversion.""" ex = self.assertRaises( exceptions.CommandError, self.run_command, 'hypervisor-stats', api_version='2.88') self.assertIn( 'The hypervisor-stats command is not supported in API version ' '2.88 or later.', str(ex)) def test_quota_show(self): self.run_command( 'quota-show --tenant ' '97f4c221bff44578b0300df4ef119353') self.assert_called( 'GET', '/os-quota-sets/97f4c221bff44578b0300df4ef119353') def test_quota_show_detail(self): self.run_command( 'quota-show --tenant ' '97f4c221bff44578b0300df4ef119353 --detail') self.assert_called( 'GET', '/os-quota-sets/97f4c221bff44578b0300df4ef119353/detail') def test_user_quota_show(self): self.run_command( 'quota-show --tenant ' '97f4c221bff44578b0300df4ef119353 --user u1') self.assert_called( 'GET', '/os-quota-sets/97f4c221bff44578b0300df4ef119353?user_id=u1') def test_user_quota_show_detail(self): self.run_command( 'quota-show --tenant ' '97f4c221bff44578b0300df4ef119353 --user u1 --detail') self.assert_called( 'GET', '/os-quota-sets/97f4c221bff44578b0300df4ef119353/detail' '?user_id=u1') def test_quota_show_no_tenant(self): self.run_command('quota-show') self.assert_called('GET', '/os-quota-sets/tenant_id') def test_quota_defaults(self): self.run_command( 'quota-defaults --tenant ' '97f4c221bff44578b0300df4ef119353') self.assert_called( 'GET', '/os-quota-sets/97f4c221bff44578b0300df4ef119353/defaults') def test_quota_defaults_no_tenant(self): self.run_command('quota-defaults') self.assert_called('GET', '/os-quota-sets/tenant_id/defaults') def test_quota_update(self): self.run_command( 'quota-update 97f4c221bff44578b0300df4ef119353' ' --instances=5') self.assert_called( 'PUT', '/os-quota-sets/97f4c221bff44578b0300df4ef119353', {'quota_set': {'instances': 5}}) def test_user_quota_update(self): self.run_command( 'quota-update 97f4c221bff44578b0300df4ef119353' ' --user=u1' ' --instances=5') self.assert_called( 'PUT', '/os-quota-sets/97f4c221bff44578b0300df4ef119353?user_id=u1', {'quota_set': {'instances': 5}}) def test_quota_force_update(self): self.run_command( 'quota-update 97f4c221bff44578b0300df4ef119353' ' --instances=5 --force') self.assert_called( 'PUT', '/os-quota-sets/97f4c221bff44578b0300df4ef119353', {'quota_set': {'force': True, 'instances': 5}}) def test_quota_update_fixed_ip(self): self.run_command( 'quota-update 97f4c221bff44578b0300df4ef119353' ' --fixed-ips=5') self.assert_called( 'PUT', '/os-quota-sets/97f4c221bff44578b0300df4ef119353', {'quota_set': {'fixed_ips': 5}}) def test_quota_update_injected_file_2_57(self): """Tests that trying to update injected_file* quota with microversion 2.57 fails. """ for quota in ('--injected-files', '--injected-file-content-bytes', '--injected-file-path-bytes'): cmd = ('quota-update 97f4c221bff44578b0300df4ef119353 %s=5' % quota) self.assertRaises(SystemExit, self.run_command, cmd, api_version='2.57') def test_quota_delete(self): self.run_command('quota-delete --tenant ' '97f4c221bff44578b0300df4ef119353') self.assert_called('DELETE', '/os-quota-sets/97f4c221bff44578b0300df4ef119353') def test_user_quota_delete(self): self.run_command('quota-delete --tenant ' '97f4c221bff44578b0300df4ef119353 ' '--user u1') self.assert_called( 'DELETE', '/os-quota-sets/97f4c221bff44578b0300df4ef119353?user_id=u1') def test_quota_class_show(self): self.run_command('quota-class-show test') self.assert_called('GET', '/os-quota-class-sets/test') def test_quota_class_update(self): # The list of args we can update. args = ( '--instances', '--cores', '--ram', '--floating-ips', '--fixed-ips', '--metadata-items', '--injected-files', '--injected-file-content-bytes', '--injected-file-path-bytes', '--key-pairs', '--security-groups', '--security-group-rules', '--server-groups', '--server-group-members' ) for arg in args: self.run_command('quota-class-update ' '97f4c221bff44578b0300df4ef119353 ' '%s=5' % arg) request_param = arg[2:].replace('-', '_') body = {'quota_class_set': {request_param: 5}} self.assert_called( 'PUT', '/os-quota-class-sets/97f4c221bff44578b0300df4ef119353', body) def test_quota_class_update_injected_file_2_57(self): """Tests that trying to update injected_file* quota with microversion 2.57 fails. """ for quota in ('--injected-files', '--injected-file-content-bytes', '--injected-file-path-bytes'): cmd = 'quota-class-update default %s=5' % quota self.assertRaises(SystemExit, self.run_command, cmd, api_version='2.57') def test_backup(self): out, err = self.run_command('backup sample-server back1 daily 1') # With microversion < 2.45 there is no output from this command. self.assertEqual(0, len(out)) self.assert_called('POST', '/servers/1234/action', {'createBackup': {'name': 'back1', 'backup_type': 'daily', 'rotation': '1'}}) self.run_command('backup 1234 back1 daily 1') self.assert_called('POST', '/servers/1234/action', {'createBackup': {'name': 'back1', 'backup_type': 'daily', 'rotation': '1'}}) def test_backup_2_45(self): """Tests the backup command with the 2.45 microversion which handles a different response and prints out the backup snapshot image details. """ out, err = self.run_command( 'backup sample-server back1 daily 1', api_version='2.45') # We should see the backup snapshot image name in the output. self.assertIn('back1', out) self.assertIn('SAVING', out) self.assert_called_anytime( 'POST', '/servers/1234/action', {'createBackup': {'name': 'back1', 'backup_type': 'daily', 'rotation': '1'}}) def test_limits(self): out = self.run_command('limits')[0] self.assert_called('GET', '/limits') self.assertIn('Personality', out) self.run_command('limits --reserved') self.assert_called('GET', '/limits?reserved=1') self.run_command('limits --tenant 1234') self.assert_called('GET', '/limits?tenant_id=1234') stdout, _err = self.run_command('limits --tenant 1234') self.assertIn('Verb', stdout) self.assertIn('Name', stdout) def test_print_absolute_limits(self): # Note: This test is to validate that no exception is # thrown if in case we pass multiple custom fields limits = [TestAbsoluteLimits('maxTotalPrivateNetworks', 3), TestAbsoluteLimits('totalPrivateNetworksUsed', 0), # Above two fields are custom fields TestAbsoluteLimits('maxImageMeta', 15), TestAbsoluteLimits('totalCoresUsed', 10), TestAbsoluteLimits('totalInstancesUsed', 5), TestAbsoluteLimits('maxServerMeta', 10), TestAbsoluteLimits('totalRAMUsed', 10240), TestAbsoluteLimits('totalFloatingIpsUsed', 10)] novaclient.v2.shell._print_absolute_limits(limits=limits) def test_limits_2_57(self): """Tests the limits command at microversion 2.57 where personality size limits should not be shown. """ out = self.run_command('limits', api_version='2.57')[0] self.assert_called('GET', '/limits') self.assertNotIn('Personality', out) def test_evacuate(self): self.run_command('evacuate sample-server new_host') self.assert_called('POST', '/servers/1234/action', {'evacuate': {'host': 'new_host', 'onSharedStorage': False}}) self.run_command('evacuate sample-server new_host ' '--password NewAdminPass') self.assert_called('POST', '/servers/1234/action', {'evacuate': {'host': 'new_host', 'onSharedStorage': False, 'adminPass': 'NewAdminPass'}}) self.run_command('evacuate sample-server new_host ' '--on-shared-storage') self.assert_called('POST', '/servers/1234/action', {'evacuate': {'host': 'new_host', 'onSharedStorage': True}}) def test_evacuate_v2_29(self): self.run_command('evacuate sample-server new_host', api_version="2.29") self.assert_called('POST', '/servers/1234/action', {'evacuate': {'host': 'new_host'}}) self.run_command('evacuate sample-server new_host ' '--password NewAdminPass', api_version="2.29") self.assert_called('POST', '/servers/1234/action', {'evacuate': {'host': 'new_host', 'adminPass': 'NewAdminPass'}}) self.run_command('evacuate --force sample-server new_host', api_version="2.29") self.assert_called('POST', '/servers/1234/action', {'evacuate': {'host': 'new_host', 'force': True}}) def test_evacuate_v2_68(self): self.run_command('evacuate sample-server new_host', api_version='2.68') self.assert_called('POST', '/servers/1234/action', {'evacuate': {'host': 'new_host'}}) self.assertRaises( SystemExit, self.run_command, 'evacuate --force sample-server new_host', api_version='2.68') def test_evacuate_with_no_target_host(self): self.run_command('evacuate sample-server') self.assert_called('POST', '/servers/1234/action', {'evacuate': {'onSharedStorage': False}}) self.run_command('evacuate sample-server --password NewAdminPass') self.assert_called('POST', '/servers/1234/action', {'evacuate': {'onSharedStorage': False, 'adminPass': 'NewAdminPass'}}) self.run_command('evacuate sample-server --on-shared-storage') self.assert_called('POST', '/servers/1234/action', {'evacuate': {'onSharedStorage': True}}) def test_get_password(self): self.run_command('get-password sample-server /foo/id_rsa') self.assert_called('GET', '/servers/1234/os-server-password') def test_get_password_without_key(self): self.run_command('get-password sample-server') self.assert_called('GET', '/servers/1234/os-server-password') def test_clear_password(self): self.run_command('clear-password sample-server') self.assert_called('DELETE', '/servers/1234/os-server-password') def test_availability_zone_list(self): self.run_command('availability-zone-list') self.assert_called('GET', '/os-availability-zone/detail') def test_console_log(self): out = self.run_command('console-log --length 20 1234')[0] self.assert_called('POST', '/servers/1234/action', body={'os-getConsoleOutput': {'length': '20'}}) self.assertIn('foo', out) def test_server_security_group_add(self): self.run_command('add-secgroup sample-server testgroup') self.assert_called('POST', '/servers/1234/action', {'addSecurityGroup': {'name': 'testgroup'}}) def test_server_security_group_remove(self): self.run_command('remove-secgroup sample-server testgroup') self.assert_called('POST', '/servers/1234/action', {'removeSecurityGroup': {'name': 'testgroup'}}) def test_server_security_group_list(self): self.run_command('list-secgroup 1234') self.assert_called('GET', '/servers/1234/os-security-groups') def test_interface_list(self): out = self.run_command('interface-list 1234')[0] self.assert_called('GET', '/servers/1234/os-interface') self.assertNotIn('Tag', out) def test_interface_list_v2_70(self): out = self.run_command('interface-list 1234', api_version='2.70')[0] self.assert_called('GET', '/servers/1234/os-interface') self.assertIn('test-tag', out) def test_interface_attach(self): self.run_command('interface-attach --port-id port_id 1234') self.assert_called('POST', '/servers/1234/os-interface', {'interfaceAttachment': {'port_id': 'port_id'}}) def test_interface_attach_with_tag_pre_v2_49(self): self.assertRaises( SystemExit, self.run_command, 'interface-attach --port-id port_id --tag test_tag 1234', api_version='2.48') def test_interface_attach_with_tag(self): out = self.run_command( 'interface-attach --port-id port_id --tag test-tag 1234', api_version='2.49')[0] self.assert_called('POST', '/servers/1234/os-interface', {'interfaceAttachment': {'port_id': 'port_id', 'tag': 'test-tag'}}) self.assertNotIn('test-tag', out) def test_interface_attach_v2_70(self): out = self.run_command( 'interface-attach --port-id port_id --tag test-tag 1234', api_version='2.70')[0] self.assert_called('POST', '/servers/1234/os-interface', {'interfaceAttachment': {'port_id': 'port_id', 'tag': 'test-tag'}}) self.assertIn('test-tag', out) def test_interface_detach(self): self.run_command('interface-detach 1234 port_id') self.assert_called('DELETE', '/servers/1234/os-interface/port_id') def test_volume_attachments(self): out = self.run_command('volume-attachments 1234')[0] self.assert_called('GET', '/servers/1234/os-volume_attachments') self.assertNotIn('test-tag', out) def test_volume_attachments_v2_70(self): out = self.run_command( 'volume-attachments 1234', api_version='2.70')[0] self.assert_called('GET', '/servers/1234/os-volume_attachments') self.assertIn('test-tag', out) def test_volume_attach(self): self.run_command('volume-attach sample-server Work /dev/vdb') self.assert_called('POST', '/servers/1234/os-volume_attachments', {'volumeAttachment': {'device': '/dev/vdb', 'volumeId': 'Work'}}) def test_volume_attach_without_device(self): self.run_command('volume-attach sample-server Work') self.assert_called('POST', '/servers/1234/os-volume_attachments', {'volumeAttachment': {'volumeId': 'Work'}}) def test_volume_attach_with_tag_pre_v2_49(self): self.assertRaises( SystemExit, self.run_command, 'volume-attach --tag test_tag sample-server Work /dev/vdb', api_version='2.48') def test_volume_attach_with_tag(self): out = self.run_command( 'volume-attach --tag test_tag sample-server Work /dev/vdb', api_version='2.49')[0] self.assert_called('POST', '/servers/1234/os-volume_attachments', {'volumeAttachment': {'device': '/dev/vdb', 'volumeId': 'Work', 'tag': 'test_tag'}}) self.assertNotIn('test-tag', out) def test_volume_attach_with_tag_v2_70(self): out = self.run_command( 'volume-attach --tag test-tag sample-server Work /dev/vdb', api_version='2.70')[0] self.assert_called('POST', '/servers/1234/os-volume_attachments', {'volumeAttachment': {'device': '/dev/vdb', 'volumeId': 'Work', 'tag': 'test-tag'}}) self.assertIn('test-tag', out) def test_volume_attachments_pre_v2_79(self): out = self.run_command( 'volume-attachments 1234', api_version='2.78')[0] self.assert_called('GET', '/servers/1234/os-volume_attachments') self.assertNotIn('DELETE ON TERMINATION', out) def test_volume_attachments_v2_79(self): out = self.run_command( 'volume-attachments 1234', api_version='2.79')[0] self.assert_called('GET', '/servers/1234/os-volume_attachments') self.assertIn('DELETE ON TERMINATION', out) def test_volume_attachments_pre_v2_89(self): out = self.run_command( 'volume-attachments 1234', api_version='2.88')[0] self.assert_called('GET', '/servers/1234/os-volume_attachments') # We can't assert just ID here as it's part of various other fields self.assertIn('| ID', out) self.assertNotIn('ATTACHMENT ID', out) self.assertNotIn('BDM UUID', out) def test_volume_attachments_v2_89(self): out = self.run_command( 'volume-attachments 1234', api_version='2.89')[0] self.assert_called('GET', '/servers/1234/os-volume_attachments') # We can't assert just ID here as it's part of various other fields self.assertNotIn('| ID', out) self.assertIn('ATTACHMENT ID', out) self.assertIn('BDM UUID', out) def test_volume_attach_with_delete_on_termination_pre_v2_79(self): self.assertRaises( SystemExit, self.run_command, 'volume-attach --delete-on-termination sample-server ' 'Work /dev/vdb', api_version='2.78') def test_volume_attach_with_delete_on_termination_v2_79(self): out = self.run_command( 'volume-attach --delete-on-termination sample-server ' '2 /dev/vdb', api_version='2.79')[0] self.assert_called('POST', '/servers/1234/os-volume_attachments', {'volumeAttachment': {'device': '/dev/vdb', 'volumeId': '2', 'delete_on_termination': True}}) self.assertIn('delete_on_termination', out) def test_volume_attach_without_delete_on_termination(self): self.run_command('volume-attach sample-server Work', api_version='2.79') self.assert_called('POST', '/servers/1234/os-volume_attachments', {'volumeAttachment': {'volumeId': 'Work'}}) def test_volume_update_pre_v285(self): """Before microversion 2.85, we should keep the original behavior""" self.run_command('volume-update sample-server Work Work', api_version='2.84') self.assert_called('PUT', '/servers/1234/os-volume_attachments/Work', {'volumeAttachment': {'volumeId': 'Work'}}) def test_volume_update_swap_v285(self): """Microversion 2.85, we should also keep the original behavior.""" self.run_command('volume-update sample-server Work Work', api_version='2.85') self.assert_called('PUT', '/servers/1234/os-volume_attachments/Work', {'volumeAttachment': {'volumeId': 'Work'}}) def test_volume_update_delete_on_termination_pre_v285(self): self.assertRaises( SystemExit, self.run_command, 'volume-update sample-server --delete-on-termination Work Work', api_version='2.84') def test_volume_update_no_delete_on_termination_pre_v285(self): self.assertRaises( SystemExit, self.run_command, 'volume-update sample-server --no-delete-on-termination Work Work', api_version='2.84') def test_volume_update_v285(self): self.run_command('volume-update sample-server --delete-on-termination ' 'Work Work', api_version='2.85') body = {'volumeAttachment': {'volumeId': 'Work', 'delete_on_termination': True}} self.assert_called('PUT', '/servers/1234/os-volume_attachments/Work', body) self.run_command('volume-update sample-server ' '--no-delete-on-termination ' 'Work Work', api_version='2.85') body = {'volumeAttachment': {'volumeId': 'Work', 'delete_on_termination': False}} self.assert_called('PUT', '/servers/1234/os-volume_attachments/Work', body) def test_volume_update_v285_conflicting(self): self.assertRaises( SystemExit, self.run_command, 'volume-update sample-server --delete-on-termination ' '--no-delete-on-termination Work Work', api_version='2.85') def test_volume_detach(self): self.run_command('volume-detach sample-server Work') self.assert_called('DELETE', '/servers/1234/os-volume_attachments/Work') def test_instance_action_list(self): self.run_command('instance-action-list sample-server') self.assert_called('GET', '/servers/1234/os-instance-actions') def test_instance_action_get(self): self.run_command('instance-action sample-server req-abcde12345') self.assert_called( 'GET', '/servers/1234/os-instance-actions/req-abcde12345') def test_instance_action_list_marker_pre_v258_not_allowed(self): cmd = 'instance-action-list sample-server --marker %s' self.assertRaises(SystemExit, self.run_command, cmd % FAKE_UUID_1, api_version='2.57') def test_instance_action_list_limit_pre_v258_not_allowed(self): cmd = 'instance-action-list sample-server --limit 10' self.assertRaises(SystemExit, self.run_command, cmd, api_version='2.57') def test_instance_action_list_changes_since_pre_v258_not_allowed(self): cmd = 'instance-action-list sample-server --changes-since ' \ '2016-02-29T06:23:22' self.assertRaises(SystemExit, self.run_command, cmd, api_version='2.57') def test_instance_action_list_limit_marker_v258(self): out = self.run_command('instance-action-list sample-server --limit 10 ' '--marker %s' % FAKE_UUID_1, api_version='2.58')[0] # Assert that the updated_at value is in the output. self.assertIn('2013-03-25T13:50:09.000000', out) self.assert_called( 'GET', '/servers/1234/os-instance-actions?' 'limit=10&marker=%s' % FAKE_UUID_1) def test_instance_action_list_with_changes_since_v258(self): self.run_command('instance-action-list sample-server ' '--changes-since 2016-02-29T06:23:22', api_version='2.58') self.assert_called( 'GET', '/servers/1234/os-instance-actions?' 'changes-since=2016-02-29T06%3A23%3A22') def test_instance_action_list_with_changes_since_invalid_value_v258(self): ex = self.assertRaises( exceptions.CommandError, self.run_command, 'instance-action-list sample-server --changes-since 0123456789', api_version='2.58') self.assertIn('Invalid changes-since value', str(ex)) def test_instance_action_list_changes_before_pre_v266_not_allowed(self): cmd = 'instance-action-list sample-server --changes-before ' \ '2016-02-29T06:23:22' self.assertRaises(SystemExit, self.run_command, cmd, api_version='2.65') def test_instance_action_list_with_changes_before_v266(self): self.run_command('instance-action-list sample-server ' '--changes-before 2016-02-29T06:23:22', api_version='2.66') self.assert_called( 'GET', '/servers/1234/os-instance-actions?' 'changes-before=2016-02-29T06%3A23%3A22') def test_instance_action_list_with_changes_before_invalid_value_v266(self): ex = self.assertRaises( exceptions.CommandError, self.run_command, 'instance-action-list sample-server --changes-before 0123456789', api_version='2.66') self.assertIn('Invalid changes-before value', str(ex)) def test_instance_usage_audit_log(self): self.run_command('instance-usage-audit-log') self.assert_called('GET', '/os-instance_usage_audit_log') def test_instance_usage_audit_log_with_before(self): self.run_command( ["instance-usage-audit-log", "--before", "2016-12-10 13:59:59.999999"]) self.assert_called('GET', '/os-instance_usage_audit_log' '/2016-12-10%2013%3A59%3A59.999999') def test_migration_list(self): self.run_command('migration-list') self.assert_called('GET', '/os-migrations') def test_migration_list_v223(self): out, _ = self.run_command('migration-list', api_version="2.23") self.assert_called('GET', '/os-migrations') # Make sure there is no UUID in the output. Uses "| UUID" to # avoid collisions with the "Instance UUID" column. self.assertNotIn('| UUID', out) def test_migration_list_with_filters(self): self.run_command('migration-list --host host1 --status finished') self.assert_called('GET', '/os-migrations?host=host1&status=finished') def test_migration_list_marker_pre_v259_not_allowed(self): cmd = 'migration-list --marker %s' self.assertRaises(SystemExit, self.run_command, cmd % FAKE_UUID_1, api_version='2.58') def test_migration_list_limit_pre_v259_not_allowed(self): cmd = 'migration-list --limit 10' self.assertRaises(SystemExit, self.run_command, cmd, api_version='2.58') def test_migration_list_changes_since_pre_v259_not_allowed(self): cmd = 'migration-list --changes-since 2016-02-29T06:23:22' self.assertRaises(SystemExit, self.run_command, cmd, api_version='2.58') def test_migration_list_limit_marker_v259(self): out, _ = self.run_command( 'migration-list --limit 10 --marker %s' % FAKE_UUID_1, api_version='2.59') self.assert_called( 'GET', '/os-migrations?limit=10&marker=%s' % FAKE_UUID_1) # Make sure the UUID column is now in the output. Uses "| UUID" to # avoid collisions with the "Instance UUID" column. self.assertIn('| UUID', out) def test_migration_list_with_changes_since_v259(self): self.run_command('migration-list --changes-since 2016-02-29T06:23:22', api_version='2.59') self.assert_called( 'GET', '/os-migrations?changes-since=2016-02-29T06%3A23%3A22') def test_migration_list_with_changes_since_invalid_value_v259(self): ex = self.assertRaises(exceptions.CommandError, self.run_command, 'migration-list --changes-since 0123456789', api_version='2.59') self.assertIn('Invalid changes-since value', str(ex)) def test_migration_list_with_changes_before_v266(self): self.run_command('migration-list --changes-before 2016-02-29T06:23:22', api_version='2.66') self.assert_called( 'GET', '/os-migrations?changes-before=2016-02-29T06%3A23%3A22') def test_migration_list_with_changes_before_invalid_value_v266(self): ex = self.assertRaises(exceptions.CommandError, self.run_command, 'migration-list --changes-before 0123456789', api_version='2.66') self.assertIn('Invalid changes-before value', str(ex)) def test_migration_list_with_changes_before_pre_v266_not_allowed(self): cmd = 'migration-list --changes-before 2016-02-29T06:23:22' self.assertRaises(SystemExit, self.run_command, cmd, api_version='2.65') def test_migration_list_with_user_id_v280(self): user_id = '13cc0930d27c4be0acc14d7c47a3e1f7' out = self.run_command('migration-list --user-id %s' % user_id, api_version='2.80')[0] self.assert_called('GET', '/os-migrations?user_id=%s' % user_id) self.assertIn('User ID', out) self.assertIn('Project ID', out) def test_migration_list_with_project_id_v280(self): project_id = 'b59c18e5fa284fd384987c5cb25a1853' out = self.run_command('migration-list --project-id %s' % project_id, api_version='2.80')[0] self.assert_called('GET', '/os-migrations?project_id=%s' % project_id) self.assertIn('User ID', out) self.assertIn('Project ID', out) def test_migration_list_with_user_and_project_id_v280(self): user_id = '13cc0930d27c4be0acc14d7c47a3e1f7' project_id = 'b59c18e5fa284fd384987c5cb25a1853' out = self.run_command('migration-list --project-id %(project_id)s ' '--user-id %(user_id)s' % {'user_id': user_id, 'project_id': project_id}, api_version='2.80')[0] self.assert_called('GET', '/os-migrations?project_id=%s&user_id=%s' % (project_id, user_id)) self.assertIn('User ID', out) self.assertIn('Project ID', out) def test_migration_list_with_user_id_pre_v280_not_allowed(self): user_id = '13cc0930d27c4be0acc14d7c47a3e1f7' cmd = 'migration-list --user-id %s' % user_id self.assertRaises(SystemExit, self.run_command, cmd, api_version='2.79') def test_migration_list_with_project_id_pre_v280_not_allowed(self): project_id = 'b59c18e5fa284fd384987c5cb25a1853' cmd = 'migration-list --project-id %s' % project_id self.assertRaises(SystemExit, self.run_command, cmd, api_version='2.79') def test_migration_list_pre_v280(self): out = self.run_command('migration-list', api_version='2.79')[0] self.assert_called('GET', '/os-migrations') self.assertNotIn('User ID', out) self.assertNotIn('Project ID', out) @mock.patch('novaclient.v2.shell._find_server') @mock.patch('os.system') def test_ssh(self, mock_system, mock_find_server): class FakeResources(object): addresses = { "skynet": [ {'version': 4, 'addr': "1.1.1.1", "OS-EXT-IPS:type": 'fixed'}, {'version': 4, 'addr': "2.2.2.2", "OS-EXT-IPS:type": 'floating'}, {'version': 6, 'addr': "2607:f0d0:1002::4", "OS-EXT-IPS:type": 'fixed'}, {'version': 6, 'addr': "7612:a1b2:2004::6"} ] } mock_find_server.return_value = FakeResources() self.run_command("ssh --login bob server") mock_system.assert_called_with("ssh -4 -p22 bob@2.2.2.2 ") self.run_command("ssh alice@server") mock_system.assert_called_with("ssh -4 -p22 alice@2.2.2.2 ") self.run_command("ssh --port 202 server") mock_system.assert_called_with("ssh -4 -p202 root@2.2.2.2 ") self.run_command("ssh --private server") mock_system.assert_called_with("ssh -4 -p22 root@1.1.1.1 ") self.run_command("ssh -i ~/my_rsa_key server --private") mock_system.assert_called_with("ssh -4 -p22 -i ~/my_rsa_key " "root@1.1.1.1 ") self.run_command("ssh --extra-opts -1 server") mock_system.assert_called_with("ssh -4 -p22 root@2.2.2.2 -1") self.run_command("ssh --ipv6 --login carol server") mock_system.assert_called_with("ssh -6 -p22 carol@7612:a1b2:2004::6 ") self.run_command("ssh --ipv6 dan@server") mock_system.assert_called_with("ssh -6 -p22 dan@7612:a1b2:2004::6 ") self.run_command("ssh --ipv6 --port 2022 server") mock_system.assert_called_with("ssh -6 -p2022 " "root@7612:a1b2:2004::6 ") self.run_command("ssh --ipv6 --private server") mock_system.assert_called_with("ssh -6 -p22 root@2607:f0d0:1002::4 ") self.run_command("ssh --ipv6 --identity /home/me/my_dsa_key " "--private server") mock_system.assert_called_with("ssh -6 -p22 -i /home/me/my_dsa_key " "root@2607:f0d0:1002::4 ") self.run_command("ssh --ipv6 --private --extra-opts -1 server") mock_system.assert_called_with("ssh -6 -p22 " "root@2607:f0d0:1002::4 -1") @mock.patch('novaclient.v2.shell._find_server') @mock.patch('os.system') def test_ssh_multinet(self, mock_system, mock_find_server): class FakeResources(object): addresses = { "skynet": [ {'version': 4, 'addr': "1.1.1.1", "OS-EXT-IPS:type": 'fixed'}, {'version': 4, 'addr': "2.2.2.2"}, {'version': 6, 'addr': "2607:f0d0:1002::4", "OS-EXT-IPS:type": 'fixed'} ], "other": [ {'version': 4, 'addr': "2.3.4.5"}, {'version': 6, 'addr': "7612:a1b2:2004::6"} ] } mock_find_server.return_value = FakeResources() self.run_command("ssh --network other server") mock_system.assert_called_with("ssh -4 -p22 root@2.3.4.5 ") self.run_command("ssh --ipv6 --network other server") mock_system.assert_called_with("ssh -6 -p22 root@7612:a1b2:2004::6 ") self.assertRaises(exceptions.ResourceNotFound, self.run_command, "ssh --ipv6 --network nonexistent server") def _check_keypair_add(self, expected_key_type=None, extra_args='', api_version=None): self.run_command("keypair-add %s test" % extra_args, api_version=api_version) expected_body = {"keypair": {"name": "test"}} if expected_key_type: expected_body["keypair"]["type"] = expected_key_type self.assert_called("POST", "/os-keypairs", expected_body) def test_keypair_add_v20(self): self._check_keypair_add(api_version="2.0") def test_keypair_add_v22(self): self._check_keypair_add('ssh', api_version="2.2") def test_keypair_add_ssh(self): self._check_keypair_add('ssh', '--key-type ssh', api_version="2.2") def test_keypair_add_ssh_x509(self): self._check_keypair_add('x509', '--key-type x509', api_version="2.2") def _check_keypair_import(self, expected_key_type=None, extra_args='', api_version=None): with mock.patch.object(builtins, 'open', mock.mock_open(read_data='FAKE_PUBLIC_KEY')): self.run_command('keypair-add --pub-key test.pub %s test' % extra_args, api_version=api_version) expected_body = {"keypair": {'public_key': 'FAKE_PUBLIC_KEY', 'name': 'test'}} if expected_key_type: expected_body["keypair"]["type"] = expected_key_type self.assert_called( 'POST', '/os-keypairs', expected_body) def test_keypair_import_v20(self): self._check_keypair_import(api_version="2.0") def test_keypair_import_v22(self): self._check_keypair_import('ssh', api_version="2.2") def test_keypair_import_ssh(self): self._check_keypair_import('ssh', '--key-type ssh', api_version="2.2") def test_keypair_import_x509(self): self._check_keypair_import('x509', '--key-type x509', api_version="2.2") def test_keypair_stdin(self): with mock.patch('sys.stdin', io.StringIO('FAKE_PUBLIC_KEY')): self.run_command('keypair-add --pub-key - test', api_version="2.2") self.assert_called( 'POST', '/os-keypairs', { 'keypair': {'public_key': 'FAKE_PUBLIC_KEY', 'name': 'test', 'type': 'ssh'}}) def test_keypair_list(self): self.run_command('keypair-list') self.assert_called('GET', '/os-keypairs') def test_keypair_list_with_user_id(self): self.run_command('keypair-list --user test_user', api_version='2.10') self.assert_called('GET', '/os-keypairs?user_id=test_user') def test_keypair_list_with_limit_and_marker(self): self.run_command('keypair-list --marker test_kp --limit 3', api_version='2.35') self.assert_called('GET', '/os-keypairs?limit=3&marker=test_kp') def test_keypair_list_with_user_id_limit_and_marker(self): self.run_command('keypair-list --user test_user --marker test_kp ' '--limit 3', api_version='2.35') self.assert_called( 'GET', '/os-keypairs?limit=3&marker=test_kp&user_id=test_user') def test_keypair_show(self): self.run_command('keypair-show test') self.assert_called('GET', '/os-keypairs/test') def test_keypair_delete(self): self.run_command('keypair-delete test') self.assert_called('DELETE', '/os-keypairs/test') def test_create_server_group(self): self.run_command('server-group-create wjsg affinity') self.assert_called('POST', '/os-server-groups', {'server_group': {'name': 'wjsg', 'policies': ['affinity']}}) def test_create_server_group_v2_64(self): self.run_command('server-group-create sg1 affinity', api_version='2.64') self.assert_called('POST', '/os-server-groups', {'server_group': { 'name': 'sg1', 'policy': 'affinity' }}) def test_create_server_group_with_rules(self): self.run_command('server-group-create sg1 anti-affinity ' '--rule max_server_per_host=3', api_version='2.64') self.assert_called('POST', '/os-server-groups', {'server_group': { 'name': 'sg1', 'policy': 'anti-affinity', 'rules': {'max_server_per_host': 3} }}) def test_create_server_group_with_multi_rules(self): self.run_command('server-group-create sg1 anti-affinity ' '--rule a=b --rule c=d', api_version='2.64') self.assert_called('POST', '/os-server-groups', {'server_group': { 'name': 'sg1', 'policy': 'anti-affinity', 'rules': {'a': 'b', 'c': 'd'} }}) def test_create_server_group_with_invalid_value(self): result = self.assertRaises( exceptions.CommandError, self.run_command, 'server-group-create sg1 anti-affinity ' '--rule max_server_per_host=foo', api_version='2.64') self.assertIn("Invalid 'max_server_per_host' value: foo", str(result)) def test_create_server_group_with_rules_pre_264(self): self.assertRaises(SystemExit, self.run_command, 'server-group-create sg1 anti-affinity ' '--rule max_server_per_host=3', api_version='2.63') def test_create_server_group_with_multiple_policies(self): self.assertRaises(SystemExit, self.run_command, 'server-group-create wjsg affinity anti-affinity') def test_delete_multi_server_groups(self): self.run_command('server-group-delete 12345 56789') self.assert_called('DELETE', '/os-server-groups/56789') self.assert_called('DELETE', '/os-server-groups/12345', pos=-2) def test_list_server_group(self): self.run_command('server-group-list') self.assert_called('GET', '/os-server-groups') def test_list_server_group_with_all_projects(self): self.run_command('server-group-list --all-projects') self.assert_called('GET', '/os-server-groups?all_projects=True') def test_list_server_group_with_limit_and_offset(self): self.run_command('server-group-list --limit 20 --offset 5') self.assert_called('GET', '/os-server-groups?limit=20&offset=5') def test_versions(self): exclusions = set([ 1, # Same as version 2.0 3, # doesn't require any changes in novaclient 4, # fixed-ip-get command is gone 5, # doesn't require any changes in novaclient 7, # doesn't require any changes in novaclient 9, # doesn't require any changes in novaclient 12, # no longer supported 13, # 13 adds information ``project_id`` and ``user_id`` to # ``os-server-groups``, but is not explicitly tested # via wraps and _SUBSTITUTIONS. 15, # doesn't require any changes in novaclient 16, # doesn't require any changes in novaclient 18, # NOTE(andreykurilin): this microversion requires changes in # HttpClient and our SessionClient, which is based on # keystoneauth1.session. Skipping this complicated change # allows to unblock implementation further microversions # before feature-freeze # (we can do it, since nova-api change didn't actually add # new microversion, just an additional checks. See # https://review.opendev.org/#/c/233076/ for more details) 20, # doesn't require any changes in novaclient 21, # doesn't require any changes in novaclient 27, # NOTE(cdent): 27 adds support for updated microversion # headers, and is tested in test_api_versions, but is # not explicitly tested via wraps and _SUBSTITUTIONS. 28, # doesn't require any changes in novaclient 31, # doesn't require any changes in novaclient 32, # doesn't require separate version-wrapped methods in # novaclient 34, # doesn't require any changes in novaclient 37, # There are no versioned wrapped shell method changes for this 38, # doesn't require any changes in novaclient 39, # There are no versioned wrapped shell method changes for this 41, # There are no version-wrapped shell method changes for this. 42, # There are no version-wrapped shell method changes for this. 43, # There are no version-wrapped shell method changes for this. 44, # There are no version-wrapped shell method changes for this. 45, # There are no version-wrapped shell method changes for this. 46, # There are no version-wrapped shell method changes for this. 47, # NOTE(cfriesen): 47 adds support for flavor details embedded # within the server details 48, # There are no version-wrapped shell method changes for this. 51, # There are no version-wrapped shell method changes for this. 52, # There are no version-wrapped shell method changes for this. 54, # There are no version-wrapped shell method changes for this. 57, # There are no version-wrapped shell method changes for this. 60, # There are no client-side changes for volume multiattach. 61, # There are no version-wrapped shell method changes for this. 62, # There are no version-wrapped shell method changes for this. 63, # There are no version-wrapped shell method changes for this. 65, # There are no version-wrapped shell method changes for this. 67, # There are no version-wrapped shell method changes for this. 69, # NOTE(tssurya): 2.69 adds support for missing keys in the # responses of `GET /servers``, ``GET /servers/detail``, # ``GET /servers/{server_id}`` and ``GET /os-services`` when # a cell is down to return minimal constructs. From 2.69 and # upwards, if the response for ``GET /servers/detail`` does # not have the 'flavor' key for those instances in the down # cell, they will be handled on the client side by being # skipped when forming the detailed lists for embedded # flavor information. 70, # There are no version-wrapped shell method changes for this. 71, # There are no version-wrapped shell method changes for this. 72, # There are no version-wrapped shell method changes for this. 74, # There are no version-wrapped shell method changes for this. 75, # There are no version-wrapped shell method changes for this. 76, # doesn't require any changes in novaclient. 77, # There are no version-wrapped shell method changes for this. 82, # There are no version-wrapped shell method changes for this. 83, # There are no version-wrapped shell method changes for this. 84, # There are no version-wrapped shell method changes for this. 86, # doesn't require any changes in novaclient. 87, # doesn't require any changes in novaclient. 89, # There are no version-wrapped shell method changes for this. 93, # There are no version-wrapped shell method changes for this. 94, # There are no version-wrapped shell method changes for this. 95, # There are no version-wrapped shell method changes for this. 96, # There are no version-wrapped shell method changes for this. ]) versions_supported = set(range(0, novaclient.API_MAX_VERSION.ver_minor + 1)) versions_covered = set() for key, values in api_versions._SUBSTITUTIONS.items(): # Exclude version-wrapped if 'novaclient.tests' not in key: for value in values: if value.start_version.ver_major == 2: versions_covered.add(value.start_version.ver_minor) versions_not_covered = versions_supported - versions_covered unaccounted_for = versions_not_covered - exclusions failure_msg = ('Minor versions %s have been skipped. Please do not ' 'raise API_MAX_VERSION without adding support or ' 'excluding them.' % sorted(unaccounted_for)) self.assertEqual(set([]), unaccounted_for, failure_msg) def test_list_v2_10(self): self.run_command('list', api_version='2.10') self.assert_called('GET', '/servers/detail') def test_server_tag_add(self): self.run_command('server-tag-add sample-server tag', api_version='2.26') self.assert_called('PUT', '/servers/1234/tags/tag', None) def test_server_tag_add_many(self): self.run_command('server-tag-add sample-server tag1 tag2 tag3', api_version='2.26') self.assert_called('PUT', '/servers/1234/tags/tag1', None, pos=-3) self.assert_called('PUT', '/servers/1234/tags/tag2', None, pos=-2) self.assert_called('PUT', '/servers/1234/tags/tag3', None, pos=-1) def test_server_tag_set(self): self.run_command('server-tag-set sample-server tag1 tag2', api_version='2.26') self.assert_called('PUT', '/servers/1234/tags', {'tags': ['tag1', 'tag2']}) def test_server_tag_list(self): self.run_command('server-tag-list sample-server', api_version='2.26') self.assert_called('GET', '/servers/1234/tags') def test_server_tag_delete(self): self.run_command('server-tag-delete sample-server tag', api_version='2.26') self.assert_called('DELETE', '/servers/1234/tags/tag') def test_server_tag_delete_many(self): self.run_command('server-tag-delete sample-server tag1 tag2 tag3', api_version='2.26') self.assert_called('DELETE', '/servers/1234/tags/tag1', pos=-3) self.assert_called('DELETE', '/servers/1234/tags/tag2', pos=-2) self.assert_called('DELETE', '/servers/1234/tags/tag3', pos=-1) def test_server_tag_delete_all(self): self.run_command('server-tag-delete-all sample-server', api_version='2.26') self.assert_called('DELETE', '/servers/1234/tags') def test_list_v2_26_tags(self): self.run_command('list --tags tag1,tag2', api_version='2.26') self.assert_called('GET', '/servers/detail?tags=tag1%2Ctag2') def test_list_v2_26_tags_any(self): self.run_command('list --tags-any tag1,tag2', api_version='2.26') self.assert_called('GET', '/servers/detail?tags-any=tag1%2Ctag2') def test_list_v2_26_not_tags(self): self.run_command('list --not-tags tag1,tag2', api_version='2.26') self.assert_called('GET', '/servers/detail?not-tags=tag1%2Ctag2') def test_list_v2_26_not_tags_any(self): self.run_command('list --not-tags-any tag1,tag2', api_version='2.26') self.assert_called('GET', '/servers/detail?not-tags-any=tag1%2Ctag2') def test_list_detail_v269_with_down_cells(self): """Tests nova list at the 2.69 microversion.""" stdout, _stderr = self.run_command('list', api_version='2.69') self.assertIn( '''\ +------+----------------+---------+------------+-------------+----------------------------------------------+ | ID | Name | Status | Task State | Power State | Networks | +------+----------------+---------+------------+-------------+----------------------------------------------+ | 9015 | | UNKNOWN | N/A | N/A | | | 9014 | help | ACTIVE | N/A | N/A | | | 1234 | sample-server | BUILD | N/A | N/A | private=10.11.12.13; public=1.2.3.4, 5.6.7.8 | | 5678 | sample-server2 | ACTIVE | N/A | N/A | private=10.13.12.13; public=4.5.6.7, 5.6.9.8 | | 9012 | sample-server3 | ACTIVE | N/A | N/A | private=10.13.12.13; public=4.5.6.7, 5.6.9.8 | | 9013 | sample-server4 | ACTIVE | N/A | N/A | | +------+----------------+---------+------------+-------------+----------------------------------------------+ ''', # noqa stdout, ) self.assert_called('GET', '/servers/detail') def test_list_v269_with_down_cells(self): stdout, _stderr = self.run_command( 'list --minimal', api_version='2.69') expected = '''\ +------+----------------+ | ID | Name | +------+----------------+ | 9015 | | | 9014 | help | | 1234 | sample-server | | 5678 | sample-server2 | +------+----------------+ ''' self.assertEqual(expected, stdout) self.assert_called('GET', '/servers') def test_show_v269_with_down_cells(self): stdout, _stderr = self.run_command('show 9015', api_version='2.69') self.assertEqual( '''\ +-----------------------------+---------------------------------------------------+ | Property | Value | +-----------------------------+---------------------------------------------------+ | OS-EXT-AZ:availability_zone | geneva | | OS-EXT-STS:power_state | 0 | | created | 2018-12-03T21:06:18Z | | flavor:disk | 1 | | flavor:ephemeral | 0 | | flavor:extra_specs | {} | | flavor:original_name | m1.tiny | | flavor:ram | 512 | | flavor:swap | 0 | | flavor:vcpus | 1 | | id | 9015 | | image | CentOS 5.2 (c99d7632-bd66-4be9-aed5-3dd14b223a76) | | status | UNKNOWN | | tenant_id | 6f70656e737461636b20342065766572 | | user_id | fake | +-----------------------------+---------------------------------------------------+ ''', # noqa stdout, ) FAKE_UUID_2 = 'c99d7632-bd66-4be9-aed5-3dd14b223a76' self.assert_called('GET', '/servers?name=9015', pos=0) self.assert_called('GET', '/servers?name=9015', pos=1) self.assert_called('GET', '/servers/9015', pos=2) self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=3) def test_list_pre_v273(self): exp = self.assertRaises(SystemExit, self.run_command, 'list --locked t', api_version='2.72') self.assertEqual(2, exp.code) def test_list_v273(self): self.run_command('list --locked t', api_version='2.73') self.assert_called('GET', '/servers/detail?locked=t') def test_list_v273_with_sort_key_dir(self): self.run_command('list --sort locked:asc', api_version='2.73') self.assert_called( 'GET', '/servers/detail?sort_dir=asc&sort_key=locked') class PollForStatusTestCase(utils.TestCase): @mock.patch("novaclient.v2.shell.time") def test_simple_usage(self, mock_time): poll_period = 3 some_id = "uuuuuuuuuuuiiiiiiiii" updated_objects = ( base.Resource(None, info={"not_default_field": "INPROGRESS"}), base.Resource(None, info={"not_default_field": "OK"})) poll_fn = mock.MagicMock(side_effect=updated_objects) novaclient.v2.shell._poll_for_status( poll_fn=poll_fn, obj_id=some_id, status_field="not_default_field", final_ok_states=["ok"], poll_period=poll_period, # just want to test printing in separate tests action="some", silent=True, show_progress=False ) self.assertEqual([mock.call(poll_period)], mock_time.sleep.call_args_list) self.assertEqual([mock.call(some_id)] * 2, poll_fn.call_args_list) @mock.patch("novaclient.v2.shell.sys.stdout") @mock.patch("novaclient.v2.shell.time") def test_print_progress(self, mock_time, mock_stdout): updated_objects = ( base.Resource(None, info={"status": "INPROGRESS", "progress": 0}), base.Resource(None, info={"status": "INPROGRESS", "progress": 50}), base.Resource(None, info={"status": "OK", "progress": 100})) poll_fn = mock.MagicMock(side_effect=updated_objects) action = "some" novaclient.v2.shell._poll_for_status( poll_fn=poll_fn, obj_id="uuuuuuuuuuuiiiiiiiii", final_ok_states=["ok"], poll_period="3", action=action, show_progress=True, silent=False) stdout_arg_list = [ mock.call("\n"), mock.call("\rServer %s... 0%% complete" % action), mock.call("\rServer %s... 50%% complete" % action), mock.call("\rServer %s... 100%% complete" % action), mock.call("\nFinished"), mock.call("\n")] self.assertEqual( stdout_arg_list, mock_stdout.write.call_args_list ) @mock.patch("novaclient.v2.shell.time") def test_error_state(self, mock_time): fault_msg = "Oops" updated_objects = ( base.Resource(None, info={"status": "error", "fault": {"message": fault_msg}}), base.Resource(None, info={"status": "error"})) poll_fn = mock.MagicMock(side_effect=updated_objects) action = "some" self.assertRaises(exceptions.ResourceInErrorState, novaclient.v2.shell._poll_for_status, poll_fn=poll_fn, obj_id="uuuuuuuuuuuiiiiiiiii", final_ok_states=["ok"], poll_period="3", action=action, show_progress=True, silent=False) self.assertRaises(exceptions.ResourceInErrorState, novaclient.v2.shell._poll_for_status, poll_fn=poll_fn, obj_id="uuuuuuuuuuuiiiiiiiii", final_ok_states=["ok"], poll_period="3", action=action, show_progress=True, silent=False) class TestUtilMethods(utils.TestCase): def setUp(self): super(TestUtilMethods, self).setUp() self.shell = self.useFixture(ShellFixture()).shell # NOTE(danms): Get a client that we can use to call things outside of # the shell main self.shell.cs = fakes.FakeClient('2.1') def test_find_images(self): """Test find_images() with a name and id.""" images = novaclient.v2.shell._find_images(self.shell.cs, [FAKE_UUID_1, 'back1']) self.assertEqual(2, len(images)) self.assertEqual(FAKE_UUID_1, images[0].id) self.assertEqual(fakes.FAKE_IMAGE_UUID_BACKUP, images[1].id) def test_find_images_missing(self): """Test find_images() where one of the images is not found.""" self.assertRaises(exceptions.CommandError, novaclient.v2.shell._find_images, self.shell.cs, [FAKE_UUID_1, 'foo']) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/v2/test_usage.py0000664000175000017500000001030100000000000024635 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 datetime from novaclient import api_versions from novaclient.tests.unit import utils from novaclient.tests.unit.v2 import fakes from novaclient.v2 import usage class UsageTest(utils.TestCase): def setUp(self): super(UsageTest, self).setUp() self.cs = fakes.FakeClient(api_versions.APIVersion("2.0")) self.usage_type = self._get_usage_type() def _get_usage_type(self): return usage.Usage def test_usage_list(self, detailed=False): now = datetime.datetime.now() usages = self.cs.usage.list(now, now, detailed) self.assert_request_id(usages, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called( 'GET', "/os-simple-tenant-usage?" + ("start=%s&" % now.isoformat()) + ("end=%s&" % now.isoformat()) + ("detailed=%s" % int(bool(detailed)))) for u in usages: self.assertIsInstance(u, usage.Usage) def test_usage_list_detailed(self): self.test_usage_list(True) def test_usage_get(self): now = datetime.datetime.now() u = self.cs.usage.get("tenantfoo", now, now) self.assert_request_id(u, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called( 'GET', "/os-simple-tenant-usage/tenantfoo?" + ("start=%s&" % now.isoformat()) + ("end=%s" % now.isoformat())) self.assertIsInstance(u, usage.Usage) def test_usage_class_get(self): start = '2012-01-22T19:48:41.750722' stop = '2012-01-22T19:48:41.750722' info = {'tenant_id': 'tenantfoo', 'start': start, 'stop': stop} u = usage.Usage(self.cs.usage, info) u.get() self.assert_request_id(u, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called( 'GET', "/os-simple-tenant-usage/tenantfoo?start=%s&end=%s" % (start, stop)) class UsageV40Test(UsageTest): def setUp(self): super(UsageV40Test, self).setUp() self.cs.api_version = api_versions.APIVersion('2.40') def test_usage_list_with_paging(self): now = datetime.datetime.now() usages = self.cs.usage.list(now, now, marker='some-uuid', limit=3) self.assert_request_id(usages, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called( 'GET', '/os-simple-tenant-usage?' + ('start=%s&' % now.isoformat()) + ('end=%s&' % now.isoformat()) + ('limit=3&marker=some-uuid&detailed=0')) for u in usages: self.assertIsInstance(u, usage.Usage) def test_usage_list_detailed_with_paging(self): now = datetime.datetime.now() usages = self.cs.usage.list( now, now, detailed=True, marker='some-uuid', limit=3) self.assert_request_id(usages, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called( 'GET', '/os-simple-tenant-usage?' + ('start=%s&' % now.isoformat()) + ('end=%s&' % now.isoformat()) + ('limit=3&marker=some-uuid&detailed=1')) for u in usages: self.assertIsInstance(u, usage.Usage) def test_usage_get_with_paging(self): now = datetime.datetime.now() u = self.cs.usage.get( 'tenantfoo', now, now, marker='some-uuid', limit=3) self.assert_request_id(u, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called( 'GET', '/os-simple-tenant-usage/tenantfoo?' + ('start=%s&' % now.isoformat()) + ('end=%s&' % now.isoformat()) + ('limit=3&marker=some-uuid')) self.assertIsInstance(u, usage.Usage) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/v2/test_versions.py0000664000175000017500000001047300000000000025413 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 novaclient import api_versions from novaclient import exceptions as exc from novaclient.tests.unit import utils from novaclient.tests.unit.v2 import fakes from novaclient.v2 import versions class VersionsTest(utils.TestCase): def setUp(self): super(VersionsTest, self).setUp() self.cs = fakes.FakeClient(api_versions.APIVersion("2.0")) self.service_type = versions.Version def test_list_services(self): vl = self.cs.versions.list() self.assert_request_id(vl, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('GET', 'http://nova-api:8774') def test_get_current(self): self.cs.callback = [] v = self.cs.versions.get_current() self.assert_request_id(v, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('GET', 'http://nova-api:8774/v2.1/') @mock.patch.object(versions.VersionManager, '_get', side_effect=exc.Unauthorized("401 RAX")) def test_get_current_with_rax_workaround(self, get): self.cs.callback = [] self.assertIsNone(self.cs.versions.get_current()) def test_get_endpoint_without_project_id(self): # create a fake client such that get_endpoint() # doesn't return uuid in url endpoint_type = 'v2.1' expected_endpoint = 'http://nova-api:8774/v2.1/' cs_2_1 = fakes.FakeClient(api_versions.APIVersion("2.0"), endpoint_type=endpoint_type) result = cs_2_1.versions.get_current() self.assert_request_id(result, fakes.FAKE_REQUEST_ID_LIST) self.assertEqual(result.manager.api.client.endpoint_type, endpoint_type, "Check endpoint_type was set") # check that the full request works as expected cs_2_1.assert_called('GET', expected_endpoint) def test_v2_get_endpoint_without_project_id(self): # create a fake client such that get_endpoint() # doesn't return uuid in url endpoint_type = 'v2' expected_endpoint = 'http://nova-api:8774/v2/' cs_2 = fakes.FakeClient(api_versions.APIVersion("2.0"), endpoint_type=endpoint_type) result = cs_2.versions.get_current() self.assert_request_id(result, fakes.FAKE_REQUEST_ID_LIST) self.assertEqual(result.manager.api.client.endpoint_type, endpoint_type, "Check v2 endpoint_type was set") # check that the full request works as expected cs_2.assert_called('GET', expected_endpoint) def test_list_versions(self): fapi = mock.Mock() version_mgr = versions.VersionManager(fapi) version_mgr._list = mock.Mock() data = [ ("https://example.com:777/v2", "https://example.com:777"), ("https://example.com/v2", "https://example.com"), ("http://example.com/compute/v2", "http://example.com/compute"), ("https://example.com/v2/prrrooojeect-uuid", "https://example.com"), ("https://example.com:777/v2.1", "https://example.com:777"), ("https://example.com/v2.1", "https://example.com"), ("http://example.com/compute/v2.1", "http://example.com/compute"), ("https://example.com/v2.1/prrrooojeect-uuid", "https://example.com"), ("http://example.com/compute", "http://example.com/compute"), ("http://compute.example.com", "http://compute.example.com"), ] for endpoint, expected in data: version_mgr._list.reset_mock() fapi.client.get_endpoint.return_value = endpoint version_mgr.list() version_mgr._list.assert_called_once_with(expected, "versions") ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/v2/test_volumes.py0000664000175000017500000001557300000000000025243 0ustar00zuulzuul00000000000000# Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock from novaclient import api_versions from novaclient.tests.unit import utils from novaclient.tests.unit.v2 import fakes from novaclient.v2 import volumes class VolumesTest(utils.TestCase): api_version = "2.0" def setUp(self): super(VolumesTest, self).setUp() self.cs = fakes.FakeClient(api_versions.APIVersion(self.api_version)) def test_create_server_volume(self): v = self.cs.volumes.create_server_volume( server_id=1234, volume_id='15e59938-07d5-11e1-90e3-e3dffe0c5983', device='/dev/vdb' ) self.assert_request_id(v, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('POST', '/servers/1234/os-volume_attachments') self.assertIsInstance(v, volumes.Volume) def test_update_server_volume(self): vol_id = '15e59938-07d5-11e1-90e3-e3dffe0c5983' v = self.cs.volumes.update_server_volume( server_id=1234, src_volid='Work', dest_volid=vol_id ) self.assert_request_id(v, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('PUT', '/servers/1234/os-volume_attachments/Work') self.assertIsInstance(v, volumes.Volume) def test_get_server_volume(self): v = self.cs.volumes.get_server_volume(1234, 'Work') self.assert_request_id(v, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('GET', '/servers/1234/os-volume_attachments/Work') self.assertIsInstance(v, volumes.Volume) def test_get_server_volume_with_exception(self): self.assertRaises(TypeError, self.cs.volumes.get_server_volume, "1234") self.assertRaises(TypeError, self.cs.volumes.get_server_volume, "1234", volume_id="Work", attachment_id="123") @mock.patch('warnings.warn') def test_get_server_volume_with_warn(self, mock_warn): self.cs.volumes.get_server_volume(1234, volume_id=None, attachment_id="Work") mock_warn.assert_called_once() def test_list_server_volumes(self): vl = self.cs.volumes.get_server_volumes(1234) self.assert_request_id(vl, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('GET', '/servers/1234/os-volume_attachments') for v in vl: self.assertIsInstance(v, volumes.Volume) def test_delete_server_volume(self): ret = self.cs.volumes.delete_server_volume(1234, 'Work') self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('DELETE', '/servers/1234/os-volume_attachments/Work') class VolumesV249Test(VolumesTest): api_version = "2.49" def test_create_server_volume_with_tag(self): v = self.cs.volumes.create_server_volume( server_id=1234, volume_id='15e59938-07d5-11e1-90e3-e3dffe0c5983', device='/dev/vdb', tag='test_tag' ) self.assert_request_id(v, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called( 'POST', '/servers/1234/os-volume_attachments', {'volumeAttachment': { 'volumeId': '15e59938-07d5-11e1-90e3-e3dffe0c5983', 'device': '/dev/vdb', 'tag': 'test_tag'}}) self.assertIsInstance(v, volumes.Volume) def test_delete_server_volume_with_exception(self): self.assertRaises(TypeError, self.cs.volumes.delete_server_volume, "1234") self.assertRaises(TypeError, self.cs.volumes.delete_server_volume, "1234", volume_id="Work", attachment_id="123") @mock.patch('warnings.warn') def test_delete_server_volume_with_warn(self, mock_warn): self.cs.volumes.delete_server_volume(1234, volume_id=None, attachment_id="Work") mock_warn.assert_called_once() class VolumesV279Test(VolumesV249Test): api_version = "2.79" def test_create_server_volume_with_delete_on_termination(self): v = self.cs.volumes.create_server_volume( server_id=1234, volume_id='15e59938-07d5-11e1-90e3-e3dffe0c5983', device='/dev/vdb', tag='tag1', delete_on_termination=True ) self.assert_request_id(v, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called( 'POST', '/servers/1234/os-volume_attachments', {'volumeAttachment': { 'volumeId': '15e59938-07d5-11e1-90e3-e3dffe0c5983', 'device': '/dev/vdb', 'tag': 'tag1', 'delete_on_termination': True}}) self.assertIsInstance(v, volumes.Volume) def test_create_server_volume_with_delete_on_termination_pre_v279(self): self.cs.api_version = api_versions.APIVersion('2.78') ex = self.assertRaises( TypeError, self.cs.volumes.create_server_volume, "1234", volume_id='15e59938-07d5-11e1-90e3-e3dffe0c5983', delete_on_termination=True) self.assertIn('delete_on_termination', str(ex)) class VolumesV285Test(VolumesV279Test): api_version = "2.85" def test_volume_update_server_volume(self): v = self.cs.volumes.update_server_volume( server_id=1234, src_volid='Work', dest_volid='Work', delete_on_termination=True ) self.assert_request_id(v, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('PUT', '/servers/1234/os-volume_attachments/Work') self.assertIsInstance(v, volumes.Volume) def test_volume_update_server_volume_pre_v285(self): self.cs.api_version = api_versions.APIVersion('2.84') ex = self.assertRaises( TypeError, self.cs.volumes.update_server_volume, "1234", 'Work', 'Work', delete_on_termination=True) self.assertIn('delete_on_termination', str(ex)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/tests/unit/v2/testfile.txt0000664000175000017500000000000500000000000024500 0ustar00zuulzuul00000000000000BLAH ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/utils.py0000664000175000017500000003072700000000000021200 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 contextlib import os import re import textwrap import time from urllib import parse from oslo_serialization import jsonutils from oslo_utils import encodeutils from oslo_utils import uuidutils import prettytable from novaclient import exceptions from novaclient.i18n import _ VALID_KEY_REGEX = re.compile(r"[\w\.\- :]+$", re.UNICODE) 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 get_service_type(f): """Retrieves service type from function.""" return getattr(f, 'service_type', None) 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 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 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 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 pretty_choice_list(values): return ', '.join("'%s'" % x for x in values) def pretty_choice_dict(values): """Returns a formatted dict as 'key=value'.""" return pretty_choice_list( ['%s=%s' % (k, values[k]) for k in sorted(values)]) def print_list(objs, fields, formatters={}, sortby_index=None): if sortby_index is None: sortby = None else: sortby = fields[sortby_index] mixed_case_fields = ['serverId'] pt = prettytable.PrettyTable([f for f in fields], caching=False) pt.align = 'l' for o in objs: row = [] for field in fields: if field in formatters: row.append(formatters[field](o)) else: if field in mixed_case_fields: field_name = field.replace(' ', '_') else: field_name = field.lower().replace(' ', '_') data = getattr(o, field_name, '') if data is None: data = '-' # '\r' would break the table, so remove it. data = str(data).replace("\r", "") row.append(data) pt.add_row(row) if sortby is not None: result = encodeutils.safe_encode(pt.get_string(sortby=sortby)) else: result = encodeutils.safe_encode(pt.get_string()) result = result.decode() print(result) def _flatten(data, prefix=None): """Flatten a dict, using name as a prefix for the keys of dict. >>> _flatten('cpu_info', {'arch':'x86_64'}) [('cpu_info_arch': 'x86_64')] """ if isinstance(data, dict): for key, value in data.items(): new_key = '%s_%s' % (prefix, key) if prefix else key if isinstance(value, (dict, list)) and value: for item in _flatten(value, new_key): yield item else: yield new_key, value else: yield prefix, data def flatten_dict(data): """Return a new dict whose sub-dicts have been merged into the original. Each of the parents keys are prepended to the child's to prevent collisions. Any string elements will be JSON parsed before flattening. >>> flatten_dict({'service': {'host':'cloud9@compute-068', 'id': 143}}) {'service_host': colud9@compute-068', 'service_id': 143} """ data = data.copy() # Try and decode any nested JSON structures. for key, value in data.items(): if isinstance(value, str): try: data[key] = jsonutils.loads(value) except ValueError: pass return dict(_flatten(data)) def print_dict(d, dict_property="Property", dict_value="Value", wrap=0): pt = prettytable.PrettyTable([dict_property, dict_value], caching=False) pt.align = 'l' for k, v in sorted(d.items()): # convert dict to str to check length if isinstance(v, (dict, list)): v = jsonutils.dumps(v, ensure_ascii=False) 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 or '\r' in v): # '\r' would break the table, so remove it. if '\r' in v: v = v.replace('\r', '') lines = v.strip().split(r'\n') col1 = k for line in lines: pt.add_row([col1, line]) col1 = '' else: if v is None: v = '-' pt.add_row([k, v]) result = encodeutils.safe_encode(pt.get_string()) result = result.decode() print(result) def find_resource(manager, name_or_id, wrap_exception=True, **find_args): """Helper for the _find_* methods.""" # for str id which is not uuid (for Flavor, Keypair and hypervsior in cells # environments search currently) if getattr(manager, 'is_alphanum_id_allowed', False): try: return manager.get(name_or_id) except exceptions.NotFound: pass # first try to get entity as uuid try: tmp_id = encodeutils.safe_encode(name_or_id) tmp_id = tmp_id.decode() if uuidutils.is_uuid_like(tmp_id): return manager.get(tmp_id) except (TypeError, exceptions.NotFound): pass # then try to get entity as name try: try: resource = getattr(manager, 'resource_class', None) name_attr = resource.NAME_ATTR if resource else 'name' kwargs = {name_attr: name_or_id} kwargs.update(find_args) return manager.find(**kwargs) except exceptions.NotFound: pass # then try to find entity by human_id try: return manager.find(human_id=name_or_id, **find_args) except exceptions.NotFound: pass except exceptions.NoUniqueMatch: msg = (_("Multiple %(class)s matches found for '%(name)s', use an ID " "to be more specific.") % {'class': manager.resource_class.__name__.lower(), 'name': name_or_id}) if wrap_exception: raise exceptions.CommandError(msg) raise exceptions.NoUniqueMatch(msg) # finally try to get entity as integer id try: return manager.get(int(name_or_id)) except (TypeError, ValueError, exceptions.NotFound): msg = (_("No %(class)s with a name or ID of '%(name)s' exists.") % {'class': manager.resource_class.__name__.lower(), 'name': name_or_id}) if wrap_exception: raise exceptions.CommandError(msg) raise exceptions.NotFound(404, msg) def format_servers_list_networks(server): output = [] for (network, addresses) in server.networks.items(): if len(addresses) == 0: continue addresses_csv = ', '.join(addresses) group = "%s=%s" % (network, addresses_csv) output.append(group) return '; '.join(output) def format_security_groups(groups): return ', '.join(group['name'] for group in groups) 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 safe_issubclass(*args): """Like issubclass, but will just return False if not a class.""" try: if issubclass(*args): return True except TypeError: pass return False def _get_resource_string(resource): if hasattr(resource, 'human_id') and resource.human_id: if hasattr(resource, 'id') and resource.id: return "%s (%s)" % (resource.human_id, resource.id) else: return resource.human_id elif hasattr(resource, 'id') and resource.id: return resource.id else: return resource def do_action_on_many(action, resources, success_msg, error_msg): """Helper to run an action on many resources.""" failure_flag = False for resource in resources: try: action(resource) print(success_msg % _get_resource_string(resource)) except Exception as e: failure_flag = True print(encodeutils.safe_encode(str(e))) if failure_flag: raise exceptions.CommandError(error_msg) def is_integer_like(val): """Returns validation of a value as an integer.""" try: int(val) return True except (TypeError, ValueError, AttributeError): return False def validate_flavor_metadata_keys(keys): for key in keys: valid_name = VALID_KEY_REGEX.match(key) if not valid_name: msg = _('Invalid key: "%s". Keys may only contain letters, ' 'numbers, spaces, underscores, periods, colons and ' 'hyphens.') raise exceptions.CommandError(msg % key) @contextlib.contextmanager def record_time(times, enabled, *args): """Record the time of a specific action. :param times: A list of tuples holds time data. :param enabled: Whether timing is enabled. :param args: Other data to be stored besides time data, these args will be joined to a string. """ if not enabled: yield else: start = time.time() yield end = time.time() times.append((' '.join(args), start, end)) def prepare_query_string(params): """Convert dict params to query string""" # Transform the dict to a sequence of two-element tuples in fixed # order, then the encoded string will be consistent in Python 2&3. if not params: return '' params = sorted(params.items(), key=lambda x: x[0]) return '?%s' % parse.urlencode(params) if params else '' def get_url_with_filter(url, filters): query_string = prepare_query_string(filters) url = "%s%s" % (url, query_string) return url ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1725030944.8244665 python-novaclient-18.7.0/novaclient/v2/0000775000175000017500000000000000000000000020004 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/v2/__init__.py0000664000175000017500000000121100000000000022110 0ustar00zuulzuul00000000000000# # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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 novaclient.v2.client import Client # noqa ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/v2/agents.py0000664000175000017500000000504500000000000021643 0ustar00zuulzuul00000000000000# Copyright 2012 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ agent interface """ from novaclient import base # NOTE(takashin): The os-agents APIs have been removed # in https://review.opendev.org/c/openstack/nova/+/749309 . # But the following API bindings remains as there are # because the python-openstackclient depends on them. class Agent(base.Resource): def __repr__(self): return "" % self.agent def _add_details(self, info): dico = 'resource' in info and info['resource'] or info for (k, v) in dico.items(): setattr(self, k, v) class AgentsManager(base.ManagerWithFind): resource_class = Agent def list(self, hypervisor=None): """List all agent builds.""" url = "/os-agents" if hypervisor: url = "/os-agents?hypervisor=%s" % hypervisor return self._list(url, "agents") def _build_update_body(self, version, url, md5hash): return {'para': {'version': version, 'url': url, 'md5hash': md5hash}} def update(self, id, version, url, md5hash): """Update an existing agent build.""" body = self._build_update_body(version, url, md5hash) return self._update('/os-agents/%s' % id, body, 'agent') def create(self, os, architecture, version, url, md5hash, hypervisor): """Create a new agent build.""" body = {'agent': {'hypervisor': hypervisor, 'os': os, 'architecture': architecture, 'version': version, 'url': url, 'md5hash': md5hash}} return self._create('/os-agents', body, 'agent') def delete(self, id): """ Deletes an existing agent build. :param id: The agent's id to delete :returns: An instance of novaclient.base.TupleWithMeta """ return self._delete('/os-agents/%s' % id) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/v2/aggregates.py0000664000175000017500000001070100000000000022466 0ustar00zuulzuul00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Aggregate interface.""" from novaclient import api_versions from novaclient import base class Aggregate(base.Resource): """An aggregates is a collection of compute hosts.""" def __repr__(self): return "" % self.id def update(self, values): """Update the name and/or availability zone.""" return self.manager.update(self, values) def add_host(self, host): return self.manager.add_host(self, host) def remove_host(self, host): return self.manager.remove_host(self, host) def set_metadata(self, metadata): return self.manager.set_metadata(self, metadata) def delete(self): """ Delete the own aggregate. :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.delete(self) @api_versions.wraps("2.81") def cache_images(self, images): return self.manager.cache_images(self, images) class AggregateManager(base.ManagerWithFind): resource_class = Aggregate def list(self): """Get a list of os-aggregates.""" return self._list('/os-aggregates', 'aggregates') def create(self, name, availability_zone): """Create a new aggregate.""" body = {'aggregate': {'name': name, 'availability_zone': availability_zone}} return self._create('/os-aggregates', body, 'aggregate') def get(self, aggregate): """Get details of the specified aggregate.""" return self._get('/os-aggregates/%s' % (base.getid(aggregate)), "aggregate") # NOTE:(dtroyer): utils.find_resource() uses manager.get() but we need to # keep the API backward compatible def get_details(self, aggregate): """Get details of the specified aggregate.""" return self.get(aggregate) def update(self, aggregate, values): """Update the name and/or availability zone.""" body = {'aggregate': values} return self._update("/os-aggregates/%s" % base.getid(aggregate), body, "aggregate") def add_host(self, aggregate, host): """Add a host into the Host Aggregate.""" body = {'add_host': {'host': host}} return self._create("/os-aggregates/%s/action" % base.getid(aggregate), body, "aggregate") def remove_host(self, aggregate, host): """Remove a host from the Host Aggregate.""" body = {'remove_host': {'host': host}} return self._create("/os-aggregates/%s/action" % base.getid(aggregate), body, "aggregate") def set_metadata(self, aggregate, metadata): """Set aggregate metadata, replacing the existing metadata.""" body = {'set_metadata': {'metadata': metadata}} return self._create("/os-aggregates/%s/action" % base.getid(aggregate), body, "aggregate") def delete(self, aggregate): """ Delete the specified aggregate. :param aggregate: The aggregate to delete :returns: An instance of novaclient.base.TupleWithMeta """ return self._delete('/os-aggregates/%s' % (base.getid(aggregate))) @api_versions.wraps("2.81") def cache_images(self, aggregate, images): """ Request images be cached on a given aggregate. :param aggregate: The aggregate to target :param images: A list of image IDs to request caching :returns: An instance of novaclient.base.TupleWithMeta """ body = { 'cache': [{'id': base.getid(image)} for image in images], } resp, body = self.api.client.post( "/os-aggregates/%s/images" % base.getid(aggregate), body=body) return self.convert_into_with_meta(body, resp) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/v2/assisted_volume_snapshots.py0000664000175000017500000000350000000000000025664 0ustar00zuulzuul00000000000000# Copyright (C) 2013, 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. """ Assisted volume snapshots - to be used by Cinder and not end users. """ from oslo_serialization import jsonutils from novaclient import base class Snapshot(base.Resource): def __repr__(self): return "" % self.id def delete(self): """ Delete this snapshot. :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.delete(self) class AssistedSnapshotManager(base.Manager): resource_class = Snapshot def create(self, volume_id, create_info): body = {'snapshot': {'volume_id': volume_id, 'create_info': create_info}} return self._create('/os-assisted-volume-snapshots', body, 'snapshot') def delete(self, snapshot, delete_info): """ Delete a specified assisted volume snapshot. :param snapshot: an assisted volume snapshot to delete :param delete_info: Information for snapshot deletion :returns: An instance of novaclient.base.TupleWithMeta """ return self._delete("/os-assisted-volume-snapshots/%s?delete_info=%s" % (base.getid(snapshot), jsonutils.dumps(delete_info))) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/v2/availability_zones.py0000664000175000017500000000312000000000000024242 0ustar00zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Availability Zone interface """ from novaclient import base class AvailabilityZone(base.Resource): """An availability zone object.""" NAME_ATTR = 'display_name' def __repr__(self): return "" % self.zoneName class AvailabilityZoneManager(base.ManagerWithFind): """Manage :class:`AvailabilityZone` resources.""" resource_class = AvailabilityZone return_parameter_name = "availabilityZoneInfo" def list(self, detailed=True): """Get a list of all availability zones. :param detailed: If True, list availability zones with details. :returns: list of :class:`AvailabilityZone` """ if detailed is True: return self._list("/os-availability-zone/detail", self.return_parameter_name) else: return self._list("/os-availability-zone", self.return_parameter_name) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/v2/client.py0000664000175000017500000002203500000000000021636 0ustar00zuulzuul00000000000000# Copyright 2012 OpenStack Foundation # Copyright 2013 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import logging from novaclient import client from novaclient import exceptions from novaclient.i18n import _ from novaclient.v2 import agents from novaclient.v2 import aggregates from novaclient.v2 import assisted_volume_snapshots from novaclient.v2 import availability_zones from novaclient.v2 import flavor_access from novaclient.v2 import flavors from novaclient.v2 import hypervisors from novaclient.v2 import images from novaclient.v2 import instance_action from novaclient.v2 import instance_usage_audit_log from novaclient.v2 import keypairs from novaclient.v2 import limits from novaclient.v2 import migrations from novaclient.v2 import networks from novaclient.v2 import quota_classes from novaclient.v2 import quotas from novaclient.v2 import server_external_events from novaclient.v2 import server_groups from novaclient.v2 import server_migrations from novaclient.v2 import servers from novaclient.v2 import services from novaclient.v2 import usage from novaclient.v2 import versions from novaclient.v2 import volumes class Client(object): """Top-level object to access the OpenStack Compute API. .. warning:: All scripts and projects should not initialize this class directly. It should be done via `novaclient.client.Client` interface. """ def __init__(self, api_version=None, auth=None, auth_token=None, auth_url=None, cacert=None, cert=None, direct_use=True, endpoint_override=None, endpoint_type='publicURL', extensions=None, http_log_debug=False, insecure=False, logger=None, os_cache=False, password=None, project_domain_id=None, project_domain_name=None, project_id=None, project_name=None, region_name=None, service_name=None, service_type='compute', session=None, timeout=None, timings=False, user_domain_id=None, user_domain_name=None, user_id=None, username=None, **kwargs): """Initialization of Client object. :param api_version: Compute API version :type api_version: novaclient.api_versions.APIVersion :param str auth: Auth :param str auth_token: Auth token :param str auth_url: Auth URL :param str cacert: ca-certificate :param str cert: certificate :param bool direct_use: Inner variable of novaclient. Do not use it outside novaclient. It's restricted. :param str endpoint_override: Bypass URL :param str endpoint_type: Endpoint Type :param str extensions: Extensions :param bool http_log_debug: Enable debugging for HTTP connections :param bool insecure: Allow insecure :param logging.Logger logger: Logger instance to be used for all logging stuff :param str password: User password :param bool os_cache: OS cache :param str project_domain_id: ID of project domain :param str project_domain_name: Name of project domain :param str project_id: Project/Tenant ID :param str project_name: Project/Tenant name :param str region_name: Region Name :param str service_name: Service Name :param str service_type: Service Type :param str session: Session :param float timeout: API timeout, None or 0 disables :param bool timings: Timings :param str user_domain_id: ID of user domain :param str user_domain_name: Name of user domain :param str user_id: User ID :param str username: Username """ if direct_use: raise exceptions.Forbidden( 403, _("'novaclient.v2.client.Client' is not designed to be " "initialized directly. It is inner class of " "novaclient. You should use " "'novaclient.client.Client' instead. Related lp " "bug-report: 1493576")) # NOTE(cyeoh): In the novaclient context (unlike Nova) the # project_id is not the same as the tenant_id. Here project_id # is a name (what the Nova API often refers to as a project or # tenant name) and tenant_id is a UUID (what the Nova API # often refers to as a project_id or tenant_id). self.project_id = project_id self.project_name = project_name self.user_id = user_id self.flavors = flavors.FlavorManager(self) self.flavor_access = flavor_access.FlavorAccessManager(self) self.glance = images.GlanceManager(self) self.limits = limits.LimitsManager(self) self.servers = servers.ServerManager(self) self.versions = versions.VersionManager(self) # extensions self.agents = agents.AgentsManager(self) self.volumes = volumes.VolumeManager(self) self.keypairs = keypairs.KeypairManager(self) self.neutron = networks.NeutronManager(self) self.quota_classes = quota_classes.QuotaClassSetManager(self) self.quotas = quotas.QuotaSetManager(self) self.usage = usage.UsageManager(self) self.aggregates = aggregates.AggregateManager(self) self.hypervisors = hypervisors.HypervisorManager(self) self.hypervisor_stats = hypervisors.HypervisorStatsManager(self) self.services = services.ServiceManager(self) self.os_cache = os_cache self.availability_zones = \ availability_zones.AvailabilityZoneManager(self) self.server_groups = server_groups.ServerGroupsManager(self) self.server_migrations = \ server_migrations.ServerMigrationsManager(self) # V2.0 extensions: # NOTE(andreykurilin): tenant_networks extension is # deprecated now, which is why it is not initialized by default. self.assisted_volume_snapshots = \ assisted_volume_snapshots.AssistedSnapshotManager(self) self.instance_action = instance_action.InstanceActionManager(self) self.instance_usage_audit_log = \ instance_usage_audit_log.InstanceUsageAuditLogManager(self) self.migrations = migrations.MigrationManager(self) self.server_external_events = \ server_external_events.ServerExternalEventManager(self) self.logger = logger or logging.getLogger(__name__) # Add in any extensions... if extensions: for extension in extensions: if extension.manager_class: setattr(self, extension.name, extension.manager_class(self)) self.client = client._construct_http_client( api_version=api_version, auth=auth, auth_token=auth_token, auth_url=auth_url, cacert=cacert, cert=cert, endpoint_override=endpoint_override, endpoint_type=endpoint_type, http_log_debug=http_log_debug, insecure=insecure, logger=self.logger, os_cache=self.os_cache, password=password, project_domain_id=project_domain_id, project_domain_name=project_domain_name, project_id=project_id, project_name=project_name, region_name=region_name, service_name=service_name, service_type=service_type, session=session, timeout=timeout, timings=timings, user_domain_id=user_domain_id, user_domain_name=user_domain_name, user_id=user_id, username=username, **kwargs) @property def api_version(self): return self.client.api_version @api_version.setter def api_version(self, value): self.client.api_version = value def __enter__(self): raise exceptions.InvalidUsage(_( "NovaClient instance can't be used as a context manager " "since it is redundant in case of SessionClient.")) def __exit__(self, t, v, tb): # do not do anything pass def get_timings(self): return self.client.get_timings() def reset_timings(self): self.client.reset_timings() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/v2/flavor_access.py0000664000175000017500000000473400000000000023200 0ustar00zuulzuul00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Flavor access interface.""" from novaclient import base from novaclient.i18n import _ class FlavorAccess(base.Resource): def __repr__(self): return ("" % (self.flavor_id, self.tenant_id)) class FlavorAccessManager(base.ManagerWithFind): """Manage :class:`FlavorAccess` resources.""" resource_class = FlavorAccess def list(self, **kwargs): # NOTE(mriedem): This looks a bit weird, you would normally expect this # method to just take a flavor arg, but it used to erroneously accept # flavor or tenant, but never actually implemented support for listing # flavor access by tenant. We leave the interface unchanged though for # backward compatibility. if kwargs.get('flavor'): return self._list('/flavors/%s/os-flavor-access' % base.getid(kwargs['flavor']), 'flavor_access') raise NotImplementedError(_('Unknown list options.')) def add_tenant_access(self, flavor, tenant): """Add a tenant to the given flavor access list.""" info = {'tenant': tenant} return self._action('addTenantAccess', flavor, info) def remove_tenant_access(self, flavor, tenant): """Remove a tenant from the given flavor access list.""" info = {'tenant': tenant} return self._action('removeTenantAccess', flavor, info) def _action(self, action, flavor, info, **kwargs): """Perform a flavor action.""" body = {action: info} self.run_hooks('modify_body_for_action', body, **kwargs) url = '/flavors/%s/action' % base.getid(flavor) resp, body = self.api.client.post(url, body=body) items = [self.resource_class(self, res) for res in body['flavor_access']] return base.ListWithMeta(items, resp) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/v2/flavors.py0000664000175000017500000002277400000000000022046 0ustar00zuulzuul00000000000000# Copyright 2010 Jacob Kaplan-Moss # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Flavor interface. """ from oslo_utils import strutils from novaclient import api_versions from novaclient import base from novaclient import exceptions from novaclient.i18n import _ from novaclient import utils class Flavor(base.Resource): """A flavor is an available hardware configuration for a server.""" HUMAN_ID = True def __repr__(self): return "" % self.name @property def ephemeral(self): """Provide a user-friendly accessor to OS-FLV-EXT-DATA:ephemeral.""" return self._info.get("OS-FLV-EXT-DATA:ephemeral", 'N/A') @property def is_public(self): """Provide a user-friendly accessor to os-flavor-access:is_public.""" return self._info.get("os-flavor-access:is_public", 'N/A') def get_keys(self): """ Get extra specs from a flavor. :returns: An instance of novaclient.base.DictWithMeta """ resp, body = self.manager.api.client.get( "/flavors/%s/os-extra_specs" % base.getid(self)) return self.manager.convert_into_with_meta(body["extra_specs"], resp) def set_keys(self, metadata): """Set extra specs on a flavor. :param metadata: A dict of key/value pairs to be set """ utils.validate_flavor_metadata_keys(metadata.keys()) body = {'extra_specs': metadata} return self.manager._create( "/flavors/%s/os-extra_specs" % base.getid(self), body, "extra_specs", return_raw=True) def unset_keys(self, keys): """Unset extra specs on a flavor. :param keys: A list of keys to be unset :returns: An instance of novaclient.base.TupleWithMeta """ result = base.TupleWithMeta((), None) for k in keys: ret = self.manager._delete( "/flavors/%s/os-extra_specs/%s" % (base.getid(self), k)) result.append_request_ids(ret.request_ids) return result def delete(self): """ Delete this flavor. :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.delete(self) @api_versions.wraps('2.55') def update(self, description=None): """ Update the description for this flavor. :param description: The description to set on the flavor. :returns: :class:`Flavor` """ return self.manager.update(self, description=description) class FlavorManager(base.ManagerWithFind): """Manage :class:`Flavor` resources.""" resource_class = Flavor is_alphanum_id_allowed = True def list(self, detailed=True, is_public=True, marker=None, min_disk=None, min_ram=None, limit=None, sort_key=None, sort_dir=None): """Get a list of all flavors. :param detailed: Whether flavor needs to be return with details (optional). :param is_public: Filter flavors with provided access type (optional). None means give all flavors and only admin has query access to all flavor types. :param marker: Begin returning flavors that appear later in the flavor list than that represented by this flavor id (optional). :param min_disk: Filters the flavors by a minimum disk space, in GiB. :param min_ram: Filters the flavors by a minimum RAM, in MiB. :param limit: maximum number of flavors to return (optional). Note the API server has a configurable default limit. If no limit is specified here or limit is larger than default, the default limit will be used. :param sort_key: Flavors list sort key (optional). :param sort_dir: Flavors list sort direction (optional). :returns: list of :class:`Flavor`. """ qparams = {} # is_public is ternary - None means give all flavors. # By default Nova assumes True and gives admins public flavors # and flavors from their own projects only. if marker: qparams['marker'] = str(marker) if min_disk: qparams['minDisk'] = int(min_disk) if min_ram: qparams['minRam'] = int(min_ram) if limit: qparams['limit'] = int(limit) if sort_key: qparams['sort_key'] = str(sort_key) if sort_dir: qparams['sort_dir'] = str(sort_dir) if not is_public: qparams['is_public'] = is_public detail = "" if detailed: detail = "/detail" return self._list("/flavors%s" % detail, "flavors", filters=qparams) def get(self, flavor): """Get a specific flavor. :param flavor: The ID of the :class:`Flavor` to get. :returns: :class:`Flavor` """ return self._get("/flavors/%s" % base.getid(flavor), "flavor") def delete(self, flavor): """Delete a specific flavor. :param flavor: Instance of :class:`Flavor` to delete or ID of the flavor to delete. :returns: An instance of novaclient.base.TupleWithMeta """ return self._delete("/flavors/%s" % base.getid(flavor)) def _build_body(self, name, ram, vcpus, disk, id, swap, ephemeral, rxtx_factor, is_public): return { "flavor": { "name": name, "ram": ram, "vcpus": vcpus, "disk": disk, "id": id, "swap": swap, "OS-FLV-EXT-DATA:ephemeral": ephemeral, "rxtx_factor": rxtx_factor, "os-flavor-access:is_public": is_public, } } def create(self, name, ram, vcpus, disk, flavorid="auto", ephemeral=0, swap=0, rxtx_factor=1.0, is_public=True, description=None): """Create a flavor. :param name: Descriptive name of the flavor :param ram: Memory in MiB for the flavor :param vcpus: Number of VCPUs for the flavor :param disk: Size of local disk in GiB :param flavorid: ID for the flavor (optional). You can use the reserved value ``"auto"`` to have Nova generate a UUID for the flavor in cases where you cannot simply pass ``None``. :param ephemeral: Ephemeral disk space in GiB. :param swap: Swap space in MiB :param rxtx_factor: RX/TX factor :param is_public: Whether or not the flavor is public. :param description: A free form description of the flavor. Limited to 65535 characters in length. Only printable characters are allowed. (Available starting with microversion 2.55) :returns: :class:`Flavor` """ try: ram = int(ram) except (TypeError, ValueError): raise exceptions.CommandError(_("Ram must be an integer.")) try: vcpus = int(vcpus) except (TypeError, ValueError): raise exceptions.CommandError(_("VCPUs must be an integer.")) try: disk = int(disk) except (TypeError, ValueError): raise exceptions.CommandError(_("Disk must be an integer.")) if flavorid == "auto": flavorid = None try: swap = int(swap) except (TypeError, ValueError): raise exceptions.CommandError(_("Swap must be an integer.")) try: ephemeral = int(ephemeral) except (TypeError, ValueError): raise exceptions.CommandError(_("Ephemeral must be an integer.")) try: rxtx_factor = float(rxtx_factor) except (TypeError, ValueError): raise exceptions.CommandError(_("rxtx_factor must be a float.")) try: is_public = strutils.bool_from_string(is_public, True) except Exception: raise exceptions.CommandError(_("is_public must be a boolean.")) supports_description = api_versions.APIVersion('2.55') if description and self.api_version < supports_description: raise exceptions.UnsupportedAttribute('description', '2.55') body = self._build_body(name, ram, vcpus, disk, flavorid, swap, ephemeral, rxtx_factor, is_public) if description: body['flavor']['description'] = description return self._create("/flavors", body, "flavor") @api_versions.wraps('2.55') def update(self, flavor, description=None): """ Update the description of the flavor. :param flavor: The :class:`Flavor` (or its ID) to update. :param description: The description to set on the flavor. """ body = { 'flavor': { 'description': description } } return self._update('/flavors/%s' % base.getid(flavor), body, 'flavor') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/v2/hypervisors.py0000664000175000017500000001500000000000000022747 0ustar00zuulzuul00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Hypervisors interface """ from urllib import parse from novaclient import api_versions from novaclient import base from novaclient import exceptions from novaclient.i18n import _ from novaclient import utils class Hypervisor(base.Resource): NAME_ATTR = 'hypervisor_hostname' def __repr__(self): return "" % self.id class HypervisorManager(base.ManagerWithFind): resource_class = Hypervisor is_alphanum_id_allowed = True def _list_base(self, detailed=True, marker=None, limit=None): path = '/os-hypervisors' if detailed: path += '/detail' params = {} if limit is not None: params['limit'] = int(limit) if marker is not None: params['marker'] = str(marker) path += utils.prepare_query_string(params) return self._list(path, 'hypervisors') @api_versions.wraps("2.0", "2.32") def list(self, detailed=True): """ Get a list of hypervisors. :param detailed: Include a detailed response. """ return self._list_base(detailed=detailed) @api_versions.wraps("2.33") def list(self, detailed=True, marker=None, limit=None): """ Get a list of hypervisors. :param detailed: Include a detailed response. :param marker: Begin returning hypervisors that appear later in the hypervisors list than that represented by this hypervisor ID. Starting with microversion 2.53 the marker must be a UUID hypervisor ID. (optional). :param limit: maximum number of hypervisors to return (optional). Note the API server has a configurable default limit. If no limit is specified here or limit is larger than default, the default limit will be used. """ return self._list_base(detailed=detailed, marker=marker, limit=limit) def search(self, hypervisor_match, servers=False, detailed=False): """ Get a list of matching hypervisors. :param hypervisor_match: The hypervisor host name or a portion of it. The hypervisor hosts are selected with the host name matching this pattern. :param servers: If True, server information is also retrieved. :param detailed: If True, detailed hypervisor information is returned. This requires API version 2.53 or greater. """ # Starting with microversion 2.53, the /servers and /search routes are # deprecated and we get the same results using GET /os-hypervisors # using query parameters for the hostname pattern and servers. if self.api_version >= api_versions.APIVersion('2.53'): url = ('/os-hypervisors%s?hypervisor_hostname_pattern=%s' % ('/detail' if detailed else '', parse.quote(hypervisor_match, safe=''))) if servers: url += '&with_servers=True' else: if detailed: raise exceptions.UnsupportedVersion( _('Parameter "detailed" requires API version 2.53 or ' 'greater.')) target = 'servers' if servers else 'search' url = ('/os-hypervisors/%s/%s' % (parse.quote(hypervisor_match, safe=''), target)) return self._list(url, 'hypervisors') def get(self, hypervisor): """ Get a specific hypervisor. :param hypervisor: Either a Hypervisor object or an ID. Starting with microversion 2.53 the ID must be a UUID value. """ return self._get("/os-hypervisors/%s" % base.getid(hypervisor), "hypervisor") def uptime(self, hypervisor): """ Get the uptime for a specific hypervisor. :param hypervisor: Either a Hypervisor object or an ID. Starting with microversion 2.53 the ID must be a UUID value. """ # Starting with microversion 2.88, the '/os-hypervisors/{id}/uptime' # route is removed in favour of returning 'uptime' in the response of # the '/os-hypervisors/{id}' route. This behaves slightly differently, # in that it won't error out if a virt driver doesn't support reporting # uptime or if the hypervisor is down, but it's a good enough # approximation if self.api_version < api_versions.APIVersion("2.88"): return self._get( "/os-hypervisors/%s/uptime" % base.getid(hypervisor), "hypervisor") resp, body = self.api.client.get( "/os-hypervisors/%s" % base.getid(hypervisor) ) content = { k: v for k, v in body['hypervisor'].items() if k in ('id', 'hypervisor_hostname', 'state', 'status', 'uptime') } return self.resource_class(self, content, loaded=True, resp=resp) def statistics(self): """ Get hypervisor statistics over all compute nodes. Kept for backwards compatibility, new code should call hypervisor_stats.statistics() instead of hypervisors.statistics() """ return self.api.hypervisor_stats.statistics() class HypervisorStats(base.Resource): def __repr__(self): return ("" % (self.count, "s" if self.count != 1 else "")) class HypervisorStatsManager(base.Manager): resource_class = HypervisorStats @api_versions.wraps("2.0", "2.87") def statistics(self): """ Get hypervisor statistics over all compute nodes. """ return self._get("/os-hypervisors/statistics", "hypervisor_statistics") @api_versions.wraps("2.88") def statistics(self): raise exceptions.UnsupportedVersion( _("The 'statistics' API is removed in API version 2.88 or later.") ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/v2/images.py0000664000175000017500000001077300000000000021633 0ustar00zuulzuul00000000000000# Copyright 2010 Jacob Kaplan-Moss # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_utils import uuidutils from novaclient import base from novaclient import exceptions from novaclient.i18n import _ class Image(base.Resource): HUMAN_ID = True def __repr__(self): return "" % self.name class GlanceManager(base.Manager): """Use glance directly from service catalog. This is used to do name to id lookups for images and listing images for the --image-with option to the 'boot' command. Do not use it for anything else besides that. You have been warned. """ resource_class = Image def find_image(self, name_or_id): """Find an image by name or id (user provided input).""" with self.alternate_service_type( 'image', allowed_types=('image',)): # glance catalog entries are the unversioned endpoint, so # we need to jam a version bit in here. if uuidutils.is_uuid_like(name_or_id): # if it's a uuid, then just go collect the info and be # done with it. return self._get('/v2/images/%s' % name_or_id, None) else: matches = self._list('/v2/images?name=%s' % name_or_id, "images") num_matches = len(matches) if num_matches == 0: msg = "No %s matching %s." % ( self.resource_class.__name__, name_or_id) raise exceptions.NotFound(404, msg) elif num_matches > 1: msg = (_("Multiple %(class)s matches found for " "'%(name)s', use an ID to be more specific.") % {'class': self.resource_class.__name__.lower(), 'name': name_or_id}) raise exceptions.NoUniqueMatch(msg) else: matches[0].append_request_ids(matches.request_ids) return matches[0] def find_images(self, names_or_ids): """Find multiple images by name or id (user provided input). :param names_or_ids: A list of strings to use to find images. :returns: novaclient.v2.images.Image objects for each images found :raises exceptions.NotFound: If one or more images is not found :raises exceptions.ClientException: If the image service returns any unexpected images. NOTE: This method always makes two calls to the image service, even if only one image is provided by ID and is returned in the first query. """ with self.alternate_service_type( 'image', allowed_types=('image',)): matches = self._list('/v2/images?id=in:%s' % ','.join( names_or_ids), 'images') matches.extend(self._list('/v2/images?names=in:%s' % ','.join( names_or_ids), 'images')) missed = (set(names_or_ids) - set(m.name for m in matches) - set(m.id for m in matches)) if missed: msg = _("Unable to find image(s): %(images)s") % { "images": ",".join(missed)} raise exceptions.NotFound(404, msg) for match in matches: match.append_request_ids(matches.request_ids) additional = [] for i in matches: if i.name not in names_or_ids and i.id not in names_or_ids: additional.append(i) if additional: msg = _('Additional images found in response') raise exceptions.ClientException(500, msg) return matches def list(self): """ Get a detailed list of all images. :rtype: list of :class:`Image` """ with self.alternate_service_type('image', allowed_types=('image',)): return self._list('/v2/images', 'images') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/v2/instance_action.py0000664000175000017500000001045700000000000023526 0ustar00zuulzuul00000000000000# Copyright 2013 Rackspace Hosting # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from novaclient import api_versions from novaclient import base class InstanceAction(base.Resource): pass class InstanceActionManager(base.ManagerWithFind): resource_class = InstanceAction def get(self, server, request_id): """ Get details of an action performed on an instance. :param request_id: The request_id of the action to get. """ return self._get("/servers/%s/os-instance-actions/%s" % (base.getid(server), request_id), 'instanceAction') @api_versions.wraps("2.0", "2.57") def list(self, server): """ Get a list of actions performed on a server. :param server: The :class:`Server` (or its ID) """ return self._list('/servers/%s/os-instance-actions' % base.getid(server), 'instanceActions') @api_versions.wraps("2.58", "2.65") def list(self, server, marker=None, limit=None, changes_since=None): """ Get a list of actions performed on a server. :param server: The :class:`Server` (or its ID) :param marker: Begin returning actions that appear later in the action list than that represented by this action request id (optional). :param limit: Maximum number of actions to return. (optional). Note the API server has a configurable default limit. If no limit is specified here or limit is larger than default, the default limit will be used. :param changes_since: List only instance actions changed later or equal to a certain point of time. The provided time should be an ISO 8061 formatted time. e.g. 2016-03-04T06:27:59Z . (optional). """ opts = {} if marker: opts['marker'] = marker if limit: opts['limit'] = limit if changes_since: opts['changes-since'] = changes_since return self._list('/servers/%s/os-instance-actions' % base.getid(server), 'instanceActions', filters=opts) @api_versions.wraps("2.66") def list(self, server, marker=None, limit=None, changes_since=None, changes_before=None): """ Get a list of actions performed on a server. :param server: The :class:`Server` (or its ID) :param marker: Begin returning actions that appear later in the action list than that represented by this action request id (optional). :param limit: Maximum number of actions to return. (optional). :param changes_since: List only instance actions changed later or equal to a certain point of time. The provided time should be an ISO 8061 formatted time. e.g. 2016-03-04T06:27:59Z . (optional). :param changes_before: List only instance actions changed earlier or equal to a certain point of time. The provided time should be an ISO 8061 formatted time. e.g. 2016-03-05T06:27:59Z . (optional). """ opts = {} if marker: opts['marker'] = marker if limit: opts['limit'] = limit if changes_since: opts['changes-since'] = changes_since if changes_before: opts['changes-before'] = changes_before return self._list('/servers/%s/os-instance-actions' % base.getid(server), 'instanceActions', filters=opts) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/v2/instance_usage_audit_log.py0000664000175000017500000000253700000000000025404 0ustar00zuulzuul00000000000000# Copyright 2013 Rackspace Hosting # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from urllib import parse from novaclient import base class InstanceUsageAuditLog(base.Resource): pass class InstanceUsageAuditLogManager(base.Manager): resource_class = InstanceUsageAuditLog def get(self, before=None): """Get server usage audits. :param before: Filters the response by the date and time before which to list usage audits. """ if before: return self._get('/os-instance_usage_audit_log/%s' % parse.quote(before, safe=''), 'instance_usage_audit_log') else: return self._get('/os-instance_usage_audit_log', 'instance_usage_audit_logs') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/v2/keypairs.py0000664000175000017500000001643000000000000022211 0ustar00zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Keypair interface """ from novaclient import api_versions from novaclient import base class Keypair(base.Resource): """ A keypair is a ssh key that can be injected into a server on launch. """ def __repr__(self): return "" % self.id def _add_details(self, info): dico = 'keypair' in info and \ info['keypair'] or info for (k, v) in dico.items(): # NOTE(rpodolyaka): keypair name allows us to uniquely identify # a specific keypair, while its id attribute # is nothing more than an implementation # detail. We can safely omit the id attribute # here to ensure setattr() won't raise # AttributeError trying to set read-only # property id if k != 'id': setattr(self, k, v) @property def id(self): return self.name def delete(self): """ Delete this keypair. :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.delete(self) class KeypairManager(base.ManagerWithFind): resource_class = Keypair keypair_prefix = "os-keypairs" is_alphanum_id_allowed = True @api_versions.wraps("2.0", "2.9") def get(self, keypair): """ Get a keypair. :param keypair: The ID of the keypair to get. :rtype: :class:`Keypair` """ return self._get("/%s/%s" % (self.keypair_prefix, base.getid(keypair)), "keypair") @api_versions.wraps("2.10") def get(self, keypair, user_id=None): """ Get a keypair. :param keypair: The ID of the keypair to get. :param user_id: Id of key-pair owner (Admin only). :rtype: :class:`Keypair` """ query_string = "?user_id=%s" % user_id if user_id else "" url = "/%s/%s%s" % (self.keypair_prefix, base.getid(keypair), query_string) return self._get(url, "keypair") @api_versions.wraps("2.0", "2.1") def create(self, name, public_key=None): """ Create a keypair :param name: name for the keypair to create :param public_key: existing public key to import """ body = {'keypair': {'name': name}} if public_key: body['keypair']['public_key'] = public_key return self._create('/%s' % self.keypair_prefix, body, 'keypair') @api_versions.wraps("2.2", "2.9") def create(self, name, public_key=None, key_type="ssh"): """ Create a keypair :param name: name for the keypair to create :param public_key: existing public key to import :param key_type: keypair type to create """ body = {'keypair': {'name': name, 'type': key_type}} if public_key: body['keypair']['public_key'] = public_key return self._create('/%s' % self.keypair_prefix, body, 'keypair') @api_versions.wraps("2.10", "2.91") def create(self, name, public_key=None, key_type="ssh", user_id=None): """ Create a keypair :param name: name for the keypair to create :param public_key: existing public key to import :param key_type: keypair type to create :param user_id: user to add. """ body = {'keypair': {'name': name, 'type': key_type}} if public_key: body['keypair']['public_key'] = public_key if user_id: body['keypair']['user_id'] = user_id return self._create('/%s' % self.keypair_prefix, body, 'keypair') @api_versions.wraps("2.92") def create(self, name, public_key, key_type="ssh", user_id=None): """ Create a keypair :param name: name for the keypair to create :param public_key: existing public key to import :param key_type: keypair type to create :param user_id: user to add. """ body = {'keypair': {'name': name, 'type': key_type, 'public_key': public_key}} if user_id: body['keypair']['user_id'] = user_id return self._create('/%s' % self.keypair_prefix, body, 'keypair') @api_versions.wraps("2.0", "2.9") def delete(self, key): """ Delete a keypair :param key: The :class:`Keypair` (or its ID) to delete. :returns: An instance of novaclient.base.TupleWithMeta """ return self._delete('/%s/%s' % (self.keypair_prefix, base.getid(key))) @api_versions.wraps("2.10") def delete(self, key, user_id=None): """ Delete a keypair :param key: The :class:`Keypair` (or its ID) to delete. :param user_id: Id of key-pair owner (Admin only). :returns: An instance of novaclient.base.TupleWithMeta """ query_string = "?user_id=%s" % user_id if user_id else "" url = '/%s/%s%s' % (self.keypair_prefix, base.getid(key), query_string) return self._delete(url) @api_versions.wraps("2.0", "2.9") def list(self): """ Get a list of keypairs. """ return self._list('/%s' % self.keypair_prefix, 'keypairs') @api_versions.wraps("2.10", "2.34") def list(self, user_id=None): """ Get a list of keypairs. :param user_id: Id of key-pairs owner (Admin only). """ params = {} if user_id: params['user_id'] = user_id return self._list('/%s' % self.keypair_prefix, 'keypairs', filters=params) @api_versions.wraps("2.35") def list(self, user_id=None, marker=None, limit=None): """ Get a list of keypairs. :param user_id: Id of key-pairs owner (Admin only). :param marker: Begin returning keypairs that appear later in the keypair list than that represented by this keypair name (optional). :param limit: maximum number of keypairs to return (optional). Note the API server has a configurable default limit. If no limit is specified here or limit is larger than default, the default limit will be used. """ params = {} if user_id: params['user_id'] = user_id if limit: params['limit'] = int(limit) if marker: params['marker'] = str(marker) return self._list('/%s' % self.keypair_prefix, 'keypairs', filters=params) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/v2/limits.py0000664000175000017500000000564700000000000021673 0ustar00zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from novaclient import base class Limits(base.Resource): """A collection of RateLimit and AbsoluteLimit objects.""" def __repr__(self): return "" @property def absolute(self): for (name, value) in self._info['absolute'].items(): yield AbsoluteLimit(name, value) @property def rate(self): for group in self._info['rate']: uri = group['uri'] regex = group['regex'] for rate in group['limit']: yield RateLimit(rate['verb'], uri, regex, rate['value'], rate['remaining'], rate['unit'], rate['next-available']) class RateLimit(object): """Data model that represents a flattened view of a single rate limit.""" def __init__(self, verb, uri, regex, value, remain, unit, next_available): self.verb = verb self.uri = uri self.regex = regex self.value = value self.remain = remain self.unit = unit self.next_available = next_available def __eq__(self, other): return self.uri == other.uri \ and self.regex == other.regex \ and self.value == other.value \ and self.verb == other.verb \ and self.remain == other.remain \ and self.unit == other.unit \ and self.next_available == other.next_available def __repr__(self): return "" % (self.verb, self.uri) class AbsoluteLimit(object): """Data model that represents a single absolute limit.""" def __init__(self, name, value): self.name = name self.value = value def __eq__(self, other): return self.value == other.value and self.name == other.name def __repr__(self): return "" % (self.name) class LimitsManager(base.Manager): """Manager object used to interact with limits resource.""" resource_class = Limits def get(self, reserved=False, tenant_id=None): """ Get a specific extension. :rtype: :class:`Limits` """ opts = {} if reserved: opts['reserved'] = 1 if tenant_id: opts['tenant_id'] = tenant_id return self._get("/limits", "limits", filters=opts) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/v2/migrations.py0000664000175000017500000002101200000000000022526 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. """ migration interface """ from novaclient import api_versions from novaclient import base class Migration(base.Resource): def __repr__(self): return "" % self.id class MigrationManager(base.ManagerWithFind): resource_class = Migration def _list_base(self, host=None, status=None, instance_uuid=None, marker=None, limit=None, changes_since=None, changes_before=None, migration_type=None, source_compute=None, user_id=None, project_id=None): opts = {} if host: opts['host'] = host if status: opts['status'] = status if instance_uuid: opts['instance_uuid'] = instance_uuid if marker: opts['marker'] = marker if limit: opts['limit'] = limit if changes_since: opts['changes-since'] = changes_since if changes_before: opts['changes-before'] = changes_before if migration_type: opts['migration_type'] = migration_type if source_compute: opts['source_compute'] = source_compute if user_id: opts['user_id'] = user_id if project_id: opts['project_id'] = project_id return self._list("/os-migrations", "migrations", filters=opts) @api_versions.wraps("2.0", "2.58") def list(self, host=None, status=None, instance_uuid=None, migration_type=None, source_compute=None): """ Get a list of migrations. :param host: filter migrations by host name (optional). :param status: filter migrations by status (optional). :param instance_uuid: filter migrations by instance uuid (optional). :param migration_type: Filter migrations by type. Valid values are: evacuation, live-migration, migration (cold), resize :param source_compute: Filter migrations by source compute host name. """ return self._list_base(host=host, status=status, instance_uuid=instance_uuid, migration_type=migration_type, source_compute=source_compute) @api_versions.wraps("2.59", "2.65") def list(self, host=None, status=None, instance_uuid=None, marker=None, limit=None, changes_since=None, migration_type=None, source_compute=None): """ Get a list of migrations. :param host: filter migrations by host name (optional). :param status: filter migrations by status (optional). :param instance_uuid: filter migrations by instance uuid (optional). :param marker: Begin returning migrations that appear later in the migrations list than that represented by this migration UUID (optional). :param limit: maximum number of migrations to return (optional). Note the API server has a configurable default limit. If no limit is specified here or limit is larger than default, the default limit will be used. :param changes_since: only return migrations changed later or equal to a certain point of time. The provided time should be an ISO 8061 formatted time. e.g. 2016-03-04T06:27:59Z . (optional). :param migration_type: Filter migrations by type. Valid values are: evacuation, live-migration, migration (cold), resize :param source_compute: Filter migrations by source compute host name. """ return self._list_base(host=host, status=status, instance_uuid=instance_uuid, marker=marker, limit=limit, changes_since=changes_since, migration_type=migration_type, source_compute=source_compute) @api_versions.wraps("2.66", "2.79") def list(self, host=None, status=None, instance_uuid=None, marker=None, limit=None, changes_since=None, changes_before=None, migration_type=None, source_compute=None): """ Get a list of migrations. :param host: filter migrations by host name (optional). :param status: filter migrations by status (optional). :param instance_uuid: filter migrations by instance uuid (optional). :param marker: Begin returning migrations that appear later in the migrations list than that represented by this migration UUID (optional). :param limit: maximum number of migrations to return (optional). Note the API server has a configurable default limit. If no limit is specified here or limit is larger than default, the default limit will be used. :param changes_since: Only return migrations changed later or equal to a certain point of time. The provided time should be an ISO 8061 formatted time. e.g. 2016-03-04T06:27:59Z . (optional). :param changes_before: Only return migrations changed earlier or equal to a certain point of time. The provided time should be an ISO 8061 formatted time. e.g. 2016-03-05T06:27:59Z . (optional). :param migration_type: Filter migrations by type. Valid values are: evacuation, live-migration, migration (cold), resize :param source_compute: Filter migrations by source compute host name. """ return self._list_base(host=host, status=status, instance_uuid=instance_uuid, marker=marker, limit=limit, changes_since=changes_since, changes_before=changes_before, migration_type=migration_type, source_compute=source_compute) @api_versions.wraps("2.80") def list(self, host=None, status=None, instance_uuid=None, marker=None, limit=None, changes_since=None, changes_before=None, migration_type=None, source_compute=None, user_id=None, project_id=None): """ Get a list of migrations. :param host: filter migrations by host name (optional). :param status: filter migrations by status (optional). :param instance_uuid: filter migrations by instance uuid (optional). :param marker: Begin returning migrations that appear later in the migrations list than that represented by this migration UUID (optional). :param limit: maximum number of migrations to return (optional). Note the API server has a configurable default limit. If no limit is specified here or limit is larger than default, the default limit will be used. :param changes_since: Only return migrations changed later or equal to a certain point of time. The provided time should be an ISO 8061 formatted time. e.g. 2016-03-04T06:27:59Z . (optional). :param changes_before: Only return migrations changed earlier or equal to a certain point of time. The provided time should be an ISO 8061 formatted time. e.g. 2016-03-05T06:27:59Z . (optional). :param migration_type: Filter migrations by type. Valid values are: evacuation, live-migration, migration, resize :param source_compute: Filter migrations by source compute host name. :param user_id: filter migrations by user (optional). :param project_id: filter migrations by project (optional). """ return self._list_base(host=host, status=status, instance_uuid=instance_uuid, marker=marker, limit=limit, changes_since=changes_since, changes_before=changes_before, migration_type=migration_type, source_compute=source_compute, user_id=user_id, project_id=project_id) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/v2/networks.py0000664000175000017500000000421600000000000022235 0ustar00zuulzuul00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Network interface. """ from novaclient import base from novaclient import exceptions from novaclient.i18n import _ class Network(base.Resource): """ A network as defined in the Networking (Neutron) API. """ HUMAN_ID = True NAME_ATTR = "name" def __repr__(self): return "" % self.name class NeutronManager(base.Manager): """A manager for name -> id lookups for neutron networks. This uses neutron directly from service catalog. Do not use it for anything else besides that. You have been warned. """ resource_class = Network def find_network(self, name): """Find a network by name (user provided input).""" with self.alternate_service_type( 'network', allowed_types=('network',)): matches = self._list('/v2.0/networks?name=%s' % name, 'networks') num_matches = len(matches) if num_matches == 0: msg = "No %s matching %s." % ( self.resource_class.__name__, name) raise exceptions.NotFound(404, msg) elif num_matches > 1: msg = (_("Multiple %(class)s matches found for " "'%(name)s', use an ID to be more specific.") % {'class': self.resource_class.__name__.lower(), 'name': name}) raise exceptions.NoUniqueMatch(msg) else: matches[0].append_request_ids(matches.request_ids) return matches[0] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/v2/quota_classes.py0000664000175000017500000001070500000000000023227 0ustar00zuulzuul00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from novaclient import api_versions from novaclient import base class QuotaClassSet(base.Resource): def update(self, *args, **kwargs): return self.manager.update(self.id, *args, **kwargs) class QuotaClassSetManager(base.Manager): resource_class = QuotaClassSet def get(self, class_name): return self._get("/os-quota-class-sets/%s" % (class_name), "quota_class_set") def _update_body(self, **kwargs): return {'quota_class_set': kwargs} # NOTE(mriedem): Before 2.50 the resources you could update was just a # kwargs dict and not validated on the client-side, only on the API server # side. @api_versions.wraps("2.0", "2.49") def update(self, class_name, **kwargs): body = self._update_body(**kwargs) for key in list(body['quota_class_set']): if body['quota_class_set'][key] is None: body['quota_class_set'].pop(key) return self._update('/os-quota-class-sets/%s' % (class_name), body, 'quota_class_set') # NOTE(mriedem): 2.50 does strict validation of the resources you can # specify since the network-related resources are blocked in 2.50. @api_versions.wraps("2.50", "2.56") def update(self, class_name, instances=None, cores=None, ram=None, metadata_items=None, injected_files=None, injected_file_content_bytes=None, injected_file_path_bytes=None, key_pairs=None, server_groups=None, server_group_members=None): resources = {} if instances is not None: resources['instances'] = instances if cores is not None: resources['cores'] = cores if ram is not None: resources['ram'] = ram if metadata_items is not None: resources['metadata_items'] = metadata_items if injected_files is not None: resources['injected_files'] = injected_files if injected_file_content_bytes is not None: resources['injected_file_content_bytes'] = ( injected_file_content_bytes) if injected_file_path_bytes is not None: resources['injected_file_path_bytes'] = injected_file_path_bytes if key_pairs is not None: resources['key_pairs'] = key_pairs if server_groups is not None: resources['server_groups'] = server_groups if server_group_members is not None: resources['server_group_members'] = server_group_members body = {'quota_class_set': resources} return self._update('/os-quota-class-sets/%s' % class_name, body, 'quota_class_set') # NOTE(mriedem): 2.57 deprecates the usage of injected_files, # injected_file_content_bytes and injected_file_path_bytes so those # kwargs are removed. @api_versions.wraps("2.57") def update(self, class_name, instances=None, cores=None, ram=None, metadata_items=None, key_pairs=None, server_groups=None, server_group_members=None): resources = {} if instances is not None: resources['instances'] = instances if cores is not None: resources['cores'] = cores if ram is not None: resources['ram'] = ram if metadata_items is not None: resources['metadata_items'] = metadata_items if key_pairs is not None: resources['key_pairs'] = key_pairs if server_groups is not None: resources['server_groups'] = server_groups if server_group_members is not None: resources['server_group_members'] = server_group_members body = {'quota_class_set': resources} return self._update('/os-quota-class-sets/%s' % class_name, body, 'quota_class_set') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/v2/quotas.py0000664000175000017500000000762400000000000021703 0ustar00zuulzuul00000000000000# Copyright 2011 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from novaclient import api_versions from novaclient import base class QuotaSet(base.Resource): def update(self, *args, **kwargs): return self.manager.update(self.id, *args, **kwargs) class QuotaSetManager(base.Manager): resource_class = QuotaSet def get(self, tenant_id, user_id=None, detail=False): url = '/os-quota-sets/%(tenant_id)s' if detail: url += '/detail' if user_id: params = {'tenant_id': tenant_id, 'user_id': user_id} url += '?user_id=%(user_id)s' else: params = {'tenant_id': tenant_id} return self._get(url % params, "quota_set") # NOTE(mriedem): Before 2.57 the resources you could update was just a # kwargs dict and not validated on the client-side, only on the API server # side. @api_versions.wraps("2.0", "2.56") def update(self, tenant_id, **kwargs): user_id = kwargs.pop('user_id', None) body = {'quota_set': kwargs} for key in list(body['quota_set']): if body['quota_set'][key] is None: body['quota_set'].pop(key) if user_id: url = '/os-quota-sets/%s?user_id=%s' % (tenant_id, user_id) else: url = '/os-quota-sets/%s' % tenant_id return self._update(url, body, 'quota_set') # NOTE(mriedem): 2.57 does strict validation of the resources you can # specify. 2.36 blocks network-related resources and 2.57 blocks # injected files related quotas. @api_versions.wraps("2.57") def update(self, tenant_id, user_id=None, force=False, instances=None, cores=None, ram=None, metadata_items=None, key_pairs=None, server_groups=None, server_group_members=None): resources = {} if force: resources['force'] = force if instances is not None: resources['instances'] = instances if cores is not None: resources['cores'] = cores if ram is not None: resources['ram'] = ram if metadata_items is not None: resources['metadata_items'] = metadata_items if key_pairs is not None: resources['key_pairs'] = key_pairs if server_groups is not None: resources['server_groups'] = server_groups if server_group_members is not None: resources['server_group_members'] = server_group_members body = {'quota_set': resources} if user_id: url = '/os-quota-sets/%s?user_id=%s' % (tenant_id, user_id) else: url = '/os-quota-sets/%s' % tenant_id return self._update(url, body, 'quota_set') def defaults(self, tenant_id): return self._get('/os-quota-sets/%s/defaults' % tenant_id, 'quota_set') def delete(self, tenant_id, user_id=None): """ Delete quota for a tenant or for a user. :param tenant_id: A tenant for which quota is to be deleted :param user_id: A user for which quota is to be deleted :returns: An instance of novaclient.base.TupleWithMeta """ if user_id: url = '/os-quota-sets/%s?user_id=%s' % (tenant_id, user_id) else: url = '/os-quota-sets/%s' % tenant_id return self._delete(url) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/v2/server_external_events.py0000664000175000017500000000237000000000000025154 0ustar00zuulzuul00000000000000# Copyright (C) 2014, 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. """ External event triggering for servers, not to be used by users. """ from novaclient import base class Event(base.Resource): def __repr__(self): return "" % self.name class ServerExternalEventManager(base.Manager): resource_class = Event def create(self, events): """Create one or more server events. :param:events: A list of dictionaries containing 'server_uuid', 'name', 'status', and 'tag' (which may be absent) """ body = {'events': events} return self._create('/os-server-external-events', body, 'events', return_raw=True) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/v2/server_groups.py0000664000175000017500000001067400000000000023273 0ustar00zuulzuul00000000000000# Copyright (c) 2014 VMware, 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. """ Server group interface. """ from novaclient import api_versions from novaclient import base from novaclient import exceptions from novaclient.i18n import _ class ServerGroup(base.Resource): """ A server group. """ def __repr__(self): return '' % self.id def delete(self): """ Delete this server group. :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.delete(self.id) class ServerGroupsManager(base.ManagerWithFind): """ Manage :class:`ServerGroup` resources. """ resource_class = ServerGroup def list(self, all_projects=False, limit=None, offset=None): """Get a list of all server groups. :param all_projects: Lists server groups for all projects. (optional) :param limit: Maximum number of server groups to return. (optional) Note the API server has a configurable default limit. If no limit is specified here or limit is larger than default, the default limit will be used. :param offset: Use with `limit` to return a slice of server groups. `offset` is where to start in the groups list. (optional) :returns: list of :class:`ServerGroup`. """ qparams = {} if all_projects: qparams['all_projects'] = bool(all_projects) if limit: qparams['limit'] = int(limit) if offset: qparams['offset'] = int(offset) return self._list('/os-server-groups', 'server_groups', filters=qparams) def get(self, id): """Get a specific server group. :param id: The ID of the :class:`ServerGroup` to get. :rtype: :class:`ServerGroup` """ return self._get('/os-server-groups/%s' % id, 'server_group') def delete(self, id): """Delete a specific server group. :param id: The ID of the :class:`ServerGroup` to delete. :returns: An instance of novaclient.base.TupleWithMeta """ return self._delete('/os-server-groups/%s' % id) @api_versions.wraps("2.0", "2.63") def create(self, name, policies): """Create (allocate) a server group. :param name: The name of the server group. :param policies: Policy name or a list of exactly one policy name to associate with the server group. :rtype: list of :class:`ServerGroup` """ policies = policies if isinstance(policies, list) else [policies] body = {'server_group': {'name': name, 'policies': policies}} return self._create('/os-server-groups', body, 'server_group') @api_versions.wraps("2.64") def create(self, name, policy, rules=None): """Create (allocate) a server group. :param name: The name of the server group. :param policy: Policy name to associate with the server group. :param rules: The rules of policy which is a dict, can be applied to the policy, now only ``max_server_per_host`` for ``anti-affinity`` policy would be supported (optional). :rtype: list of :class:`ServerGroup` """ body = {'server_group': { 'name': name, 'policy': policy }} if rules: key = 'max_server_per_host' try: if key in rules: rules[key] = int(rules[key]) except ValueError: msg = _("Invalid '%(key)s' value: %(value)s") raise exceptions.CommandError(msg % { 'key': key, 'value': rules[key]}) body['server_group']['rules'] = rules return self._create('/os-server-groups', body, 'server_group') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/v2/server_migrations.py0000664000175000017500000000555400000000000024131 0ustar00zuulzuul00000000000000# Copyright 2016 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from novaclient import api_versions from novaclient import base class ServerMigration(base.Resource): def __repr__(self): return "" class ServerMigrationsManager(base.ManagerWithFind): resource_class = ServerMigration @api_versions.wraps("2.22") def live_migrate_force_complete(self, server, migration): """ Force on-going live migration to complete :param server: The :class:`Server` (or its ID) :param migration: Migration id that will be forced to complete :returns: An instance of novaclient.base.TupleWithMeta """ body = {'force_complete': None} resp, body = self.api.client.post( '/servers/%s/migrations/%s/action' % (base.getid(server), base.getid(migration)), body=body) return self.convert_into_with_meta(body, resp) @api_versions.wraps("2.23") def get(self, server, migration): """ Get a migration of a specified server :param server: The :class:`Server` (or its ID) :param migration: Migration id that will be gotten. :returns: An instance of novaclient.v2.server_migrations.ServerMigration """ return self._get('/servers/%s/migrations/%s' % (base.getid(server), base.getid(migration)), 'migration') @api_versions.wraps("2.23") def list(self, server): """ Get a migrations list of a specified server :param server: The :class:`Server` (or its ID) :returns: An instance of novaclient.base.ListWithMeta """ return self._list( '/servers/%s/migrations' % (base.getid(server)), "migrations") @api_versions.wraps("2.24") def live_migration_abort(self, server, migration): """ Cancel an ongoing live migration :param server: The :class:`Server` (or its ID) :param migration: Migration id that will be cancelled :returns: An instance of novaclient.base.TupleWithMeta """ return self._delete( '/servers/%s/migrations/%s' % (base.getid(server), base.getid(migration))) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/v2/servers.py0000664000175000017500000026200100000000000022050 0ustar00zuulzuul00000000000000# Copyright 2010 Jacob Kaplan-Moss # Copyright 2011 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Server interface. """ import base64 import collections from urllib import parse from novaclient import api_versions from novaclient import base from novaclient import crypto from novaclient import exceptions from novaclient.i18n import _ _SENTINEL = object() REBOOT_SOFT, REBOOT_HARD = 'SOFT', 'HARD' CONSOLE_TYPE_ACTION_MAPPING = { 'novnc': 'os-getVNCConsole', 'xvpvnc': 'os-getVNCConsole', 'spice-html5': 'os-getSPICEConsole', 'rdp-html5': 'os-getRDPConsole', 'serial': 'os-getSerialConsole' } CONSOLE_TYPE_PROTOCOL_MAPPING = { 'novnc': 'vnc', 'xvpvnc': 'vnc', 'spice-html5': 'spice', 'rdp-html5': 'rdp', 'serial': 'serial', 'webmks': 'mks' } class Server(base.Resource): HUMAN_ID = True def __repr__(self): return '' % getattr(self, 'name', 'unknown-name') def delete(self): """ Delete (i.e. shut down and delete the image) this server. :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.delete(self) @api_versions.wraps("2.0", "2.18") def update(self, name=None): """ Update attributes of this server. :param name: Update the server's name. :returns: :class:`Server` """ return self.manager.update(self, name=name) @api_versions.wraps("2.19", "2.89") def update(self, name=None, description=None): """ Update attributes of this server. :param name: Update the server's name. :param description: Update the server's description. :returns: :class:`Server` """ update_kwargs = {"name": name} if description is not None: update_kwargs["description"] = description return self.manager.update(self, **update_kwargs) @api_versions.wraps("2.90") def update(self, name=None, description=None, hostname=None): """ Update attributes of this server. :param name: Update the server's name. :param description: Update the server's description. :param hostname: Update the server's hostname. :returns: :class:`Server` """ update_kwargs = {"name": name} if description is not None: update_kwargs["description"] = description if hostname is not None: update_kwargs["hostname"] = hostname return self.manager.update(self, **update_kwargs) def get_console_output(self, length=None): """ Get text console log output from Server. :param length: The number of lines you would like to retrieve (as int) """ return self.manager.get_console_output(self, length) def get_vnc_console(self, console_type): """ Get vnc console for a Server. :param console_type: Type of console ('novnc' or 'xvpvnc') """ return self.manager.get_vnc_console(self, console_type) def get_spice_console(self, console_type): """ Get spice console for a Server. :param console_type: Type of console ('spice-html5') """ return self.manager.get_spice_console(self, console_type) def get_rdp_console(self, console_type): """ Get rdp console for a Server. :param console_type: Type of console ('rdp-html5') """ return self.manager.get_rdp_console(self, console_type) def get_serial_console(self, console_type): """ Get serial console for a Server. :param console_type: Type of console ('serial') """ return self.manager.get_serial_console(self, console_type) def get_mks_console(self): """ Get mks console for a Server. """ return self.manager.get_mks_console(self) def get_console_url(self, console_type): """ Retrieve a console of a particular protocol and console_type :param console_type: Type of console """ return self.manager.get_console_url(self, console_type) def get_password(self, private_key=None): """ Get password for a Server. Returns the clear password of an instance if private_key is provided, returns the ciphered password otherwise. :param private_key: Path to private key file for decryption (optional) """ return self.manager.get_password(self, private_key) def clear_password(self): """ Get password for a Server. """ return self.manager.clear_password(self) def stop(self): """ Stop -- Stop the running server. :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.stop(self) def force_delete(self): """ Force delete -- Force delete a server. :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.force_delete(self) def restore(self): """ Restore -- Restore a server in 'soft-deleted' state. :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.restore(self) def start(self): """ Start -- Start the paused server. :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.start(self) def pause(self): """ Pause -- Pause the running server. :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.pause(self) def unpause(self): """ Unpause -- Unpause the paused server. :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.unpause(self) @api_versions.wraps("2.0", "2.72") def lock(self): """ Lock -- Lock the instance from certain operations. :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.lock(self) @api_versions.wraps("2.73") def lock(self, reason=None): """ Lock -- Lock the instance from certain operations. :param reason: (Optional) The lock reason. :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.lock(self, reason=reason) def unlock(self): """ Unlock -- Remove instance lock. :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.unlock(self) def suspend(self): """ Suspend -- Suspend the running server. :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.suspend(self) def resume(self): """ Resume -- Resume the suspended server. :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.resume(self) def rescue(self, password=None, image=None): """ Rescue -- Rescue the problematic server. :param password: The admin password to be set in the rescue instance. :param image: The :class:`Image` to rescue with. :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.rescue(self, password, image) def unrescue(self): """ Unrescue -- Unrescue the rescued server. :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.unrescue(self) def shelve(self): """ Shelve -- Shelve the server. :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.shelve(self) def shelve_offload(self): """ Shelve_offload -- Remove a shelved server from the compute node. :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.shelve_offload(self) @api_versions.wraps("2.0", "2.76") def unshelve(self): """ Unshelve -- Unshelve the server. :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.unshelve(self) @api_versions.wraps("2.77", "2.90") def unshelve(self, availability_zone=None): """ Unshelve -- Unshelve the server. :param availability_zone: The specified availability zone name (Optional) :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.unshelve(self, availability_zone=availability_zone) @api_versions.wraps("2.91") def unshelve(self, availability_zone=object(), host=None): """ Unshelve -- Unshelve the server. :param availability_zone: If specified the instance will be unshelved to the availability_zone. If None is passed the instance defined availability_zone is unpin and the instance will be scheduled to any availability_zone (free scheduling). If not specified the instance will be unshelved to either its defined availability_zone or any availability_zone (free scheduling). :param host: The specified host (Optional) :returns: An instance of novaclient.base.TupleWithMeta """ if ( availability_zone is None or isinstance(availability_zone, str) ) and host: return self.manager.unshelve( self, availability_zone=availability_zone, host=host) if availability_zone is None or isinstance(availability_zone, str): return self.manager.unshelve( self, availability_zone=availability_zone) if host: return self.manager.unshelve(self, host=host) return self.manager.unshelve(self) def diagnostics(self): """Diagnostics -- Retrieve server diagnostics.""" return self.manager.diagnostics(self) @api_versions.wraps("2.78") def topology(self): """Retrieve server topology.""" return self.manager.topology(self) @api_versions.wraps("2.0", "2.55") def migrate(self): """ Migrate a server to a new host. :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.migrate(self) @api_versions.wraps("2.56") def migrate(self, host=None): """ Migrate a server to a new host. :param host: (Optional) The target host. :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.migrate(self, host=host) def change_password(self, password): """ Update the admin password for a server. :param password: string to set as the admin password on the server :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.change_password(self, password) def reboot(self, reboot_type=REBOOT_SOFT): """ Reboot the server. :param reboot_type: either :data:`REBOOT_SOFT` for a software-level reboot, or `REBOOT_HARD` for a virtual power cycle hard reboot. :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.reboot(self, reboot_type) # NOTE(stephenfin): It would be nice to make everything bar image a # kwarg-only argument but there are backwards-compatbility concerns def rebuild( self, image, password=None, preserve_ephemeral=False, *, disk_config=None, name=None, meta=None, files=None, description=_SENTINEL, key_name=_SENTINEL, userdata=_SENTINEL, trusted_image_certificates=_SENTINEL, hostname=_SENTINEL, ): """ Rebuild -- shut down and then re-image -- this server. :param image: The :class:`Image` (or its ID) to re-image with. :param password: String to set as password on the rebuilt server. :param preserve_ephemeral: If True, request that any ephemeral device be preserved when rebuilding the instance. Defaults to False. :param disk_config: Partitioning mode to use on the rebuilt server. Valid values are 'AUTO' or 'MANUAL' :param name: Something to name the server. :param meta: A dict of arbitrary key/value metadata to store for this server. Both keys and values must be <=255 characters. :param files: A dict of files to overwrite on the server upon boot. Keys are file names (i.e. ``/etc/passwd``) and values are the file contents (either as a string or as a file-like object). A maximum of five entries is allowed, and each file must be 10k or less. (deprecated starting with microversion 2.57) :param description: Optional description of the server. If None is specified, the existing description will be unset. (starting from microversion 2.19) :param key_name: Optional key pair name for rebuild operation. If None is specified, the existing key will be unset. (starting from microversion 2.54) :param userdata: Optional user data to pass to be exposed by the metadata server; this can be a file type object as well or a string. If None is specified, the existing user_data is unset. (starting from microversion 2.57) :param trusted_image_certificates: A list of trusted certificate IDs or None to unset/reset the servers trusted image certificates (starting from microversion 2.63) :param hostname: Optional hostname to configure for the instance. If None is specified, the existing hostname will be unset. (starting from microversion 2.90) :returns: :class:`Server` """ return self.manager.rebuild( self, image, password=password, disk_config=disk_config, preserve_ephemeral=preserve_ephemeral, name=name, meta=meta, files=files, description=description, key_name=key_name, userdata=userdata, trusted_image_certificates=trusted_image_certificates, hostname=hostname, ) def resize(self, flavor, *, disk_config=None): """ Resize the server's resources. :param flavor: The :class:`Flavor` (or its ID) to resize to. :param disk_config: Partitioning mode to use on the rebuilt server. Valid values are 'AUTO' or 'MANUAL'. :returns: An instance of novaclient.base.TupleWithMeta Until a resize event is confirmed with :meth:`confirm_resize`, the old server will be kept around and you'll be able to roll back to the old flavor quickly with :meth:`revert_resize`. All resizes are automatically confirmed after 24 hours by default. """ return self.manager.resize(self, flavor, disk_config=disk_config) def create_image(self, image_name, metadata=None): """ Create an image based on this server. :param image_name: The name to assign the newly create image. :param metadata: Metadata to assign to the image. """ return self.manager.create_image(self, image_name, metadata) def backup(self, backup_name, backup_type, rotation): """ Backup a server instance. :param backup_name: Name of the backup image :param backup_type: The backup type, like 'daily' or 'weekly' :param rotation: Int parameter representing how many backups to keep around. :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.backup(self, backup_name, backup_type, rotation) def confirm_resize(self): """ Confirm that the resize worked, thus removing the original server. :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.confirm_resize(self) def revert_resize(self): """ Revert a previous resize, switching back to the old server. :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.revert_resize(self) @property def networks(self): """ Generate a simplified list of addresses :returns: An OrderedDict, keyed by network name, and sorted by network name in ascending order. """ networks = collections.OrderedDict() try: # Sort the keys by network name in natural (ascending) order. network_labels = sorted(self.addresses.keys()) for network_label in network_labels: address_list = self.addresses[network_label] networks[network_label] = [a['addr'] for a in address_list] return networks except AttributeError: return {} @api_versions.wraps("2.0", "2.24") def live_migrate(self, host=None, block_migration=False, disk_over_commit=None): """ Migrates a running instance to a new machine. :param host: destination host name. :param block_migration: if True, do block_migration, the default value is False and None will be mapped to False :param disk_over_commit: if True, allow disk over commit, the default value is None which is mapped to False :returns: An instance of novaclient.base.TupleWithMeta """ if block_migration is None: block_migration = False if disk_over_commit is None: disk_over_commit = False return self.manager.live_migrate(self, host, block_migration, disk_over_commit) @api_versions.wraps("2.25", "2.29") def live_migrate(self, host=None, block_migration=None): """ Migrates a running instance to a new machine. :param host: destination host name. :param block_migration: if True, do block_migration, the default value is None which is mapped to 'auto'. :returns: An instance of novaclient.base.TupleWithMeta """ if block_migration is None: block_migration = "auto" return self.manager.live_migrate(self, host, block_migration) @api_versions.wraps("2.30", "2.67") def live_migrate(self, host=None, block_migration=None, force=None): """ Migrates a running instance to a new machine. :param host: destination host name. :param block_migration: if True, do block_migration, the default value is None which is mapped to 'auto'. :param force: force to bypass the scheduler if host is provided. :returns: An instance of novaclient.base.TupleWithMeta """ if block_migration is None: block_migration = "auto" return self.manager.live_migrate(self, host, block_migration, force) @api_versions.wraps("2.68") def live_migrate(self, host=None, block_migration=None): """ Migrates a running instance to a new machine. :param host: destination host name. :param block_migration: if True, do block_migration, the default value is None which is mapped to 'auto'. :returns: An instance of novaclient.base.TupleWithMeta """ if block_migration is None: block_migration = "auto" return self.manager.live_migrate(self, host, block_migration) def reset_state(self, state='error'): """ Reset the state of an instance to active or error. :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.reset_state(self, state) def reset_network(self): """ Reset network of an instance. :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.reset_network(self) def add_security_group(self, security_group): """ Add a security group to an instance. :param security_group: The name of security group to add :returns: An instance of novaclient.base.DictWithMeta """ return self.manager.add_security_group(self, security_group) def remove_security_group(self, security_group): """ Remove a security group from an instance. :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.remove_security_group(self, security_group) def list_security_group(self): """ List security group(s) of an instance. """ return self.manager.list_security_group(self) @api_versions.wraps("2.0", "2.13") def evacuate(self, host=None, on_shared_storage=True, password=None): """ Evacuate an instance from failed host to specified host. :param host: Name of the target host :param on_shared_storage: Specifies whether instance files located on shared storage. :param password: string to set as admin password on the evacuated server. :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.evacuate(self, host, on_shared_storage, password) @api_versions.wraps("2.14", "2.28") def evacuate(self, host=None, password=None): """ Evacuate an instance from failed host to specified host. :param host: Name of the target host :param password: string to set as admin password on the evacuated server. :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.evacuate(self, host, password) @api_versions.wraps("2.29", "2.67") def evacuate(self, host=None, password=None, force=None): """ Evacuate an instance from failed host to specified host. :param host: Name of the target host :param password: string to set as admin password on the evacuated server. :param force: forces to bypass the scheduler if host is provided. :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.evacuate(self, host, password, force) @api_versions.wraps("2.68") def evacuate(self, host=None, password=None): """ Evacuate an instance from failed host to specified host. :param host: Name of the target host :param password: string to set as admin password on the evacuated server. :returns: An instance of novaclient.base.TupleWithMeta """ return self.manager.evacuate(self, host, password) def interface_list(self): """ List interfaces attached to an instance. """ return self.manager.interface_list(self) @api_versions.wraps("2.0", "2.48") def interface_attach(self, port_id, net_id, fixed_ip): """ Attach a network interface to an instance. """ return self.manager.interface_attach(self, port_id, net_id, fixed_ip) @api_versions.wraps("2.49") def interface_attach(self, port_id, net_id, fixed_ip, tag=None): """ Attach a network interface to an instance with an optional tag. """ return self.manager.interface_attach(self, port_id, net_id, fixed_ip, tag) def interface_detach(self, port_id): """ Detach a network interface from an instance. """ return self.manager.interface_detach(self, port_id) def trigger_crash_dump(self): """Trigger crash dump in an instance""" return self.manager.trigger_crash_dump(self) @api_versions.wraps('2.26') def tag_list(self): """ Get list of tags from an instance. """ return self.manager.tag_list(self) @api_versions.wraps('2.26') def delete_tag(self, tag): """ Remove single tag from an instance. """ return self.manager.delete_tag(self, tag) @api_versions.wraps('2.26') def delete_all_tags(self): """ Remove all tags from an instance. """ return self.manager.delete_all_tags(self) @api_versions.wraps('2.26') def set_tags(self, tags): """ Set list of tags to an instance. """ return self.manager.set_tags(self, tags) @api_versions.wraps('2.26') def add_tag(self, tag): """ Add single tag to an instance. """ return self.manager.add_tag(self, tag) class NetworkInterface(base.Resource): @property def id(self): return self.port_id def __repr__(self): return '' % self.id class SecurityGroup(base.Resource): def __str__(self): return str(self.id) class ServerManager(base.BootingManagerWithFind): resource_class = Server @staticmethod def transform_userdata(userdata): if hasattr(userdata, 'read'): userdata = userdata.read() # NOTE(melwitt): Text file data is converted to bytes prior to # base64 encoding. The utf-8 encoding will fail for binary files. try: userdata = userdata.encode("utf-8") except AttributeError: # In python 3, 'bytes' object has no attribute 'encode' pass return base64.b64encode(userdata).decode('utf-8') def _boot( self, response_key, name, image, flavor, meta=None, files=None, userdata=None, reservation_id=False, return_raw=False, min_count=None, max_count=None, security_groups=None, key_name=None, availability_zone=None, block_device_mapping=None, block_device_mapping_v2=None, nics=None, scheduler_hints=None, config_drive=None, admin_pass=None, disk_config=None, access_ip_v4=None, access_ip_v6=None, description=None, tags=None, trusted_image_certificates=None, host=None, hypervisor_hostname=None, hostname=None, ): """ Create (boot) a new server. """ body = {"server": { "name": name, "imageRef": str(base.getid(image)) if image else '', "flavorRef": str(base.getid(flavor)), }} if userdata: body["server"]["user_data"] = self.transform_userdata(userdata) if meta: body["server"]["metadata"] = meta if reservation_id: body["server"]["return_reservation_id"] = reservation_id return_raw = True if key_name: body["server"]["key_name"] = key_name if scheduler_hints: body['os:scheduler_hints'] = scheduler_hints if config_drive: body["server"]["config_drive"] = config_drive if admin_pass: body["server"]["adminPass"] = admin_pass if not min_count: min_count = 1 if not max_count: max_count = min_count body["server"]["min_count"] = min_count body["server"]["max_count"] = max_count if security_groups: body["server"]["security_groups"] = [{'name': sg} for sg in security_groups] # Files are a slight bit tricky. They're passed in a "personality" # list to the POST. Each item is a dict giving a file name and the # base64-encoded contents of the file. We want to allow passing # either an open file *or* some contents as files here. if files: personality = body['server']['personality'] = [] for filepath, file_or_string in sorted(files.items(), key=lambda x: x[0]): if hasattr(file_or_string, 'read'): data = file_or_string.read() else: data = file_or_string if isinstance(data, str): data = data.encode('utf-8') cont = base64.b64encode(data).decode('utf-8') personality.append({ 'path': filepath, 'contents': cont, }) if availability_zone: body["server"]["availability_zone"] = availability_zone # Block device mappings are passed as a list of dictionaries # in the create API if block_device_mapping: body['server']['block_device_mapping'] = \ self._parse_block_device_mapping(block_device_mapping) elif block_device_mapping_v2: # Following logic can't be removed because it will leaves # a valid boot with both --image and --block-device # failed , see bug 1433609 for more info if image: bdm_dict = {'uuid': base.getid(image), 'source_type': 'image', 'destination_type': 'local', 'boot_index': 0, 'delete_on_termination': True} block_device_mapping_v2.insert(0, bdm_dict) body['server']['block_device_mapping_v2'] = block_device_mapping_v2 if nics is not None: # With microversion 2.37+ nics can be an enum of 'auto' or 'none' # or a list of dicts. if isinstance(nics, str): all_net_data = nics else: # NOTE(tr3buchet): nics can be an empty list all_net_data = [] for nic_info in nics: net_data = {} # if value is empty string, do not send value in body if nic_info.get('net-id'): net_data['uuid'] = nic_info['net-id'] if (nic_info.get('v4-fixed-ip') and nic_info.get('v6-fixed-ip')): raise base.exceptions.CommandError(_( "Only one of 'v4-fixed-ip' and 'v6-fixed-ip' " "may be provided.")) elif nic_info.get('v4-fixed-ip'): net_data['fixed_ip'] = nic_info['v4-fixed-ip'] elif nic_info.get('v6-fixed-ip'): net_data['fixed_ip'] = nic_info['v6-fixed-ip'] if nic_info.get('port-id'): net_data['port'] = nic_info['port-id'] if nic_info.get('tag'): net_data['tag'] = nic_info['tag'] all_net_data.append(net_data) body['server']['networks'] = all_net_data if disk_config is not None: body['server']['OS-DCF:diskConfig'] = disk_config if access_ip_v4 is not None: body['server']['accessIPv4'] = access_ip_v4 if access_ip_v6 is not None: body['server']['accessIPv6'] = access_ip_v6 if description: body['server']['description'] = description if tags: body['server']['tags'] = tags if trusted_image_certificates: body['server']['trusted_image_certificates'] = ( trusted_image_certificates) if host: body['server']['host'] = host if hypervisor_hostname: body['server']['hypervisor_hostname'] = hypervisor_hostname if hostname: body['server']['hostname'] = hostname return self._create( '/servers', body, response_key, return_raw=return_raw, ) def get(self, server): """ Get a server. :param server: ID of the :class:`Server` to get. :rtype: :class:`Server` """ return self._get("/servers/%s" % base.getid(server), "server") def list(self, detailed=True, search_opts=None, marker=None, limit=None, sort_keys=None, sort_dirs=None): """ Get a list of servers. :param detailed: Whether to return detailed server info (optional). :param search_opts: Search options to filter out servers which don't match the search_opts (optional). The search opts format is a dictionary of key / value pairs that will be appended to the query string. For a complete list of keys see: https://docs.openstack.org/api-ref/compute/#list-servers :param marker: Begin returning servers that appear later in the server list than that represented by this server id (optional). :param limit: Maximum number of servers to return (optional). Note the API server has a configurable default limit. If no limit is specified here or limit is larger than default, the default limit will be used. If limit == -1, all servers will be returned. :param sort_keys: List of sort keys :param sort_dirs: List of sort directions :rtype: list of :class:`Server` Examples: client.servers.list() - returns detailed list of servers client.servers.list(search_opts={'status': 'ERROR'}) - returns list of servers in error state. client.servers.list(limit=10) - returns only 10 servers """ if search_opts is None: search_opts = {} qparams = {} # In microversion 2.73 we added ``locked`` filtering option # for listing server details. if ('locked' in search_opts and self.api_version < api_versions.APIVersion('2.73')): raise exceptions.UnsupportedAttribute("locked", "2.73") for opt, val in search_opts.items(): # support locked=False from 2.73 microversion if val or (opt == 'locked' and val is False): if isinstance(val, str): val = val.encode('utf-8') qparams[opt] = val # NOTE(gibi): The False value won't actually do anything until we # fix bug 1871409 and clean up the API inconsistency, but we do it # in preparation for that (hopefully backportable) fix if opt == 'config_drive' and val is not None: qparams[opt] = str(val) detail = "" if detailed: detail = "/detail" result = base.ListWithMeta([], None) while True: if marker: qparams['marker'] = marker if limit and limit != -1: qparams['limit'] = limit # Transform the dict to a sequence of two-element tuples in fixed # order, then the encoded string will be consistent in Python 2&3. if qparams or sort_keys or sort_dirs: # sort keys and directions are unique since the same parameter # key is repeated for each associated value # (ie, &sort_key=key1&sort_key=key2&sort_key=key3) items = list(qparams.items()) if sort_keys: items.extend(('sort_key', sort_key) for sort_key in sort_keys) if sort_dirs: items.extend(('sort_dir', sort_dir) for sort_dir in sort_dirs) new_qparams = sorted(items, key=lambda x: x[0]) query_string = "?%s" % parse.urlencode(new_qparams) else: query_string = "" servers = self._list("/servers%s%s" % (detail, query_string), "servers") result.extend(servers) result.append_request_ids(servers.request_ids) if not servers or limit != -1: break marker = result[-1].id return result def get_vnc_console(self, server, console_type): """ Get a vnc console for an instance :param server: The :class:`Server` (or its ID) to get console for. :param console_type: Type of vnc console to get ('novnc' or 'xvpvnc') :returns: An instance of novaclient.base.DictWithMeta """ return self.get_console_url(server, console_type) def get_spice_console(self, server, console_type): """ Get a spice console for an instance :param server: The :class:`Server` (or its ID) to get console for. :param console_type: Type of spice console to get ('spice-html5') :returns: An instance of novaclient.base.DictWithMeta """ return self.get_console_url(server, console_type) def get_rdp_console(self, server, console_type): """ Get a rdp console for an instance :param server: The :class:`Server` (or its ID) to get console for. :param console_type: Type of rdp console to get ('rdp-html5') :returns: An instance of novaclient.base.DictWithMeta """ return self.get_console_url(server, console_type) def get_serial_console(self, server, console_type): """ Get a serial console for an instance :param server: The :class:`Server` (or its ID) to get console for. :param console_type: Type of serial console to get ('serial') :returns: An instance of novaclient.base.DictWithMeta """ return self.get_console_url(server, console_type) def _get_protocol(self, console_type): protocol = CONSOLE_TYPE_PROTOCOL_MAPPING.get(console_type) if not protocol: raise exceptions.UnsupportedConsoleType(console_type) return protocol @api_versions.wraps('2.0', '2.5') def get_console_url(self, server, console_type): """ Retrieve a console url of a server. :param server: server to get console url for :param console_type: type can be novnc, xvpvnc, spice-html5, rdp-html5 and serial. """ action = CONSOLE_TYPE_ACTION_MAPPING.get(console_type) if not action: raise exceptions.UnsupportedConsoleType(console_type) return self._action(action, server, {'type': console_type}) @api_versions.wraps('2.8') def get_mks_console(self, server): """ Get a mks console for an instance :param server: The :class:`Server` (or its ID) to get console for. :returns: An instance of novaclient.base.DictWithMeta """ return self.get_console_url(server, 'webmks') @api_versions.wraps('2.6') def get_console_url(self, server, console_type): """ Retrieve a console url of a server. :param server: server to get console url for :param console_type: type can be novnc/xvpvnc for protocol vnc; spice-html5 for protocol spice; rdp-html5 for protocol rdp; serial for protocol serial. webmks for protocol mks (since version 2.8). """ if self.api_version < api_versions.APIVersion('2.8'): if console_type == 'webmks': raise exceptions.UnsupportedConsoleType(console_type) protocol = self._get_protocol(console_type) body = {'remote_console': {'protocol': protocol, 'type': console_type}} url = '/servers/%s/remote-consoles' % base.getid(server) resp, body = self.api.client.post(url, body=body) return self.convert_into_with_meta(body, resp) def get_password(self, server, private_key=None): """ Get admin password of an instance Returns the admin password of an instance in the clear if private_key is provided, returns the ciphered password otherwise. Requires that openssl is installed and in the path :param server: The :class:`Server` (or its ID) for which the admin password is to be returned :param private_key: The private key to decrypt password (optional) :returns: An instance of novaclient.base.StrWithMeta or novaclient.base.BytesWithMeta or novaclient.base.UnicodeWithMeta """ resp, body = self.api.client.get("/servers/%s/os-server-password" % base.getid(server)) ciphered_pw = body.get('password', '') if body else '' if private_key and ciphered_pw: try: ciphered_pw = crypto.decrypt_password(private_key, ciphered_pw) except Exception as exc: ciphered_pw = '%sFailed to decrypt:\n%s' % (exc, ciphered_pw) return self.convert_into_with_meta(ciphered_pw, resp) def clear_password(self, server): """ Clear the admin password of an instance Remove the admin password for an instance from the metadata server. :param server: The :class:`Server` (or its ID) for which the admin password is to be cleared """ return self._delete("/servers/%s/os-server-password" % base.getid(server)) def stop(self, server): """ Stop the server. :param server: The :class:`Server` (or its ID) to stop :returns: An instance of novaclient.base.TupleWithMeta """ resp, body = self._action_return_resp_and_body('os-stop', server, None) return base.TupleWithMeta((resp, body), resp) def force_delete(self, server): """ Force delete the server. :param server: The :class:`Server` (or its ID) to force delete :returns: An instance of novaclient.base.TupleWithMeta """ resp, body = self._action_return_resp_and_body('forceDelete', server, None) return base.TupleWithMeta((resp, body), resp) def restore(self, server): """ Restore soft-deleted server. :param server: The :class:`Server` (or its ID) to restore :returns: An instance of novaclient.base.TupleWithMeta """ resp, body = self._action_return_resp_and_body('restore', server, None) return base.TupleWithMeta((resp, body), resp) def start(self, server): """ Start the server. :param server: The :class:`Server` (or its ID) to start :returns: An instance of novaclient.base.TupleWithMeta """ return self._action('os-start', server, None) def pause(self, server): """ Pause the server. :param server: The :class:`Server` (or its ID) to pause :returns: An instance of novaclient.base.TupleWithMeta """ return self._action('pause', server, None) def unpause(self, server): """ Unpause the server. :param server: The :class:`Server` (or its ID) to unpause :returns: An instance of novaclient.base.TupleWithMeta """ return self._action('unpause', server, None) @api_versions.wraps("2.0", "2.72") def lock(self, server): """ Lock the server. :param server: The :class:`Server` (or its ID) to lock :returns: An instance of novaclient.base.TupleWithMeta """ return self._action('lock', server, None) @api_versions.wraps("2.73") def lock(self, server, reason=None): """ Lock the server. :param server: The :class:`Server` (or its ID) to lock :param reason: (Optional) The lock reason. :returns: An instance of novaclient.base.TupleWithMeta """ info = None if reason: info = {'locked_reason': reason} return self._action('lock', server, info) def unlock(self, server): """ Unlock the server. :param server: The :class:`Server` (or its ID) to unlock :returns: An instance of novaclient.base.TupleWithMeta """ return self._action('unlock', server, None) def suspend(self, server): """ Suspend the server. :param server: The :class:`Server` (or its ID) to suspend :returns: An instance of novaclient.base.TupleWithMeta """ return self._action('suspend', server, None) def resume(self, server): """ Resume the server. :param server: The :class:`Server` (or its ID) to resume :returns: An instance of novaclient.base.TupleWithMeta """ return self._action('resume', server, None) def rescue(self, server, password=None, image=None): """ Rescue the server. :param server: The :class:`Server` to rescue. :param password: The admin password to be set in the rescue instance. :param image: The :class:`Image` to rescue with. :returns: An instance of novaclient.base.TupleWithMeta """ info = {} if password: info['adminPass'] = password if image: info['rescue_image_ref'] = base.getid(image) resp, body = self._action_return_resp_and_body('rescue', server, info or None) return base.TupleWithMeta((resp, body), resp) def unrescue(self, server): """ Unrescue the server. :param server: The :class:`Server` (or its ID) to unrescue :returns: An instance of novaclient.base.TupleWithMeta """ return self._action('unrescue', server, None) def shelve(self, server): """ Shelve the server. :param server: The :class:`Server` (or its ID) to shelve :returns: An instance of novaclient.base.TupleWithMeta """ return self._action('shelve', server, None) def shelve_offload(self, server): """ Remove a shelved instance from the compute node. :param server: The :class:`Server` (or its ID) to shelve offload :returns: An instance of novaclient.base.TupleWithMeta """ return self._action('shelveOffload', server, None) @api_versions.wraps("2.0", "2.76") def unshelve(self, server): """ Unshelve the server. :param server: The :class:`Server` (or its ID) to unshelve :returns: An instance of novaclient.base.TupleWithMeta """ return self._action('unshelve', server, None) @api_versions.wraps("2.77", "2.90") def unshelve(self, server, availability_zone=None): """ Unshelve the server. :param server: The :class:`Server` (or its ID) to unshelve :param availability_zone: The specified availability zone name (Optional) :returns: An instance of novaclient.base.TupleWithMeta """ info = None if availability_zone: info = {'availability_zone': availability_zone} return self._action('unshelve', server, info) @api_versions.wraps("2.91") def unshelve(self, server, availability_zone=object(), host=None): """ Unshelve the server. :param availability_zone: If specified the instance will be unshelved to the availability_zone. If None is passed the instance defined availability_zone is unpin and the instance will be scheduled to any availability_zone (free scheduling). If not specified the instance will be unshelved to either its defined availability_zone or any availability_zone (free scheduling). :param host: The specified host (Optional) :returns: An instance of novaclient.base.TupleWithMeta """ info = None if availability_zone is None or isinstance(availability_zone, str): info = {'availability_zone': availability_zone} if host: if info: info['host'] = host else: info = {'host': host} return self._action('unshelve', server, info) def ips(self, server): """ Return IP Addresses associated with the server. Often a cheaper call then getting all the details for a server. :param server: The :class:`Server` (or its ID) for which the IP addresses are to be returned :returns: An instance of novaclient.base.DictWithMeta """ resp, body = self.api.client.get("/servers/%s/ips" % base.getid(server)) return base.DictWithMeta(body['addresses'], resp) def diagnostics(self, server): """ Retrieve server diagnostics. :param server: The :class:`Server` (or its ID) for which diagnostics to be returned :returns: An instance of novaclient.base.TupleWithMeta """ resp, body = self.api.client.get("/servers/%s/diagnostics" % base.getid(server)) return base.TupleWithMeta((resp, body), resp) @api_versions.wraps("2.78") def topology(self, server): """ Retrieve server topology. :param server: The :class:`Server` (or its ID) for which topology to be returned :returns: An instance of novaclient.base.DictWithMeta """ resp, body = self.api.client.get("/servers/%s/topology" % base.getid(server)) return base.DictWithMeta(body, resp) def _validate_create_nics(self, nics): # nics are required with microversion 2.37+ and can be a string or list if self.api_version > api_versions.APIVersion('2.36'): if not nics: raise ValueError('nics are required after microversion 2.36') elif nics and not isinstance(nics, (list, tuple)): raise ValueError('nics must be a list or a tuple, not %s' % type(nics)) # NOTE(stephenfin): It would be nice to make everything bar name, image and # flavor a kwarg-only argument but there are backwards-compatbility # concerns def create( self, name, image, flavor, meta=None, files=None, reservation_id=False, min_count=None, max_count=None, security_groups=None, userdata=None, key_name=None, availability_zone=None, block_device_mapping=None, block_device_mapping_v2=None, nics=None, scheduler_hints=None, config_drive=None, disk_config=None, admin_pass=None, access_ip_v4=None, access_ip_v6=None, description=None, tags=None, trusted_image_certificates=None, host=None, hypervisor_hostname=None, hostname=None, ): """ Create (boot) a new server. In order to create a server with pre-existing ports that contain a ``resource_request`` value, such as for guaranteed minimum bandwidth quality of service support, microversion ``2.72`` is required. :param name: Something to name the server. :param image: The :class:`Image` to boot with. :param flavor: The :class:`Flavor` to boot onto. :param meta: A dict of arbitrary key/value metadata to store for this server. Both keys and values must be <=255 characters. :param files: A dict of files to overwrite on the server upon boot. Keys are file names (i.e. ``/etc/passwd``) and values are the file contents (either as a string or as a file-like object). A maximum of five entries is allowed, and each file must be 10k or less. (deprecated starting with microversion 2.57) :param reservation_id: Return a reservation_id for the set of servers being requested, boolean. :param min_count: The minimum number of servers to launch. :param max_count: The maximum number of servers to launch. :param security_groups: A list of security group names :param userdata: User data to pass to be exposed by the metadata server this can be a file type object as well or a string. :param key_name: Name of previously created keypair to inject into the instance. :param availability_zone: Name of the availability zone for instance placement. :param block_device_mapping: A dict of block device mappings for this server. :param block_device_mapping_v2: A list of block device mappings (dicts) for this server. :param nics: An ordered list of nics (dicts) to be added to this server, with information about connected networks, fixed IPs, port etc. Beginning in microversion 2.37 this field is required and also supports a single string value of 'auto' or 'none'. The 'auto' value means the Compute service will automatically allocate a network for the project if one is not available. This is the same behavior as not passing anything for nics before microversion 2.37. The 'none' value tells the Compute service to not allocate any networking for the server. :param scheduler_hints: Arbitrary key-value pairs specified by the client to help boot an instance. :param config_drive: A boolean value to enable config drive. :param disk_config: Control how the disk is partitioned when the server is created. Possible values are 'AUTO' or 'MANUAL'. :param admin_pass: Add a user supplied admin password. :param access_ip_v4: Add alternative access IP (v4) :param access_ip_v6: Add alternative access IP (v6) :param description: Optional description of the server (allowed since microversion 2.19) :param tags: A list of arbitrary strings to be added to the server as tags (allowed since microversion 2.52) :param trusted_image_certificates: A list of trusted certificate IDs (allowed since microversion 2.63) :param host: Requested host to create servers (allowed since microversion 2.74) :param hypervisor_hostname: Requested hypervisor hostname to create servers (allowed since microversion 2.74) :param hostname: Requested hostname of server (allowed since microversion 2.90) """ if not min_count: min_count = 1 if not max_count: max_count = min_count if min_count > max_count: min_count = max_count boot_args = [name, image, flavor] if description and self.api_version < api_versions.APIVersion("2.19"): raise exceptions.UnsupportedAttribute("description", "2.19") self._validate_create_nics(nics) tags_microversion = api_versions.APIVersion("2.32") if self.api_version < tags_microversion: if nics: for nic_info in nics: if nic_info.get("tag"): raise ValueError("Setting interface tags is " "unsupported before microversion " "2.32") if block_device_mapping_v2: for bdm in block_device_mapping_v2: if bdm.get("tag"): raise ValueError("Setting block device tags is " "unsupported before microversion " "2.32") if tags and self.api_version < api_versions.APIVersion("2.52"): raise exceptions.UnsupportedAttribute("tags", "2.52") personality_files_deprecation = api_versions.APIVersion('2.57') if files and self.api_version >= personality_files_deprecation: raise exceptions.UnsupportedAttribute('files', '2.0', '2.56') trusted_certs_microversion = api_versions.APIVersion("2.63") if (trusted_image_certificates and self.api_version < trusted_certs_microversion): raise exceptions.UnsupportedAttribute("trusted_image_certificates", "2.63") if (block_device_mapping_v2 and self.api_version < api_versions.APIVersion('2.67')): for bdm in block_device_mapping_v2: if bdm.get('volume_type'): raise ValueError( "Block device volume_type is not supported before " "microversion 2.67") host_microversion = api_versions.APIVersion("2.74") if host and self.api_version < host_microversion: raise exceptions.UnsupportedAttribute("host", "2.74") hypervisor_hostname_microversion = api_versions.APIVersion("2.74") if (hypervisor_hostname and self.api_version < hypervisor_hostname_microversion): raise exceptions.UnsupportedAttribute( "hypervisor_hostname", "2.74") hostname_microversion = api_versions.APIVersion("2.90") if hostname and self.api_version < hostname_microversion: raise exceptions.UnsupportedAttribute("hostname", "2.90") boot_kwargs = dict( meta=meta, files=files, userdata=userdata, reservation_id=reservation_id, min_count=min_count, max_count=max_count, security_groups=security_groups, key_name=key_name, availability_zone=availability_zone, scheduler_hints=scheduler_hints, config_drive=config_drive, disk_config=disk_config, admin_pass=admin_pass, access_ip_v4=access_ip_v4, access_ip_v6=access_ip_v6, description=description, tags=tags, trusted_image_certificates=trusted_image_certificates, host=host, hypervisor_hostname=hypervisor_hostname, hostname=hostname) if block_device_mapping: boot_kwargs['block_device_mapping'] = block_device_mapping elif block_device_mapping_v2: boot_kwargs['block_device_mapping_v2'] = block_device_mapping_v2 if nics: boot_kwargs['nics'] = nics response_key = "server" if not reservation_id else "reservation_id" return self._boot(response_key, *boot_args, **boot_kwargs) @api_versions.wraps("2.0", "2.18") def update(self, server, name=None): """ Update attributes of a server. :param server: The :class:`Server` (or its ID) to update. :param name: Update the server's name. :returns: :class:`Server` """ if name is None: return body = { "server": { "name": name, }, } return self._update("/servers/%s" % base.getid(server), body, "server") @api_versions.wraps("2.19", "2.89") def update(self, server, name=None, description=None): """ Update attributes of a server. :param server: The :class:`Server` (or its ID) to update. :param name: Update the server's name. :param description: Update the server's description. If it equals to empty string(i.g. ""), the server description will be removed. :returns: :class:`Server` """ if name is None and description is None: return body = {"server": {}} if name: body["server"]["name"] = name if description == "": body["server"]["description"] = None elif description: body["server"]["description"] = description return self._update("/servers/%s" % base.getid(server), body, "server") @api_versions.wraps("2.90") def update(self, server, name=None, description=None, hostname=None): """ Update attributes of a server. :param server: The :class:`Server` (or its ID) to update. :param name: Update the server's name. :param description: Update the server's description. If it equals to empty string(i.g. ""), the server description will be removed. :param hostname: Update the server's hostname as recorded by the metadata service. Note that a separate utility running on the guest will be necessary to reflect these changes in the guest itself. :returns: :class:`Server` """ if name is None and description is None and hostname is None: return body = {"server": {}} if name: body["server"]["name"] = name if description == "": body["server"]["description"] = None elif description: body["server"]["description"] = description if hostname: body["server"]["hostname"] = hostname return self._update("/servers/%s" % base.getid(server), body, "server") def change_password(self, server, password): """ Update the password for a server. :param server: The :class:`Server` (or its ID) for which the admin password is to be changed :returns: An instance of novaclient.base.TupleWithMeta """ return self._action("changePassword", server, {"adminPass": password}) def delete(self, server): """ Delete (i.e. shut down and delete the image) this server. :param server: The :class:`Server` (or its ID) to delete :returns: An instance of novaclient.base.TupleWithMeta """ return self._delete("/servers/%s" % base.getid(server)) def reboot(self, server, reboot_type=REBOOT_SOFT): """ Reboot a server. :param server: The :class:`Server` (or its ID) to share onto. :param reboot_type: either :data:`REBOOT_SOFT` for a software-level reboot, or `REBOOT_HARD` for a virtual power cycle hard reboot. :returns: An instance of novaclient.base.TupleWithMeta """ return self._action('reboot', server, {'type': reboot_type}) # NOTE(stephenfin): It would be nice to make everything bar server and # image a kwarg-only argument but there are backwards-compatbility concerns def rebuild( self, server, image, password=None, disk_config=None, preserve_ephemeral=False, name=None, meta=None, files=None, *, description=_SENTINEL, key_name=_SENTINEL, userdata=_SENTINEL, trusted_image_certificates=_SENTINEL, hostname=_SENTINEL, ): """ Rebuild -- shut down and then re-image -- a server. :param server: The :class:`Server` (or its ID) to share onto. :param image: The :class:`Image` (or its ID) to re-image with. :param password: String to set as password on the rebuilt server. :param disk_config: Partitioning mode to use on the rebuilt server. Valid values are 'AUTO' or 'MANUAL' :param preserve_ephemeral: If True, request that any ephemeral device be preserved when rebuilding the instance. Defaults to False. :param name: Something to name the server. :param meta: A dict of arbitrary key/value metadata to store for this server. Both keys and values must be <=255 characters. :param files: A dict of files to overwrite on the server upon boot. Keys are file names (i.e. ``/etc/passwd``) and values are the file contents (either as a string or as a file-like object). A maximum of five entries is allowed, and each file must be 10k or less. (deprecated starting with microversion 2.57) :param description: Optional description of the server. If None is specified, the existing description will be unset. (starting from microversion 2.19) :param key_name: Optional key pair name for rebuild operation. If None is specified, the existing key will be unset. (starting from microversion 2.54) :param userdata: Optional user data to pass to be exposed by the metadata server; this can be a file type object as well or a string. If None is specified, the existing user_data is unset. (starting from microversion 2.57) :param trusted_image_certificates: A list of trusted certificate IDs or None to unset/reset the servers trusted image certificates (starting from microversion 2.63) :param hostname: Optional hostname to configure for the instance. If None is specified, the existing hostname will be unset. (starting from microversion 2.90) :returns: :class:`Server` """ # Microversion 2.19 adds the optional 'description' parameter if ( description is not _SENTINEL and self.api_version < api_versions.APIVersion('2.19') ): raise exceptions.UnsupportedAttribute('description', '2.19') # Microversion 2.54 adds the optional 'key_name' parameter if ( key_name is not _SENTINEL and self.api_version < api_versions.APIVersion('2.54') ): raise exceptions.UnsupportedAttribute('key_name', '2.54') # Microversion 2.57 deprecates personality files and adds support # for user_data. if files and self.api_version >= api_versions.APIVersion('2.57'): raise exceptions.UnsupportedAttribute('files', '2.0', '2.56') if ( userdata is not _SENTINEL and self.api_version < api_versions.APIVersion('2.57') ): raise exceptions.UnsupportedAttribute('userdata', '2.57') # Microversion 2.63 adds trusted image certificate support if ( trusted_image_certificates is not _SENTINEL and self.api_version < api_versions.APIVersion('2.63') ): raise exceptions.UnsupportedAttribute( 'trusted_image_certificates', '2.63') # Microversion 2.90 adds the optional 'hostname' parameter if ( hostname is not _SENTINEL and self.api_version < api_versions.APIVersion('2.90') ): raise exceptions.UnsupportedAttribute('hostname', '2.90') body = {'imageRef': base.getid(image)} if password is not None: body['adminPass'] = password if disk_config is not None: body['OS-DCF:diskConfig'] = disk_config if preserve_ephemeral is not False: body['preserve_ephemeral'] = True if name is not None: body['name'] = name if description is not _SENTINEL: body["description"] = description if key_name is not _SENTINEL: body['key_name'] = key_name if trusted_image_certificates is not _SENTINEL: body["trusted_image_certificates"] = trusted_image_certificates if hostname is not _SENTINEL: body["hostname"] = hostname if meta: body['metadata'] = meta if files: personality = body['personality'] = [] for filepath, file_or_string in sorted( files.items(), key=lambda x: x[0], ): if hasattr(file_or_string, 'read'): data = file_or_string.read() else: data = file_or_string cont = base64.b64encode(data.encode('utf-8')).decode('utf-8') personality.append({ 'path': filepath, 'contents': cont, }) if userdata is not _SENTINEL: # If userdata is specified but None, it means unset the existing # user_data on the instance. userdata = userdata body['user_data'] = (userdata if userdata is None else self.transform_userdata(userdata)) resp, body = self._action_return_resp_and_body( 'rebuild', server, body, ) return Server(self, body['server'], resp=resp) @api_versions.wraps("2.0", "2.55") def migrate(self, server): """ Migrate a server to a new host. :param server: The :class:`Server` (or its ID). :returns: An instance of novaclient.base.TupleWithMeta """ return self._action('migrate', server) @api_versions.wraps("2.56") def migrate(self, server, host=None): """ Migrate a server to a new host. :param server: The :class:`Server` (or its ID). :param host: (Optional) The target host. :returns: An instance of novaclient.base.TupleWithMeta """ info = {} if host: info['host'] = host return self._action('migrate', server, info) # NOTE(stephenfin): It would be nice to make disk_config a kwarg-only # argument but there are backwards-compatbility concerns def resize(self, server, flavor, disk_config=None): """ Resize a server's resources. :param server: The :class:`Server` (or its ID) to share onto. :param flavor: The :class:`Flavor` (or its ID) to resize to. :param disk_config: Partitioning mode to use on the rebuilt server. Valid values are 'AUTO' or 'MANUAL'. :returns: An instance of novaclient.base.TupleWithMeta Until a resize event is confirmed with :meth:`confirm_resize`, the old server will be kept around and you'll be able to roll back to the old flavor quickly with :meth:`revert_resize`. All resizes are automatically confirmed after 24 hours by default. """ info = {'flavorRef': base.getid(flavor)} if disk_config is not None: info['OS-DCF:diskConfig'] = disk_config return self._action('resize', server, info=info) def confirm_resize(self, server): """ Confirm that the resize worked, thus removing the original server. :param server: The :class:`Server` (or its ID) to share onto. :returns: An instance of novaclient.base.TupleWithMeta """ return self._action('confirmResize', server) def revert_resize(self, server): """ Revert a previous resize, switching back to the old server. :param server: The :class:`Server` (or its ID) to share onto. :returns: An instance of novaclient.base.TupleWithMeta """ return self._action('revertResize', server) def create_image(self, server, image_name, metadata=None): """ Snapshot a server. :param server: The :class:`Server` (or its ID) to share onto. :param image_name: Name to give the snapshot image :param metadata: Metadata to give newly-created image entity :returns: An instance of novaclient.base.StrWithMeta (The snapshot image's UUID) """ body = {'name': image_name, 'metadata': metadata or {}} resp, body = self._action_return_resp_and_body('createImage', server, body) # The 2.45 microversion returns the image_id in the response body, # not as a location header. if self.api_version >= api_versions.APIVersion('2.45'): image_uuid = body['image_id'] else: location = resp.headers['location'] image_uuid = location.split('/')[-1] return base.StrWithMeta(image_uuid, resp) def backup(self, server, backup_name, backup_type, rotation): """ Backup a server instance. :param server: The :class:`Server` (or its ID) to share onto. :param backup_name: Name of the backup image :param backup_type: The backup type, like 'daily' or 'weekly' :param rotation: Int parameter representing how many backups to keep around. :returns: An instance of novaclient.base.TupleWithMeta if the request microversion is < 2.45, otherwise novaclient.base.DictWithMeta. """ body = {'name': backup_name, 'backup_type': backup_type, 'rotation': rotation} return self._action('createBackup', server, body) def set_meta(self, server, metadata): """ Set a server's metadata :param server: The :class:`Server` to add metadata to :param metadata: A dict of metadata to be added to the server """ body = {'metadata': metadata} return self._create("/servers/%s/metadata" % base.getid(server), body, "metadata") def set_meta_item(self, server, key, value): """ Updates an item of server metadata :param server: The :class:`Server` to add metadata to :param key: metadata key to update :param value: string value """ body = {'meta': {key: value}} return self._update("/servers/%s/metadata/%s" % (base.getid(server), key), body) def list_meta(self, server): """ Lists all metadata for a server. :param server: The :class:`Server` (or its ID). :returns: A dict of metadata. """ resp, body = self.api.client.get("/servers/%s/metadata" % base.getid(server)) return base.DictWithMeta(body, resp) def get_meta(self, server, key): """ Shows details for a metadata item, by key, for a server. :param server: The :class:`Server` (or its ID). :param key: metadata key to get """ resp, body = self.api.client.get("/servers/%s/metadata/%s" % (base.getid(server), key)) return base.DictWithMeta(body, resp) def get_console_output(self, server, length=None): """ Get text console log output from Server. :param server: The :class:`Server` (or its ID) whose console output you would like to retrieve. :param length: The number of tail loglines you would like to retrieve. :returns: An instance of novaclient.base.StrWithMeta or novaclient.base.UnicodeWithMeta """ resp, body = self._action_return_resp_and_body('os-getConsoleOutput', server, {'length': length}) return self.convert_into_with_meta(body['output'], resp) def delete_meta(self, server, keys): """ Delete metadata from a server :param server: The :class:`Server` to add metadata to :param keys: A list of metadata keys to delete from the server :returns: An instance of novaclient.base.TupleWithMeta """ result = base.TupleWithMeta((), None) for k in keys: ret = self._delete("/servers/%s/metadata/%s" % (base.getid(server), k)) result.append_request_ids(ret.request_ids) return result def _live_migrate(self, server, host, block_migration, disk_over_commit, force): """Inner function to abstract changes in live migration API.""" body = { 'host': host, 'block_migration': block_migration, } if disk_over_commit is not None: body['disk_over_commit'] = disk_over_commit # NOTE(stephenfin): For some silly reason, we don't set this if it's # False, hence why we're not explicitly checking against None if force: body['force'] = force return self._action('os-migrateLive', server, body) @api_versions.wraps('2.0', '2.24') def live_migrate(self, server, host, block_migration, disk_over_commit): """ Migrates a running instance to a new machine. :param server: instance id which comes from nova list. :param host: destination host name. :param block_migration: if True, do block_migration. :param disk_over_commit: if True, allow disk overcommit. :returns: An instance of novaclient.base.TupleWithMeta """ return self._live_migrate(server, host, block_migration=block_migration, disk_over_commit=disk_over_commit, force=None) @api_versions.wraps('2.25', '2.29') def live_migrate(self, server, host, block_migration): """ Migrates a running instance to a new machine. :param server: instance id which comes from nova list. :param host: destination host name. :param block_migration: if True, do block_migration, can be set as 'auto' :returns: An instance of novaclient.base.TupleWithMeta """ return self._live_migrate(server, host, block_migration=block_migration, disk_over_commit=None, force=None) @api_versions.wraps('2.30', '2.67') def live_migrate(self, server, host, block_migration, force=None): """ Migrates a running instance to a new machine. :param server: instance id which comes from nova list. :param host: destination host name. :param block_migration: if True, do block_migration, can be set as 'auto' :param force: forces to bypass the scheduler if host is provided. :returns: An instance of novaclient.base.TupleWithMeta """ return self._live_migrate(server, host, block_migration=block_migration, disk_over_commit=None, force=force) @api_versions.wraps('2.68') def live_migrate(self, server, host, block_migration): """ Migrates a running instance to a new machine. :param server: instance id which comes from nova list. :param host: destination host name. :param block_migration: if True, do block_migration, can be set as 'auto' :returns: An instance of novaclient.base.TupleWithMeta """ return self._live_migrate(server, host, block_migration=block_migration, disk_over_commit=None, force=None) def reset_state(self, server, state='error'): """ Reset the state of an instance to active or error. :param server: ID of the instance to reset the state of. :param state: Desired state; either 'active' or 'error'. Defaults to 'error'. :returns: An instance of novaclient.base.TupleWithMeta """ return self._action('os-resetState', server, dict(state=state)) def reset_network(self, server): """ Reset network of an instance. :param server: The :class:`Server` for network is to be reset :returns: An instance of novaclient.base.TupleWithMeta """ return self._action('resetNetwork', server) def add_security_group(self, server, security_group): """ Add a Security Group to an instance :param server: ID of the instance. :param security_group: The name of security group to add. :returns: An instance of novaclient.base.DictWithMeta """ return self._action('addSecurityGroup', server, {'name': security_group}) def remove_security_group(self, server, security_group): """ Remove a Security Group to an instance :param server: ID of the instance. :param security_group: The name of security group to remove. :returns: An instance of novaclient.base.TupleWithMeta """ return self._action('removeSecurityGroup', server, {'name': security_group}) def list_security_group(self, server): """ List Security Group(s) of an instance :param server: ID of the instance. """ return self._list('/servers/%s/os-security-groups' % base.getid(server), 'security_groups', SecurityGroup) def _evacuate(self, server, host, on_shared_storage, password, force): """Inner function to abstract changes in evacuate API.""" body = {} if on_shared_storage is not None: body['onSharedStorage'] = on_shared_storage if host is not None: body['host'] = host if password is not None: body['adminPass'] = password if force: body['force'] = force resp, body = self._action_return_resp_and_body('evacuate', server, body) return base.TupleWithMeta((resp, body), resp) @api_versions.wraps("2.0", "2.13") def evacuate(self, server, host=None, on_shared_storage=True, password=None): """ Evacuate a server instance. :param server: The :class:`Server` (or its ID) to evacuate to. :param host: Name of the target host. :param on_shared_storage: Specifies whether instance files located on shared storage :param password: string to set as password on the evacuated server. :returns: An instance of novaclient.base.TupleWithMeta """ return self._evacuate(server, host, on_shared_storage=on_shared_storage, password=password, force=None) @api_versions.wraps("2.14", "2.28") def evacuate(self, server, host=None, password=None): """ Evacuate a server instance. :param server: The :class:`Server` (or its ID) to evacuate to. :param host: Name of the target host. :param password: string to set as password on the evacuated server. :returns: An instance of novaclient.base.TupleWithMeta """ return self._evacuate(server, host, on_shared_storage=None, password=password, force=None) @api_versions.wraps("2.29", "2.67") def evacuate(self, server, host=None, password=None, force=None): """ Evacuate a server instance. :param server: The :class:`Server` (or its ID) to evacuate to. :param host: Name of the target host. :param password: string to set as password on the evacuated server. :param force: forces to bypass the scheduler if host is provided. :returns: An instance of novaclient.base.TupleWithMeta """ return self._evacuate(server, host, on_shared_storage=None, password=password, force=force) @api_versions.wraps("2.68") def evacuate(self, server, host=None, password=None): """ Evacuate a server instance. :param server: The :class:`Server` (or its ID) to evacuate to. :param host: Name of the target host. :param password: string to set as password on the evacuated server. :returns: An instance of novaclient.base.TupleWithMeta """ return self._evacuate(server, host, on_shared_storage=None, password=password, force=None) def interface_list(self, server): """ List attached network interfaces :param server: The :class:`Server` (or its ID) to query. """ return self._list('/servers/%s/os-interface' % base.getid(server), 'interfaceAttachments', obj_class=NetworkInterface) @api_versions.wraps("2.0", "2.48") def interface_attach(self, server, port_id, net_id, fixed_ip): """ Attach a network_interface to an instance. :param server: The :class:`Server` (or its ID) to attach to. :param port_id: The port to attach. """ body = {'interfaceAttachment': {}} if port_id: body['interfaceAttachment']['port_id'] = port_id if net_id: body['interfaceAttachment']['net_id'] = net_id if fixed_ip: body['interfaceAttachment']['fixed_ips'] = [ {'ip_address': fixed_ip}] return self._create('/servers/%s/os-interface' % base.getid(server), body, 'interfaceAttachment', obj_class=NetworkInterface) @api_versions.wraps("2.49") def interface_attach(self, server, port_id, net_id, fixed_ip, tag=None): """ Attach a network_interface to an instance. :param server: The :class:`Server` (or its ID) to attach to. :param port_id: The port to attach. The port_id and net_id parameters are mutually exclusive. :param net_id: The ID of the network to attach. :param fixed_ip: The fixed IP addresses. If the fixed_ip is specified, the net_id has to be specified at the same time. :param tag: The tag. """ body = {'interfaceAttachment': {}} if port_id: body['interfaceAttachment']['port_id'] = port_id if net_id: body['interfaceAttachment']['net_id'] = net_id if fixed_ip: body['interfaceAttachment']['fixed_ips'] = [ {'ip_address': fixed_ip}] if tag: body['interfaceAttachment']['tag'] = tag return self._create('/servers/%s/os-interface' % base.getid(server), body, 'interfaceAttachment', obj_class=NetworkInterface) def interface_detach(self, server, port_id): """ Detach a network_interface from an instance. :param server: The :class:`Server` (or its ID) to detach from. :param port_id: The port to detach. :returns: An instance of novaclient.base.TupleWithMeta """ return self._delete('/servers/%s/os-interface/%s' % (base.getid(server), port_id)) @api_versions.wraps("2.17") def trigger_crash_dump(self, server): """Trigger crash dump in an instance""" return self._action("trigger_crash_dump", server) def _action(self, action, server, info=None, **kwargs): """ Perform a server "action" -- reboot/rebuild/resize/etc. """ resp, body = self._action_return_resp_and_body(action, server, info=info, **kwargs) return self.convert_into_with_meta(body, resp) def _action_return_resp_and_body(self, action, server, info=None, **kwargs): """ Perform a server "action" and return response headers and body """ body = {action: info} self.run_hooks('modify_body_for_action', body, **kwargs) url = '/servers/%s/action' % base.getid(server) return self.api.client.post(url, body=body) @api_versions.wraps('2.26') def tag_list(self, server): """ Get list of tags from an instance. """ resp, body = self.api.client.get( "/servers/%s/tags" % base.getid(server)) return base.ListWithMeta(body['tags'], resp) @api_versions.wraps('2.26') def delete_tag(self, server, tag): """ Remove single tag from an instance. """ return self._delete("/servers/%s/tags/%s" % (base.getid(server), tag)) @api_versions.wraps('2.26') def delete_all_tags(self, server): """ Remove all tags from an instance. """ return self._delete("/servers/%s/tags" % base.getid(server)) @api_versions.wraps('2.26') def set_tags(self, server, tags): """ Set list of tags to an instance. """ body = {"tags": tags} return self._update("/servers/%s/tags" % base.getid(server), body) @api_versions.wraps('2.26') def add_tag(self, server, tag): """ Add single tag to an instance. """ return self._update( "/servers/%s/tags/%s" % (base.getid(server), tag), None) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/v2/services.py0000664000175000017500000001251700000000000022207 0ustar00zuulzuul00000000000000# Copyright 2012 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Service interface. """ from urllib import parse from novaclient import api_versions from novaclient import base class Service(base.Resource): def __repr__(self): return "" % self.id def _add_details(self, info): dico = 'resource' in info and info['resource'] or info for (k, v) in dico.items(): setattr(self, k, v) class ServiceManager(base.ManagerWithFind): resource_class = Service def list(self, host=None, binary=None): """ Get a list of services. :param host: destination host name. """ url = "/os-services" filters = [] if host: filters.append(("host", host)) if binary: filters.append(("binary", binary)) if filters: url = "%s?%s" % (url, parse.urlencode(filters)) return self._list(url, "services") @api_versions.wraps("2.0", "2.10") def _update_body(self, host, binary, disabled_reason=None): body = {"host": host, "binary": binary} if disabled_reason is not None: body["disabled_reason"] = disabled_reason return body @api_versions.wraps("2.11") def _update_body(self, host, binary, disabled_reason=None, force_down=None): body = {"host": host, "binary": binary} if disabled_reason is not None: body["disabled_reason"] = disabled_reason if force_down is not None: body["forced_down"] = force_down return body @api_versions.wraps('2.0', '2.52') def enable(self, host, binary): """Enable the service specified by hostname and binary.""" body = self._update_body(host, binary) return self._update("/os-services/enable", body, "service") @api_versions.wraps('2.53') def enable(self, service_uuid): """Enable the service specified by the service UUID ID. :param service_uuid: The UUID ID of the service to enable. """ return self._update( "/os-services/%s" % service_uuid, {'status': 'enabled'}, "service") @api_versions.wraps('2.0', '2.52') def disable(self, host, binary): """Disable the service specified by hostname and binary.""" body = self._update_body(host, binary) return self._update("/os-services/disable", body, "service") @api_versions.wraps('2.53') def disable(self, service_uuid): """Disable the service specified by the service UUID ID. :param service_uuid: The UUID ID of the service to disable. """ return self._update("/os-services/%s" % service_uuid, {'status': 'disabled'}, "service") @api_versions.wraps('2.0', '2.52') def disable_log_reason(self, host, binary, reason): """Disable the service with reason.""" body = self._update_body(host, binary, reason) return self._update("/os-services/disable-log-reason", body, "service") @api_versions.wraps('2.53') def disable_log_reason(self, service_uuid, reason): """Disable the service with a reason. :param service_uuid: The UUID ID of the service to disable. :param reason: The reason for disabling a service. The minimum length is 1 and the maximum length is 255. """ body = { 'status': 'disabled', 'disabled_reason': reason } return self._update("/os-services/%s" % service_uuid, body, "service") def delete(self, service_id): """Delete a service. :param service_id: Before microversion 2.53, this must be an integer id and may not uniquely the service in a multi-cell deployment. Starting with microversion 2.53 this must be a UUID. """ return self._delete("/os-services/%s" % service_id) @api_versions.wraps("2.11", "2.52") def force_down(self, host, binary, force_down=None): """Force service state to down specified by hostname and binary.""" body = self._update_body(host, binary, force_down=force_down) return self._update("/os-services/force-down", body, "service") @api_versions.wraps("2.53") def force_down(self, service_uuid, force_down): """Update the service's ``forced_down`` field specified by the service UUID ID. :param service_uuid: The UUID ID of the service. :param force_down: Whether or not this service was forced down manually by an administrator. This value is useful to know that some 3rd party has verified the service should be marked down. """ return self._update("/os-services/%s" % service_uuid, {'forced_down': force_down}, "service") ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/v2/shell.py0000664000175000017500000060733600000000000021504 0ustar00zuulzuul00000000000000# Copyright 2010 Jacob Kaplan-Moss # Copyright 2011 OpenStack Foundation # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import argparse import collections import datetime import getpass import logging import os import pprint import sys import time from oslo_utils import netutils from oslo_utils import strutils from oslo_utils import timeutils import novaclient from novaclient import api_versions from novaclient import base from novaclient import client from novaclient import exceptions from novaclient.i18n import _ from novaclient import shell from novaclient import utils from novaclient.v2 import availability_zones from novaclient.v2 import quotas from novaclient.v2 import servers logger = logging.getLogger(__name__) def emit_duplicated_image_with_warning(img, image_with): img_uuid_list = [str(image.id) for image in img] print(_('WARNING: Multiple matching images: %(img_uuid_list)s\n' 'Using image: %(chosen_one)s') % {'img_uuid_list': img_uuid_list, 'chosen_one': img_uuid_list[0]}, file=sys.stderr) # TODO(takashin): Remove this along with the deprecated commands in the first # major python-novaclient release AFTER the nova server 24.0.0 X release. def _emit_agent_deprecation_warning(): print('This command has been deprecated since 23.0.0 Wallaby Release ' 'and will be removed in the first major release ' 'after the Nova server 24.0.0 X release.', file=sys.stderr) CLIENT_BDM2_KEYS = { 'id': 'uuid', 'source': 'source_type', 'dest': 'destination_type', 'bus': 'disk_bus', 'device': 'device_name', 'size': 'volume_size', 'format': 'guest_format', 'bootindex': 'boot_index', 'type': 'device_type', 'shutdown': 'delete_on_termination', 'tag': 'tag', 'volume_type': 'volume_type' # added in 2.67 } def _key_value_pairing(text): try: (k, v) = text.split('=', 1) return (k, v) except ValueError: msg = _("'%s' is not in the format of 'key=value'") % text raise argparse.ArgumentTypeError(msg) def _meta_parsing(metadata): try: return dict(v.split('=', 1) for v in metadata) except ValueError: msg = _("'%s' is not in the format of 'key=value'") % metadata raise argparse.ArgumentTypeError(msg) def _match_image(cs, wanted_properties): image_list = cs.glance.list() images_matched = [] match = set(wanted_properties) for img in image_list: img_dict = {} # exclude any unhashable entries for key, value in img.to_dict().items(): try: set([key, value]) except TypeError: pass else: img_dict[key] = value if match == match.intersection(set(img_dict.items())): images_matched.append(img) return images_matched def _supports_block_device_tags(cs): if (cs.api_version == api_versions.APIVersion('2.32') or cs.api_version >= api_versions.APIVersion('2.42')): return True else: return False def _parse_device_spec(device_spec): spec_dict = {} for arg in device_spec.split(','): if '=' in arg: spec_dict.update([arg.split('=')]) else: raise argparse.ArgumentTypeError( _("Expected a comma-separated list of key=value pairs. '%s' " "is not a key=value pair.") % arg) return spec_dict def _parse_block_device_mapping_v2(cs, args, image): bdm = [] if args.boot_volume: bdm_dict = {'uuid': args.boot_volume, 'source_type': 'volume', 'destination_type': 'volume', 'boot_index': 0, 'delete_on_termination': False} bdm.append(bdm_dict) if args.snapshot: bdm_dict = {'uuid': args.snapshot, 'source_type': 'snapshot', 'destination_type': 'volume', 'boot_index': 0, 'delete_on_termination': False} bdm.append(bdm_dict) supports_volume_type = cs.api_version >= api_versions.APIVersion('2.67') for device_spec in args.block_device: spec_dict = _parse_device_spec(device_spec) bdm_dict = {} if ('tag' in spec_dict and not _supports_block_device_tags(cs)): raise exceptions.CommandError( _("'tag' in block device mapping is not supported " "in API version %(version)s.") % {'version': cs.api_version.get_string()}) if 'volume_type' in spec_dict and not supports_volume_type: raise exceptions.CommandError( _("'volume_type' in block device mapping is not supported " "in API version %(version)s.") % {'version': cs.api_version.get_string()}) for key, value in spec_dict.items(): bdm_dict[CLIENT_BDM2_KEYS[key]] = value source_type = bdm_dict.get('source_type') if not source_type: bdm_dict['source_type'] = 'blank' elif source_type not in ( 'volume', 'image', 'snapshot', 'blank'): raise exceptions.CommandError( _("The value of source_type key of --block-device " "should be one of 'volume', 'image', 'snapshot' " "or 'blank' but it was '%(action)s'") % {'action': source_type}) destination_type = bdm_dict.get('destination_type') if not destination_type: source_type = bdm_dict['source_type'] if source_type in ('image', 'blank'): bdm_dict['destination_type'] = 'local' if source_type in ('snapshot', 'volume'): bdm_dict['destination_type'] = 'volume' elif destination_type not in ('local', 'volume'): raise exceptions.CommandError( _("The value of destination_type key of --block-device " "should be either 'local' or 'volume' but it " "was '%(action)s'") % {'action': destination_type}) # Convert the delete_on_termination to a boolean or set it to true by # default for local block devices when not specified. if 'delete_on_termination' in bdm_dict: action = bdm_dict['delete_on_termination'] if action not in ['remove', 'preserve']: raise exceptions.CommandError( _("The value of shutdown key of --block-device shall be " "either 'remove' or 'preserve' but it was '%(action)s'") % {'action': action}) bdm_dict['delete_on_termination'] = (action == 'remove') elif bdm_dict.get('destination_type') == 'local': bdm_dict['delete_on_termination'] = True bdm.append(bdm_dict) for ephemeral_spec in args.ephemeral: bdm_dict = {'source_type': 'blank', 'destination_type': 'local', 'boot_index': -1, 'delete_on_termination': True} try: eph_dict = _parse_device_spec(ephemeral_spec) except ValueError: err_msg = (_("Invalid ephemeral argument '%s'.") % args.ephemeral) raise argparse.ArgumentTypeError(err_msg) if 'size' in eph_dict: bdm_dict['volume_size'] = eph_dict['size'] if 'format' in eph_dict: bdm_dict['guest_format'] = eph_dict['format'] bdm.append(bdm_dict) if args.swap: bdm_dict = {'source_type': 'blank', 'destination_type': 'local', 'boot_index': -1, 'delete_on_termination': True, 'guest_format': 'swap', 'volume_size': args.swap} bdm.append(bdm_dict) return bdm def _supports_nic_tags(cs): if ((cs.api_version >= api_versions.APIVersion('2.32') and cs.api_version <= api_versions.APIVersion('2.36')) or cs.api_version >= api_versions.APIVersion('2.42')): return True else: return False def _parse_nics(cs, args): supports_auto_alloc = cs.api_version >= api_versions.APIVersion('2.37') supports_nic_tags = _supports_nic_tags(cs) nic_keys = {'net-id', 'v4-fixed-ip', 'v6-fixed-ip', 'port-id', 'net-name'} if supports_auto_alloc and supports_nic_tags: # API version >= 2.42 nic_keys.add('tag') err_msg = (_("Invalid nic argument '%s'. Nic arguments must be of " "the form --nic , " "with only one of net-id, net-name or port-id " "specified. Specifying a --nic of auto or none cannot " "be used with any other --nic value.")) elif supports_auto_alloc and not supports_nic_tags: # 2.41 >= API version >= 2.37 err_msg = (_("Invalid nic argument '%s'. Nic arguments must be of " "the form --nic , " "with only one of net-id, net-name or port-id " "specified. Specifying a --nic of auto or none cannot " "be used with any other --nic value.")) elif not supports_auto_alloc and supports_nic_tags: # 2.36 >= API version >= 2.32 nic_keys.add('tag') err_msg = (_("Invalid nic argument '%s'. Nic arguments must be of " "the form --nic , " "with only one of net-id, net-name or port-id " "specified.")) else: # API version <= 2.31 err_msg = (_("Invalid nic argument '%s'. Nic arguments must be of " "the form --nic , " "with only one of net-id, net-name or port-id " "specified.")) auto_or_none = False nics = [] for nic_str in args.nics: nic_info = {} nic_info_set = False for kv_str in nic_str.split(","): if auto_or_none: # Since we start with auto_or_none being False, it being true # means we've parsed an auto or none argument, then continued # after the comma to another key=value pair. Since auto or none # can only be given by themselves, raise. raise exceptions.CommandError(_("'auto' or 'none' cannot be " "used with any other nic " "arguments")) try: # handle the special auto/none cases if kv_str in ('auto', 'none'): if not supports_auto_alloc: raise exceptions.CommandError(err_msg % nic_str) if nic_info_set: # Since we start with nic_info_set being False, it # being true means we've parsed a key=value pair, then # landed on a auto or none argument after the comma. # Since auto or none can only be given by themselves, # raise. raise exceptions.CommandError( _("'auto' or 'none' cannot be used with any " "other nic arguments")) nics.append(kv_str) auto_or_none = True continue k, v = kv_str.split("=", 1) except ValueError: raise exceptions.CommandError(err_msg % nic_str) if k in nic_keys: # if user has given a net-name resolve it to network ID if k == 'net-name': k = 'net-id' v = _find_network_id(cs, v) # if some argument was given multiple times if k in nic_info: raise exceptions.CommandError(err_msg % nic_str) nic_info[k] = v nic_info_set = True else: raise exceptions.CommandError(err_msg % nic_str) if auto_or_none: continue if 'v4-fixed-ip' in nic_info and not netutils.is_valid_ipv4( nic_info['v4-fixed-ip']): raise exceptions.CommandError(_("Invalid ipv4 address.")) if 'v6-fixed-ip' in nic_info and not netutils.is_valid_ipv6( nic_info['v6-fixed-ip']): raise exceptions.CommandError(_("Invalid ipv6 address.")) if bool(nic_info.get('net-id')) == bool(nic_info.get('port-id')): raise exceptions.CommandError(err_msg % nic_str) nics.append(nic_info) if nics: if auto_or_none: if len(nics) > 1: raise exceptions.CommandError(err_msg % nic_str) # change the single list entry to a string nics = nics[0] else: # Default to 'auto' if API version >= 2.37 and nothing was specified if supports_auto_alloc: nics = 'auto' return nics def _boot(cs, args): """Boot a new server.""" if not args.flavor: raise exceptions.CommandError(_("you need to specify a Flavor ID.")) if args.image: image = _find_image(cs, args.image) else: image = None if not image and args.image_with: images = _match_image(cs, args.image_with) if len(images) > 1: emit_duplicated_image_with_warning(images, args.image_with) if images: image = images[0] else: raise exceptions.CommandError(_("No images match the property " "expected by --image-with")) min_count = 1 max_count = 1 if args.min_count is not None: if args.min_count < 1: raise exceptions.CommandError(_("min_count should be >= 1")) min_count = args.min_count max_count = min_count if args.max_count is not None: if args.max_count < 1: raise exceptions.CommandError(_("max_count should be >= 1")) max_count = args.max_count if (args.min_count is not None and args.max_count is not None and args.min_count > args.max_count): raise exceptions.CommandError(_("min_count should be <= max_count")) flavor = _find_flavor(cs, args.flavor) meta = _meta_parsing(args.meta) include_files = cs.api_version < api_versions.APIVersion('2.57') if include_files: files = {} for f in args.files: try: dst, src = f.split('=', 1) with open(src) as fo: files[dst] = fo.read() except IOError as e: raise exceptions.CommandError( _("Can't open '%(src)s': %(exc)s") % {'src': src, 'exc': e}) except ValueError: raise exceptions.CommandError( _("Invalid file argument '%s'. " "File arguments must be of the " "form '--file '") % f) # use the os-keypair extension key_name = None if args.key_name is not None: key_name = args.key_name if args.user_data: try: with open(args.user_data) as f: userdata = f.read() except IOError as e: raise exceptions.CommandError(_("Can't open '%(user_data)s': " "%(exc)s") % {'user_data': args.user_data, 'exc': e}) else: userdata = None if args.availability_zone: availability_zone = args.availability_zone else: availability_zone = None if args.security_groups: security_groups = args.security_groups.split(',') else: security_groups = None block_device_mapping = {} for bdm in args.block_device_mapping: device_name, mapping = bdm.split('=', 1) block_device_mapping[device_name] = mapping block_device_mapping_v2 = _parse_block_device_mapping_v2(cs, args, image) n_boot_args = len(list(filter( bool, (image, args.boot_volume, args.snapshot)))) have_bdm = block_device_mapping_v2 or block_device_mapping # Fail if more than one boot devices are present # or if there is no device to boot from. if n_boot_args > 1 or n_boot_args == 0 and not have_bdm: raise exceptions.CommandError( _("you need to specify at least one source ID (Image, Snapshot, " "or Volume), a block device mapping or provide a set of " "properties to match against an image")) if block_device_mapping and block_device_mapping_v2: raise exceptions.CommandError( _("you can't mix old block devices (--block-device-mapping) " "with the new ones (--block-device, --boot-volume, --snapshot, " "--ephemeral, --swap)")) nics = _parse_nics(cs, args) hints = {} if args.scheduler_hints: for key, value in args.scheduler_hints: # NOTE(vish): multiple copies of the same hint will # result in a list of values if key in hints: if isinstance(hints[key], str): hints[key] = [hints[key]] hints[key] += [value] else: hints[key] = value boot_args = [args.name, image, flavor] if str(args.config_drive).lower() in ("true", "1"): config_drive = True elif str(args.config_drive).lower() in ("false", "0", "", "none"): config_drive = None else: raise exceptions.CommandError( _("The value of the '--config-drive' option must be " "a boolean value.")) boot_kwargs = dict( meta=meta, key_name=key_name, min_count=min_count, max_count=max_count, userdata=userdata, availability_zone=availability_zone, security_groups=security_groups, block_device_mapping=block_device_mapping, block_device_mapping_v2=block_device_mapping_v2, nics=nics, scheduler_hints=hints, config_drive=config_drive, admin_pass=args.admin_pass, access_ip_v4=args.access_ip_v4, access_ip_v6=args.access_ip_v6, reservation_id=args.return_reservation_id) if 'description' in args: boot_kwargs["description"] = args.description if 'tags' in args and args.tags: boot_kwargs["tags"] = args.tags.split(',') if 'host' in args and args.host: boot_kwargs["host"] = args.host if 'hypervisor_hostname' in args and args.hypervisor_hostname: boot_kwargs["hypervisor_hostname"] = args.hypervisor_hostname if include_files: boot_kwargs['files'] = files if ( 'trusted_image_certificates' in args and args.trusted_image_certificates ): boot_kwargs['trusted_image_certificates'] = ( args.trusted_image_certificates) elif utils.env('OS_TRUSTED_IMAGE_CERTIFICATE_IDS'): if cs.api_version >= api_versions.APIVersion('2.63'): boot_kwargs["trusted_image_certificates"] = utils.env( 'OS_TRUSTED_IMAGE_CERTIFICATE_IDS').split(',') else: raise exceptions.UnsupportedAttribute( "OS_TRUSTED_IMAGE_CERTIFICATE_IDS", "2.63") if 'hostname' in args and args.hostname: boot_kwargs['hostname'] = args.hostname return boot_args, boot_kwargs @utils.arg( '--flavor', default=None, metavar='', help=_("Name or ID of flavor (see 'nova flavor-list').")) @utils.arg( '--image', default=None, metavar='', help=_("Name or ID of image (see 'glance image-list'). ")) @utils.arg( '--image-with', default=[], type=_key_value_pairing, action='append', metavar='', help=_("Image metadata property (see 'glance image-show'). ")) @utils.arg( '--boot-volume', default=None, metavar="", help=_("Volume ID to boot from.")) @utils.arg( '--snapshot', default=None, metavar="", help=_("Snapshot ID to boot from (will create a volume).")) @utils.arg( '--min-count', default=None, type=int, metavar='', help=_("Boot at least servers (limited by quota).")) @utils.arg( '--max-count', default=None, type=int, metavar='', help=_("Boot up to servers (limited by quota).")) @utils.arg( '--meta', metavar="", action='append', default=[], help=_("Record arbitrary key/value metadata to /meta_data.json " "on the metadata server. Can be specified multiple times.")) @utils.arg( '--file', metavar="", action='append', dest='files', default=[], help=_("Store arbitrary files from locally to " "on the new server. More files can be injected using multiple " "'--file' options. Limited by the 'injected_files' quota value. " "The default value is 5. You can get the current quota value by " "'Personality' limit from 'nova limits' command."), start_version='2.0', end_version='2.56') @utils.arg( '--key-name', default=os.environ.get('NOVACLIENT_DEFAULT_KEY_NAME'), metavar='', help=_("Key name of keypair that should be created earlier with \ the command keypair-add.")) @utils.arg('name', metavar='', help=_('Name for the new server.')) @utils.arg( '--user-data', default=None, metavar='', help=_("user data file to pass to be exposed by the metadata server.")) @utils.arg( '--availability-zone', default=None, metavar='', help=_("The availability zone for server placement.")) @utils.arg( '--security-groups', default=None, metavar='', help=_("Comma separated list of security group names.")) @utils.arg( '--block-device-mapping', metavar="", action='append', default=[], help=_("Block device mapping in the format " "=:::.")) @utils.arg( '--block-device', metavar="key1=value1[,key2=value2...]", action='append', default=[], start_version='2.0', end_version='2.31', help=_("Block device mapping with the keys: " "id=UUID (image_id, snapshot_id or volume_id only if using source " "image, snapshot or volume) " "source=source type (image, snapshot, volume or blank), " "dest=destination type of the block device (volume or local), " "bus=device's bus (e.g. uml, lxc, virtio, ...; if omitted, " "hypervisor driver chooses a suitable default, " "honoured only if device type is supplied) " "type=device type (e.g. disk, cdrom, ...; defaults to 'disk') " "device=name of the device (e.g. vda, xda, ...; " "if omitted, hypervisor driver chooses suitable device " "depending on selected bus; note the libvirt driver always " "uses default device names), " "size=size of the block device in MiB(for swap) and in " "GiB(for other formats) " "(if omitted, hypervisor driver calculates size), " "format=device will be formatted (e.g. swap, ntfs, ...; optional), " "bootindex=integer used for ordering the boot disks " "(for image backed instances it is equal to 0, " "for others need to be specified) and " "shutdown=shutdown behaviour (either preserve or remove, " "for local destination set to remove).")) @utils.arg( '--block-device', metavar="key1=value1[,key2=value2...]", action='append', default=[], start_version='2.32', end_version='2.32', help=_("Block device mapping with the keys: " "id=UUID (image_id, snapshot_id or volume_id only if using source " "image, snapshot or volume) " "source=source type (image, snapshot, volume or blank), " "dest=destination type of the block device (volume or local), " "bus=device's bus (e.g. uml, lxc, virtio, ...; if omitted, " "hypervisor driver chooses a suitable default, " "honoured only if device type is supplied) " "type=device type (e.g. disk, cdrom, ...; defaults to 'disk') " "device=name of the device (e.g. vda, xda, ...; " "tag=device metadata tag (optional) " "if omitted, hypervisor driver chooses suitable device " "depending on selected bus; note the libvirt driver always " "uses default device names), " "size=size of the block device in MiB(for swap) and in " "GiB(for other formats) " "(if omitted, hypervisor driver calculates size), " "format=device will be formatted (e.g. swap, ntfs, ...; optional), " "bootindex=integer used for ordering the boot disks " "(for image backed instances it is equal to 0, " "for others need to be specified) and " "shutdown=shutdown behaviour (either preserve or remove, " "for local destination set to remove).")) @utils.arg( '--block-device', metavar="key1=value1[,key2=value2...]", action='append', default=[], start_version='2.33', end_version='2.41', help=_("Block device mapping with the keys: " "id=UUID (image_id, snapshot_id or volume_id only if using source " "image, snapshot or volume) " "source=source type (image, snapshot, volume or blank), " "dest=destination type of the block device (volume or local), " "bus=device's bus (e.g. uml, lxc, virtio, ...; if omitted, " "hypervisor driver chooses a suitable default, " "honoured only if device type is supplied) " "type=device type (e.g. disk, cdrom, ...; defaults to 'disk') " "device=name of the device (e.g. vda, xda, ...; " "if omitted, hypervisor driver chooses suitable device " "depending on selected bus; note the libvirt driver always " "uses default device names), " "size=size of the block device in MiB(for swap) and in " "GiB(for other formats) " "(if omitted, hypervisor driver calculates size), " "format=device will be formatted (e.g. swap, ntfs, ...; optional), " "bootindex=integer used for ordering the boot disks " "(for image backed instances it is equal to 0, " "for others need to be specified) and " "shutdown=shutdown behaviour (either preserve or remove, " "for local destination set to remove).")) @utils.arg( '--block-device', metavar="key1=value1[,key2=value2...]", action='append', default=[], start_version='2.42', end_version='2.66', help=_("Block device mapping with the keys: " "id=UUID (image_id, snapshot_id or volume_id only if using source " "image, snapshot or volume) " "source=source type (image, snapshot, volume or blank), " "dest=destination type of the block device (volume or local), " "bus=device's bus (e.g. uml, lxc, virtio, ...; if omitted, " "hypervisor driver chooses a suitable default, " "honoured only if device type is supplied) " "type=device type (e.g. disk, cdrom, ...; defaults to 'disk') " "device=name of the device (e.g. vda, xda, ...; " "if omitted, hypervisor driver chooses suitable device " "depending on selected bus; note the libvirt driver always " "uses default device names), " "size=size of the block device in MiB(for swap) and in " "GiB(for other formats) " "(if omitted, hypervisor driver calculates size), " "format=device will be formatted (e.g. swap, ntfs, ...; optional), " "bootindex=integer used for ordering the boot disks " "(for image backed instances it is equal to 0, " "for others need to be specified), " "shutdown=shutdown behaviour (either preserve or remove, " "for local destination set to remove) and " "tag=device metadata tag (optional).")) @utils.arg( '--block-device', metavar="key1=value1[,key2=value2...]", action='append', default=[], start_version='2.67', help=_("Block device mapping with the keys: " "id=UUID (image_id, snapshot_id or volume_id only if using source " "image, snapshot or volume) " "source=source type (image, snapshot, volume or blank), " "dest=destination type of the block device (volume or local), " "bus=device's bus (e.g. uml, lxc, virtio, ...; if omitted, " "hypervisor driver chooses a suitable default, " "honoured only if device type is supplied) " "type=device type (e.g. disk, cdrom, ...; defaults to 'disk') " "device=name of the device (e.g. vda, xda, ...; " "if omitted, hypervisor driver chooses suitable device " "depending on selected bus; note the libvirt driver always " "uses default device names), " "size=size of the block device in MiB(for swap) and in " "GiB(for other formats) " "(if omitted, hypervisor driver calculates size), " "format=device will be formatted (e.g. swap, ntfs, ...; optional), " "bootindex=integer used for ordering the boot disks " "(for image backed instances it is equal to 0, " "for others need to be specified), " "shutdown=shutdown behaviour (either preserve or remove, " "for local destination set to remove) and " "tag=device metadata tag (optional), " "volume_type=type of volume to create (either ID or name) when " "source is blank, image or snapshot and dest is volume (optional)." )) @utils.arg( '--swap', metavar="", default=None, help=_("Create and attach a local swap block device of MiB.")) @utils.arg( '--ephemeral', metavar="size=[,format=]", action='append', default=[], help=_("Create and attach a local ephemeral block device of GiB " "and format it to .")) @utils.arg( '--hint', action='append', dest='scheduler_hints', type=_key_value_pairing, default=[], metavar='', help=_("Send arbitrary key/value pairs to the scheduler for custom " "use.")) @utils.arg( '--nic', metavar="", action='append', dest='nics', default=[], start_version='2.0', end_version='2.31', help=_("Create a NIC on the server. " "Specify option multiple times to create multiple NICs. " "net-id: attach NIC to network with this UUID " "net-name: attach NIC to network with this name " "(either port-id or net-id or net-name must be provided), " "v4-fixed-ip: IPv4 fixed address for NIC (optional), " "v6-fixed-ip: IPv6 fixed address for NIC (optional), " "port-id: attach NIC to port with this UUID " "(either port-id or net-id must be provided).")) @utils.arg( '--nic', metavar="", action='append', dest='nics', default=[], start_version='2.32', end_version='2.36', help=_("Create a NIC on the server. " "Specify option multiple times to create multiple nics. " "net-id: attach NIC to network with this UUID " "net-name: attach NIC to network with this name " "(either port-id or net-id or net-name must be provided), " "v4-fixed-ip: IPv4 fixed address for NIC (optional), " "v6-fixed-ip: IPv6 fixed address for NIC (optional), " "port-id: attach NIC to port with this UUID " "tag: interface metadata tag (optional) " "(either port-id or net-id must be provided).")) @utils.arg( '--nic', metavar="", action='append', dest='nics', default=[], start_version='2.37', end_version='2.41', help=_("Create a NIC on the server. " "Specify option multiple times to create multiple nics unless " "using the special 'auto' or 'none' values. " "auto: automatically allocate network resources if none are " "available. This cannot be specified with any other nic value and " "cannot be specified multiple times. " "none: do not attach a NIC at all. This cannot be specified " "with any other nic value and cannot be specified multiple times. " "net-id: attach NIC to network with a specific UUID. " "net-name: attach NIC to network with this name " "(either port-id or net-id or net-name must be provided), " "v4-fixed-ip: IPv4 fixed address for NIC (optional), " "v6-fixed-ip: IPv6 fixed address for NIC (optional), " "port-id: attach NIC to port with this UUID " "(either port-id or net-id must be provided).")) @utils.arg( '--nic', metavar="", action='append', dest='nics', default=[], start_version='2.42', help=_("Create a NIC on the server. " "Specify option multiple times to create multiple nics unless " "using the special 'auto' or 'none' values. " "auto: automatically allocate network resources if none are " "available. This cannot be specified with any other nic value and " "cannot be specified multiple times. " "none: do not attach a NIC at all. This cannot be specified " "with any other nic value and cannot be specified multiple times. " "net-id: attach NIC to network with a specific UUID. " "net-name: attach NIC to network with this name " "(either port-id or net-id or net-name must be provided), " "v4-fixed-ip: IPv4 fixed address for NIC (optional), " "v6-fixed-ip: IPv6 fixed address for NIC (optional), " "port-id: attach NIC to port with this UUID " "tag: interface metadata tag (optional) " "(either port-id or net-id must be provided).")) @utils.arg( '--config-drive', metavar="", dest='config_drive', default=False, help=_("Enable config drive. The value must be a boolean value.")) @utils.arg( '--poll', dest='poll', action="store_true", default=False, help=_('Report the new server boot progress until it completes.')) @utils.arg( '--admin-pass', dest='admin_pass', metavar='', default=None, help=_('Admin password for the instance.')) @utils.arg( '--access-ip-v4', dest='access_ip_v4', metavar='', default=None, help=_('Alternative access IPv4 of the instance.')) @utils.arg( '--access-ip-v6', dest='access_ip_v6', metavar='', default=None, help=_('Alternative access IPv6 of the instance.')) @utils.arg( '--description', metavar='', dest='description', default=None, help=_('Description for the server.'), start_version="2.19") @utils.arg( '--tags', metavar='', default=None, help=_('Tags for the server.' 'Tags must be separated by commas: --tags '), start_version="2.52") @utils.arg( '--return-reservation-id', dest='return_reservation_id', action="store_true", default=False, help=_("Return a reservation id bound to created servers.")) @utils.arg( '--trusted-image-certificate-id', metavar='', action='append', dest='trusted_image_certificates', default=[], help=_('Trusted image certificate IDs used to validate certificates ' 'during the image signature verification process. ' 'Defaults to env[OS_TRUSTED_IMAGE_CERTIFICATE_IDS]. ' 'May be specified multiple times to pass multiple trusted image ' 'certificate IDs.'), start_version="2.63") @utils.arg( '--host', metavar='', dest='host', default=None, help=_('Requested host to create servers. Admin only by default.'), start_version="2.74") @utils.arg( '--hypervisor-hostname', metavar='', dest='hypervisor_hostname', default=None, help=_('Requested hypervisor hostname to create servers. Admin only by ' 'default.'), start_version="2.74") @utils.arg( '--hostname', help=_( 'Hostname for the instance. This sets the hostname stored in the ' 'metadata server: a utility such as cloud-init running on the guest ' 'is required to propagate these changes to the guest.' ), start_version='2.90') def do_boot(cs, args): """Boot a new server.""" boot_args, boot_kwargs = _boot(cs, args) server = cs.servers.create(*boot_args, **boot_kwargs) if boot_kwargs['reservation_id']: new_server = {'reservation_id': server} utils.print_dict(new_server) return else: _print_server(cs, args, server) if args.poll: _poll_for_status(cs.servers.get, server.id, 'building', ['active']) def _poll_for_status(poll_fn, obj_id, action, final_ok_states, poll_period=5, show_progress=True, status_field="status", silent=False): """Block while an action is being performed, periodically printing progress. """ def print_progress(progress): if show_progress: msg = (_('\rServer %(action)s... %(progress)s%% complete') % dict(action=action, progress=progress)) else: msg = _('\rServer %(action)s...') % dict(action=action) sys.stdout.write(msg) sys.stdout.flush() if not silent: print() while True: obj = poll_fn(obj_id) status = getattr(obj, status_field) if status: status = status.lower() progress = getattr(obj, 'progress', None) or 0 if status in final_ok_states: if not silent: print_progress(100) print(_("\nFinished")) break elif status == "error": if not silent: print(_("\nError %s server") % action) raise exceptions.ResourceInErrorState(obj) elif status == "deleted": if not silent: print(_("\nDeleted %s server") % action) raise exceptions.InstanceInDeletedState(obj.fault["message"]) if not silent: print_progress(progress) time.sleep(poll_period) def _expand_dict_attr(collection, attr): """Expand item attribute whose value is a dict. Take a collection of items where the named attribute is known to have a dictionary value and replace the named attribute with multiple attributes whose names are the keys of the dictionary namespaced with the original attribute name. """ for item in collection: field = getattr(item, attr) delattr(item, attr) for subkey in field.keys(): setattr(item, attr + ':' + subkey, field[subkey]) item.set_info(attr + ':' + subkey, field[subkey]) def _translate_keys(collection, convert): for item in collection: keys = item.__dict__.keys() item_dict = item.to_dict() for from_key, to_key in convert: if from_key in keys and to_key not in keys: setattr(item, to_key, item_dict[from_key]) item.set_info(to_key, item_dict[from_key]) def _translate_extended_states(collection): power_states = [ 'NOSTATE', # 0x00 'Running', # 0x01 '', # 0x02 'Paused', # 0x03 'Shutdown', # 0x04 '', # 0x05 'Crashed', # 0x06 'Suspended' # 0x07 ] for item in collection: try: setattr(item, 'power_state', power_states[getattr(item, 'power_state')]) except AttributeError: setattr(item, 'power_state', "N/A") try: getattr(item, 'task_state') except AttributeError: setattr(item, 'task_state', "N/A") item.set_info('power_state', item.power_state) item.set_info('task_state', item.task_state) def _translate_flavor_keys(collection): _translate_keys(collection, [('ram', 'memory_mib')]) def _print_flavor_extra_specs(flavor): try: return flavor.get_keys() except exceptions.NotFound: return "N/A" def _print_flavor_list(cs, flavors, show_extra_specs=False): _translate_flavor_keys(flavors) headers = [ 'ID', 'Name', 'Memory_MiB', 'Disk', 'Ephemeral', 'Swap', 'VCPUs', 'RXTX_Factor', 'Is_Public', ] formatters = {} if show_extra_specs: # Starting with microversion 2.61, extra specs are included # in the flavor details response. if cs.api_version < api_versions.APIVersion('2.61'): formatters = {'extra_specs': _print_flavor_extra_specs} headers.append('extra_specs') if cs.api_version >= api_versions.APIVersion('2.55'): headers.append('Description') utils.print_list(flavors, headers, formatters) @utils.arg( '--extra-specs', dest='extra_specs', action='store_true', default=False, help=_('Get extra-specs of each flavor.')) @utils.arg( '--all', dest='all', action='store_true', default=False, help=_('Display all flavors (Admin only).')) @utils.arg( '--marker', dest='marker', metavar='', default=None, help=_('The last flavor ID of the previous page; displays list of flavors' ' after "marker".')) @utils.arg( '--min-disk', dest='min_disk', metavar='', default=None, help=_('Filters the flavors by a minimum disk space, in GiB.')) @utils.arg( '--min-ram', dest='min_ram', metavar='', default=None, help=_('Filters the flavors by a minimum RAM, in MiB.')) @utils.arg( '--limit', dest='limit', metavar='', type=int, default=None, help=_("Maximum number of flavors to display. If limit is bigger than " "'CONF.api.max_limit' option of Nova API, limit " "'CONF.api.max_limit' will be used instead.")) @utils.arg( '--sort-key', dest='sort_key', metavar='', default=None, help=_('Flavors list sort key.')) @utils.arg( '--sort-dir', dest='sort_dir', metavar='', default=None, help=_('Flavors list sort direction.')) def do_flavor_list(cs, args): """Print a list of available 'flavors' (sizes of servers).""" if args.all: flavors = cs.flavors.list(is_public=None, min_disk=args.min_disk, min_ram=args.min_ram, sort_key=args.sort_key, sort_dir=args.sort_dir) else: flavors = cs.flavors.list(marker=args.marker, min_disk=args.min_disk, min_ram=args.min_ram, sort_key=args.sort_key, sort_dir=args.sort_dir, limit=args.limit) _print_flavor_list(cs, flavors, args.extra_specs) @utils.arg( 'flavor', metavar='', help=_("Name or ID of the flavor to delete.")) def do_flavor_delete(cs, args): """Delete a specific flavor""" flavor = _find_flavor(cs, args.flavor) cs.flavors.delete(flavor) @utils.arg( 'flavor', metavar='', help=_("Name or ID of flavor.")) def do_flavor_show(cs, args): """Show details about the given flavor.""" flavor = _find_flavor(cs, args.flavor) _print_flavor(flavor) @utils.arg( 'name', metavar='', help=_("Unique name of the new flavor.")) @utils.arg( 'id', metavar='', help=_("Unique ID of the new flavor." " Specifying 'auto' will generated a UUID for the ID.")) @utils.arg( 'ram', metavar='', help=_("Memory size in MiB.")) @utils.arg( 'disk', metavar='', help=_("Disk size in GiB.")) @utils.arg( '--ephemeral', metavar='', help=_("Ephemeral space size in GiB (default 0)."), default=0) @utils.arg( 'vcpus', metavar='', help=_("Number of vcpus")) @utils.arg( '--swap', metavar='', help=_("Additional swap space size in MiB (default 0)."), default=0) @utils.arg( '--rxtx-factor', metavar='', help=_("RX/TX factor (default 1)."), default=1.0) @utils.arg( '--is-public', metavar='', help=_("Make flavor accessible to the public (default true)."), type=lambda v: strutils.bool_from_string(v, True), default=True) @utils.arg( '--description', metavar='', help=_('A free form description of the flavor. Limited to 65535 ' 'characters in length. Only printable characters are allowed.'), start_version='2.55') def do_flavor_create(cs, args): """Create a new flavor.""" if cs.api_version >= api_versions.APIVersion('2.55'): description = args.description else: description = None f = cs.flavors.create(args.name, args.ram, args.vcpus, args.disk, args.id, args.ephemeral, args.swap, args.rxtx_factor, args.is_public, description) _print_flavor_list(cs, [f]) @api_versions.wraps('2.55') @utils.arg( 'flavor', metavar='', help=_('Name or ID of the flavor to update.')) @utils.arg( 'description', metavar='', help=_('A free form description of the flavor. Limited to 65535 ' 'characters in length. Only printable characters are allowed.')) def do_flavor_update(cs, args): """Update the description of an existing flavor.""" flavorid = _find_flavor(cs, args.flavor) flavor = cs.flavors.update(flavorid, args.description) _print_flavor_list(cs, [flavor]) @utils.arg( 'flavor', metavar='', help=_("Name or ID of flavor.")) @utils.arg( 'action', metavar='', choices=['set', 'unset'], help=_("Actions: 'set' or 'unset'.")) @utils.arg( 'metadata', metavar='', nargs='+', action='append', default=[], help=_('Extra_specs to set/unset (only key is necessary on unset).')) def do_flavor_key(cs, args): """Set or unset extra_spec for a flavor.""" flavor = _find_flavor(cs, args.flavor) keypair = _extract_metadata(args) if args.action == 'set': flavor.set_keys(keypair) elif args.action == 'unset': flavor.unset_keys(keypair.keys()) @utils.arg( '--flavor', metavar='', help=_("Filter results by flavor name or ID.")) def do_flavor_access_list(cs, args): """Print access information about the given flavor.""" if args.flavor: flavor = _find_flavor(cs, args.flavor) if flavor.is_public: raise exceptions.CommandError(_("Access list not available " "for public flavors.")) kwargs = {'flavor': flavor} else: raise exceptions.CommandError(_("Unable to get all access lists. " "Specify --flavor")) try: access_list = cs.flavor_access.list(**kwargs) except NotImplementedError as e: raise exceptions.CommandError("%s" % str(e)) columns = ['Flavor_ID', 'Tenant_ID'] utils.print_list(access_list, columns) @utils.arg( 'flavor', metavar='', help=_("Flavor name or ID to add access for the given tenant.")) @utils.arg( 'tenant', metavar='', help=_('Tenant ID to add flavor access for.')) def do_flavor_access_add(cs, args): """Add flavor access for the given tenant.""" flavor = _find_flavor(cs, args.flavor) access_list = cs.flavor_access.add_tenant_access(flavor, args.tenant) columns = ['Flavor_ID', 'Tenant_ID'] utils.print_list(access_list, columns) @utils.arg( 'flavor', metavar='', help=_("Flavor name or ID to remove access for the given tenant.")) @utils.arg( 'tenant', metavar='', help=_('Tenant ID to remove flavor access for.')) def do_flavor_access_remove(cs, args): """Remove flavor access for the given tenant.""" flavor = _find_flavor(cs, args.flavor) access_list = cs.flavor_access.remove_tenant_access(flavor, args.tenant) columns = ['Flavor_ID', 'Tenant_ID'] utils.print_list(access_list, columns) def _extract_metadata(args): metadata = {} for metadatum in args.metadata[0]: # Can only pass the key in on 'delete' # So this doesn't have to have '=' if metadatum.find('=') > -1: (key, value) = metadatum.split('=', 1) else: key = metadatum value = None metadata[key] = value return metadata def _print_image(image): info = image.to_dict() # ignore links, we don't need to present those info.pop('links', None) # try to replace a server entity to just an id server = info.pop('server', None) try: info['server'] = server['id'] except (KeyError, TypeError): pass # break up metadata and display each on its own row metadata = info.pop('metadata', {}) try: for key, value in metadata.items(): _key = 'metadata %s' % key info[_key] = value except AttributeError: pass utils.print_dict(info) def _print_flavor(flavor): info = flavor.to_dict() # ignore links, we don't need to present those info.pop('links') # Starting with microversion 2.61, extra specs are included # in the flavor details response. if 'extra_specs' not in info: info.update({"extra_specs": _print_flavor_extra_specs(flavor)}) utils.print_dict(info) @utils.arg( '--reservation-id', dest='reservation_id', metavar='', default=None, help=_('Only return servers that match reservation-id.')) @utils.arg( '--ip', dest='ip', metavar='', default=None, help=_('Search with regular expression match by IP address.')) @utils.arg( '--ip6', dest='ip6', metavar='', default=None, help=_('Search with regular expression match by IPv6 address.')) @utils.arg( '--name', dest='name', metavar='', default=None, help=_('Search with regular expression match by name.')) @utils.arg( '--status', dest='status', metavar='', default=None, help=_('Search by server status.')) @utils.arg( '--flavor', dest='flavor', metavar='', default=None, help=_('Search by flavor name or ID.')) @utils.arg( '--image', dest='image', metavar='', default=None, help=_('Search by image name or ID.')) @utils.arg( '--host', dest='host', metavar='', default=None, help=_('Search servers by hostname to which they are assigned (Admin ' 'only).')) @utils.arg( '--all-tenants', dest='all_tenants', metavar='<0|1>', nargs='?', type=int, const=1, default=int(strutils.bool_from_string( os.environ.get("ALL_TENANTS", 'false'), True)), help=_('Display information from all tenants (Admin only).')) @utils.arg( '--tenant', # nova db searches by project_id dest='tenant', metavar='', nargs='?', help=_('Display information from single tenant (Admin only).')) @utils.arg( '--user', dest='user', metavar='', nargs='?', help=_('Display information from single user (Admin only until ' 'microversion 2.82).')) @utils.arg( '--deleted', dest='deleted', action="store_true", default=False, help=_('Only display deleted servers (Admin only).')) @utils.arg( '--fields', default=None, metavar='', help=_('Comma-separated list of fields to display. ' 'Use the show command to see which fields are available.')) @utils.arg( '--minimal', dest='minimal', action="store_true", default=False, help=_('Get only UUID and name.')) @utils.arg( '--sort', dest='sort', metavar='[:]', help=_('Comma-separated list of sort keys and directions in the form ' 'of [:]. The direction defaults to descending if ' 'not specified.')) @utils.arg( '--marker', dest='marker', metavar='', default=None, help=_('The last server UUID of the previous page; displays list of ' 'servers after "marker".')) @utils.arg( '--limit', dest='limit', metavar='', type=int, default=None, help=_("Maximum number of servers to display. If limit == -1, all servers " "will be displayed. If limit is bigger than 'CONF.api.max_limit' " "option of Nova API, limit 'CONF.api.max_limit' will be used " "instead.")) @utils.arg( '--availability-zone', dest='availability_zone', metavar='', default=None, help=_('Display servers based on their availability zone (Admin only ' 'until microversion 2.82).')) @utils.arg( '--key-name', dest='key_name', metavar='', default=None, help=_('Display servers based on their keypair name (Admin only until ' 'microversion 2.82).')) @utils.arg( '--config-drive', action='store_true', group='config_drive', default=None, help=_('Display servers that have a config drive attached. (Admin only ' 'until microversion 2.82).')) # NOTE(gibi): this won't actually do anything until bug 1871409 is fixed # and the REST API is cleaned up regarding the values of config_drive @utils.arg( '--no-config-drive', action='store_false', group='config_drive', help=_('Display servers that do not have a config drive attached (Admin ' 'only until microversion 2.82)')) @utils.arg( '--progress', dest='progress', metavar='', default=None, help=_('Display servers based on their progress value (Admin only until ' 'microversion 2.82).')) @utils.arg( '--vm-state', dest='vm_state', metavar='', default=None, help=_('Display servers based on their vm_state value (Admin only until ' 'microversion 2.82).')) @utils.arg( '--task-state', dest='task_state', metavar='', default=None, help=_('Display servers based on their task_state value (Admin only until ' 'microversion 2.82).')) # TODO(gibi): this is now only work with the integer power state values. # Later on we can extend this to accept the string values of the power state # and translate it to integers towards the REST API. @utils.arg( '--power-state', dest='power_state', metavar='', default=None, help=_('Display servers based on their power_state value (Admin only ' 'until microversion 2.82).')) @utils.arg( '--changes-since', dest='changes_since', metavar='', default=None, help=_("List only servers changed later or equal to a certain point of " "time. The provided time should be an ISO 8061 formatted time. " "e.g. 2016-03-04T06:27:59Z .")) @utils.arg( '--changes-before', dest='changes_before', metavar='', default=None, help=_("List only servers changed earlier or equal to a certain point of " "time. The provided time should be an ISO 8061 formatted time. " "e.g. 2016-03-04T06:27:59Z ."), start_version="2.66") @utils.arg( '--tags', dest='tags', metavar='', default=None, help=_("The given tags must all be present for a server to be included in " "the list result. Boolean expression in this case is 't1 AND t2'. " "Tags must be separated by commas: --tags "), start_version="2.26") @utils.arg( '--tags-any', dest='tags-any', metavar='', default=None, help=_("If one of the given tags is present the server will be included " "in the list result. Boolean expression in this case is " "'t1 OR t2'. Tags must be separated by commas: " "--tags-any "), start_version="2.26") @utils.arg( '--not-tags', dest='not-tags', metavar='', default=None, help=_("Only the servers that do not have any of the given tags will " "be included in the list results. Boolean expression in this case " "is 'NOT(t1 AND t2)'. Tags must be separated by commas: " "--not-tags "), start_version="2.26") @utils.arg( '--not-tags-any', dest='not-tags-any', metavar='', default=None, help=_("Only the servers that do not have at least one of the given tags " "will be included in the list result. Boolean expression in this " "case is 'NOT(t1 OR t2)'. Tags must be separated by commas: " "--not-tags-any "), start_version="2.26") @utils.arg( '--locked', dest='locked', metavar='', default=None, help=_("Display servers based on their locked value. A value must be " "specified; eg. 'true' will list only locked servers and 'false' " "will list only unlocked servers."), start_version="2.73") def do_list(cs, args): """List servers.""" imageid = None flavorid = None if args.image: imageid = _find_image(cs, args.image).id if args.flavor: flavorid = _find_flavor(cs, args.flavor).id # search by tenant or user only works with all_tenants if args.tenant: args.all_tenants = 1 search_opts = { 'all_tenants': args.all_tenants, 'reservation_id': args.reservation_id, 'ip': args.ip, 'ip6': args.ip6, 'name': args.name, 'image': imageid, 'flavor': flavorid, 'status': args.status, 'tenant_id': args.tenant, 'user_id': args.user, 'host': args.host, 'deleted': args.deleted, 'changes-since': args.changes_since, 'availability_zone': args.availability_zone, 'config_drive': args.config_drive, 'key_name': args.key_name, 'progress': args.progress, 'vm_state': args.vm_state, 'task_state': args.task_state, 'power_state': args.power_state, } for arg in ('tags', "tags-any", 'not-tags', 'not-tags-any'): if arg in args: search_opts[arg] = getattr(args, arg) filters = {'security_groups': utils.format_security_groups} # In microversion 2.47 we started embedding flavor info in server details. have_embedded_flavor_info = ( cs.api_version >= api_versions.APIVersion('2.47')) # If we don't have embedded flavor info then we only report the flavor id # rather than looking up the rest of the information. if not have_embedded_flavor_info: filters['flavor'] = lambda f: f['id'] id_col = 'ID' detailed = not args.minimal sort_keys = [] sort_dirs = [] if args.sort: for sort in args.sort.split(','): sort_key, _sep, sort_dir = sort.partition(':') if not sort_dir: sort_dir = 'desc' elif sort_dir not in ('asc', 'desc'): raise exceptions.CommandError(_( 'Unknown sort direction: %s') % sort_dir) sort_keys.append(sort_key) sort_dirs.append(sort_dir) if search_opts['changes-since']: try: timeutils.parse_isotime(search_opts['changes-since']) except ValueError: raise exceptions.CommandError(_('Invalid changes-since value: %s') % search_opts['changes-since']) # In microversion 2.66 we added ``changes-before`` option # in server details. have_added_changes_before = ( cs.api_version >= api_versions.APIVersion('2.66')) if have_added_changes_before and args.changes_before: search_opts['changes-before'] = args.changes_before try: timeutils.parse_isotime(search_opts['changes-before']) except ValueError: raise exceptions.CommandError(_('Invalid changes-before value: %s') % search_opts['changes-before']) # In microversion 2.73 we added ``locked`` option in server details. have_added_locked = cs.api_version >= api_versions.APIVersion('2.73') if have_added_locked and args.locked: search_opts['locked'] = args.locked servers = cs.servers.list(detailed=detailed, search_opts=search_opts, sort_keys=sort_keys, sort_dirs=sort_dirs, marker=args.marker, limit=args.limit) convert = [('OS-EXT-SRV-ATTR:host', 'host'), ('OS-EXT-STS:task_state', 'task_state'), ('OS-EXT-SRV-ATTR:instance_name', 'instance_name'), ('OS-EXT-STS:power_state', 'power_state'), ('hostId', 'host_id')] _translate_keys(servers, convert) _translate_extended_states(servers) formatters = {} cols = [] fmts = {} # For detailed lists, if we have embedded flavor information then replace # the "flavor" attribute with more detailed information. if detailed and have_embedded_flavor_info: if cs.api_version >= api_versions.APIVersion('2.69'): # NOTE(tssurya): From 2.69, we will have the key 'flavor' missing # in the server response during infrastructure failure situations. # For those servers with partial constructs we just skip the # process of expanding the flavor information. servers_final = [] for server in servers: if hasattr(server, 'flavor'): servers_final.append(server) _expand_dict_attr(servers_final, 'flavor') else: _expand_dict_attr(servers, 'flavor') if servers: cols, fmts = _get_list_table_columns_and_formatters( args.fields, servers, exclude_fields=('id',), filters=filters) if args.minimal: columns = [ id_col, 'Name'] elif cols: columns = [id_col] + cols formatters.update(fmts) else: columns = [ id_col, 'Name', 'Status', 'Task State', 'Power State', 'Networks' ] # If getting the data for all tenants, print # Tenant ID as well if search_opts['all_tenants']: columns.insert(2, 'Tenant ID') if search_opts['changes-since'] or search_opts.get('changes-before'): columns.append('Updated') formatters['Networks'] = utils.format_servers_list_networks sortby_index = 1 if args.sort: sortby_index = None utils.print_list(servers, columns, formatters, sortby_index=sortby_index) 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. If exclude_fields is specified, any field both in fields and exclude_fields will be ignored. :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: novaclient.exceptions.CommandError """ if not fields: return [], {} if not objs: obj = None elif isinstance(objs, list): obj = objs[0] else: obj = objs columns = [] formatters = {} existing_fields = set() non_existent_fields = [] exclude_fields = set(exclude_fields) # NOTE(ttsiouts): Bug #1733917. Validating the fields using the keys of # the Resource.to_dict(). Adding also the 'networks' field. if obj: obj_dict = obj.to_dict() existing_fields = set(['networks']) | set(obj_dict.keys()) for field in fields.split(','): if field not in existing_fields: non_existent_fields.append(field) continue if field in exclude_fields: continue field_title, formatter = utils.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 @utils.arg( '--hard', dest='reboot_type', action='store_const', const=servers.REBOOT_HARD, default=servers.REBOOT_SOFT, help=_('Perform a hard reboot (instead of a soft one). ' 'Note: Ironic does not currently support soft reboot; ' 'consequently, bare metal nodes will always do a hard ' 'reboot, regardless of the use of this option.')) @utils.arg( 'server', metavar='', nargs='+', help=_('Name or ID of server(s).')) @utils.arg( '--poll', dest='poll', action="store_true", default=False, help=_('Poll until reboot is complete.')) def do_reboot(cs, args): """Reboot a server.""" servers = [_find_server(cs, s) for s in args.server] utils.do_action_on_many( lambda s: s.reboot(args.reboot_type), servers, _("Request to reboot server %s has been accepted."), _("Unable to reboot the specified server(s).")) if args.poll: utils.do_action_on_many( lambda s: _poll_for_status(cs.servers.get, s.id, 'rebooting', ['active'], show_progress=False), servers, _("Wait for server %s reboot."), _("Wait for specified server(s) failed.")) @utils.arg('server', metavar='', help=_('Name or ID of server.')) @utils.arg('image', metavar='', help=_("Name or ID of new image.")) @utils.arg( '--rebuild-password', dest='rebuild_password', metavar='', default=False, help=_("Set the provided admin password on the rebuilt server.")) @utils.arg( '--poll', dest='poll', action="store_true", default=False, help=_('Report the server rebuild progress until it completes.')) @utils.arg( '--minimal', dest='minimal', action="store_true", default=False, help=_('Skips flavor/image lookups when showing servers.')) @utils.arg( '--preserve-ephemeral', action="store_true", default=False, help=_('Preserve the default ephemeral storage partition on rebuild.')) @utils.arg( '--name', metavar='', default=None, help=_('Name for the new server.')) @utils.arg( '--description', metavar='', dest='description', default=None, help=_('New description for the server.'), start_version="2.19") @utils.arg( '--meta', metavar="", action='append', default=[], help=_("Record arbitrary key/value metadata to /meta_data.json " "on the metadata server. Can be specified multiple times.")) @utils.arg( '--file', metavar="", action='append', dest='files', default=[], help=_("Store arbitrary files from locally to " "on the new server. More files can be injected using multiple " "'--file' options. You may store up to 5 files by default. " "The maximum number of files is specified by the 'Personality' " "limit reported by the 'nova limits' command."), start_version='2.0', end_version='2.56') @utils.arg( '--key-name', metavar='', default=None, help=_("Keypair name to set in the server. " "Cannot be specified with the '--key-unset' option."), start_version='2.54') @utils.arg( '--key-unset', action='store_true', default=False, help=_("Unset keypair in the server. " "Cannot be specified with the '--key-name' option."), start_version='2.54') @utils.arg( '--user-data', default=None, metavar='', help=_("User data file to pass to be exposed by the metadata server."), start_version='2.57') @utils.arg( '--user-data-unset', action='store_true', default=False, help=_("Unset user_data in the server. Cannot be specified with the " "'--user-data' option."), start_version='2.57') @utils.arg( '--trusted-image-certificate-id', metavar='', action='append', dest='trusted_image_certificates', default=[], help=_('Trusted image certificate IDs used to validate certificates ' 'during the image signature verification process. ' 'Defaults to env[OS_TRUSTED_IMAGE_CERTIFICATE_IDS]. ' 'May be specified multiple times to pass multiple trusted image ' 'certificate IDs.'), start_version="2.63") @utils.arg( '--trusted-image-certificates-unset', action='store_true', default=False, help=_("Unset trusted_image_certificates in the server. Cannot be " "specified with the '--trusted-image-certificate-id' option."), start_version="2.63") @utils.arg( '--hostname', help=_( 'New hostname for the instance. This only updates the hostname ' 'stored in the metadata server: a utility running on the guest ' 'is required to propagate these changes to the guest.' ), start_version='2.90') def do_rebuild(cs, args): """Shutdown, re-image, and re-boot a server.""" server = _find_server(cs, args.server) image = _find_image(cs, args.image) kwargs = {'preserve_ephemeral': args.preserve_ephemeral, 'name': args.name, 'meta': _meta_parsing(args.meta)} if args.rebuild_password is not False: kwargs['password'] = args.rebuild_password else: kwargs['password'] = None if 'description' in args: kwargs['description'] = args.description # 2.57 deprecates the --file option and adds the --user-data and # --user-data-unset options. if cs.api_version < api_versions.APIVersion('2.57'): files = {} for f in args.files: try: dst, src = f.split('=', 1) with open(src, 'r') as s: files[dst] = s.read() except IOError as e: raise exceptions.CommandError( _("Can't open '%(src)s': %(exc)s") % {'src': src, 'exc': e}) except ValueError: raise exceptions.CommandError( _("Invalid file argument '%s'. " "File arguments must be of the " "form '--file '") % f) kwargs['files'] = files else: if args.user_data_unset: kwargs['userdata'] = None if args.user_data: raise exceptions.CommandError( _("Cannot specify '--user-data-unset' with " "'--user-data'.")) elif args.user_data: try: with open(args.user_data) as f: kwargs['userdata'] = f.read() except IOError as e: raise exceptions.CommandError( _("Can't open '%(user_data)s': %(exc)s") % { 'user_data': args.user_data, 'exc': e, } ) if cs.api_version >= api_versions.APIVersion('2.54'): if args.key_unset: kwargs['key_name'] = None if args.key_name: raise exceptions.CommandError( _("Cannot specify '--key-unset' with '--key-name'.")) elif args.key_name: kwargs['key_name'] = args.key_name if cs.api_version >= api_versions.APIVersion('2.63'): # First determine if the user specified anything via the command line # or the environment variable. trusted_image_certificates = None if ('trusted_image_certificates' in args and args.trusted_image_certificates): trusted_image_certificates = args.trusted_image_certificates elif utils.env('OS_TRUSTED_IMAGE_CERTIFICATE_IDS'): trusted_image_certificates = utils.env( 'OS_TRUSTED_IMAGE_CERTIFICATE_IDS').split(',') if args.trusted_image_certificates_unset: kwargs['trusted_image_certificates'] = None # Check for conflicts in option usage. if trusted_image_certificates: raise exceptions.CommandError( _("Cannot specify '--trusted-image-certificates-unset' " "with '--trusted-image-certificate-id' or with " "OS_TRUSTED_IMAGE_CERTIFICATE_IDS env variable set.")) elif trusted_image_certificates: # Only specify the kwarg if there is a value specified to avoid # confusion with unsetting the value. kwargs['trusted_image_certificates'] = trusted_image_certificates elif utils.env('OS_TRUSTED_IMAGE_CERTIFICATE_IDS'): raise exceptions.UnsupportedAttribute( "OS_TRUSTED_IMAGE_CERTIFICATE_IDS", "2.63") if 'hostname' in args and args.hostname is not None: kwargs['hostname'] = args.hostname server = server.rebuild(image, **kwargs) _print_server(cs, args, server) if args.poll: _poll_for_status(cs.servers.get, server.id, 'rebuilding', ['active']) @utils.arg( 'server', metavar='', help=_('Name (old name) or ID of server.')) @utils.arg( '--name', metavar='', dest='name', default=None, help=_('New name for the server.')) @utils.arg( '--description', metavar='', dest='description', default=None, help=_('New description for the server. If it equals to empty string ' '(i.g. ""), the server description will be removed.'), start_version="2.19") @utils.arg( '--hostname', help=_( 'New hostname for the instance. This only updates the hostname ' 'stored in the metadata server: a utility running on the guest ' 'is required to propagate these changes to the guest.' ), start_version='2.90') def do_update(cs, args): """Update the name or the description for a server.""" update_kwargs = {} if args.name: update_kwargs["name"] = args.name if "description" in args and args.description is not None: update_kwargs["description"] = args.description if "hostname" in args and args.hostname is not None: update_kwargs["hostname"] = args.hostname _find_server(cs, args.server).update(**update_kwargs) @utils.arg('server', metavar='', help=_('Name or ID of server.')) @utils.arg( 'flavor', metavar='', help=_("Name or ID of new flavor.")) @utils.arg( '--poll', dest='poll', action="store_true", default=False, help=_('Report the server resize progress until it completes.')) def do_resize(cs, args): """Resize a server.""" server = _find_server(cs, args.server) flavor = _find_flavor(cs, args.flavor) server.resize(flavor) if args.poll: _poll_for_status(cs.servers.get, server.id, 'resizing', ['active', 'verify_resize']) @utils.arg('server', metavar='', help=_('Name or ID of server.')) def do_resize_confirm(cs, args): """Confirm a previous resize.""" _find_server(cs, args.server).confirm_resize() @utils.arg('server', metavar='', help=_('Name or ID of server.')) def do_resize_revert(cs, args): """Revert a previous resize (and return to the previous VM).""" _find_server(cs, args.server).revert_resize() @utils.arg('server', metavar='', help=_('Name or ID of server.')) @utils.arg( '--host', metavar='', default=None, help=_('Destination host name.'), start_version='2.56') @utils.arg( '--poll', dest='poll', action="store_true", default=False, help=_('Report the server migration progress until it completes.')) def do_migrate(cs, args): """Migrate a server.""" update_kwargs = {} if 'host' in args and args.host: update_kwargs['host'] = args.host server = _find_server(cs, args.server) server.migrate(**update_kwargs) if args.poll: _poll_for_status(cs.servers.get, server.id, 'migrating', ['active', 'verify_resize']) @utils.arg('server', metavar='', help=_('Name or ID of server.')) def do_pause(cs, args): """Pause a server.""" _find_server(cs, args.server).pause() @utils.arg('server', metavar='', help=_('Name or ID of server.')) def do_unpause(cs, args): """Unpause a server.""" _find_server(cs, args.server).unpause() @utils.arg( '--all-tenants', action='store_const', const=1, default=0, help=_('Stop server(s) in another tenant by name (Admin only).')) @utils.arg( 'server', metavar='', nargs='+', help=_('Name or ID of server(s).')) def do_stop(cs, args): """Stop the server(s).""" find_args = {'all_tenants': args.all_tenants} utils.do_action_on_many( lambda s: _find_server(cs, s, **find_args).stop(), args.server, _("Request to stop server %s has been accepted."), _("Unable to stop the specified server(s).")) @utils.arg( '--all-tenants', action='store_const', const=1, default=0, help=_('Start server(s) in another tenant by name (Admin only).')) @utils.arg( 'server', metavar='', nargs='+', help=_('Name or ID of server(s).')) def do_start(cs, args): """Start the server(s).""" find_args = {'all_tenants': args.all_tenants} utils.do_action_on_many( lambda s: _find_server(cs, s, **find_args).start(), args.server, _("Request to start server %s has been accepted."), _("Unable to start the specified server(s).")) # From microversion 2.73, we can specify a reason for locking the server. @utils.arg('server', metavar='', help=_('Name or ID of server.')) @utils.arg( '--reason', metavar='', help=_('Reason for locking the server.'), start_version='2.73') def do_lock(cs, args): """Lock a server. A normal (non-admin) user will not be able to execute actions on a locked server. """ update_kwargs = {} if 'reason' in args and args.reason is not None: update_kwargs['reason'] = args.reason server = _find_server(cs, args.server) server.lock(**update_kwargs) @utils.arg('server', metavar='', help=_('Name or ID of server.')) def do_unlock(cs, args): """Unlock a server.""" _find_server(cs, args.server).unlock() @utils.arg('server', metavar='', help=_('Name or ID of server.')) def do_suspend(cs, args): """Suspend a server.""" _find_server(cs, args.server).suspend() @utils.arg('server', metavar='', help=_('Name or ID of server.')) def do_resume(cs, args): """Resume a server.""" _find_server(cs, args.server).resume() @utils.arg('server', metavar='', help=_('Name or ID of server.')) @utils.arg( '--password', metavar='', dest='password', help=_('The admin password to be set in the rescue environment.')) @utils.arg( '--image', metavar='', dest='image', help=_('The image to rescue with.')) def do_rescue(cs, args): """Reboots a server into rescue mode, which starts the machine from either the initial image or a specified image, attaching the current boot disk as secondary. """ kwargs = {} if args.image: kwargs['image'] = _find_image(cs, args.image) if args.password: kwargs['password'] = args.password utils.print_dict(_find_server(cs, args.server).rescue(**kwargs)[1]) @utils.arg('server', metavar='', help=_('Name or ID of server.')) def do_unrescue(cs, args): """Restart the server from normal boot disk again.""" _find_server(cs, args.server).unrescue() @utils.arg('server', metavar='', help=_('Name or ID of server.')) def do_shelve(cs, args): """Shelve a server.""" _find_server(cs, args.server).shelve() @utils.arg('server', metavar='', help=_('Name or ID of server.')) def do_shelve_offload(cs, args): """Remove a shelved server from the compute node.""" _find_server(cs, args.server).shelve_offload() @utils.arg('server', metavar='', help=_('Name or ID of server.')) @utils.arg( '--availability-zone', metavar='', default=None, dest='availability_zone', help=_('Name of the availability zone in which to unshelve a ' 'SHELVED_OFFLOADED server.'), start_version='2.77') def do_unshelve(cs, args): """Unshelve a server.""" update_kwargs = {} # Microversion >= 2.77 will support user to specify an # availability_zone to unshelve a shelve offloaded server. if cs.api_version >= api_versions.APIVersion('2.77'): if 'availability_zone' in args and args.availability_zone is not None: update_kwargs['availability_zone'] = args.availability_zone server = _find_server(cs, args.server) server.unshelve(**update_kwargs) @utils.arg('server', metavar='', help=_('Name or ID of server.')) def do_diagnostics(cs, args): """Retrieve server diagnostics.""" server = _find_server(cs, args.server) utils.print_dict(cs.servers.diagnostics(server)[1], wrap=80) @api_versions.wraps("2.78") @utils.arg('server', metavar='', help=_('Name or ID of server.')) def do_server_topology(cs, args): """Retrieve server topology.""" server = _find_server(cs, args.server) # This prints a dict with only two properties: nodes and pagesize_kb # nodes is a list of dicts so it does not print very well, it's just a # json blob in the output. utils.print_dict(cs.servers.topology(server), wrap=80) @utils.arg( 'server', metavar='', help=_('Name or ID of a server for which the network cache should ' 'be refreshed from neutron (Admin only).')) def do_refresh_network(cs, args): """Refresh server network information.""" server = _find_server(cs, args.server) cs.server_external_events.create([{'server_uuid': server.id, 'name': 'network-changed'}]) @utils.arg('server', metavar='', help=_('Name or ID of server.')) def do_set_password(cs, args): """ Change the admin password for a server. """ server = _find_server(cs, args.server) p1 = getpass.getpass('New password: ') p2 = getpass.getpass('Again: ') if p1 != p2: raise exceptions.CommandError(_("Passwords do not match.")) server.change_password(p1) @utils.arg('server', metavar='', help=_('Name or ID of server.')) @utils.arg('name', metavar='', help=_('Name of snapshot.')) @utils.arg( '--metadata', metavar="", action='append', default=[], help=_("Record arbitrary key/value metadata to /meta_data.json " "on the metadata server. Can be specified multiple times.")) @utils.arg( '--show', dest='show', action="store_true", default=False, help=_('Print image info.')) @utils.arg( '--poll', dest='poll', action="store_true", default=False, help=_('Report the snapshot progress and poll until image creation is ' 'complete.')) def do_image_create(cs, args): """Create a new image by taking a snapshot of a running server.""" server = _find_server(cs, args.server) meta = _meta_parsing(args.metadata) or None image_uuid = cs.servers.create_image(server, args.name, meta) if args.poll: _poll_for_status(cs.glance.find_image, image_uuid, 'snapshotting', ['active']) # NOTE(sirp): A race-condition exists between when the image finishes # uploading and when the servers's `task_state` is cleared. To account # for this, we need to poll a second time to ensure the `task_state` is # cleared before returning, ensuring that a snapshot taken immediately # after this function returns will succeed. # # A better long-term solution will be to separate 'snapshotting' and # 'image-uploading' in Nova and clear the task-state once the VM # snapshot is complete but before the upload begins. task_state_field = "OS-EXT-STS:task_state" if hasattr(server, task_state_field): _poll_for_status(cs.servers.get, server.id, 'image_snapshot', [None], status_field=task_state_field, show_progress=False, silent=True) if args.show: _print_image(_find_image(cs, image_uuid)) @utils.arg('server', metavar='', help=_('Name or ID of server.')) @utils.arg('name', metavar='', help=_('Name of the backup image.')) @utils.arg( 'backup_type', metavar='', help=_('The backup type, like "daily" or "weekly".')) @utils.arg( 'rotation', metavar='', help=_('Int parameter representing how many backups to keep ' 'around.')) def do_backup(cs, args): """Backup a server by creating a 'backup' type snapshot.""" result = _find_server(cs, args.server).backup(args.name, args.backup_type, args.rotation) # Microversion >= 2.45 will return a DictWithMeta that has the image_id # in it for the backup snapshot image. if cs.api_version >= api_versions.APIVersion('2.45'): _print_image(_find_image(cs, result['image_id'])) @utils.arg( 'server', metavar='', help=_("Name or ID of server.")) @utils.arg( 'action', metavar='', choices=['set', 'delete'], help=_("Actions: 'set' or 'delete'.")) @utils.arg( 'metadata', metavar='', nargs='+', action='append', default=[], help=_('Metadata to set or delete (only key is necessary on delete).')) def do_meta(cs, args): """Set or delete metadata on a server.""" server = _find_server(cs, args.server) metadata = _extract_metadata(args) if args.action == 'set': cs.servers.set_meta(server, metadata) elif args.action == 'delete': cs.servers.delete_meta(server, sorted(metadata.keys(), reverse=True)) def _print_server(cs, args, server=None, wrap=0): # By default when searching via name we will do a # findall(name=blah) and due a REST /details which is not the same # as a .get() and doesn't get the information about flavors and # images. This fix it as we redo the call with the id which does a # .get() to get all information. if not server: server = _find_server(cs, args.server) minimal = getattr(args, "minimal", False) try: networks = server.networks except Exception as e: raise exceptions.CommandError(str(e)) info = server.to_dict() for network_label, address_list in networks.items(): info['%s network' % network_label] = ', '.join(address_list) flavor = info.get('flavor', {}) if cs.api_version >= api_versions.APIVersion('2.47'): # The "flavor" field is a JSON representation of a dict containing the # flavor information used at boot. if minimal: # To retain something similar to the previous behaviour, keep the # 'flavor' field name but just output the original name. info['flavor'] = flavor['original_name'] else: # Replace the "flavor" field with individual namespaced fields. del info['flavor'] for key in flavor.keys(): info['flavor:' + key] = flavor[key] else: # Prior to microversion 2.47 we just have the ID of the flavor so we # need to retrieve the flavor information (which may have changed # since the instance was booted). flavor_id = flavor.get('id', '') if minimal: info['flavor'] = flavor_id else: try: info['flavor'] = '%s (%s)' % (_find_flavor(cs, flavor_id).name, flavor_id) except Exception: info['flavor'] = '%s (%s)' % (_("Flavor not found"), flavor_id) if 'security_groups' in info: # when we have multiple nics the info will include the # security groups N times where N == number of nics. Be nice # and only display it once. info['security_groups'] = ', '.join( sorted(set(group['name'] for group in info['security_groups']))) image = info.get('image', {}) if image: image_id = image.get('id', '') if minimal: info['image'] = image_id else: try: info['image'] = '%s (%s)' % (_find_image(cs, image_id).name, image_id) except Exception: info['image'] = '%s (%s)' % (_("Image not found"), image_id) else: # Booted from volume info['image'] = _("Attempt to boot from volume - no image supplied") info.pop('links', None) info.pop('addresses', None) info.pop('OS-EXT-SRV-ATTR:user_data', None) utils.print_dict(info, wrap=wrap) @utils.arg( '--minimal', dest='minimal', action="store_true", default=False, help=_('Skips flavor/image lookups when showing servers.')) @utils.arg('server', metavar='', help=_('Name or ID of server.')) @utils.arg( '--wrap', dest='wrap', metavar='', type=int, default=0, help=_('Wrap the output to a specified length, or 0 to disable.')) def do_show(cs, args): """Show details about the given server.""" _print_server(cs, args, wrap=args.wrap) @utils.arg( '--all-tenants', action='store_const', const=1, default=0, help=_('Delete server(s) in another tenant by name (Admin only).')) @utils.arg( 'server', metavar='', nargs='+', help=_('Name or ID of server(s).')) def do_delete(cs, args): """Immediately shut down and delete specified server(s).""" find_args = {'all_tenants': args.all_tenants} utils.do_action_on_many( lambda s: _find_server(cs, s, **find_args).delete(), args.server, _("Request to delete server %s has been accepted."), _("Unable to delete the specified server(s).")) def _find_server(cs, server, raise_if_notfound=True, **find_args): """Get a server by name or ID. :param cs: NovaClient's instance :param server: identifier of server :param raise_if_notfound: raise an exception if server is not found :param find_args: argument to search server """ if raise_if_notfound: return utils.find_resource(cs.servers, server, **find_args) else: try: return utils.find_resource(cs.servers, server, wrap_exception=False) except exceptions.NoUniqueMatch as e: raise exceptions.CommandError(str(e)) except exceptions.NotFound: # The server can be deleted return server def _find_image(cs, image): """Get an image by name or ID.""" try: return cs.glance.find_image(image) except (exceptions.NotFound, exceptions.NoUniqueMatch) as e: raise exceptions.CommandError(str(e)) def _find_images(cs, images): """Get images by name or ID.""" try: return cs.glance.find_images(images) except (exceptions.NotFound, exceptions.NoUniqueMatch) as e: raise exceptions.CommandError(str(e)) def _find_flavor(cs, flavor): """Get a flavor by name, ID, or RAM size.""" try: return utils.find_resource(cs.flavors, flavor, is_public=None) except exceptions.NotFound: return cs.flavors.find(ram=flavor) def _find_network_id(cs, net_name): """Get unique network ID from network name from neutron""" try: return cs.neutron.find_network(net_name).id except (exceptions.NotFound, exceptions.NoUniqueMatch) as e: raise exceptions.CommandError(str(e)) def _print_volume(volume): utils.print_dict(volume.to_dict()) def _translate_availability_zone_keys(collection): _translate_keys(collection, [('zoneName', 'name'), ('zoneState', 'status')]) def _translate_volume_attachments_keys(collection): _translate_keys(collection, [('serverId', 'server_id'), ('volumeId', 'volume_id')]) @utils.arg( 'server', metavar='', help=_('Name or ID of server.')) @utils.arg( 'volume', metavar='', help=_('ID of the volume to attach.')) @utils.arg( 'device', metavar='', default=None, nargs='?', help=_('Name of the device e.g. /dev/vdb. ' 'Use "auto" for autoassign (if supported). ' 'Libvirt driver will use default device name.')) @utils.arg( '--tag', metavar='', default=None, help=_('Tag for the attached volume.'), start_version="2.49") @utils.arg( '--delete-on-termination', action='store_true', default=False, help=_('Specify if the attached volume should be deleted ' 'when the server is destroyed.'), start_version="2.79") def do_volume_attach(cs, args): """Attach a volume to a server.""" if args.device == 'auto': args.device = None update_kwargs = {} if 'tag' in args and args.tag: update_kwargs['tag'] = args.tag if 'delete_on_termination' in args and args.delete_on_termination: update_kwargs['delete_on_termination'] = args.delete_on_termination volume = cs.volumes.create_server_volume(_find_server(cs, args.server).id, args.volume, args.device, **update_kwargs) _print_volume(volume) @utils.arg( 'server', metavar='', help=_('Name or ID of server.')) @utils.arg( 'src_volume', metavar='', help=_('ID of the source (original) volume.')) @utils.arg( 'dest_volume', metavar='', help=_('ID of the destination volume.')) @utils.arg( '--delete-on-termination', default=None, group='delete_on_termination', action='store_true', help=_('Specify that the volume should be deleted ' 'when the server is destroyed.'), start_version='2.85') @utils.arg( '--no-delete-on-termination', group='delete_on_termination', action='store_false', help=_('Specify that the volume should not be deleted ' 'when the server is destroyed.'), start_version='2.85') def do_volume_update(cs, args): """Update the attachment on the server. If dest_volume is the same as the src_volume then the command migrates the data from the attached volume to the specified available volume and swaps out the active attachment to the new volume. Otherwise it only updates the parameters of the existing attachment. """ kwargs = dict() if (cs.api_version >= api_versions.APIVersion('2.85') and args.delete_on_termination is not None): kwargs['delete_on_termination'] = args.delete_on_termination cs.volumes.update_server_volume(_find_server(cs, args.server).id, args.src_volume, args.dest_volume, **kwargs) @utils.arg( 'server', metavar='', help=_('Name or ID of server.')) @utils.arg( 'attachment_id', metavar='', help=_('ID of the volume to detach.')) def do_volume_detach(cs, args): """Detach a volume from a server.""" cs.volumes.delete_server_volume(_find_server(cs, args.server).id, args.attachment_id) @utils.arg( 'server', metavar='', help=_('Name or ID of server.')) def do_volume_attachments(cs, args): """List all the volumes attached to a server.""" volumes = cs.volumes.get_server_volumes(_find_server(cs, args.server).id) _translate_volume_attachments_keys(volumes) # Microversion >= 2.70 returns the tag value. fields = ['ID', 'DEVICE', 'SERVER ID', 'VOLUME ID'] if cs.api_version >= api_versions.APIVersion('2.89'): fields.remove('ID') fields.append('ATTACHMENT ID') fields.append('BDM UUID') if cs.api_version >= api_versions.APIVersion('2.70'): fields.append('TAG') # Microversion >= 2.79 returns the delete_on_termination value. if cs.api_version >= api_versions.APIVersion('2.79'): fields.append('DELETE ON TERMINATION') utils.print_list(volumes, fields) @api_versions.wraps('2.0', '2.5') def console_dict_accessor(cs, data): return data['console'] @api_versions.wraps('2.6') def console_dict_accessor(cs, data): return data['remote_console'] class Console(object): def __init__(self, console_dict): self.type = console_dict['type'] self.url = console_dict['url'] def print_console(cs, data): utils.print_list([Console(console_dict_accessor(cs, data))], ['Type', 'Url']) @utils.arg('server', metavar='', help=_('Name or ID of server.')) @utils.arg( 'console_type', metavar='', help=_('Type of vnc console ("novnc" or "xvpvnc").')) def do_get_vnc_console(cs, args): """Get a vnc console to a server.""" server = _find_server(cs, args.server) data = server.get_vnc_console(args.console_type) print_console(cs, data) @utils.arg('server', metavar='', help=_('Name or ID of server.')) @utils.arg( 'console_type', metavar='', help=_('Type of spice console ("spice-html5").')) def do_get_spice_console(cs, args): """Get a spice console to a server.""" server = _find_server(cs, args.server) data = server.get_spice_console(args.console_type) print_console(cs, data) @utils.arg('server', metavar='', help=_('Name or ID of server.')) @utils.arg( 'console_type', metavar='', help=_('Type of rdp console ("rdp-html5").')) def do_get_rdp_console(cs, args): """Get a rdp console to a server.""" server = _find_server(cs, args.server) data = server.get_rdp_console(args.console_type) print_console(cs, data) @utils.arg('server', metavar='', help=_('Name or ID of server.')) @utils.arg( '--console-type', default='serial', help=_('Type of serial console, default="serial".')) def do_get_serial_console(cs, args): """Get a serial console to a server.""" if args.console_type not in ('serial',): raise exceptions.CommandError( _("Invalid parameter value for 'console_type', " "currently supported 'serial'.")) server = _find_server(cs, args.server) data = server.get_serial_console(args.console_type) print_console(cs, data) @api_versions.wraps('2.8') @utils.arg('server', metavar='', help=_('Name or ID of server.')) def do_get_mks_console(cs, args): """Get an MKS console to a server.""" server = _find_server(cs, args.server) data = server.get_mks_console() print_console(cs, data) @utils.arg('server', metavar='', help=_('Name or ID of server.')) @utils.arg( 'private_key', metavar='', help=_('Private key (used locally to decrypt password) (Optional). ' 'When specified, the command displays the clear (decrypted) VM ' 'password. When not specified, the ciphered VM password is ' 'displayed.'), nargs='?', default=None) def do_get_password(cs, args): """Get the admin password for a server. This operation calls the metadata service to query metadata information and does not read password information from the server itself. """ server = _find_server(cs, args.server) data = server.get_password(args.private_key) print(data) @utils.arg('server', metavar='', help=_('Name or ID of server.')) def do_clear_password(cs, args): """Clear the admin password for a server from the metadata server. This action does not actually change the instance server password. """ server = _find_server(cs, args.server) server.clear_password() def _print_floating_ip_list(floating_ips): convert = [('instance_id', 'server_id')] _translate_keys(floating_ips, convert) utils.print_list(floating_ips, ['Id', 'IP', 'Server Id', 'Fixed IP', 'Pool']) @utils.arg('server', metavar='', help=_('Name or ID of server.')) @utils.arg( '--length', metavar='', default=None, help=_('Length in lines to tail.')) def do_console_log(cs, args): """Get console log output of a server.""" server = _find_server(cs, args.server) data = server.get_console_output(length=args.length) print(data) @utils.arg('server', metavar='', help=_('Name or ID of server.')) @utils.arg( 'secgroup', metavar='', help=_('Name or ID of Security Group.')) def do_add_secgroup(cs, args): """Add a Security Group to a server.""" server = _find_server(cs, args.server) server.add_security_group(args.secgroup) @utils.arg('server', metavar='', help=_('Name or ID of server.')) @utils.arg( 'secgroup', metavar='', help=_('Name of Security Group.')) def do_remove_secgroup(cs, args): """Remove a Security Group from a server.""" server = _find_server(cs, args.server) server.remove_security_group(args.secgroup) @utils.arg('server', metavar='', help=_('Name or ID of server.')) def do_list_secgroup(cs, args): """List Security Group(s) of a server.""" server = _find_server(cs, args.server) groups = server.list_security_group() _print_secgroups(groups) def _print_secgroups(secgroups): utils.print_list(secgroups, ['Id', 'Name', 'Description']) @api_versions.wraps("2.0", "2.1") def _keypair_create(cs, args, name, pub_key): return cs.keypairs.create(name, pub_key) @api_versions.wraps("2.2", "2.9") def _keypair_create(cs, args, name, pub_key): return cs.keypairs.create(name, pub_key, key_type=args.key_type) @api_versions.wraps("2.10") def _keypair_create(cs, args, name, pub_key): return cs.keypairs.create(name, pub_key, key_type=args.key_type, user_id=args.user) @utils.arg('name', metavar='', help=_('Name of key.')) @utils.arg( '--pub-key', metavar='', default=None, help=_('Path to a public ssh key.')) @utils.arg( '--key-type', metavar='', default='ssh', help=_('Keypair type. Can be ssh or x509.'), start_version="2.2") @utils.arg( '--user', metavar='', default=None, help=_('ID of user to whom to add key-pair (Admin only).'), start_version="2.10") def do_keypair_add(cs, args): """Create a new key pair for use with servers.""" name = args.name pub_key = args.pub_key if pub_key: if pub_key == '-': pub_key = sys.stdin.read() else: try: with open(os.path.expanduser(pub_key)) as f: pub_key = f.read() except IOError as e: raise exceptions.CommandError( _("Can't open or read '%(key)s': %(exc)s") % {'key': pub_key, 'exc': e} ) keypair = _keypair_create(cs, args, name, pub_key) if not pub_key: private_key = keypair.private_key print(private_key) @api_versions.wraps("2.0", "2.9") @utils.arg('name', metavar='', help=_('Keypair name to delete.')) def do_keypair_delete(cs, args): """Delete keypair given by its name.""" name = _find_keypair(cs, args.name) cs.keypairs.delete(name) @api_versions.wraps("2.10") @utils.arg('name', metavar='', help=_('Keypair name to delete.')) @utils.arg( '--user', metavar='', default=None, help=_('ID of key-pair owner (Admin only).')) def do_keypair_delete(cs, args): """Delete keypair given by its name.""" cs.keypairs.delete(args.name, args.user) @api_versions.wraps("2.0", "2.1") def _get_keypairs_list_columns(cs, args): return ['Name', 'Fingerprint'] @api_versions.wraps("2.2") def _get_keypairs_list_columns(cs, args): return ['Name', 'Type', 'Fingerprint'] @api_versions.wraps("2.0", "2.9") def do_keypair_list(cs, args): """Print a list of keypairs for a user""" keypairs = cs.keypairs.list() columns = _get_keypairs_list_columns(cs, args) utils.print_list(keypairs, columns) @api_versions.wraps("2.10", "2.34") @utils.arg( '--user', metavar='', default=None, help=_('List key-pairs of specified user ID (Admin only).')) def do_keypair_list(cs, args): """Print a list of keypairs for a user""" keypairs = cs.keypairs.list(args.user) columns = _get_keypairs_list_columns(cs, args) utils.print_list(keypairs, columns) @api_versions.wraps("2.35") @utils.arg( '--user', metavar='', default=None, help=_('List key-pairs of specified user ID (Admin only).')) @utils.arg( '--marker', dest='marker', metavar='', default=None, help=_('The last keypair of the previous page; displays list of keypairs ' 'after "marker".')) @utils.arg( '--limit', dest='limit', metavar='', type=int, default=None, help=_("Maximum number of keypairs to display. If limit is bigger than " "'CONF.api.max_limit' option of Nova API, limit " "'CONF.api.max_limit' will be used instead.")) def do_keypair_list(cs, args): """Print a list of keypairs for a user""" keypairs = cs.keypairs.list(args.user, args.marker, args.limit) columns = _get_keypairs_list_columns(cs, args) utils.print_list(keypairs, columns) def _print_keypair(keypair): kp = keypair.to_dict() pk = kp.pop('public_key') utils.print_dict(kp) print(_("Public key: %s") % pk) @api_versions.wraps("2.0", "2.9") @utils.arg( 'keypair', metavar='', help=_("Name of keypair.")) def do_keypair_show(cs, args): """Show details about the given keypair.""" keypair = _find_keypair(cs, args.keypair) _print_keypair(keypair) @api_versions.wraps("2.10") @utils.arg( 'keypair', metavar='', help=_("Name of keypair.")) @utils.arg( '--user', metavar='', default=None, help=_('ID of key-pair owner (Admin only).')) def do_keypair_show(cs, args): """Show details about the given keypair.""" keypair = cs.keypairs.get(args.keypair, args.user) _print_keypair(keypair) def _find_keypair(cs, keypair): """Get a keypair by name.""" return utils.find_resource(cs.keypairs, keypair) def _print_absolute_limits(limits): """Prints absolute limits.""" class Limit(object): def __init__(self, name, used, max, other): self.name = name self.used = used self.max = max self.other = other limit_map = { 'maxServerMeta': {'name': 'Server Meta', 'type': 'max'}, 'maxPersonality': {'name': 'Personality', 'type': 'max'}, 'maxPersonalitySize': {'name': 'Personality Size', 'type': 'max'}, 'maxImageMeta': {'name': 'ImageMeta', 'type': 'max'}, 'maxTotalKeypairs': {'name': 'Keypairs', 'type': 'max'}, 'totalCoresUsed': {'name': 'Cores', 'type': 'used'}, 'maxTotalCores': {'name': 'Cores', 'type': 'max'}, 'totalRAMUsed': {'name': 'RAM', 'type': 'used'}, 'maxTotalRAMSize': {'name': 'RAM', 'type': 'max'}, 'totalInstancesUsed': {'name': 'Instances', 'type': 'used'}, 'maxTotalInstances': {'name': 'Instances', 'type': 'max'}, 'totalFloatingIpsUsed': {'name': 'FloatingIps', 'type': 'used'}, 'maxTotalFloatingIps': {'name': 'FloatingIps', 'type': 'max'}, 'totalSecurityGroupsUsed': {'name': 'SecurityGroups', 'type': 'used'}, 'maxSecurityGroups': {'name': 'SecurityGroups', 'type': 'max'}, 'maxSecurityGroupRules': {'name': 'SecurityGroupRules', 'type': 'max'}, 'maxServerGroups': {'name': 'ServerGroups', 'type': 'max'}, 'totalServerGroupsUsed': {'name': 'ServerGroups', 'type': 'used'}, 'maxServerGroupMembers': {'name': 'ServerGroupMembers', 'type': 'max'}, } max = {} used = {} other = {} limit_names = [] columns = ['Name', 'Used', 'Max'] for limit in limits: map = limit_map.get(limit.name, {'name': limit.name, 'type': 'other'}) name = map['name'] if map['type'] == 'max': max[name] = limit.value elif map['type'] == 'used': used[name] = limit.value else: other[name] = limit.value if 'Other' not in columns: columns.append('Other') if name not in limit_names: limit_names.append(name) limit_names.sort() limit_list = [] for name in limit_names: limit_list.append(Limit( name, used.get(name, '-'), max.get(name, '-'), other.get(name, '-'), )) utils.print_list(limit_list, columns) def _print_rate_limits(limits): """print rate limits.""" columns = ['Verb', 'URI', 'Value', 'Remain', 'Unit', 'Next_Available'] utils.print_list(limits, columns) @utils.arg( '--tenant', # nova db searches by project_id dest='tenant', metavar='', nargs='?', help=_('Display information from single tenant (Admin only).')) @utils.arg( '--reserved', dest='reserved', action='store_true', default=False, help=_('Include reservations count.')) def do_limits(cs, args): """Print rate and absolute limits.""" limits = cs.limits.get(args.reserved, args.tenant) _print_rate_limits(limits.rate) _print_absolute_limits(limits.absolute) def _get_usage_marker(usage): marker = None if hasattr(usage, 'server_usages') and usage.server_usages: marker = usage.server_usages[-1]['instance_id'] return marker def _get_usage_list_marker(usage_list): marker = None if usage_list: marker = _get_usage_marker(usage_list[-1]) return marker def _merge_usage(usage, next_usage): usage.server_usages.extend(next_usage.server_usages) usage.total_hours += next_usage.total_hours usage.total_memory_mb_usage += next_usage.total_memory_mb_usage usage.total_vcpus_usage += next_usage.total_vcpus_usage usage.total_local_gb_usage += next_usage.total_local_gb_usage def _merge_usage_list(usages, next_usage_list): for next_usage in next_usage_list: if next_usage.tenant_id in usages: _merge_usage(usages[next_usage.tenant_id], next_usage) else: usages[next_usage.tenant_id] = next_usage @utils.arg( '--start', metavar='', help=_('Usage range start date ex 2012-01-20. (default: 4 weeks ago)'), default=None) @utils.arg( '--end', metavar='', help=_('Usage range end date, ex 2012-01-20. (default: tomorrow)'), default=None) def do_usage_list(cs, args): """List usage data for all tenants.""" dateformat = "%Y-%m-%d" rows = ["Tenant ID", "Servers", "RAM MiB-Hours", "CPU Hours", "Disk GiB-Hours"] now = timeutils.utcnow() if args.start: start = datetime.datetime.strptime(args.start, dateformat) else: start = now - datetime.timedelta(weeks=4) if args.end: end = datetime.datetime.strptime(args.end, dateformat) else: end = now + datetime.timedelta(days=1) def simplify_usage(u): simplerows = [x.lower().replace(" ", "_") for x in rows] setattr(u, simplerows[0], u.tenant_id) setattr(u, simplerows[1], "%d" % len(u.server_usages)) setattr(u, simplerows[2], "%.2f" % u.total_memory_mb_usage) setattr(u, simplerows[3], "%.2f" % u.total_vcpus_usage) setattr(u, simplerows[4], "%.2f" % u.total_local_gb_usage) if cs.api_version < api_versions.APIVersion('2.40'): usage_list = cs.usage.list(start, end, detailed=True) else: # If the number of instances used to calculate the usage is greater # than CONF.api.max_limit, the usage will be split across multiple # requests and the responses will need to be merged back together. usages = collections.OrderedDict() usage_list = cs.usage.list(start, end, detailed=True) _merge_usage_list(usages, usage_list) marker = _get_usage_list_marker(usage_list) while marker: next_usage_list = cs.usage.list( start, end, detailed=True, marker=marker) marker = _get_usage_list_marker(next_usage_list) if marker: _merge_usage_list(usages, next_usage_list) usage_list = list(usages.values()) print(_("Usage from %(start)s to %(end)s:") % {'start': start.strftime(dateformat), 'end': end.strftime(dateformat)}) for usage in usage_list: simplify_usage(usage) utils.print_list(usage_list, rows) @utils.arg( '--start', metavar='', help=_('Usage range start date ex 2012-01-20. (default: 4 weeks ago)'), default=None) @utils.arg( '--end', metavar='', help=_('Usage range end date, ex 2012-01-20. (default: tomorrow)'), default=None) @utils.arg( '--tenant', metavar='', default=None, help=_('UUID of tenant to get usage for.')) def do_usage(cs, args): """Show usage data for a single tenant.""" dateformat = "%Y-%m-%d" rows = ["Servers", "RAM MiB-Hours", "CPU Hours", "Disk GiB-Hours"] now = timeutils.utcnow() if args.start: start = datetime.datetime.strptime(args.start, dateformat) else: start = now - datetime.timedelta(weeks=4) if args.end: end = datetime.datetime.strptime(args.end, dateformat) else: end = now + datetime.timedelta(days=1) def simplify_usage(u): simplerows = [x.lower().replace(" ", "_") for x in rows] setattr(u, simplerows[0], "%d" % len(u.server_usages)) setattr(u, simplerows[1], "%.2f" % u.total_memory_mb_usage) setattr(u, simplerows[2], "%.2f" % u.total_vcpus_usage) setattr(u, simplerows[3], "%.2f" % u.total_local_gb_usage) if args.tenant: tenant_id = args.tenant else: if isinstance(cs.client, client.SessionClient): auth = cs.client.auth tenant_id = auth.get_auth_ref(cs.client.session).project_id else: tenant_id = cs.client.tenant_id if cs.api_version < api_versions.APIVersion('2.40'): usage = cs.usage.get(tenant_id, start, end) else: # If the number of instances used to calculate the usage is greater # than CONF.api.max_limit, the usage will be split across multiple # requests and the responses will need to be merged back together. usage = cs.usage.get(tenant_id, start, end) marker = _get_usage_marker(usage) while marker: next_usage = cs.usage.get(tenant_id, start, end, marker=marker) marker = _get_usage_marker(next_usage) if marker: _merge_usage(usage, next_usage) print(_("Usage from %(start)s to %(end)s:") % {'start': start.strftime(dateformat), 'end': end.strftime(dateformat)}) if getattr(usage, 'total_vcpus_usage', None): simplify_usage(usage) utils.print_list([usage], rows) else: print(_('None')) @utils.arg( '--hypervisor', metavar='', default=None, help=_('Type of hypervisor.')) def do_agent_list(cs, args): """DEPRECATED List all builds.""" _emit_agent_deprecation_warning() result = cs.agents.list(args.hypervisor) columns = ["Agent_id", "Hypervisor", "OS", "Architecture", "Version", 'Md5hash', 'Url'] utils.print_list(result, columns) @utils.arg('os', metavar='', help=_('Type of OS.')) @utils.arg( 'architecture', metavar='', help=_('Type of architecture.')) @utils.arg('version', metavar='', help=_('Version.')) @utils.arg('url', metavar='', help=_('URL.')) @utils.arg('md5hash', metavar='', help=_('MD5 hash.')) @utils.arg( 'hypervisor', metavar='', default='xen', help=_('Type of hypervisor.')) def do_agent_create(cs, args): """DEPRECATED Create new agent build.""" _emit_agent_deprecation_warning() result = cs.agents.create(args.os, args.architecture, args.version, args.url, args.md5hash, args.hypervisor) utils.print_dict(result.to_dict()) @utils.arg('id', metavar='', help=_('ID of the agent-build.')) def do_agent_delete(cs, args): """DEPRECATED Delete existing agent build.""" _emit_agent_deprecation_warning() cs.agents.delete(args.id) @utils.arg('id', metavar='', help=_('ID of the agent-build.')) @utils.arg('version', metavar='', help=_('Version.')) @utils.arg('url', metavar='', help=_('URL')) @utils.arg('md5hash', metavar='', help=_('MD5 hash.')) def do_agent_modify(cs, args): """DEPRECATED Modify existing agent build.""" _emit_agent_deprecation_warning() result = cs.agents.update(args.id, args.version, args.url, args.md5hash) utils.print_dict(result.to_dict()) def _find_aggregate(cs, aggregate): """Get an aggregate by name or ID.""" return utils.find_resource(cs.aggregates, aggregate) def do_aggregate_list(cs, args): """Print a list of all aggregates.""" aggregates = cs.aggregates.list() columns = ['Id', 'Name', 'Availability Zone'] if cs.api_version >= api_versions.APIVersion('2.41'): columns.append('UUID') utils.print_list(aggregates, columns) @utils.arg('name', metavar='', help=_('Name of aggregate.')) @utils.arg( 'availability_zone', metavar='', default=None, nargs='?', help=_('The availability zone of the aggregate (optional).')) def do_aggregate_create(cs, args): """Create a new aggregate with the specified details.""" aggregate = cs.aggregates.create(args.name, args.availability_zone) _print_aggregate_details(cs, aggregate) @utils.arg( 'aggregate', metavar='', help=_('Name or ID of aggregate to delete.')) def do_aggregate_delete(cs, args): """Delete the aggregate.""" aggregate = _find_aggregate(cs, args.aggregate) cs.aggregates.delete(aggregate) print(_("Aggregate %s has been successfully deleted.") % aggregate.id) @utils.arg( 'aggregate', metavar='', help=_('Name or ID of aggregate to update.')) @utils.arg( '--name', metavar='', dest='name', help=_('New name for aggregate.')) @utils.arg( '--availability-zone', metavar='', dest='availability_zone', help=_('New availability zone for aggregate.')) def do_aggregate_update(cs, args): """Update the aggregate's name and optionally availability zone.""" aggregate = _find_aggregate(cs, args.aggregate) updates = {} if args.name: updates["name"] = args.name if args.availability_zone: updates["availability_zone"] = args.availability_zone if not updates: raise exceptions.CommandError(_( "Either '--name ' or '--availability-zone " "' must be specified.")) aggregate = cs.aggregates.update(aggregate.id, updates) print(_("Aggregate %s has been successfully updated.") % aggregate.id) _print_aggregate_details(cs, aggregate) @utils.arg( 'aggregate', metavar='', help=_('Name or ID of aggregate to update.')) @utils.arg( 'metadata', metavar='', nargs='+', action='append', default=[], help=_('Metadata to add/update to aggregate. ' 'Specify only the key to delete a metadata item.')) def do_aggregate_set_metadata(cs, args): """Update the metadata associated with the aggregate.""" aggregate = _find_aggregate(cs, args.aggregate) metadata = _extract_metadata(args) currentmetadata = getattr(aggregate, 'metadata', {}) if set(metadata.items()) & set(currentmetadata.items()): raise exceptions.CommandError(_("metadata already exists")) for key, value in metadata.items(): if value is None and key not in currentmetadata: raise exceptions.CommandError(_("metadata key %s does not exist" " hence can not be deleted") % key) aggregate = cs.aggregates.set_metadata(aggregate.id, metadata) print(_("Metadata has been successfully updated for aggregate %s.") % aggregate.id) _print_aggregate_details(cs, aggregate) @utils.arg( 'aggregate', metavar='', help=_('Name or ID of aggregate.')) @utils.arg( 'host', metavar='', help=_('The host to add to the aggregate.')) def do_aggregate_add_host(cs, args): """Add the host to the specified aggregate.""" aggregate = _find_aggregate(cs, args.aggregate) aggregate = cs.aggregates.add_host(aggregate.id, args.host) print(_("Host %(host)s has been successfully added for aggregate " "%(aggregate_id)s ") % {'host': args.host, 'aggregate_id': aggregate.id}) _print_aggregate_details(cs, aggregate) @utils.arg( 'aggregate', metavar='', help=_('Name or ID of aggregate.')) @utils.arg( 'host', metavar='', help=_('The host to remove from the aggregate.')) def do_aggregate_remove_host(cs, args): """Remove the specified host from the specified aggregate.""" aggregate = _find_aggregate(cs, args.aggregate) aggregate = cs.aggregates.remove_host(aggregate.id, args.host) print(_("Host %(host)s has been successfully removed from aggregate " "%(aggregate_id)s ") % {'host': args.host, 'aggregate_id': aggregate.id}) _print_aggregate_details(cs, aggregate) @utils.arg( 'aggregate', metavar='', help=_('Name or ID of aggregate.')) def do_aggregate_show(cs, args): """Show details of the specified aggregate.""" aggregate = _find_aggregate(cs, args.aggregate) _print_aggregate_details(cs, aggregate) def _print_aggregate_details(cs, aggregate): columns = ['Id', 'Name', 'Availability Zone', 'Hosts', 'Metadata'] if cs.api_version >= api_versions.APIVersion('2.41'): columns.append('UUID') def parser_metadata(fields): return utils.pretty_choice_dict(getattr(fields, 'metadata', {}) or {}) def parser_hosts(fields): return utils.pretty_choice_list(getattr(fields, 'hosts', [])) formatters = { 'Metadata': parser_metadata, 'Hosts': parser_hosts, } utils.print_list([aggregate], columns, formatters=formatters) @api_versions.wraps("2.81") @utils.arg( 'aggregate', metavar='', help=_('Name or ID of the aggregate.')) @utils.arg( 'images', metavar='', nargs='+', help=_('Name or ID of image(s) to cache on the hosts within ' 'the aggregate.')) def do_aggregate_cache_images(cs, args): """Request images be cached.""" aggregate = _find_aggregate(cs, args.aggregate) images = _find_images(cs, args.images) cs.aggregates.cache_images(aggregate, images) @utils.arg('server', metavar='', help=_('Name or ID of server.')) @utils.arg( 'host', metavar='', default=None, nargs='?', help=_('Destination host name. If no host is specified, the scheduler ' 'will choose one.')) @utils.arg( '--block-migrate', action='store_true', dest='block_migrate', default=False, help=_('True in case of block_migration. (Default=False:live_migration)'), start_version="2.0", end_version="2.24") @utils.arg( '--block-migrate', action='store_true', dest='block_migrate', default="auto", help=_('True in case of block_migration. (Default=auto:live_migration)'), start_version="2.25") @utils.arg( '--disk-over-commit', action='store_true', dest='disk_over_commit', default=False, help=_('Allow overcommit. (Default=False)'), start_version="2.0", end_version="2.24") @utils.arg( '--force', dest='force', action='store_true', default=False, help=_('Force a live-migration by not verifying the provided destination ' 'host by the scheduler. WARNING: This could result in failures to ' 'actually live migrate the server to the specified host. It is ' 'recommended to either not specify a host so that the scheduler ' 'will pick one, or specify a host without --force.'), start_version='2.30', end_version='2.67') def do_live_migration(cs, args): """Migrate running server to a new machine.""" update_kwargs = {} if 'disk_over_commit' in args: update_kwargs['disk_over_commit'] = args.disk_over_commit if 'force' in args and args.force: update_kwargs['force'] = args.force _find_server(cs, args.server).live_migrate(args.host, args.block_migrate, **update_kwargs) @api_versions.wraps("2.22") @utils.arg('server', metavar='', help=_('Name or ID of server.')) @utils.arg('migration', metavar='', help=_('ID of migration.')) def do_live_migration_force_complete(cs, args): """Force on-going live migration to complete.""" server = _find_server(cs, args.server) cs.server_migrations.live_migrate_force_complete(server, args.migration) @api_versions.wraps("2.23") @utils.arg('server', metavar='', help=_('Name or ID of server.')) def do_server_migration_list(cs, args): """Get the migrations list of specified server.""" server = _find_server(cs, args.server) migrations = cs.server_migrations.list(server) fields = ['Id', 'Source Node', 'Dest Node', 'Source Compute', 'Dest Compute', 'Dest Host', 'Status', 'Server UUID', 'Created At', 'Updated At'] format_name = ["Total Memory Bytes", "Processed Memory Bytes", "Remaining Memory Bytes", "Total Disk Bytes", "Processed Disk Bytes", "Remaining Disk Bytes"] format_key = ["memory_total_bytes", "memory_processed_bytes", "memory_remaining_bytes", "disk_total_bytes", "disk_processed_bytes", "disk_remaining_bytes"] if cs.api_version >= api_versions.APIVersion("2.80"): fields.append("Project ID") fields.append("User ID") formatters = map(lambda field: utils.make_field_formatter(field)[1], format_key) formatters = dict(zip(format_name, formatters)) utils.print_list(migrations, fields + format_name, formatters) @api_versions.wraps("2.23") @utils.arg('server', metavar='', help=_('Name or ID of server.')) @utils.arg('migration', metavar='', help=_('ID of migration.')) def do_server_migration_show(cs, args): """Get the migration of specified server.""" server = _find_server(cs, args.server) migration = cs.server_migrations.get(server, args.migration) utils.print_dict(migration.to_dict()) @api_versions.wraps("2.24") @utils.arg('server', metavar='', help=_('Name or ID of server.')) @utils.arg('migration', metavar='', help=_('ID of migration.')) def do_live_migration_abort(cs, args): """Abort an on-going live migration.""" server = _find_server(cs, args.server) cs.server_migrations.live_migration_abort(server, args.migration) @utils.arg( '--all-tenants', action='store_const', const=1, default=0, help=_('Reset state server(s) in another tenant by name (Admin only).')) @utils.arg( 'server', metavar='', nargs='+', help=_('Name or ID of server(s).')) @utils.arg( '--active', action='store_const', dest='state', default='error', const='active', help=_('Request the server be reset to "active" state instead ' 'of "error" state (the default).')) def do_reset_state(cs, args): """Reset the state of a server.""" failure_flag = False find_args = {'all_tenants': args.all_tenants} for server in args.server: try: _find_server(cs, server, **find_args).reset_state(args.state) msg = "Reset state for server %s succeeded; new state is %s" print(msg % (server, args.state)) except Exception as e: failure_flag = True msg = "Reset state for server %s failed: %s" % (server, e) print(msg) if failure_flag: msg = "Unable to reset the state for the specified server(s)." raise exceptions.CommandError(msg) @utils.arg('server', metavar='', help=_('Name or ID of server.')) def do_reset_network(cs, args): """Reset network of a server.""" _find_server(cs, args.server).reset_network() @utils.arg( '--host', metavar='', default=None, help=_('Name of host.')) @utils.arg( '--binary', metavar='', default=None, help=_('Service binary.')) def do_service_list(cs, args): """Show a list of all running services. Filter by host & binary.""" result = cs.services.list(host=args.host, binary=args.binary) columns = ["Id", "Binary", "Host", "Zone", "Status", "State", "Updated_at", "Disabled Reason"] if cs.api_version >= api_versions.APIVersion('2.11'): columns.append("Forced down") utils.print_list(result, columns) # Before microversion 2.53, the service was identified using it's host/binary # values. @api_versions.wraps('2.0', '2.52') @utils.arg('host', metavar='', help=_('Name of host.')) def do_service_enable(cs, args): """Enable the service.""" result = cs.services.enable(args.host, 'nova-compute') utils.print_list([result], ['Host', 'Binary', 'Status']) # Starting in microversion 2.53, the service is identified by UUID ID. @api_versions.wraps('2.53') @utils.arg('id', metavar='', help=_('ID of the service as a UUID.')) def do_service_enable(cs, args): """Enable the service.""" result = cs.services.enable(args.id) utils.print_list([result], ['ID', 'Host', 'Binary', 'Status']) # Before microversion 2.53, the service was identified using it's host/binary # values. @api_versions.wraps('2.0', '2.52') @utils.arg('host', metavar='', help=_('Name of host.')) @utils.arg( '--reason', metavar='', help=_('Reason for disabling service.')) def do_service_disable(cs, args): """Disable the service.""" if args.reason: result = cs.services.disable_log_reason(args.host, 'nova-compute', args.reason) utils.print_list([result], ['Host', 'Binary', 'Status', 'Disabled Reason']) else: result = cs.services.disable(args.host, 'nova-compute') utils.print_list([result], ['Host', 'Binary', 'Status']) # Starting in microversion 2.53, the service is identified by UUID ID. @api_versions.wraps('2.53') @utils.arg('id', metavar='', help=_('ID of the service as a UUID.')) @utils.arg( '--reason', metavar='', help=_('Reason for disabling the service.')) def do_service_disable(cs, args): """Disable the service.""" if args.reason: result = cs.services.disable_log_reason(args.id, args.reason) utils.print_list( [result], ['ID', 'Host', 'Binary', 'Status', 'Disabled Reason']) else: result = cs.services.disable(args.id) utils.print_list([result], ['ID', 'Host', 'Binary', 'Status']) # Before microversion 2.53, the service was identified using it's host/binary # values. @api_versions.wraps("2.11", "2.52") @utils.arg('host', metavar='', help=_('Name of host.')) @utils.arg( '--unset', dest='force_down', help=_("Unset the force state down of service."), action='store_false', default=True) def do_service_force_down(cs, args): """Force service to down.""" result = cs.services.force_down(args.host, 'nova-compute', args.force_down) utils.print_list([result], ['Host', 'Binary', 'Forced down']) # Starting in microversion 2.53, the service is identified by UUID ID. @api_versions.wraps('2.53') @utils.arg('id', metavar='', help=_('ID of the service as a UUID.')) @utils.arg( '--unset', dest='force_down', help=_("Unset the forced_down state of the service."), action='store_false', default=True) def do_service_force_down(cs, args): """Force service to down.""" result = cs.services.force_down(args.id, args.force_down) utils.print_list([result], ['ID', 'Host', 'Binary', 'Forced down']) # Before microversion 2.53, the service was identified using it's host/binary # values. @api_versions.wraps('2.0', '2.52') @utils.arg('id', metavar='', help=_('ID of service as an integer. Note that this may not ' 'uniquely identify a service in a multi-cell deployment.')) def do_service_delete(cs, args): """Delete the service by integer ID. If deleting a nova-compute service, be sure to stop the actual nova-compute process on the physical host before deleting the service with this command. Failing to do so can lead to the running service re-creating orphaned compute_nodes table records in the database. """ cs.services.delete(args.id) # Starting in microversion 2.53, the service is identified by UUID ID. @api_versions.wraps('2.53') @utils.arg('id', metavar='', help=_('ID of service as a UUID.')) def do_service_delete(cs, args): """Delete the service by UUID ID. If deleting a nova-compute service, be sure to stop the actual nova-compute process on the physical host before deleting the service with this command. Failing to do so can lead to the running service re-creating orphaned compute_nodes table records in the database. """ cs.services.delete(args.id) def _find_hypervisor(cs, hypervisor): """Get a hypervisor by name or ID.""" return utils.find_resource(cs.hypervisors, hypervisor) def _do_hypervisor_list(cs, matching=None, limit=None, marker=None): columns = ['ID', 'Hypervisor hostname', 'State', 'Status'] if matching: utils.print_list(cs.hypervisors.search(matching), columns) else: params = {} if limit is not None: params['limit'] = limit if marker is not None: params['marker'] = marker # Since we're not outputting detail data, choose # detailed=False for server-side efficiency utils.print_list(cs.hypervisors.list(False, **params), columns) @api_versions.wraps("2.0", "2.32") @utils.arg( '--matching', metavar='', default=None, help=_('List hypervisors matching the given (or pattern).')) def do_hypervisor_list(cs, args): """List hypervisors.""" _do_hypervisor_list(cs, matching=args.matching) @api_versions.wraps("2.33") @utils.arg( '--matching', metavar='', default=None, help=_('List hypervisors matching the given (or pattern). ' 'If matching is used limit and marker options will be ignored.')) @utils.arg( '--marker', dest='marker', metavar='', default=None, help=_('The last hypervisor of the previous page; displays list of ' 'hypervisors after "marker".')) @utils.arg( '--limit', dest='limit', metavar='', type=int, default=None, help=_("Maximum number of hypervisors to display. If limit is bigger than " "'CONF.api.max_limit' option of Nova API, limit " "'CONF.api.max_limit' will be used instead.")) def do_hypervisor_list(cs, args): """List hypervisors.""" _do_hypervisor_list( cs, matching=args.matching, limit=args.limit, marker=args.marker) @utils.arg( 'hostname', metavar='', help=_('The hypervisor hostname (or pattern) to search for.')) def do_hypervisor_servers(cs, args): """List servers belonging to specific hypervisors.""" hypers = cs.hypervisors.search(args.hostname, servers=True) class InstanceOnHyper(object): def __init__(self, **kwargs): self.__dict__.update(kwargs) # Massage the result into a list to be displayed instances = [] for hyper in hypers: hyper_host = hyper.hypervisor_hostname hyper_id = hyper.id if hasattr(hyper, 'servers'): instances.extend([InstanceOnHyper(id=serv['uuid'], name=serv['name'], hypervisor_hostname=hyper_host, hypervisor_id=hyper_id) for serv in hyper.servers]) # Output the data utils.print_list(instances, ['ID', 'Name', 'Hypervisor ID', 'Hypervisor Hostname']) @utils.arg( 'hypervisor', metavar='', help=_('Name or ID of the hypervisor. Starting with microversion 2.53 ' 'the ID must be a UUID.')) @utils.arg( '--wrap', dest='wrap', metavar='', default=40, help=_('Wrap the output to a specified length. ' 'Default is 40 or 0 to disable')) def do_hypervisor_show(cs, args): """Display the details of the specified hypervisor.""" hyper = _find_hypervisor(cs, args.hypervisor) utils.print_dict(utils.flatten_dict(hyper.to_dict()), wrap=int(args.wrap)) @utils.arg( 'hypervisor', metavar='', help=_('Name or ID of the hypervisor. Starting with microversion 2.53 ' 'the ID must be a UUID.')) def do_hypervisor_uptime(cs, args): """Display the uptime of the specified hypervisor.""" hyper = _find_hypervisor(cs, args.hypervisor) hyper = cs.hypervisors.uptime(hyper) # Output the uptime information utils.print_dict(hyper.to_dict()) @api_versions.wraps('2.0', '2.87') def do_hypervisor_stats(cs, args): """Get hypervisor statistics over all compute nodes.""" stats = cs.hypervisor_stats.statistics() utils.print_dict(stats.to_dict()) @api_versions.wraps('2.88') def do_hypervisor_stats(cs, args): msg = _( "The hypervisor-stats command is not supported in API version 2.88 " "or later." ) raise exceptions.CommandError(msg) @utils.arg('server', metavar='', help=_('Name or ID of server.')) @utils.arg( '--port', dest='port', action='store', type=int, default=22, help=_('Optional flag to indicate which port to use for ssh. ' '(Default=22)')) @utils.arg( '--private', dest='private', action='store_true', default=False, help=argparse.SUPPRESS) @utils.arg( '--address-type', dest='address_type', action='store', type=str, default='floating', help=_('Optional flag to indicate which IP type to use. Possible values ' 'includes fixed and floating (the Default).')) @utils.arg( '--network', metavar='', help=_('Network to use for the ssh.'), default=None) @utils.arg( '--ipv6', dest='ipv6', action='store_true', default=False, help=_('Optional flag to indicate whether to use an IPv6 address ' 'attached to a server. (Defaults to IPv4 address)')) @utils.arg( '--login', metavar='', help=_('Login to use.'), default="root") @utils.arg( '-i', '--identity', dest='identity', help=_('Private key file, same as the -i option to the ssh command.'), default='') @utils.arg( '--extra-opts', dest='extra', help=_('Extra options to pass to ssh. see: man ssh.'), default='') def do_ssh(cs, args): """SSH into a server.""" if '@' in args.server: user, server = args.server.split('@', 1) args.login = user args.server = server addresses = _find_server(cs, args.server).addresses address_type = "fixed" if args.private else args.address_type version = 6 if args.ipv6 else 4 pretty_version = 'IPv%d' % version # Select the network to use. if args.network: network_addresses = addresses.get(args.network) if not network_addresses: msg = _("Server '%(server)s' is not attached to network " "'%(network)s'") raise exceptions.ResourceNotFound( msg % {'server': args.server, 'network': args.network}) else: if len(addresses) > 1: msg = _("Server '%(server)s' is attached to more than one network." " Please pick the network to use.") raise exceptions.CommandError(msg % {'server': args.server}) elif not addresses: msg = _("Server '%(server)s' is not attached to any network.") raise exceptions.CommandError(msg % {'server': args.server}) else: network_addresses = list(addresses.values())[0] # Select the address in the selected network. # If the extension is not present, we assume the address to be floating. match = lambda addr: all(( addr.get('version') == version, addr.get('OS-EXT-IPS:type', 'floating') == address_type)) matching_addresses = [address.get('addr') for address in network_addresses if match(address)] if not any(matching_addresses): msg = _("No address that would match network '%(network)s'" " and type '%(address_type)s' of version %(pretty_version)s " "has been found for server '%(server)s'.") raise exceptions.ResourceNotFound(msg % { 'network': args.network, 'address_type': address_type, 'pretty_version': pretty_version, 'server': args.server}) elif len(matching_addresses) > 1: msg = _("More than one %(pretty_version)s %(address_type)s address " "found.") raise exceptions.CommandError(msg % {'pretty_version': pretty_version, 'address_type': address_type}) else: ip_address = matching_addresses[0] identity = '-i %s' % args.identity if len(args.identity) else '' cmd = "ssh -%d -p%d %s %s@%s %s" % (version, args.port, identity, args.login, ip_address, args.extra) logger.debug("Executing cmd '%s'", cmd) os.system(cmd) # NOTE(mriedem): In the 2.50 microversion, the os-quota-class-sets API # will return the server_groups and server_group_members, but no longer # return floating_ips, fixed_ips, security_groups or security_group_members # as those are deprecated as networking service proxies and/or because # nova-network is deprecated. Similar to the 2.36 microversion. # NOTE(mriedem): In the 2.57 microversion, the os-quota-sets and # os-quota-class-sets APIs will no longer return injected_files, # injected_file_content_bytes or injected_file_content_bytes since personality # files (file injection) is deprecated starting with v2.57. _quota_resources = ['instances', 'cores', 'ram', 'floating_ips', 'fixed_ips', 'metadata_items', 'injected_files', 'injected_file_content_bytes', 'injected_file_path_bytes', 'key_pairs', 'security_groups', 'security_group_rules', 'server_groups', 'server_group_members'] def _quota_show(quotas): class FormattedQuota(object): def __init__(self, key, value): setattr(self, 'quota', key) setattr(self, 'limit', value) quota_list = [] for resource in _quota_resources: try: quota = FormattedQuota(resource, getattr(quotas, resource)) quota_list.append(quota) except AttributeError: pass columns = ['Quota', 'Limit'] utils.print_list(quota_list, columns) def _quota_update(manager, identifier, args): updates = {} for resource in _quota_resources: val = getattr(args, resource, None) if val is not None: updates[resource] = val if updates: # default value of force is None to make sure this client # will be compatible with old nova server force_update = getattr(args, 'force', None) user_id = getattr(args, 'user', None) if isinstance(manager, quotas.QuotaSetManager): manager.update(identifier, force=force_update, user_id=user_id, **updates) else: manager.update(identifier, **updates) @utils.arg( '--tenant', metavar='', default=None, help=_('ID of tenant to list the quotas for.')) @utils.arg( '--user', metavar='', default=None, help=_('ID of user to list the quotas for.')) @utils.arg( '--detail', action='store_true', default=False, help=_('Show detailed info (limit, reserved, in-use).')) def do_quota_show(cs, args): """List the quotas for a tenant/user.""" if args.tenant: project_id = args.tenant elif isinstance(cs.client, client.SessionClient): auth = cs.client.auth project_id = auth.get_auth_ref(cs.client.session).project_id else: project_id = cs.client.tenant_id _quota_show(cs.quotas.get(project_id, user_id=args.user, detail=args.detail)) @utils.arg( '--tenant', metavar='', default=None, help=_('ID of tenant to list the default quotas for.')) def do_quota_defaults(cs, args): """List the default quotas for a tenant.""" if args.tenant: project_id = args.tenant elif isinstance(cs.client, client.SessionClient): auth = cs.client.auth project_id = auth.get_auth_ref(cs.client.session).project_id else: project_id = cs.client.tenant_id _quota_show(cs.quotas.defaults(project_id)) @api_versions.wraps("2.0", "2.35") @utils.arg( 'tenant', metavar='', help=_('ID of tenant to set the quotas for.')) @utils.arg( '--user', metavar='', default=None, help=_('ID of user to set the quotas for.')) @utils.arg( '--instances', metavar='', type=int, default=None, help=_('New value for the "instances" quota.')) @utils.arg( '--cores', metavar='', type=int, default=None, help=_('New value for the "cores" quota.')) @utils.arg( '--ram', metavar='', type=int, default=None, help=_('New value for the "ram" quota.')) @utils.arg( '--floating-ips', metavar='', type=int, default=None, action=shell.DeprecatedAction, help=_('New value for the "floating-ips" quota.')) @utils.arg( '--fixed-ips', metavar='', type=int, default=None, action=shell.DeprecatedAction, help=_('New value for the "fixed-ips" quota.')) @utils.arg( '--metadata-items', metavar='', type=int, default=None, help=_('New value for the "metadata-items" quota.')) @utils.arg( '--injected-files', metavar='', type=int, default=None, help=_('New value for the "injected-files" quota.')) @utils.arg( '--injected-file-content-bytes', metavar='', type=int, default=None, help=_('New value for the "injected-file-content-bytes" quota.')) @utils.arg( '--injected-file-path-bytes', metavar='', type=int, default=None, help=_('New value for the "injected-file-path-bytes" quota.')) @utils.arg( '--key-pairs', metavar='', type=int, default=None, help=_('New value for the "key-pairs" quota.')) @utils.arg( '--security-groups', metavar='', type=int, default=None, action=shell.DeprecatedAction, help=_('New value for the "security-groups" quota.')) @utils.arg( '--security-group-rules', metavar='', type=int, default=None, action=shell.DeprecatedAction, help=_('New value for the "security-group-rules" quota.')) @utils.arg( '--server-groups', metavar='', type=int, default=None, help=_('New value for the "server-groups" quota.')) @utils.arg( '--server-group-members', metavar='', type=int, default=None, help=_('New value for the "server-group-members" quota.')) @utils.arg( '--force', dest='force', action="store_true", default=None, help=_('Whether force update the quota even if the already used and ' 'reserved exceeds the new quota.')) def do_quota_update(cs, args): """Update the quotas for a tenant/user.""" _quota_update(cs.quotas, args.tenant, args) # 2.36 does not support updating quota for floating IPs, fixed IPs, security # groups or security group rules. # 2.57 does not support updating injected_file* quotas. @api_versions.wraps("2.36") @utils.arg( 'tenant', metavar='', help=_('ID of tenant to set the quotas for.')) @utils.arg( '--user', metavar='', default=None, help=_('ID of user to set the quotas for.')) @utils.arg( '--instances', metavar='', type=int, default=None, help=_('New value for the "instances" quota.')) @utils.arg( '--cores', metavar='', type=int, default=None, help=_('New value for the "cores" quota.')) @utils.arg( '--ram', metavar='', type=int, default=None, help=_('New value for the "ram" quota.')) @utils.arg( '--metadata-items', metavar='', type=int, default=None, help=_('New value for the "metadata-items" quota.')) @utils.arg( '--injected-files', metavar='', type=int, default=None, help=_('New value for the "injected-files" quota.'), start_version='2.36', end_version='2.56') @utils.arg( '--injected-file-content-bytes', metavar='', type=int, default=None, help=_('New value for the "injected-file-content-bytes" quota.'), start_version='2.36', end_version='2.56') @utils.arg( '--injected-file-path-bytes', metavar='', type=int, default=None, help=_('New value for the "injected-file-path-bytes" quota.'), start_version='2.36', end_version='2.56') @utils.arg( '--key-pairs', metavar='', type=int, default=None, help=_('New value for the "key-pairs" quota.')) @utils.arg( '--server-groups', metavar='', type=int, default=None, help=_('New value for the "server-groups" quota.')) @utils.arg( '--server-group-members', metavar='', type=int, default=None, help=_('New value for the "server-group-members" quota.')) @utils.arg( '--force', dest='force', action="store_true", default=None, help=_('Whether force update the quota even if the already used and ' 'reserved exceeds the new quota.')) def do_quota_update(cs, args): """Update the quotas for a tenant/user.""" _quota_update(cs.quotas, args.tenant, args) @utils.arg( '--tenant', metavar='', required=True, help=_('ID of tenant to delete quota for.')) @utils.arg( '--user', metavar='', help=_('ID of user to delete quota for.')) def do_quota_delete(cs, args): """Delete quota for a tenant/user so their quota will Revert back to default. """ cs.quotas.delete(args.tenant, user_id=args.user) @utils.arg( 'class_name', metavar='', help=_('Name of quota class to list the quotas for.')) def do_quota_class_show(cs, args): """List the quotas for a quota class.""" _quota_show(cs.quota_classes.get(args.class_name)) @api_versions.wraps("2.0", "2.49") @utils.arg( 'class_name', metavar='', help=_('Name of quota class to set the quotas for.')) @utils.arg( '--instances', metavar='', type=int, default=None, help=_('New value for the "instances" quota.')) @utils.arg( '--cores', metavar='', type=int, default=None, help=_('New value for the "cores" quota.')) @utils.arg( '--ram', metavar='', type=int, default=None, help=_('New value for the "ram" quota.')) @utils.arg( '--floating-ips', metavar='', type=int, default=None, action=shell.DeprecatedAction, help=_('New value for the "floating-ips" quota.')) @utils.arg( '--fixed-ips', metavar='', type=int, default=None, action=shell.DeprecatedAction, help=_('New value for the "fixed-ips" quota.')) @utils.arg( '--metadata-items', metavar='', type=int, default=None, help=_('New value for the "metadata-items" quota.')) @utils.arg( '--injected-files', metavar='', type=int, default=None, help=_('New value for the "injected-files" quota.')) @utils.arg( '--injected-file-content-bytes', metavar='', type=int, default=None, help=_('New value for the "injected-file-content-bytes" quota.')) @utils.arg( '--injected-file-path-bytes', metavar='', type=int, default=None, help=_('New value for the "injected-file-path-bytes" quota.')) @utils.arg( '--key-pairs', metavar='', type=int, default=None, help=_('New value for the "key-pairs" quota.')) @utils.arg( '--security-groups', metavar='', type=int, default=None, action=shell.DeprecatedAction, help=_('New value for the "security-groups" quota.')) @utils.arg( '--security-group-rules', metavar='', type=int, default=None, action=shell.DeprecatedAction, help=_('New value for the "security-group-rules" quota.')) @utils.arg( '--server-groups', metavar='', type=int, default=None, help=_('New value for the "server-groups" quota.')) @utils.arg( '--server-group-members', metavar='', type=int, default=None, help=_('New value for the "server-group-members" quota.')) def do_quota_class_update(cs, args): """Update the quotas for a quota class.""" _quota_update(cs.quota_classes, args.class_name, args) # 2.50 does not support updating quota class values for floating IPs, # fixed IPs, security groups or security group rules. # 2.57 does not support updating injected_file* quotas. @api_versions.wraps("2.50") @utils.arg( 'class_name', metavar='', help=_('Name of quota class to set the quotas for.')) @utils.arg( '--instances', metavar='', type=int, default=None, help=_('New value for the "instances" quota.')) @utils.arg( '--cores', metavar='', type=int, default=None, help=_('New value for the "cores" quota.')) @utils.arg( '--ram', metavar='', type=int, default=None, help=_('New value for the "ram" quota.')) @utils.arg( '--metadata-items', metavar='', type=int, default=None, help=_('New value for the "metadata-items" quota.')) @utils.arg( '--injected-files', metavar='', type=int, default=None, help=_('New value for the "injected-files" quota.'), start_version='2.50', end_version='2.56') @utils.arg( '--injected-file-content-bytes', metavar='', type=int, default=None, help=_('New value for the "injected-file-content-bytes" quota.'), start_version='2.50', end_version='2.56') @utils.arg( '--injected-file-path-bytes', metavar='', type=int, default=None, help=_('New value for the "injected-file-path-bytes" quota.'), start_version='2.50', end_version='2.56') @utils.arg( '--key-pairs', metavar='', type=int, default=None, help=_('New value for the "key-pairs" quota.')) @utils.arg( '--server-groups', metavar='', type=int, default=None, help=_('New value for the "server-groups" quota.')) @utils.arg( '--server-group-members', metavar='', type=int, default=None, help=_('New value for the "server-group-members" quota.')) def do_quota_class_update(cs, args): """Update the quotas for a quota class.""" _quota_update(cs.quota_classes, args.class_name, args) @utils.arg('server', metavar='', help=_('Name or ID of server.')) @utils.arg( 'host', metavar='', nargs='?', help=_("Name or ID of the target host. " "If no host is specified, the scheduler will choose one.")) @utils.arg( '--password', dest='password', metavar='', help=_("Set the provided admin password on the evacuated server. Not" " applicable if the server is on shared storage.")) @utils.arg( '--on-shared-storage', dest='on_shared_storage', action="store_true", default=False, help=_('Specifies whether server files are located on shared storage.'), start_version='2.0', end_version='2.13') @utils.arg( '--force', dest='force', action='store_true', default=False, help=_('Force an evacuation by not verifying the provided destination ' 'host by the scheduler. WARNING: This could result in failures to ' 'actually evacuate the server to the specified host. It is ' 'recommended to either not specify a host so that the scheduler ' 'will pick one, or specify a host without --force.'), start_version='2.29', end_version='2.67') def do_evacuate(cs, args): """Evacuate server from failed host.""" # TODO(stephenfin): Simply call '_server_evacuate' instead? server = _find_server(cs, args.server) on_shared_storage = getattr(args, 'on_shared_storage', None) force = getattr(args, 'force', None) update_kwargs = {} if on_shared_storage is not None: update_kwargs['on_shared_storage'] = on_shared_storage if force: update_kwargs['force'] = force res = server.evacuate(host=args.host, password=args.password, **update_kwargs)[1] if isinstance(res, dict): utils.print_dict(res) def _print_interfaces(interfaces, show_tag=False): columns = ['Port State', 'Port ID', 'Net ID', 'IP addresses', 'MAC Addr'] if show_tag: columns.append('Tag') class FormattedInterface(object): def __init__(self, interface): for col in columns: key = col.lower().replace(" ", "_") if hasattr(interface, key): setattr(self, key, getattr(interface, key)) self.ip_addresses = ",".join([fip['ip_address'] for fip in interface.fixed_ips]) utils.print_list([FormattedInterface(i) for i in interfaces], columns) @utils.arg('server', metavar='', help=_('Name or ID of server.')) def do_interface_list(cs, args): """List interfaces attached to a server.""" server = _find_server(cs, args.server) res = server.interface_list() # The "tag" field is in the response starting with microversion 2.70. show_tag = cs.api_version >= api_versions.APIVersion('2.70') _print_interfaces(res, show_tag=show_tag) @utils.arg('server', metavar='', help=_('Name or ID of server.')) @utils.arg( '--port-id', metavar='', help=_('Port ID.'), dest="port_id") @utils.arg( '--net-id', metavar='', help=_('Network ID'), default=None, dest="net_id") @utils.arg( '--fixed-ip', metavar='', help=_('Requested fixed IP.'), default=None, dest="fixed_ip") @utils.arg( '--tag', metavar='', default=None, dest="tag", help=_('Tag for the attached interface.'), start_version="2.49") def do_interface_attach(cs, args): """Attach a network interface to a server.""" server = _find_server(cs, args.server) update_kwargs = {} if 'tag' in args and args.tag: update_kwargs['tag'] = args.tag network_interface = server.interface_attach( args.port_id, args.net_id, args.fixed_ip, **update_kwargs) _print_interface(network_interface) def _print_interface(interface): ni_dict = interface.to_dict() fixed_ips = ni_dict.pop('fixed_ips', None) ni_dict['ip_address'] = (",".join( [fip['ip_address'] for fip in fixed_ips]) if fixed_ips is not None else None) utils.print_dict(ni_dict) @utils.arg('server', metavar='', help=_('Name or ID of server.')) @utils.arg('port_id', metavar='', help=_('Port ID.')) def do_interface_detach(cs, args): """Detach a network interface from a server.""" server = _find_server(cs, args.server) server.interface_detach(args.port_id) @api_versions.wraps("2.17") @utils.arg('server', metavar='', help=_('Name or ID of server.')) def do_trigger_crash_dump(cs, args): """Trigger crash dump in an instance.""" server = _find_server(cs, args.server) server.trigger_crash_dump() def _treeizeAvailabilityZone(zone): """Build a tree view for availability zones.""" AvailabilityZone = availability_zones.AvailabilityZone az = AvailabilityZone(zone.manager, zone.to_dict(), zone._loaded) result = [] # Zone tree view item az.zoneName = zone.zoneName az.zoneState = ('available' if zone.zoneState['available'] else 'not available') az.set_info('zoneName', az.zoneName) az.set_info('zoneState', az.zoneState) result.append(az) if zone.hosts is not None: zone_hosts = sorted(zone.hosts.items(), key=lambda x: x[0]) for (host, services) in zone_hosts: # Host tree view item az = AvailabilityZone(zone.manager, zone.to_dict(), zone._loaded) az.zoneName = '|- %s' % host az.zoneState = '' az.set_info('zoneName', az.zoneName) az.set_info('zoneState', az.zoneState) result.append(az) for (svc, state) in services.items(): # Service tree view item az = AvailabilityZone(zone.manager, zone.to_dict(), zone._loaded) az.zoneName = '| |- %s' % svc az.zoneState = '%s %s %s' % ( 'enabled' if state['active'] else 'disabled', ':-)' if state['available'] else 'XXX', state['updated_at']) az.set_info('zoneName', az.zoneName) az.set_info('zoneState', az.zoneState) result.append(az) return result @utils.service_type('compute') def do_availability_zone_list(cs, _args): """List all the availability zones.""" try: availability_zones = cs.availability_zones.list() except exceptions.Forbidden as e: # policy doesn't allow probably try: availability_zones = cs.availability_zones.list(detailed=False) except Exception: raise e result = [] for zone in availability_zones: result += _treeizeAvailabilityZone(zone) _translate_availability_zone_keys(result) utils.print_list(result, ['Name', 'Status'], sortby_index=None) def _print_server_group_details(cs, server_group): if cs.api_version < api_versions.APIVersion('2.13'): columns = ['Id', 'Name', 'Policies', 'Members', 'Metadata'] elif cs.api_version < api_versions.APIVersion('2.64'): columns = ['Id', 'Name', 'Project Id', 'User Id', 'Policies', 'Members', 'Metadata'] else: columns = ['Id', 'Name', 'Project Id', 'User Id', 'Policy', 'Rules', 'Members'] utils.print_list(server_group, columns) @utils.arg( '--limit', dest='limit', metavar='', type=int, default=None, help=_("Maximum number of server groups to display. If limit is bigger " "than 'CONF.api.max_limit' option of Nova API, limit " "'CONF.api.max_limit' will be used instead.")) @utils.arg( '--offset', dest='offset', metavar='', type=int, default=None, help=_('The offset of groups list to display; use with limit to ' 'return a slice of server groups.')) @utils.arg( '--all-projects', dest='all_projects', action='store_true', default=False, help=_('Display server groups from all projects (Admin only).')) def do_server_group_list(cs, args): """Print a list of all server groups.""" server_groups = cs.server_groups.list(all_projects=args.all_projects, limit=args.limit, offset=args.offset) _print_server_group_details(cs, server_groups) @api_versions.wraps("2.0", "2.63") @utils.arg('name', metavar='', help=_('Server group name.')) @utils.arg( 'policy', metavar='', help=_('Policy for the server group.')) def do_server_group_create(cs, args): """Create a new server group with the specified details.""" server_group = cs.server_groups.create(name=args.name, policies=args.policy) _print_server_group_details(cs, [server_group]) @api_versions.wraps("2.64") @utils.arg('name', metavar='', help=_('Server group name.')) @utils.arg( 'policy', metavar='', help=_('Policy for the server group.')) @utils.arg( '--rule', metavar="", dest='rules', action='append', default=[], help=_('A rule for the policy. Currently, only the ' '"max_server_per_host" rule is supported for the ' '"anti-affinity" policy.')) def do_server_group_create(cs, args): """Create a new server group with the specified details.""" rules = _meta_parsing(args.rules) server_group = cs.server_groups.create(name=args.name, policy=args.policy, rules=rules) _print_server_group_details(cs, [server_group]) @utils.arg( 'id', metavar='', nargs='+', help=_("Unique ID(s) of the server group to delete.")) def do_server_group_delete(cs, args): """Delete specific server group(s).""" failure_count = 0 for sg in args.id: try: cs.server_groups.delete(sg) print(_("Server group %s has been successfully deleted.") % sg) except Exception as e: failure_count += 1 print(_("Delete for server group %(sg)s failed: %(e)s") % {'sg': sg, 'e': e}) if failure_count == len(args.id): raise exceptions.CommandError(_("Unable to delete any of the " "specified server groups.")) @utils.arg( 'id', metavar='', help=_("Unique ID of the server group to get.")) def do_server_group_get(cs, args): """Get a specific server group.""" server_group = cs.server_groups.get(args.id) _print_server_group_details(cs, [server_group]) def do_version_list(cs, args): """List all API versions.""" result = cs.versions.list() if 'min_version' in dir(result[0]): columns = ["Id", "Status", "Updated", "Min Version", "Version"] else: columns = ["Id", "Status", "Updated"] print(_("Client supported API versions:")) print(_("Minimum version %(v)s") % {'v': novaclient.API_MIN_VERSION.get_string()}) print(_("Maximum version %(v)s") % {'v': novaclient.API_MAX_VERSION.get_string()}) print(_("\nServer supported API versions:")) utils.print_list(result, columns) @api_versions.wraps("2.26") @utils.arg('server', metavar='', help=_('Name or ID of server.')) def do_server_tag_list(cs, args): """Get list of tags from a server.""" server = _find_server(cs, args.server) tags = server.tag_list() formatters = {'Tag': lambda o: o} utils.print_list(tags, ['Tag'], formatters=formatters) @api_versions.wraps("2.26") @utils.arg('server', metavar='', help=_('Name or ID of server.')) @utils.arg('tag', metavar='', nargs='+', help=_('Tag(s) to add.')) def do_server_tag_add(cs, args): """Add one or more tags to a server.""" server = _find_server(cs, args.server) utils.do_action_on_many( lambda t: server.add_tag(t), args.tag, _("Request to add tag %s to specified server has been accepted."), _("Unable to add the specified tag to the server.")) @api_versions.wraps("2.26") @utils.arg('server', metavar='', help=_('Name or ID of server.')) @utils.arg('tags', metavar='', nargs='+', help=_('Tag(s) to set.')) def do_server_tag_set(cs, args): """Set list of tags to a server.""" server = _find_server(cs, args.server) server.set_tags(args.tags) @api_versions.wraps("2.26") @utils.arg('server', metavar='', help=_('Name or ID of server.')) @utils.arg('tag', metavar='', nargs='+', help=_('Tag(s) to delete.')) def do_server_tag_delete(cs, args): """Delete one or more tags from a server.""" server = _find_server(cs, args.server) utils.do_action_on_many( lambda t: server.delete_tag(t), args.tag, _("Request to delete tag %s from specified server has been accepted."), _("Unable to delete the specified tag from the server.")) @api_versions.wraps("2.26") @utils.arg('server', metavar='', help=_('Name or ID of server.')) def do_server_tag_delete_all(cs, args): """Delete all tags from a server.""" server = _find_server(cs, args.server) server.delete_all_tags() @utils.arg('server', metavar='', help='Name or ID of server.') def do_force_delete(cs, args): """Force delete a server.""" utils.find_resource(cs.servers, args.server).force_delete() @utils.arg('server', metavar='', help='Name or ID of server.') def do_restore(cs, args): """Restore a soft-deleted server.""" utils.find_resource(cs.servers, args.server, deleted=True).restore() class EvacuateHostResponse(base.Resource): pass def _server_evacuate(cs, server, args): success = True error_message = "" try: if api_versions.APIVersion('2.68') <= cs.api_version: # if microversion >= 2.68 cs.servers.evacuate(server=server['uuid'], host=args.target_host) elif api_versions.APIVersion('2.29') <= cs.api_version: # if microversion 2.29 - 2.67 force = getattr(args, 'force', None) cs.servers.evacuate(server=server['uuid'], host=args.target_host, force=force) elif api_versions.APIVersion("2.14") <= cs.api_version: # if microversion 2.14 - 2.28 cs.servers.evacuate(server=server['uuid'], host=args.target_host) else: # else microversion 2.0 - 2.13 on_shared_storage = getattr(args, 'on_shared_storage', None) cs.servers.evacuate(server=server['uuid'], host=args.target_host, on_shared_storage=on_shared_storage) except Exception as e: success = False error_message = _("Error while evacuating instance: %s") % e return EvacuateHostResponse(base.Manager, {"server_uuid": server['uuid'], "evacuate_accepted": success, "error_message": error_message}) def _hyper_servers(cs, host, strict): hypervisors = cs.hypervisors.search(host, servers=True) for hyper in hypervisors: if strict and hyper.hypervisor_hostname != host: continue if hasattr(hyper, 'servers'): for server in hyper.servers: yield server if strict: break else: if strict: msg = (_("No hypervisor matching '%s' could be found.") % host) raise exceptions.NotFound(404, msg) @utils.arg('host', metavar='', help='The hypervisor hostname (or pattern) to search for. ' 'WARNING: Use a fully qualified domain name if you only ' 'want to evacuate from a specific host.') @utils.arg( '--target_host', metavar='', default=None, help=_('Name of target host. If no host is specified the scheduler will ' 'select a target.')) @utils.arg( '--on-shared-storage', dest='on_shared_storage', action="store_true", default=False, help=_('Specifies whether all instances files are on shared storage'), start_version='2.0', end_version='2.13') @utils.arg( '--force', dest='force', action='store_true', default=False, help=_('Force an evacuation by not verifying the provided destination ' 'host by the scheduler. WARNING: This could result in failures to ' 'actually evacuate the server to the specified host. It is ' 'recommended to either not specify a host so that the scheduler ' 'will pick one, or specify a host without --force.'), start_version='2.29', end_version='2.67') @utils.arg( '--strict', dest='strict', action='store_true', default=False, help=_('Evacuate host with exact hypervisor hostname match')) def do_host_evacuate(cs, args): """Evacuate all instances from failed host.""" response = [] for server in _hyper_servers(cs, args.host, args.strict): response.append(_server_evacuate(cs, server, args)) utils.print_list(response, [ "Server UUID", "Evacuate Accepted", "Error Message", ]) def _server_live_migrate(cs, server, args): class HostEvacuateLiveResponse(object): def __init__(self, server_uuid, live_migration_accepted, error_message): self.server_uuid = server_uuid self.live_migration_accepted = live_migration_accepted self.error_message = error_message success = True error_message = "" update_kwargs = {} try: # API >= 2.30 if 'force' in args and args.force: update_kwargs['force'] = args.force # API 2.0->2.24 if 'disk_over_commit' in args: update_kwargs['disk_over_commit'] = args.disk_over_commit cs.servers.live_migrate(server['uuid'], args.target_host, args.block_migrate, **update_kwargs) except Exception as e: success = False error_message = _("Error while live migrating instance: %s") % e return HostEvacuateLiveResponse(server['uuid'], success, error_message) @utils.arg('host', metavar='', help='The hypervisor hostname (or pattern) to search for. ' 'WARNING: Use a fully qualified domain name if you only ' 'want to live migrate from a specific host.') @utils.arg( '--target-host', metavar='', default=None, help=_('Name of target host. If no host is specified, the scheduler will ' 'choose one.')) @utils.arg( '--block-migrate', action='store_true', default=False, help=_('Enable block migration. (Default=False)'), start_version="2.0", end_version="2.24") @utils.arg( '--block-migrate', action='store_true', default="auto", help=_('Enable block migration. (Default=auto)'), start_version="2.25") @utils.arg( '--disk-over-commit', action='store_true', default=False, help=_('Enable disk overcommit.'), start_version="2.0", end_version="2.24") @utils.arg( '--max-servers', type=int, dest='max_servers', metavar='', help='Maximum number of servers to live migrate simultaneously') @utils.arg( '--force', dest='force', action='store_true', default=False, help=_('Force a live-migration by not verifying the provided destination ' 'host by the scheduler. WARNING: This could result in failures to ' 'actually live migrate the servers to the specified host. It is ' 'recommended to either not specify a host so that the scheduler ' 'will pick one, or specify a host without --force.'), start_version='2.30', end_version='2.67') @utils.arg( '--strict', dest='strict', action='store_true', default=False, help=_('live Evacuate host with exact hypervisor hostname match')) def do_host_evacuate_live(cs, args): """Live migrate all instances off the specified host to other available hosts. """ response = [] migrating = 0 for server in _hyper_servers(cs, args.host, args.strict): response.append(_server_live_migrate(cs, server, args)) migrating = migrating + 1 if (args.max_servers is not None and migrating >= args.max_servers): break utils.print_list(response, [ "Server UUID", "Live Migration Accepted", "Error Message", ]) class HostServersMigrateResponse(base.Resource): pass def _server_migrate(cs, server): success = True error_message = "" try: cs.servers.migrate(server['uuid']) except Exception as e: success = False error_message = _("Error while migrating instance: %s") % e return HostServersMigrateResponse(base.Manager, {"server_uuid": server['uuid'], "migration_accepted": success, "error_message": error_message}) @utils.arg('host', metavar='', help='The hypervisor hostname (or pattern) to search for. ' 'WARNING: Use a fully qualified domain name if you only ' 'want to cold migrate from a specific host.') @utils.arg( '--strict', dest='strict', action='store_true', default=False, help=_('Migrate host with exact hypervisor hostname match')) def do_host_servers_migrate(cs, args): """Cold migrate all instances off the specified host to other available hosts. """ response = [] for server in _hyper_servers(cs, args.host, args.strict): response.append(_server_migrate(cs, server)) utils.print_list(response, [ "Server UUID", "Migration Accepted", "Error Message", ]) @utils.arg( 'server', metavar='', help=_('Name or UUID of the server to show actions for.'), start_version="2.0", end_version="2.20") @utils.arg( 'server', metavar='', help=_('Name or UUID of the server to show actions for. Only UUID can be ' 'used to show actions for a deleted server.'), start_version="2.21") @utils.arg( 'request_id', metavar='', help=_('Request ID of the action to get.')) def do_instance_action(cs, args): """Show an action.""" if cs.api_version < api_versions.APIVersion("2.21"): server = _find_server(cs, args.server) else: server = _find_server(cs, args.server, raise_if_notfound=False) action_resource = cs.instance_action.get(server, args.request_id) action = action_resource.to_dict() if 'events' in action: action['events'] = pprint.pformat(action['events']) utils.print_dict(action) @api_versions.wraps("2.0", "2.57") @utils.arg( 'server', metavar='', help=_('Name or UUID of the server to list actions for.'), start_version="2.0", end_version="2.20") @utils.arg( 'server', metavar='', help=_('Name or UUID of the server to list actions for. Only UUID can be ' 'used to list actions on a deleted server.'), start_version="2.21") def do_instance_action_list(cs, args): """List actions on a server.""" if cs.api_version < api_versions.APIVersion("2.21"): server = _find_server(cs, args.server) else: server = _find_server(cs, args.server, raise_if_notfound=False) actions = cs.instance_action.list(server) utils.print_list(actions, ['Action', 'Request_ID', 'Message', 'Start_Time'], sortby_index=3) @api_versions.wraps("2.58", "2.65") @utils.arg( 'server', metavar='', help=_('Name or UUID of the server to list actions for. Only UUID can be ' 'used to list actions on a deleted server.')) @utils.arg( '--marker', dest='marker', metavar='', default=None, help=_('The last instance action of the previous page; displays list of ' 'actions after "marker".')) @utils.arg( '--limit', dest='limit', metavar='', type=int, default=None, help=_('Maximum number of instance actions to display. Note that there ' 'is a configurable max limit on the server, and the limit that is ' 'used will be the minimum of what is requested here and what ' 'is configured in the server.')) @utils.arg( '--changes-since', dest='changes_since', metavar='', default=None, help=_('List only instance actions changed later or equal to a certain ' 'point of time. The provided time should be an ISO 8061 formatted ' 'time. e.g. 2016-03-04T06:27:59Z.')) def do_instance_action_list(cs, args): """List actions on a server.""" server = _find_server(cs, args.server, raise_if_notfound=False) if args.changes_since: try: timeutils.parse_isotime(args.changes_since) except ValueError: raise exceptions.CommandError(_('Invalid changes-since value: %s') % args.changes_since) actions = cs.instance_action.list(server, marker=args.marker, limit=args.limit, changes_since=args.changes_since) # TODO(yikun): Output a "Marker" column if there is a next link? utils.print_list(actions, ['Action', 'Request_ID', 'Message', 'Start_Time', 'Updated_At'], sortby_index=3) @api_versions.wraps("2.66") @utils.arg( 'server', metavar='', help=_('Name or UUID of the server to list actions for. Only UUID can be ' 'used to list actions on a deleted server.')) @utils.arg( '--marker', dest='marker', metavar='', default=None, help=_('The last instance action of the previous page; displays list of ' 'actions after "marker".')) @utils.arg( '--limit', dest='limit', metavar='', type=int, default=None, help=_('Maximum number of instance actions to display. Note that there ' 'is a configurable max limit on the server, and the limit that is ' 'used will be the minimum of what is requested here and what ' 'is configured in the server.')) @utils.arg( '--changes-since', dest='changes_since', metavar='', default=None, help=_('List only instance actions changed later or equal to a certain ' 'point of time. The provided time should be an ISO 8061 formatted ' 'time. e.g. 2016-03-04T06:27:59Z.')) @utils.arg( '--changes-before', dest='changes_before', metavar='', default=None, help=_('List only instance actions changed earlier or equal to a certain ' 'point of time. The provided time should be an ISO 8061 formatted ' 'time. e.g. 2016-03-04T06:27:59Z.'), start_version="2.66") def do_instance_action_list(cs, args): """List actions on a server.""" server = _find_server(cs, args.server, raise_if_notfound=False) if args.changes_since: try: timeutils.parse_isotime(args.changes_since) except ValueError: raise exceptions.CommandError(_('Invalid changes-since value: %s') % args.changes_since) # In microversion 2.66 we added ``changes-before`` option # in instance actions. if args.changes_before: try: timeutils.parse_isotime(args.changes_before) except ValueError: raise exceptions.CommandError(_('Invalid changes-before value: %s') % args.changes_before) actions = cs.instance_action.list(server, marker=args.marker, limit=args.limit, changes_since=args.changes_since, changes_before=args.changes_before) utils.print_list(actions, ['Action', 'Request_ID', 'Message', 'Start_Time', 'Updated_At'], sortby_index=3) @utils.arg('host', metavar='', help='The hypervisor hostname (or pattern) to search for. ' 'WARNING: Use a fully qualified domain name if you only ' 'want to update metadata for servers on a specific host.') @utils.arg( 'action', metavar='', choices=['set', 'delete'], help=_("Actions: 'set' or 'delete'")) @utils.arg( 'metadata', metavar='', nargs='+', action='append', default=[], help=_('Metadata to set or delete (only key is necessary on delete)')) @utils.arg( '--strict', dest='strict', action='store_true', default=False, help=_('Set host-meta to the hypervisor with exact hostname match')) def do_host_meta(cs, args): """Set or Delete metadata on all instances of a host.""" for server in _hyper_servers(cs, args.host, args.strict): metadata = _extract_metadata(args) if args.action == 'set': cs.servers.set_meta(server['uuid'], metadata) elif args.action == 'delete': cs.servers.delete_meta(server['uuid'], metadata.keys()) def _print_migrations(cs, migrations): fields = ['Source Node', 'Dest Node', 'Source Compute', 'Dest Compute', 'Dest Host', 'Status', 'Instance UUID', 'Old Flavor', 'New Flavor', 'Created At', 'Updated At'] def old_flavor(migration): return migration.old_instance_type_id def new_flavor(migration): return migration.new_instance_type_id def migration_type(migration): return migration.migration_type formatters = {'Old Flavor': old_flavor, 'New Flavor': new_flavor} # Insert migrations UUID after ID if cs.api_version >= api_versions.APIVersion("2.59"): fields.insert(0, "UUID") if cs.api_version >= api_versions.APIVersion("2.23"): fields.insert(0, "Id") fields.append("Type") formatters.update({"Type": migration_type}) if cs.api_version >= api_versions.APIVersion("2.80"): fields.append("Project ID") fields.append("User ID") utils.print_list(migrations, fields, formatters) @api_versions.wraps("2.0", "2.58") @utils.arg( '--instance-uuid', dest='instance_uuid', metavar='', help=_('Fetch migrations for the given instance.')) @utils.arg( '--host', dest='host', metavar='', help=_('Fetch migrations for the given source or destination host.')) @utils.arg( '--status', dest='status', metavar='', help=_('Fetch migrations for the given status.')) @utils.arg( '--migration-type', dest='migration_type', metavar='', help=_('Filter migrations by type. Valid values are: evacuation, ' 'live-migration, migration (cold), resize')) @utils.arg( '--source-compute', dest='source_compute', metavar='', help=_('Filter migrations by source compute host name.')) def do_migration_list(cs, args): """Print a list of migrations.""" migrations = cs.migrations.list(args.host, args.status, instance_uuid=args.instance_uuid, migration_type=args.migration_type, source_compute=args.source_compute) _print_migrations(cs, migrations) @api_versions.wraps("2.59", "2.65") @utils.arg( '--instance-uuid', dest='instance_uuid', metavar='', help=_('Fetch migrations for the given instance.')) @utils.arg( '--host', dest='host', metavar='', help=_('Fetch migrations for the given source or destination host.')) @utils.arg( '--status', dest='status', metavar='', help=_('Fetch migrations for the given status.')) @utils.arg( '--migration-type', dest='migration_type', metavar='', help=_('Filter migrations by type. Valid values are: evacuation, ' 'live-migration, migration (cold), resize')) @utils.arg( '--source-compute', dest='source_compute', metavar='', help=_('Filter migrations by source compute host name.')) @utils.arg( '--marker', dest='marker', metavar='', default=None, help=_('The last migration of the previous page; displays list of ' 'migrations after "marker". Note that the marker is the ' 'migration UUID.')) @utils.arg( '--limit', dest='limit', metavar='', type=int, default=None, help=_('Maximum number of migrations to display. Note that there is a ' 'configurable max limit on the server, and the limit that is used ' 'will be the minimum of what is requested here and what ' 'is configured in the server.')) @utils.arg( '--changes-since', dest='changes_since', metavar='', default=None, help=_('List only migrations changed later or equal to a certain point ' 'of time. The provided time should be an ISO 8061 formatted time. ' 'e.g. 2016-03-04T06:27:59Z .')) def do_migration_list(cs, args): """Print a list of migrations.""" if args.changes_since: try: timeutils.parse_isotime(args.changes_since) except ValueError: raise exceptions.CommandError(_('Invalid changes-since value: %s') % args.changes_since) migrations = cs.migrations.list(args.host, args.status, instance_uuid=args.instance_uuid, marker=args.marker, limit=args.limit, changes_since=args.changes_since, migration_type=args.migration_type, source_compute=args.source_compute) # TODO(yikun): Output a "Marker" column if there is a next link? _print_migrations(cs, migrations) @api_versions.wraps("2.66") @utils.arg( '--instance-uuid', dest='instance_uuid', metavar='', help=_('Fetch migrations for the given instance.')) @utils.arg( '--host', dest='host', metavar='', help=_('Fetch migrations for the given source or destination host.')) @utils.arg( '--status', dest='status', metavar='', help=_('Fetch migrations for the given status.')) @utils.arg( '--migration-type', dest='migration_type', metavar='', help=_('Filter migrations by type. Valid values are: evacuation, ' 'live-migration, migration (cold), resize')) @utils.arg( '--source-compute', dest='source_compute', metavar='', help=_('Filter migrations by source compute host name.')) @utils.arg( '--marker', dest='marker', metavar='', default=None, help=_('The last migration of the previous page; displays list of ' 'migrations after "marker". Note that the marker is the ' 'migration UUID.')) @utils.arg( '--limit', dest='limit', metavar='', type=int, default=None, help=_('Maximum number of migrations to display. Note that there is a ' 'configurable max limit on the server, and the limit that is used ' 'will be the minimum of what is requested here and what ' 'is configured in the server.')) @utils.arg( '--changes-since', dest='changes_since', metavar='', default=None, help=_('List only migrations changed later or equal to a certain point ' 'of time. The provided time should be an ISO 8061 formatted time. ' 'e.g. 2016-03-04T06:27:59Z .')) @utils.arg( '--changes-before', dest='changes_before', metavar='', default=None, help=_('List only migrations changed earlier or equal to a certain point ' 'of time. The provided time should be an ISO 8061 formatted time. ' 'e.g. 2016-03-04T06:27:59Z .'), start_version="2.66") @utils.arg( '--project-id', dest='project_id', metavar='', default=None, help=_('Filter the migrations by the given project ID.'), start_version='2.80') @utils.arg( '--user-id', dest='user_id', metavar='', default=None, help=_('Filter the migrations by the given user ID.'), start_version='2.80') def do_migration_list(cs, args): """Print a list of migrations.""" if args.changes_since: try: timeutils.parse_isotime(args.changes_since) except ValueError: raise exceptions.CommandError(_('Invalid changes-since value: %s') % args.changes_since) if args.changes_before: try: timeutils.parse_isotime(args.changes_before) except ValueError: raise exceptions.CommandError(_('Invalid changes-before value: %s') % args.changes_before) kwargs = dict( instance_uuid=args.instance_uuid, marker=args.marker, limit=args.limit, changes_since=args.changes_since, changes_before=args.changes_before, migration_type=args.migration_type, source_compute=args.source_compute) if cs.api_version >= api_versions.APIVersion('2.80'): kwargs['project_id'] = args.project_id kwargs['user_id'] = args.user_id migrations = cs.migrations.list(args.host, args.status, **kwargs) _print_migrations(cs, migrations) @utils.arg( '--before', dest='before', metavar='', default=None, help=_("Filters the response by the date and time before which to list " "usage audits. The date and time stamp format is as follows: " "CCYY-MM-DD hh:mm:ss.NNNNNN ex 2015-08-27 09:49:58 or " "2015-08-27 09:49:58.123456.")) def do_instance_usage_audit_log(cs, args): """List/Get server usage audits.""" audit_log = cs.instance_usage_audit_log.get(before=args.before).to_dict() if 'hosts_not_run' in audit_log: audit_log['hosts_not_run'] = pprint.pformat(audit_log['hosts_not_run']) utils.print_dict(audit_log) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/v2/usage.py0000664000175000017500000001244200000000000021465 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. """ Usage interface. """ import oslo_utils from novaclient import api_versions from novaclient import base class Usage(base.Resource): """ Usage contains information about a tenant's physical resource usage """ def __repr__(self): return "" def get(self): fmt = '%Y-%m-%dT%H:%M:%S.%f' if self.start and self.stop and self.tenant_id: # set_loaded() first ... so if we have to bail, we know we tried. self.set_loaded(True) start = oslo_utils.timeutils.parse_strtime(self.start, fmt=fmt) stop = oslo_utils.timeutils.parse_strtime(self.stop, fmt=fmt) new = self.manager.get(self.tenant_id, start, stop) if new: self._add_details(new._info) self.append_request_ids(new.request_ids) class UsageManager(base.ManagerWithFind): """ Manage :class:`Usage` resources. """ resource_class = Usage usage_prefix = 'os-simple-tenant-usage' def _usage_query(self, start, end, marker=None, limit=None, detailed=None): query = "?start=%s&end=%s" % (start.isoformat(), end.isoformat()) if limit: query = "%s&limit=%s" % (query, int(limit)) if marker: query = "%s&marker=%s" % (query, marker) if detailed is not None: query = "%s&detailed=%s" % (query, int(bool(detailed))) return query @api_versions.wraps("2.0", "2.39") def list(self, start, end, detailed=False): """ Get usage for all tenants :param start: :class:`datetime.datetime` Start date in UTC :param end: :class:`datetime.datetime` End date in UTC :param detailed: Whether to include information about each instance whose usage is part of the report :rtype: list of :class:`Usage`. """ query_string = self._usage_query(start, end, detailed=detailed) url = '/%s%s' % (self.usage_prefix, query_string) return self._list(url, 'tenant_usages') @api_versions.wraps("2.40") def list(self, start, end, detailed=False, marker=None, limit=None): """ Get usage for all tenants :param start: :class:`datetime.datetime` Start date in UTC :param end: :class:`datetime.datetime` End date in UTC :param detailed: Whether to include information about each instance whose usage is part of the report :param marker: Begin returning usage data for instances that appear later in the instance list than that represented by this instance UUID (optional). :param limit: Maximum number of instances to include in the usage (optional). Note the API server has a configurable default limit. If no limit is specified here or limit is larger than default, the default limit will be used. :rtype: list of :class:`Usage`. """ query_string = self._usage_query(start, end, marker, limit, detailed) url = '/%s%s' % (self.usage_prefix, query_string) return self._list(url, 'tenant_usages') @api_versions.wraps("2.0", "2.39") def get(self, tenant_id, start, end): """ Get usage for a specific tenant. :param tenant_id: Tenant ID to fetch usage for :param start: :class:`datetime.datetime` Start date in UTC :param end: :class:`datetime.datetime` End date in UTC :rtype: :class:`Usage` """ query_string = self._usage_query(start, end) url = '/%s/%s%s' % (self.usage_prefix, tenant_id, query_string) return self._get(url, 'tenant_usage') @api_versions.wraps("2.40") def get(self, tenant_id, start, end, marker=None, limit=None): """ Get usage for a specific tenant. :param tenant_id: Tenant ID to fetch usage for :param start: :class:`datetime.datetime` Start date in UTC :param end: :class:`datetime.datetime` End date in UTC :param marker: Begin returning usage data for instances that appear later in the instance list than that represented by this instance UUID (optional). :param limit: Maximum number of instances to include in the usage (optional). Note the API server has a configurable default limit. If no limit is specified here or limit is larger than default, the default limit will be used. :rtype: :class:`Usage` """ query_string = self._usage_query(start, end, marker, limit) url = '/%s/%s%s' % (self.usage_prefix, tenant_id, query_string) return self._get(url, 'tenant_usage') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/v2/versions.py0000664000175000017500000001000400000000000022221 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. """ version interface """ from urllib import parse from novaclient import base from novaclient import exceptions as exc class Version(base.Resource): """ Compute REST API information """ def __repr__(self): return "" class VersionManager(base.ManagerWithFind): resource_class = Version def _get_current(self): """Returns info about current version.""" # TODO(sdague): we've now got to make up to 3 HTTP requests to # determine what version we are running, due to differences in # deployments and versions. We really need to cache the # results of this per endpoint and keep the results of it for # some reasonable TTL (like 24 hours) to reduce our round trip # traffic. try: # Assume that the value of get_endpoint() is something # we can get the version of. This is a 404 for Nova < # Mitaka if the service catalog contains project_id. # # TODO(sdague): add microversion for when this will # change url = "%s" % self.api.client.get_endpoint() return self._get(url, "version") except exc.NotFound: # If that's a 404, we can instead try hacking together # an endpoint root url by chopping off the last 2 /s. # This is kind of gross, but we've had this baked in # so long people got used to this hard coding. # # NOTE(sdague): many service providers don't really # implement GET / in the expected way, if we do a GET # /v2 that's actually a 300 redirect to # /v2/... because of how paste works. So adding the # end slash is really important. url = "%s/" % url.rsplit("/", 1)[0] return self._get(url, "version") def get_current(self): try: return self._get_current() except exc.Unauthorized: # NOTE(sdague): RAX's repose configuration blocks access to the # versioned endpoint, which is definitely non-compliant behavior. # However, there is no defcore test for this yet. Remove this code # block once we land things in defcore. return None def list(self): """List all versions.""" endpoint = self.api.client.get_endpoint() url = parse.urlparse(endpoint) # NOTE(andreykurilin): endpoint URL has at least 3 formats: # 1. the classic (legacy) endpoint: # http://{host}:{optional_port}/v{2 or 2.1}/{project-id} # 2. starting from microversion 2.18 project-id is not included: # http://{host}:{optional_port}/v{2 or 2.1} # 3. under wsgi: # http://{host}:{optional_port}/compute/v{2 or 2.1} if (url.path.endswith("v2") or "/v2/" in url.path or url.path.endswith("v2.1") or "/v2.1/" in url.path): # this way should handle all 3 possible formats path = url.path[:url.path.rfind("/v2")] version_url = '%s://%s%s' % (url.scheme, url.netloc, path) else: # NOTE(andreykurilin): probably, it is one of the next cases: # * https://compute.example.com/ # * https://example.com/compute # leave as is without cropping. version_url = endpoint return self._list(version_url, "versions") ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/novaclient/v2/volumes.py0000664000175000017500000002115400000000000022053 0ustar00zuulzuul00000000000000# Copyright 2011 Denali Systems, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Volume interface """ import warnings from novaclient import api_versions from novaclient import base class Volume(base.Resource): """ A volume is an extra block level storage to the OpenStack instances. """ NAME_ATTR = 'display_name' def __repr__(self): return "" % self.id class VolumeManager(base.Manager): """ Manage :class:`Volume` resources. This is really about volume attachments. """ resource_class = Volume @staticmethod def _get_request_body_for_create(volume_id, device=None, tag=None, delete_on_termination=False): body = {'volumeAttachment': {'volumeId': volume_id}} if device is not None: body['volumeAttachment']['device'] = device if tag is not None: body['volumeAttachment']['tag'] = tag if delete_on_termination: body['volumeAttachment']['delete_on_termination'] = ( delete_on_termination) return body @api_versions.wraps("2.0", "2.48") def create_server_volume(self, server_id, volume_id, device=None): """ Attach a volume identified by the volume ID to the given server ID :param server_id: The ID of the server :param volume_id: The ID of the volume to attach. :param device: The device name (optional) :rtype: :class:`Volume` """ return self._create( "/servers/%s/os-volume_attachments" % server_id, VolumeManager._get_request_body_for_create(volume_id, device), "volumeAttachment") @api_versions.wraps("2.49", "2.78") def create_server_volume(self, server_id, volume_id, device=None, tag=None): """ Attach a volume identified by the volume ID to the given server ID :param server_id: The ID of the server :param volume_id: The ID of the volume to attach. :param device: The device name (optional) :param tag: The tag (optional) :rtype: :class:`Volume` """ return self._create( "/servers/%s/os-volume_attachments" % server_id, VolumeManager._get_request_body_for_create(volume_id, device, tag), "volumeAttachment") @api_versions.wraps("2.79") def create_server_volume(self, server_id, volume_id, device=None, tag=None, delete_on_termination=False): """ Attach a volume identified by the volume ID to the given server ID :param server_id: The ID of the server. :param volume_id: The ID of the volume to attach. :param device: The device name (optional). :param tag: The tag (optional). :param delete_on_termination: Marked whether to delete the attached volume when the server is deleted (optional). :rtype: :class:`Volume` """ return self._create( "/servers/%s/os-volume_attachments" % server_id, VolumeManager._get_request_body_for_create(volume_id, device, tag, delete_on_termination), "volumeAttachment") @api_versions.wraps("2.0", "2.84") def update_server_volume(self, server_id, src_volid, dest_volid): """ Swaps the existing volume attachment to point to a new volume. Takes a server, a source (attached) volume and a destination volume and performs a hypervisor assisted data migration from src to dest volume, detaches the original (source) volume and attaches the new destination volume. Note that not all backing hypervisor drivers support this operation and it may be disabled via policy. :param server_id: The ID of the server :param source_volume: The ID of the src volume :param dest_volume: The ID of the destination volume :rtype: :class:`Volume` """ body = {'volumeAttachment': {'volumeId': dest_volid}} return self._update("/servers/%s/os-volume_attachments/%s" % (server_id, src_volid,), body, "volumeAttachment") @api_versions.wraps("2.85") def update_server_volume(self, server_id, src_volid, dest_volid, delete_on_termination=None): """ Swaps the existing volume attachment to point to a new volume. Takes a server, a source (attached) volume and a destination volume and performs a hypervisor assisted data migration from src to dest volume, detaches the original (source) volume and attaches the new destination volume. Note that not all backing hypervisor drivers support this operation and it may be disabled via policy. :param server_id: The ID of the server :param source_volume: The ID of the src volume :param dest_volume: The ID of the destination volume :param delete_on_termination: Marked whether to delete the attached volume when the server is deleted (optional). :rtype: :class:`Volume` """ body = {'volumeAttachment': {'volumeId': dest_volid}} if delete_on_termination is not None: body['volumeAttachment']['delete_on_termination'] = ( delete_on_termination) return self._update("/servers/%s/os-volume_attachments/%s" % (server_id, src_volid), body, "volumeAttachment") def get_server_volume(self, server_id, volume_id=None, attachment_id=None): """ Get the volume identified by the volume ID, that is attached to the given server ID :param server_id: The ID of the server :param volume_id: The ID of the volume to attach :rtype: :class:`Volume` """ if attachment_id is not None and volume_id is not None: raise TypeError("You cannot specify both volume_id " "and attachment_id arguments.") elif attachment_id is not None: warnings.warn("attachment_id argument " "of volumes.get_server_volume " "method is deprecated in favor " "of volume_id.") volume_id = attachment_id if volume_id is None: raise TypeError("volume_id is required argument.") return self._get("/servers/%s/os-volume_attachments/%s" % (server_id, volume_id,), "volumeAttachment") def get_server_volumes(self, server_id): """ Get a list of all the attached volumes for the given server ID :param server_id: The ID of the server :rtype: list of :class:`Volume` """ return self._list("/servers/%s/os-volume_attachments" % server_id, "volumeAttachments") def delete_server_volume(self, server_id, volume_id=None, attachment_id=None): """ Detach a volume identified by the volume ID from the given server :param server_id: The ID of the server :param volume_id: The ID of the volume to attach :returns: An instance of novaclient.base.TupleWithMeta """ if attachment_id is not None and volume_id is not None: raise TypeError("You cannot specify both volume_id " "and attachment_id arguments.") elif attachment_id is not None: warnings.warn("attachment_id argument " "of volumes.delete_server_volume " "method is deprecated in favor " "of volume_id.") volume_id = attachment_id if volume_id is None: raise TypeError("volume_id is required argument.") return self._delete("/servers/%s/os-volume_attachments/%s" % (server_id, volume_id,)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/pyproject.toml0000664000175000017500000000014400000000000020226 0ustar00zuulzuul00000000000000[build-system] requires = ["pbr>=5.7.0", "setuptools>=64.0.0", "wheel"] build-backend = "pbr.build" ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1725030944.8244665 python-novaclient-18.7.0/python_novaclient.egg-info/0000775000175000017500000000000000000000000022550 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030944.0 python-novaclient-18.7.0/python_novaclient.egg-info/PKG-INFO0000664000175000017500000000600200000000000023643 0ustar00zuulzuul00000000000000Metadata-Version: 1.2 Name: python-novaclient Version: 18.7.0 Summary: Client library for OpenStack Compute API Home-page: https://docs.openstack.org/python-novaclient/latest Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: Apache License, Version 2.0 Description: ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/python-novaclient.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on ============================================ Python bindings to the OpenStack Compute API ============================================ .. image:: https://img.shields.io/pypi/v/python-novaclient.svg :target: https://pypi.org/project/python-novaclient/ :alt: Latest Version This is a client for the OpenStack Compute API. It provides a Python API (the ``novaclient`` module) and a deprecated command-line script (``nova``). The Python API implements 100% of the OpenStack Compute API. * License: Apache License, Version 2.0 * `PyPi`_ - package installation * `Online Documentation`_ * `Launchpad project`_ - release management * `Blueprints`_ - feature specifications * `Bugs`_ - issue tracking * `Source`_ * `Specs`_ * `How to Contribute`_ * `Release Notes`_ .. _PyPi: https://pypi.org/project/python-novaclient .. _Online Documentation: https://docs.openstack.org/python-novaclient/latest .. _Launchpad project: https://launchpad.net/python-novaclient .. _Blueprints: https://blueprints.launchpad.net/python-novaclient .. _Bugs: https://bugs.launchpad.net/python-novaclient .. _Source: https://opendev.org/openstack/python-novaclient .. _How to Contribute: https://docs.opendev.org/opendev/infra-manual/latest/developers.html .. _Specs: https://specs.openstack.org/openstack/nova-specs/ .. _Release Notes: https://docs.openstack.org/releasenotes/python-novaclient Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Environment :: OpenStack Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Information Technology Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: Implementation :: CPython Requires-Python: >=3.8 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030944.0 python-novaclient-18.7.0/python_novaclient.egg-info/SOURCES.txt0000664000175000017500000003727300000000000024450 0ustar00zuulzuul00000000000000.coveragerc .mailmap .pre-commit-config.yaml .stestr.conf .zuul.yaml AUTHORS CONTRIBUTING.rst ChangeLog HACKING.rst LICENSE README.rst bindep.txt pyproject.toml requirements.txt setup.cfg setup.py test-requirements.txt tox.ini doc/requirements.txt doc/source/conf.py doc/source/index.rst doc/source/_extra/.htaccess doc/source/cli/index.rst doc/source/cli/nova.rst doc/source/contributor/contributing.rst doc/source/contributor/deprecation-policy.rst doc/source/contributor/index.rst doc/source/contributor/microversions.rst doc/source/contributor/testing.rst doc/source/reference/index.rst doc/source/user/index.rst doc/source/user/python-api.rst doc/source/user/shell.rst doc/test/redirect-tests.txt novaclient/__init__.py novaclient/api_versions.py novaclient/base.py novaclient/client.py novaclient/crypto.py novaclient/exceptions.py novaclient/extension.py novaclient/i18n.py novaclient/shell.py novaclient/utils.py novaclient/tests/__init__.py novaclient/tests/functional/README.rst novaclient/tests/functional/__init__.py novaclient/tests/functional/base.py novaclient/tests/functional/clouds.yaml.sample novaclient/tests/functional/test_auth.py novaclient/tests/functional/api/__init__.py novaclient/tests/functional/api/test_servers.py novaclient/tests/functional/hooks/check_resources.py novaclient/tests/functional/v2/__init__.py novaclient/tests/functional/v2/fake_crypto.py novaclient/tests/functional/v2/test_aggregates.py novaclient/tests/functional/v2/test_consoles.py novaclient/tests/functional/v2/test_device_tagging.py novaclient/tests/functional/v2/test_extended_attributes.py novaclient/tests/functional/v2/test_flavor.py novaclient/tests/functional/v2/test_flavor_access.py novaclient/tests/functional/v2/test_hypervisors.py novaclient/tests/functional/v2/test_image_meta.py novaclient/tests/functional/v2/test_instance_action.py novaclient/tests/functional/v2/test_instance_usage_audit_log.py novaclient/tests/functional/v2/test_instances.py novaclient/tests/functional/v2/test_keypairs.py novaclient/tests/functional/v2/test_migrations.py novaclient/tests/functional/v2/test_networks.py novaclient/tests/functional/v2/test_os_services.py novaclient/tests/functional/v2/test_quota_classes.py novaclient/tests/functional/v2/test_quotas.py novaclient/tests/functional/v2/test_resize.py novaclient/tests/functional/v2/test_server_groups.py novaclient/tests/functional/v2/test_servers.py novaclient/tests/functional/v2/test_trigger_crash_dump.py novaclient/tests/functional/v2/test_usage.py novaclient/tests/functional/v2/legacy/__init__.py novaclient/tests/functional/v2/legacy/test_consoles.py novaclient/tests/functional/v2/legacy/test_extended_attributes.py novaclient/tests/functional/v2/legacy/test_flavor_access.py novaclient/tests/functional/v2/legacy/test_hypervisors.py novaclient/tests/functional/v2/legacy/test_instances.py novaclient/tests/functional/v2/legacy/test_keypairs.py novaclient/tests/functional/v2/legacy/test_os_services.py novaclient/tests/functional/v2/legacy/test_quotas.py novaclient/tests/functional/v2/legacy/test_readonly_nova.py novaclient/tests/functional/v2/legacy/test_server_groups.py novaclient/tests/functional/v2/legacy/test_servers.py novaclient/tests/functional/v2/legacy/test_usage.py novaclient/tests/unit/__init__.py novaclient/tests/unit/fake_actions_module.py novaclient/tests/unit/fakes.py novaclient/tests/unit/idfake.pem novaclient/tests/unit/test_api_versions.py novaclient/tests/unit/test_base.py novaclient/tests/unit/test_client.py novaclient/tests/unit/test_crypto.py novaclient/tests/unit/test_discover.py novaclient/tests/unit/test_exceptions.py novaclient/tests/unit/test_shell.py novaclient/tests/unit/test_utils.py novaclient/tests/unit/utils.py novaclient/tests/unit/fixture_data/__init__.py novaclient/tests/unit/fixture_data/agents.py novaclient/tests/unit/fixture_data/aggregates.py novaclient/tests/unit/fixture_data/availability_zones.py novaclient/tests/unit/fixture_data/base.py novaclient/tests/unit/fixture_data/client.py novaclient/tests/unit/fixture_data/floatingips.py novaclient/tests/unit/fixture_data/hypervisors.py novaclient/tests/unit/fixture_data/images.py novaclient/tests/unit/fixture_data/keypairs.py novaclient/tests/unit/fixture_data/limits.py novaclient/tests/unit/fixture_data/quotas.py novaclient/tests/unit/fixture_data/server_groups.py novaclient/tests/unit/fixture_data/server_migrations.py novaclient/tests/unit/fixture_data/servers.py novaclient/tests/unit/v2/__init__.py novaclient/tests/unit/v2/fakes.py novaclient/tests/unit/v2/test_agents.py novaclient/tests/unit/v2/test_aggregates.py novaclient/tests/unit/v2/test_assisted_volume_snapshots.py novaclient/tests/unit/v2/test_availability_zone.py novaclient/tests/unit/v2/test_client.py novaclient/tests/unit/v2/test_flavor_access.py novaclient/tests/unit/v2/test_flavors.py novaclient/tests/unit/v2/test_hypervisors.py novaclient/tests/unit/v2/test_images.py novaclient/tests/unit/v2/test_instance_actions.py novaclient/tests/unit/v2/test_instance_usage_audit_log.py novaclient/tests/unit/v2/test_keypairs.py novaclient/tests/unit/v2/test_limits.py novaclient/tests/unit/v2/test_migrations.py novaclient/tests/unit/v2/test_quota_classes.py novaclient/tests/unit/v2/test_quotas.py novaclient/tests/unit/v2/test_server_external_events.py novaclient/tests/unit/v2/test_server_groups.py novaclient/tests/unit/v2/test_server_migrations.py novaclient/tests/unit/v2/test_servers.py novaclient/tests/unit/v2/test_services.py novaclient/tests/unit/v2/test_shell.py novaclient/tests/unit/v2/test_usage.py novaclient/tests/unit/v2/test_versions.py novaclient/tests/unit/v2/test_volumes.py novaclient/tests/unit/v2/testfile.txt novaclient/v2/__init__.py novaclient/v2/agents.py novaclient/v2/aggregates.py novaclient/v2/assisted_volume_snapshots.py novaclient/v2/availability_zones.py novaclient/v2/client.py novaclient/v2/flavor_access.py novaclient/v2/flavors.py novaclient/v2/hypervisors.py novaclient/v2/images.py novaclient/v2/instance_action.py novaclient/v2/instance_usage_audit_log.py novaclient/v2/keypairs.py novaclient/v2/limits.py novaclient/v2/migrations.py novaclient/v2/networks.py novaclient/v2/quota_classes.py novaclient/v2/quotas.py novaclient/v2/server_external_events.py novaclient/v2/server_groups.py novaclient/v2/server_migrations.py novaclient/v2/servers.py novaclient/v2/services.py novaclient/v2/shell.py novaclient/v2/usage.py novaclient/v2/versions.py novaclient/v2/volumes.py python_novaclient.egg-info/PKG-INFO python_novaclient.egg-info/SOURCES.txt python_novaclient.egg-info/dependency_links.txt python_novaclient.egg-info/entry_points.txt python_novaclient.egg-info/not-zip-safe python_novaclient.egg-info/pbr.json python_novaclient.egg-info/requires.txt python_novaclient.egg-info/top_level.txt releasenotes/notes/.placeholder releasenotes/notes/add-filter-to-nova-list-831dcbb34420fb29.yaml releasenotes/notes/add-osprofiler-support-cc9dd228242e9919.yaml releasenotes/notes/add-support-for-volume-backed-rebuild-6a32d9d88fed6b4a.yaml releasenotes/notes/add-user-agent-string-db77210dfd3ec671.yaml releasenotes/notes/bp-add-locked-reason-3f136db97b820c73.yaml releasenotes/notes/bp-cold-migration-with-target-queens-e361d4ae977aa396.yaml releasenotes/notes/bp-deprecate-image-meta-proxy-api-1483b75cf73b021e.yaml releasenotes/notes/bp-handling-down-cell-728cdb1efd1ea75b.yaml releasenotes/notes/bp-keypair-generation-removal-1b5d84a8906d3918.yaml releasenotes/notes/bp-more-migration-list-filters-6c801896c7ee5cdc.yaml releasenotes/notes/bp-unshelve-to-host-b220131a00dff8a2.yaml releasenotes/notes/bug-1669140-c21d045491201352.yaml releasenotes/notes/bug-1744118-0b064d7062117317.yaml releasenotes/notes/bug-1764420-flavor-delete-output-7b80f73deee5a869.yaml releasenotes/notes/bug-1767287-cc28d60d9e59f9bd.yaml releasenotes/notes/bug-1778536-a1b5d65a0d4ad622.yaml releasenotes/notes/bug-1825061-2beb95db4d6df0cb.yaml releasenotes/notes/bug-1845322-463ee407b60131c9.yaml releasenotes/notes/clarify-project-id-variable-5832698315000438.yaml releasenotes/notes/deprecate-agent-d0f58718ad1782f6.yaml releasenotes/notes/deprecate-baremetal-d67f58a2986b3565.yaml releasenotes/notes/deprecate-cellsv1-extension-16482759993d112f.yaml releasenotes/notes/deprecate-certs-1558d8e3b7888938.yaml releasenotes/notes/deprecate-cli-75074850847a8452.yaml releasenotes/notes/deprecate-cloudpipe-670202797fdf97b6.yaml releasenotes/notes/deprecate-connection-pool-arg-cef35346d5ebf40c.yaml releasenotes/notes/deprecate-force-option-7116d792bba17f09.yaml releasenotes/notes/deprecate-instance-name-option-bc76629d28f1d456.yaml releasenotes/notes/deprecate-network-cli-f0a539528be594d3.yaml releasenotes/notes/deprecate-no-cache-arg-7814806b4f79c1b9.yaml releasenotes/notes/deprecate-proxy-args-a3f4e224f7664ff8.yaml releasenotes/notes/deprecate-service-binary-arg-2d5c446f5a2409a7.yaml releasenotes/notes/deprecate-volume-service-name-arg-4c65e8866f9624dd.yaml releasenotes/notes/deprecate_cell_name_arg-eb34cb7c43cfcb89.yaml releasenotes/notes/deprecate_contrib_extensions-0ec70c070b09eedb.yaml releasenotes/notes/drop-deprecated-aggregate-update-args-17bd019f4be34b18.yaml releasenotes/notes/drop-python2-support-d3a1bedc75445edc.yaml releasenotes/notes/fix-booting-with-multiple-nics-c6e5885b948d35ba.yaml releasenotes/notes/fix-raw-python-error-debd3edb17c2f675.yaml releasenotes/notes/fix-rebuild-userdata-9315e5784feb8ba9.yaml releasenotes/notes/fix-tag-attribute-disappearing-25483a80f548ef35.yaml releasenotes/notes/fix-token-auth-6c48c63a759f51d5.yaml releasenotes/notes/fixed-ListExtResource-given-in-place-of-ListExtManager-a759a27079d16a44.yaml releasenotes/notes/get-list-metadata-8afcc8f32ad82dda.yaml releasenotes/notes/get-rid-off-redundant-methods-47e679c13e88f28a.yaml releasenotes/notes/global_request_id-26f4e4301f84d403.yaml releasenotes/notes/image-api-deprecation-41944dc6fc024918.yaml releasenotes/notes/instance-uuid-flag-in-migration-list-5d2fed7657d3def5.yaml releasenotes/notes/interface-attach-output-02d633d9b2a60da1.yaml releasenotes/notes/keystoneauth-8ec1e6be14cdbae3.yaml releasenotes/notes/log-request-id-ce106497e0520fad.yaml releasenotes/notes/make-console-public-0c776bfda240cd9d.yaml releasenotes/notes/microversion-2.37-d03da96406a45e67.yaml releasenotes/notes/microversion-v2_28-abf653ae5cf5c4a9.yaml releasenotes/notes/microversion-v2_31-3e1a16eb5eb53f59.yaml releasenotes/notes/microversion-v2_33-10d12ea3b25839e8.yaml releasenotes/notes/microversion-v2_34-a9c5601811152964.yaml releasenotes/notes/microversion-v2_35-537619a43278fbb5.yaml releasenotes/notes/microversion-v2_38-0618fe2b3c7f96f9.yaml releasenotes/notes/microversion-v2_40-484adba0806b08bf.yaml releasenotes/notes/microversion-v2_41-6df7a5a66a9ded35.yaml releasenotes/notes/microversion-v2_43-76db2ac463b431e4.yaml releasenotes/notes/microversion-v2_44-d60c8834e436ad3d.yaml releasenotes/notes/microversion-v2_45-1bfcae3914280534.yaml releasenotes/notes/microversion-v2_47-4aa54fbbd519e421.yaml releasenotes/notes/microversion-v2_49-56bde596ee13366d.yaml releasenotes/notes/microversion-v2_50-4f484658d66d01aa.yaml releasenotes/notes/microversion-v2_52-2fe81b3bf2e4b4ea.yaml releasenotes/notes/microversion-v2_53-3463b546a38c5f84.yaml releasenotes/notes/microversion-v2_54-6c7ccb61eff6cb6d.yaml releasenotes/notes/microversion-v2_55-flavor-description-a93718b31f1f0f39.yaml releasenotes/notes/microversion-v2_57-acae2ee11ddae4fb.yaml releasenotes/notes/microversion-v2_58-327c1031ebfe4a3a.yaml releasenotes/notes/microversion-v2_59-4160c852d7d8812d.yaml releasenotes/notes/microversion-v2_61-9a8faa02fddf9ed6.yaml releasenotes/notes/microversion-v2_62-479a23f0d4307500.yaml releasenotes/notes/microversion-v2_63-cd058a9145550cae.yaml releasenotes/notes/microversion-v2_64-66366829ec65bea4.yaml.yaml releasenotes/notes/microversion-v2_65-3c89c5932f4391cb.yaml releasenotes/notes/microversion-v2_66-cda5d6dc31b56b46.yaml releasenotes/notes/microversion-v2_67-da6d9b12730b8562.yaml releasenotes/notes/microversion-v2_71-a87b4bb4205c46e2.yaml releasenotes/notes/microversion-v2_72-d910ce07ec3948d6.yaml releasenotes/notes/microversion-v2_74-43b128fe6b84b630.yaml releasenotes/notes/microversion-v2_75-ea7fa3ba1396edea.yaml releasenotes/notes/microversion-v2_77-ffee30c180aa4dbe.yaml releasenotes/notes/microversion-v2_78-77a12630e668c2ae.yaml releasenotes/notes/microversion-v2_79-f13bc0414743dc16.yaml releasenotes/notes/microversion-v2_80-c2394316f9212865.yaml releasenotes/notes/microversion-v2_81-3ddd8e2fc7e45030.yaml releasenotes/notes/microversion-v2_85-230931f88c4f1d52.yaml releasenotes/notes/microversion-v2_88-d91136020e3a3621.yaml releasenotes/notes/microversion-v2_90-259779668e67dfb5.yaml releasenotes/notes/microversion-v2_94-5368d5dd7c5f6484.yaml releasenotes/notes/microversion-v2_95-3c6ad46be2656684.yaml releasenotes/notes/microversion-v2_96-a50af976133de0ab.yaml releasenotes/notes/microversion_v2_70-09cbe0933b3a9335.yaml releasenotes/notes/microversion_v2_89-af6223273b2bdfb0.yaml releasenotes/notes/no-glance-proxy-5c13001a4b13e8ce.yaml releasenotes/notes/no-neutron-proxy-18fd54febe939a6b.yaml releasenotes/notes/pike-rm-deprecated-img-d58e9ae2d774cbfc.yaml releasenotes/notes/pike-rm-deprecated-net-272aeb62b329a5bc.yaml releasenotes/notes/remove-auth-system-b2cd247b8a312b72.yaml releasenotes/notes/remove-certs-4333342189200d91.yaml releasenotes/notes/remove-cloudpipe-6c790c57dc3796eb.yaml releasenotes/notes/remove-contrib-8b5e35ac8dddbab3.yaml releasenotes/notes/remove-deprecated-cellsv1-extentions-commands-4b26c826ad5194ca.yaml releasenotes/notes/remove-deprecated-methods-train-c450fe317c90d7f0.yaml releasenotes/notes/remove-deprecated-option-14.0.0-c6d7189938f5f063.yaml releasenotes/notes/remove-deprecated-option-in-3.3.0-82a413157838570d.yaml releasenotes/notes/remove-deprecated-option-in-9.0.0-bc76629d28f1d4c4.yaml releasenotes/notes/remove-hosts-d08855550c40b9c6.yaml releasenotes/notes/remove-py26-support-f31379e86f40d975.yaml releasenotes/notes/remove-run_tests.sh-3bdcaee4d388177a.yaml releasenotes/notes/remove-service-binary-arg-ec2838214c8c7abc.yaml releasenotes/notes/remove-virt-interfaces-add-rm-fixed-floating-398c905d9c91cca8.yaml releasenotes/notes/remove_api_v_1_1-88b3f18ce1423b46.yaml releasenotes/notes/rename-apikey-to-password-735588d841efa49e.yaml releasenotes/notes/rename-bypass-url-42cd96956a6bc6dc.yaml releasenotes/notes/restrict-args-for-novaclient-ep-491098c3634365be.yaml releasenotes/notes/restrict-direct-use-of-v2client-c8e1ee2afefec5a1.yaml releasenotes/notes/restrict-interface-parameter-e5fe166f39ba0935.yaml releasenotes/notes/return-request-id-to-caller-52c5423794b33f8b.yaml releasenotes/notes/rm-baremetal-cli-api-fbc8c242d48cd2fb.yaml releasenotes/notes/rm-deprecated-commands-options-ocata-00f249810e5bdf97.yaml releasenotes/notes/search-hypervisor-detailed-352f3ac70d42fe6e.yaml releasenotes/notes/server-networks-sorted-1d3a7f1c1f88e846.yaml releasenotes/notes/show-instance-usage-audit-logs-7826b411fac1283b.yaml releasenotes/notes/strict_hostname_match-f37243f0520a09a2.yaml releasenotes/notes/switch-to-sessionclient-aa49d16599fea570.yaml releasenotes/notes/volume-cli-removal-ffcb94421a356042.yaml releasenotes/source/2023.1.rst releasenotes/source/2023.2.rst releasenotes/source/2024.1.rst releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/liberty.rst releasenotes/source/mitaka.rst releasenotes/source/newton.rst releasenotes/source/ocata.rst releasenotes/source/pike.rst releasenotes/source/queens.rst releasenotes/source/rocky.rst releasenotes/source/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/_static/.placeholder releasenotes/source/_templates/.placeholder releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po releasenotes/source/locale/fr/LC_MESSAGES/releasenotes.po tools/nova.bash_completion tools/nova.zsh_completion././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030944.0 python-novaclient-18.7.0/python_novaclient.egg-info/dependency_links.txt0000664000175000017500000000000100000000000026616 0ustar00zuulzuul00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030944.0 python-novaclient-18.7.0/python_novaclient.egg-info/entry_points.txt0000664000175000017500000000006000000000000026042 0ustar00zuulzuul00000000000000[console_scripts] nova = novaclient.shell:main ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030944.0 python-novaclient-18.7.0/python_novaclient.egg-info/not-zip-safe0000664000175000017500000000000100000000000024776 0ustar00zuulzuul00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030944.0 python-novaclient-18.7.0/python_novaclient.egg-info/pbr.json0000664000175000017500000000005700000000000024230 0ustar00zuulzuul00000000000000{"git_version": "42ded0ad", "is_release": true}././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030944.0 python-novaclient-18.7.0/python_novaclient.egg-info/requires.txt0000664000175000017500000000022400000000000025146 0ustar00zuulzuul00000000000000PrettyTable>=0.7.2 iso8601>=0.1.11 keystoneauth1>=3.5.0 oslo.i18n>=3.15.3 oslo.serialization>=2.20.0 oslo.utils>=3.33.0 pbr>=3.0.0 stevedore>=2.0.1 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030944.0 python-novaclient-18.7.0/python_novaclient.egg-info/top_level.txt0000664000175000017500000000001300000000000025274 0ustar00zuulzuul00000000000000novaclient ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1725030944.780464 python-novaclient-18.7.0/releasenotes/0000775000175000017500000000000000000000000020004 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1725030944.856468 python-novaclient-18.7.0/releasenotes/notes/0000775000175000017500000000000000000000000021134 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/.placeholder0000664000175000017500000000000000000000000023405 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/add-filter-to-nova-list-831dcbb34420fb29.yaml0000664000175000017500000000105100000000000030566 0ustar00zuulzuul00000000000000--- features: - | Added the following filters support for the ``nova list`` command, these filters are admin-only restricted until microversion 2.82: * --availability-zone * --config-drive * --no-config-drive * --key-name * --power-state * --task-state * --vm-state * --progress Existing user filter will be available to non admin since `microversion 2.83`_. .. _microversion 2.83: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id76 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/add-osprofiler-support-cc9dd228242e9919.yaml0000664000175000017500000000230500000000000030615 0ustar00zuulzuul00000000000000--- prelude: > OSprofiler support was added to the client. That makes possible to trigger Nova operation trace generation from the CLI. features: - A new ``--profile`` option was added to allow Nova profiling from the CLI. If the user wishes to trace a nova boot request he or she needs to type the following command -- ``nova --profile boot --image --flavor ``, where ``secret_key`` should match one of the keys defined in nova.conf. As a result of this operation additional information regarding ``trace_id`` will be printed, that can be used to generate human-friendly html report -- ``osprofiler trace show --html --out trace.html``. To enable profiling, user needs to have osprofiler installed in the local environment via ``pip install osprofiler``. security: - OSprofiler support, that was added during the Ocata release cycle, requires passing of trace information between various OpenStack services. This information is signed by one of the HMAC keys defined in nova.conf file. That means that only someone who knows this key is able to send the proper header to trigger profiling. ././@PaxHeader0000000000000000000000000000020700000000000011454 xustar0000000000000000113 path=python-novaclient-18.7.0/releasenotes/notes/add-support-for-volume-backed-rebuild-6a32d9d88fed6b4a.yaml 22 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/add-support-for-volume-backed-rebuild-6a32d9d88fed6b4a.y0000664000175000017500000000042100000000000033077 0ustar00zuulzuul00000000000000--- features: - | Added support for `microversion 2.93`_. This microversion provides the ability to rebuild a volume backed instance. .. _microversion 2.93: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#microversion-2-93 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/add-user-agent-string-db77210dfd3ec671.yaml0000664000175000017500000000040100000000000030412 0ustar00zuulzuul00000000000000--- features: - novaclient now adds information about itself to the keystoneauth user-agent. Adding information about wrapping libraries or consuming applications can be found at https://docs.openstack.org/developer/python-novaclient/api.html ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/bp-add-locked-reason-3f136db97b820c73.yaml0000664000175000017500000000046400000000000030036 0ustar00zuulzuul00000000000000--- features: - Added a ``--reason`` option to ``nova lock`` command that enables users to specify a reason when locking a server and a ``locked`` filtering/sorting option to ``nova list`` command which enables users to filter/sort servers based on their ``locked`` value in microversion 2.73. ././@PaxHeader0000000000000000000000000000020600000000000011453 xustar0000000000000000112 path=python-novaclient-18.7.0/releasenotes/notes/bp-cold-migration-with-target-queens-e361d4ae977aa396.yaml 22 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/bp-cold-migration-with-target-queens-e361d4ae977aa396.ya0000664000175000017500000000061600000000000032761 0ustar00zuulzuul00000000000000--- features: - Added a new ``--host`` option to ``nova migrate`` command in microversion 2.56. It enables administrators to specify a target host when cold migating a server. The target host will be validated by the scheduler. The target host cannot be the same as the current host on which the server is running and must be in the same cell that the server is currently in. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/bp-deprecate-image-meta-proxy-api-1483b75cf73b021e.yaml0000664000175000017500000000037500000000000032442 0ustar00zuulzuul00000000000000--- upgrade: - Starting from microversion 2.39 'image-metadata' proxy API in Nova is deprecated and python API bindings will not work, although 'image-meta' CLI commands will still work, because of the fallback on the CLI to version 2.35. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/bp-handling-down-cell-728cdb1efd1ea75b.yaml0000664000175000017500000000064500000000000030526 0ustar00zuulzuul00000000000000--- features: - | From microversion 2.69 the results of ``nova list``, ``nova show`` and ``nova service-list`` may contain missing information in their outputs when there are partial infrastructure failure periods in the deployment. See `Handling Down Cells`_ for more information on the missing keys/info. .. _Handling Down Cells: https://developer.openstack.org/api-guide/compute/down_cells.html ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/bp-keypair-generation-removal-1b5d84a8906d3918.yaml0000664000175000017500000000052400000000000031746 0ustar00zuulzuul00000000000000--- features: - | Support has been added for `microversion 2.92`_. This microversion only accepts to import a public key and no longer to generate one, hence now the public_key parameter be mandatory. .. _microversion 2.92: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#microversion-2-92 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/bp-more-migration-list-filters-6c801896c7ee5cdc.yaml0000664000175000017500000000065000000000000032301 0ustar00zuulzuul00000000000000--- features: - | The ``--migration-type`` and ``--source-compute`` options are added to the ``nova migration-list`` CLI and related kwargs are added to the ``novaclient.v2.migrations.MigrationManager.list`` method. These can be used to filter the list of migrations by type (evacuation, live-migration, migration, resize) and the name of the source compute service host involved in the migration. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/bp-unshelve-to-host-b220131a00dff8a2.yaml0000664000175000017500000000060500000000000030030 0ustar00zuulzuul00000000000000--- features: - | Support has been added for `microversion 2.91`_. This microversion allows specifying a destination host to unshelve a shelve offloaded server. And availability zone can be set to None to unpin the availability zone of a server. .. _microversion 2.91: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#microversion-2-91 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/bug-1669140-c21d045491201352.yaml0000664000175000017500000000025700000000000025324 0ustar00zuulzuul00000000000000--- issues: - | The ``nova show`` command will no longer output the ``user_data`` column. This is traditionally binary data of limited value from a CLI perspective. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/bug-1744118-0b064d7062117317.yaml0000664000175000017500000000044400000000000025326 0ustar00zuulzuul00000000000000--- fixes: - | A fix is made for `bug 1744118`_ which adds the below missing CLI arguments. * OS_PROJECT_DOMAIN_ID * OS_PROJECT_DOMAIN_NAME * OS_USER_DOMAIN_ID * OS_USER_DOMAIN_NAME .. _bug 1744118: https://bugs.launchpad.net/python-novaclient/+bug/1744118././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/bug-1764420-flavor-delete-output-7b80f73deee5a869.yaml0000664000175000017500000000023500000000000032025 0ustar00zuulzuul00000000000000--- upgrade: - | The ``flavor-delete`` command no longer prints out the details of the deleted flavor. On successful deletion, there is no output. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/bug-1767287-cc28d60d9e59f9bd.yaml0000664000175000017500000000031300000000000026031 0ustar00zuulzuul00000000000000--- upgrade: - | The ``nova server-group-create`` command now only supports specifying a single policy name when creating the server group. This is to match the server-side API validation. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/bug-1778536-a1b5d65a0d4ad622.yaml0000664000175000017500000000073400000000000025720 0ustar00zuulzuul00000000000000--- upgrade: - The deprecated ``--bypass-url`` command line argument has been removed. deprecations: - | The ``--endpoint-override`` command line argument has been deprecated. It is renamed to ``--os-endpoint-override`` to avoid misinterpreting command line arguments. It defaults to the ``OS_ENDPOINT_OVERRIDE`` environment variable. See `bug 1778536`_ for more details. .. _bug 1778536: https://bugs.launchpad.net/python-novaclient/+bug/1778536 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/bug-1825061-2beb95db4d6df0cb.yaml0000664000175000017500000000025400000000000026133 0ustar00zuulzuul00000000000000--- fixes: - | A check for a value of the '--config-drive' option has been added on the ``nova boot`` command. A boolean value is only allowed in the option now. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/bug-1845322-463ee407b60131c9.yaml0000664000175000017500000000032600000000000025474 0ustar00zuulzuul00000000000000--- upgrade: - | The ``--hint`` option for the ``boot`` command expects a key-value argument. Previously, if this was not the case, the argument would be silently ignored. It will now raise an error. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/clarify-project-id-variable-5832698315000438.yaml0000664000175000017500000000124600000000000031053 0ustar00zuulzuul00000000000000--- deprecations: - Keyword argument **tenant_id** of novaclient.client.Client entry-point was deprecated in favor of **project_id**. - Keyword argument **tenant_name** of novaclient.client.Client entry-point was deprecated in favor of **project_name**. other: - The meaning of 'project_id' variable of novaclient.client.Client entry-point was not clear. In different cases it was used as ID or Name of project (in terms of Keystone). The time to identify meaning is come, so now project_id/tenant_id variables specifically mean Project ID (in terms of Keystone) and project_name/tenant_name variables mean Project Name (in terms of Keystone). ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/deprecate-agent-d0f58718ad1782f6.yaml0000664000175000017500000000041400000000000027216 0ustar00zuulzuul00000000000000--- deprecations: - | The following CLIs are deprecated. - ``nova agent-create`` - ``nova agent-delete`` - ``nova agent-list`` - ``nova agent-modify`` The CLIs will be removed in the first major release after Nova 24.0.0 X is released. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/deprecate-baremetal-d67f58a2986b3565.yaml0000664000175000017500000000067600000000000030017 0ustar00zuulzuul00000000000000--- deprecations: - | The following CLIs and python API bindings are now deprecated for removal: * nova baremetal-node-list * nova baremetal-node-show * nova baremetal-interface-list These will be removed in the first major python-novaclient release after the Nova 15.0.0 Ocata release. Use python-ironicclient or python-openstackclient for CLI and python-ironicclient or openstacksdk for python API bindings. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/deprecate-cellsv1-extension-16482759993d112f.yaml0000664000175000017500000000045100000000000031267 0ustar00zuulzuul00000000000000--- deprecations: - | The following CLIs and their backing API bindings are deprecated. - ``nova list-extensions`` - ``nova cell-capacities`` - ``nova cell-show`` The CLIs and API bindings will be removed in the first major release after Nova 20.0.0 Train is released. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/deprecate-certs-1558d8e3b7888938.yaml0000664000175000017500000000040400000000000027121 0ustar00zuulzuul00000000000000--- deprecations: - | The ``nova x509-create-cert`` and ``nova x509-get-root-cert`` commands and ``novaclient.v2.certs`` API binding are now deprecated and will be removed in the first major release after the Nova server 16.0.0 Pike release. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/deprecate-cli-75074850847a8452.yaml0000664000175000017500000000064500000000000026376 0ustar00zuulzuul00000000000000--- deprecations: - | The ``nova`` CLI is now deprecated. This is the signal that it is time to start using the openstack CLI. No new features will be added to the ``nova`` CLI, though fixes to the CLI will be assessed on a case by case basis. Fixes to the API bindings, development of new API bindings, and changes to the compute commands in the openstack CLI are exempt from this deprecation. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/deprecate-cloudpipe-670202797fdf97b6.yaml0000664000175000017500000000055400000000000030037 0ustar00zuulzuul00000000000000--- deprecations: - | The os-cloudpipe API has been removed from Nova. As a result, the ``nova cloudpipe-list``, ``nova cloudpipe-create``, and ``nova cloudpipe-configure`` commands and the ``novaclient.v2.cloudpipe`` API bindings are now deprecated, and will be removed in the first major release after the Nova server 16.0.0 Pike release. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/deprecate-connection-pool-arg-cef35346d5ebf40c.yaml0000664000175000017500000000013600000000000032204 0ustar00zuulzuul00000000000000--- deprecations: - The **connection_pool** variable is deprecated now and will be ignored. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/deprecate-force-option-7116d792bba17f09.yaml0000664000175000017500000000051000000000000030510 0ustar00zuulzuul00000000000000--- upgrade: - | Added support for `microversion 2.68`_, which removes the ``--force`` option from the ``nova evacuate``, ``nova live-migration``, ``nova host-evacuate`` and ``nova host-evacuate-live`` commands. .. _microversion 2.68: https://docs.openstack.org/nova/latest/api_microversion_history.html#id61 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/deprecate-instance-name-option-bc76629d28f1d456.yaml0000664000175000017500000000030500000000000032150 0ustar00zuulzuul00000000000000--- deprecations: - | The ``--instance-name`` option has been deprecated from the ``nova list`` command because the instance name query parameter is ignored by the compute REST API. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/deprecate-network-cli-f0a539528be594d3.yaml0000664000175000017500000000361700000000000030363 0ustar00zuulzuul00000000000000--- upgrade: - | The ability to update the following network-related resources via the ``nova quota-update`` and ``nova quota-class-update`` commands is now deprecated: * Fixed IPs * Floating IPs * Security Groups * Security Group Rules By default the quota and limits CLIs will not update or show those resources using microversion >= 2.36. You can still use them, however, by specifying ``--os-compute-api-version 2.35``. Quota information for network resources should be retrieved from python-neutronclient or python-openstackclient. deprecations: - | The following commands are now deprecated: * dns-create * dns-create-private-domain * dns-create-public-domain * dns-delete * dns-delete-domain * dns-domains * dns-list * fixed-ip-get * fixed-ip-reserve * fixed-ip-unreserve * floating-ip-create * floating-ip-delete * floating-ip-list * floating-ip-pool-list * floating-ip-bulk-create * floating-ip-bulk-delete * floating-ip-bulk-list * network-create * network-delete * network-disassociate * network-associate-host * network-associate-project * network-list * network-show * scrub * secgroup-create * secgroup-delete * secgroup-list * secgroup-update * secgroup-add-group-rule * secgroup-delete-group-rule * secgroup-add-rule * secgroup-delete-rule * secgroup-list-rules * secgroup-list-default-rules * secgroup-add-default-rule * secgroup-delete-default-rule * tenant-network-create * tenant-network-delete * tenant-network-list * tenant-network-show With the 2.36 microversion these will fail in the API. The CLI will fallback to passing the 2.35 microversion to ease the transition. Network resource information should be retrieved from python-neutronclient or python-openstackclient. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/deprecate-no-cache-arg-7814806b4f79c1b9.yaml0000664000175000017500000000033600000000000030272 0ustar00zuulzuul00000000000000--- deprecations: - novaclient.client.Client entry-point accepted two arguments with same meaning (**no_cache** and **os_cache**). Since **os_cache** is more widely used in our code, **no_cache** was deprecated. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/deprecate-proxy-args-a3f4e224f7664ff8.yaml0000664000175000017500000000033100000000000030313 0ustar00zuulzuul00000000000000--- deprecations: - The **proxy_tenant_id** and **proxy_token** arguments to the novaclient.client.Client entry-point were never documented nor tested and are now deprecated for removal in a future release. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/deprecate-service-binary-arg-2d5c446f5a2409a7.yaml0000664000175000017500000000077100000000000031604 0ustar00zuulzuul00000000000000--- deprecations: - | The ``binary`` argument to the ``nova service-enable``, ``nova service-disable``, and ``nova service-force-down`` commands has been deprecated. The only binary that it makes sense to use is ``nova-compute`` since disabling a service like ``nova-scheduler`` or ``nova-conductor`` does not actually do anything, and starting in the 16.0.0 Pike release the compute API will not be able to look up services other than ``nova-compute`` for these operations. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/deprecate-volume-service-name-arg-4c65e8866f9624dd.yaml0000664000175000017500000000053100000000000032566 0ustar00zuulzuul00000000000000--- deprecations: - CLI argument for volume_service_name was deprecated long time ago. All novaclient's methods for communication with Volume API were deprecated and removed. There is no need to leave **volume_service_name** argument of novaclient.client.Client entry-point since it is not used anywhere, so it is removed now. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/deprecate_cell_name_arg-eb34cb7c43cfcb89.yaml0000664000175000017500000000041000000000000031237 0ustar00zuulzuul00000000000000--- deprecations: - CLI argument ``--cell_name`` for ``nova migration-list`` command is deprecated. Nova API does not have logic for handling cell_name parameter in **os-migrations**, and while the parameter is passed to Nova it has never been used. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/deprecate_contrib_extensions-0ec70c070b09eedb.yaml0000664000175000017500000000215100000000000032313 0ustar00zuulzuul00000000000000--- prelude: > All extensions of API V2.0 were merged to 2.1, but NovaClient continued to store them as a separate entities. upgrade: - All managers and resources from novaclient.v2.contrib submodules are moved to appropriate submodules of novaclient.v2 (except barametal and tenant_networks, which were deprecated previously) - All shell commands from novaclient.v2.contrib submodules are moved to novaclient.v2.shell module. - novaclient.v2.client.Client imports all modules (which were located in submodules of novaclient.v2.contrib) by-default for api version v2 - Method novaclient.client.discover_extensions returns only barametal and tenant_networks extensions, since they are not included by default. - There are no modules and extensions for "deferred_delete", "host_evacuate", "host_evacuate_live" and "metadata_extensions" anymore. Previously, they contained only shell commands and shell module auto loads them (there is no ability to not do it). deprecations: - All modules of novaclient.v2.contrib are deprecated now and will be removed after OpenStack Pike. ././@PaxHeader0000000000000000000000000000020700000000000011454 xustar0000000000000000113 path=python-novaclient-18.7.0/releasenotes/notes/drop-deprecated-aggregate-update-args-17bd019f4be34b18.yaml 22 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/drop-deprecated-aggregate-update-args-17bd019f4be34b18.y0000664000175000017500000000033600000000000032740 0ustar00zuulzuul00000000000000--- upgrade: - | The deprecated `name` and `availability_zone` positional arguments in the ``nova aggregate-update`` command have been removed. Use the ``--name`` and ``--availability-zone`` options instead. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/drop-python2-support-d3a1bedc75445edc.yaml0000664000175000017500000000011600000000000030526 0ustar00zuulzuul00000000000000--- upgrade: - | Python 2 is no longer supported. Python 3 is required. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/fix-booting-with-multiple-nics-c6e5885b948d35ba.yaml0000664000175000017500000000021600000000000032233 0ustar00zuulzuul00000000000000--- fixes: - Fix an ability to boot server with multiple nics which was broken with microversion 2.42 (fix tag attribute disappearing). ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/fix-raw-python-error-debd3edb17c2f675.yaml0000664000175000017500000000040500000000000030501 0ustar00zuulzuul00000000000000--- fixes: - | `Bug #1903727 `_: Fixed raw Python error message when using ``nova`` without a subcommand while passing an optional argument, such as ``--os-compute-api-version 2.87``. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/fix-rebuild-userdata-9315e5784feb8ba9.yaml0000664000175000017500000000026000000000000030273 0ustar00zuulzuul00000000000000--- fixes: - | The user data argument in the ``nova rebuild`` command was passing the filename as userdata. Now this passes the contents of the file as intended. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/fix-tag-attribute-disappearing-25483a80f548ef35.yaml0000664000175000017500000000070400000000000032113 0ustar00zuulzuul00000000000000--- fixes: - | Microversion 2.42 is related to the following bug. * https://bugs.launchpad.net/nova/+bug/1658571 The following options have been changed as of Microversion 2.42. * Remove ``tag`` attribute in ``--block-device`` option on the server boot (nova boot) between microversion 2.33 and 2.41. * Remove ``tag`` attribute in ``--nic`` option on the server boot (nova boot) between microversion 2.37 and 2.41. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/fix-token-auth-6c48c63a759f51d5.yaml0000664000175000017500000000017000000000000027032 0ustar00zuulzuul00000000000000--- fixes: - Fix an ability to authenticate using Keystone Token which was broken with novaclient 7.0.0 release. ././@PaxHeader0000000000000000000000000000023000000000000011450 xustar0000000000000000130 path=python-novaclient-18.7.0/releasenotes/notes/fixed-ListExtResource-given-in-place-of-ListExtManager-a759a27079d16a44.yaml 22 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/fixed-ListExtResource-given-in-place-of-ListExtManager-a0000664000175000017500000000072700000000000033567 0ustar00zuulzuul00000000000000--- fixes: - | The contents of the list_extensions.py file was moved from contrib to v2 directory in release 7.0.0, and a stub importing the objects from the new location was left in its place for backward compatibility, together with a warning informing about the new location. However, the stub incorrectly assigned the ListExtResource class to the ListExtManager name. This has now been fixed, and ListExtManager is used instead. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/get-list-metadata-8afcc8f32ad82dda.yaml0000664000175000017500000000012100000000000030035 0ustar00zuulzuul00000000000000--- features: - | Adds supports for Nova Server get and list metadata API. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/get-rid-off-redundant-methods-47e679c13e88f28a.yaml0000664000175000017500000000077100000000000031733 0ustar00zuulzuul00000000000000--- deprecations: - | ``novaclient.utils.add_resource_manager_extra_kwargs_hook`` and ``novaclient.utils.get_resource_manager_extra_kwargs`` were designed for supporting extensions in nova/novaclient. Nowadays, this "extensions" feature is abandoned and both ``add_resource_manager_extra_kwargs_hook``, ``add_resource_manager_extra_kwargs_hook`` are not used in novaclient's code. These methods are not documented, so we are removing them without standard deprecation cycle. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/global_request_id-26f4e4301f84d403.yaml0000664000175000017500000000026300000000000027556 0ustar00zuulzuul00000000000000--- features: - | A new ``global_request_id`` parameter is accepted on the client constructor, which will then pass ``X-OpenStack-Request-ID`` on all requests made. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/image-api-deprecation-41944dc6fc024918.yaml0000664000175000017500000000066600000000000030235 0ustar00zuulzuul00000000000000--- deprecations: - | The following CLIs and python API bindings are now deprecated for removal: * nova image-delete * nova image-list * nova image-meta * nova image-show These will be removed in the first major python-novaclient release after the Nova 15.0.0 Ocata release. Use python-glanceclient or python-openstackclient for CLI and python-glanceclient or openstacksdk for python API bindings. ././@PaxHeader0000000000000000000000000000020600000000000011453 xustar0000000000000000112 path=python-novaclient-18.7.0/releasenotes/notes/instance-uuid-flag-in-migration-list-5d2fed7657d3def5.yaml 22 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/instance-uuid-flag-in-migration-list-5d2fed7657d3def5.ya0000664000175000017500000000047600000000000033114 0ustar00zuulzuul00000000000000--- features: - | A new ``--instance-uuid`` option is added to ``nova migration-list`` command. This is used to query the migration history of a specific server by the migration-list command. Please use ``nova server-migration-list`` command for querying in-progress migrations of a specific server. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/interface-attach-output-02d633d9b2a60da1.yaml0000664000175000017500000000057400000000000030763 0ustar00zuulzuul00000000000000--- upgrade: - The ``nova interface-attach`` command shows output of its result when it is successful. - | The following methods return a ``NetworkInterface`` object instead of a ``Server`` object. * The ``interface_attach`` method in the ``novaclient.v2.Server`` class * The ``interface_attach`` method in the ``novaclient.v2.ServerManager`` class ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/keystoneauth-8ec1e6be14cdbae3.yaml0000664000175000017500000000056600000000000027261 0ustar00zuulzuul00000000000000--- features: - keystoneauth plugins are now supported. upgrade: - novaclient now requires the keystoneauth library. deprecations: - novaclient auth strategy plugins are deprecated. Please use keystoneauth auth plugins instead. - nova credentials is deprecated. Please use openstack token issue - nova endpoints is deprecated. Please use openstack catalog list instead. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/log-request-id-ce106497e0520fad.yaml0000664000175000017500000000101300000000000027063 0ustar00zuulzuul00000000000000--- prelude: > - Log 'x-openstack-request-id' or 'x-compute-request-id' in each API call. If the caller (e.g. heat) uses oslo.log, the caller's request id in oslo.context and the callee's request id can be output in the same log message (same line). features: - Log 'x-openstack-request-id' or 'x-compute-request-id' in each API call. If the caller (e.g. heat) uses oslo.log, the caller's request id in oslo.context and the callee's request id can be output in the same log message (same line). ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/make-console-public-0c776bfda240cd9d.yaml0000664000175000017500000000052300000000000030224 0ustar00zuulzuul00000000000000--- features: - Provides a public unified interface 'get_console_url' for classes 'novaclient.v2.servers.Server' and 'novaclient.v2.servers.ServerManager'. Users (Heat, OpenStack-client and etc.) can call this public interface instead of calling the individual methods to retrieve a console url of a particular protocol. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-2.37-d03da96406a45e67.yaml0000664000175000017500000000224600000000000027270 0ustar00zuulzuul00000000000000--- features: - | The 2.37 microversion is now supported. This introduces the following changes: * CLI: The **--nic** value for the **nova boot** command now takes two special values, 'auto' and 'none'. If --nic is not specified, the CLI defaults to 'auto'. * Python API: The **nics** kwarg is required when creating a server using the *novaclient.v2.servers.ServerManager.create* API. The **nics** value can be a list of dicts or a string with value 'auto' or 'none'. upgrade: - | With the 2.37 microversion, the **nics** kwarg is required when creating a server using the *novaclient.v2.servers.ServerManager.create* API. The **nics** value can be a list of dicts or an enum string with one of the following values: * **auto**: This tells the Compute service to automatically allocate a network for the project if one is not available and then associate an IP from that network with the server. This is the same behavior as passing nics=None before the 2.37 microversion. * **none**: This tells the Compute service to not allocate any networking for the server. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_28-abf653ae5cf5c4a9.yaml0000664000175000017500000000017700000000000030040 0ustar00zuulzuul00000000000000--- upgrade: - Support v2.28 microversion - cpu_info property of hypervisor resource is a json now (previously it was text). ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_31-3e1a16eb5eb53f59.yaml0000664000175000017500000000014700000000000027666 0ustar00zuulzuul00000000000000--- upgrade: - Support for microversion 2.31 which fixes a bug in the os-console-auth-tokens API ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_33-10d12ea3b25839e8.yaml0000664000175000017500000000031200000000000027515 0ustar00zuulzuul00000000000000--- features: - Added microversion v2.33 that adds pagination support for hypervisors with the help of new optional parameters 'limit' and 'marker' which were added to hypervisor-list command.././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_34-a9c5601811152964.yaml0000664000175000017500000000006500000000000027306 0ustar00zuulzuul00000000000000--- upgrade: - Support for microversion 2.34 added.././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_35-537619a43278fbb5.yaml0000664000175000017500000000030400000000000027454 0ustar00zuulzuul00000000000000--- features: - Added microversion v2.35 that adds pagination support for keypairs with the help of new optional parameters 'limit' and 'marker' which were added to keypair-list command.././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_38-0618fe2b3c7f96f9.yaml0000664000175000017500000000031500000000000027631 0ustar00zuulzuul00000000000000--- upgrade: - Support for microversion 2.38 added. As of microversion 2.38, invalid statuses passed to ``nova list --status invalid_status`` will result in a HTTP 400 Bad Request error response.././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_40-484adba0806b08bf.yaml0000664000175000017500000000023400000000000027653 0ustar00zuulzuul00000000000000--- features: - Added microversion v2.40 which introduces pagination support for usage with the help of new optional parameters 'limit' and 'marker'. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_41-6df7a5a66a9ded35.yaml0000664000175000017500000000033600000000000027760 0ustar00zuulzuul00000000000000--- features: - | Added support for microversion 2.41 which shows the aggregate UUID in CLI output when listing, creating, showing, updating, setting metadata, and adding or removing hosts from an aggregate. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_43-76db2ac463b431e4.yaml0000664000175000017500000000103100000000000027573 0ustar00zuulzuul00000000000000--- deprecations: - | The following CLIs and their backing API bindings are deprecated and capped at microversion 2.43: * ``nova host-describe`` - superseded by ``nova hypervisor-show`` * ``nova host-list`` - superseded by ``nova hypervisor-list`` * ``nova host-update`` - superseded by ``nova service-enable`` and ``nova service-disable`` * ``nova host-action`` - no alternative by design The CLIs and API bindings will be removed in the first major release after Nova 16.0.0 Pike is released. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_44-d60c8834e436ad3d.yaml0000664000175000017500000000126100000000000027612 0ustar00zuulzuul00000000000000--- deprecations: - | The following CLIs and their backing API bindings are deprecated and capped at microversion 2.44: * ``nova add-fixed-ip``: use python-neutronclient or openstacksdk * ``nova remove-fixed-ip``: use python-neutronclient or openstacksdk * ``nova floating-ip-associate``: use python-neutronclient or openstacksdk * ``nova floating-ip-disassociate``: use python-neutronclient or openstacksdk * ``nova virtual-interface-list``: there is no replacement as this is only implemented for nova-network which is deprecated The CLIs and API bindings will be removed in the first major release after Nova 16.0.0 Pike is released. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_45-1bfcae3914280534.yaml0000664000175000017500000000116200000000000027522 0ustar00zuulzuul00000000000000--- features: - | Support was added for microversion 2.45. This changes how the ``createImage`` and ``createBackup`` server action APIs return the created snapshot image ID in the response. With microversion 2.45 and later, the image ID is return in a json dict response body with an ``image_id`` key and uuid value. The old ``Location`` response header is no longer returned in microversion 2.45 or later. There are no changes to the ``nova image-create`` CLI. However, the ``nova backup`` CLI will print out the backup snapshot image information with microversion 2.45 or greater now. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_47-4aa54fbbd519e421.yaml0000664000175000017500000000155200000000000027666 0ustar00zuulzuul00000000000000--- features: - | Added support for microversion 2.47 which returns the flavor details directly embedded in the server details when listing or showing servers. With this change, CLI requests with microversion >= 2.47 will no longer need to do additional queries to get the flavor and flavor extra_specs information. Instead, the flavor information will be output as separate key/value pairs with the keys namespaced with the "flavor:" prefix. As one would expect, these keys can also be specified as output fields when listing servers, like this: ``nova list --fields name,flavor:original_name`` When displaying details of a single server, the ``--minimal`` option will display a ``flavor`` field with a value of the ``original_name`` of the flavor. Prior to this microversion the value was the ``id`` of the flavor. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_49-56bde596ee13366d.yaml0000664000175000017500000000034600000000000027633 0ustar00zuulzuul00000000000000--- features: - | Added support for microversion 2.49 that enables users to attach tagged interfaces and volumes. A new ``--tag`` option is added to ``nova volume-attach`` and ``nova interface-attach`` commands. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_50-4f484658d66d01aa.yaml0000664000175000017500000000266600000000000027543 0ustar00zuulzuul00000000000000--- fixes: - | Adds support for the ``2.50`` microversion which fixes the ``nova quota-class-show`` and ``nova quota-class-update`` commands in the following ways: * The ``server_groups`` and ``server_group_members`` quota resources will now be shown in the output table for ``nova quota-class-show``. * The ``floating_ips``, ``fixed_ips``, ``security_groups`` and ``security_group_rules`` quota resources will no longer be able to be updated using ``nova quota-class-update`` nor will they be shown in the output of ``nova quota-class-show``. Use python-openstackclient or python-neutronclient to work with quotas for network resources. In addition, the ``nova quota-class-update`` CLI was previously incorrectly limiting the ability to update quota class values for ``floating_ips``, ``fixed_ips``, ``security_groups`` and ``security_group_rules`` based on the 2.36 microversion. That has been changed to limit based on the ``2.50`` microversion. upgrade: - | The ``novaclient.v2.quota_classes.QuotaClassSetManager.update`` method now defines specific kwargs starting with microversion ``2.50`` since updating network-related resource quota class values is not supported on the server with microversion ``2.50``. The list of excluded resources is: - ``fixed_ips`` - ``floating_ips`` - ``networks`` - ``security_groups`` - ``security_group_rules`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_52-2fe81b3bf2e4b4ea.yaml0000664000175000017500000000052200000000000030020 0ustar00zuulzuul00000000000000--- features: - | `Microversion 2.52`_ is now supported which adds the ``--tags`` option to the ``nova boot`` command and a ``tags`` kwarg to the ``novaclient.v2.servers.ServerManager.create()`` python API binding method. .. _Microversion 2.52: https://docs.openstack.org/nova/latest/api_microversion_history.html#id47 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_53-3463b546a38c5f84.yaml0000664000175000017500000000340000000000000027454 0ustar00zuulzuul00000000000000--- features: - | Added support for `microversion 2.53`_. The following changes were made for the ``services`` commands and python API bindings: - The ``nova service-list`` command and API will have a UUID value for the ``id`` field in the output and response, respectively. - The ``nova service-enable`` command and API will require a UUID service id value to uniquely identify the service rather than a ``host`` and ``binary`` value. The UUID ``id`` field will also be in the command output. - The ``nova service-disable`` command and API will require a UUID service id value to uniquely identify the service rather than a ``host`` and ``binary`` value. The UUID ``id`` field will also be in the command output. - The ``nova service-force-down`` command and API will require a UUID service id value to uniquely identify the service rather than a ``host`` and ``binary`` value. The UUID ``id`` field will also be in the command output. - The ``nova service-delete`` command and API will require a UUID service id value to uniquely identify the service rather than an integer service id value. The following changes were made for the ``hypervisors`` commands and python API bindings: - The ID field in the various ``nova hypervisor-*`` commands and ``Hypervisor.id`` attribute in the API binding will now be a UUID value. - If paging over hypervisors using ``nova hypervisor-list``, the ``--marker`` must be a UUID value. - The ``nova hypervisor-show`` and ``nova hypervisor-uptime`` commands and APIs now take a UUID value for the hypervisor ID. .. _microversion 2.53: https://docs.openstack.org/nova/latest/api_microversion_history.html#id48 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_54-6c7ccb61eff6cb6d.yaml0000664000175000017500000000035200000000000030116 0ustar00zuulzuul00000000000000--- features: - | Adds support for microversion 2.54 which adds resetting keypair and unsetting keypair in rebuild operation. Adds optional ``--key-name`` and ``--key-unset`` options in the ``nova rebuild`` command. ././@PaxHeader0000000000000000000000000000020700000000000011454 xustar0000000000000000113 path=python-novaclient-18.7.0/releasenotes/notes/microversion-v2_55-flavor-description-a93718b31f1f0f39.yaml 22 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_55-flavor-description-a93718b31f1f0f39.y0000664000175000017500000000045300000000000032654 0ustar00zuulzuul00000000000000--- features: - | Support is added for compute API version 2.55. This adds the ability to create a flavor with a description, show the description of a flavor, and update the description on an existing flavor. A new ``nova flavor-update `` command is added. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_57-acae2ee11ddae4fb.yaml0000664000175000017500000000307300000000000030241 0ustar00zuulzuul00000000000000--- features: - | Support is added for the 2.57 microversion: * A ``userdata`` keyword argument can be passed to the ``Server.rebuild`` python API binding. If set to None, it will unset any existing userdata on the server. * The ``--user-data`` and ``--user-data-unset`` options are added to the ``nova rebuild`` CLI. The options are mutually exclusive. Specifying ``--user-data`` will overwrite the existing userdata in the server, and ``--user-data-unset`` will unset any existing userdata on the server. upgrade: - | Support is added for the 2.57 microversion: * The ``--file`` option for the ``nova boot`` and ``nova rebuild`` CLIs is capped at the 2.56 microversion. Similarly, the ``file`` parameter to the ``Server.create`` and ``Server.rebuild`` python API binding methods is capped at 2.56. Users are recommended to use the ``--user-data`` option instead. * The ``--injected-files``, ``--injected-file-content-bytes`` and ``--injected-file-path-bytes`` options are capped at the 2.56 microversion in the ``nova quota-update`` and ``nova quota-class-update`` commands. * The ``maxPersonality`` and ``maxPersonalitySize`` fields are capped at the 2.56 microversion in the ``nova limits`` command and API binding. * The ``injected_files``, ``injected_file_content_bytes`` and ``injected_file_path_bytes`` entries are capped at version 2.56 from the output of the ``nova quota-show`` and ``nova quota-class-show`` commands and related python API bindings. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_58-327c1031ebfe4a3a.yaml0000664000175000017500000000055300000000000027657 0ustar00zuulzuul00000000000000--- features: - | Added support for microversion v2.58 which introduces pagination support for instance actions with the help of new optional parameters ``limit``, ``marker``, and also adds the new filter ``changes-since``. Users can use ``changes-since`` filter to filter the results based on the last time the instance action was updated. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_59-4160c852d7d8812d.yaml0000664000175000017500000000053700000000000027466 0ustar00zuulzuul00000000000000--- features: - | Added support for microversion v2.59 which introduces pagination support for migrations with the help of new optional parameters ``limit``, ``marker``, and also adds the new filter ``changes-since``. Users can use ``changes-since`` filter to filter the results based on the last time the migration was updated. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_61-9a8faa02fddf9ed6.yaml0000664000175000017500000000063500000000000030123 0ustar00zuulzuul00000000000000--- other: - | Starting from microversion 2.61, the responses of the 'Flavor' APIs include the 'extra_specs' parameter. Therefore 'Flavors extra-specs' (os-extra_specs) API calls have been removed in the following commands since microversion 2.61. * ``nova flavor-list`` * ``nova flavor-show`` There are no behavior changes in the CLI. This is just a performance optimization. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_62-479a23f0d4307500.yaml0000664000175000017500000000070000000000000027354 0ustar00zuulzuul00000000000000--- features: - | Adds support for microversion 2.62 which adds ``host`` (hostname) and ``hostId`` (an obfuscated hashed host id string) fields to the instance action ``GET /servers/{server_id}/os-instance-actions/{req_id}`` API. The event columns are already included in the result of "nova instance-action " command, therefore does not have any CLI or python API binding impacts in the client. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_63-cd058a9145550cae.yaml0000664000175000017500000000126500000000000027610 0ustar00zuulzuul00000000000000--- features: - | Added support for `microversion 2.63`_, which includes the following changes: - New environment variable called ``OS_TRUSTED_IMAGE_CERTIFICATE_IDS`` - New ``nova boot`` option called ``--trusted-image-certificate-id`` - New ``nova rebuild`` options called ``--trusted-image-certificate-id`` and ``--trusted-image-certificates-unset`` - New kwarg called ``trusted_image_certificates`` added to python API bindings: - ``novaclient.v2.servers.ServerManager.create()`` - ``novaclient.v2.servers.ServerManager.rebuild()`` .. _microversion 2.63: https://docs.openstack.org/nova/latest/api_microversion_history.html#id57 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_64-66366829ec65bea4.yaml.yaml0000664000175000017500000000126000000000000030507 0ustar00zuulzuul00000000000000--- features: - | Added support for `microversion 2.64`_, which includes the following changes: * The ``--rule`` options is added to the ``nova server-group-create`` CLI that enables user to create server group with specific policy rules. * Remove ``metadata`` column in the output of ``nova server-group-create``, ``nova server-group-get``, ``nova server-group-list``. * Remove ``policies`` column, add ``policy`` and ``rules`` columns in the output of ``nova server-group-create``, ``nova server-group-get``, ``nova server-group-list``. .. _microversion 2.64: https://docs.openstack.org/nova/latest/api_microversion_history.html#id58 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_65-3c89c5932f4391cb.yaml0000664000175000017500000000056400000000000027551 0ustar00zuulzuul00000000000000--- features: - | Support has been added for the compute API `2.65`_ microversion. This allows calling ``nova live-migration-abort`` on live migrations that are in ``queued`` or ``preparing`` status in addition to the already accepted ``running`` status. .. _2.65: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id59 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_66-cda5d6dc31b56b46.yaml0000664000175000017500000000123600000000000027751 0ustar00zuulzuul00000000000000--- features: - | Added support for `microversion 2.66`_ which adds ``changes-before`` parameter to the servers, os-instance-actions or os-migrations APIs. * This parameter (``changes-before``) does not change any read-deleted behavior in the os-instance-actions or os-migrations APIs. * Like the ``changes-since`` filter, the ``changes-before`` filter will also return deleted servers. * The ``--changes-before`` options is added to the ``nova list``, ``nova instance-action-list`` and ``nova migration-list`` CLIs. .. _microversion 2.66: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id59 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_67-da6d9b12730b8562.yaml0000664000175000017500000000070500000000000027535 0ustar00zuulzuul00000000000000--- features: - | Support is added for the `2.67 microversion`_ which allows specifying a ``volume_type`` with the ``--block-device`` option on the ``nova boot`` command. The ``novaclient.v2.servers.ServerManager.create()`` method now also supports a ``volume_type`` entry in the ``block_device_mapping_v2`` parameter. .. _2.67 microversion: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id60 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_71-a87b4bb4205c46e2.yaml0000664000175000017500000000044200000000000027601 0ustar00zuulzuul00000000000000--- features: - | Added support for `microversion 2.71`_ which outputs the `server_groups` field in the following commands: * ``nova show`` * ``nova rebuild`` .. _microversion 2.71: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id64 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_72-d910ce07ec3948d6.yaml0000664000175000017500000000077300000000000027627 0ustar00zuulzuul00000000000000--- features: - | Support has been added for `microversion 2.72`_. This microversion allows creating a server using the ``nova boot`` command with pre-existing ports having a ``resource_request`` value to enable features such as guaranteed minimum bandwidth for `quality of service`_. .. _microversion 2.72: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id65 .. _quality of service: https://docs.openstack.org/neutron/latest/admin/config-qos.html ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_74-43b128fe6b84b630.yaml0000664000175000017500000000065400000000000027536 0ustar00zuulzuul00000000000000--- features: - | Support is added for the `2.74 microversion`_ which allows specifying the ``--host`` and ``--hypervisor-hostname`` options on the ``nova boot`` command. The ``novaclient.v2.servers.ServerManager.create()`` method now also supports ``host`` and ``hypervisor_hostname`` parameters. .. _2.74 microversion: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id66 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_75-ea7fa3ba1396edea.yaml0000664000175000017500000000114700000000000030113 0ustar00zuulzuul00000000000000--- features: - | Added support for `microversion 2.75`_. The following changes were made: - Return all fields of ``server`` in ``nova rebuild`` command which are returned in ``nova show``. Both command will return the same set of fields of ``server`` representation. - Default return value of ``swap`` field will be 0 (integer) in below commands: - ``nova flavor-list`` - ``nova flavor-show`` - ``nova flavor-create`` - ``nova flavor-update`` .. _microversion 2.75: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id67 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_77-ffee30c180aa4dbe.yaml0000664000175000017500000000044100000000000030103 0ustar00zuulzuul00000000000000--- features: - | Support has been added for `microversion 2.77`_. This microversion allows specifying an availability zone to unshelve a shelve offloaded server. .. _microversion 2.77: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id69 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_78-77a12630e668c2ae.yaml0000664000175000017500000000066100000000000027541 0ustar00zuulzuul00000000000000--- features: - | Added support for `microversion 2.78`_ which outputs the server NUMA topology information in the following command: * ``nova server-topology`` And associated python API bindings: * ``novaclient.v2.servers.Server.topology`` * ``novaclient.v2.servers.ServerManager.topology`` .. _microversion 2.78: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id70 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_79-f13bc0414743dc16.yaml0000664000175000017500000000107300000000000027525 0ustar00zuulzuul00000000000000--- features: - | Added support for `microversion 2.79`_ which includes the following changes: - The ``--delete-on-termination`` option is added to the ``nova volume-attach`` CLI. - A ``DELETE ON TERMINATION`` column is added to the ``nova volume-attachments`` table. - New kwarg called ``delete_on_termination`` added to the python API binding: - ``novaclient.v2.volumes.VolumeManager.create_server_volume()`` .. _microversion 2.79: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id71 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_80-c2394316f9212865.yaml0000664000175000017500000000140700000000000027320 0ustar00zuulzuul00000000000000--- features: - | Added support for `microversion 2.80`_ which adds ``user_id`` and ``project_id`` filter parameters to the ``GET /os-migrations`` API. New kwargs ``project_id`` and ``user_id`` have been added to the following python API binding: - novaclient.v2.migrations.MigrationManager.list The following CLI changes have been made: - The ``--project-id`` and ``--user-id`` options are added to the ``nova migration-list`` CLI. - The ``nova server-migration-list`` and ``nova server-migration-show`` commands will show the ``Project ID`` and ``User ID`` values when using microversion 2.80 or greater. .. _microversion 2.80: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id72 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_81-3ddd8e2fc7e45030.yaml0000664000175000017500000000055700000000000027677 0ustar00zuulzuul00000000000000--- features: - | Added support for `microversion 2.81`_ which adds image pre-caching support by aggregate. - The ``aggregate-cache-images`` command is added to the CLI - The ``cache_images()`` method is added to the python API binding .. _microversion 2.81: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id73 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_85-230931f88c4f1d52.yaml0000664000175000017500000000123000000000000027452 0ustar00zuulzuul00000000000000--- features: - | Support is added for compute API `microversion 2.85`_. This adds the ability to update an attached volume with a ``delete_on_termination``, which specify if the attached volume should be deleted when the server is destroyed. - The ``--delete-on-termination`` and ``--no-delete-on-termination`` options are added to the ``nova volume-update`` CLI. - New kwarg called ``delete_on_termination`` added to the python API binding: - ``novaclient.v2.volumes.VolumeManager.update_server_volume()`` .. _microversion 2.85: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id78 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_88-d91136020e3a3621.yaml0000664000175000017500000000134000000000000027355 0ustar00zuulzuul00000000000000--- features: - | Added support for `microversion 2.88`_. The ``novaclient.v2.hypervisors.HypervisorManager.uptime`` method will now transparently switch between the ``/os-hypervisors/{id}/uptime`` API, which is deprecated in 2.88, and the ``/os-hypervisors/{id}`` API, which now includes uptime information, based on the microversion. .. _microversion 2.88: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#microversion-2-88 deprecations: - | The ``nova hypervisor-stats`` command and underlying ``novaclient.v2.hypervisors.HypervisorStatsManager.statistics`` API are deprecated starting in microversion 2.88 and will return an error starting on this version. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_90-259779668e67dfb5.yaml0000664000175000017500000000126400000000000027512 0ustar00zuulzuul00000000000000--- features: - | Added support for `microversion 2.90`_. This microversion provides the ability to manually configure the instance ``hostname`` attribute when creating a new instance (``nova boot --hostname HOSTNAME ...``), updating an existing instance (``nova update --hostname HOSTNAME ...``), or rebuilding an existing instance (``nova rebuild --hostname HOSTNAME``). This attribute is published via the metadata service and config drive and can be used by init scripts such as ``cloud-init`` to configure the guest's hostname. .. _microversion 2.90: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#microversion-2-90 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_94-5368d5dd7c5f6484.yaml0000664000175000017500000000100500000000000027553 0ustar00zuulzuul00000000000000--- features: - | Added support for `microversion 2.94`_. There are no client-side changes for this microversion, but sending this microversion allows the ``hostname`` parameter in the server create, server update, and server rebuild APIs to be a fully qualified domain name (FQDN). Prior to this microversion, server-side validation only allows short names as the ``hostname``. .. _microversion 2.94: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id83 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_95-3c6ad46be2656684.yaml0000664000175000017500000000067200000000000027552 0ustar00zuulzuul00000000000000--- features: - | Added support for `microversion 2.95`_. There are no client-side changes for this microversion, but sending this microversion triggers evacuated instances to be stopped on the destination host. Prior to this microversion, instances were keeping their state from source to destination host. .. _microversion 2.95: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id84 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion-v2_96-a50af976133de0ab.yaml0000664000175000017500000000045700000000000027673 0ustar00zuulzuul00000000000000--- features: - | The `microversion 2.96`_ has been added. This microversion adds pinned_availability_zone in ``server show`` and ``server list --long`` responses. .. _microversion 2.96: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#microversion-2-96 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion_v2_70-09cbe0933b3a9335.yaml0000664000175000017500000000055200000000000027607 0ustar00zuulzuul00000000000000--- features: - | Added support for `microversion 2.70`_ which outputs the `Tag` field in the following commands: * ``nova interface-list`` * ``nova interface-attach`` * ``nova volume-attachments`` * ``nova volume-attach`` .. _microversion 2.70: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id63 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/microversion_v2_89-af6223273b2bdfb0.yaml0000664000175000017500000000103300000000000027742 0ustar00zuulzuul00000000000000--- features: - | Added support for `microversion 2.89`_. This microversion removes the ``id`` field while adding the ``attachment_id`` and ``bdm_uuid`` fields to the responses of ``GET /servers/{server_id}/os-volume_attachments`` and ``GET /servers/{server_id}/os-volume_attachments/{volume_id}`` with these changes reflected in novaclient under the ``nova volume-attachments`` command. .. _microversion 2.89: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#microversion-2-89 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/no-glance-proxy-5c13001a4b13e8ce.yaml0000664000175000017500000000061100000000000027232 0ustar00zuulzuul00000000000000--- upgrade: - | The 2.36 microversion deprecated the image proxy API. As such, CLI calls now directly call the image service to get image details, for example, as a convenience to boot a server with an image name rather than the image id. To do this the following is assumed: #. There is an **image** entry in the service catalog. #. The image v2 API is available. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/no-neutron-proxy-18fd54febe939a6b.yaml0000664000175000017500000000060100000000000027671 0ustar00zuulzuul00000000000000--- upgrade: - | The 2.36 microversion deprecated the network proxy APIs in Nova. Because of this we now go directly to neutron for name to net-id lookups. For nova-net deployements the old proxies will continue to be used. To do this the following is assumed: #. There is a **network** entry in the service catalog. #. The network v2 API is available. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/pike-rm-deprecated-img-d58e9ae2d774cbfc.yaml0000664000175000017500000000051700000000000030707 0ustar00zuulzuul00000000000000--- prelude: > Deprecated image commands and python API bindings have been removed. upgrade: - | The following deprecated image commands have been removed:: * nova image-list * nova image-show * nova image-meta * nova image-delete Along with the related python API bindings in ``novaclient.v2.images``. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/pike-rm-deprecated-net-272aeb62b329a5bc.yaml0000664000175000017500000000456600000000000030544 0ustar00zuulzuul00000000000000--- prelude: > Deprecated network-related resource commands and python API bindings have been removed. From this point on, python-novaclient will no longer work with nova-network *except* for the ``nova virtual-interface-list``, ``nova add-fixed-ip`` and ``nova remove-fixed-ip`` commands. upgrade: - | The following deprecated network-related resource commands have been removed:: * nova dns-create * nova dns-create-private-domain * nova dns-create-public-domain * nova dns-delete * nova dns-delete-domain * nova dns-domains * nova dns-list * nova fixed-ip-get * nova fixed-ip-reserve * nova fixed-ip-unreserve * nova floating-ip-create * nova floating-ip-delete * nova floating-ip-list * nova floating-ip-bulk-create * nova floating-ip-bulk-delete * nova floating-ip-bulk-list * nova floating-ip-pool-list * nova net * nova net-create * nova net-delete * nova net-list * nova network-create * nova network-delete * nova network-list * nova network-show * nova network-associate-host * nova-network-associate-project * nova network-disassociate * nova scrub * nova secgroup-create * nova secgroup-delete * nova secgroup-list * nova secgroup-update * nova secgroup-add-rule * nova secgroup-delete-rule * nova secgroup-list-rules * nova secgroup-add-default-rule * nova secgroup-delete-default-rule * nova secgroup-list-default-rules * nova secgroup-add-group-rule * nova secgroup-delete-group-rule * nova tenant-network-create * nova tenant-network-delete * nova tenant-network-list * nova tenant-network-show Along with the following python API bindings:: * novaclient.v2.contrib.tenant_networks * novaclient.v2.fixed_ips * novaclient.v2.floating_ip_dns * novaclient.v2.floating_ip_pools * novaclient.v2.floating_ips * novaclient.v2.floating_ips_bulk * novaclient.v2.fping * novaclient.v2.networks * novaclient.v2.security_group_default_rules * novaclient.v2.security_group_rules * novaclient.v2.security_groups deprecations: - | The ``only_contrib`` parameter for the ``novaclient.client.discover_extensions`` method is deprecated and now results in an empty list returned since all contrib extensions are either required or have been removed.././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/remove-auth-system-b2cd247b8a312b72.yaml0000664000175000017500000000066600000000000030007 0ustar00zuulzuul00000000000000--- prelude: > The ability to use non-Keystone authentication systems has been removed. upgrade: - The ``--os-auth-system`` CLI option and ``OS_AUTH_SYSTEM`` environment variable usage was deprecated in the 3.1.0 release during the Mitaka development series. This release drops the support for using those options to load non-Keystone authentication systems via the ``openstack.client.auth_plugin`` extension point. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/remove-certs-4333342189200d91.yaml0000664000175000017500000000031600000000000026260 0ustar00zuulzuul00000000000000--- upgrade: - | The ``nova x509-create-cert`` and ``nova x509-get-root-cert`` commands and ``novaclient.v2.certs`` API binding were deprecated in the 9.0.0 release and have now been removed. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/remove-cloudpipe-6c790c57dc3796eb.yaml0000664000175000017500000000032000000000000027526 0ustar00zuulzuul00000000000000--- upgrade: - | The deprecated ``nova cloudpipe-list``, ``nova cloudpipe-create``, and ``nova cloudpipe-configure`` commands and the ``novaclient.v2.cloudpipe`` API bindings have been removed. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/remove-contrib-8b5e35ac8dddbab3.yaml0000664000175000017500000000030100000000000027456 0ustar00zuulzuul00000000000000--- upgrade: - All modules of ``novaclient.v2.contrib`` have been removed. - The ``only_contrib`` parameter for the ``novaclient.client.discover_extensions`` method is no longer valid. ././@PaxHeader0000000000000000000000000000021700000000000011455 xustar0000000000000000121 path=python-novaclient-18.7.0/releasenotes/notes/remove-deprecated-cellsv1-extentions-commands-4b26c826ad5194ca.yaml 22 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/remove-deprecated-cellsv1-extentions-commands-4b26c826ad0000664000175000017500000000026400000000000033403 0ustar00zuulzuul00000000000000--- upgrade: - | The following CLIs and their backing API bindings have been removed. - ``nova list-extensions`` - ``nova cell-capacities`` - ``nova cell-show`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/remove-deprecated-methods-train-c450fe317c90d7f0.yaml0000664000175000017500000000100600000000000032400 0ustar00zuulzuul00000000000000--- upgrade: - | The following properties have been removed. - ``novaclient.client.SessionClient`` - ``management_url`` - ``novaclient.v2.client.Client`` - ``projectid`` - ``tenant_id`` The following methods have been removed. - ``novaclient.client.get_client_class`` - ``novaclient.v2.client.Client`` - ``set_management_url`` - ``authenticate`` The ``novaclient.v2.client.Client.__enter__`` method now raises the ``InvalidUsage`` runtime error. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/remove-deprecated-option-14.0.0-c6d7189938f5f063.yaml0000664000175000017500000000026400000000000031542 0ustar00zuulzuul00000000000000--- upgrade: - | The following deprecated options have been removed. * ``--endpoint-override`` (Authentication option) * ``--instance-name`` (``nova list`` command) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/remove-deprecated-option-in-3.3.0-82a413157838570d.yaml0000664000175000017500000000461300000000000031710 0ustar00zuulzuul00000000000000--- update: - The following deprecated options have been removed: - create instances: - --num-instance replaced by --min-count and --max-count - --key_name replaced by --key-name - --user_data replaced by --user-data - --availability_zone replaced by -- availability-zone - --security_groups replaced by --sercurity-groups - --block_device_mapping replaced by --block-device-mapping - list servers: - --reservation_id replaced by --reservation-id - --instance_name replaced by --instance-name - --all_tenants replaced by --all-tenants - rebuild instance: - --rebuild_password replaced by --rebuild-password - get serial console: - --console_type replaced by --console-type - create dns private domain: - --availability_zone replaced by --availability-zone - list security groups: - --all_tenants replaced by --all-tenants - add key pairs: - --pub_key replaced by --pub-key - live-migrate servers: - --block_migrate replaced by --block-migrate - --disk_over_commit replaced by --disk-over-commit - update quotas: - --floating_ips replaced by --floating-ips - --metadata_items replaced by --metadata-items - --injected_files replaced by --injected-files - --injected_file_content_bytes replaced by --injected-file-content-bytes - update quota classes: - --floating_ips replaced by --floating-ips - --metadata_items replaced by --metadata-items - --injected_files replaced by --injected-files - --injected_file_content_bytes replaced by --injected-file-content-bytes - create server groups: - --policy - Authentication Options: - --os_username replaced by --os-username - --os_password replaced by --os-password - --os_tenant_name replaced by --os-tenant-name - --os_auth_url replaced by --os-auth-url - --os_region_name replaced by --os-region-name - --os_auth_system replaced by --os-auth-system - --endpoint-type replaced by --os-endpoint-type - Optional arguments: - --service_type replaced by --service-type - --service_name replaced by --service-name - --volume_service_name replaced by --volume-service-name - --os_compute_api_version replaced by --os-compute-api-version - --bypass_url replaced by --bypass-url ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/remove-deprecated-option-in-9.0.0-bc76629d28f1d4c4.yaml0000664000175000017500000000055300000000000032216 0ustar00zuulzuul00000000000000--- upgrade: - | The following deprecated options have been removed: - ``--tenant`` (from ``flavor access list``) - ``--cell_name`` (from ``migration list``) - ``--volume-service-name`` (global option) As a result, the ``novaclient.v2.migrations.MigrationManager.list`` python API binding method no longer takes a ``cell_name`` kwarg. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/remove-hosts-d08855550c40b9c6.yaml0000664000175000017500000000073100000000000026534 0ustar00zuulzuul00000000000000--- upgrade: - | The following CLIs and their backing API bindings were deprecated and capped at microversion 2.43: * ``nova host-describe`` - superseded by ``nova hypervisor-show`` * ``nova host-list`` - superseded by ``nova hypervisor-list`` * ``nova host-update`` - superseded by ``nova service-enable`` and ``nova service-disable`` * ``nova host-action`` - no alternative by design The CLIs and API bindings have now been removed. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/remove-py26-support-f31379e86f40d975.yaml0000664000175000017500000000011500000000000027722 0ustar00zuulzuul00000000000000--- upgrade: - Python 2.6 support has been removed from python-novaclient. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/remove-run_tests.sh-3bdcaee4d388177a.yaml0000664000175000017500000000014700000000000030333 0ustar00zuulzuul00000000000000--- upgrade: - The ``run_tests.sh`` shell script that was deprecated in Newton has been removed. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/remove-service-binary-arg-ec2838214c8c7abc.yaml0000664000175000017500000000026700000000000031305 0ustar00zuulzuul00000000000000--- upgrade: - | The deprecated ``binary`` argument to the ``nova service-enable``, ``nova service-disable``, and ``nova service-force-down`` commands has been removed. ././@PaxHeader0000000000000000000000000000021600000000000011454 xustar0000000000000000120 path=python-novaclient-18.7.0/releasenotes/notes/remove-virt-interfaces-add-rm-fixed-floating-398c905d9c91cca8.yaml 22 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/remove-virt-interfaces-add-rm-fixed-floating-398c905d9c90000664000175000017500000000116100000000000033131 0ustar00zuulzuul00000000000000--- upgrade: - | The following CLIs and their backing API bindings were deprecated and capped at microversion 2.44: * ``nova add-fixed-ip``: use python-neutronclient or openstacksdk * ``nova remove-fixed-ip``: use python-neutronclient or openstacksdk * ``nova floating-ip-associate``: use python-neutronclient or openstacksdk * ``nova floating-ip-disassociate``: use python-neutronclient or openstacksdk * ``nova virtual-interface-list``: there is no replacement as this is only implemented for nova-network which is deprecated The CLIs and API bindings have now been removed. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/remove_api_v_1_1-88b3f18ce1423b46.yaml0000664000175000017500000000015600000000000027274 0ustar00zuulzuul00000000000000--- upgrade: - remove version 1.1 API support as we only support v2 and v2.1 API in nova side now. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/rename-apikey-to-password-735588d841efa49e.yaml0000664000175000017500000000030600000000000031206 0ustar00zuulzuul00000000000000--- deprecations: - The **api_key** variable of novaclient.client.Client entry-point was deprecated in favor of **password**. Nothing has changed in the case of positional argument usage. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/rename-bypass-url-42cd96956a6bc6dc.yaml0000664000175000017500000000060700000000000027677 0ustar00zuulzuul00000000000000--- deprecations: - The **--bypass-url** CLI argument was deprecated in favor of **--endpoint-override** - The **bypass_url** argument of novaclient.client.Client entry-point was deprecated in favor of **endpoint_override**. fixes: - Inability to use bypass-url with keystone session is fixed. features: - You can use **bypass-url** / **endpoint-override** with Keystone V3. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/restrict-args-for-novaclient-ep-491098c3634365be.yaml0000664000175000017500000000034100000000000032141 0ustar00zuulzuul00000000000000--- upgrade: - | novaclient.client.Client entry-point accepts only 5 positional arguments:: version, username, api_key, project_id, auth_url Using positional arguments for all other options is now an error. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/restrict-direct-use-of-v2client-c8e1ee2afefec5a1.yaml0000664000175000017500000000032700000000000032644 0ustar00zuulzuul00000000000000--- upgrade: - novaclient.v2.client.Client raises an exception in case of direct usage instead of warning message. novaclient.client.Client is a primary interface to initialize the python client for Nova. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/restrict-interface-parameter-e5fe166f39ba0935.yaml0000664000175000017500000000021700000000000032020 0ustar00zuulzuul00000000000000--- deprecations: - keyword argument **interface** of novaclient.client.Client entry-point was deprecated in favor of **endpoint_type**; ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/return-request-id-to-caller-52c5423794b33f8b.yaml0000664000175000017500000000251100000000000031357 0ustar00zuulzuul00000000000000--- prelude: > Methods in manager classes and resource classes return wrapper classes that wrap values returned originally. For example, a wrapper class for list, a wrapper class for dict, a wrapper class for str and so on. The wrapper classes have a 'request_ids' property for request IDs returned from Nova (nova-api). So the caller can get the Nova's request IDs, then output them to logs with its own request ID. The function to output them to the logs will be implemented in other projects (cinder, heat, etc.). features: - Methods in manager classes and resource classes return wrapper classes that wrap values returned originally. For example, a wrapper class for list, a wrapper class for dict, a wrapper class for str and so on. The wrapper classes have a 'request_ids' property for request IDs returned from Nova (nova-api). So the caller can get the Nova's request IDs, then output them to logs with its own request ID. The function to output them to the logs will be implemented in other projects (cinder, heat, etc.). upgrade: - In case that methods return a response object and body originally and body is None, the methods return the wrapper class for tuple as 'body' instead of the wrapper class for None. The wrapper class for None has not been added. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/rm-baremetal-cli-api-fbc8c242d48cd2fb.yaml0000664000175000017500000000040600000000000030340 0ustar00zuulzuul00000000000000--- upgrade: - | The baremetal CLIs and python API bindings were deprecated in the Newton release and have been removed. Use python-openstackclient or python-ironicclient for CLIs. Use python-ironicclient or openstacksdk for python API bindings././@PaxHeader0000000000000000000000000000020600000000000011453 xustar0000000000000000112 path=python-novaclient-18.7.0/releasenotes/notes/rm-deprecated-commands-options-ocata-00f249810e5bdf97.yaml 22 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/rm-deprecated-commands-options-ocata-00f249810e5bdf97.ya0000664000175000017500000000054600000000000032726 0ustar00zuulzuul00000000000000--- prelude: > Several deprecated commands have been removed. See the upgrade section for details. upgrade: - | The following deprecated commands have been removed: * absolute-limits * add-floating-ip * aggregate-details * credentials * endpoints * rate-limits * remove-floating-ip * rename * root-password ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/search-hypervisor-detailed-352f3ac70d42fe6e.yaml0000664000175000017500000000055300000000000031544 0ustar00zuulzuul00000000000000--- features: - | The ``novaclient.v2.hypervisors.HypervisorManager.search`` method now accepts a ``detailed`` boolean kwarg which defaults to False but when True will search for the given hypervisor hostname match and return details about any matching hypervisors. Specifying ``detailed=True`` requires compute API version 2.53 or greater. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/server-networks-sorted-1d3a7f1c1f88e846.yaml0000664000175000017500000000045700000000000030731 0ustar00zuulzuul00000000000000--- other: - | The ``novaclient.v2.servers.Server.networks`` property method now returns an OrderedDict where the keys are sorted in natural (ascending) order. This means the ``nova show`` and ``nova list`` output will have predictable sort order on the networks attached to a server. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/show-instance-usage-audit-logs-7826b411fac1283b.yaml0000664000175000017500000000056000000000000032101 0ustar00zuulzuul00000000000000--- features: - Added new client API and CLI (``nova instance-usage-audit-log``) to get server usage audit logs. By default, it lists usage audits for all servers on all compute hosts where usage auditing is configured. If you specify the ``--before`` option, the result is filtered by the date and time before which to list server usage audits. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/strict_hostname_match-f37243f0520a09a2.yaml0000664000175000017500000000072400000000000030445 0ustar00zuulzuul00000000000000--- features: - | Provides "--strict" option for "nova host-servers-migrate", "nova host-evacuate", "nova host-evacuate-live" and "nova host-meta" commands. When "--strict" option is used, the action will be applied to a single compute with the exact hypervisor hostname string match rather than to the computes with hostname substring match. When the specified hostname does not exist in the system, "NotFound" error code will be returned. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/switch-to-sessionclient-aa49d16599fea570.yaml0000664000175000017500000000074100000000000031051 0ustar00zuulzuul00000000000000--- upgrade: - | When using novaclient as a library (via novaclient.client.Client entry-point), an internal implementation of HTTPClient was used if a session object is not specified. For better user experience we switched to using SessionClient which uses keystoneauth (Keystone folks maintain this library) for all auth stuff. The SessionClient interface is similar to HTTPClient, but there is a small possibility that you will notice a difference. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/notes/volume-cli-removal-ffcb94421a356042.yaml0000664000175000017500000000043500000000000027670 0ustar00zuulzuul00000000000000--- upgrade: - Volume, volume-type and volume-snapshot create/update/delete/list CLIs and python API bindings are removed. Use python-cinderclient or python-openstackclient for CLIs instead. Use python-cinderclient or python-openstacksdk for python API bindings instead. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1725030944.8604684 python-novaclient-18.7.0/releasenotes/source/0000775000175000017500000000000000000000000021304 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/source/2023.1.rst0000664000175000017500000000020200000000000022555 0ustar00zuulzuul00000000000000=========================== 2023.1 Series Release Notes =========================== .. release-notes:: :branch: stable/2023.1 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/source/2023.2.rst0000664000175000017500000000020200000000000022556 0ustar00zuulzuul00000000000000=========================== 2023.2 Series Release Notes =========================== .. release-notes:: :branch: stable/2023.2 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/source/2024.1.rst0000664000175000017500000000020200000000000022556 0ustar00zuulzuul00000000000000=========================== 2024.1 Series Release Notes =========================== .. release-notes:: :branch: stable/2024.1 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1725030944.8604684 python-novaclient-18.7.0/releasenotes/source/_static/0000775000175000017500000000000000000000000022732 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/source/_static/.placeholder0000664000175000017500000000000000000000000025203 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1725030944.8604684 python-novaclient-18.7.0/releasenotes/source/_templates/0000775000175000017500000000000000000000000023441 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/source/_templates/.placeholder0000664000175000017500000000000000000000000025712 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/source/conf.py0000664000175000017500000000137000000000000022604 0ustar00zuulzuul00000000000000# -*- coding: utf-8 -*- # # python-novaclient Release Notes documentation build configuration file # -- 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 = [ 'reno.sphinxext', 'openstackdocstheme', ] # The master toctree document. master_doc = 'index' # -- 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' # -- Options for Internationalization output ------------------------------ locale_dirs = ['locale/'] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/source/index.rst0000664000175000017500000000063500000000000023151 0ustar00zuulzuul00000000000000Welcome to Nova Client Release Notes documentation! =================================================== Contents ======== .. toctree:: :maxdepth: 2 unreleased 2024.1 2023.2 2023.1 zed yoga xena wallaby victoria ussuri train stein rocky queens pike ocata newton mitaka liberty Indices and tables ================== * :ref:`genindex` * :ref:`search` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/source/liberty.rst0000664000175000017500000000021300000000000023504 0ustar00zuulzuul00000000000000============================== Liberty Series Release Notes ============================== .. release-notes:: :branch: stable/liberty ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1725030944.780464 python-novaclient-18.7.0/releasenotes/source/locale/0000775000175000017500000000000000000000000022543 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1725030944.780464 python-novaclient-18.7.0/releasenotes/source/locale/en_GB/0000775000175000017500000000000000000000000023515 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1725030944.8644686 python-novaclient-18.7.0/releasenotes/source/locale/en_GB/LC_MESSAGES/0000775000175000017500000000000000000000000025302 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po0000664000175000017500000001607200000000000030341 0ustar00zuulzuul00000000000000# Andi Chandler , 2017. #zanata # Andi Chandler , 2018. #zanata # Andi Chandler , 2022. #zanata msgid "" msgstr "" "Project-Id-Version: python-novaclient\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-06-24 11:46+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2022-07-05 09:42+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 "" "**auto**: This tells the Compute service to automatically allocate a network " "for the project if one is not available and then associate an IP from that " "network with the server. This is the same behavior as passing nics=None " "before the 2.37 microversion." msgstr "" "**auto**: This tells the Compute service to automatically allocate a network " "for the project if one is not available and then associate an IP from that " "network with the server. This is the same behaviour as passing nics=None " "before the 2.37 microversion." msgid "" "**none**: This tells the Compute service to not allocate any networking for " "the server." msgstr "" "**none**: This tells the Compute service to not allocate any networking for " "the server." msgid "--all_tenants replaced by --all-tenants" msgstr "--all_tenants replaced by --all-tenants" msgid "--availability-zone" msgstr "--availability-zone" msgid "--availability_zone replaced by -- availability-zone" msgstr "--availability_zone replaced by -- availability-zone" msgid "--availability_zone replaced by --availability-zone" msgstr "--availability_zone replaced by --availability-zone" msgid "--block_device_mapping replaced by --block-device-mapping" msgstr "--block_device_mapping replaced by --block-device-mapping" msgid "--block_migrate replaced by --block-migrate" msgstr "--block_migrate replaced by --block-migrate" msgid "--bypass_url replaced by --bypass-url" msgstr "--bypass_url replaced by --bypass-url" msgid "--config-drive" msgstr "--config-drive" msgid "--console_type replaced by --console-type" msgstr "--console_type replaced by --console-type" msgid "--disk_over_commit replaced by --disk-over-commit" msgstr "--disk_over_commit replaced by --disk-over-commit" msgid "--endpoint-type replaced by --os-endpoint-type" msgstr "--endpoint-type replaced by --os-endpoint-type" msgid "--floating_ips replaced by --floating-ips" msgstr "--floating_ips replaced by --floating-ips" msgid "--injected_file_content_bytes replaced by --injected-file-content-bytes" msgstr "" "--injected_file_content_bytes replaced by --injected-file-content-bytes" msgid "--injected_files replaced by --injected-files" msgstr "--injected_files replaced by --injected-files" msgid "--instance_name replaced by --instance-name" msgstr "--instance_name replaced by --instance-name" msgid "--key-name" msgstr "--key-name" msgid "--key_name replaced by --key-name" msgstr "--key_name replaced by --key-name" msgid "--metadata_items replaced by --metadata-items" msgstr "--metadata_items replaced by --metadata-items" msgid "--no-config-drive" msgstr "--no-config-drive" msgid "--num-instance replaced by --min-count and --max-count" msgstr "--num-instance replaced by --min-count and --max-count" msgid "--os_auth_system replaced by --os-auth-system" msgstr "--os_auth_system replaced by --os-auth-system" msgid "--os_auth_url replaced by --os-auth-url" msgstr "--os_auth_url replaced by --os-auth-url" msgid "--os_compute_api_version replaced by --os-compute-api-version" msgstr "--os_compute_api_version replaced by --os-compute-api-version" msgid "--os_password replaced by --os-password" msgstr "--os_password replaced by --os-password" msgid "--os_region_name replaced by --os-region-name" msgstr "--os_region_name replaced by --os-region-name" msgid "--os_tenant_name replaced by --os-tenant-name" msgstr "--os_tenant_name replaced by --os-tenant-name" msgid "--os_username replaced by --os-username" msgstr "--os_username replaced by --os-username" msgid "--policy" msgstr "--policy" msgid "--power-state" msgstr "--power-state" msgid "--progress" msgstr "--progress" msgid "--pub_key replaced by --pub-key" msgstr "--pub_key replaced by --pub-key" msgid "--rebuild_password replaced by --rebuild-password" msgstr "--rebuild_password replaced by --rebuild-password" msgid "--reservation_id replaced by --reservation-id" msgstr "--reservation_id replaced by --reservation-id" msgid "--security_groups replaced by --sercurity-groups" msgstr "--security_groups replaced by --security-groups" msgid "--service_name replaced by --service-name" msgstr "--service_name replaced by --service-name" msgid "--service_type replaced by --service-type" msgstr "--service_type replaced by --service-type" msgid "--task-state" msgstr "--task-state" msgid "--user_data replaced by --user-data" msgstr "--user_data replaced by --user-data" msgid "--vm-state" msgstr "--vm-state" msgid "--volume_service_name replaced by --volume-service-name" msgstr "--volume_service_name replaced by --volume-service-name" msgid "10.0.0" msgstr "10.0.0" msgid "10.1.0" msgstr "10.1.0" msgid "10.1.1" msgstr "10.1.1" msgid "10.2.0" msgstr "10.2.0" msgid "10.3.0" msgstr "10.3.0" msgid "11.0.0" msgstr "11.0.0" msgid "11.0.1" msgstr "11.0.1" msgid "3.0.0" msgstr "3.0.0" msgid "3.3.0" msgstr "3.3.0" msgid "4.0.0" msgstr "4.0.0" msgid "4.1.0" msgstr "4.1.0" msgid "5.0.0" msgstr "5.0.0" msgid "5.1.0" msgstr "5.1.0" msgid "6.0.0" msgstr "6.0.0" msgid ":ref:`search`" msgstr ":ref:`search`" msgid "Current Series Release Notes" msgstr "Current Series Release Notes" msgid "Liberty Series Release Notes" msgstr "Liberty Series Release Notes" msgid "Mitaka Series Release Notes" msgstr "Mitaka Series Release Notes" msgid "Newton Series Release Notes" msgstr "Newton Series Release Notes" msgid "Ocata Series Release Notes" msgstr "Ocata Series Release Notes" msgid "Pike Series Release Notes" msgstr "Pike Series Release Notes" msgid "Queens Series Release Notes" msgstr "Queens Series Release Notes" msgid "Rocky Series Release Notes" msgstr "Rocky Series Release Notes" msgid "Stein Series Release Notes" msgstr "Stein Series Release Notes" 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 "Xena Series Release Notes" msgstr "Xena Series Release Notes" msgid "Yoga Series Release Notes" msgstr "Yoga Series Release Notes" msgid "secgroup-create" msgstr "secgroup-create" msgid "secgroup-delete" msgstr "secgroup-delete" msgid "secgroup-delete-default-rule" msgstr "secgroup-delete-default-rule" msgid "secgroup-delete-group-rule" msgstr "secgroup-delete-group-rule" msgid "secgroup-delete-rule" msgstr "secgroup-delete-rule" msgid "secgroup-list" msgstr "secgroup-list" msgid "secgroup-list-default-rules" msgstr "secgroup-list-default-rules" ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1725030944.780464 python-novaclient-18.7.0/releasenotes/source/locale/fr/0000775000175000017500000000000000000000000023152 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1725030944.8644686 python-novaclient-18.7.0/releasenotes/source/locale/fr/LC_MESSAGES/0000775000175000017500000000000000000000000024737 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/source/locale/fr/LC_MESSAGES/releasenotes.po0000664000175000017500000001146700000000000030001 0ustar00zuulzuul00000000000000# Gérald LONLAS , 2016. #zanata msgid "" msgstr "" "Project-Id-Version: Nova Client Release Notes 7.1.1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-03-24 05:03+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2016-10-22 06:13+0000\n" "Last-Translator: Gérald LONLAS \n" "Language-Team: French\n" "Language: fr\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n > 1)\n" msgid "3.0.0" msgstr "3.0.0" msgid "3.3.0" msgstr "3.3.0" msgid "4.0.0" msgstr "4.0.0" msgid "4.1.0" msgstr "4.1.0" msgid "5.0.0" msgstr "5.0.0" msgid "5.1.0" msgstr "5.1.0" msgid "6.0.0" msgstr "6.0.0" msgid ":ref:`genindex`" msgstr ":ref:`genindex`" msgid ":ref:`search`" msgstr ":ref:`search`" msgid "Contents" msgstr "Contenu" msgid "Current Series Release Notes" msgstr "Note de la release actuelle" msgid "Deprecation Notes" msgstr "Notes dépréciées " msgid "Fixed IPs" msgstr "IP fixes" msgid "Floating IPs" msgstr "IP flottantes" msgid "Indices and tables" msgstr "Index et table des matières" msgid "Known Issues" msgstr "Problèmes connus" msgid "Liberty Series Release Notes" msgstr "Note de release pour Liberty" msgid "Mitaka Series Release Notes" msgstr "Note de release pour Mitaka" msgid "New Features" msgstr "Nouvelles fonctionnalités" msgid "Newton Series Release Notes" msgstr "Note de release pour Newton" msgid "Security Group Rules" msgstr "Règles de groupe de sécurité" msgid "Security Groups" msgstr "Groupes de sécurité" msgid "Upgrade Notes" msgstr "Notes de mises à jours" msgid "Welcome to Nova Client Release Notes documentation!" msgstr "Bienvenue dans la documentation de la note de Release du Client Nova" msgid "absolute-limits" msgstr "absolute-limits" msgid "add-floating-ip" msgstr "add-floating-ip" msgid "aggregate-details" msgstr "aggregate-details" msgid "credentials" msgstr "informations d'identification" msgid "dns-create" msgstr "dns-create" msgid "dns-create-private-domain" msgstr "dns-create-private-domain" msgid "dns-create-public-domain" msgstr "dns-create-public-domain" msgid "dns-delete" msgstr "dns-delete" msgid "dns-delete-domain" msgstr "dns-delete-domain" msgid "dns-domains" msgstr "dns-domains" msgid "dns-list" msgstr "dns-list" msgid "endpoints" msgstr "points de terminaison" msgid "fixed-ip-get" msgstr "fixed-ip-get" msgid "fixed-ip-reserve" msgstr "fixed-ip-reserve" msgid "fixed-ip-unreserve" msgstr "fixed-ip-unreserve" msgid "floating-ip-bulk-create" msgstr "floating-ip-bulk-create" msgid "floating-ip-bulk-delete" msgstr "floating-ip-bulk-delete" msgid "floating-ip-bulk-list" msgstr "floating-ip-bulk-list" msgid "floating-ip-create" msgstr "floating-ip-create" msgid "floating-ip-delete" msgstr "floating-ip-delete" msgid "floating-ip-list" msgstr "floating-ip-list" msgid "floating-ip-pool-list" msgstr "floating-ip-pool-list" msgid "network-associate-host" msgstr "network-associate-host" msgid "network-associate-project" msgstr "network-associate-project" msgid "network-create" msgstr "network-create" msgid "network-delete" msgstr "network-delete" msgid "network-disassociate" msgstr "network-disassociate" msgid "network-list" msgstr "network-list" msgid "network-show" msgstr "network-show" msgid "nova baremetal-interface-list" msgstr "nova baremetal-interface-list" msgid "nova baremetal-node-list" msgstr "nova baremetal-node-list" msgid "nova baremetal-node-show" msgstr "nova baremetal-node-show" msgid "nova image-delete" msgstr "nova image-delete" msgid "nova image-list" msgstr "nova image-list" msgid "nova image-meta" msgstr "nova image-meta" msgid "nova image-show" msgstr "nova image-show" msgid "rate-limits" msgstr "rate-limits" msgid "remove-floating-ip" msgstr "remove-floating-ip" msgid "rename" msgstr "rename" msgid "root-password" msgstr "root-password" msgid "secgroup-add-default-rule" msgstr "secgroup-add-default-rule" msgid "secgroup-add-group-rule" msgstr "secgroup-add-group-rule" msgid "secgroup-add-rule" msgstr "secgroup-add-rule" msgid "secgroup-create" msgstr "secgroup-create" msgid "secgroup-delete" msgstr "secgroup-delete" msgid "secgroup-delete-default-rule" msgstr "secgroup-delete-default-rule" msgid "secgroup-delete-group-rule" msgstr "secgroup-delete-group-rule" msgid "secgroup-delete-rule" msgstr "secgroup-delete-rule" msgid "secgroup-list" msgstr "secgroup-list" msgid "secgroup-list-default-rules" msgstr "secgroup-list-default-rules" msgid "secgroup-list-rules" msgstr "secgroup-list-rules" msgid "secgroup-update" msgstr "secgroup-update" msgid "tenant-network-create" msgstr "tenant-network-create" msgid "tenant-network-delete" msgstr "tenant-network-delete" msgid "tenant-network-list" msgstr "tenant-network-list" msgid "tenant-network-show" msgstr "tenant-network-show" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/source/mitaka.rst0000664000175000017500000000022300000000000023301 0ustar00zuulzuul00000000000000=================================== Mitaka Series Release Notes =================================== .. release-notes:: :branch: stable/mitaka ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/source/newton.rst0000664000175000017500000000022300000000000023345 0ustar00zuulzuul00000000000000=================================== Newton Series Release Notes =================================== .. release-notes:: :branch: stable/newton ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/source/ocata.rst0000664000175000017500000000022100000000000023120 0ustar00zuulzuul00000000000000=================================== Ocata Series Release Notes =================================== .. release-notes:: :branch: stable/ocata ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/source/pike.rst0000664000175000017500000000021700000000000022766 0ustar00zuulzuul00000000000000=================================== Pike Series Release Notes =================================== .. release-notes:: :branch: stable/pike ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/source/queens.rst0000664000175000017500000000022300000000000023333 0ustar00zuulzuul00000000000000=================================== Queens Series Release Notes =================================== .. release-notes:: :branch: stable/queens ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/source/rocky.rst0000664000175000017500000000022100000000000023160 0ustar00zuulzuul00000000000000=================================== Rocky Series Release Notes =================================== .. release-notes:: :branch: stable/rocky ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/source/stein.rst0000664000175000017500000000022100000000000023153 0ustar00zuulzuul00000000000000=================================== Stein Series Release Notes =================================== .. release-notes:: :branch: stable/stein ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/source/train.rst0000664000175000017500000000017600000000000023157 0ustar00zuulzuul00000000000000========================== Train Series Release Notes ========================== .. release-notes:: :branch: stable/train ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/source/unreleased.rst0000664000175000017500000000015300000000000024164 0ustar00zuulzuul00000000000000============================ Current Series Release Notes ============================ .. release-notes:: ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/source/ussuri.rst0000664000175000017500000000020200000000000023362 0ustar00zuulzuul00000000000000=========================== Ussuri Series Release Notes =========================== .. release-notes:: :branch: stable/ussuri ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/source/victoria.rst0000664000175000017500000000022000000000000023650 0ustar00zuulzuul00000000000000============================= Victoria Series Release Notes ============================= .. release-notes:: :branch: unmaintained/victoria ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/source/wallaby.rst0000664000175000017500000000021400000000000023466 0ustar00zuulzuul00000000000000============================ Wallaby Series Release Notes ============================ .. release-notes:: :branch: unmaintained/wallaby ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/source/xena.rst0000664000175000017500000000020000000000000022761 0ustar00zuulzuul00000000000000========================= Xena Series Release Notes ========================= .. release-notes:: :branch: unmaintained/xena ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/source/yoga.rst0000664000175000017500000000020000000000000022765 0ustar00zuulzuul00000000000000========================= Yoga Series Release Notes ========================= .. release-notes:: :branch: unmaintained/yoga ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/releasenotes/source/zed.rst0000664000175000017500000000017400000000000022622 0ustar00zuulzuul00000000000000======================== Zed Series Release Notes ======================== .. release-notes:: :branch: unmaintained/zed ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/requirements.txt0000664000175000017500000000072100000000000020577 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>=3.0.0 # Apache-2.0 keystoneauth1>=3.5.0 # Apache-2.0 iso8601>=0.1.11 # MIT oslo.i18n>=3.15.3 # Apache-2.0 oslo.serialization>=2.20.0 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 PrettyTable>=0.7.2 # BSD stevedore>=2.0.1 # Apache-2.0 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1725030944.8644686 python-novaclient-18.7.0/setup.cfg0000664000175000017500000000204600000000000017136 0ustar00zuulzuul00000000000000[metadata] name = python-novaclient summary = Client library for OpenStack Compute API description_file = README.rst license = Apache License, Version 2.0 author = OpenStack author_email = openstack-discuss@lists.openstack.org home_page = https://docs.openstack.org/python-novaclient/latest python_requires = >=3.8 classifier = Development Status :: 5 - Production/Stable Environment :: Console Environment :: OpenStack Intended Audience :: Developers Intended Audience :: Information Technology License :: OSI Approved :: Apache Software License Operating System :: OS Independent Programming Language :: Python Programming Language :: Python :: 3 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3 :: Only Programming Language :: Python :: Implementation :: CPython [files] packages = novaclient [entry_points] console_scripts = nova = novaclient.shell:main [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/setup.py0000664000175000017500000000127100000000000017026 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=1725030915.0 python-novaclient-18.7.0/test-requirements.txt0000664000175000017500000000100700000000000021552 0ustar00zuulzuul00000000000000hacking>=6.1.0,<6.2.0 # Apache-2.0 bandit>=1.1.0 # Apache-2.0 coverage>=4.4.1 # Apache-2.0 ddt>=1.0.1 # MIT fixtures>=3.0.0 # Apache-2.0/BSD python-keystoneclient>=3.8.0 # Apache-2.0 python-cinderclient>=4.0.1 # Apache-2.0 python-glanceclient>=2.8.0 # Apache-2.0 python-neutronclient>=6.7.0 # Apache-2.0 requests-mock>=1.2.0 # Apache-2.0 openstacksdk>=0.11.2 # 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 tempest>=17.1.0 # Apache-2.0 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1725030944.8644686 python-novaclient-18.7.0/tools/0000775000175000017500000000000000000000000016453 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/tools/nova.bash_completion0000664000175000017500000000156500000000000022515 0ustar00zuulzuul00000000000000_nova_opts="" # lazy init _nova_flags="" # lazy init _nova_opts_exp="" # lazy init _nova() { local cur prev nbc cflags COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" if [ "x$_nova_opts" == "x" ] ; then nbc="`nova bash-completion | sed -e "s/ *-h */ /" -e "s/ *-i */ /"`" _nova_opts="`echo "$nbc" | sed -e "s/--[a-z0-9_-]*//g" -e "s/ */ /g"`" _nova_flags="`echo " $nbc" | sed -e "s/ [^-][^-][a-z0-9_-]*//g" -e "s/ */ /g"`" _nova_opts_exp="`echo "$_nova_opts" | tr ' ' '|'`" fi if [[ " ${COMP_WORDS[@]} " =~ " "($_nova_opts_exp)" " && "$prev" != "help" ]] ; then COMPLETION_CACHE=~/.novaclient/*/*-cache cflags="$_nova_flags "$(cat $COMPLETION_CACHE 2> /dev/null | tr '\n' ' ') COMPREPLY=($(compgen -W "${cflags}" -- ${cur})) else COMPREPLY=($(compgen -W "${_nova_opts}" -- ${cur})) fi return 0 } complete -F _nova nova ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/tools/nova.zsh_completion0000664000175000017500000000133000000000000022372 0ustar00zuulzuul00000000000000#compdef nova local -a nbc _nova_opts _nova_flags _nova_opts_exp cur prev nbc=(${(ps: :)$(_call_program options "$service bash-completion" 2>/dev/null)}) _nova_opts=(${nbc:#-*}) _nova_flags=(${(M)nbc:#-*}) _nova_opt_exp=${${nbc:#-*}// /|} cur=$words[CURRENT] prev=$words[(( CURRENT - 1 ))] _checkcomp(){ for word in $words[@]; do if [[ -n ${_nova_opts[(r)$word]} ]]; then return 0 fi done return 1 } echo $_nova_opts[@] |grep --color nova if [[ "$prev" != "help" ]] && _checkcomp; then COMPLETION_CACHE=(~/.novaclient/*/*-cache) cflags=($_nova_flags[@] ${(ps: :)$(cat $COMPLETION_CACHE 2>/dev/null)}) compadd "$@" -d $cflags[@] else compadd "$@" -d $_nova_opts[@] fi ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1725030915.0 python-novaclient-18.7.0/tox.ini0000664000175000017500000000574400000000000016640 0ustar00zuulzuul00000000000000[tox] envlist = py3,pep8,docs minversion = 3.18.0 ignore_basepython_conflict = true [testenv] basepython = python3 usedevelop = true # tox is silly... these need to be separated by a newline.... allowlist_externals = find rm make passenv = ZUUL_CACHE_DIR REQUIREMENTS_PIP_LOCATION deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt commands = find . -type f -name "*.pyc" -delete stestr run {posargs} [testenv:pep8] commands = flake8 {posargs} [testenv:bandit] commands = bandit -r novaclient -n5 -x tests [testenv:venv] deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = {posargs} [testenv:docs] deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/doc/requirements.txt commands = rm -rf doc/build/html doc/build/doctrees sphinx-build -W -b html -d doc/build/doctrees doc/source doc/build/html # Test the redirects. This must run after the main docs build whereto doc/build/html/.htaccess doc/test/redirect-tests.txt [testenv:pdf-docs] envdir = {toxworkdir}/docs deps = {[testenv:docs]deps} commands = rm -rf doc/build/pdf sphinx-build -W -b latex doc/source doc/build/pdf make -C doc/build/pdf [testenv:releasenotes] deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/doc/requirements.txt commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [testenv:functional{,-py38,-py39,-py310,-py311,-py312}] passenv = OS_* commands = stestr --test-path=./novaclient/tests/functional run --concurrency=1 {posargs} python novaclient/tests/functional/hooks/check_resources.py [testenv:cover] setenv = PYTHON=coverage run --source novaclient --parallel-mode commands = stestr run {posargs} coverage combine coverage html -d cover coverage xml -o cover/coverage.xml coverage report [flake8] # Following checks should be enabled in the future. # # H404 multi line docstring should start without a leading new line # H405 multi line docstring summary not separated with an empty line # # Following checks are ignored on purpose. # # Additional checks are also ignored on purpose: F811, F821, W504 ignore = E731,F811,F821,H404,H405,W504 show-source = true exclude=.venv,.git,.tox,dist,*lib/python*,*egg,build,doc/source/conf.py,releasenotes [hacking] import_exceptions = novaclient.i18n [testenv:bindep] # Do not install any requirements. We want this to be fast and work even if # system dependencies are missing, since it's used to tell you what system # dependencies are missing! This also means that bindep must be installed # separately, outside of the requirements files. deps = bindep skipsdist=True usedevelop=False commands = bindep test