diff options
| author | Corey Bryant <corey.bryant@canonical.com> | 2019-09-25 14:23:09 -0400 |
|---|---|---|
| committer | git-ubuntu importer <ubuntu-devel-discuss@lists.ubuntu.com> | 2019-09-26 00:18:33 +0000 |
| commit | fddfc0e68940906cce3341f47a97af27d99e4b47 (patch) | |
| tree | 0b906d0c52205f5ff17f3b2aa67c1b41a19c4796 | |
| parent | 1488118c4720744447e6fb460944d5a0221ab44a (diff) | |
4.0.0-0ubuntu1 (patches unapplied)import/4.0.0-0ubuntu1ubuntu/eoan-proposedubuntu/eoan-develubuntu/eoan
Imported using git-ubuntu import.
Notes
Notes:
* New upstream release for OpenStack Train.
* d/control: Align (Build-)Depends with upstream.
198 files changed, 4973 insertions, 5799 deletions
@@ -193,8 +193,7 @@ - openstack-cover-jobs - openstack-lower-constraints-jobs - openstack-python-jobs - - openstack-python36-jobs - - openstack-python37-jobs + - openstack-python3-train-jobs - publish-openstack-docs-pti - check-requirements - release-notes-jobs-python3 @@ -65,6 +65,7 @@ Clark Boylan <clark.boylan@gmail.com> Colleen Murphy <colleen@gazlene.net> Colleen Murphy <colleen@puppetlabs.com> Colleen Murphy <comurphy@suse.com> +Corey Bryant <corey.bryant@canonical.com> Cyril Roelandt <cyril.roelandt@enovance.com> Dag Stenstad <dag@stenstad.net> Daisuke Fujita <fuzita.daisuke@jp.fujitsu.com> @@ -198,6 +199,7 @@ Marcos Fermin Lobo <marcos.fermin.lobo@cern.ch> Marek Aufart <maufart@redhat.com> Marek Denis <marek.denis@cern.ch> Mark Vanderwiel <vanderwl@us.ibm.com> +Martin Chlumsky <martin.chlumsky@gmail.com> Martin Schuppert <mschuppert@redhat.com> Masayuki Igawa <masayuki@igawa.io> Matt Fischer <matt@mattfischer.com> @@ -373,6 +375,7 @@ melanie witt <melwittt@gmail.com> melissaml <ma.lei@99cloud.net> nidhimittalhada <nidhimittal19@gmail.com> npraveen35 <npraveen35@gmail.com> +pengyuesheng <pengyuesheng@gohighsec.com> phil-hopkins-a <phil.hopkins@rackspace.com> qinchunhua <qin.chunhua@zte.com.cn> qingszhao <zhao.daqing@99cloud.net> @@ -415,6 +418,8 @@ zheng yin <yin.zheng@easystack.cn> zhengsenyan <zhengsy_sean@126.com> zhiyong.dai <zhiyong.dai@easystack.cn> zhiyuan_cai <luckyvega.g@gmail.com> +zhouhenglc <zhouhenglc@inspur.com> zhouqi <qi.zhou@easystack.cn> +zhu.boxiang <zhu.boxiang@99cloud.net> zhufl <zhu.fanglei@zte.com.cn> zhurong <aaronzhu1121@gmail.com> @@ -1,21 +1,78 @@ CHANGES ======= +4.0.0 +----- + +* Update release table for Train and 4.0.0 +* More aggregate functional race chasing +* Add doc and relnote for review 639652 +* Default to Cinder v3 API +* Clean up app initialization and config +* Follow-up: fix the invalid releasenote link +* Microversion 2.77: Support Specifying AZ to unshelve +* Bump lower constraint of python-zunclient +* Remove races in floating ip functional tests +* Bump min osc-lib to 1.14.0 +* Format location columns in network commands +* Add floating IP Port Forwarding commands +* Bump hacking version +* Remove token\_endpoint auth type +* Format aggregate command fields and de-race functional tests +* Add CLI argument tests before making changes +* Fix functional.base.TestCase.openstack() to optionally omit --os-auth-type +* Document 2.53 behavior for compute service list/delete +* Add openstack server create --boot-from-volume option +* Fix <id> description for --block-device-mapping +* Support type=image with --block-device-mapping option +* Fix compute service set handling for 2.53+ +* Add 'openstack server resize (confirm|revert)' commands +* Make configuration show not require auth +* Fix typo: "to and endpoint" +* Fix module paths for volumev3 volume backup commands +* openstack port create support --extra-dhcp-option +* Mention compute service set --up|--down requires 2.11 or greater +* Update api-ref location +* Add host and hypervisor\_hostname to create servers +* Support IPv6 addresses better +* Add Python 3 Train unit tests +* docs: clarify compute service --service option +* Update the constraints url +* Add Python 3 Train unit tests +* Fix BFV server list handling with --name-lookup-one-by-one +* Use cliff formattable columns in volume v2 commands +* Use cliff formattable columns in volume v1 commands +* Use cliff formattable columns in object storage commands +* Use cliff formattable columns in image commands +* Use cliff formattable columns in identity commands +* Microversion 2.73: Support adding the reason behind a server lock +* Allow "server migrate" (not live) to take "--host" option +* Add server add/remove volume description for microversion 2.20 +* Add server event command documentation for compute API 2.21 + 3.19.0 ------ * Add changes-before attribute to server list * Deprecate openstack server migrate --host option +* Remove deprecated volume commands and args * Batch up minor cleanups for release +* Remove deprecated network options +* Remove deprecated image commands +* Remove deprecated identity commands and args +* Remove deprecated compute commands * Serialize more aggregate functional tests * Compute: Add description support for server * Remove code migrated to osc-lib long ago * Aggregate functional test tweak * Update sphinx requirement +* Rename review.openstack.org to review.opendev.org * Stop leaving temp files after unit test runs +* Use cliff formattable columns in network commands * Blacklist Bandit 1.6.0 due to directory exclusion bug * Fix bug in endpoint group deletion * document the --timing option +* Change default security group protocol to 'any' * Fix link to new opendev repo * Document that server dump create requires 2.17 * Dropping the py35 testing @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: python-openstackclient -Version: 3.19.0 +Version: 4.0.0 Summary: OpenStack Command-line Client Home-page: https://docs.openstack.org/python-openstackclient/latest/ Author: OpenStack @@ -24,8 +24,8 @@ Description: ======================== :alt: Latest Version OpenStackClient (aka OSC) is a command-line client for OpenStack that brings - the command set for Compute, Identity, Image, Object Store and Block Storage - APIs together in a single shell with a uniform command structure. + the command set for Compute, Identity, Image, Network, Object Store and Block + Storage APIs together in a single shell with a uniform command structure. The primary goal is to provide a unified shell command structure and a common language to describe operations in OpenStack. @@ -109,18 +109,6 @@ Description: ======================== If a password is not provided above (in plaintext), you will be interactively prompted to provide one securely. - Authentication may also be performed using an already-acquired token - and a URL pointing directly to the service API that presumably was acquired - from the Service Catalog:: - - export OS_TOKEN=<token> - export OS_URL=<url-to-openstack-service> - - The corresponding command-line options look very similar:: - - --os-token <token> - --os-url <url-to-openstack-service> - Platform: UNKNOWN Classifier: Environment :: OpenStack @@ -133,3 +121,4 @@ Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 @@ -16,8 +16,8 @@ OpenStackClient :alt: Latest Version OpenStackClient (aka OSC) is a command-line client for OpenStack that brings -the command set for Compute, Identity, Image, Object Store and Block Storage -APIs together in a single shell with a uniform command structure. +the command set for Compute, Identity, Image, Network, Object Store and Block +Storage APIs together in a single shell with a uniform command structure. The primary goal is to provide a unified shell command structure and a common language to describe operations in OpenStack. @@ -100,15 +100,3 @@ The corresponding command-line options look very similar:: If a password is not provided above (in plaintext), you will be interactively prompted to provide one securely. - -Authentication may also be performed using an already-acquired token -and a URL pointing directly to the service API that presumably was acquired -from the Service Catalog:: - - export OS_TOKEN=<token> - export OS_URL=<url-to-openstack-service> - -The corresponding command-line options look very similar:: - - --os-token <token> - --os-url <url-to-openstack-service> diff --git a/debian/changelog b/debian/changelog index c049180..eaba0c4 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,10 @@ +python-openstackclient (4.0.0-0ubuntu1) eoan; urgency=medium + + * New upstream release for OpenStack Train. + * d/control: Align (Build-)Depends with upstream. + + -- Corey Bryant <corey.bryant@canonical.com> Wed, 25 Sep 2019 14:23:09 -0400 + python-openstackclient (3.19.0-0ubuntu2) eoan; urgency=medium * d/tests/control: Drop Python 2 autopkgtest now that we no longer diff --git a/debian/control b/debian/control index cc6b411..08b68eb 100644 --- a/debian/control +++ b/debian/control @@ -30,7 +30,7 @@ Build-Depends-Indep: python3-fixtures (>= 3.0.0), python3-glanceclient (>= 1:2.8.0), python3-gnocchiclient (>= 3.3.1) <!nodoc>, - python3-hacking (>= 0.12.0), + python3-hacking (>= 1.1.0), python3-heatclient (>= 1.10.0) <!nodoc>, python3-ironic-inspector-client (>= 1.5.0) <!nodoc>, python3-ironicclient (>= 2.3.0) <!nodoc>, @@ -42,12 +42,12 @@ Build-Depends-Indep: python3-mock (>= 2.0.0), python3-muranoclient (>= 0.8.2) <!nodoc>, python3-neutronclient (>= 1:6.7.0) <!nodoc>, - python3-novaclient (>= 2:10.0.0), + python3-novaclient (>= 2:15.0.0), python3-octaviaclient (>= 1.3.0) <!nodoc>, python3-openstackdocstheme (>= 1.23.2), python3-openstacksdk (>= 0.17.0), python3-os-client-config (>= 1.28.0), - python3-osc-lib (>= 1.11.0), + python3-osc-lib (>= 1.14.0), python3-oslo.i18n (>= 3.15.3), python3-oslo.serialization (>= 1.2.0), python3-oslo.utils (>= 3.33.0), @@ -69,7 +69,7 @@ Build-Depends-Indep: python3-troveclient (>= 1:2.2.0) <!nodoc>, python3-wrapt (>= 1.7.0), python3-zaqarclient (>= 1.0.0) <!nodoc>, - python3-zunclient (>= 1.3.0) <!nodoc>, + python3-zunclient (>= 3.4.0) <!nodoc>, subunit, Standards-Version: 4.1.3 Vcs-Browser: https://git.launchpad.net/~ubuntu-server-dev/ubuntu/+source/python-openstackclient @@ -108,9 +108,9 @@ Depends: python3-keystoneauth1 (>= 3.6.2), python3-keystoneclient (>= 1:3.17.0), python3-neutronclient (>= 1:2.6.0), - python3-novaclient (>= 2:10.0.0), + python3-novaclient (>= 2:15.0.0), python3-openstacksdk (>= 0.17.0), - python3-osc-lib (>= 1.11.0), + python3-osc-lib (>= 1.14.0), python3-oslo.i18n (>= 3.15.3), python3-oslo.serialization (>= 1.2.0), python3-oslo.utils (>= 3.33.0), diff --git a/doc/source/cli/authentication.rst b/doc/source/cli/authentication.rst index b153f54..3b404bc 100644 --- a/doc/source/cli/authentication.rst +++ b/doc/source/cli/authentication.rst @@ -39,15 +39,6 @@ There are at least three authentication types that are always available: (described below as token/endpoint) in that a token and an authentication URL are supplied and the plugin retrieves a new token. [Required: ``--os-auth-url``, ``--os-token``] -* **Token/Endpoint**: This is the original token authentication (known as 'token - flow' in the early CLI documentation in the OpenStack wiki). It requires - a token and a direct endpoint that is used in the API call. The difference - from the new Token type is this token is used as-is, no call is made - to the Identity service from the client. This type is most often used to - bootstrap a Keystone server where the token is the ``admin_token`` configured - in ``keystone.conf``. It will also work with other services and a regular - scoped token such as one obtained from a ``token issue`` command. - [Required: ``--os-url``, ``--os-token``] * **Others**: Other authentication plugins such as SAML, Kerberos, and OAuth1.0 are under development and also supported. To use them, they must be selected by supplying the ``--os-auth-type`` option. @@ -76,8 +67,6 @@ by the ``ClientManager`` object. is selected based on the existing options. This is a short-circuit evaluation, the first match wins. - * If ``--os-url`` and ``--os-token`` are both present ``token_endpoint`` - is selected * If ``--os-username`` is supplied ``password`` is selected * If ``--os-token`` is supplied ``token`` is selected * If no selection has been made by now exit with error diff --git a/doc/source/cli/backwards-incompatible.rst b/doc/source/cli/backwards-incompatible.rst index fcb6868..9d43754 100644 --- a/doc/source/cli/backwards-incompatible.rst +++ b/doc/source/cli/backwards-incompatible.rst @@ -16,16 +16,93 @@ from this backwards incompatible change handling. Backwards Incompatible Changes ============================== -.. Carry this section as comments until 4.0 release -.. Release 4.0 -.. ----------- +Release 4.0 +----------- + +1. Remove ``ip fixed add|remove`` commands. + Use ``server add|remove fixed ip`` commands instead. + + * Removed in: 4.0 + * Commit: https://review.opendev.org/612781 + +2. Remove ``ip floating add|remove`` commands. + Use ``server add|remove floating ip`` commands instead. + + * Removed in: 4.0 + * Commit: https://review.opendev.org/612781 + +3. Remove ``service create`` option ``--type``. Service type is + a positional argument. + + * Removed in: 4.0 + * Commit: https://review.opendev.org/612798 + +4. Remove ``role list`` options ``--project`` and ``--user``. + Use ``role assignment list`` options ``--project`` and ``--user`` instead. + + * Removed in: 4.0 + * Commit: https://review.opendev.org/612798 + +5. Remove ``user role list`` command. + Use ``role assignment list`` options ``--project`` and ``--user`` instead. + + * Removed in: 4.0 + * Commit: https://review.opendev.org/612798 + +6. Remove ``image create|set`` option ``--owner``. + Use ``--project`` option instead. + + * Removed in: 4.0 + * Commit: https://review.opendev.org/659431 + +7. Remove ``port create|set`` options ``--device-id`` and ``--host-id``. + Use ``--device`` and ``--host`` instead. + + * Removed in: 4.0 + * Commit: https://review.opendev.org/613644 + +8. Remove ``router set`` option ``--clear-routes``. + Use ``no-route`` option instead. + + * Removed in: 4.0 + * Commit: https://review.opendev.org/613644 + +9. Remove ``security group rule create`` options ``--src-ip`` and ``--src-group``. + Use ``--remote-ip`` and ``--remote-group`` options instead. + + * Removed in: 4.0 + * Commit: https://review.opendev.org/613644 + +10. Remove ``backup`` commands. + Use ``volume backup`` commands instead. + + * Removed in: 4.0 + * Commit: https://review.opendev.org/612751 + +11. Remove ``snapshot`` commands. + Use ``volume snapshot`` commands instead. + + * Removed in: 4.0 + * Commit: https://review.opendev.org/612751 + +12. Remove ``volume create`` options ``--project``, ``--user``, ``--multi-attach``. + + * Removed in: 4.0 + * Commit: https://review.opendev.org/612751 + +13. Change ``volume transfer request accept`` to use new option ``--auth-key`` + rather than a second positional argument. + + * Removed in: 4.0 + * Commit: https://review.opendev.org/612751 -.. 1. Change ``volume transfer request accept`` to use new option ``--auth-key`` -.. rather than a second positional argument. +14. Remove 'Token/Endpoint' auth plugin support (type ``token_endpoint``). + This remained as a compatibility for the ``admin_token`` auth type to + support the ``--url`` global option. That option is also now removed, + use ``--endpoint`` instead. -.. * As of: 4.0 -.. * Remove in: <5.0> -.. * Commit: <tbd> + * Removed in: 4.0 + * Commit: https://review.opendev.org/<tbd> Release 3.12 ------------ @@ -46,7 +123,7 @@ Release 3.12 * As of: 3.12.0 * Removed in: n/a * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1657956 - * Commit: https://review.openstack.org/#/c/423081/ + * Commit: https://review.opendev.org/#/c/423081/ Release 3.10 ------------ @@ -55,7 +132,7 @@ Release 3.10 with Nova-network clouds. * As of: 3.10 - * Commit: https://review.openstack.org/460679 + * Commit: https://review.opendev.org/460679 2. The positional argument ``<snapshot-name>`` of the ``volume snapshot create`` command is no longer optional. @@ -67,7 +144,7 @@ Release 3.10 * As of: 3.10 * Bug: 1659894 - * Commit: https://review.openstack.org/440497 + * Commit: https://review.opendev.org/440497 Release 3.0 ----------- @@ -81,7 +158,7 @@ Release 3.0 * As of: 3.0 * Removed in: n/a * Bug: n/a - * Commit: https://review.openstack.org/332938 + * Commit: https://review.opendev.org/332938 Releases Before 3.0 @@ -95,7 +172,7 @@ Releases Before 3.0 * As of: 1.0.2 * Removed in: TBD * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1406654 - * Commit: https://review.openstack.org/#/c/147379/ + * Commit: https://review.opendev.org/#/c/147379/ 2. <type> should not be optional for command `openstack service create` @@ -107,7 +184,7 @@ Releases Before 3.0 * As of: 1.0.2 * Removed in: TBD * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1404073 - * Commit: https://review.openstack.org/#/c/143242/ + * Commit: https://review.opendev.org/#/c/143242/ 3. Command `openstack security group rule delete` now requires rule id @@ -119,7 +196,7 @@ Releases Before 3.0 * As of: 1.2.1 * Removed in: NA * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1450872 - * Commit: https://review.openstack.org/#/c/179446/ + * Commit: https://review.opendev.org/#/c/179446/ 4. Command `openstack image create` does not update already existing image @@ -133,7 +210,7 @@ Releases Before 3.0 * As of: 1.5.0 * Removed in: NA * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1461817 - * Commit: https://review.openstack.org/#/c/194654/ + * Commit: https://review.opendev.org/#/c/194654/ 5. Command `openstack network list --dhcp` has been removed @@ -147,7 +224,7 @@ Releases Before 3.0 * As of: 1.6.0 * Removed in: NA * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/472613 - * Commit: https://review.openstack.org/#/c/194654/ + * Commit: https://review.opendev.org/#/c/194654/ 6. Plugin interface change for default API versions @@ -162,7 +239,7 @@ Releases Before 3.0 * As of: 1.2.1 * Removed in: NA * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1453229 - * Commit: https://review.openstack.org/#/c/181514/ + * Commit: https://review.opendev.org/#/c/181514/ 7. `image set` commands will no longer return the modified resource @@ -186,7 +263,7 @@ Releases Before 3.0 * As of 1.9.0 * Removed in: NA * Bug: https://launchpad.net/bugs/1506841 - * Commit: https://review.openstack.org/#/c/236736/ + * Commit: https://review.opendev.org/#/c/236736/ 9. `flavor set/unset` commands will no longer return the modified resource @@ -198,7 +275,7 @@ Releases Before 3.0 * As of: NA * Removed in: NA * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1546065 - * Commit: https://review.openstack.org/#/c/280663/ + * Commit: https://review.opendev.org/#/c/280663/ 10. `security group set` commands will no longer return the modified resource @@ -210,7 +287,7 @@ Releases Before 3.0 * As of: NA * Removed in: NA * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1546065 - * Commit: https://review.openstack.org/#/c/281087/ + * Commit: https://review.opendev.org/#/c/281087/ 11. `compute agent set` commands will no longer return the modified resource @@ -222,7 +299,7 @@ Releases Before 3.0 * As of: NA * Removed in: NA * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1546065 - * Commit: https://review.openstack.org/#/c/281088/ + * Commit: https://review.opendev.org/#/c/281088/ 12. `<version> <url> <md5hash>` should be optional for command `openstack compute agent set` @@ -235,7 +312,7 @@ Releases Before 3.0 * As of: NA * Removed in: NA * Bug: NA - * Commit: https://review.openstack.org/#/c/328819/ + * Commit: https://review.opendev.org/#/c/328819/ 13. `aggregate set` commands will no longer return the modified resource @@ -247,7 +324,7 @@ Releases Before 3.0 * As of: NA * Removed in: NA * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1546065 - * Commit: https://review.openstack.org/#/c/281089/ + * Commit: https://review.opendev.org/#/c/281089/ 14. Output of `ip floating list` command has changed. @@ -291,7 +368,7 @@ Releases Before 3.0 * As of: NA * Removed in: NA * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1519502 - * Commit: https://review.openstack.org/#/c/277720/ + * Commit: https://review.opendev.org/#/c/277720/ For Developers ============== @@ -302,4 +379,4 @@ update this file. To review all changes that are affected, use the following query: -https://review.openstack.org/#/q/project:openstack/python-openstackclient+AND+message:BackwardsIncompatibleImpact,n,z +https://review.opendev.org/#/q/project:openstack/python-openstackclient+AND+message:BackwardsIncompatibleImpact,n,z diff --git a/doc/source/cli/command-objects/backup.rst b/doc/source/cli/command-objects/backup.rst deleted file mode 100644 index f892327..0000000 --- a/doc/source/cli/command-objects/backup.rst +++ /dev/null @@ -1,137 +0,0 @@ -====== -backup -====== - -Block Storage v1, v2 - -backup create -------------- - -Create new backup -(Deprecated, please use ``volume backup create`` instead) - -.. program:: backup create -.. code:: bash - - openstack backup create - [--container <container>] - [--name <name>] - [--description <description>] - [--snapshot <snapshot>] - [--force] - [--incremental] - <volume> - -.. option:: --container <container> - - Optional backup container name - -.. option:: --name <name> - - Name of the backup - -.. option:: --description <description> - - Description of the backup - -.. option:: --snapshot <snapshot> - - Snapshot to backup (name or ID) - - *Volume version 2 only* - -.. option:: --force - - Allow to back up an in-use volume - - *Volume version 2 only* - -.. option:: --incremental - - Perform an incremental backup - - *Volume version 2 only* - -.. _backup_create-backup: -.. describe:: <volume> - - Volume to backup (name or ID) - -backup delete -------------- - -Delete backup(s) -(Deprecated, please use ``volume backup delete`` instead) - -.. program:: backup delete -.. code:: bash - - openstack backup delete - [--force] - <backup> [<backup> ...] - -.. option:: --force - - Allow delete in state other than error or available - - *Volume version 2 only* - -.. _backup_delete-backup: -.. describe:: <backup> - - Backup(s) to delete (name or ID) - -backup list ------------ - -List backups -(Deprecated, please use ``volume backup list`` instead) - -.. program:: backup list -.. code:: bash - - openstack backup list - -.. _backup_list-backup: -.. option:: --long - - List additional fields in output - -backup restore --------------- - -Restore backup -(Deprecated, please use ``volume backup restore`` instead) - -.. program:: backup restore -.. code:: bash - - openstack backup restore - <backup> - <volume> - -.. _backup_restore-backup: -.. describe:: <backup> - - Backup to restore (name or ID) - -.. describe:: <volume> - - Volume to restore to (name or ID) - -backup show ------------ - -Display backup details -(Deprecated, please use ``volume backup show`` instead) - -.. program:: backup show -.. code:: bash - - openstack backup show - <backup> - -.. _backup_show-backup: -.. describe:: <backup> - - Backup to display (name or ID) diff --git a/doc/source/cli/command-objects/compute-service.rst b/doc/source/cli/command-objects/compute-service.rst index ba624ea..a76a9c2 100644 --- a/doc/source/cli/command-objects/compute-service.rst +++ b/doc/source/cli/command-objects/compute-service.rst @@ -18,13 +18,19 @@ Delete compute service(s) .. _compute_service_delete-service: .. describe:: <service> - Compute service(s) to delete (ID only) + Compute service(s) to delete (ID only). If using + ``--os-compute-api-version`` 2.53 or greater, the ID is a UUID which can + be retrieved by listing compute services using the same 2.53+ microversion. compute service list -------------------- List compute services +Using ``--os-compute-api-version`` 2.53 or greater will return the ID as a +UUID value which can be used to uniquely identify the service in a multi-cell +deployment. + .. program:: compute service list .. code:: bash @@ -39,7 +45,8 @@ List compute services .. option:: --service <service> - List only specified service (name only) + List only specified service binaries (name only). For example, + ``nova-compute``, ``nova-conductor``, etc. .. option:: --long @@ -73,11 +80,12 @@ Set compute service properties .. option:: --up - Force up service + Force up service. Requires ``--os-compute-api-version`` 2.11 or greater. .. option:: --down - Force down service + Force down service. . Requires ``--os-compute-api-version`` 2.11 or + greater. .. _compute_service_set-host: .. describe:: <host> @@ -86,4 +94,4 @@ Set compute service properties .. describe:: <service> - Name of service (Binary name) + Name of service (Binary name), for example ``nova-compute`` diff --git a/doc/source/cli/command-objects/endpoint.rst b/doc/source/cli/command-objects/endpoint.rst index 030947c..6d02532 100644 --- a/doc/source/cli/command-objects/endpoint.rst +++ b/doc/source/cli/command-objects/endpoint.rst @@ -7,7 +7,7 @@ Identity v2, v3 endpoint add project -------------------- -Associate a project to and endpoint for endpoint filtering +Associate a project to an endpoint for endpoint filtering .. program:: endpoint add project .. code:: bash diff --git a/doc/source/cli/command-objects/floating-ip-pool.rst b/doc/source/cli/command-objects/floating-ip-pool.rst index 9213b86..5c8f3aa 100644 --- a/doc/source/cli/command-objects/floating-ip-pool.rst +++ b/doc/source/cli/command-objects/floating-ip-pool.rst @@ -2,7 +2,7 @@ floating ip pool ================ -Compute v2, Network v2 +Network v2 floating ip pool list --------------------- diff --git a/doc/source/cli/command-objects/floating-ip-port-forwarding.rst b/doc/source/cli/command-objects/floating-ip-port-forwarding.rst new file mode 100644 index 0000000..5012787 --- /dev/null +++ b/doc/source/cli/command-objects/floating-ip-port-forwarding.rst @@ -0,0 +1,173 @@ +=========================== +floating ip port forwarding +=========================== + +Network v2 + +floating ip port forwarding create +---------------------------------- + +Create floating IP Port Forwarding + +.. program:: floating ip port forwarding create +.. code:: bash + + openstack floating ip port forwarding create + --internal-ip-address <internal-ip-address> + --port <port> + --internal-protocol-port <port-number> + --external-protocol-port <port-number> + --protocol <protocol> + <floating-ip> + + +.. describe:: --internal-ip-address <internal-ip-address> + + The fixed IPv4 address of the network port associated + to the floating IP port forwarding + +.. describe:: --port <port> + + The name or ID of the network port associated to the + floating IP port forwarding + +.. describe:: --internal-protocol-port <port-number> + + The protocol port number of the network port fixed + IPv4 address associated to the floating IP port + forwarding + +.. describe:: --external-protocol-port <port-number> + + The protocol port number of the port forwarding's + floating IP address + +.. describe:: --protocol <protocol> + + The protocol used in the floating IP port forwarding, + for instance: TCP, UDP + +.. describe:: <floating-ip> + + Floating IP that the port forwarding belongs to (IP + address or ID) + +floating ip port forwarding delete +---------------------------------- + +Delete floating IP Port Forwarding(s) + +.. program:: floating ip port forwarding delete +.. code:: bash + + openstack floating ip port forwarding delete <floating-ip> + <port-forwarding-id> [<port-forwarding-id> ...] + +.. describe:: <floating-ip> + + Floating IP that the port forwarding belongs to (IP + address or ID) + +.. describe:: <port-forwarding-id> + + The ID of the floating IP port forwarding + +floating ip port forwarding list +-------------------------------- + +List floating IP Port Forwarding(s) + +.. program:: floating ip port forwarding list +.. code:: bash + + openstack floating ip port forwarding list + [--port <port>] + [--external-protocol-port <port-number>] + [--protocol protocol] + <floating-ip> + +.. option:: --port <port> + + The ID of the network port associated to the floating + IP port forwarding + +.. option:: --protocol <protocol> + + The IP protocol used in the floating IP port + forwarding + +.. describe:: <floating-ip> + + Floating IP that the port forwarding belongs to (IP + address or ID) + +floating ip port forwarding set +------------------------------- + +Set floating IP Port Forwarding properties + +.. program:: floating ip port forwarding set +.. code:: bash + + openstack floating ip port forwarding set + [--port <port>] + [--internal-ip-address <internal-ip-address>] + [--internal-protocol-port <port-number>] + [--external-protocol-port <port-number>] + [--protocol <protocol>] + <floating-ip> + <port-forwarding-id> + +.. option:: --port <port> + + The ID of the network port associated to the floating + IP port forwarding + +.. option:: --internal-ip-address <internal-ip-address> + + The fixed IPv4 address of the network port associated + to the floating IP port forwarding + +.. option:: --internal-protocol-port <port-number> + + The TCP/UDP/other protocol port number of the network + port fixed IPv4 address associated to the floating IP + port forwarding + +.. option:: --external-protocol-port <port-number> + + The TCP/UDP/other protocol port number of the port + forwarding's floating IP address + +.. option:: --protocol <protocol> + + The IP protocol used in the floating IP port + forwarding + +.. describe:: <floating-ip> + + Floating IP that the port forwarding belongs to (IP + address or ID) + +.. describe:: <port-forwarding-id> + + The ID of the floating IP port forwarding + +floating ip port forwarding show +-------------------------------- + +Display floating IP Port Forwarding details + +.. program:: floating ip port forwarding show +.. code:: bash + + openstack floating ip show <floating-ip> <port-forwarding-id> + +.. describe:: <floating-ip> + + Floating IP that the port forwarding belongs to (IP + address or ID) + +.. describe:: <port-forwarding-id> + + The ID of the floating IP port forwarding diff --git a/doc/source/cli/command-objects/floating-ip.rst b/doc/source/cli/command-objects/floating-ip.rst index 749c32d..d122ccb 100644 --- a/doc/source/cli/command-objects/floating-ip.rst +++ b/doc/source/cli/command-objects/floating-ip.rst @@ -2,7 +2,7 @@ floating ip =========== -Compute v2, Network v2 +Network v2 floating ip create ------------------ diff --git a/doc/source/cli/command-objects/ip-fixed.rst b/doc/source/cli/command-objects/ip-fixed.rst deleted file mode 100644 index f5b11dc..0000000 --- a/doc/source/cli/command-objects/ip-fixed.rst +++ /dev/null @@ -1,47 +0,0 @@ -======== -ip fixed -======== - -Compute v2 - -ip fixed add ------------- - -Add fixed IP address to server -(Deprecated, please use ``server add fixed ip`` instead) - -.. program:: ip fixed add -.. code:: bash - - openstack ip fixed add - <network> - <server> - -.. describe:: <network> - - Network to fetch an IP address from (name or ID) - -.. describe:: <server> - - Server to receive the IP address (name or ID) - -ip fixed remove ---------------- - -Remove fixed IP address from server -(Deprecated, please use ``server remove fixed ip`` instead) - -.. program:: ip fixed remove -.. code:: bash - - openstack ip fixed remove - <ip-address> - <server> - -.. describe:: <ip-address> - - IP address to remove from server (name only) - -.. describe:: <server> - - Server to remove the IP address from (name or ID) diff --git a/doc/source/cli/command-objects/role.rst b/doc/source/cli/command-objects/role.rst index 9819fd1..e2a2b45 100644 --- a/doc/source/cli/command-objects/role.rst +++ b/doc/source/cli/command-objects/role.rst @@ -146,68 +146,12 @@ List roles .. code:: bash openstack role list - --domain <domain> | --project <project> [--project-domain <project-domain>] - --user <user> [--user-domain <user-domain>] | --group <group> [--group-domain <group-domain>] - --inherited + [--domain <domain>] .. option:: --domain <domain> Filter roles by <domain> (name or ID) - (Deprecated if being used to list assignments in conjunction with the - ``--user <user>``, option, please use ``role assignment list`` instead) - -.. option:: --project <project> - - Filter roles by <project> (name or ID) - - (Deprecated, please use ``role assignment list`` instead) - -.. option:: --user <user> - - Filter roles by <user> (name or ID) - - (Deprecated, please use ``role assignment list`` instead) - -.. option:: --group <group> - - Filter roles by <group> (name or ID) - - (Deprecated, please use ``role assignment list`` instead) - -.. option:: --user-domain <user-domain> - - Domain the user belongs to (name or ID). - This can be used in case collisions between user names exist. - - (Deprecated, please use ``role assignment list`` instead) - - .. versionadded:: 3 - -.. option:: --group-domain <group-domain> - - Domain the group belongs to (name or ID). - This can be used in case collisions between group names exist. - - (Deprecated, please use ``role assignment list`` instead) - - .. versionadded:: 3 - -.. option:: --project-domain <project-domain> - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - - (Deprecated, please use ``role assignment list`` instead) - - .. versionadded:: 3 - -.. option:: --inherited - - Specifies if the role grant is inheritable to the sub projects. - - (Deprecated, please use ``role assignment list`` instead) - .. versionadded:: 3 role remove diff --git a/doc/source/cli/command-objects/security-group-rule.rst b/doc/source/cli/command-objects/security-group-rule.rst index 1dbf16d..5a2d834 100644 --- a/doc/source/cli/command-objects/security-group-rule.rst +++ b/doc/source/cli/command-objects/security-group-rule.rst @@ -27,8 +27,9 @@ Create a new security group rule .. option:: --remote-ip <ip-address> - Remote IP address block - (may use CIDR notation; default for IPv4 rule: 0.0.0.0/0) + Remote IP address block (may use CIDR notation; + default for IPv4 rule: 0.0.0.0/0, + default for IPv6 rule: ::/0) .. option:: --remote-group <group> @@ -61,8 +62,8 @@ Create a new security group rule IP protocol (ah, dccp, egp, esp, gre, icmp, igmp, ipv6-encap, ipv6-frag, ipv6-icmp, ipv6-nonxt, ipv6-opts, ipv6-route, ospf, pgm, rsvp, sctp, tcp, - udp, udplite, vrrp and integer representations [0-255]; - default: tcp) + udp, udplite, vrrp and integer representations [0-255] + or any; default: any (all protocols)) *Network version 2* @@ -134,6 +135,7 @@ List security group rules openstack security group rule list [--all-projects] [--protocol <protocol>] + [--ethertype <ethertype>] [--ingress | --egress] [--long] [<group>] @@ -151,13 +153,18 @@ List security group rules *Compute version 2 does not have additional fields to display.* - .. option:: --protocol List rules by the IP protocol (ah, dhcp, egp, esp, gre, icmp, igmp, ipv6-encap, ipv6-frag, ipv6-icmp, ipv6-nonxt,ipv6-opts, ipv6-route, ospf, pgm, rsvp, sctp, tcp, udp, udplite, vrrp and integer - representations [0-255]) + representations [0-255] or any; default: any (all protocols)) + + *Network version 2* + +.. option:: --ethertype + + List rules by the Ethertype (IPv4 or IPv6) *Network version 2* diff --git a/doc/source/cli/command-objects/snapshot.rst b/doc/source/cli/command-objects/snapshot.rst deleted file mode 100644 index fc51606..0000000 --- a/doc/source/cli/command-objects/snapshot.rst +++ /dev/null @@ -1,176 +0,0 @@ -======== -snapshot -======== - -Block Storage v1, v2 - -snapshot create ---------------- - -Create new snapshot -(Deprecated, please use ``volume snapshot create`` instead) - -.. program:: snapshot create -.. code:: bash - - openstack snapshot create - [--name <name>] - [--description <description>] - [--force] - [--property <key=value> [...] ] - <volume> - -.. option:: --name <name> - - Name of the snapshot - -.. option:: --description <description> - - Description of the snapshot - -.. option:: --force - - Create a snapshot attached to an instance. Default is False - -.. option:: --property <key=value> - - Set a property to this snapshot (repeat option to set multiple properties) - - *Volume version 2 only* - -.. _snapshot_create-snapshot: -.. describe:: <volume> - - Volume to snapshot (name or ID) - -snapshot delete ---------------- - -Delete snapshot(s) -(Deprecated, please use ``volume snapshot delete`` instead) - -.. program:: snapshot delete -.. code:: bash - - openstack snapshot delete - <snapshot> [<snapshot> ...] - -.. _snapshot_delete-snapshot: -.. describe:: <snapshot> - - Snapshot(s) to delete (name or ID) - -snapshot list -------------- - -List snapshots -(Deprecated, please use ``volume snapshot list`` instead) - -.. program:: snapshot list -.. code:: bash - - openstack snapshot list - [--all-projects] - [--long] - [--limit <num-snapshots>] - [--marker <snapshot>] - -.. option:: --all-projects - - Include all projects (admin only) - -.. option:: --long - - List additional fields in output - -.. option:: --limit <num-snapshots> - - Maximum number of snapshots to display - - *Volume version 2 only* - -.. option:: --marker <snapshot> - - The last snapshot ID of the previous page - - *Volume version 2 only* - -snapshot set ------------- - -Set snapshot properties -(Deprecated, please use ``volume snapshot set`` instead) - -.. program:: snapshot set -.. code:: bash - - openstack snapshot set - [--name <name>] - [--description <description>] - [--property <key=value> [...] ] - [--state <state>] - <snapshot> - -.. _snapshot_restore-snapshot: -.. option:: --name <name> - - New snapshot name - -.. option:: --description <description> - - New snapshot description - -.. option:: --property <key=value> - - Property to add or modify for this snapshot (repeat option to set multiple properties) - -.. option:: --state <state> - - New snapshot state. - ("available", "error", "creating", "deleting", or "error_deleting") (admin only) - (This option simply changes the state of the snapshot in the database with - no regard to actual status, exercise caution when using) - - *Volume version 2 only* - -.. describe:: <snapshot> - - Snapshot to modify (name or ID) - -snapshot show -------------- - -Display snapshot details -(Deprecated, please use ``volume snapshot show`` instead) - -.. program:: snapshot show -.. code:: bash - - openstack snapshot show - <snapshot> - -.. _snapshot_show-snapshot: -.. describe:: <snapshot> - - Snapshot to display (name or ID) - -snapshot unset --------------- - -Unset snapshot properties -(Deprecated, please use ``volume snapshot unset`` instead) - -.. program:: snapshot unset -.. code:: bash - - openstack snapshot unset - [--property <key>] - <snapshot> - -.. option:: --property <key> - - Property to remove from snapshot (repeat option to remove multiple properties) - -.. describe:: <snapshot> - - Snapshot to modify (name or ID) diff --git a/doc/source/cli/command-objects/usage.rst b/doc/source/cli/command-objects/usage.rst index 9cd0f70..bb13176 100644 --- a/doc/source/cli/command-objects/usage.rst +++ b/doc/source/cli/command-objects/usage.rst @@ -9,6 +9,9 @@ usage list List resource usage per project +Compute API v2.40+ returns all matching entities rather than being +limited to the API server configured maximum (``CONF.api.max_limit``). + .. program:: usage list .. code:: bash diff --git a/doc/source/cli/command-objects/user-role.rst b/doc/source/cli/command-objects/user-role.rst deleted file mode 100644 index 4f443f3..0000000 --- a/doc/source/cli/command-objects/user-role.rst +++ /dev/null @@ -1,27 +0,0 @@ -========= -user role -========= - -Identity v2 - -user role list --------------- - -List user-role assignments - -*Removed in version 3.* - -.. program:: user role list -.. code:: bash - - openstack user role list - [--project <project>] - [<user>] - -.. option:: --project <project> - - Filter users by `<project>` (name or ID) - -.. describe:: <user> - - User to list (name or ID) diff --git a/doc/source/cli/commands.rst b/doc/source/cli/commands.rst index e302fda..7c38aa5 100644 --- a/doc/source/cli/commands.rst +++ b/doc/source/cli/commands.rst @@ -95,9 +95,9 @@ referring to both Compute and Volume quotas. * ``extension``: (**Compute**, **Identity**, **Network**, **Volume**) OpenStack server API extensions * ``federation protocol``: (**Identity**) the underlying protocol used while federating identities * ``flavor``: (**Compute**) predefined server configurations: ram, root disk and so on -* ``fixed ip``: (**Compute**, **Network**) - an internal IP address assigned to a server -* ``floating ip``: (**Compute**, **Network**) - a public IP address that can be mapped to a server -* ``floating ip pool``: (**Compute**, **Network**) - a pool of public IP addresses +* ``fixed ip``: (**Compute**) - an internal IP address assigned to a server +* ``floating ip``: (**Network**) - a public IP address that can be mapped to a server +* ``floating ip pool``: (**Network**) - a pool of public IP addresses * ``group``: (**Identity**) a grouping of users * ``host``: (**Compute**) - the physical computer running compute services * ``hypervisor``: (**Compute**) the virtual machine manager @@ -106,9 +106,6 @@ referring to both Compute and Volume quotas. * ``image``: (**Image**) a disk image * ``image member``: (**Image**) a project that is a member of an Image * ``ip availability``: (**Network**) - details of IP usage of a network -* ``ip fixed``: (**Compute**, **Network**) - an internal IP address assigned to a server -* ``ip floating``: (**Compute**, **Network**) - a public IP address that can be mapped to a server -* ``ip floating pool``: (**Compute**, **Network**) - a pool of public IP addresses * ``keypair``: (**Compute**) an SSH public key * ``limits``: (**Compute**, **Volume**) resource usage limits * ``mapping``: (**Identity**) a definition to translate identity provider attributes to Identity concepts diff --git a/doc/source/cli/data/glance.csv b/doc/source/cli/data/glance.csv index 2985e30..994a8fd 100644 --- a/doc/source/cli/data/glance.csv +++ b/doc/source/cli/data/glance.csv @@ -1,24 +1,24 @@ -explain,WONTFIX,Describe a specific model.
-image-create,image create,Create a new image.
-image-deactivate,image set --deactivate,Deactivate specified image.
-image-delete,image delete,Delete specified image.
-image-download,image save,Download a specific image.
-image-list,image list,List images you can access.
-image-reactivate,image set --activate,Reactivate specified image.
-image-show,image show,Describe a specific image.
-image-tag-delete,image set --tag <tag>,Delete the tag associated with the given image.
-image-tag-update,image unset --tag <tag>,Update an image with the given tag.
-image-update,image set,Update an existing image.
-image-upload,,Upload data for a specific image.
-location-add,,Add a location (and related metadata) to an image.
-location-delete,,Remove locations (and related metadata) from an image.
-location-update,,Update metadata of an image's location.
-member-create,image add project,Create member for a given image.
-member-delete,image remove project,Delete image member.
-member-list,,Describe sharing permissions by image.
-member-update,image set --accept --reject --status,Update the status of a member for a given image.
-task-create,,Create a new task.
-task-list,,List tasks you can access.
-task-show,,Describe a specific task.
-bash-completion,complete,Prints arguments for bash_completion.
-help,help,Display help about this program or one of its subcommands.
\ No newline at end of file +explain,WONTFIX,Describe a specific model. +image-create,image create,Create a new image. +image-deactivate,image set --deactivate,Deactivate specified image. +image-delete,image delete,Delete specified image. +image-download,image save,Download a specific image. +image-list,image list,List images you can access. +image-reactivate,image set --activate,Reactivate specified image. +image-show,image show,Describe a specific image. +image-tag-delete,image unset --tag <tag>,Delete the tag associated with the given image. +image-tag-update,image set --tag <tag>,Update an image with the given tag. +image-update,image set,Update an existing image. +image-upload,,Upload data for a specific image. +location-add,,Add a location (and related metadata) to an image. +location-delete,,Remove locations (and related metadata) from an image. +location-update,,Update metadata of an image's location. +member-create,image add project,Create member for a given image. +member-delete,image remove project,Delete image member. +member-list,,Describe sharing permissions by image. +member-update,image set --accept --reject --status,Update the status of a member for a given image. +task-create,,Create a new task. +task-list,,List tasks you can access. +task-show,,Describe a specific task. +bash-completion,complete,Prints arguments for bash_completion. +help,help,Display help about this program or one of its subcommands. diff --git a/doc/source/index.rst b/doc/source/index.rst index abe0803..ea4cd3e 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -55,7 +55,7 @@ Contributing OpenStackClient utilizes all of the usual OpenStack processes and requirements for contributions. The code is hosted `on OpenStack's Git server`_. `Bug reports`_ -and `blueprints`_ may be submitted to the :code:`python-openstackclient` project +may be submitted to the :code:`python-openstackclient` Storyboard project on `Launchpad`_. Code may be submitted to the :code:`openstack/python-openstackclient` project using `Gerrit`_. Developers may also be found in the `IRC channel`_ ``#openstack-sdks``. @@ -64,7 +64,6 @@ Developers may also be found in the `IRC channel`_ ``#openstack-sdks``. .. _Launchpad: https://launchpad.net/python-openstackclient .. _Gerrit: http://docs.openstack.org/infra/manual/developers.html#development-workflow .. _Bug reports: https://storyboard.openstack.org/#!/project/975 -.. _blueprints: https://blueprints.launchpad.net/python-openstackclient .. _PyPi: https://pypi.org/project/python-openstackclient .. _tarball: http://tarballs.openstack.org/python-openstackclient .. _IRC channel: https://wiki.openstack.org/wiki/IRC diff --git a/lower-constraints.txt b/lower-constraints.txt index 43d724a..a84643c 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -22,14 +22,14 @@ extras==1.0.0 fasteners==0.7.0 fixtures==3.0.0 flake8-import-order==0.13 -flake8==2.5.5 +flake8==2.6.2 future==0.16.0 futurist==1.2.0 gitdb==0.6.4 GitPython==1.0.1 gnocchiclient==3.3.1 greenlet==0.4.10 -hacking==0.12.0 +hacking==1.1.0 httplib2==0.9.1 idna==2.6 iso8601==0.1.11 @@ -54,7 +54,7 @@ openstacksdk==0.17.0 os-client-config==1.28.0 os-service-types==1.2.0 os-testr==1.0.0 -osc-lib==1.10.0 +osc-lib==1.14.0 oslo.concurrency==3.26.0 oslo.config==5.2.0 oslo.context==2.19.2 @@ -71,14 +71,13 @@ paramiko==2.0.0 Paste==2.0.2 PasteDeploy==1.5.0 pbr==2.0.0 -pep8==1.5.7 pika-pool==0.1.3 pika==0.10.0 ply==3.10 positional==1.2.1 prettytable==0.7.2 pyasn1==0.1.8 -pycodestyle==2.3.1 +pycodestyle==2.0.0 pycparser==2.18 pyflakes==0.8.1 pyinotify==0.9.6 @@ -100,7 +99,7 @@ python-mimeparse==1.6.0 python-mistralclient==3.1.0 python-muranoclient==0.8.2 python-neutronclient==6.7.0 -python-novaclient==10.0.0 +python-novaclient==15.0.0 python-octaviaclient==1.3.0 python-rsdclient==0.1.0 python-saharaclient==1.4.0 @@ -110,7 +109,7 @@ python-subunit==1.0.0 python-swiftclient==3.2.0 python-troveclient==2.2.0 python-zaqarclient==1.0.0 -python-zunclient==1.3.0 +python-zunclient==3.4.0 pytz==2013.6 PyYAML==3.12 repoze.lru==0.7 diff --git a/openstackclient/api/auth_plugin.py b/openstackclient/api/auth_plugin.py deleted file mode 100644 index a106b1e..0000000 --- a/openstackclient/api/auth_plugin.py +++ /dev/null @@ -1,61 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -"""Authentication Plugin Library""" - -from keystoneauth1 import loading -from keystoneauth1 import token_endpoint - -from openstackclient.i18n import _ - - -class TokenEndpoint(loading.BaseLoader): - """Auth plugin to handle traditional token/endpoint usage - - Keystoneauth contains a Token plugin class that now correctly - handles the token/endpoint auth compatible with OSC. However, - the AdminToken loader deprecates the 'url' argument, which breaks - OSC compatibility, so make one that works. - """ - - @property - def plugin_class(self): - return token_endpoint.Token - - def load_from_options(self, url, token, **kwargs): - """A plugin for static authentication with an existing token - - :param string url: Service endpoint - :param string token: Existing token - """ - - return super(TokenEndpoint, self).load_from_options( - endpoint=url, - token=token, - ) - - def get_options(self): - """Return the legacy options""" - - options = [ - loading.Opt( - 'url', - help=_('Specific service endpoint to use'), - ), - loading.Opt( - 'token', - secret=True, - help=_('Authentication token to use'), - ), - ] - return options diff --git a/openstackclient/api/compute_v2.py b/openstackclient/api/compute_v2.py index 7dc4e44..e30177a 100644 --- a/openstackclient/api/compute_v2.py +++ b/openstackclient/api/compute_v2.py @@ -145,7 +145,7 @@ class APIv2(api.BaseAPI): ): """Create a new floating ip - https://developer.openstack.org/api-ref/compute/#create-allocate-floating-ip-address + https://docs.openstack.org/api-ref/compute/#create-allocate-floating-ip-address :param pool: Name of floating IP pool """ @@ -170,7 +170,7 @@ class APIv2(api.BaseAPI): ): """Delete a floating IP - https://developer.openstack.org/api-ref/compute/#delete-deallocate-floating-ip-address + https://docs.openstack.org/api-ref/compute/#delete-deallocate-floating-ip-address :param string floating_ip_id: Floating IP ID @@ -189,7 +189,7 @@ class APIv2(api.BaseAPI): ): """Return a security group given name or ID - https://developer.openstack.org/api-ref/compute/#list-floating-ip-addresses + https://docs.openstack.org/api-ref/compute/#list-floating-ip-addresses :param string floating_ip: Floating IP address @@ -209,7 +209,7 @@ class APIv2(api.BaseAPI): ): """Get floating IPs - https://developer.openstack.org/api-ref/compute/#show-floating-ip-address-details + https://docs.openstack.org/api-ref/compute/#show-floating-ip-address-details :returns: list of floating IPs @@ -258,7 +258,7 @@ class APIv2(api.BaseAPI): ): """Get floating IP pools - https://developer.openstack.org/api-ref/compute/?expanded=#list-floating-ip-pools + https://docs.openstack.org/api-ref/compute/?expanded=#list-floating-ip-pools :returns: list of floating IP pools @@ -276,7 +276,7 @@ class APIv2(api.BaseAPI): ): """Lists hypervisor Hosts - https://developer.openstack.org/api-ref/compute/#list-hosts + https://docs.openstack.org/api-ref/compute/#list-hosts Valid for Compute 2.0 - 2.42 :param string zone: @@ -299,7 +299,7 @@ class APIv2(api.BaseAPI): ): """Modify host properties - https://developer.openstack.org/api-ref/compute/#update-host-status + https://docs.openstack.org/api-ref/compute/#update-host-status Valid for Compute 2.0 - 2.42 status @@ -329,7 +329,7 @@ class APIv2(api.BaseAPI): ): """Show host - https://developer.openstack.org/api-ref/compute/#show-host-details + https://docs.openstack.org/api-ref/compute/#show-host-details Valid for Compute 2.0 - 2.42 """ @@ -356,7 +356,7 @@ class APIv2(api.BaseAPI): ): """Create a new network - https://developer.openstack.org/api-ref/compute/#create-network + https://docs.openstack.org/api-ref/compute/#create-network :param string name: Network label (required) @@ -387,7 +387,7 @@ class APIv2(api.BaseAPI): ): """Delete a network - https://developer.openstack.org/api-ref/compute/#delete-network + https://docs.openstack.org/api-ref/compute/#delete-network :param string network: Network name or ID @@ -411,7 +411,7 @@ class APIv2(api.BaseAPI): ): """Return a network given name or ID - https://developer.openstack.org/api-ref/compute/#show-network-details + https://docs.openstack.org/api-ref/compute/#show-network-details :param string network: Network name or ID @@ -431,7 +431,7 @@ class APIv2(api.BaseAPI): ): """Get networks - https://developer.openstack.org/api-ref/compute/#list-networks + https://docs.openstack.org/api-ref/compute/#list-networks :returns: list of networks @@ -450,7 +450,7 @@ class APIv2(api.BaseAPI): ): """Create a new security group - https://developer.openstack.org/api-ref/compute/#create-security-group + https://docs.openstack.org/api-ref/compute/#create-security-group :param string name: Security group name @@ -476,7 +476,7 @@ class APIv2(api.BaseAPI): ): """Delete a security group - https://developer.openstack.org/api-ref/compute/#delete-security-group + https://docs.openstack.org/api-ref/compute/#delete-security-group :param string security_group: Security group name or ID @@ -500,7 +500,7 @@ class APIv2(api.BaseAPI): ): """Return a security group given name or ID - https://developer.openstack.org/api-ref/compute/#show-security-group-details + https://docs.openstack.org/api-ref/compute/#show-security-group-details :param string security_group: Security group name or ID @@ -523,7 +523,7 @@ class APIv2(api.BaseAPI): ): """Get security groups - https://developer.openstack.org/api-ref/compute/#list-security-groups + https://docs.openstack.org/api-ref/compute/#list-security-groups :param integer limit: query return count limit @@ -556,7 +556,7 @@ class APIv2(api.BaseAPI): ): """Update a security group - https://developer.openstack.org/api-ref/compute/#update-security-group + https://docs.openstack.org/api-ref/compute/#update-security-group :param string security_group: Security group name or ID @@ -600,7 +600,7 @@ class APIv2(api.BaseAPI): ): """Create a new security group rule - https://developer.openstack.org/api-ref/compute/#create-security-group-rule + https://docs.openstack.org/api-ref/compute/#create-security-group-rule :param string security_group_id: Security group ID @@ -643,7 +643,7 @@ class APIv2(api.BaseAPI): ): """Delete a security group rule - https://developer.openstack.org/api-ref/compute/#delete-security-group-rule + https://docs.openstack.org/api-ref/compute/#delete-security-group-rule :param string security_group_rule_id: Security group rule ID diff --git a/openstackclient/common/client_config.py b/openstackclient/common/client_config.py deleted file mode 100644 index a22dd0c..0000000 --- a/openstackclient/common/client_config.py +++ /dev/null @@ -1,71 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -"""OpenStackConfig subclass for argument compatibility""" - -from osc_lib.cli import client_config - - -# Sublcass OpenStackConfig in order to munge config values -# before auth plugins are loaded -class OSC_Config(client_config.OSC_Config): - - # TODO(dtroyer): Remove _auth_default_domain when the v3otp fix is - # backported to osc-lib, should be in release 1.3.0 - def _auth_default_domain(self, config): - """Set a default domain from available arguments - - Migrated from clientmanager.setup_auth() - """ - - identity_version = config.get('identity_api_version', '') - auth_type = config.get('auth_type', None) - - # TODO(mordred): This is a usability improvement that's broadly useful - # We should port it back up into os-client-config. - default_domain = config.get('default_domain', None) - if (identity_version == '3' and - not auth_type.startswith('v2') and - default_domain): - - # NOTE(stevemar): If PROJECT_DOMAIN_ID or PROJECT_DOMAIN_NAME is - # present, then do not change the behaviour. Otherwise, set the - # PROJECT_DOMAIN_ID to 'OS_DEFAULT_DOMAIN' for better usability. - if ( - auth_type in ("password", "v3password", "v3totp") and - not config['auth'].get('project_domain_id') and - not config['auth'].get('project_domain_name') - ): - config['auth']['project_domain_id'] = default_domain - - # NOTE(stevemar): If USER_DOMAIN_ID or USER_DOMAIN_NAME is present, - # then do not change the behaviour. Otherwise, set the - # USER_DOMAIN_ID to 'OS_DEFAULT_DOMAIN' for better usability. - # NOTE(aloga): this should only be set if there is a username. - # TODO(dtroyer): Move this to os-client-config after the plugin has - # been loaded so we can check directly if the options are accepted. - if ( - auth_type in ("password", "v3password", "v3totp") and - not config['auth'].get('user_domain_id') and - not config['auth'].get('user_domain_name') - ): - config['auth']['user_domain_id'] = default_domain - return config - - def load_auth_plugin(self, config): - """Get auth plugin and validate args""" - - loader = self._get_auth_loader(config) - config = self._validate_auth(config, loader) - auth_plugin = loader.load_from_options(**config['auth']) - return auth_plugin diff --git a/openstackclient/common/configuration.py b/openstackclient/common/configuration.py index 57825bb..53b30d5 100644 --- a/openstackclient/common/configuration.py +++ b/openstackclient/common/configuration.py @@ -25,6 +25,8 @@ REDACTED = "<redacted>" class ShowConfiguration(command.ShowOne): _description = _("Display configuration details") + auth_required = False + def get_parser(self, prog_name): parser = super(ShowConfiguration, self).get_parser(prog_name) mask_group = parser.add_mutually_exclusive_group() @@ -45,13 +47,21 @@ class ShowConfiguration(command.ShowOne): def take_action(self, parsed_args): - auth_plg_name = self.app.client_manager.auth_plugin_name - secret_opts = [o.dest for o in base.get_plugin_options(auth_plg_name) - if o.secret] - info = self.app.client_manager.get_configuration() + + # Assume a default secret list in case we do not have an auth_plugin + secret_opts = ["password", "token"] + + if getattr(self.app.client_manager, "auth_plugin_name", None): + auth_plg_name = self.app.client_manager.auth_plugin_name + secret_opts = [ + o.dest for o in base.get_plugin_options(auth_plg_name) + if o.secret + ] + for key, value in six.iteritems(info.pop('auth', {})): if parsed_args.mask and key.lower() in secret_opts: - value = REDACTED + value = REDACTED info['auth.' + key] = value + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py index fa64647..3834de1 100644 --- a/openstackclient/compute/v2/aggregate.py +++ b/openstackclient/compute/v2/aggregate.py @@ -18,6 +18,7 @@ import logging +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -58,6 +59,15 @@ class AddAggregateHost(command.ShowOne): info = {} info.update(data._info) + + # Special mapping for columns to make the output easier to read: + # 'metadata' --> 'properties' + info.update( + { + 'hosts': format_columns.ListColumn(info.pop('hosts')), + 'properties': format_columns.DictColumn(info.pop('metadata')), + }, + ) return zip(*sorted(six.iteritems(info))) @@ -101,8 +111,20 @@ class CreateAggregate(command.ShowOne): parsed_args.property, )._info) - # TODO(dtroyer): re-format metadata field to properites as - # in the set command + # Special mapping for columns to make the output easier to read: + # 'metadata' --> 'properties' + hosts = None + properties = None + if 'hosts' in info.keys(): + hosts = format_columns.ListColumn(info.pop('hosts')) + if 'metadata' in info.keys(): + properties = format_columns.DictColumn(info.pop('metadata')) + info.update( + { + 'hosts': hosts, + 'properties': properties, + }, + ) return zip(*sorted(six.iteritems(info))) @@ -186,6 +208,10 @@ class ListAggregate(command.Lister): return (column_headers, (utils.get_item_properties( s, columns, + formatters={ + 'Hosts': format_columns.ListColumn, + 'Metadata': format_columns.DictColumn, + }, ) for s in data)) @@ -220,6 +246,15 @@ class RemoveAggregateHost(command.ShowOne): info = {} info.update(data._info) + + # Special mapping for columns to make the output easier to read: + # 'metadata' --> 'properties' + info.update( + { + 'hosts': format_columns.ListColumn(info.pop('hosts')), + 'properties': format_columns.DictColumn(info.pop('metadata')), + }, + ) return zip(*sorted(six.iteritems(info))) @@ -326,7 +361,12 @@ class ShowAggregate(command.ShowOne): # 'metadata' --> 'properties' data._info.update( { - 'properties': utils.format_dict(data._info.pop('metadata')), + 'hosts': format_columns.ListColumn( + data._info.pop('hosts') + ), + 'properties': format_columns.DictColumn( + data._info.pop('metadata') + ), }, ) diff --git a/openstackclient/compute/v2/fixedip.py b/openstackclient/compute/v2/fixedip.py deleted file mode 100644 index 0c0b619..0000000 --- a/openstackclient/compute/v2/fixedip.py +++ /dev/null @@ -1,100 +0,0 @@ -# Copyright 2013 OpenStack Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -"""Fixed IP action implementations""" - -import logging - -from osc_lib.command import command -from osc_lib import utils - -from openstackclient.i18n import _ - - -class AddFixedIP(command.Command): - _description = _("Add fixed IP address to server") - - # TODO(tangchen): Remove this class and ``ip fixed add`` command - # two cycles after Mitaka. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def get_parser(self, prog_name): - parser = super(AddFixedIP, self).get_parser(prog_name) - parser.add_argument( - "network", - metavar="<network>", - help=_("Network to fetch an IP address from (name or ID)"), - ) - parser.add_argument( - "server", - metavar="<server>", - help=_("Server to receive the IP address (name or ID)"), - ) - return parser - - def take_action(self, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "server add fixed ip" instead.')) - - compute_client = self.app.client_manager.compute - - network = utils.find_resource( - compute_client.networks, parsed_args.network) - - server = utils.find_resource( - compute_client.servers, parsed_args.server) - - server.add_fixed_ip(network.id) - - -class RemoveFixedIP(command.Command): - _description = _("Remove fixed IP address from server") - - # TODO(tangchen): Remove this class and ``ip fixed remove`` command - # two cycles after Mitaka. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def get_parser(self, prog_name): - parser = super(RemoveFixedIP, self).get_parser(prog_name) - parser.add_argument( - "ip_address", - metavar="<ip-address>", - help=_("IP address to remove from server (name only)"), - ) - parser.add_argument( - "server", - metavar="<server>", - help=_("Server to remove the IP address from (name or ID)"), - ) - return parser - - def take_action(self, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "server remove fixed ip" instead.')) - - compute_client = self.app.client_manager.compute - - server = utils.find_resource( - compute_client.servers, parsed_args.server) - - server.remove_fixed_ip(parsed_args.ip_address) diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 2cc5f1e..4f1e48a 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -432,7 +432,7 @@ class ShowFlavor(command.ShowOne): projects = [utils.get_field(access, 'tenant_id') for access in flavor_access] # TODO(Huanxuan Ao): This format case can be removed after - # patch https://review.openstack.org/#/c/330223/ merged. + # patch https://review.opendev.org/#/c/330223/ merged. access_projects = utils.format_list(projects) except Exception as e: msg = _("Failed to get access projects list " diff --git a/openstackclient/compute/v2/floatingip.py b/openstackclient/compute/v2/floatingip.py deleted file mode 100644 index 69595be..0000000 --- a/openstackclient/compute/v2/floatingip.py +++ /dev/null @@ -1,97 +0,0 @@ -# Copyright 2013 OpenStack Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -"""Floating IP action implementations""" - -import logging - -from osc_lib.command import command -from osc_lib import utils - -from openstackclient.i18n import _ - - -class AddFloatingIP(command.Command): - _description = _("Add floating IP address to server") - - # TODO(tangchen): Remove this class and ``ip floating add`` command - # two cycles after Mitaka. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def get_parser(self, prog_name): - parser = super(AddFloatingIP, self).get_parser(prog_name) - parser.add_argument( - "ip_address", - metavar="<ip-address>", - help=_("IP address to add to server (name only)"), - ) - parser.add_argument( - "server", - metavar="<server>", - help=_("Server to receive the IP address (name or ID)"), - ) - return parser - - def take_action(self, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "server add floating ip" instead.')) - - compute_client = self.app.client_manager.compute - - server = utils.find_resource( - compute_client.servers, parsed_args.server) - - server.add_floating_ip(parsed_args.ip_address) - - -class RemoveFloatingIP(command.Command): - _description = _("Remove floating IP address from server") - - # TODO(tangchen): Remove this class and ``ip floating remove`` command - # two cycles after Mitaka. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def get_parser(self, prog_name): - parser = super(RemoveFloatingIP, self).get_parser(prog_name) - parser.add_argument( - "ip_address", - metavar="<ip-address>", - help=_("IP address to remove from server (name only)"), - ) - parser.add_argument( - "server", - metavar="<server>", - help=_("Server to remove the IP address from (name or ID)"), - ) - return parser - - def take_action(self, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "server remove floating ip" instead.')) - - compute_client = self.app.client_manager.compute - - server = utils.find_resource( - compute_client.servers, parsed_args.server) - - server.remove_floating_ip(parsed_args.ip_address) diff --git a/openstackclient/compute/v2/hypervisor.py b/openstackclient/compute/v2/hypervisor.py index 406aa91..0d367fe 100644 --- a/openstackclient/compute/v2/hypervisor.py +++ b/openstackclient/compute/v2/hypervisor.py @@ -112,7 +112,7 @@ class ShowHypervisor(command.ShowOne): # example: 17:37:14 up 2:33, 3 users, # load average: 0.33, 0.36, 0.34 m = re.match( - "\s*(.+)\sup\s+(.+),\s+(.+)\susers?,\s+load average:\s(.+)", + r"\s*(.+)\sup\s+(.+),\s+(.+)\susers?,\s+load average:\s(.+)", uptime['uptime']) if m: hypervisor["host_time"] = m.group(1) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 3e1deed..538c9c4 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -79,37 +79,37 @@ def _format_servers_list_power_state(state): def _get_ip_address(addresses, address_type, ip_address_family): - # Old style addresses - if address_type in addresses: - for addy in addresses[address_type]: + # Old style addresses + if address_type in addresses: + for addy in addresses[address_type]: + if int(addy['version']) in ip_address_family: + return addy['addr'] + + # New style addresses + new_address_type = address_type + if address_type == 'public': + new_address_type = 'floating' + if address_type == 'private': + new_address_type = 'fixed' + for network in addresses: + for addy in addresses[network]: + # Case where it is list of strings + if isinstance(addy, six.string_types): + if new_address_type == 'fixed': + return addresses[network][0] + else: + return addresses[network][-1] + # Case where it is a dict + if 'OS-EXT-IPS:type' not in addy: + continue + if addy['OS-EXT-IPS:type'] == new_address_type: if int(addy['version']) in ip_address_family: return addy['addr'] - - # New style addresses - new_address_type = address_type - if address_type == 'public': - new_address_type = 'floating' - if address_type == 'private': - new_address_type = 'fixed' - for network in addresses: - for addy in addresses[network]: - # Case where it is list of strings - if isinstance(addy, six.string_types): - if new_address_type == 'fixed': - return addresses[network][0] - else: - return addresses[network][-1] - # Case where it is a dict - if 'OS-EXT-IPS:type' not in addy: - continue - if addy['OS-EXT-IPS:type'] == new_address_type: - if int(addy['version']) in ip_address_family: - return addy['addr'] - msg = _("ERROR: No %(type)s IP version %(family)s address found") - raise exceptions.CommandError( - msg % {"type": address_type, - "family": ip_address_family} - ) + msg = _("ERROR: No %(type)s IP version %(family)s address found") + raise exceptions.CommandError( + msg % {"type": address_type, + "family": ip_address_family} + ) def _prefix_checked_value(prefix): @@ -424,7 +424,10 @@ class AddServerSecurityGroup(command.Command): class AddServerVolume(command.Command): - _description = _("Add volume to server") + _description = _( + "Add volume to server. " + "Specify ``--os-compute-api-version 2.20`` or higher to add a volume " + "to a server with status ``SHELVED`` or ``SHELVED_OFFLOADED``.") def get_parser(self, prog_name): parser = super(AddServerVolume, self).get_parser(prog_name) @@ -551,6 +554,33 @@ class CreateServer(command.ShowOne): help=_('Select an availability zone for the server'), ) parser.add_argument( + '--host', + metavar='<host>', + help=_('Requested host to create servers. Admin only ' + 'by default. (supported by --os-compute-api-version 2.74 ' + 'or above)'), + ) + parser.add_argument( + '--hypervisor-hostname', + metavar='<hypervisor-hostname>', + help=_('Requested hypervisor hostname to create servers. Admin ' + 'only by default. (supported by --os-compute-api-version ' + '2.74 or above)'), + ) + parser.add_argument( + '--boot-from-volume', + metavar='<volume-size>', + type=int, + help=_('When used in conjunction with the ``--image`` or ' + '``--image-property`` option, this option automatically ' + 'creates a block device mapping with a boot index of 0 ' + 'and tells the compute service to create a volume of the ' + 'given size (in GB) from the specified image and use it ' + 'as the root disk of the server. The root volume will not ' + 'be deleted when the server is deleted. This option is ' + 'mutually exclusive with the ``--volume`` option.') + ) + parser.add_argument( '--block-device-mapping', metavar='<dev-name=mapping>', action=parseractions.KeyValueAction, @@ -563,9 +593,11 @@ class CreateServer(command.ShowOne): '<dev-name>=<id>:<type>:<size(GB)>:<delete-on-terminate>\n' '<dev-name>: block device name, like: vdb, xvdc ' '(required)\n' - '<id>: UUID of the volume or snapshot (required)\n' - '<type>: volume or snapshot; default: volume (optional)\n' - '<size(GB)>: volume size if create from snapshot ' + '<id>: Name or ID of the volume, volume snapshot or image ' + '(required)\n' + '<type>: volume, snapshot or image; default: volume ' + '(optional)\n' + '<size(GB)>: volume size if create from image or snapshot ' '(optional)\n' '<delete-on-terminate>: true or false; default: false ' '(optional)\n' @@ -711,6 +743,10 @@ class CreateServer(command.ShowOne): # Lookup parsed_args.volume volume = None if parsed_args.volume: + # --volume and --boot-from-volume are mutually exclusive. + if parsed_args.boot_from_volume: + raise exceptions.CommandError( + _('--volume is not allowed with --boot-from-volume')) volume = utils.find_resource( volume_client.volumes, parsed_args.volume, @@ -720,8 +756,6 @@ class CreateServer(command.ShowOne): flavor = utils.find_resource(compute_client.flavors, parsed_args.flavor) - boot_args = [parsed_args.server_name, image, flavor] - files = {} for f in parsed_args.file: dst, src = f.split('=', 1) @@ -768,6 +802,20 @@ class CreateServer(command.ShowOne): 'source_type': 'volume', 'destination_type': 'volume' }] + elif parsed_args.boot_from_volume: + # Tell nova to create a root volume from the image provided. + block_device_mapping_v2 = [{ + 'uuid': image.id, + 'boot_index': '0', + 'source_type': 'image', + 'destination_type': 'volume', + 'volume_size': parsed_args.boot_from_volume + }] + # If booting from volume we do not pass an image to compute. + image = None + + boot_args = [parsed_args.server_name, image, flavor] + # Handle block device by device name order, like: vdb -> vdc -> vdd for dev_name in sorted(six.iterkeys(parsed_args.block_device_mapping)): dev_map = parsed_args.block_device_mapping[dev_name] @@ -776,7 +824,7 @@ class CreateServer(command.ShowOne): mapping = {'device_name': dev_name} # 1. decide source and destination type if (len(dev_map) > 1 and - dev_map[1] in ('volume', 'snapshot')): + dev_map[1] in ('volume', 'snapshot', 'image')): mapping['source_type'] = dev_map[1] else: mapping['source_type'] = 'volume' @@ -791,14 +839,29 @@ class CreateServer(command.ShowOne): snapshot_id = utils.find_resource( volume_client.volume_snapshots, dev_map[0]).id mapping['uuid'] = snapshot_id + elif mapping['source_type'] == 'image': + # NOTE(mriedem): In case --image is specified with the same + # image, that becomes the root disk for the server. If the + # block device is specified with a root device name, e.g. + # vda, then the compute API will likely fail complaining + # that there is a conflict. So if using the same image ID, + # which doesn't really make sense but it's allowed, the + # device name would need to be a non-root device, e.g. vdb. + # Otherwise if the block device image is different from the + # one specified by --image, then the compute service will + # create a volume from the image and attach it to the + # server as a non-root volume. + image_id = utils.find_resource( + image_client.images, dev_map[0]).id + mapping['uuid'] = image_id # 3. append size and delete_on_termination if exist if len(dev_map) > 2 and dev_map[2]: mapping['volume_size'] = dev_map[2] if len(dev_map) > 3 and dev_map[3]: mapping['delete_on_termination'] = dev_map[3] else: - msg = _("Volume or snapshot (name or ID) must be specified if " - "--block-device-mapping is specified") + msg = _("Volume, volume snapshot or image (name or ID) must " + "be specified if --block-device-mapping is specified") raise exceptions.CommandError(msg) block_device_mapping_v2.append(mapping) @@ -924,6 +987,21 @@ class CreateServer(command.ShowOne): if parsed_args.description: boot_kwargs['description'] = parsed_args.description + if parsed_args.host: + if compute_client.api_version < api_versions.APIVersion("2.74"): + msg = _("Specifying --host is not supported for " + "--os-compute-api-version less than 2.74") + raise exceptions.CommandError(msg) + boot_kwargs['host'] = parsed_args.host + + if parsed_args.hypervisor_hostname: + if compute_client.api_version < api_versions.APIVersion("2.74"): + msg = _("Specifying --hypervisor-hostname is not supported " + "for --os-compute-api-version less than 2.74") + raise exceptions.CommandError(msg) + boot_kwargs['hypervisor_hostname'] = ( + parsed_args.hypervisor_hostname) + LOG.debug('boot_args: %s', boot_args) LOG.debug('boot_kwargs: %s', boot_kwargs) @@ -1160,6 +1238,21 @@ class ListServer(command.Lister): " The provided time should be an ISO 8061 formatted time" " (e.g., 2016-03-04T06:27:59Z).") ) + lock_group = parser.add_mutually_exclusive_group() + lock_group.add_argument( + '--locked', + action='store_true', + default=False, + help=_('Only display locked servers. ' + 'Requires ``--os-compute-api-version`` 2.73 or greater.'), + ) + lock_group.add_argument( + '--unlocked', + action='store_true', + default=False, + help=_('Only display unlocked servers. ' + 'Requires ``--os-compute-api-version`` 2.73 or greater.'), + ) return parser def take_action(self, parsed_args): @@ -1215,6 +1308,18 @@ class ListServer(command.Lister): 'changes-before': parsed_args.changes_before, 'changes-since': parsed_args.changes_since, } + support_locked = (compute_client.api_version >= + api_versions.APIVersion('2.73')) + if not support_locked and (parsed_args.locked or parsed_args.unlocked): + msg = _('--os-compute-api-version 2.73 or greater is required to ' + 'use the (un)locked filter option.') + raise exceptions.CommandError(msg) + elif support_locked: + # Only from 2.73. + if parsed_args.locked: + search_opts['locked'] = True + if parsed_args.unlocked: + search_opts['locked'] = False LOG.debug('search options: %s', search_opts) if search_opts['changes-before']: @@ -1320,9 +1425,12 @@ class ListServer(command.Lister): # Create a dict that maps image_id to image object. # Needed so that we can display the "Image Name" column. # "Image Name" is not crucial, so we swallow any exceptions. + # The 'image' attribute can be an empty string if the server was + # booted from a volume. if parsed_args.name_lookup_one_by_one or image_id: for i_id in set(filter(lambda x: x is not None, - (s.image.get('id') for s in data))): + (s.image.get('id') for s in data + if s.image))): try: images[i_id] = image_client.images.get(i_id) except Exception: @@ -1412,16 +1520,28 @@ class LockServer(command.Command): nargs='+', help=_('Server(s) to lock (name or ID)'), ) + parser.add_argument( + '--reason', + metavar='<reason>', + default=None, + help=_("Reason for locking the server(s). Requires " + "``--os-compute-api-version`` 2.73 or greater.") + ) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute + support_reason = compute_client.api_version >= api_versions.APIVersion( + '2.73') + if not support_reason and parsed_args.reason: + msg = _('--os-compute-api-version 2.73 or greater is required to ' + 'use the --reason option.') + raise exceptions.CommandError(msg) for server in parsed_args.server: - utils.find_resource( - compute_client.servers, - server, - ).lock() + serv = utils.find_resource(compute_client.servers, server) + (serv.lock(reason=parsed_args.reason) if support_reason + else serv.lock()) # FIXME(dtroyer): Here is what I want, how with argparse/cliff? @@ -1470,13 +1590,13 @@ class MigrateServer(command.Command): 'and ``--live-migration`` are used, ``--live-migration`` ' 'takes priority.'), ) - # TODO(mriedem): Add support for --os-compute-api-version >= 2.56 where - # you can cold migrate to a specified target host. host_group.add_argument( '--host', metavar='<hostname>', - help=_('Live migrate the server to the specified host. Requires ' - '``--os-compute-api-version`` 2.30 or greater.'), + help=_('Migrate the server to the specified host. Requires ' + '``--os-compute-api-version`` 2.30 or greater when used ' + 'with the ``--live-migration`` option, otherwise requires ' + '``--os-compute-api-version`` 2.56 or greater.'), ) migration_group = parser.add_mutually_exclusive_group() migration_group.add_argument( @@ -1566,16 +1686,22 @@ class MigrateServer(command.Command): kwargs['disk_over_commit'] = parsed_args.disk_overcommit server.live_migrate(**kwargs) else: - if (parsed_args.block_migration or parsed_args.disk_overcommit or - parsed_args.host): - # TODO(mriedem): Allow --host for cold migration if - # --os-compute-api-version >= 2.56. + if parsed_args.block_migration or parsed_args.disk_overcommit: raise exceptions.CommandError( "--live-migration must be specified if " - "--block-migration, --disk-overcommit or --host is " + "--block-migration or --disk-overcommit is " "specified") + if parsed_args.host: + if (compute_client.api_version < + api_versions.APIVersion('2.56')): + msg = _( + '--os-compute-api-version 2.56 or greater is ' + 'required to use --host without --live-migration.' + ) + raise exceptions.CommandError(msg) - server.migrate() + kwargs = {'host': parsed_args.host} if parsed_args.host else {} + server.migrate(**kwargs) if parsed_args.wait: if utils.wait_for_status( @@ -1949,7 +2075,11 @@ class RemoveServerSecurityGroup(command.Command): class RemoveServerVolume(command.Command): - _description = _("Remove volume from server") + _description = _( + "Remove volume from server. " + "Specify ``--os-compute-api-version 2.20`` or higher to remove a " + "volume from a server with status ``SHELVED`` or " + "``SHELVED_OFFLOADED``.") def get_parser(self, prog_name): parser = super(RemoveServerVolume, self).get_parser(prog_name) @@ -2096,11 +2226,66 @@ the new server and restart the old one.""") self.app.stdout.write(_('Error resizing server\n')) raise SystemExit elif parsed_args.confirm: + self.log.warning(_( + "The --confirm option has been deprecated. Please use the " + "'openstack server resize confirm' command instead.")) compute_client.servers.confirm_resize(server) elif parsed_args.revert: + self.log.warning(_( + "The --revert option has been deprecated. Please use the " + "'openstack server resize revert' command instead.")) compute_client.servers.revert_resize(server) +class ResizeConfirm(command.Command): + _description = _("""Confirm server resize. + +Confirm (verify) success of resize operation and release the old server.""") + + def get_parser(self, prog_name): + parser = super(ResizeConfirm, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='<server>', + help=_('Server (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + + compute_client = self.app.client_manager.compute + server = utils.find_resource( + compute_client.servers, + parsed_args.server, + ) + server.confirm_resize() + + +class ResizeRevert(command.Command): + _description = _("""Revert server resize. + +Revert the resize operation. Release the new server and restart the old +one.""") + + def get_parser(self, prog_name): + parser = super(ResizeRevert, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='<server>', + help=_('Server (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + + compute_client = self.app.client_manager.compute + server = utils.find_resource( + compute_client.servers, + parsed_args.server, + ) + server.revert_resize() + + class RestoreServer(command.Command): _description = _("Restore server(s)") @@ -2626,12 +2811,32 @@ class UnshelveServer(command.Command): nargs='+', help=_('Server(s) to unshelve (name or ID)'), ) + parser.add_argument( + '--availability-zone', + default=None, + help=_('Name of the availability zone in which to unshelve a ' + 'SHELVED_OFFLOADED server (supported by ' + '--os-compute-api-version 2.77 or above)'), + ) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute + support_az = compute_client.api_version >= api_versions.APIVersion( + '2.77') + if not support_az and parsed_args.availability_zone: + msg = _("--os-compute-api-version 2.77 or greater is required " + "to support the '--availability-zone' option.") + raise exceptions.CommandError(msg) + for server in parsed_args.server: - utils.find_resource( - compute_client.servers, - server, - ).unshelve() + if support_az: + utils.find_resource( + compute_client.servers, + server + ).unshelve(availability_zone=parsed_args.availability_zone) + else: + utils.find_resource( + compute_client.servers, + server, + ).unshelve() diff --git a/openstackclient/compute/v2/server_event.py b/openstackclient/compute/v2/server_event.py index c7d2e2e..6d33d02 100644 --- a/openstackclient/compute/v2/server_event.py +++ b/openstackclient/compute/v2/server_event.py @@ -28,7 +28,10 @@ LOG = logging.getLogger(__name__) class ListServerEvent(command.Lister): - _description = _("List recent events of a server") + _description = _( + "List recent events of a server. " + "Specify ``--os-compute-api-version 2.21`` " + "or higher to show events for a deleted server.") def get_parser(self, prog_name): parser = super(ListServerEvent, self).get_parser(prog_name) @@ -92,8 +95,11 @@ class ListServerEvent(command.Lister): class ShowServerEvent(command.ShowOne): _description = _( - "Show server event details. Specify ``--os-compute-api-version 2.51`` " - "or higher to show events for non-admin users.") + "Show server event details. " + "Specify ``--os-compute-api-version 2.21`` " + "or higher to show event details for a deleted server. " + "Specify ``--os-compute-api-version 2.51`` " + "or higher to show event details for non-admin users.") def get_parser(self, prog_name): parser = super(ShowServerEvent, self).get_parser(prog_name) diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 18e6d9d..625c0fa 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -37,7 +37,10 @@ class DeleteService(command.Command): "service", metavar="<service>", nargs='+', - help=_("Compute service(s) to delete (ID only)") + help=_("Compute service(s) to delete (ID only). If using " + "``--os-compute-api-version`` 2.53 or greater, the ID is " + "a UUID which can be retrieved by listing compute services " + "using the same 2.53+ microversion.") ) return parser @@ -60,7 +63,11 @@ class DeleteService(command.Command): class ListService(command.Lister): - _description = _("List compute services") + _description = _("List compute services. Using " + "``--os-compute-api-version`` 2.53 or greater will " + "return the ID as a UUID value which can be used to " + "uniquely identify the service in a multi-cell " + "deployment.") def get_parser(self, prog_name): parser = super(ListService, self).get_parser(prog_name) @@ -72,7 +79,8 @@ class ListService(command.Lister): parser.add_argument( "--service", metavar="<service>", - help=_("List only specified service (name only)") + help=_("List only specified service binaries (name only). For " + "example, ``nova-compute``, ``nova-conductor``, etc.") ) parser.add_argument( "--long", @@ -126,7 +134,8 @@ class SetService(command.Command): parser.add_argument( "service", metavar="<service>", - help=_("Name of service (Binary name)") + help=_("Name of service (Binary name), for example " + "``nova-compute``") ) enabled_group = parser.add_mutually_exclusive_group() enabled_group.add_argument( @@ -150,15 +159,44 @@ class SetService(command.Command): up_down_group.add_argument( '--up', action='store_true', - help=_('Force up service'), + help=_('Force up service. Requires ``--os-compute-api-version`` ' + '2.11 or greater.'), ) up_down_group.add_argument( '--down', action='store_true', - help=_('Force down service'), + help=_('Force down service. Requires ``--os-compute-api-version`` ' + '2.11 or greater.'), ) return parser + @staticmethod + def _find_service_by_host_and_binary(cs, host, binary): + """Utility method to find a compute service by host and binary + + :param host: the name of the compute service host + :param binary: the compute service binary, e.g. nova-compute + :returns: novaclient.v2.services.Service dict-like object + :raises: CommandError if no or multiple results were found + """ + services = cs.list(host=host, binary=binary) + # Did we find anything? + if not len(services): + msg = _('Compute service for host "%(host)s" and binary ' + '"%(binary)s" not found.') % { + 'host': host, 'binary': binary} + raise exceptions.CommandError(msg) + # Did we find more than one result? This should not happen but let's + # be safe. + if len(services) > 1: + # TODO(mriedem): If we have an --id option for 2.53+ then we can + # say to use that option to uniquely identify the service. + msg = _('Multiple compute services found for host "%(host)s" and ' + 'binary "%(binary)s". Unable to proceed.') % { + 'host': host, 'binary': binary} + raise exceptions.CommandError(msg) + return services[0] + def take_action(self, parsed_args): compute_client = self.app.client_manager.compute cs = compute_client.services @@ -169,6 +207,20 @@ class SetService(command.Command): "--disable specified.") raise exceptions.CommandError(msg) + # Starting with microversion 2.53, there is a single + # PUT /os-services/{service_id} API for updating nova-compute + # services. If 2.53+ is used we need to find the nova-compute + # service using the --host and --service (binary) values. + requires_service_id = ( + compute_client.api_version >= api_versions.APIVersion('2.53')) + service_id = None + if requires_service_id: + # TODO(mriedem): Add an --id option so users can pass the service + # id (as a uuid) directly rather than make us look it up using + # host/binary. + service_id = SetService._find_service_by_host_and_binary( + cs, parsed_args.host, parsed_args.service).id + result = 0 enabled = None try: @@ -179,14 +231,21 @@ class SetService(command.Command): if enabled is not None: if enabled: - cs.enable(parsed_args.host, parsed_args.service) + args = (service_id,) if requires_service_id else ( + parsed_args.host, parsed_args.service) + cs.enable(*args) else: if parsed_args.disable_reason: - cs.disable_log_reason(parsed_args.host, - parsed_args.service, - parsed_args.disable_reason) + args = (service_id, parsed_args.disable_reason) if \ + requires_service_id else ( + parsed_args.host, + parsed_args.service, + parsed_args.disable_reason) + cs.disable_log_reason(*args) else: - cs.disable(parsed_args.host, parsed_args.service) + args = (service_id,) if requires_service_id else ( + parsed_args.host, parsed_args.service) + cs.disable(*args) except Exception: status = "enabled" if enabled else "disabled" LOG.error("Failed to set service status to %s", status) @@ -204,8 +263,9 @@ class SetService(command.Command): 'required') raise exceptions.CommandError(msg) try: - cs.force_down(parsed_args.host, parsed_args.service, - force_down=force_down) + args = (service_id, force_down) if requires_service_id else ( + parsed_args.host, parsed_args.service, force_down) + cs.force_down(*args) except Exception: state = "down" if force_down else "up" LOG.error("Failed to set service state to %s", state) diff --git a/openstackclient/identity/v2_0/catalog.py b/openstackclient/identity/v2_0/catalog.py index 993bdd5..5d1e306 100644 --- a/openstackclient/identity/v2_0/catalog.py +++ b/openstackclient/identity/v2_0/catalog.py @@ -15,6 +15,7 @@ import logging +from cliff import columns as cliff_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -26,20 +27,21 @@ from openstackclient.i18n import _ LOG = logging.getLogger(__name__) -def _format_endpoints(eps=None): - if not eps: - return "" - ret = '' - for index, ep in enumerate(eps): - region = eps[index].get('region') - if region is None: - region = '<none>' - ret += region + '\n' - for endpoint_type in ['publicURL', 'internalURL', 'adminURL']: - url = eps[index].get(endpoint_type) - if url: - ret += " %s: %s\n" % (endpoint_type, url) - return ret +class EndpointsColumn(cliff_columns.FormattableColumn): + def human_readable(self): + if not self._value: + return "" + ret = '' + for ep in self._value: + region = ep.get('region') + if region is None: + region = '<none>' + ret += region + '\n' + for endpoint_type in ['publicURL', 'internalURL', 'adminURL']: + url = ep.get(endpoint_type) + if url: + ret += " %s: %s\n" % (endpoint_type, url) + return ret class ListCatalog(command.Lister): @@ -60,7 +62,7 @@ class ListCatalog(command.Lister): (utils.get_dict_properties( s, columns, formatters={ - 'Endpoints': _format_endpoints, + 'Endpoints': EndpointsColumn, }, ) for s in data)) @@ -91,7 +93,7 @@ class ShowCatalog(command.ShowOne): if (service.get('name') == parsed_args.service or service.get('type') == parsed_args.service): data = service - data['endpoints'] = _format_endpoints(data['endpoints']) + data['endpoints'] = EndpointsColumn(data['endpoints']) if 'endpoints_links' in data: data.pop('endpoints_links') break diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py index 9bb5fc4..e001886 100644 --- a/openstackclient/identity/v2_0/project.py +++ b/openstackclient/identity/v2_0/project.py @@ -18,6 +18,7 @@ import logging from keystoneauth1 import exceptions as ks_exc +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -297,7 +298,7 @@ class ShowProject(command.ShowOne): if v is not None: properties[k] = v - info['properties'] = utils.format_dict(properties) + info['properties'] = format_columns.DictColumn(properties) return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py index e254e05..e9fe50f 100644 --- a/openstackclient/identity/v2_0/role.py +++ b/openstackclient/identity/v2_0/role.py @@ -148,155 +148,11 @@ class DeleteRole(command.Command): class ListRole(command.Lister): _description = _("List roles") - def get_parser(self, prog_name): - parser = super(ListRole, self).get_parser(prog_name) - parser.add_argument( - '--project', - metavar='<project>', - help=_('Filter roles by <project> (name or ID)'), - ) - parser.add_argument( - '--user', - metavar='<user>', - help=_('Filter roles by <user> (name or ID)'), - ) - return parser - def take_action(self, parsed_args): - - def _deprecated(): - # NOTE(henry-nash): Deprecated as of Newton, so we should remove - # this in the 'P' release. - self.log.warning(_('Listing assignments using role list is ' - 'deprecated as of the Newton release. Use role ' - 'assignment list --user <user-name> --project ' - '<project-name> --names instead.')) - identity_client = self.app.client_manager.identity - auth_ref = self.app.client_manager.auth_ref - - # No user or project specified, list all roles in the system - if not parsed_args.user and not parsed_args.project: - columns = ('ID', 'Name') - data = identity_client.roles.list() - elif parsed_args.user and parsed_args.project: - user = utils.find_resource( - identity_client.users, - parsed_args.user, - ) - project = utils.find_resource( - identity_client.projects, - parsed_args.project, - ) - _deprecated() - data = identity_client.roles.roles_for_user(user.id, project.id) - - elif parsed_args.user: - user = utils.find_resource( - identity_client.users, - parsed_args.user, - ) - if self.app.client_manager.auth_ref: - project = utils.find_resource( - identity_client.projects, - auth_ref.project_id - ) - else: - msg = _("Project must be specified") - raise exceptions.CommandError(msg) - _deprecated() - data = identity_client.roles.roles_for_user(user.id, project.id) - elif parsed_args.project: - project = utils.find_resource( - identity_client.projects, - parsed_args.project, - ) - if self.app.client_manager.auth_ref: - user = utils.find_resource( - identity_client.users, - auth_ref.user_id - ) - else: - msg = _("User must be specified") - raise exceptions.CommandError(msg) - _deprecated() - data = identity_client.roles.roles_for_user(user.id, project.id) - - if parsed_args.user or parsed_args.project: - columns = ('ID', 'Name', 'Project', 'User') - for user_role in data: - user_role.user = user.name - user_role.project = project.name - - return (columns, - (utils.get_item_properties( - s, columns, - formatters={}, - ) for s in data)) - - -class ListUserRole(command.Lister): - _description = _("List user-role assignments") - - def get_parser(self, prog_name): - parser = super(ListUserRole, self).get_parser(prog_name) - parser.add_argument( - 'user', - metavar='<user>', - nargs='?', - help=_('User to list (name or ID)'), - ) - parser.add_argument( - '--project', - metavar='<project>', - help=_('Filter users by <project> (name or ID)'), - ) - return parser - - def take_action(self, parsed_args): - identity_client = self.app.client_manager.identity - auth_ref = self.app.client_manager.auth_ref - - # Project and user are required, if not included in command args - # default to the values used for authentication. For token-flow - # authentication they must be included on the command line. - if (not parsed_args.project and - self.app.client_manager.auth_ref.project_id): - parsed_args.project = auth_ref.project_id - if not parsed_args.project: - msg = _("Project must be specified") - raise exceptions.CommandError(msg) - - if (not parsed_args.user and - self.app.client_manager.auth_ref.user_id): - parsed_args.user = auth_ref.user_id - if not parsed_args.user: - msg = _("User must be specified") - raise exceptions.CommandError(msg) - - self.log.warning(_('Listing assignments using user role list is ' - 'deprecated as of the Newton release. Use role ' - 'assignment list --user <user-name> --project ' - '<project-name> --names instead.')) - project = utils.find_resource( - identity_client.tenants, - parsed_args.project, - ) - user = utils.find_resource(identity_client.users, parsed_args.user) - - data = identity_client.roles.roles_for_user(user.id, project.id) - - columns = ( - 'ID', - 'Name', - 'Project', - 'User', - ) - # Add the names to the output even though they will be constant - for role in data: - role.user = user.name - role.project = project.name + columns = ('ID', 'Name') + data = identity_client.roles.list() return (columns, (utils.get_item_properties( diff --git a/openstackclient/identity/v2_0/service.py b/openstackclient/identity/v2_0/service.py index 80f2d72..653de8e 100644 --- a/openstackclient/identity/v2_0/service.py +++ b/openstackclient/identity/v2_0/service.py @@ -15,7 +15,6 @@ """Service action implementations""" -import argparse import logging from osc_lib.command import command @@ -36,17 +35,11 @@ class CreateService(command.ShowOne): def get_parser(self, prog_name): parser = super(CreateService, self).get_parser(prog_name) parser.add_argument( - 'type_or_name', + 'type', metavar='<type>', help=_('New service type (compute, image, identity, volume, etc)'), ) - type_or_name_group = parser.add_mutually_exclusive_group() - type_or_name_group.add_argument( - '--type', - metavar='<type>', - help=argparse.SUPPRESS, - ) - type_or_name_group.add_argument( + parser.add_argument( '--name', metavar='<name>', help=_('New service name'), @@ -61,29 +54,14 @@ class CreateService(command.ShowOne): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - type_or_name = parsed_args.type_or_name name = parsed_args.name type = parsed_args.type - # If only a single positional is present, it's a <type>. - # This is not currently legal so it is considered a new case. - if not type and not name: - type = type_or_name - # If --type option is present then positional is handled as <name>; - # display deprecation message. - elif type: - name = type_or_name - LOG.warning(_('The argument --type is deprecated, use service' - ' create --name <service-name> type instead.')) - # If --name option is present the positional is handled as <type>. - # Making --type optional is new, but back-compatible - elif name: - type = type_or_name - service = identity_client.services.create( name, type, - parsed_args.description) + parsed_args.description, + ) info = {} info.update(service._info) diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index 2a3dde6..0675877 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -15,8 +15,10 @@ """Identity v2.0 User action implementations""" +import functools import logging +from cliff import columns as cliff_columns from keystoneauth1 import exceptions as ks_exc from osc_lib.command import command from osc_lib import exceptions @@ -29,6 +31,31 @@ from openstackclient.i18n import _ LOG = logging.getLogger(__name__) +class ProjectColumn(cliff_columns.FormattableColumn): + """Formattable column for project column. + + Unlike the parent FormattableColumn class, the initializer of the + class takes project_cache as the second argument. + osc_lib.utils.get_item_properties instantiate cliff FormattableColumn + object with a single parameter "column value", so you need to pass + a partially initialized class like + ``functools.partial(ProjectColumn, project_cache)``. + """ + + def __init__(self, value, project_cache=None): + super(ProjectColumn, self).__init__(value) + self.project_cache = project_cache or {} + + def human_readable(self): + project = self._value + if not project: + return "" + if project in self.project_cache.keys(): + return self.project_cache[project].name + else: + return project + + class CreateUser(command.ShowOne): _description = _("Create new user") @@ -187,15 +214,7 @@ class ListUser(command.Lister): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - - def _format_project(project): - if not project: - return "" - if project in project_cache.keys(): - return project_cache[project].name - else: - return project - + formatters = {} project = None if parsed_args.project: project = utils.find_resource( @@ -227,6 +246,8 @@ class ListUser(command.Lister): except Exception: # Just forget it if there's any trouble pass + formatters['tenantId'] = functools.partial( + ProjectColumn, project_cache=project_cache) else: columns = column_headers = ('ID', 'Name') data = identity_client.users.list(tenant_id=project) @@ -251,7 +272,7 @@ class ListUser(command.Lister): (utils.get_item_properties( s, columns, mixed_case_fields=('tenantId',), - formatters={'tenantId': _format_project}, + formatters=formatters, ) for s in data)) diff --git a/openstackclient/identity/v3/catalog.py b/openstackclient/identity/v3/catalog.py index 28f4fad..59430c4 100644 --- a/openstackclient/identity/v3/catalog.py +++ b/openstackclient/identity/v3/catalog.py @@ -15,6 +15,7 @@ import logging +from cliff import columns as cliff_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -26,15 +27,16 @@ from openstackclient.i18n import _ LOG = logging.getLogger(__name__) -def _format_endpoints(eps=None): - if not eps: - return "" - ret = '' - for ep in eps: - region = ep.get('region_id') or ep.get('region') or '<none>' - ret += region + '\n' - ret += " %s: %s\n" % (ep['interface'], ep['url']) - return ret +class EndpointsColumn(cliff_columns.FormattableColumn): + def human_readable(self): + if not self._value: + return "" + ret = '' + for ep in self._value: + region = ep.get('region_id') or ep.get('region') or '<none>' + ret += region + '\n' + ret += " %s: %s\n" % (ep['interface'], ep['url']) + return ret class ListCatalog(command.Lister): @@ -55,7 +57,7 @@ class ListCatalog(command.Lister): (utils.get_dict_properties( s, columns, formatters={ - 'Endpoints': _format_endpoints, + 'Endpoints': EndpointsColumn, }, ) for s in data)) @@ -86,7 +88,7 @@ class ShowCatalog(command.ShowOne): if (service.get('name') == parsed_args.service or service.get('type') == parsed_args.service): data = dict(service) - data['endpoints'] = _format_endpoints(data['endpoints']) + data['endpoints'] = EndpointsColumn(data['endpoints']) if 'links' in data: data.pop('links') break diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py index d8951d3..b331518 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -15,6 +15,7 @@ import logging +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -103,7 +104,7 @@ class CreateIdentityProvider(command.ShowOne): enabled=parsed_args.enabled) idp._info.pop('links', None) - remote_ids = utils.format_list(idp._info.pop('remote_ids', [])) + remote_ids = format_columns.ListColumn(idp._info.pop('remote_ids', [])) idp._info['remote_ids'] = remote_ids return zip(*sorted(six.iteritems(idp._info))) @@ -245,6 +246,6 @@ class ShowIdentityProvider(command.ShowOne): id=parsed_args.identity_provider) idp._info.pop('links', None) - remote_ids = utils.format_list(idp._info.pop('remote_ids', [])) + remote_ids = format_columns.ListColumn(idp._info.pop('remote_ids', [])) idp._info['remote_ids'] = remote_ids return zip(*sorted(six.iteritems(idp._info))) diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 58a76f8..0eeddd3 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -266,131 +266,28 @@ class ListRole(command.Lister): def get_parser(self, prog_name): parser = super(ListRole, self).get_parser(prog_name) - - # TODO(henry-nash): The use of the List Role command to list - # assignments (as well as roles) has been deprecated. In order - # to support domain specific roles, we are overriding the domain - # option to allow specification of the domain for the role. This does - # not conflict with any existing commands, since for the deprecated - # assignments listing you were never allowed to only specify a domain - # (you also needed to specify a user). - # - # Once we have removed the deprecated options entirely, we must - # replace the call to _add_identity_and_resource_options_to_parser() - # below with just adding the domain option into the parser. - _add_identity_and_resource_options_to_parser(parser) + parser.add_argument( + '--domain', + metavar='<domain>', + help=_('Include <domain> (name or ID)'), + ) return parser def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - if parsed_args.user: - user = common.find_user( - identity_client, - parsed_args.user, - parsed_args.user_domain, - ) - elif parsed_args.group: - group = common.find_group( - identity_client, - parsed_args.group, - parsed_args.group_domain, - ) - if parsed_args.domain: domain = common.find_domain( identity_client, parsed_args.domain, ) - elif parsed_args.project: - project = common.find_project( - identity_client, - parsed_args.project, - parsed_args.project_domain, - ) - - # no user or group specified, list all roles in the system - if not parsed_args.user and not parsed_args.group: - if not parsed_args.domain: - columns = ('ID', 'Name') - data = identity_client.roles.list() - else: - columns = ('ID', 'Name', 'Domain') - data = identity_client.roles.list(domain_id=domain.id) - for role in data: - role.domain = domain.name - elif parsed_args.user and parsed_args.domain: - columns = ('ID', 'Name', 'Domain', 'User') - data = identity_client.roles.list( - user=user, - domain=domain, - os_inherit_extension_inherited=parsed_args.inherited - ) - for user_role in data: - user_role.user = user.name - user_role.domain = domain.name - self.log.warning(_('Listing assignments using role list is ' - 'deprecated. Use role assignment list --user ' - '<user-name> --domain <domain-name> --names ' - 'instead.')) - elif parsed_args.user and parsed_args.project: - columns = ('ID', 'Name', 'Project', 'User') - data = identity_client.roles.list( - user=user, - project=project, - os_inherit_extension_inherited=parsed_args.inherited - ) - for user_role in data: - user_role.user = user.name - user_role.project = project.name - self.log.warning(_('Listing assignments using role list is ' - 'deprecated. Use role assignment list --user ' - '<user-name> --project <project-name> --names ' - 'instead.')) - elif parsed_args.user: - columns = ('ID', 'Name') - data = identity_client.roles.list( - user=user, - domain='default', - os_inherit_extension_inherited=parsed_args.inherited - ) - self.log.warning(_('Listing assignments using role list is ' - 'deprecated. Use role assignment list --user ' - '<user-name> --domain default --names ' - 'instead.')) - elif parsed_args.group and parsed_args.domain: - columns = ('ID', 'Name', 'Domain', 'Group') - data = identity_client.roles.list( - group=group, - domain=domain, - os_inherit_extension_inherited=parsed_args.inherited - ) - for group_role in data: - group_role.group = group.name - group_role.domain = domain.name - self.log.warning(_('Listing assignments using role list is ' - 'deprecated. Use role assignment list --group ' - '<group-name> --domain <domain-name> --names ' - 'instead.')) - elif parsed_args.group and parsed_args.project: - columns = ('ID', 'Name', 'Project', 'Group') - data = identity_client.roles.list( - group=group, - project=project, - os_inherit_extension_inherited=parsed_args.inherited - ) - for group_role in data: - group_role.group = group.name - group_role.project = project.name - self.log.warning(_('Listing assignments using role list is ' - 'deprecated. Use role assignment list --group ' - '<group-name> --project <project-name> --names ' - 'instead.')) + columns = ('ID', 'Name', 'Domain') + data = identity_client.roles.list(domain_id=domain.id) + for role in data: + role.domain = domain.name else: - msg = _("Error: If a user or group is specified, " - "either --domain or --project must also be " - "specified to list role grants.") - raise exceptions.CommandError(msg) + columns = ('ID', 'Name') + data = identity_client.roles.list() return (columns, (utils.get_item_properties( diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 64c4049..c2dab3e 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -21,8 +21,10 @@ import logging import os import sys +from cliff import columns as cliff_columns from glanceclient.common import utils as gc_utils from osc_lib.api import utils as api_utils +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import utils @@ -46,19 +48,18 @@ DISK_CHOICES = ["ami", "ari", "aki", "vhd", "vmdk", "raw", "qcow2", "vhdx", LOG = logging.getLogger(__name__) -def _format_visibility(data): - """Return a formatted visibility string +class VisibilityColumn(cliff_columns.FormattableColumn): + def human_readable(self): + """Return a formatted visibility string - :param data: - The server's visibility (is_public) status value: True, False - :rtype: - A string formatted to public/private - """ + :rtype: + A string formatted to public/private + """ - if data: - return 'public' - else: - return 'private' + if self._value: + return 'public' + else: + return 'private' class CreateImage(command.ShowOne): @@ -182,29 +183,16 @@ class CreateImage(command.ShowOne): help=_("Set a property on this image " "(repeat option to set multiple properties)"), ) - # NOTE(dtroyer): --owner is deprecated in Jan 2016 in an early - # 2.x release. Do not remove before Jan 2017 - # and a 3.x release. - project_group = parser.add_mutually_exclusive_group() - project_group.add_argument( + parser.add_argument( "--project", metavar="<project>", help=_("Set an alternate project on this image (name or ID)"), ) - project_group.add_argument( - "--owner", - metavar="<project>", - help=argparse.SUPPRESS, - ) return parser def take_action(self, parsed_args): image_client = self.app.client_manager.image - if getattr(parsed_args, 'owner', None) is not None: - LOG.warning(_('The --owner option is deprecated, ' - 'please use --project instead.')) - # Build an attribute dict from the parsed args, only include # attributes that were actually set on the command line kwargs = {} @@ -281,7 +269,8 @@ class CreateImage(command.ShowOne): kwargs['data'].close() info.update(image._info) - info['properties'] = utils.format_dict(info.get('properties', {})) + info['properties'] = format_columns.DictColumn( + info.get('properties', {})) return zip(*sorted(six.iteritems(info))) @@ -442,8 +431,8 @@ class ListImage(command.Lister): s, columns, formatters={ - 'is_public': _format_visibility, - 'properties': utils.format_dict, + 'is_public': VisibilityColumn, + 'properties': format_columns.DictColumn, }, ) for s in data) ) @@ -599,29 +588,16 @@ class SetImage(command.Command): metavar="<checksum>", help=_("Image hash used for verification"), ) - # NOTE(dtroyer): --owner is deprecated in Jan 2016 in an early - # 2.x release. Do not remove before Jan 2017 - # and a 3.x release. - project_group = parser.add_mutually_exclusive_group() - project_group.add_argument( + parser.add_argument( "--project", metavar="<project>", help=_("Set an alternate project on this image (name or ID)"), ) - project_group.add_argument( - "--owner", - metavar="<project>", - help=argparse.SUPPRESS, - ) return parser def take_action(self, parsed_args): image_client = self.app.client_manager.image - if getattr(parsed_args, 'owner', None) is not None: - LOG.warning(_('The --owner option is deprecated, ' - 'please use --project instead.')) - kwargs = {} copy_attrs = ('name', 'owner', 'min_disk', 'min_ram', 'properties', 'container_format', 'disk_format', 'size', 'store', @@ -740,5 +716,6 @@ class ShowImage(command.ShowOne): if parsed_args.human_readable: if 'size' in info: info['size'] = utils.format_size(info['size']) - info['properties'] = utils.format_dict(info.get('properties', {})) + info['properties'] = format_columns.DictColumn( + info.get('properties', {})) return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index bdec99d..2b10c3a 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -22,6 +22,7 @@ import logging from glanceclient.common import utils as gc_utils from openstack.image import image_signer from osc_lib.api import utils as api_utils +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -65,11 +66,11 @@ def _format_image(image): properties[key] = image.get(key) # format the tags if they are there - info['tags'] = utils.format_list(image.get('tags')) + info['tags'] = format_columns.ListColumn(image.get('tags')) # add properties back into the dictionary as a top-level key if properties: - info['properties'] = utils.format_dict(properties) + info['properties'] = format_columns.DictColumn(properties) return info @@ -250,20 +251,11 @@ class CreateImage(command.ShowOne): help=_("Set a tag on this image " "(repeat option to set multiple tags)"), ) - # NOTE(dtroyer): --owner is deprecated in Jan 2016 in an early - # 2.x release. Do not remove before Jan 2017 - # and a 3.x release. - project_group = parser.add_mutually_exclusive_group() - project_group.add_argument( + parser.add_argument( "--project", metavar="<project>", help=_("Set an alternate project on this image (name or ID)"), ) - project_group.add_argument( - "--owner", - metavar="<project>", - help=argparse.SUPPRESS, - ) common.add_project_domain_option_to_parser(parser) for deadopt in self.deadopts: parser.add_argument( @@ -321,16 +313,10 @@ class CreateImage(command.ShowOne): kwargs['visibility'] = 'community' if parsed_args.shared: kwargs['visibility'] = 'shared' - # Handle deprecated --owner option - project_arg = parsed_args.project - if parsed_args.owner: - project_arg = parsed_args.owner - LOG.warning(_('The --owner option is deprecated, ' - 'please use --project instead.')) - if project_arg: + if parsed_args.project: kwargs['owner'] = common.find_project( identity_client, - project_arg, + parsed_args.project, parsed_args.project_domain, ).id @@ -347,13 +333,6 @@ class CreateImage(command.ShowOne): LOG.warning(_("Failed to get an image file.")) return {}, {} - if parsed_args.owner: - kwargs['owner'] = common.find_project( - identity_client, - parsed_args.owner, - parsed_args.project_domain, - ).id - # sign an image using a given local private key file if parsed_args.sign_key_path or parsed_args.sign_cert_id: if not parsed_args.file: @@ -678,7 +657,7 @@ class ListImage(command.Lister): s, columns, formatters={ - 'tags': utils.format_list, + 'tags': format_columns.ListColumn, }, ) for s in data) ) @@ -933,20 +912,11 @@ class SetImage(command.Command): action="store_true", help=_("Activate the image"), ) - # NOTE(dtroyer): --owner is deprecated in Jan 2016 in an early - # 2.x release. Do not remove before Jan 2017 - # and a 3.x release. - project_group = parser.add_mutually_exclusive_group() - project_group.add_argument( + parser.add_argument( "--project", metavar="<project>", help=_("Set an alternate project on this image (name or ID)"), ) - project_group.add_argument( - "--owner", - metavar="<project>", - help=argparse.SUPPRESS, - ) common.add_project_domain_option_to_parser(parser) for deadopt in self.deadopts: parser.add_argument( @@ -1020,17 +990,11 @@ class SetImage(command.Command): kwargs['visibility'] = 'community' if parsed_args.shared: kwargs['visibility'] = 'shared' - # Handle deprecated --owner option - project_arg = parsed_args.project - if parsed_args.owner: - project_arg = parsed_args.owner - LOG.warning(_('The --owner option is deprecated, ' - 'please use --project instead.')) project_id = None - if project_arg: + if parsed_args.project: project_id = common.find_project( identity_client, - project_arg, + parsed_args.project, parsed_args.project_domain, ).id kwargs['owner'] = project_id diff --git a/openstackclient/network/v2/address_scope.py b/openstackclient/network/v2/address_scope.py index 71c1a9a..7efbb63 100644 --- a/openstackclient/network/v2/address_scope.py +++ b/openstackclient/network/v2/address_scope.py @@ -15,6 +15,7 @@ import logging +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -27,6 +28,11 @@ from openstackclient.network import sdk_utils LOG = logging.getLogger(__name__) +_formatters = { + 'location': format_columns.DictColumn, +} + + def _get_columns(item): column_map = { 'is_shared': 'shared', @@ -100,7 +106,7 @@ class CreateAddressScope(command.ShowOne): attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.create_address_scope(**attrs) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters={}) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) @@ -289,6 +295,6 @@ class ShowAddressScope(command.ShowOne): parsed_args.address_scope, ignore_missing=False) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters={}) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index 8ac8e10..e0aa043 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -13,6 +13,7 @@ """IP Floating action implementations""" +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import utils @@ -24,6 +25,7 @@ from openstackclient.network.v2 import _tag _formatters = { + 'location': format_columns.DictColumn, 'port_details': utils.format_dict, } diff --git a/openstackclient/network/v2/floating_ip_port_forwarding.py b/openstackclient/network/v2/floating_ip_port_forwarding.py new file mode 100644 index 0000000..f94bcc0 --- /dev/null +++ b/openstackclient/network/v2/floating_ip_port_forwarding.py @@ -0,0 +1,371 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +"""Floating IP Port Forwarding action implementations""" +import logging + +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.i18n import _ +from openstackclient.network import sdk_utils + + +LOG = logging.getLogger(__name__) + + +def _get_columns(item): + column_map = { + 'tenant_id': 'project_id', + } + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + + +class CreateFloatingIPPortForwarding(command.ShowOne): + _description = _("Create floating IP port forwarding") + + def get_parser(self, prog_name): + parser = super(CreateFloatingIPPortForwarding, + self).get_parser(prog_name) + parser.add_argument( + '--internal-ip-address', + required=True, + metavar='<internal-ip-address>', + help=_("The fixed IPv4 address of the network " + "port associated to the floating IP port forwarding") + ) + parser.add_argument( + '--port', + metavar='<port>', + required=True, + help=_("The name or ID of the network port associated " + "to the floating IP port forwarding") + ) + parser.add_argument( + '--internal-protocol-port', + type=int, + metavar='<port-number>', + required=True, + help=_("The protocol port number " + "of the network port fixed IPv4 address " + "associated to the floating IP port forwarding") + ) + parser.add_argument( + '--external-protocol-port', + type=int, + metavar='<port-number>', + required=True, + help=_("The protocol port number of " + "the port forwarding's floating IP address") + ) + parser.add_argument( + '--protocol', + metavar='<protocol>', + required=True, + help=_("The protocol used in the floating IP " + "port forwarding, for instance: TCP, UDP") + ) + parser.add_argument( + 'floating_ip', + metavar='<floating-ip>', + help=_("Floating IP that the port forwarding belongs to " + "(IP address or ID)") + ) + return parser + + def take_action(self, parsed_args): + attrs = {} + client = self.app.client_manager.network + floating_ip = client.find_ip( + parsed_args.floating_ip, + ignore_missing=False, + ) + + if parsed_args.internal_protocol_port is not None: + if (parsed_args.internal_protocol_port <= 0 or + parsed_args.internal_protocol_port > 65535): + msg = _("The port number range is <1-65535>") + raise exceptions.CommandError(msg) + attrs['internal_port'] = parsed_args.internal_protocol_port + + if parsed_args.external_protocol_port is not None: + if (parsed_args.external_protocol_port <= 0 or + parsed_args.external_protocol_port > 65535): + msg = _("The port number range is <1-65535>") + raise exceptions.CommandError(msg) + attrs['external_port'] = parsed_args.external_protocol_port + + if parsed_args.port: + port = client.find_port(parsed_args.port, + ignore_missing=False) + attrs['internal_port_id'] = port.id + attrs['internal_ip_address'] = parsed_args.internal_ip_address + attrs['protocol'] = parsed_args.protocol + + obj = client.create_floating_ip_port_forwarding( + floating_ip.id, + **attrs + ) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns) + return (display_columns, data) + + +class DeleteFloatingIPPortForwarding(command.Command): + _description = _("Delete floating IP port forwarding") + + def get_parser(self, prog_name): + parser = super(DeleteFloatingIPPortForwarding, + self).get_parser(prog_name) + parser.add_argument( + 'floating_ip', + metavar='<floating-ip>', + help=_("Floating IP that the port forwarding belongs to " + "(IP address or ID)") + ) + parser.add_argument( + 'port_forwarding_id', + nargs="+", + metavar="<port-forwarding-id>", + help=_("The ID of the floating IP port forwarding(s) to delete") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + floating_ip = client.find_ip( + parsed_args.floating_ip, + ignore_missing=False, + ) + result = 0 + + for port_forwarding_id in parsed_args.port_forwarding_id: + try: + client.delete_floating_ip_port_forwarding( + floating_ip.id, + port_forwarding_id, + ignore_missing=False, + ) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete floating IP port forwarding " + "'%(port_forwarding_id)s': %(e)s"), + {'port_forwarding_id': port_forwarding_id, 'e': e}) + if result > 0: + total = len(parsed_args.port_forwarding_id) + msg = (_("%(result)s of %(total)s Port forwarding failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) + + +class ListFloatingIPPortForwarding(command.Lister): + _description = _("List floating IP port forwarding") + + def get_parser(self, prog_name): + parser = super(ListFloatingIPPortForwarding, + self).get_parser(prog_name) + parser.add_argument( + 'floating_ip', + metavar='<floating-ip>', + help=_("Floating IP that the port forwarding belongs to " + "(IP address or ID)") + ) + parser.add_argument( + '--port', + metavar='<port>', + help=_("Filter the list result by the ID or name of " + "the internal network port") + ) + parser.add_argument( + '--external-protocol-port', + metavar='<port-number>', + dest='external_protocol_port', + help=_("Filter the list result by the " + "protocol port number of the floating IP") + ) + parser.add_argument( + '--protocol', + metavar='protocol', + help=_("Filter the list result by the port protocol") + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + + columns = ( + 'id', + 'internal_port_id', + 'internal_ip_address', + 'internal_port', + 'external_port', + 'protocol', + ) + headers = ( + 'ID', + 'Internal Port ID', + 'Internal IP Address', + 'Internal Port', + 'External Port', + 'Protocol', + ) + + query = {} + + if parsed_args.port: + port = client.find_port(parsed_args.port, + ignore_missing=False) + query['internal_port_id'] = port.id + if parsed_args.external_protocol_port is not None: + query['external_port'] = parsed_args.external_protocol_port + if parsed_args.protocol is not None: + query['protocol'] = parsed_args.protocol + + obj = client.find_ip( + parsed_args.floating_ip, + ignore_missing=False, + ) + + data = client.floating_ip_port_forwardings(obj, **query) + + return (headers, + (utils.get_item_properties( + s, columns, + formatters={}, + ) for s in data)) + + +class SetFloatingIPPortForwarding(command.Command): + _description = _("Set floating IP Port Forwarding Properties") + + def get_parser(self, prog_name): + parser = super(SetFloatingIPPortForwarding, + self).get_parser(prog_name) + parser.add_argument( + 'floating_ip', + metavar='<floating-ip>', + help=_("Floating IP that the port forwarding belongs to " + "(IP address or ID)") + ) + parser.add_argument( + 'port_forwarding_id', + metavar='<port-forwarding-id>', + help=_("The ID of the floating IP port forwarding") + ) + parser.add_argument( + '--port', + metavar='<port>', + help=_("The ID of the network port associated to " + "the floating IP port forwarding") + ) + parser.add_argument( + '--internal-ip-address', + metavar='<internal-ip-address>', + help=_("The fixed IPv4 address of the network port " + "associated to the floating IP port forwarding") + ) + parser.add_argument( + '--internal-protocol-port', + metavar='<port-number>', + type=int, + help=_("The TCP/UDP/other protocol port number of the " + "network port fixed IPv4 address associated to " + "the floating IP port forwarding") + ) + parser.add_argument( + '--external-protocol-port', + type=int, + metavar='<port-number>', + help=_("The TCP/UDP/other protocol port number of the " + "port forwarding's floating IP address") + ) + parser.add_argument( + '--protocol', + metavar='<protocol>', + choices=['tcp', 'udp'], + help=_("The IP protocol used in the floating IP port forwarding") + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + floating_ip = client.find_ip( + parsed_args.floating_ip, + ignore_missing=False, + ) + + attrs = {} + if parsed_args.port: + port = client.find_port(parsed_args.port, + ignore_missing=False) + attrs['internal_port_id'] = port.id + + if parsed_args.internal_ip_address: + attrs['internal_ip_address'] = parsed_args.internal_ip_address + if parsed_args.internal_protocol_port is not None: + if (parsed_args.internal_protocol_port <= 0 or + parsed_args.internal_protocol_port > 65535): + msg = _("The port number range is <1-65535>") + raise exceptions.CommandError(msg) + attrs['internal_port'] = parsed_args.internal_protocol_port + + if parsed_args.external_protocol_port is not None: + if (parsed_args.external_protocol_port <= 0 or + parsed_args.external_protocol_port > 65535): + msg = _("The port number range is <1-65535>") + raise exceptions.CommandError(msg) + attrs['external_port'] = parsed_args.external_protocol_port + + if parsed_args.protocol: + attrs['protocol'] = parsed_args.protocol + + client.update_floating_ip_port_forwarding( + floating_ip.id, parsed_args.port_forwarding_id, **attrs) + + +class ShowFloatingIPPortForwarding(command.ShowOne): + _description = _("Display floating IP Port Forwarding details") + + def get_parser(self, prog_name): + parser = super(ShowFloatingIPPortForwarding, + self).get_parser(prog_name) + parser.add_argument( + 'floating_ip', + metavar='<floating-ip>', + help=_("Floating IP that the port forwarding belongs to " + "(IP address or ID)") + ) + parser.add_argument( + 'port_forwarding_id', + metavar="<port-forwarding-id>", + help=_("The ID of the floating IP port forwarding") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + floating_ip = client.find_ip( + parsed_args.floating_ip, + ignore_missing=False, + ) + obj = client.find_floating_ip_port_forwarding( + floating_ip, + parsed_args.port_forwarding_id, + ignore_missing=False, + ) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns) + return (display_columns, data) diff --git a/openstackclient/network/v2/ip_availability.py b/openstackclient/network/v2/ip_availability.py index 1d96358..c026baa 100644 --- a/openstackclient/network/v2/ip_availability.py +++ b/openstackclient/network/v2/ip_availability.py @@ -13,6 +13,7 @@ """IP Availability Info implementations""" +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import utils @@ -20,8 +21,10 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import sdk_utils + _formatters = { - 'subnet_ip_availability': utils.format_list_of_dicts, + 'location': format_columns.DictColumn, + 'subnet_ip_availability': format_columns.ListDictColumn, } diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index f512393..09f3556 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -13,6 +13,8 @@ """Network action implementations""" +from cliff import columns as cliff_columns +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import utils @@ -23,24 +25,27 @@ from openstackclient.network import sdk_utils from openstackclient.network.v2 import _tag -def _format_admin_state(item): - return 'UP' if item else 'DOWN' +class AdminStateColumn(cliff_columns.FormattableColumn): + def human_readable(self): + return 'UP' if self._value else 'DOWN' -def _format_router_external(item): - return 'External' if item else 'Internal' +class RouterExternalColumn(cliff_columns.FormattableColumn): + def human_readable(self): + return 'External' if self._value else 'Internal' _formatters = { - 'subnets': utils.format_list, - 'subnet_ids': utils.format_list, - 'admin_state_up': _format_admin_state, - 'is_admin_state_up': _format_admin_state, - 'router:external': _format_router_external, - 'is_router_external': _format_router_external, - 'availability_zones': utils.format_list, - 'availability_zone_hints': utils.format_list, - 'tags': utils.format_list, + 'subnets': format_columns.ListColumn, + 'subnet_ids': format_columns.ListColumn, + 'admin_state_up': AdminStateColumn, + 'is_admin_state_up': AdminStateColumn, + 'location': format_columns.DictColumn, + 'router:external': RouterExternalColumn, + 'is_router_external': RouterExternalColumn, + 'availability_zones': format_columns.ListColumn, + 'availability_zone_hints': format_columns.ListColumn, + 'tags': format_columns.ListColumn, } diff --git a/openstackclient/network/v2/network_agent.py b/openstackclient/network/v2/network_agent.py index 46e8d4b..a6ed362 100644 --- a/openstackclient/network/v2/network_agent.py +++ b/openstackclient/network/v2/network_agent.py @@ -15,6 +15,8 @@ import logging +from cliff import columns as cliff_columns +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -26,19 +28,23 @@ from openstackclient.network import sdk_utils LOG = logging.getLogger(__name__) -def _format_alive(alive): - return ":-)" if alive else "XXX" +class AliveColumn(cliff_columns.FormattableColumn): + def human_readable(self): + return ":-)" if self._value else "XXX" -def _format_admin_state(state): - return 'UP' if state else 'DOWN' +class AdminStateColumn(cliff_columns.FormattableColumn): + def human_readable(self): + return 'UP' if self._value else 'DOWN' + _formatters = { - 'is_alive': _format_alive, - 'alive': _format_alive, - 'admin_state_up': _format_admin_state, - 'is_admin_state_up': _format_admin_state, - 'configurations': utils.format_dict, + 'is_alive': AliveColumn, + 'alive': AliveColumn, + 'admin_state_up': AdminStateColumn, + 'is_admin_state_up': AdminStateColumn, + 'location': format_columns.DictColumn, + 'configurations': format_columns.DictColumn, } diff --git a/openstackclient/network/v2/network_auto_allocated_topology.py b/openstackclient/network/v2/network_auto_allocated_topology.py index 36f3920..f6070a0 100644 --- a/openstackclient/network/v2/network_auto_allocated_topology.py +++ b/openstackclient/network/v2/network_auto_allocated_topology.py @@ -15,6 +15,7 @@ import logging +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import utils @@ -25,6 +26,11 @@ from openstackclient.network import sdk_utils LOG = logging.getLogger(__name__) +_formatters = { + 'location': format_columns.DictColumn, +} + + def _get_columns(item): column_map = { 'tenant_id': 'project_id', @@ -93,16 +99,17 @@ class CreateAutoAllocatedTopology(command.ShowOne): obj = client.validate_auto_allocated_topology(parsed_args.project) columns = _format_check_resource_columns() - data = utils.get_item_properties(_format_check_resource(obj), - columns, - formatters={}) - + data = utils.get_item_properties( + _format_check_resource(obj), + columns, + formatters=_formatters, + ) return (columns, data) def get_topology(self, client, parsed_args): obj = client.get_auto_allocated_topology(parsed_args.project) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters={}) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) def take_action(self, parsed_args): diff --git a/openstackclient/network/v2/network_flavor.py b/openstackclient/network/v2/network_flavor.py index c9d368b..355d04c 100644 --- a/openstackclient/network/v2/network_flavor.py +++ b/openstackclient/network/v2/network_flavor.py @@ -15,6 +15,7 @@ import logging +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -27,6 +28,11 @@ from openstackclient.network import sdk_utils LOG = logging.getLogger(__name__) +_formatters = { + 'location': format_columns.DictColumn, +} + + def _get_columns(item): column_map = { 'is_enabled': 'enabled', @@ -136,7 +142,7 @@ class CreateNetworkFlavor(command.ShowOne): attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.create_flavor(**attrs) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters={}) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) @@ -300,5 +306,5 @@ class ShowNetworkFlavor(command.ShowOne): client = self.app.client_manager.network obj = client.find_flavor(parsed_args.flavor, ignore_missing=False) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return display_columns, data diff --git a/openstackclient/network/v2/network_flavor_profile.py b/openstackclient/network/v2/network_flavor_profile.py index 6cf0c41..492fd43 100644 --- a/openstackclient/network/v2/network_flavor_profile.py +++ b/openstackclient/network/v2/network_flavor_profile.py @@ -13,6 +13,7 @@ import logging +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -25,6 +26,11 @@ from openstackclient.network import sdk_utils LOG = logging.getLogger(__name__) +_formatters = { + 'location': format_columns.DictColumn, +} + + def _get_columns(item): column_map = { 'is_enabled': 'enabled', @@ -110,7 +116,7 @@ class CreateNetworkFlavorProfile(command.ShowOne): obj = client.create_service_profile(**attrs) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters={}) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) @@ -246,5 +252,5 @@ class ShowNetworkFlavorProfile(command.ShowOne): obj = client.find_service_profile(parsed_args.flavor_profile, ignore_missing=False) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) diff --git a/openstackclient/network/v2/network_meter.py b/openstackclient/network/v2/network_meter.py index df0e1da..cde7a30 100644 --- a/openstackclient/network/v2/network_meter.py +++ b/openstackclient/network/v2/network_meter.py @@ -15,6 +15,7 @@ import logging +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -26,6 +27,11 @@ from openstackclient.network import sdk_utils LOG = logging.getLogger(__name__) +_formatters = { + 'location': format_columns.DictColumn, +} + + def _get_columns(item): column_map = { 'is_shared': 'shared', @@ -102,7 +108,7 @@ class CreateMeter(command.ShowOne): attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.create_metering_label(**attrs) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters={}) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) @@ -186,5 +192,5 @@ class ShowMeter(command.ShowOne): obj = client.find_metering_label(parsed_args.meter, ignore_missing=False) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return display_columns, data diff --git a/openstackclient/network/v2/network_meter_rule.py b/openstackclient/network/v2/network_meter_rule.py index 49ff9e1..5f31255 100644 --- a/openstackclient/network/v2/network_meter_rule.py +++ b/openstackclient/network/v2/network_meter_rule.py @@ -15,6 +15,7 @@ import logging +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -26,6 +27,11 @@ from openstackclient.network import sdk_utils LOG = logging.getLogger(__name__) +_formatters = { + 'location': format_columns.DictColumn, +} + + def _get_columns(item): column_map = { 'tenant_id': 'project_id', @@ -116,7 +122,7 @@ class CreateMeterRule(command.ShowOne): attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.create_metering_label_rule(**attrs) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters={}) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) @@ -199,5 +205,5 @@ class ShowMeterRule(command.ShowOne): obj = client.find_metering_label_rule(parsed_args.meter_rule_id, ignore_missing=False) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return display_columns, data diff --git a/openstackclient/network/v2/network_qos_policy.py b/openstackclient/network/v2/network_qos_policy.py index fd5ff93..1622de4 100644 --- a/openstackclient/network/v2/network_qos_policy.py +++ b/openstackclient/network/v2/network_qos_policy.py @@ -15,6 +15,7 @@ import logging +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -27,6 +28,11 @@ from openstackclient.network import sdk_utils LOG = logging.getLogger(__name__) +_formatters = { + 'location': format_columns.DictColumn, +} + + def _get_columns(item): column_map = { 'is_shared': 'shared', @@ -119,7 +125,7 @@ class CreateNetworkQosPolicy(command.ShowOne): attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.create_qos_policy(**attrs) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters={}) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) @@ -279,5 +285,5 @@ class ShowNetworkQosPolicy(command.ShowOne): obj = client.find_qos_policy(parsed_args.policy, ignore_missing=False) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) diff --git a/openstackclient/network/v2/network_qos_rule.py b/openstackclient/network/v2/network_qos_rule.py index 28c5600..d74beda 100644 --- a/openstackclient/network/v2/network_qos_rule.py +++ b/openstackclient/network/v2/network_qos_rule.py @@ -15,6 +15,7 @@ import itertools +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -46,6 +47,11 @@ ACTION_SET = 'update' ACTION_SHOW = 'get' +_formatters = { + 'location': format_columns.DictColumn, +} + + def _get_columns(item): column_map = { 'tenant_id': 'project_id', @@ -208,7 +214,7 @@ class CreateNetworkQosRule(command.ShowOne): msg = (_('Failed to create Network QoS rule: %(e)s') % {'e': e}) raise exceptions.CommandError(msg) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return display_columns, data @@ -358,5 +364,5 @@ class ShowNetworkQosRule(command.ShowOne): {'rule': rule_id, 'e': e}) raise exceptions.CommandError(msg) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return display_columns, data diff --git a/openstackclient/network/v2/network_qos_rule_type.py b/openstackclient/network/v2/network_qos_rule_type.py index 7b92c8a..e842944 100644 --- a/openstackclient/network/v2/network_qos_rule_type.py +++ b/openstackclient/network/v2/network_qos_rule_type.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import utils @@ -20,6 +21,11 @@ from openstackclient.i18n import _ from openstackclient.network import sdk_utils +_formatters = { + 'location': format_columns.DictColumn, +} + + def _get_columns(item): column_map = { "type": "rule_type_name", @@ -65,5 +71,5 @@ class ShowNetworkQosRuleType(command.ShowOne): client = self.app.client_manager.network obj = client.get_qos_rule_type(parsed_args.rule_type) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return display_columns, data diff --git a/openstackclient/network/v2/network_rbac.py b/openstackclient/network/v2/network_rbac.py index 140c837..1781193 100644 --- a/openstackclient/network/v2/network_rbac.py +++ b/openstackclient/network/v2/network_rbac.py @@ -15,6 +15,7 @@ import logging +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -27,6 +28,11 @@ from openstackclient.network import sdk_utils LOG = logging.getLogger(__name__) +_formatters = { + 'location': format_columns.DictColumn, +} + + def _get_columns(item): column_map = { 'target_tenant': 'target_project_id', @@ -136,7 +142,7 @@ class CreateNetworkRBAC(command.ShowOne): attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.create_rbac_policy(**attrs) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return display_columns, data @@ -293,5 +299,5 @@ class ShowNetworkRBAC(command.ShowOne): obj = client.find_rbac_policy(parsed_args.rbac_policy, ignore_missing=False) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return display_columns, data diff --git a/openstackclient/network/v2/network_segment.py b/openstackclient/network/v2/network_segment.py index c1a672e..5899dc6 100644 --- a/openstackclient/network/v2/network_segment.py +++ b/openstackclient/network/v2/network_segment.py @@ -15,6 +15,7 @@ import logging +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -26,6 +27,11 @@ from openstackclient.network import sdk_utils LOG = logging.getLogger(__name__) +_formatters = { + 'location': format_columns.DictColumn, +} + + def _get_columns(item): return sdk_utils.get_osc_show_columns_for_sdk_resource(item, {}) @@ -90,7 +96,7 @@ class CreateNetworkSegment(command.ShowOne): attrs['segmentation_id'] = parsed_args.segment obj = client.create_segment(**attrs) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) @@ -242,5 +248,5 @@ class ShowNetworkSegment(command.ShowOne): ignore_missing=False ) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) diff --git a/openstackclient/network/v2/network_segment_range.py b/openstackclient/network/v2/network_segment_range.py index 567b5b6..f03bcc1 100644 --- a/openstackclient/network/v2/network_segment_range.py +++ b/openstackclient/network/v2/network_segment_range.py @@ -19,6 +19,7 @@ import itertools import logging +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -31,6 +32,10 @@ from openstackclient.network import sdk_utils LOG = logging.getLogger(__name__) +_formatters = { + 'location': format_columns.DictColumn, +} + def _get_columns(item): return sdk_utils.get_osc_show_columns_for_sdk_resource(item, {}) @@ -212,7 +217,7 @@ class CreateNetworkSegmentRange(command.ShowOne): attrs['physical_network'] = parsed_args.physical_network obj = network_client.create_network_segment_range(**attrs) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) + data = utils.get_item_properties(obj, columns, formatters=_formatters) data = _update_additional_fields_from_props(columns, props=data) return (display_columns, data) @@ -358,7 +363,7 @@ class ListNetworkSegmentRange(command.Lister): parsed_args.used and _is_prop_empty(columns, props, 'used') or \ parsed_args.unused and \ not _is_prop_empty(columns, props, 'used'): - continue + continue if parsed_args.long: props = _update_additional_fields_from_props(columns, props) display_props += (props,) @@ -451,6 +456,6 @@ class ShowNetworkSegmentRange(command.ShowOne): ignore_missing=False ) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) + data = utils.get_item_properties(obj, columns, formatters=_formatters) data = _update_additional_fields_from_props(columns, props=data) return (display_columns, data) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index f6d6fc7..4d7e518 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -18,6 +18,8 @@ import copy import json import logging +from cliff import columns as cliff_columns +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -33,23 +35,25 @@ from openstackclient.network.v2 import _tag LOG = logging.getLogger(__name__) -def _format_admin_state(state): - return 'UP' if state else 'DOWN' +class AdminStateColumn(cliff_columns.FormattableColumn): + def human_readable(self): + return 'UP' if self._value else 'DOWN' _formatters = { - 'admin_state_up': _format_admin_state, - 'is_admin_state_up': _format_admin_state, - 'allowed_address_pairs': utils.format_list_of_dicts, - 'binding_profile': utils.format_dict, - 'binding_vif_details': utils.format_dict, - 'binding:profile': utils.format_dict, - 'binding:vif_details': utils.format_dict, - 'dns_assignment': utils.format_list_of_dicts, - 'extra_dhcp_opts': utils.format_list_of_dicts, - 'fixed_ips': utils.format_list_of_dicts, - 'security_group_ids': utils.format_list, - 'tags': utils.format_list, + 'admin_state_up': AdminStateColumn, + 'is_admin_state_up': AdminStateColumn, + 'allowed_address_pairs': format_columns.ListDictColumn, + 'binding_profile': format_columns.DictColumn, + 'binding_vif_details': format_columns.DictColumn, + 'binding:profile': format_columns.DictColumn, + 'binding:vif_details': format_columns.DictColumn, + 'dns_assignment': format_columns.ListDictColumn, + 'extra_dhcp_opts': format_columns.ListDictColumn, + 'fixed_ips': format_columns.ListDictColumn, + 'location': format_columns.DictColumn, + 'security_group_ids': format_columns.ListColumn, + 'tags': format_columns.ListColumn, } @@ -96,21 +100,6 @@ class JSONKeyValueAction(argparse.Action): def _get_attrs(client_manager, parsed_args): attrs = {} - # Handle deprecated options - # NOTE(dtroyer): --device-id and --host-id were deprecated in Mar 2016. - # Do not remove before 3.x release or Mar 2017. - if parsed_args.device_id: - attrs['device_id'] = parsed_args.device_id - LOG.warning(_( - 'The --device-id option is deprecated, ' - 'please use --device instead.' - )) - if parsed_args.host_id: - attrs['binding:host_id'] = parsed_args.host_id - LOG.warning(_( - 'The --host-id option is deprecated, ' - 'please use --host instead.' - )) if parsed_args.description is not None: attrs['description'] = parsed_args.description if parsed_args.device: @@ -235,19 +224,11 @@ def _add_updatable_args(parser): metavar='<description>', help=_("Description of this port") ) - # NOTE(dtroyer): --device-id is deprecated in Mar 2016. Do not - # remove before 3.x release or Mar 2017. - device_group = parser.add_mutually_exclusive_group() - device_group.add_argument( + parser.add_argument( '--device', metavar='<device-id>', help=_("Port device ID") ) - device_group.add_argument( - '--device-id', - metavar='<device-id>', - help=argparse.SUPPRESS, - ) parser.add_argument( '--mac-address', metavar='<mac-address>', @@ -268,19 +249,11 @@ def _add_updatable_args(parser): "macvtap | normal | baremetal | virtio-forwarder, " "default: normal)") ) - # NOTE(dtroyer): --host-id is deprecated in Mar 2016. Do not - # remove before 3.x release or Mar 2017. - host_group = parser.add_mutually_exclusive_group() - host_group.add_argument( + parser.add_argument( '--host', metavar='<host-id>', help=_("Allocate port on host <host-id> (ID only)") ) - host_group.add_argument( - '--host-id', - metavar='<host-id>', - help=argparse.SUPPRESS, - ) parser.add_argument( '--dns-domain', metavar='dns-domain', @@ -308,6 +281,19 @@ def _convert_address_pairs(parsed_args): return ops +def _convert_extra_dhcp_options(parsed_args): + dhcp_options = [] + for opt in parsed_args.extra_dhcp_options: + option = {} + option['opt_name'] = opt['name'] + if 'value' in opt: + option['opt_value'] = opt['value'] + if 'ip-version' in opt: + option['ip_version'] = opt['ip-version'] + dhcp_options.append(option) + return dhcp_options + + class CreatePort(command.ShowOne): _description = _("Create a new port") @@ -378,8 +364,18 @@ class CreatePort(command.ShowOne): metavar='<name>', help=_("Name of this port") ) - # TODO(singhj): Add support for extended options: - # dhcp + parser.add_argument( + '--extra-dhcp-option', + metavar='name=<name>[,value=<value>,ip-version={4,6}]', + default=[], + action=parseractions.MultiKeyValueCommaAction, + dest='extra_dhcp_options', + required_keys=['name'], + optional_keys=['value', "ip-version"], + help=_('Extra DHCP options to be assigned to this port: ' + 'name=<name>[,value=<value>,ip-version={4,6}] ' + '(repeat option to set multiple extra DHCP options)')) + secgroups = parser.add_mutually_exclusive_group() secgroups.add_argument( '--security-group', @@ -453,6 +449,9 @@ class CreatePort(command.ShowOne): attrs['allowed_address_pairs'] = ( _convert_address_pairs(parsed_args)) + if parsed_args.extra_dhcp_options: + attrs["extra_dhcp_opts"] = _convert_extra_dhcp_options(parsed_args) + if parsed_args.qos_policy: attrs['qos_policy_id'] = client.find_qos_policy( parsed_args.qos_policy, ignore_missing=False).id diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 2ec3e2f..02da50c 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -13,11 +13,12 @@ """Router action implementations""" -import argparse import copy import json import logging +from cliff import columns as cliff_columns +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -32,33 +33,37 @@ from openstackclient.network.v2 import _tag LOG = logging.getLogger(__name__) -def _format_admin_state(state): - return 'UP' if state else 'DOWN' +class AdminStateColumn(cliff_columns.FormattableColumn): + def human_readable(self): + return 'UP' if self._value else 'DOWN' -def _format_router_info(info): - try: - return json.dumps(info) - except (TypeError, KeyError): - return '' +class RouterInfoColumn(cliff_columns.FormattableColumn): + def human_readable(self): + try: + return json.dumps(self._value) + except (TypeError, KeyError): + return '' -def _format_routes(routes): - # Map the route keys to match --route option. - for route in routes: - if 'nexthop' in route: - route['gateway'] = route.pop('nexthop') - return utils.format_list_of_dicts(routes) +class RoutesColumn(cliff_columns.FormattableColumn): + def human_readable(self): + # Map the route keys to match --route option. + for route in self._value: + if 'nexthop' in route: + route['gateway'] = route.pop('nexthop') + return utils.format_list_of_dicts(self._value) _formatters = { - 'admin_state_up': _format_admin_state, - 'is_admin_state_up': _format_admin_state, - 'external_gateway_info': _format_router_info, - 'availability_zones': utils.format_list, - 'availability_zone_hints': utils.format_list, - 'routes': _format_routes, - 'tags': utils.format_list, + 'admin_state_up': AdminStateColumn, + 'is_admin_state_up': AdminStateColumn, + 'external_gateway_info': RouterInfoColumn, + 'availability_zones': format_columns.ListColumn, + 'availability_zone_hints': format_columns.ListColumn, + 'location': format_columns.DictColumn, + 'routes': RoutesColumn, + 'tags': format_columns.ListColumn, } @@ -528,8 +533,6 @@ class SetRouter(command.Command): action='store_true', help=_("Set router to centralized mode (disabled router only)") ) - routes_group = parser.add_mutually_exclusive_group() - # ToDo(Reedip):Remove mutual exclusiveness once clear-routes is removed parser.add_argument( '--route', metavar='destination=<subnet>,gateway=<ip-address>', @@ -542,18 +545,13 @@ class SetRouter(command.Command): "gateway: nexthop IP address " "(repeat option to set multiple routes)") ) - routes_group.add_argument( + parser.add_argument( '--no-route', action='store_true', help=_("Clear routes associated with the router. " "Specify both --route and --no-route to overwrite " "current value of route.") ) - routes_group.add_argument( - '--clear-routes', - action='store_true', - help=argparse.SUPPRESS, - ) routes_ha = parser.add_mutually_exclusive_group() routes_ha.add_argument( '--ha', @@ -619,21 +617,16 @@ class SetRouter(command.Command): attrs['ha'] = True elif parsed_args.no_ha: attrs['ha'] = False - if parsed_args.clear_routes: - LOG.warning(_( - 'The --clear-routes option is deprecated, ' - 'please use --no-route instead.' - )) if parsed_args.routes is not None: for route in parsed_args.routes: route['nexthop'] = route.pop('gateway') attrs['routes'] = parsed_args.routes - if not (parsed_args.no_route or parsed_args.clear_routes): + if not parsed_args.no_route: # Map the route keys and append to the current routes. # The REST API will handle route validation and duplicates. attrs['routes'] += obj.routes - elif parsed_args.no_route or parsed_args.clear_routes: + elif parsed_args.no_route: attrs['routes'] = [] if (parsed_args.disable_snat or parsed_args.enable_snat or parsed_args.fixed_ip) and not parsed_args.external_gateway: @@ -720,7 +713,7 @@ class ShowRouter(command.ShowOne): setattr(obj, 'interfaces_info', interfaces_info) display_columns, columns = _get_columns(obj) - _formatters['interfaces_info'] = _format_router_info + _formatters['interfaces_info'] = RouterInfoColumn data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index ed6c8d7..38b4e97 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -15,6 +15,8 @@ import argparse +from cliff import columns as cliff_columns +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import utils import six @@ -65,13 +67,25 @@ def _format_compute_security_group_rules(sg_rules): return utils.format_list(rules, separator='\n') +class NetworkSecurityGroupRulesColumn(cliff_columns.FormattableColumn): + def human_readable(self): + return _format_network_security_group_rules(self._value) + + +class ComputeSecurityGroupRulesColumn(cliff_columns.FormattableColumn): + def human_readable(self): + return _format_compute_security_group_rules(self._value) + + _formatters_network = { - 'security_group_rules': _format_network_security_group_rules, + 'location': format_columns.DictColumn, + 'security_group_rules': NetworkSecurityGroupRulesColumn, } _formatters_compute = { - 'rules': _format_compute_security_group_rules, + 'location': format_columns.DictColumn, + 'rules': ComputeSecurityGroupRulesColumn, } diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index 961125a..15f099b 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -16,6 +16,7 @@ import argparse import logging +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib import exceptions from osc_lib import utils @@ -31,6 +32,11 @@ from openstackclient.network import utils as network_utils LOG = logging.getLogger(__name__) +_formatters = { + 'location': format_columns.DictColumn, +} + + def _format_security_group_rule_show(obj): data = network_utils.transform_compute_security_group_rule(obj) return zip(*sorted(six.iteritems(data))) @@ -62,6 +68,17 @@ def _format_network_port_range(rule): return port_range +def _format_remote_ip_prefix(rule): + remote_ip_prefix = rule['remote_ip_prefix'] + if remote_ip_prefix is None: + ethertype = rule['ether_type'] + if ethertype == 'IPv4': + remote_ip_prefix = '0.0.0.0/0' + elif ethertype == 'IPv6': + remote_ip_prefix = '::/0' + return remote_ip_prefix + + def _get_columns(item): column_map = { 'tenant_id': 'project_id', @@ -108,26 +125,14 @@ class CreateSecurityGroupRule(common.NetworkAndComputeShowOne): "--remote-ip", metavar="<ip-address>", help=_("Remote IP address block (may use CIDR notation; " - "default for IPv4 rule: 0.0.0.0/0)"), + "default for IPv4 rule: 0.0.0.0/0, " + "default for IPv6 rule: ::/0)"), ) remote_group.add_argument( "--remote-group", metavar="<group>", help=_("Remote security group (name or ID)"), ) - # Handle deprecated options - # NOTE(dtroyer): --src-ip and --src-group were deprecated in Nov 2016. - # Do not remove before 4.x release or Nov 2017. - remote_group.add_argument( - "--src-ip", - metavar="<ip-address>", - help=argparse.SUPPRESS, - ) - remote_group.add_argument( - "--src-group", - metavar="<group>", - help=argparse.SUPPRESS, - ) return parser def update_parser_network(self, parser): @@ -168,7 +173,7 @@ class CreateSecurityGroupRule(common.NetworkAndComputeShowOne): "ipv6-encap, ipv6-frag, ipv6-icmp, ipv6-nonxt, " "ipv6-opts, ipv6-route, ospf, pgm, rsvp, sctp, tcp, " "udp, udplite, vrrp and integer representations [0-255] " - "or any; default: tcp)") + "or any; default: any (all protocols))") ) protocol_group.add_argument( '--proto', @@ -233,8 +238,8 @@ class CreateSecurityGroupRule(common.NetworkAndComputeShowOne): ) return parser - def _get_protocol(self, parsed_args): - protocol = 'tcp' + def _get_protocol(self, parsed_args, default_protocol='any'): + protocol = default_protocol if parsed_args.protocol is not None: protocol = parsed_args.protocol if parsed_args.proto is not None: @@ -243,6 +248,14 @@ class CreateSecurityGroupRule(common.NetworkAndComputeShowOne): protocol = None return protocol + def _get_ethertype(self, parsed_args, protocol): + ethertype = 'IPv4' + if parsed_args.ethertype is not None: + ethertype = parsed_args.ethertype + elif self._is_ipv6_protocol(protocol): + ethertype = 'IPv6' + return ethertype + def _is_ipv6_protocol(self, protocol): # NOTE(rtheis): Neutron has deprecated protocol icmpv6. # However, while the OSC CLI doesn't document the protocol, @@ -277,12 +290,8 @@ class CreateSecurityGroupRule(common.NetworkAndComputeShowOne): # NOTE(rtheis): Use ethertype specified else default based # on IP protocol. - if parsed_args.ethertype: - attrs['ethertype'] = parsed_args.ethertype - elif self._is_ipv6_protocol(attrs['protocol']): - attrs['ethertype'] = 'IPv6' - else: - attrs['ethertype'] = 'IPv4' + attrs['ethertype'] = self._get_ethertype(parsed_args, + attrs['protocol']) # NOTE(rtheis): Validate the port range and ICMP type and code. # It would be ideal if argparse could do this. @@ -310,33 +319,17 @@ class CreateSecurityGroupRule(common.NetworkAndComputeShowOne): if parsed_args.icmp_code is not None and parsed_args.icmp_code >= 0: attrs['port_range_max'] = parsed_args.icmp_code - # NOTE(dtroyer): --src-ip and --src-group were deprecated in Nov 2016. - # Do not remove before 4.x release or Nov 2017. - if not (parsed_args.remote_group is None and - parsed_args.src_group is None): + if parsed_args.remote_group is not None: attrs['remote_group_id'] = client.find_security_group( - parsed_args.remote_group or parsed_args.src_group, + parsed_args.remote_group, ignore_missing=False ).id - if parsed_args.src_group: - LOG.warning( - _("The %(old)s option is deprecated, " - "please use %(new)s instead."), - {'old': '--src-group', 'new': '--remote-group'}, - ) - elif not (parsed_args.remote_ip is None and - parsed_args.src_ip is None): - attrs['remote_ip_prefix'] = ( - parsed_args.remote_ip or parsed_args.src_ip - ) - if parsed_args.src_ip: - LOG.warning( - _("The %(old)s option is deprecated, " - "please use %(new)s instead."), - {'old': '--src-ip', 'new': '--remote-ip'}, - ) + elif parsed_args.remote_ip is not None: + attrs['remote_ip_prefix'] = parsed_args.remote_ip elif attrs['ethertype'] == 'IPv4': attrs['remote_ip_prefix'] = '0.0.0.0/0' + elif attrs['ethertype'] == 'IPv6': + attrs['remote_ip_prefix'] = '::/0' attrs['security_group_id'] = security_group_id if parsed_args.project is not None: identity_client = self.app.client_manager.identity @@ -350,40 +343,24 @@ class CreateSecurityGroupRule(common.NetworkAndComputeShowOne): # Create and show the security group rule. obj = client.create_security_group_rule(**attrs) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) def take_action_compute(self, client, parsed_args): group = client.api.security_group_find(parsed_args.group) - protocol = self._get_protocol(parsed_args) + protocol = self._get_protocol(parsed_args, default_protocol='tcp') if protocol == 'icmp': from_port, to_port = -1, -1 else: from_port, to_port = parsed_args.dst_port - # NOTE(dtroyer): --src-ip and --src-group were deprecated in Nov 2016. - # Do not remove before 4.x release or Nov 2017. remote_ip = None - if not (parsed_args.remote_group is None and - parsed_args.src_group is None): + if parsed_args.remote_group is not None: parsed_args.remote_group = client.api.security_group_find( - parsed_args.remote_group or parsed_args.src_group, + parsed_args.remote_group, )['id'] - if parsed_args.src_group: - LOG.warning( - _("The %(old)s option is deprecated, " - "please use %(new)s instead."), - {'old': '--src-group', 'new': '--remote-group'}, - ) - if not (parsed_args.remote_ip is None and - parsed_args.src_ip is None): - remote_ip = parsed_args.remote_ip or parsed_args.src_ip - if parsed_args.src_ip: - LOG.warning( - _("The %(old)s option is deprecated, " - "please use %(new)s instead."), - {'old': '--src-ip', 'new': '--remote-ip'}, - ) + if parsed_args.remote_ip is not None: + remote_ip = parsed_args.remote_ip else: remote_ip = '0.0.0.0/0' @@ -434,6 +411,7 @@ class ListSecurityGroupRule(common.NetworkAndComputeLister): """ rule = rule.to_dict() rule['port_range'] = _format_network_port_range(rule) + rule['remote_ip_prefix'] = _format_remote_ip_prefix(rule) return rule def update_parser_common(self, parser): @@ -462,8 +440,14 @@ class ListSecurityGroupRule(common.NetworkAndComputeLister): "ah, dhcp, egp, esp, gre, icmp, igmp, " "ipv6-encap, ipv6-frag, ipv6-icmp, ipv6-nonxt, " "ipv6-opts, ipv6-route, ospf, pgm, rsvp, sctp, tcp, " - "udp, udplite, vrrp and integer representations [0-255])." - ) + "udp, udplite, vrrp and integer representations [0-255] " + "or any; default: any (all protocols))") + ) + parser.add_argument( + '--ethertype', + metavar='<ethertype>', + type=_convert_to_lowercase, + help=_("List rules by the Ethertype (IPv4 or IPv6)") ) direction_group = parser.add_mutually_exclusive_group() direction_group.add_argument( @@ -505,11 +489,12 @@ class ListSecurityGroupRule(common.NetworkAndComputeLister): column_headers = ( 'ID', 'IP Protocol', + 'Ethertype', 'IP Range', 'Port Range', ) if parsed_args.long: - column_headers = column_headers + ('Direction', 'Ethertype',) + column_headers = column_headers + ('Direction',) column_headers = column_headers + ('Remote Security Group',) if parsed_args.group is None: column_headers = column_headers + ('Security Group',) @@ -520,11 +505,12 @@ class ListSecurityGroupRule(common.NetworkAndComputeLister): columns = ( 'id', 'protocol', + 'ether_type', 'remote_ip_prefix', 'port_range', ) if parsed_args.long: - columns = columns + ('direction', 'ether_type',) + columns = columns + ('direction',) columns = columns + ('remote_group_id',) # Get the security group rules using the requested query. @@ -563,6 +549,7 @@ class ListSecurityGroupRule(common.NetworkAndComputeLister): columns = ( "ID", "IP Protocol", + "Ethertype", "IP Range", "Port Range", "Remote Security Group", @@ -611,8 +598,11 @@ class ShowSecurityGroupRule(common.NetworkAndComputeShowOne): def take_action_network(self, client, parsed_args): obj = client.find_security_group_rule(parsed_args.rule, ignore_missing=False) + # necessary for old rules that have None in this field + if not obj['remote_ip_prefix']: + obj['remote_ip_prefix'] = _format_remote_ip_prefix(obj) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) def take_action_compute(self, client, parsed_args): diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 0733f37..c536886 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -16,6 +16,8 @@ import copy import logging +from cliff import columns as cliff_columns +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -40,23 +42,28 @@ def _update_arguments(obj_list, parsed_args_list, option): raise exceptions.CommandError(msg) -def _format_allocation_pools(data): - pool_formatted = ['%s-%s' % (pool.get('start', ''), pool.get('end', '')) - for pool in data] - return ','.join(pool_formatted) +class AllocationPoolsColumn(cliff_columns.FormattableColumn): + def human_readable(self): + pool_formatted = ['%s-%s' % (pool.get('start', ''), + pool.get('end', '')) + for pool in self._value] + return ','.join(pool_formatted) -def _format_host_routes(data): - # Map the host route keys to match --host-route option. - return utils.format_list_of_dicts(convert_entries_to_gateway(data)) +class HostRoutesColumn(cliff_columns.FormattableColumn): + def human_readable(self): + # Map the host route keys to match --host-route option. + return utils.format_list_of_dicts( + convert_entries_to_gateway(self._value)) _formatters = { - 'allocation_pools': _format_allocation_pools, - 'dns_nameservers': utils.format_list, - 'host_routes': _format_host_routes, - 'service_types': utils.format_list, - 'tags': utils.format_list, + 'allocation_pools': AllocationPoolsColumn, + 'dns_nameservers': format_columns.ListColumn, + 'host_routes': HostRoutesColumn, + 'location': format_columns.DictColumn, + 'service_types': format_columns.ListColumn, + 'tags': format_columns.ListColumn, } diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index ba0b6c4..d5a1547 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -15,6 +15,7 @@ import logging +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -41,8 +42,9 @@ def _get_columns(item): _formatters = { - 'prefixes': utils.format_list, - 'tags': utils.format_list, + 'location': format_columns.DictColumn, + 'prefixes': format_columns.ListColumn, + 'tags': format_columns.ListColumn, } diff --git a/openstackclient/object/v1/account.py b/openstackclient/object/v1/account.py index 4847f8b..95be813 100644 --- a/openstackclient/object/v1/account.py +++ b/openstackclient/object/v1/account.py @@ -13,9 +13,9 @@ """Account v1 action implementations""" +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command -from osc_lib import utils import six from openstackclient.i18n import _ @@ -48,7 +48,8 @@ class ShowAccount(command.ShowOne): def take_action(self, parsed_args): data = self.app.client_manager.object_store.account_show() if 'properties' in data: - data['properties'] = utils.format_dict(data.pop('properties')) + data['properties'] = format_columns.DictColumn( + data.pop('properties')) return zip(*sorted(six.iteritems(data))) diff --git a/openstackclient/object/v1/container.py b/openstackclient/object/v1/container.py index 9f689ab..02e8d27 100644 --- a/openstackclient/object/v1/container.py +++ b/openstackclient/object/v1/container.py @@ -17,6 +17,7 @@ import logging +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import utils @@ -230,7 +231,7 @@ class ShowContainer(command.ShowOne): container=parsed_args.container, ) if 'properties' in data: - data['properties'] = utils.format_dict(data.pop('properties')) + data['properties'] = format_columns.DictColumn(data['properties']) return zip(*sorted(six.iteritems(data))) diff --git a/openstackclient/object/v1/object.py b/openstackclient/object/v1/object.py index aeb0253..3747e19 100644 --- a/openstackclient/object/v1/object.py +++ b/openstackclient/object/v1/object.py @@ -17,6 +17,7 @@ import logging +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -284,7 +285,7 @@ class ShowObject(command.ShowOne): object=parsed_args.object, ) if 'properties' in data: - data['properties'] = utils.format_dict(data.pop('properties')) + data['properties'] = format_columns.DictColumn(data['properties']) return zip(*sorted(six.iteritems(data))) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 22d8412..7fbda1a 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -25,7 +25,6 @@ from osc_lib import shell import six import openstackclient -from openstackclient.common import client_config as cloud_config from openstackclient.common import clientmanager @@ -58,10 +57,10 @@ class OpenStackShell(shell.OpenStackShell): def _final_defaults(self): super(OpenStackShell, self)._final_defaults() - # Set the default plugin to token_endpoint if url and token are given - if (self.options.url and self.options.token): - # Use service token authentication - self._auth_type = 'token_endpoint' + # Set the default plugin to admin_token if endpoint and token are given + if (self.options.endpoint and self.options.token): + # Use token authentication + self._auth_type = 'admin_token' else: self._auth_type = 'password' @@ -133,37 +132,7 @@ class OpenStackShell(shell.OpenStackShell): def initialize_app(self, argv): super(OpenStackShell, self).initialize_app(argv) - # Argument precedence is really broken in multiple places - # so we're just going to fix it here until o-c-c and osc-lib - # get sorted out. - # TODO(dtroyer): remove when os-client-config and osc-lib are fixed - - # First, throw away what has already been done with o-c-c and - # use our own. - try: - self.cloud_config = cloud_config.OSC_Config( - override_defaults={ - 'interface': None, - 'auth_type': self._auth_type, - }, - ) - except (IOError, OSError): - self.log.critical("Could not read clouds.yaml configuration file") - self.print_help_if_requested() - raise - - if not self.options.debug: - self.options.debug = None - - # NOTE(dtroyer): Need to do this with validate=False to defer the - # auth plugin handling to ClientManager.setup_auth() - self.cloud = self.cloud_config.get_one_cloud( - cloud=self.options.cloud, - argparse=self.options, - validate=False, - ) - - # Then, re-create the client_manager with the correct arguments + # Re-create the client_manager with our subclass self.client_manager = clientmanager.ClientManager( cli_options=self.cloud, api_version=self.api_version, @@ -209,5 +178,6 @@ def main(argv=None): return OpenStackShell().run(argv) + if __name__ == "__main__": sys.exit(main()) diff --git a/openstackclient/tests/functional/base.py b/openstackclient/tests/functional/base.py index 1414e6b..08e9390 100644 --- a/openstackclient/tests/functional/base.py +++ b/openstackclient/tests/functional/base.py @@ -44,10 +44,32 @@ class TestCase(testtools.TestCase): @classmethod def openstack(cls, cmd, cloud=ADMIN_CLOUD, fail_ok=False): - """Executes openstackclient command for the given action.""" - return execute( - 'openstack --os-cloud={cloud} '.format(cloud=cloud) + - cmd, fail_ok=fail_ok) + """Executes openstackclient command for the given action + + NOTE(dtroyer): There is a subtle distinction between pasing + cloud=None and cloud='': for compatibility reasons passing + cloud=None continues to include the option '--os-auth-type none' + in the command while passing cloud='' omits the '--os-auth-type' + option completely to let the default handlers be invoked. + """ + if cloud is None: + # Execute command with no auth + return execute( + 'openstack --os-auth-type none ' + cmd, + fail_ok=fail_ok + ) + elif cloud == '': + # Execute command with no auth options at all + return execute( + 'openstack ' + cmd, + fail_ok=fail_ok + ) + else: + # Execure command with an explicit cloud specified + return execute( + 'openstack --os-cloud=' + cloud + ' ' + cmd, + fail_ok=fail_ok + ) @classmethod def is_service_enabled(cls, service): diff --git a/openstackclient/tests/functional/common/test_args.py b/openstackclient/tests/functional/common/test_args.py new file mode 100644 index 0000000..02cad6c --- /dev/null +++ b/openstackclient/tests/functional/common/test_args.py @@ -0,0 +1,79 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import json + +from tempest.lib import exceptions as tempest_exc + +from openstackclient.tests.functional import base + + +class ArgumentTests(base.TestCase): + """Functional tests for command line arguments""" + + def test_default_auth_type(self): + cmd_output = json.loads(self.openstack( + 'configuration show -f json', + cloud='', + )) + self.assertIsNotNone(cmd_output) + self.assertIn( + 'auth_type', + cmd_output.keys(), + ) + self.assertEqual( + 'password', + cmd_output['auth_type'], + ) + + def test_auth_type_none(self): + cmd_output = json.loads(self.openstack( + 'configuration show -f json', + cloud=None, + )) + self.assertIsNotNone(cmd_output) + self.assertIn( + 'auth_type', + cmd_output.keys(), + ) + self.assertEqual( + 'none', + cmd_output['auth_type'], + ) + + def test_auth_type_token_endpoint_opt(self): + # Make sure token_endpoint is really gone + try: + self.openstack( + 'configuration show -f json --os-auth-type token_endpoint', + cloud=None, + ) + except tempest_exc.CommandFailed as e: + self.assertIn('--os-auth-type: invalid choice:', str(e)) + self.assertIn('token_endpoint', str(e)) + else: + self.fail('CommandFailed should be raised') + + def test_auth_type_password_opt(self): + cmd_output = json.loads(self.openstack( + 'configuration show -f json --os-auth-type password', + cloud=None, + )) + self.assertIsNotNone(cmd_output) + self.assertIn( + 'auth_type', + cmd_output.keys(), + ) + self.assertEqual( + 'password', + cmd_output['auth_type'], + ) diff --git a/openstackclient/tests/functional/common/test_configuration.py b/openstackclient/tests/functional/common/test_configuration.py index 63a17d0..17e0f45 100644 --- a/openstackclient/tests/functional/common/test_configuration.py +++ b/openstackclient/tests/functional/common/test_configuration.py @@ -37,6 +37,10 @@ class ConfigurationTests(base.TestCase): configuration.REDACTED, cmd_output['auth.password'] ) + self.assertIn( + 'auth.password', + cmd_output.keys(), + ) # Test show --mask cmd_output = json.loads(self.openstack( @@ -65,3 +69,26 @@ class ConfigurationTests(base.TestCase): configuration.REDACTED, cmd_output['auth.password'] ) + + +class ConfigurationTestsNoAuth(base.TestCase): + """Functional test for configuration with no auth""" + + def test_configuration_show(self): + + # Test show without option + raw_output = self.openstack( + 'configuration show', + cloud=None, + ) + items = self.parse_listing(raw_output) + self.assert_table_structure(items, BASIC_CONFIG_HEADERS) + + cmd_output = json.loads(self.openstack( + 'configuration show -f json', + cloud=None, + )) + self.assertNotIn( + 'auth.password', + cmd_output, + ) diff --git a/openstackclient/tests/functional/compute/v2/test_aggregate.py b/openstackclient/tests/functional/compute/v2/test_aggregate.py index c591dd6..be318ae 100644 --- a/openstackclient/tests/functional/compute/v2/test_aggregate.py +++ b/openstackclient/tests/functional/compute/v2/test_aggregate.py @@ -11,6 +11,7 @@ # under the License. import json +import time import uuid from openstackclient.tests.functional import base @@ -19,6 +20,31 @@ from openstackclient.tests.functional import base class AggregateTests(base.TestCase): """Functional tests for aggregate""" + def wait_for_status(self, check_type, check_name, desired_status, + wait=120, interval=5, failures=None): + current_status = "notset" + if failures is None: + failures = ['error'] + total_sleep = 0 + while total_sleep < wait: + output = json.loads(self.openstack( + check_type + ' show -f json ' + check_name)) + current_status = output['name'] + if (current_status == desired_status): + print('{} {} now has status {}' + .format(check_type, check_name, current_status)) + return + print('Checking {} {} Waiting for {} current status: {}' + .format(check_type, check_name, + desired_status, current_status)) + if current_status in failures: + raise Exception( + 'Current status {} of {} {} is one of failures {}' + .format(current_status, check_type, check_name, failures)) + time.sleep(interval) + total_sleep += interval + self.assertOutput(desired_status, current_status) + def test_aggregate_crud(self): """Test create, delete multiple""" name1 = uuid.uuid4().hex @@ -36,12 +62,16 @@ class AggregateTests(base.TestCase): 'nova', cmd_output['availability_zone'] ) - # TODO(dtroyer): enable the following once the properties field - # is correctly formatted in the create output - # self.assertIn( - # "a='b'", - # cmd_output['properties'] - # ) + self.assertIn( + 'a', + cmd_output['properties'] + ) + self.wait_for_status('aggregate', name1, name1) + self.addCleanup( + self.openstack, + 'aggregate delete ' + name1, + fail_ok=True, + ) name2 = uuid.uuid4().hex cmd_output = json.loads(self.openstack( @@ -57,6 +87,12 @@ class AggregateTests(base.TestCase): 'external', cmd_output['availability_zone'] ) + self.wait_for_status('aggregate', name2, name2) + self.addCleanup( + self.openstack, + 'aggregate delete ' + name2, + fail_ok=True, + ) # Test aggregate set name3 = uuid.uuid4().hex @@ -69,6 +105,11 @@ class AggregateTests(base.TestCase): name1 ) self.assertOutput('', raw_output) + self.addCleanup( + self.openstack, + 'aggregate delete ' + name3, + fail_ok=True, + ) cmd_output = json.loads(self.openstack( 'aggregate show -f json ' + @@ -83,11 +124,11 @@ class AggregateTests(base.TestCase): cmd_output['availability_zone'] ) self.assertIn( - "c='d'", + 'c', cmd_output['properties'] ) self.assertNotIn( - "a='b'", + 'a', cmd_output['properties'] ) diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index 6330ac9..2bca70d 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -485,7 +485,7 @@ class ServerTests(common.ComputeTestCase): cmd_output['status'], ) - # NOTE(dtroyer): Prior to https://review.openstack.org/#/c/407111 + # NOTE(dtroyer): Prior to https://review.opendev.org/#/c/407111 # --block-device-mapping was ignored if --volume # present on the command line. Now we should see the # attachment. @@ -614,6 +614,169 @@ class ServerTests(common.ComputeTestCase): # the attached volume had been deleted pass + def test_server_boot_with_bdm_image(self): + # Tests creating a server where the root disk is backed by the given + # --image but a --block-device-mapping with type=image is provided so + # that the compute service creates a volume from that image and + # attaches it as a non-root volume on the server. The block device is + # marked as delete_on_termination=True so it will be automatically + # deleted when the server is deleted. + + # create server with bdm type=image + # NOTE(mriedem): This test is a bit unrealistic in that specifying the + # same image in the block device as the --image option does not really + # make sense, but we just want to make sure everything is processed + # as expected where nova creates a volume from the image and attaches + # that volume to the server. + server_name = uuid.uuid4().hex + server = json.loads(self.openstack( + 'server create -f json ' + + '--flavor ' + self.flavor_name + ' ' + + '--image ' + self.image_name + ' ' + + '--block-device-mapping ' + # This means create a 1GB volume from the specified image, attach + # it to the server at /dev/vdb and delete the volume when the + # server is deleted. + 'vdb=' + self.image_name + ':image:1:true ' + + self.network_arg + ' ' + + '--wait ' + + server_name + )) + self.assertIsNotNone(server["id"]) + self.assertEqual( + server_name, + server['name'], + ) + self.wait_for_status(server_name, 'ACTIVE') + + # check server volumes_attached, format is + # {"volumes_attached": "id='2518bc76-bf0b-476e-ad6b-571973745bb5'",} + cmd_output = json.loads(self.openstack( + 'server show -f json ' + + server_name + )) + volumes_attached = cmd_output['volumes_attached'] + self.assertTrue(volumes_attached.startswith('id=')) + attached_volume_id = volumes_attached.replace('id=', '') + + # check the volume that attached on server + cmd_output = json.loads(self.openstack( + 'volume show -f json ' + + attached_volume_id + )) + attachments = cmd_output['attachments'] + self.assertEqual( + 1, + len(attachments), + ) + self.assertEqual( + server['id'], + attachments[0]['server_id'], + ) + self.assertEqual( + "in-use", + cmd_output['status'], + ) + # TODO(mriedem): If we can parse the volume_image_metadata field from + # the volume show output we could assert the image_name is what we + # specified. volume_image_metadata is something like this: + # {u'container_format': u'bare', u'min_ram': u'0', + # u'disk_format': u'qcow2', u'image_name': u'cirros-0.4.0-x86_64-disk', + # u'image_id': u'05496c83-e2df-4c2f-9e48-453b6e49160d', + # u'checksum': u'443b7623e27ecf03dc9e01ee93f67afe', u'min_disk': u'0', + # u'size': u'12716032'} + + # delete server, then check the attached volume has been deleted + self.openstack('server delete --wait ' + server_name) + cmd_output = json.loads(self.openstack( + 'volume list -f json' + )) + target_volume = [each_volume + for each_volume in cmd_output + if each_volume['ID'] == attached_volume_id] + if target_volume: + # check the attached volume is 'deleting' status + self.assertEqual('deleting', target_volume[0]['Status']) + else: + # the attached volume had been deleted + pass + + def test_boot_from_volume(self): + # Tests creating a server using --image and --boot-from-volume where + # the compute service will create a root volume of the specified size + # using the provided image, attach it as the root disk for the server + # and not delete the volume when the server is deleted. + server_name = uuid.uuid4().hex + server = json.loads(self.openstack( + 'server create -f json ' + + '--flavor ' + self.flavor_name + ' ' + + '--image ' + self.image_name + ' ' + + '--boot-from-volume 1 ' + # create a 1GB volume from the image + self.network_arg + ' ' + + '--wait ' + + server_name + )) + self.assertIsNotNone(server["id"]) + self.assertEqual( + server_name, + server['name'], + ) + self.wait_for_status(server_name, 'ACTIVE') + + # check server volumes_attached, format is + # {"volumes_attached": "id='2518bc76-bf0b-476e-ad6b-571973745bb5'",} + cmd_output = json.loads(self.openstack( + 'server show -f json ' + + server_name + )) + volumes_attached = cmd_output['volumes_attached'] + self.assertTrue(volumes_attached.startswith('id=')) + attached_volume_id = volumes_attached.replace('id=', '') + # Don't leak the volume when the test exits. + self.addCleanup(self.openstack, 'volume delete ' + attached_volume_id) + + # Since the server is volume-backed the GET /servers/{server_id} + # response will have image=''. + self.assertEqual('', cmd_output['image']) + + # check the volume that attached on server + cmd_output = json.loads(self.openstack( + 'volume show -f json ' + + attached_volume_id + )) + # The volume size should be what we specified on the command line. + self.assertEqual(1, int(cmd_output['size'])) + attachments = cmd_output['attachments'] + self.assertEqual( + 1, + len(attachments), + ) + self.assertEqual( + server['id'], + attachments[0]['server_id'], + ) + self.assertEqual( + "in-use", + cmd_output['status'], + ) + # TODO(mriedem): If we can parse the volume_image_metadata field from + # the volume show output we could assert the image_name is what we + # specified. volume_image_metadata is something like this: + # {u'container_format': u'bare', u'min_ram': u'0', + # u'disk_format': u'qcow2', u'image_name': u'cirros-0.4.0-x86_64-disk', + # u'image_id': u'05496c83-e2df-4c2f-9e48-453b6e49160d', + # u'checksum': u'443b7623e27ecf03dc9e01ee93f67afe', u'min_disk': u'0', + # u'size': u'12716032'} + + # delete server, then check the attached volume was not deleted + self.openstack('server delete --wait ' + server_name) + cmd_output = json.loads(self.openstack( + 'volume show -f json ' + + attached_volume_id + )) + # check the volume is in 'available' status + self.assertEqual('available', cmd_output['status']) + def test_server_create_with_none_network(self): """Test server create with none network option.""" server_name = uuid.uuid4().hex diff --git a/openstackclient/tests/functional/identity/v2/test_role.py b/openstackclient/tests/functional/identity/v2/test_role.py index 82e19aa..124603d 100644 --- a/openstackclient/tests/functional/identity/v2/test_role.py +++ b/openstackclient/tests/functional/identity/v2/test_role.py @@ -29,38 +29,6 @@ class RoleTests(common.IdentityTests): items = self.parse_listing(raw_output) self.assert_table_structure(items, common.BASIC_LIST_HEADERS) - def test_role_list_with_user_project(self): - project_name = self._create_dummy_project() - role_name = self._create_dummy_role() - username = self._create_dummy_user() - raw_output = self.openstack( - 'role add ' - '--project %(project)s ' - '--user %(user)s ' - '%(role)s' % {'project': project_name, - 'user': username, - 'role': role_name}) - self.addCleanup( - self.openstack, - 'role remove ' - '--project %(project)s ' - '--user %(user)s ' - '%(role)s' % {'project': project_name, - 'user': username, - 'role': role_name}) - items = self.parse_show(raw_output) - self.assert_show_fields(items, self.ROLE_FIELDS) - - raw_output = self.openstack( - 'role list ' - '--project %(project)s ' - '--user %(user)s ' - '' % {'project': project_name, - 'user': username}) - items = self.parse_listing(raw_output) - self.assert_table_structure(items, common.BASIC_LIST_HEADERS) - self.assertEqual(1, len(items)) - def test_role_show(self): role_name = self._create_dummy_role() raw_output = self.openstack('role show %s' % role_name) diff --git a/openstackclient/tests/functional/identity/v3/test_role.py b/openstackclient/tests/functional/identity/v3/test_role.py index fb9e061..38bfff7 100644 --- a/openstackclient/tests/functional/identity/v3/test_role.py +++ b/openstackclient/tests/functional/identity/v3/test_role.py @@ -31,47 +31,6 @@ class RoleTests(common.IdentityTests): items = self.parse_listing(raw_output) self.assert_table_structure(items, common.BASIC_LIST_HEADERS) - def test_role_list_with_user_project(self): - role_name = self._create_dummy_role() - username = self._create_dummy_user() - raw_output = self.openstack( - 'role add ' - '--project %(project)s ' - '--project-domain %(project_domain)s ' - '--user %(user)s ' - '--user-domain %(user_domain)s ' - '%(role)s' % {'project': self.project_name, - 'project_domain': self.domain_name, - 'user': username, - 'user_domain': self.domain_name, - 'role': role_name}) - self.addCleanup( - self.openstack, - 'role remove ' - '--project %(project)s ' - '--project-domain %(project_domain)s ' - '--user %(user)s ' - '--user-domain %(user_domain)s ' - '%(role)s' % {'project': self.project_name, - 'project_domain': self.domain_name, - 'user': username, - 'user_domain': self.domain_name, - 'role': role_name}) - self.assertEqual(0, len(raw_output)) - raw_output = self.openstack( - 'role list ' - '--project %(project)s ' - '--project-domain %(project_domain)s ' - '--user %(user)s ' - '--user-domain %(user_domain)s ' - '' % {'project': self.project_name, - 'project_domain': self.domain_name, - 'user': username, - 'user_domain': self.domain_name}) - items = self.parse_listing(raw_output) - self.assert_table_structure(items, common.BASIC_LIST_HEADERS) - self.assertEqual(1, len(items)) - def test_role_show(self): role_name = self._create_dummy_role() raw_output = self.openstack('role show %s' % role_name) diff --git a/openstackclient/tests/functional/image/v1/test_image.py b/openstackclient/tests/functional/image/v1/test_image.py index 30490bf..b9774ab 100644 --- a/openstackclient/tests/functional/image/v1/test_image.py +++ b/openstackclient/tests/functional/image/v1/test_image.py @@ -104,6 +104,6 @@ class ImageTests(base.BaseImageTests): self.name )) self.assertEqual( - "a='b', c='d'", + {'a': 'b', 'c': 'd'}, json_output["properties"], ) diff --git a/openstackclient/tests/functional/image/v2/test_image.py b/openstackclient/tests/functional/image/v2/test_image.py index 3185c3b..264ba51 100644 --- a/openstackclient/tests/functional/image/v2/test_image.py +++ b/openstackclient/tests/functional/image/v2/test_image.py @@ -14,7 +14,6 @@ import json import uuid import fixtures -# from glanceclient import exc as image_exceptions from openstackclient.tests.functional.image import base @@ -81,7 +80,7 @@ class ImageTests(base.BaseImageTests): json_output = json.loads(self.openstack( 'image list --tag ' + self.image_tag + ' --long -f json' )) - for taglist in [img['Tags'].split(', ') for img in json_output]: + for taglist in [img['Tags'] for img in json_output]: self.assertIn( self.image_tag, taglist @@ -127,10 +126,8 @@ class ImageTests(base.BaseImageTests): 'image show -f json ' + self.name )) - # NOTE(dtroyer): Don't do a full-string compare so we are tolerant of - # new artributes in the returned data - self.assertIn("a='b'", json_output["properties"]) - self.assertIn("c='d'", json_output["properties"]) + self.assertIn("a", json_output["properties"]) + self.assertIn("c", json_output["properties"]) self.openstack( 'image unset ' + @@ -142,15 +139,13 @@ class ImageTests(base.BaseImageTests): 'image show -f json ' + self.name )) - # NOTE(dtroyer): Don't do a full-string compare so we are tolerant of - # new artributes in the returned data - self.assertNotIn("a='b'", json_output["properties"]) - self.assertNotIn("c='d'", json_output["properties"]) + self.assertNotIn("a", json_output["properties"]) + self.assertNotIn("c", json_output["properties"]) # Test tags self.assertNotIn( '01', - json_output["tags"].split(', ') + json_output["tags"] ) self.openstack( 'image set ' + @@ -163,7 +158,7 @@ class ImageTests(base.BaseImageTests): )) self.assertIn( '01', - json_output["tags"].split(', ') + json_output["tags"] ) self.openstack( @@ -177,7 +172,7 @@ class ImageTests(base.BaseImageTests): )) self.assertNotIn( '01', - json_output["tags"].split(', ') + json_output["tags"] ) def test_image_set_rename(self): diff --git a/openstackclient/tests/functional/network/v2/common.py b/openstackclient/tests/functional/network/v2/common.py index 5243ecd..2287f32 100644 --- a/openstackclient/tests/functional/network/v2/common.py +++ b/openstackclient/tests/functional/network/v2/common.py @@ -62,21 +62,13 @@ class NetworkTagTests(NetworkTests): self._set_resource_and_tag_check('unset', name1, '--all-tag', []) self._set_resource_and_tag_check('set', name2, '--no-tag', []) - def _assertTagsEqual(self, expected, actual): - # TODO(amotoki): Should migrate to cliff format columns. - # At now, unit test assert method needs to be replaced - # to handle format columns, so format_list() is used. - # NOTE: The order of tag is undeterminestic. - actual_tags = filter(bool, actual.split(', ')) - self.assertEqual(set(expected), set(actual_tags)) - def _list_tag_check(self, project_id, expected): cmd_output = json.loads(self.openstack( '{} list --long --project {} -f json'.format(self.base_command, project_id))) for name, tags in expected: net = [n for n in cmd_output if n['Name'] == name][0] - self._assertTagsEqual(tags, net['Tags']) + self.assertEqual(set(tags), set(net['Tags'])) def _create_resource_for_tag_test(self, name, args): return json.loads(self.openstack( @@ -89,7 +81,7 @@ class NetworkTagTests(NetworkTests): self.addCleanup( self.openstack, '{} delete {}'.format(self.base_command, name)) self.assertIsNotNone(cmd_output["id"]) - self._assertTagsEqual(expected, cmd_output['tags']) + self.assertEqual(set(expected), set(cmd_output['tags'])) return name def _set_resource_and_tag_check(self, command, name, args, expected): @@ -100,4 +92,4 @@ class NetworkTagTests(NetworkTests): cmd_output = json.loads(self.openstack( '{} show -f json {}'.format(self.base_command, name) )) - self._assertTagsEqual(expected, cmd_output['tags']) + self.assertEqual(set(expected), set(cmd_output['tags'])) diff --git a/openstackclient/tests/functional/network/v2/test_floating_ip.py b/openstackclient/tests/functional/network/v2/test_floating_ip.py index f189c2d..9d109f8 100644 --- a/openstackclient/tests/functional/network/v2/test_floating_ip.py +++ b/openstackclient/tests/functional/network/v2/test_floating_ip.py @@ -24,12 +24,9 @@ class FloatingIpTests(common.NetworkTests): def setUpClass(cls): common.NetworkTests.setUpClass() if cls.haz_network: + # Create common networks that all tests share cls.EXTERNAL_NETWORK_NAME = uuid.uuid4().hex - cls.EXTERNAL_SUBNET_NAME = uuid.uuid4().hex cls.PRIVATE_NETWORK_NAME = uuid.uuid4().hex - cls.PRIVATE_SUBNET_NAME = uuid.uuid4().hex - cls.ROUTER = uuid.uuid4().hex - cls.PORT_NAME = uuid.uuid4().hex # Create a network for the floating ip json_output = json.loads(cls.openstack( @@ -46,57 +43,11 @@ class FloatingIpTests(common.NetworkTests): )) cls.private_network_id = json_output["id"] - # Try random subnet range for subnet creating - # Because we can not determine ahead of time what subnets are - # already in use, possibly by another test running in parallel, - # try 4 times - for i in range(4): - # Make a random subnet - cls.external_subnet = ".".join(map( - str, - (random.randint(0, 223) for _ in range(3)) - )) + ".0/26" - cls.private_subnet = ".".join(map( - str, - (random.randint(0, 223) for _ in range(3)) - )) + ".0/26" - try: - # Create a subnet for the network - json_output = json.loads(cls.openstack( - 'subnet create -f json ' + - '--network ' + cls.EXTERNAL_NETWORK_NAME + ' ' + - '--subnet-range ' + cls.external_subnet + ' ' + - cls.EXTERNAL_SUBNET_NAME - )) - cls.external_subnet_id = json_output["id"] - # Create a subnet for the private network - json_output = json.loads(cls.openstack( - 'subnet create -f json ' + - '--network ' + cls.PRIVATE_NETWORK_NAME + ' ' + - '--subnet-range ' + cls.private_subnet + ' ' + - cls.PRIVATE_SUBNET_NAME - )) - cls.private_subnet_id = json_output["id"] - except Exception: - if (i == 3): - # raise the exception at the last time - raise - pass - else: - # break and no longer retry if create successfully - break - @classmethod def tearDownClass(cls): try: if cls.haz_network: del_output = cls.openstack( - 'subnet delete ' + - cls.EXTERNAL_SUBNET_NAME + ' ' + - cls.PRIVATE_SUBNET_NAME - ) - cls.assertOutput('', del_output) - del_output = cls.openstack( 'network delete ' + cls.EXTERNAL_NETWORK_NAME + ' ' + cls.PRIVATE_NETWORK_NAME @@ -114,11 +65,50 @@ class FloatingIpTests(common.NetworkTests): # Verify setup self.assertIsNotNone(self.external_network_id) self.assertIsNotNone(self.private_network_id) - self.assertIsNotNone(self.external_subnet_id) - self.assertIsNotNone(self.private_subnet_id) + + def _create_subnet(self, network_name, subnet_name): + subnet_id = None + + # Try random subnet range for subnet creating + # Because we can not determine ahead of time what subnets are + # already in use, possibly by another test running in parallel, + # try 4 times + for i in range(4): + # Make a random subnet + subnet = ".".join(map( + str, + (random.randint(0, 223) for _ in range(3)) + )) + ".0/26" + try: + # Create a subnet for the network + json_output = json.loads(self.openstack( + 'subnet create -f json ' + + '--network ' + network_name + ' ' + + '--subnet-range ' + subnet + ' ' + + subnet_name + )) + self.assertIsNotNone(json_output["id"]) + subnet_id = json_output["id"] + except Exception: + if (i == 3): + # raise the exception at the last time + raise + pass + else: + # break and no longer retry if create successfully + break + return subnet_id def test_floating_ip_delete(self): """Test create, delete multiple""" + + # Subnets must exist even if not directly referenced here + ext_subnet_id = self._create_subnet( + self.EXTERNAL_NETWORK_NAME, + "ext-test-delete" + ) + self.addCleanup(self.openstack, 'subnet delete ' + ext_subnet_id) + json_output = json.loads(self.openstack( 'floating ip create -f json ' + '--description aaaa ' + @@ -151,6 +141,14 @@ class FloatingIpTests(common.NetworkTests): def test_floating_ip_list(self): """Test create defaults, list filters, delete""" + + # Subnets must exist even if not directly referenced here + ext_subnet_id = self._create_subnet( + self.EXTERNAL_NETWORK_NAME, + "ext-test-delete" + ) + self.addCleanup(self.openstack, 'subnet delete ' + ext_subnet_id) + json_output = json.loads(self.openstack( 'floating ip create -f json ' + '--description aaaa ' + @@ -237,6 +235,22 @@ class FloatingIpTests(common.NetworkTests): def test_floating_ip_set_and_unset_port(self): """Test Floating IP Set and Unset port""" + + # Subnets must exist even if not directly referenced here + ext_subnet_id = self._create_subnet( + self.EXTERNAL_NETWORK_NAME, + "ext-test-delete" + ) + self.addCleanup(self.openstack, 'subnet delete ' + ext_subnet_id) + priv_subnet_id = self._create_subnet( + self.PRIVATE_NETWORK_NAME, + "priv-test-delete" + ) + self.addCleanup(self.openstack, 'subnet delete ' + priv_subnet_id) + + self.ROUTER = uuid.uuid4().hex + self.PORT_NAME = uuid.uuid4().hex + json_output = json.loads(self.openstack( 'floating ip create -f json ' + '--description aaaa ' + @@ -253,7 +267,7 @@ class FloatingIpTests(common.NetworkTests): json_output = json.loads(self.openstack( 'port create -f json ' + '--network ' + self.PRIVATE_NETWORK_NAME + ' ' + - '--fixed-ip subnet=' + self.PRIVATE_SUBNET_NAME + ' ' + + '--fixed-ip subnet=' + priv_subnet_id + ' ' + self.PORT_NAME )) self.assertIsNotNone(json_output["id"]) diff --git a/openstackclient/tests/functional/network/v2/test_network.py b/openstackclient/tests/functional/network/v2/test_network.py index 1a74496..71b533e 100644 --- a/openstackclient/tests/functional/network/v2/test_network.py +++ b/openstackclient/tests/functional/network/v2/test_network.py @@ -120,14 +120,14 @@ class NetworkTests(common.NetworkTagTests): cmd_output["description"], ) self.assertEqual( - 'UP', + True, cmd_output["admin_state_up"], ) self.assertFalse( cmd_output["shared"], ) self.assertEqual( - 'Internal', + False, cmd_output["router:external"], ) @@ -231,12 +231,12 @@ class NetworkTests(common.NetworkTagTests): ) # Check the default values self.assertEqual( - 'UP', + True, cmd_output["admin_state_up"], ) self.assertFalse(cmd_output["shared"]) self.assertEqual( - 'Internal', + False, cmd_output["router:external"], ) self.assertFalse(cmd_output["is_default"]) @@ -266,7 +266,7 @@ class NetworkTests(common.NetworkTagTests): cmd_output["description"], ) self.assertEqual( - 'DOWN', + False, cmd_output["admin_state_up"], ) self.assertTrue(cmd_output["shared"]) @@ -398,12 +398,12 @@ class NetworkTests(common.NetworkTagTests): cmd_output["description"], ) self.assertEqual( - 'UP', + True, cmd_output["admin_state_up"], ) self.assertFalse(cmd_output["shared"]) self.assertEqual( - 'Internal', + False, cmd_output["router:external"], ) @@ -432,12 +432,12 @@ class NetworkTests(common.NetworkTagTests): cmd_output["description"], ) self.assertEqual( - 'DOWN', + False, cmd_output["admin_state_up"], ) self.assertTrue(cmd_output["shared"]) self.assertEqual( - 'External', + True, cmd_output["router:external"], ) self.assertFalse(cmd_output["is_default"]) diff --git a/openstackclient/tests/functional/network/v2/test_network_agent.py b/openstackclient/tests/functional/network/v2/test_network_agent.py index 86769e0..963227d 100644 --- a/openstackclient/tests/functional/network/v2/test_network_agent.py +++ b/openstackclient/tests/functional/network/v2/test_network_agent.py @@ -59,7 +59,7 @@ class NetworkAgentTests(common.NetworkTests): 'network agent show -f json %s' % agent_ids[0] )) self.assertEqual( - "DOWN", + False, cmd_output['admin_state_up'], ) @@ -72,7 +72,7 @@ class NetworkAgentTests(common.NetworkTests): 'network agent show -f json %s' % agent_ids[0] )) self.assertEqual( - "UP", + True, cmd_output['admin_state_up'], ) diff --git a/openstackclient/tests/functional/network/v2/test_port.py b/openstackclient/tests/functional/network/v2/test_port.py index e3067d9..a20d204 100644 --- a/openstackclient/tests/functional/network/v2/test_port.py +++ b/openstackclient/tests/functional/network/v2/test_port.py @@ -155,7 +155,7 @@ class PortTests(common.NetworkTagTests): self.addCleanup(self.openstack, 'port delete %s' % id1) self.assertEqual(name, json_output.get('name')) self.assertEqual('xyzpdq', json_output.get('description')) - self.assertEqual('DOWN', json_output.get('admin_state_up')) + self.assertEqual(False, json_output.get('admin_state_up')) raw_output = self.openstack( 'port set --enable %s' % @@ -166,11 +166,11 @@ class PortTests(common.NetworkTagTests): json_output = json.loads(self.openstack( 'port show -f json %s' % name )) - sg_id = json_output.get('security_group_ids') + sg_id = json_output.get('security_group_ids')[0] self.assertEqual(name, json_output.get('name')) self.assertEqual('xyzpdq', json_output.get('description')) - self.assertEqual('UP', json_output.get('admin_state_up')) + self.assertEqual(True, json_output.get('admin_state_up')) self.assertIsNotNone(json_output.get('mac_address')) raw_output = self.openstack( @@ -180,7 +180,7 @@ class PortTests(common.NetworkTagTests): json_output = json.loads(self.openstack( 'port show -f json %s' % name )) - self.assertEqual('', json_output.get('security_group_ids')) + self.assertEqual([], json_output.get('security_group_ids')) def test_port_admin_set(self): """Test create, set (as admin), show, delete""" @@ -229,7 +229,7 @@ class PortTests(common.NetworkTagTests): id1 = json_output.get('id') self.addCleanup(self.openstack, 'port delete %s' % id1) self.assertEqual(name, json_output.get('name')) - self.assertEqual(sg_id1, json_output.get('security_group_ids')) + self.assertEqual([sg_id1], json_output.get('security_group_ids')) raw_output = self.openstack( 'port set ' @@ -242,16 +242,10 @@ class PortTests(common.NetworkTagTests): 'port show -f json %s' % name )) self.assertEqual(name, json_output.get('name')) - self.assertIn( - # TODO(dtroyer): output formatters should not mess with JSON! - sg_id1, - json_output.get('security_group_ids'), - ) - self.assertIn( - # TODO(dtroyer): output formatters should not mess with JSON! - sg_id2, - json_output.get('security_group_ids'), - ) + # NOTE(amotoki): The order of the field is not predictable, + self.assertIsInstance(json_output.get('security_group_ids'), list) + self.assertEqual(sorted([sg_id1, sg_id2]), + sorted(json_output.get('security_group_ids'))) raw_output = self.openstack( 'port unset --security-group %s %s' % (sg_id1, id1)) @@ -261,9 +255,8 @@ class PortTests(common.NetworkTagTests): 'port show -f json %s' % name )) self.assertEqual( - # TODO(dtroyer): output formatters should do this on JSON! - sg_id2, - json_output.get('security_group_ids'), + [sg_id2], + json_output.get('security_group_ids') ) def _create_resource_for_tag_test(self, name, args): diff --git a/openstackclient/tests/functional/network/v2/test_router.py b/openstackclient/tests/functional/network/v2/test_router.py index 9d5beff..05aad7a 100644 --- a/openstackclient/tests/functional/network/v2/test_router.py +++ b/openstackclient/tests/functional/network/v2/test_router.py @@ -90,7 +90,7 @@ class RouterTests(common.NetworkTagTests): cmd_output["name"], ) self.assertEqual( - "DOWN", + False, cmd_output["admin_state_up"], ) self.assertEqual( @@ -109,7 +109,7 @@ class RouterTests(common.NetworkTagTests): cmd_output["name"], ) self.assertEqual( - "UP", + True, cmd_output["admin_state_up"], ) self.assertEqual( @@ -230,7 +230,7 @@ class RouterTests(common.NetworkTagTests): cmd_output["description"], ) self.assertEqual( - 'DOWN', + False, cmd_output["admin_state_up"], ) diff --git a/openstackclient/tests/functional/network/v2/test_subnet.py b/openstackclient/tests/functional/network/v2/test_subnet.py index d5309ee..38030e0 100644 --- a/openstackclient/tests/functional/network/v2/test_subnet.py +++ b/openstackclient/tests/functional/network/v2/test_subnet.py @@ -236,7 +236,7 @@ class SubnetTests(common.NetworkTagTests): cmd_output["gateway_ip"], ) self.assertEqual( - 'network:floatingip_agent_gateway', + ['network:floatingip_agent_gateway'], cmd_output["service_types"], ) @@ -253,7 +253,7 @@ class SubnetTests(common.NetworkTagTests): new_name )) self.assertEqual( - '', + [], cmd_output["service_types"], ) diff --git a/openstackclient/tests/functional/network/v2/test_subnet_pool.py b/openstackclient/tests/functional/network/v2/test_subnet_pool.py index 6be5052..dbcf01e 100644 --- a/openstackclient/tests/functional/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/functional/network/v2/test_subnet_pool.py @@ -38,7 +38,7 @@ class SubnetPoolTests(common.NetworkTagTests): cmd_output["name"] ) self.assertEqual( - pool_prefix, + [pool_prefix], cmd_output["prefixes"] ) @@ -50,7 +50,7 @@ class SubnetPoolTests(common.NetworkTagTests): cmd_output["name"] ) self.assertEqual( - pool_prefix, + [pool_prefix], cmd_output["prefixes"] ) @@ -104,7 +104,7 @@ class SubnetPoolTests(common.NetworkTagTests): cmd_output["project_id"], ) self.assertEqual( - pool_prefix, + [pool_prefix], cmd_output["prefixes"], ) @@ -126,7 +126,7 @@ class SubnetPoolTests(common.NetworkTagTests): cmd_output["project_id"], ) self.assertEqual( - pool_prefix, + [pool_prefix], cmd_output["prefixes"], ) @@ -193,7 +193,7 @@ class SubnetPoolTests(common.NetworkTagTests): cmd_output["description"], ) self.assertEqual( - pool_prefix, + [pool_prefix], cmd_output["prefixes"], ) self.assertEqual( @@ -239,9 +239,9 @@ class SubnetPoolTests(common.NetworkTagTests): 'bbbb', cmd_output["description"], ) - self.assertInOutput( - "10.110.0.0/16", - cmd_output["prefixes"], + self.assertEqual( + sorted(["10.110.0.0/16", pool_prefix]), + sorted(cmd_output["prefixes"]), ) self.assertEqual( 8, diff --git a/openstackclient/tests/functional/volume/v1/test_qos.py b/openstackclient/tests/functional/volume/v1/test_qos.py index 434840f..d8277df 100644 --- a/openstackclient/tests/functional/volume/v1/test_qos.py +++ b/openstackclient/tests/functional/volume/v1/test_qos.py @@ -93,7 +93,7 @@ class QosTests(common.BaseVolumeTests): cmd_output['name'] ) self.assertEqual( - "Alpha='c', Beta='b'", + {'Alpha': 'c', 'Beta': 'b'}, cmd_output['properties'] ) @@ -114,7 +114,7 @@ class QosTests(common.BaseVolumeTests): cmd_output['name'] ) self.assertEqual( - "Beta='b'", + {'Beta': 'b'}, cmd_output['properties'] ) diff --git a/openstackclient/tests/functional/volume/v1/test_snapshot.py b/openstackclient/tests/functional/volume/v1/test_snapshot.py index 083cd1b..5a76a2e 100644 --- a/openstackclient/tests/functional/volume/v1/test_snapshot.py +++ b/openstackclient/tests/functional/volume/v1/test_snapshot.py @@ -204,7 +204,7 @@ class VolumeSnapshotTests(common.BaseVolumeTests): cmd_output["display_description"], ) self.assertEqual( - "Alpha='a', Beta='b'", + {'Alpha': 'a', 'Beta': 'b'}, cmd_output["properties"], ) @@ -221,7 +221,7 @@ class VolumeSnapshotTests(common.BaseVolumeTests): new_name )) self.assertEqual( - "Beta='b'", + {'Beta': 'b'}, cmd_output["properties"], ) @@ -236,7 +236,4 @@ class VolumeSnapshotTests(common.BaseVolumeTests): 'volume snapshot show -f json ' + new_name )) - self.assertNotIn( - "Beta='b'", - cmd_output["properties"], - ) + self.assertEqual({}, cmd_output["properties"]) diff --git a/openstackclient/tests/functional/volume/v1/test_volume.py b/openstackclient/tests/functional/volume/v1/test_volume.py index 001bbe6..013bc6a 100644 --- a/openstackclient/tests/functional/volume/v1/test_volume.py +++ b/openstackclient/tests/functional/volume/v1/test_volume.py @@ -123,7 +123,7 @@ class VolumeTests(common.BaseVolumeTests): cmd_output["display_description"], ) self.assertEqual( - "Alpha='a'", + {'Alpha': 'a'}, cmd_output["properties"], ) self.assertEqual( @@ -165,7 +165,7 @@ class VolumeTests(common.BaseVolumeTests): cmd_output["display_description"], ) self.assertEqual( - "Beta='b', Gamma='c'", + {'Beta': 'b', 'Gamma': 'c'}, cmd_output["properties"], ) self.assertEqual( @@ -186,7 +186,7 @@ class VolumeTests(common.BaseVolumeTests): new_name )) self.assertEqual( - "Gamma='c'", + {'Gamma': 'c'}, cmd_output["properties"], ) diff --git a/openstackclient/tests/functional/volume/v1/test_volume_type.py b/openstackclient/tests/functional/volume/v1/test_volume_type.py index eb9d7f6..fb8dabd 100644 --- a/openstackclient/tests/functional/volume/v1/test_volume_type.py +++ b/openstackclient/tests/functional/volume/v1/test_volume_type.py @@ -66,8 +66,7 @@ class VolumeTypeTests(common.BaseVolumeTests): cmd_output = json.loads(self.openstack( 'volume type show -f json %s' % name )) - # TODO(amotoki): properties output should be machine-readable - self.assertEqual("a='b', c='d'", cmd_output['properties']) + self.assertEqual({'a': 'b', 'c': 'd'}, cmd_output['properties']) raw_output = self.openstack( 'volume type unset --property a %s' % name @@ -76,7 +75,7 @@ class VolumeTypeTests(common.BaseVolumeTests): cmd_output = json.loads(self.openstack( 'volume type show -f json %s' % name )) - self.assertEqual("c='d'", cmd_output['properties']) + self.assertEqual({'c': 'd'}, cmd_output['properties']) def test_volume_type_set_unset_multiple_properties(self): name = uuid.uuid4().hex @@ -97,7 +96,7 @@ class VolumeTypeTests(common.BaseVolumeTests): cmd_output = json.loads(self.openstack( 'volume type show -f json %s' % name )) - self.assertEqual("a='b', c='d'", cmd_output['properties']) + self.assertEqual({'a': 'b', 'c': 'd'}, cmd_output['properties']) raw_output = self.openstack( 'volume type unset --property a --property c %s' % name @@ -106,7 +105,7 @@ class VolumeTypeTests(common.BaseVolumeTests): cmd_output = json.loads(self.openstack( 'volume type show -f json %s' % name )) - self.assertEqual("", cmd_output['properties']) + self.assertEqual({}, cmd_output['properties']) def test_multi_delete(self): vol_type1 = uuid.uuid4().hex @@ -133,34 +132,32 @@ class VolumeTypeTests(common.BaseVolumeTests): '--encryption-key-size 128 ' '--encryption-control-location front-end ' + encryption_type)) - # TODO(amotoki): encryption output should be machine-readable - expected = ["provider='LuksEncryptor'", - "cipher='aes-xts-plain64'", - "key_size='128'", - "control_location='front-end'"] - for attr in expected: - self.assertIn(attr, cmd_output['encryption']) + expected = {'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': 128, + 'control_location': 'front-end'} + for attr, value in expected.items(): + self.assertEqual(value, cmd_output['encryption'][attr]) # test show encryption type cmd_output = json.loads(self.openstack( 'volume type show -f json --encryption-type ' + encryption_type)) - expected = ["provider='LuksEncryptor'", - "cipher='aes-xts-plain64'", - "key_size='128'", - "control_location='front-end'"] - for attr in expected: - self.assertIn(attr, cmd_output['encryption']) + expected = {'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': 128, + 'control_location': 'front-end'} + for attr, value in expected.items(): + self.assertEqual(value, cmd_output['encryption'][attr]) # test list encryption type cmd_output = json.loads(self.openstack( 'volume type list -f json --encryption-type')) encryption_output = [t['Encryption'] for t in cmd_output if t['Name'] == encryption_type][0] - # TODO(amotoki): encryption output should be machine-readable - expected = ["provider='LuksEncryptor'", - "cipher='aes-xts-plain64'", - "key_size='128'", - "control_location='front-end'"] - for attr in expected: - self.assertIn(attr, encryption_output) + expected = {'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': 128, + 'control_location': 'front-end'} + for attr, value in expected.items(): + self.assertEqual(value, encryption_output[attr]) # test set new encryption type raw_output = self.openstack( 'volume type set ' @@ -185,12 +182,12 @@ class VolumeTypeTests(common.BaseVolumeTests): cmd_output = json.loads(self.openstack( 'volume type show -f json --encryption-type ' + name )) - expected = ["provider='LuksEncryptor'", - "cipher='aes-xts-plain64'", - "key_size='128'", - "control_location='front-end'"] - for attr in expected: - self.assertIn(attr, cmd_output['encryption']) + expected = {'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': 128, + 'control_location': 'front-end'} + for attr, value in expected.items(): + self.assertEqual(value, cmd_output['encryption'][attr]) # test unset encryption type raw_output = self.openstack( 'volume type unset --encryption-type ' + name @@ -199,7 +196,7 @@ class VolumeTypeTests(common.BaseVolumeTests): cmd_output = json.loads(self.openstack( 'volume type show -f json --encryption-type ' + name )) - self.assertEqual('', cmd_output['encryption']) + self.assertEqual({}, cmd_output['encryption']) # test delete encryption type raw_output = self.openstack('volume type delete ' + encryption_type) self.assertEqual('', raw_output) diff --git a/openstackclient/tests/functional/volume/v2/test_qos.py b/openstackclient/tests/functional/volume/v2/test_qos.py index 646becc..f9f6e09 100644 --- a/openstackclient/tests/functional/volume/v2/test_qos.py +++ b/openstackclient/tests/functional/volume/v2/test_qos.py @@ -74,7 +74,7 @@ class QosTests(common.BaseVolumeTests): cmd_output['consumer'] ) self.assertEqual( - "Alpha='a'", + {'Alpha': 'a'}, cmd_output['properties'] ) @@ -97,7 +97,7 @@ class QosTests(common.BaseVolumeTests): cmd_output['name'] ) self.assertEqual( - "Alpha='c', Beta='b'", + {'Alpha': 'c', 'Beta': 'b'}, cmd_output['properties'] ) @@ -118,7 +118,7 @@ class QosTests(common.BaseVolumeTests): cmd_output['name'] ) self.assertEqual( - "Beta='b'", + {'Beta': 'b'}, cmd_output['properties'] ) diff --git a/openstackclient/tests/functional/volume/v2/test_volume.py b/openstackclient/tests/functional/volume/v2/test_volume.py index 2930d48..19fd589 100644 --- a/openstackclient/tests/functional/volume/v2/test_volume.py +++ b/openstackclient/tests/functional/volume/v2/test_volume.py @@ -128,7 +128,7 @@ class VolumeTests(common.BaseVolumeTests): cmd_output["description"], ) self.assertEqual( - "Alpha='a'", + {'Alpha': 'a'}, cmd_output["properties"], ) self.assertEqual( @@ -170,7 +170,7 @@ class VolumeTests(common.BaseVolumeTests): cmd_output["description"], ) self.assertEqual( - "Beta='b', Gamma='c'", + {'Beta': 'b', 'Gamma': 'c'}, cmd_output["properties"], ) self.assertEqual( @@ -196,7 +196,7 @@ class VolumeTests(common.BaseVolumeTests): new_name )) self.assertEqual( - "Gamma='c'", + {'Gamma': 'c'}, cmd_output["properties"], ) self.assertEqual( diff --git a/openstackclient/tests/functional/volume/v2/test_backup.py b/openstackclient/tests/functional/volume/v2/test_volume_backup.py index 8f5c032..6868bd4 100644 --- a/openstackclient/tests/functional/volume/v2/test_backup.py +++ b/openstackclient/tests/functional/volume/v2/test_volume_backup.py @@ -46,14 +46,14 @@ class VolumeBackupTests(common.BaseVolumeTests): 'volume backup create -f json ' + vol_id )) - self.wait_for_status("backup", backup['id'], "available") + self.wait_for_status("volume backup", backup['id'], "available") # restore the backup backup_restored = json.loads(self.openstack( 'volume backup restore -f json %s %s' % (backup['id'], vol_id))) self.assertEqual(backup_restored['backup_id'], backup['id']) - self.wait_for_status("backup", backup['id'], "available") + self.wait_for_status("volume backup", backup['id'], "available") self.wait_for_status("volume", backup_restored['volume_id'], "available") self.addCleanup(self.openstack, 'volume delete %s' % vol_id) diff --git a/openstackclient/tests/functional/volume/v2/test_snapshot.py b/openstackclient/tests/functional/volume/v2/test_volume_snapshot.py index 264f4ad..8d32d99 100644 --- a/openstackclient/tests/functional/volume/v2/test_snapshot.py +++ b/openstackclient/tests/functional/volume/v2/test_volume_snapshot.py @@ -182,7 +182,7 @@ class VolumeSnapshotTests(common.BaseVolumeTests): cmd_output["description"], ) self.assertEqual( - "Alpha='a'", + {'Alpha': 'a'}, cmd_output["properties"], ) self.wait_for_status('volume snapshot', name, 'available') @@ -216,7 +216,7 @@ class VolumeSnapshotTests(common.BaseVolumeTests): cmd_output["description"], ) self.assertEqual( - "Alpha='c', Beta='b'", + {'Alpha': 'c', 'Beta': 'b'}, cmd_output["properties"], ) @@ -233,7 +233,7 @@ class VolumeSnapshotTests(common.BaseVolumeTests): new_name )) self.assertEqual( - "Beta='b'", + {'Beta': 'b'}, cmd_output["properties"], ) @@ -249,6 +249,6 @@ class VolumeSnapshotTests(common.BaseVolumeTests): new_name )) self.assertNotIn( - "Beta='b'", + {'Beta': 'b'}, cmd_output["properties"], ) diff --git a/openstackclient/tests/functional/volume/v2/test_volume_type.py b/openstackclient/tests/functional/volume/v2/test_volume_type.py index d8dd5bd..3f1a6ea 100644 --- a/openstackclient/tests/functional/volume/v2/test_volume_type.py +++ b/openstackclient/tests/functional/volume/v2/test_volume_type.py @@ -65,8 +65,7 @@ class VolumeTypeTests(common.BaseVolumeTests): cmd_output = json.loads(self.openstack( 'volume type show -f json %s' % name )) - # TODO(amotoki): properties output should be machine-readable - self.assertEqual("a='b', c='d'", cmd_output['properties']) + self.assertEqual({'a': 'b', 'c': 'd'}, cmd_output['properties']) raw_output = self.openstack( 'volume type unset --property a %s' % name @@ -75,7 +74,7 @@ class VolumeTypeTests(common.BaseVolumeTests): cmd_output = json.loads(self.openstack( 'volume type show -f json %s' % name )) - self.assertEqual("c='d'", cmd_output['properties']) + self.assertEqual({'c': 'd'}, cmd_output['properties']) def test_volume_type_set_unset_multiple_properties(self): name = uuid.uuid4().hex @@ -96,7 +95,7 @@ class VolumeTypeTests(common.BaseVolumeTests): cmd_output = json.loads(self.openstack( 'volume type show -f json %s' % name )) - self.assertEqual("a='b', c='d'", cmd_output['properties']) + self.assertEqual({'a': 'b', 'c': 'd'}, cmd_output['properties']) raw_output = self.openstack( 'volume type unset --property a --property c %s' % name @@ -105,7 +104,7 @@ class VolumeTypeTests(common.BaseVolumeTests): cmd_output = json.loads(self.openstack( 'volume type show -f json %s' % name )) - self.assertEqual("", cmd_output['properties']) + self.assertEqual({}, cmd_output['properties']) def test_volume_type_set_unset_project(self): name = uuid.uuid4().hex @@ -155,35 +154,32 @@ class VolumeTypeTests(common.BaseVolumeTests): '--encryption-key-size 128 ' '--encryption-control-location front-end ' + encryption_type)) - # TODO(amotoki): encryption output should be machine-readable - expected = ["provider='LuksEncryptor'", - "cipher='aes-xts-plain64'", - "key_size='128'", - "control_location='front-end'"] - for attr in expected: - self.assertIn(attr, cmd_output['encryption']) + expected = {'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': 128, + 'control_location': 'front-end'} + for attr, value in expected.items(): + self.assertEqual(value, cmd_output['encryption'][attr]) # test show encryption type cmd_output = json.loads(self.openstack( 'volume type show -f json --encryption-type ' + encryption_type)) - # TODO(amotoki): encryption output should be machine-readable - expected = ["provider='LuksEncryptor'", - "cipher='aes-xts-plain64'", - "key_size='128'", - "control_location='front-end'"] - for attr in expected: - self.assertIn(attr, cmd_output['encryption']) + expected = {'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': 128, + 'control_location': 'front-end'} + for attr, value in expected.items(): + self.assertEqual(value, cmd_output['encryption'][attr]) # test list encryption type cmd_output = json.loads(self.openstack( 'volume type list -f json --encryption-type')) encryption_output = [t['Encryption'] for t in cmd_output if t['Name'] == encryption_type][0] - # TODO(amotoki): encryption output should be machine-readable - expected = ["provider='LuksEncryptor'", - "cipher='aes-xts-plain64'", - "key_size='128'", - "control_location='front-end'"] - for attr in expected: - self.assertIn(attr, encryption_output) + expected = {'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': 128, + 'control_location': 'front-end'} + for attr, value in expected.items(): + self.assertEqual(value, encryption_output[attr]) # test set existing encryption type raw_output = self.openstack( 'volume type set ' @@ -193,12 +189,12 @@ class VolumeTypeTests(common.BaseVolumeTests): self.assertEqual('', raw_output) cmd_output = json.loads(self.openstack( 'volume type show -f json --encryption-type ' + encryption_type)) - expected = ["provider='LuksEncryptor'", - "cipher='aes-xts-plain64'", - "key_size='256'", - "control_location='back-end'"] - for attr in expected: - self.assertIn(attr, cmd_output['encryption']) + expected = {'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': 256, + 'control_location': 'back-end'} + for attr, value in expected.items(): + self.assertEqual(value, cmd_output['encryption'][attr]) # test set new encryption type cmd_output = json.loads(self.openstack( 'volume type create -f json --private ' + @@ -222,12 +218,12 @@ class VolumeTypeTests(common.BaseVolumeTests): cmd_output = json.loads(self.openstack( 'volume type show -f json --encryption-type ' + name )) - expected = ["provider='LuksEncryptor'", - "cipher='aes-xts-plain64'", - "key_size='128'", - "control_location='front-end'"] - for attr in expected: - self.assertIn(attr, cmd_output['encryption']) + expected = {'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': 128, + 'control_location': 'front-end'} + for attr, value in expected.items(): + self.assertEqual(value, cmd_output['encryption'][attr]) # test unset encryption type raw_output = self.openstack( 'volume type unset --encryption-type ' + name @@ -236,7 +232,7 @@ class VolumeTypeTests(common.BaseVolumeTests): cmd_output = json.loads(self.openstack( 'volume type show -f json --encryption-type ' + name )) - self.assertEqual('', cmd_output['encryption']) + self.assertEqual({}, cmd_output['encryption']) # test delete encryption type raw_output = self.openstack('volume type delete ' + encryption_type) self.assertEqual('', raw_output) diff --git a/openstackclient/tests/functional/volume/v3/test_snapshot.py b/openstackclient/tests/functional/volume/v3/test_volume_snapshot.py index 38e0563..28eee6d 100644 --- a/openstackclient/tests/functional/volume/v3/test_snapshot.py +++ b/openstackclient/tests/functional/volume/v3/test_volume_snapshot.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -from openstackclient.tests.functional.volume.v2 import test_snapshot as v2 +from openstackclient.tests.functional.volume.v2 import test_volume_snapshot as v2 # noqa from openstackclient.tests.functional.volume.v3 import common diff --git a/openstackclient/tests/unit/api/test_object_store_v1.py b/openstackclient/tests/unit/api/test_object_store_v1.py index acf9555..74b6249 100644 --- a/openstackclient/tests/unit/api/test_object_store_v1.py +++ b/openstackclient/tests/unit/api/test_object_store_v1.py @@ -184,7 +184,7 @@ class TestObject(TestObjectAPIv1): } # TODO(dtroyer): When requests_mock gains the ability to # match against request.body add this check - # https://review.openstack.org/127316 + # https://review.opendev.org/127316 self.requests_mock.register_uri( 'PUT', FAKE_URL + '/qaz/counter.txt', diff --git a/openstackclient/tests/unit/common/test_clientmanager.py b/openstackclient/tests/unit/common/test_clientmanager.py index f15f9af..a83b131 100644 --- a/openstackclient/tests/unit/common/test_clientmanager.py +++ b/openstackclient/tests/unit/common/test_clientmanager.py @@ -28,19 +28,19 @@ class TestClientManager(osc_lib_test_utils.TestClientManager): """Allow subclasses to override the ClientManager class""" return clientmanager.ClientManager - def test_client_manager_token_endpoint(self): + def test_client_manager_admin_token(self): token_auth = { - 'url': fakes.AUTH_URL, + 'endpoint': fakes.AUTH_URL, 'token': fakes.AUTH_TOKEN, } client_manager = self._make_clientmanager( auth_args=token_auth, - auth_plugin_name='token_endpoint', + auth_plugin_name='admin_token', ) self.assertEqual( fakes.AUTH_URL, - client_manager._cli_options.config['auth']['url'], + client_manager._cli_options.config['auth']['endpoint'], ) self.assertEqual( fakes.AUTH_TOKEN, diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index ee7d498..7357e14 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -505,7 +505,7 @@ class FakeSecurityGroup(object): @staticmethod def get_security_groups(security_groups=None, count=2): - """Get an iterable MagicMock object with a list of faked security groups. + """Get an iterable MagicMock with a list of faked security groups. If security groups list is provided, then initialize the Mock object with the list. Otherwise create one. diff --git a/openstackclient/tests/unit/compute/v2/test_aggregate.py b/openstackclient/tests/unit/compute/v2/test_aggregate.py index 3efe0db..0937047 100644 --- a/openstackclient/tests/unit/compute/v2/test_aggregate.py +++ b/openstackclient/tests/unit/compute/v2/test_aggregate.py @@ -16,6 +16,7 @@ import mock from mock import call +from osc_lib.cli import format_columns from osc_lib import exceptions from osc_lib import utils @@ -31,16 +32,16 @@ class TestAggregate(compute_fakes.TestComputev2): 'availability_zone', 'hosts', 'id', - 'metadata', 'name', + 'properties', ) data = ( fake_ag.availability_zone, - fake_ag.hosts, + format_columns.ListColumn(fake_ag.hosts), fake_ag.id, - fake_ag.metadata, fake_ag.name, + format_columns.DictColumn(fake_ag.metadata), ) def setUp(self): @@ -75,7 +76,7 @@ class TestAggregateAddHost(TestAggregate): self.aggregate_mock.add_host.assert_called_once_with(self.fake_ag, parsed_args.host) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) class TestAggregateCreate(TestAggregate): @@ -99,7 +100,7 @@ class TestAggregateCreate(TestAggregate): self.aggregate_mock.create.assert_called_once_with(parsed_args.name, None) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_aggregate_create_with_zone(self): arglist = [ @@ -116,7 +117,7 @@ class TestAggregateCreate(TestAggregate): self.aggregate_mock.create.assert_called_once_with(parsed_args.name, parsed_args.zone) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_aggregate_create_with_property(self): arglist = [ @@ -135,7 +136,7 @@ class TestAggregateCreate(TestAggregate): self.aggregate_mock.set_metadata.assert_called_once_with( self.fake_ag, parsed_args.property) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) class TestAggregateDelete(TestAggregate): @@ -234,8 +235,11 @@ class TestAggregateList(TestAggregate): TestAggregate.fake_ag.id, TestAggregate.fake_ag.name, TestAggregate.fake_ag.availability_zone, - {key: value for key, value in TestAggregate.fake_ag.metadata.items() - if key != 'availability_zone'}, + format_columns.DictColumn({ + key: value + for key, value in TestAggregate.fake_ag.metadata.items() + if key != 'availability_zone' + }), ), ) def setUp(self): @@ -250,7 +254,7 @@ class TestAggregateList(TestAggregate): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.list_columns, columns) - self.assertEqual(self.list_data, tuple(data)) + self.assertItemEqual(self.list_data, tuple(data)) def test_aggregate_list_with_long(self): arglist = [ @@ -263,7 +267,7 @@ class TestAggregateList(TestAggregate): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.list_columns_long, columns) - self.assertEqual(self.list_data_long, tuple(data)) + self.assertListItemEqual(self.list_data_long, tuple(data)) class TestAggregateRemoveHost(TestAggregate): @@ -290,7 +294,7 @@ class TestAggregateRemoveHost(TestAggregate): self.aggregate_mock.remove_host.assert_called_once_with( self.fake_ag, parsed_args.host) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) class TestAggregateSet(TestAggregate): @@ -440,13 +444,14 @@ class TestAggregateShow(TestAggregate): data = ( TestAggregate.fake_ag.availability_zone, - TestAggregate.fake_ag.hosts, + format_columns.ListColumn(TestAggregate.fake_ag.hosts), TestAggregate.fake_ag.id, TestAggregate.fake_ag.name, - utils.format_dict( - {key: value - for key, value in TestAggregate.fake_ag.metadata.items() - if key != 'availability_zone'}), + format_columns.DictColumn({ + key: value + for key, value in TestAggregate.fake_ag.metadata.items() + if key != 'availability_zone' + }), ) def setUp(self): @@ -467,7 +472,7 @@ class TestAggregateShow(TestAggregate): self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, tuple(data)) class TestAggregateUnset(TestAggregate): diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 4a37a81..5c98188 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -14,6 +14,7 @@ # import argparse import collections +import copy import getpass import mock @@ -65,9 +66,22 @@ class TestServer(compute_fakes.TestComputev2): self.methods = {} def setup_servers_mock(self, count): - servers = compute_fakes.FakeServer.create_servers(attrs=self.attrs, - methods=self.methods, - count=count) + # If we are creating more than one server, make one of them + # boot-from-volume + include_bfv = count > 1 + servers = compute_fakes.FakeServer.create_servers( + attrs=self.attrs, + methods=self.methods, + count=count - 1 if include_bfv else count + ) + if include_bfv: + attrs = copy.deepcopy(self.attrs) + attrs['image'] = '' + bfv_server = compute_fakes.FakeServer.create_one_server( + attrs=attrs, + methods=self.methods + ) + servers.append(bfv_server) # This is the return value for utils.find_resource() self.servers_mock.get = compute_fakes.FakeServer.get_servers(servers, @@ -91,7 +105,18 @@ class TestServer(compute_fakes.TestComputev2): for s in servers: method = getattr(s, method_name) - method.assert_called_with() + if method_name == 'lock': + version = self.app.client_manager.compute.api_version + if version >= api_versions.APIVersion('2.73'): + method.assert_called_with(reason=None) + elif method_name == 'unshelve': + version = self.app.client_manager.compute.api_version + if version >= api_versions.APIVersion('2.77'): + method.assert_called_with(availability_zone=None) + else: + method.assert_called_with() + else: + method.assert_called_with() self.assertIsNone(result) @@ -1677,6 +1702,33 @@ class TestServerCreate(TestServer): self.cmd.take_action, parsed_args) + def test_server_create_volume_boot_from_volume_conflict(self): + # Tests that specifying --volume and --boot-from-volume results in + # an error. Since --boot-from-volume requires --image or + # --image-property but those are in a mutex group with --volume, we + # only specify --volume and --boot-from-volume for this test since + # the validation is not handled with argparse. + arglist = [ + '--flavor', self.flavor.id, + '--volume', 'volume1', + '--boot-from-volume', '1', + self.new_server.name, + ] + verifylist = [ + ('flavor', self.flavor.id), + ('volume', 'volume1'), + ('boot_from_volume', 1), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) + # Assert it is the error we expect. + self.assertIn('--volume is not allowed with --boot-from-volume', + six.text_type(ex)) + def test_server_create_image_property(self): arglist = [ '--image-property', 'hypervisor_type=qemu', @@ -1919,6 +1971,237 @@ class TestServerCreate(TestServer): self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) + def test_server_create_with_host_v274(self): + + # Explicit host is supported for nova api version 2.74 or above + self.app.client_manager.compute.api_version = 2.74 + + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--host', 'host1', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('host', 'host1'), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch.object(api_versions, + 'APIVersion', + return_value=2.74): + # In base command class ShowOne in cliff, abstract method + # take_action() returns a two-part tuple with a tuple of + # column names and a tuple of data to be shown. + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = dict( + meta=None, + files={}, + reservation_id=None, + min_count=1, + max_count=1, + security_groups=[], + userdata=None, + key_name=None, + availability_zone=None, + block_device_mapping_v2=[], + nics='auto', + scheduler_hints={}, + config_drive=None, + host='host1', + ) + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + self.image, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + self.assertFalse(self.images_mock.called) + self.assertFalse(self.flavors_mock.called) + + def test_server_create_with_host_pre_v274(self): + + # Host is not supported for nova api version below 2.74 + self.app.client_manager.compute.api_version = 2.73 + + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--host', 'host1', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('host', 'host1'), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch.object(api_versions, + 'APIVersion', + return_value=2.74): + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + def test_server_create_with_hypervisor_hostname_v274(self): + + # Explicit hypervisor_hostname is supported for nova api version + # 2.74 or above + self.app.client_manager.compute.api_version = 2.74 + + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--hypervisor-hostname', 'node1', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('hypervisor_hostname', 'node1'), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch.object(api_versions, + 'APIVersion', + return_value=2.74): + # In base command class ShowOne in cliff, abstract method + # take_action() returns a two-part tuple with a tuple of + # column names and a tuple of data to be shown. + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = dict( + meta=None, + files={}, + reservation_id=None, + min_count=1, + max_count=1, + security_groups=[], + userdata=None, + key_name=None, + availability_zone=None, + block_device_mapping_v2=[], + nics='auto', + scheduler_hints={}, + config_drive=None, + hypervisor_hostname='node1', + ) + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + self.image, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + self.assertFalse(self.images_mock.called) + self.assertFalse(self.flavors_mock.called) + + def test_server_create_with_hypervisor_hostname_pre_v274(self): + + # Hypervisor_hostname is not supported for nova api version below 2.74 + self.app.client_manager.compute.api_version = 2.73 + + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--hypervisor-hostname', 'node1', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('hypervisor_hostname', 'node1'), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch.object(api_versions, + 'APIVersion', + return_value=2.74): + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + def test_server_create_with_host_and_hypervisor_hostname_v274(self): + + # Explicit host and hypervisor_hostname is supported for nova api + # version 2.74 or above + self.app.client_manager.compute.api_version = 2.74 + + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--host', 'host1', + '--hypervisor-hostname', 'node1', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('host', 'host1'), + ('hypervisor_hostname', 'node1'), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch.object(api_versions, + 'APIVersion', + return_value=2.74): + # In base command class ShowOne in cliff, abstract method + # take_action() returns a two-part tuple with a tuple of + # column names and a tuple of data to be shown. + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = dict( + meta=None, + files={}, + reservation_id=None, + min_count=1, + max_count=1, + security_groups=[], + userdata=None, + key_name=None, + availability_zone=None, + block_device_mapping_v2=[], + nics='auto', + scheduler_hints={}, + config_drive=None, + host='host1', + hypervisor_hostname='node1', + ) + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + self.image, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + self.assertFalse(self.images_mock.called) + self.assertFalse(self.flavors_mock.called) + class TestServerDelete(TestServer): @@ -2122,7 +2405,8 @@ class TestServerList(TestServer): Image = collections.namedtuple('Image', 'id name') self.images_mock.list.return_value = [ Image(id=s.image['id'], name=self.image.name) - for s in self.servers + # Image will be an empty string if boot-from-volume + for s in self.servers if s.image ] Flavor = collections.namedtuple('Flavor', 'id name') @@ -2137,7 +2421,8 @@ class TestServerList(TestServer): s.name, s.status, server._format_servers_list_networks(s.networks), - self.image.name, + # Image will be an empty string if boot-from-volume + self.image.name if s.image else s.image, self.flavor.name, )) self.data_long.append(( @@ -2149,8 +2434,9 @@ class TestServerList(TestServer): getattr(s, 'OS-EXT-STS:power_state') ), server._format_servers_list_networks(s.networks), - self.image.name, - s.image['id'], + # Image will be an empty string if boot-from-volume + self.image.name if s.image else s.image, + s.image['id'] if s.image else s.image, self.flavor.name, s.flavor['id'], getattr(s, 'OS-EXT-AZ:availability_zone'), @@ -2162,7 +2448,8 @@ class TestServerList(TestServer): s.name, s.status, server._format_servers_list_networks(s.networks), - s.image['id'], + # Image will be an empty string if boot-from-volume + s.image['id'] if s.image else s.image, s.flavor['id'] )) @@ -2296,6 +2583,80 @@ class TestServerList(TestServer): self.assertEqual(self.columns, columns) self.assertEqual(tuple(self.data), tuple(data)) + def test_server_list_with_locked_pre_v273(self): + + arglist = [ + '--locked' + ] + verifylist = [ + ('locked', True) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.73 or greater is required', str(ex)) + + def test_server_list_with_locked_v273(self): + + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.73') + arglist = [ + '--locked' + ] + verifylist = [ + ('locked', True) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.search_opts['locked'] = True + self.servers_mock.list.assert_called_with(**self.kwargs) + + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + def test_server_list_with_unlocked_v273(self): + + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.73') + arglist = [ + '--unlocked' + ] + verifylist = [ + ('unlocked', True) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.search_opts['locked'] = False + self.servers_mock.list.assert_called_with(**self.kwargs) + + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + def test_server_list_with_locked_and_unlocked_v273(self): + + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.73') + arglist = [ + '--locked', + '--unlocked' + ] + verifylist = [ + ('locked', True), + ('unlocked', True) + ] + + ex = self.assertRaises( + utils.ParserException, + self.check_parser, self.cmd, arglist, verifylist) + self.assertIn('Argument parse failed', str(ex)) + def test_server_list_with_flavor(self): arglist = [ @@ -2487,6 +2848,72 @@ class TestServerLock(TestServer): def test_server_lock_multi_servers(self): self.run_method_with_servers('lock', 3) + def test_server_lock_with_reason(self): + server = compute_fakes.FakeServer.create_one_server() + arglist = [ + server.id, + '--reason', "blah", + ] + verifylist = [ + ('reason', "blah"), + ('server', [server.id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.73 or greater is required', str(ex)) + + +class TestServerLockV273(TestServerLock): + + def setUp(self): + super(TestServerLockV273, self).setUp() + + self.server = compute_fakes.FakeServer.create_one_server( + methods=self.methods) + + # This is the return value for utils.find_resource() + self.servers_mock.get.return_value = self.server + + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.73') + + # Get the command object to test + self.cmd = server.LockServer(self.app, None) + + def test_server_lock_with_reason(self): + arglist = [ + self.server.id, + '--reason', "blah", + ] + verifylist = [ + ('reason', "blah"), + ('server', [self.server.id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.servers_mock.get.assert_called_with(self.server.id) + self.server.lock.assert_called_with(reason="blah") + + def test_server_lock_multi_servers_with_reason(self): + server2 = compute_fakes.FakeServer.create_one_server( + methods=self.methods) + arglist = [ + self.server.id, server2.id, + '--reason', "choo..choo", + ] + verifylist = [ + ('reason', "choo..choo"), + ('server', [self.server.id, server2.id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.assertEqual(2, self.servers_mock.get.call_count) + self.server.lock.assert_called_with(reason="choo..choo") + self.assertEqual(2, self.server.lock.call_count) + class TestServerMigrate(TestServer): @@ -2528,6 +2955,32 @@ class TestServerMigrate(TestServer): self.assertNotCalled(self.servers_mock.live_migrate) self.assertIsNone(result) + def test_server_migrate_with_host_2_56(self): + # Tests that --host is allowed for a cold migration + # for microversion 2.56 and greater. + arglist = [ + '--host', 'fakehost', self.server.id, + ] + verifylist = [ + ('live', None), + ('live_migration', False), + ('host', 'fakehost'), + ('block_migration', False), + ('disk_overcommit', False), + ('wait', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.56') + + result = self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server.migrate.assert_called_with(host='fakehost') + self.assertNotCalled(self.servers_mock.live_migrate) + self.assertIsNone(result) + def test_server_migrate_with_block_migration(self): arglist = [ '--block-migration', self.server.id, @@ -2566,8 +3019,9 @@ class TestServerMigrate(TestServer): self.assertNotCalled(self.servers_mock.live_migrate) self.assertNotCalled(self.servers_mock.migrate) - def test_server_migrate_with_host(self): - # Tests that --host is not allowed for a cold migration. + def test_server_migrate_with_host_pre_2_56(self): + # Tests that --host is not allowed for a cold migration + # before microversion 2.56 (the test defaults to 2.1). arglist = [ '--host', 'fakehost', self.server.id, ] @@ -2585,9 +3039,10 @@ class TestServerMigrate(TestServer): parsed_args) # Make sure it's the error we expect. - self.assertIn("--live-migration must be specified if " - "--block-migration, --disk-overcommit or --host is " - "specified", six.text_type(ex)) + self.assertIn('--os-compute-api-version 2.56 or greater is required ' + 'to use --host without --live-migration.', + six.text_type(ex)) + self.servers_mock.get.assert_called_with(self.server.id) self.assertNotCalled(self.servers_mock.live_migrate) self.assertNotCalled(self.servers_mock.migrate) @@ -3603,13 +4058,18 @@ class TestServerResize(TestServer): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) + with mock.patch.object(self.cmd.log, 'warning') as mock_warning: + result = self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with(self.server.id) self.assertNotCalled(self.servers_mock.resize) self.servers_mock.confirm_resize.assert_called_with(self.server) self.assertNotCalled(self.servers_mock.revert_resize) self.assertIsNone(result) + # A warning should have been logged for using --confirm. + mock_warning.assert_called_once() + self.assertIn('The --confirm option has been deprecated.', + six.text_type(mock_warning.call_args[0][0])) def test_server_resize_revert(self): arglist = [ @@ -3623,13 +4083,18 @@ class TestServerResize(TestServer): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) + with mock.patch.object(self.cmd.log, 'warning') as mock_warning: + result = self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with(self.server.id) self.assertNotCalled(self.servers_mock.resize) self.assertNotCalled(self.servers_mock.confirm_resize) self.servers_mock.revert_resize.assert_called_with(self.server) self.assertIsNone(result) + # A warning should have been logged for using --revert. + mock_warning.assert_called_once() + self.assertIn('The --revert option has been deprecated.', + six.text_type(mock_warning.call_args[0][0])) @mock.patch.object(common_utils, 'wait_for_status', return_value=True) def test_server_resize_with_wait_ok(self, mock_wait_for_status): @@ -3710,6 +4175,74 @@ class TestServerResize(TestServer): ) +class TestServerResizeConfirm(TestServer): + + def setUp(self): + super(TestServerResizeConfirm, self).setUp() + + methods = { + 'confirm_resize': None, + } + self.server = compute_fakes.FakeServer.create_one_server( + methods=methods) + + # This is the return value for utils.find_resource() + self.servers_mock.get.return_value = self.server + + self.servers_mock.confirm_resize.return_value = None + + # Get the command object to test + self.cmd = server.ResizeConfirm(self.app, None) + + def test_resize_confirm(self): + arglist = [ + self.server.id, + ] + verifylist = [ + ('server', self.server.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server.confirm_resize.assert_called_with() + + +class TestServerResizeRevert(TestServer): + + def setUp(self): + super(TestServerResizeRevert, self).setUp() + + methods = { + 'revert_resize': None, + } + self.server = compute_fakes.FakeServer.create_one_server( + methods=methods) + + # This is the return value for utils.find_resource() + self.servers_mock.get.return_value = self.server + + self.servers_mock.revert_resize.return_value = None + + # Get the command object to test + self.cmd = server.ResizeRevert(self.app, None) + + def test_resize_revert(self): + arglist = [ + self.server.id, + ] + verifylist = [ + ('server', self.server.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server.revert_resize.assert_called_with() + + class TestServerRestore(TestServer): def setUp(self): @@ -4248,6 +4781,56 @@ class TestServerUnshelve(TestServer): def test_unshelve_multi_servers(self): self.run_method_with_servers('unshelve', 3) + def test_unshelve_server_with_specified_az(self): + server = compute_fakes.FakeServer.create_one_server() + arglist = [ + server.id, + '--availability-zone', "foo-az", + ] + verifylist = [ + ('availability_zone', "foo-az"), + ('server', [server.id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.77 or greater is required', str(ex)) + + +class TestServerUnshelveV277(TestServerUnshelve): + + def setUp(self): + super(TestServerUnshelveV277, self).setUp() + + self.server = compute_fakes.FakeServer.create_one_server( + methods=self.methods) + + # This is the return value for utils.find_resource() + self.servers_mock.get.return_value = self.server + + # Get the command object to test + self.cmd = server.UnshelveServer(self.app, None) + + def test_specified_az_to_unshelve_with_v277(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.77') + + arglist = [ + '--availability-zone', "foo-az", + self.server.id, + ] + verifylist = [ + ('availability_zone', "foo-az"), + ('server', [self.server.id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + self.servers_mock.get.assert_called_with(self.server.id) + self.server.unshelve.assert_called_with(availability_zone="foo-az") + class TestServerGeneral(TestServer): OLD = { diff --git a/openstackclient/tests/unit/compute/v2/test_server_backup.py b/openstackclient/tests/unit/compute/v2/test_server_backup.py index 9a481e0..24a9453 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_backup.py +++ b/openstackclient/tests/unit/compute/v2/test_server_backup.py @@ -13,6 +13,7 @@ import mock +from osc_lib.cli import format_columns from osc_lib import exceptions from osc_lib import utils as common_utils @@ -69,7 +70,7 @@ class TestServerBackupCreate(TestServerBackup): image['owner'], image['protected'], 'active', - common_utils.format_list(image.get('tags')), + format_columns.ListColumn(image.get('tags')), image['visibility'], ) return datalist @@ -134,7 +135,7 @@ class TestServerBackupCreate(TestServerBackup): ) self.assertEqual(self.image_columns(images[0]), columns) - self.assertEqual(self.image_data(images[0]), data) + self.assertItemEqual(self.image_data(images[0]), data) def test_server_backup_create_options(self): servers = self.setup_servers_mock(count=1) @@ -168,7 +169,7 @@ class TestServerBackupCreate(TestServerBackup): ) self.assertEqual(self.image_columns(images[0]), columns) - self.assertEqual(self.image_data(images[0]), data) + self.assertItemEqual(self.image_data(images[0]), data) @mock.patch.object(common_utils, 'wait_for_status', return_value=False) def test_server_backup_wait_fail(self, mock_wait_for_status): @@ -268,4 +269,4 @@ class TestServerBackupCreate(TestServerBackup): ) self.assertEqual(self.image_columns(images[0]), columns) - self.assertEqual(self.image_data(images[0]), data) + self.assertItemEqual(self.image_data(images[0]), data) diff --git a/openstackclient/tests/unit/compute/v2/test_server_image.py b/openstackclient/tests/unit/compute/v2/test_server_image.py index 47bd682..02e4312 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_image.py +++ b/openstackclient/tests/unit/compute/v2/test_server_image.py @@ -12,6 +12,7 @@ # import mock +from osc_lib.cli import format_columns from osc_lib import exceptions from osc_lib import utils as common_utils @@ -67,7 +68,7 @@ class TestServerImageCreate(TestServerImage): image['owner'], image['protected'], 'active', - common_utils.format_list(image.get('tags')), + format_columns.ListColumn(image.get('tags')), image['visibility'], ) return datalist @@ -129,7 +130,7 @@ class TestServerImageCreate(TestServerImage): ) self.assertEqual(self.image_columns(images[0]), columns) - self.assertEqual(self.image_data(images[0]), data) + self.assertItemEqual(self.image_data(images[0]), data) def test_server_image_create_options(self): servers = self.setup_servers_mock(count=1) @@ -157,7 +158,7 @@ class TestServerImageCreate(TestServerImage): ) self.assertEqual(self.image_columns(images[0]), columns) - self.assertEqual(self.image_data(images[0]), data) + self.assertItemEqual(self.image_data(images[0]), data) @mock.patch.object(common_utils, 'wait_for_status', return_value=False) def test_server_create_image_wait_fail(self, mock_wait_for_status): @@ -225,4 +226,4 @@ class TestServerImageCreate(TestServerImage): ) self.assertEqual(self.image_columns(images[0]), columns) - self.assertEqual(self.image_data(images[0]), data) + self.assertItemEqual(self.image_data(images[0]), data) diff --git a/openstackclient/tests/unit/compute/v2/test_service.py b/openstackclient/tests/unit/compute/v2/test_service.py index bd29912..0d663b2 100644 --- a/openstackclient/tests/unit/compute/v2/test_service.py +++ b/openstackclient/tests/unit/compute/v2/test_service.py @@ -17,6 +17,7 @@ import mock from mock import call from novaclient import api_versions from osc_lib import exceptions +import six from openstackclient.compute.v2 import service from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes @@ -344,7 +345,7 @@ class TestServiceSet(TestService): '2.11') result = self.cmd.take_action(parsed_args) self.service_mock.force_down.assert_called_once_with( - self.service.host, self.service.binary, force_down=False) + self.service.host, self.service.binary, False) self.assertNotCalled(self.service_mock.enable) self.assertNotCalled(self.service_mock.disable) self.assertIsNone(result) @@ -365,7 +366,7 @@ class TestServiceSet(TestService): '2.11') result = self.cmd.take_action(parsed_args) self.service_mock.force_down.assert_called_once_with( - self.service.host, self.service.binary, force_down=True) + self.service.host, self.service.binary, True) self.assertNotCalled(self.service_mock.enable) self.assertNotCalled(self.service_mock.disable) self.assertIsNone(result) @@ -390,7 +391,7 @@ class TestServiceSet(TestService): self.service_mock.enable.assert_called_once_with( self.service.host, self.service.binary) self.service_mock.force_down.assert_called_once_with( - self.service.host, self.service.binary, force_down=True) + self.service.host, self.service.binary, True) self.assertIsNone(result) def test_service_set_enable_and_state_down_with_exception(self): @@ -415,4 +416,99 @@ class TestServiceSet(TestService): self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) self.service_mock.force_down.assert_called_once_with( - self.service.host, self.service.binary, force_down=True) + self.service.host, self.service.binary, True) + + def test_service_set_2_53_disable_down(self): + # Tests disabling and forcing down a compute service with microversion + # 2.53 which requires looking up the service by host and binary. + arglist = [ + '--disable', + '--down', + self.service.host, + self.service.binary, + ] + verifylist = [ + ('disable', True), + ('down', True), + ('host', self.service.host), + ('service', self.service.binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.53') + service_id = '339478d0-0b95-4a94-be63-d5be05dfeb1c' + self.service_mock.list.return_value = [mock.Mock(id=service_id)] + result = self.cmd.take_action(parsed_args) + self.service_mock.disable.assert_called_once_with(service_id) + self.service_mock.force_down.assert_called_once_with(service_id, True) + self.assertIsNone(result) + + def test_service_set_2_53_disable_reason(self): + # Tests disabling with reason a compute service with microversion + # 2.53 which requires looking up the service by host and binary. + reason = 'earthquake' + arglist = [ + '--disable', + '--disable-reason', reason, + self.service.host, + self.service.binary, + ] + verifylist = [ + ('disable', True), + ('disable_reason', reason), + ('host', self.service.host), + ('service', self.service.binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.53') + service_id = '339478d0-0b95-4a94-be63-d5be05dfeb1c' + self.service_mock.list.return_value = [mock.Mock(id=service_id)] + result = self.cmd.take_action(parsed_args) + self.service_mock.disable_log_reason.assert_called_once_with( + service_id, reason) + self.assertIsNone(result) + + def test_service_set_2_53_enable_up(self): + # Tests enabling and bringing up a compute service with microversion + # 2.53 which requires looking up the service by host and binary. + arglist = [ + '--enable', + '--up', + self.service.host, + self.service.binary, + ] + verifylist = [ + ('enable', True), + ('up', True), + ('host', self.service.host), + ('service', self.service.binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.53') + service_id = '339478d0-0b95-4a94-be63-d5be05dfeb1c' + self.service_mock.list.return_value = [mock.Mock(id=service_id)] + result = self.cmd.take_action(parsed_args) + self.service_mock.enable.assert_called_once_with(service_id) + self.service_mock.force_down.assert_called_once_with(service_id, False) + self.assertIsNone(result) + + def test_service_set_find_service_by_host_and_binary_no_results(self): + # Tests that no compute services are found by host and binary. + self.service_mock.list.return_value = [] + ex = self.assertRaises(exceptions.CommandError, + self.cmd._find_service_by_host_and_binary, + self.service_mock, 'fake-host', 'nova-compute') + self.assertIn('Compute service for host "fake-host" and binary ' + '"nova-compute" not found.', six.text_type(ex)) + + def test_service_set_find_service_by_host_and_binary_many_results(self): + # Tests that more than one compute service is found by host and binary. + self.service_mock.list.return_value = [mock.Mock(), mock.Mock()] + ex = self.assertRaises(exceptions.CommandError, + self.cmd._find_service_by_host_and_binary, + self.service_mock, 'fake-host', 'nova-compute') + self.assertIn('Multiple compute services found for host "fake-host" ' + 'and binary "nova-compute". Unable to proceed.', + six.text_type(ex)) diff --git a/openstackclient/tests/unit/identity/v2_0/test_catalog.py b/openstackclient/tests/unit/identity/v2_0/test_catalog.py index 65af58a..362dec0 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_catalog.py +++ b/openstackclient/tests/unit/identity/v2_0/test_catalog.py @@ -71,17 +71,9 @@ class TestCatalogList(TestCatalog): datalist = (( 'supernova', 'compute', - 'one\n publicURL: https://public.one.example.com\n ' - 'internalURL: https://internal.one.example.com\n ' - 'adminURL: https://admin.one.example.com\n' - 'two\n publicURL: https://public.two.example.com\n ' - 'internalURL: https://internal.two.example.com\n ' - 'adminURL: https://admin.two.example.com\n' - '<none>\n publicURL: https://public.none.example.com\n ' - 'internalURL: https://internal.none.example.com\n ' - 'adminURL: https://admin.none.example.com\n', + catalog.EndpointsColumn(self.service_catalog['endpoints']), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) def test_catalog_list_with_endpoint_url(self): attr = { @@ -121,11 +113,9 @@ class TestCatalogList(TestCatalog): datalist = (( 'supernova', 'compute', - 'one\n publicURL: https://public.one.example.com\n' - 'two\n publicURL: https://public.two.example.com\n ' - 'internalURL: https://internal.two.example.com\n' + catalog.EndpointsColumn(service_catalog['endpoints']), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) class TestCatalogShow(TestCatalog): @@ -160,6 +150,18 @@ class TestCatalogShow(TestCatalog): collist = ('endpoints', 'id', 'name', 'type') self.assertEqual(collist, columns) datalist = ( + catalog.EndpointsColumn(self.service_catalog['endpoints']), + self.service_catalog.id, + 'supernova', + 'compute', + ) + self.assertItemEqual(datalist, data) + + +class TestFormatColumns(TestCatalog): + def test_endpoints_column_human_readabale(self): + col = catalog.EndpointsColumn(self.service_catalog['endpoints']) + self.assertEqual( 'one\n publicURL: https://public.one.example.com\n ' 'internalURL: https://internal.one.example.com\n ' 'adminURL: https://admin.one.example.com\n' @@ -169,8 +171,23 @@ class TestCatalogShow(TestCatalog): '<none>\n publicURL: https://public.none.example.com\n ' 'internalURL: https://internal.none.example.com\n ' 'adminURL: https://admin.none.example.com\n', - self.service_catalog.id, - 'supernova', - 'compute', - ) - self.assertEqual(datalist, data) + col.human_readable()) + + def test_endpoints_column_human_readable_with_partial_endpoint_urls(self): + endpoints = [ + { + 'region': 'one', + 'publicURL': 'https://public.one.example.com', + }, + { + 'region': 'two', + 'publicURL': 'https://public.two.example.com', + 'internalURL': 'https://internal.two.example.com', + }, + ] + col = catalog.EndpointsColumn(endpoints) + self.assertEqual( + 'one\n publicURL: https://public.one.example.com\n' + 'two\n publicURL: https://public.two.example.com\n ' + 'internalURL: https://internal.two.example.com\n', + col.human_readable()) diff --git a/openstackclient/tests/unit/identity/v2_0/test_project.py b/openstackclient/tests/unit/identity/v2_0/test_project.py index c726f2a..7af7b39 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_project.py +++ b/openstackclient/tests/unit/identity/v2_0/test_project.py @@ -16,6 +16,7 @@ import mock from keystoneauth1 import exceptions as ks_exc +from osc_lib.cli import format_columns from osc_lib import exceptions from osc_lib import utils @@ -640,9 +641,9 @@ class TestProjectShow(TestProject): True, self.fake_proj_show.id, self.fake_proj_show.name, - '', + format_columns.DictColumn({}), ) - self.assertEqual(datalist, data) + self.assertItemEqual(datalist, data) class TestProjectUnset(TestProject): diff --git a/openstackclient/tests/unit/identity/v2_0/test_role.py b/openstackclient/tests/unit/identity/v2_0/test_role.py index 684ce80..643d77f 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_role.py +++ b/openstackclient/tests/unit/identity/v2_0/test_role.py @@ -278,7 +278,7 @@ class TestRoleList(TestRole): # Get the command object to test self.cmd = role.ListRole(self.app, None) - def test_role_list_no_options(self): + def test_role_list(self): arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -299,137 +299,6 @@ class TestRoleList(TestRole): self.assertEqual(datalist, tuple(data)) -class TestUserRoleList(TestRole): - - columns = ( - 'ID', - 'Name', - 'Project', - 'User' - ) - - def setUp(self): - super(TestUserRoleList, self).setUp() - - self.projects_mock.get.return_value = self.fake_project - - self.users_mock.get.return_value = self.fake_user - - self.roles_mock.roles_for_user.return_value = [self.fake_role] - - # Get the command object to test - self.cmd = role.ListUserRole(self.app, None) - - def test_user_role_list_no_options_unscoped_token(self): - auth_ref = identity_fakes.fake_auth_ref( - identity_fakes.UNSCOPED_TOKEN, - fake_service=self.fake_service, - ) - self.ar_mock = mock.PropertyMock(return_value=auth_ref) - type(self.app.client_manager).auth_ref = self.ar_mock - - arglist = [] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # This argument combination should raise a CommandError - self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args, - ) - - def test_user_role_list_no_options_scoped_token(self): - arglist = [] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. - columns, data = self.cmd.take_action(parsed_args) - - self.roles_mock.roles_for_user.assert_called_with( - self.fake_user.id, - self.fake_project.id, - ) - - collist = ('ID', 'Name', 'Project', 'User') - self.assertEqual(collist, columns) - datalist = (( - self.fake_role.id, - self.fake_role.name, - self.fake_project.name, - self.fake_user.name, - ), ) - self.assertEqual(datalist, tuple(data)) - - def test_user_role_list_project_unscoped_token(self): - auth_ref = identity_fakes.fake_auth_ref( - identity_fakes.UNSCOPED_TOKEN, - fake_service=self.fake_service, - ) - self.ar_mock = mock.PropertyMock(return_value=auth_ref) - type(self.app.client_manager).auth_ref = self.ar_mock - - self.projects_mock.get.return_value = self.fake_project - arglist = [ - '--project', self.fake_project.name, - ] - verifylist = [ - ('project', self.fake_project.name), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. - columns, data = self.cmd.take_action(parsed_args) - - self.roles_mock.roles_for_user.assert_called_with( - self.fake_user.id, - self.fake_project.id, - ) - - self.assertEqual(columns, columns) - datalist = (( - self.fake_role.id, - self.fake_role.name, - self.fake_project.name, - self.fake_user.name, - ), ) - self.assertEqual(datalist, tuple(data)) - - def test_user_role_list_project_scoped_token(self): - self.projects_mock.get.return_value = self.fake_project - arglist = [ - '--project', self.fake_project.name, - ] - verifylist = [ - ('project', self.fake_project.name), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. - columns, data = self.cmd.take_action(parsed_args) - - self.roles_mock.roles_for_user.assert_called_with( - self.fake_user.id, - self.fake_project.id, - ) - - self.assertEqual(columns, columns) - datalist = (( - self.fake_role.id, - self.fake_role.name, - self.fake_project.name, - self.fake_user.name, - ), ) - self.assertEqual(datalist, tuple(data)) - - class TestRoleRemove(TestRole): def setUp(self): diff --git a/openstackclient/tests/unit/identity/v2_0/test_service.py b/openstackclient/tests/unit/identity/v2_0/test_service.py index 1948bf4..6c4374e 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_service.py +++ b/openstackclient/tests/unit/identity/v2_0/test_service.py @@ -55,43 +55,14 @@ class TestServiceCreate(TestService): # Get the command object to test self.cmd = service.CreateService(self.app, None) - def test_service_create_with_type_positional(self): + def test_service_create(self): arglist = [ self.fake_service_c.type, ] verifylist = [ - ('type_or_name', self.fake_service_c.type), - ('type', None), - ('description', None), - ('name', None), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # In base command class ShowOne in cliff, abstract method take_action() - # returns a two-part tuple with a tuple of column names and a tuple of - # data to be shown. - columns, data = self.cmd.take_action(parsed_args) - - # ServiceManager.create(name, service_type, description) - self.services_mock.create.assert_called_with( - None, - self.fake_service_c.type, - None, - ) - - self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) - - def test_service_create_with_type_option(self): - arglist = [ - '--type', self.fake_service_c.type, - self.fake_service_c.name, - ] - verifylist = [ - ('type_or_name', self.fake_service_c.name), ('type', self.fake_service_c.type), - ('description', None), ('name', None), + ('description', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -102,7 +73,7 @@ class TestServiceCreate(TestService): # ServiceManager.create(name, service_type, description) self.services_mock.create.assert_called_with( - self.fake_service_c.name, + None, self.fake_service_c.type, None, ) @@ -116,10 +87,9 @@ class TestServiceCreate(TestService): self.fake_service_c.type, ] verifylist = [ - ('type_or_name', self.fake_service_c.type), - ('type', None), - ('description', None), + ('type', self.fake_service_c.type), ('name', self.fake_service_c.name), + ('description', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -145,10 +115,9 @@ class TestServiceCreate(TestService): self.fake_service_c.type, ] verifylist = [ - ('type_or_name', self.fake_service_c.type), - ('type', None), - ('description', self.fake_service_c.description), + ('type', self.fake_service_c.type), ('name', self.fake_service_c.name), + ('description', self.fake_service_c.description), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/unit/identity/v2_0/test_user.py b/openstackclient/tests/unit/identity/v2_0/test_user.py index a8b9497..0a0d4b3 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_user.py +++ b/openstackclient/tests/unit/identity/v2_0/test_user.py @@ -482,7 +482,7 @@ class TestUserList(TestUser): self.users_mock.list.assert_called_with(tenant_id=None) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, tuple(data)) + self.assertListItemEqual(self.datalist, tuple(data)) def test_user_list_project(self): arglist = [ @@ -502,7 +502,7 @@ class TestUserList(TestUser): self.users_mock.list.assert_called_with(tenant_id=project_id) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, tuple(data)) + self.assertListItemEqual(self.datalist, tuple(data)) def test_user_list_long(self): arglist = [ @@ -525,11 +525,13 @@ class TestUserList(TestUser): datalist = (( self.fake_user_l.id, self.fake_user_l.name, - self.fake_project_l.name, + user.ProjectColumn( + self.fake_project_l.id, + {self.fake_project_l.id: self.fake_project_l}), self.fake_user_l.email, True, ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) class TestUserSet(TestUser): @@ -817,4 +819,4 @@ class TestUserShow(TestUser): self.fake_user.name, self.fake_project.id, ) - self.assertEqual(datalist, data) + self.assertItemEqual(datalist, data) diff --git a/openstackclient/tests/unit/identity/v3/fakes.py b/openstackclient/tests/unit/identity/v3/fakes.py index e5727a6..e9ff068 100644 --- a/openstackclient/tests/unit/identity/v3/fakes.py +++ b/openstackclient/tests/unit/identity/v3/fakes.py @@ -20,6 +20,7 @@ import uuid from keystoneauth1 import access from keystoneauth1 import fixture import mock +from osc_lib.cli import format_columns from openstackclient.tests.unit import fakes from openstackclient.tests.unit import utils @@ -304,7 +305,7 @@ TOKEN_WITH_DOMAIN_ID = { idp_id = 'test_idp' idp_description = 'super exciting IdP description' idp_remote_ids = ['entity1', 'entity2'] -formatted_idp_remote_ids = 'entity1, entity2' +formatted_idp_remote_ids = format_columns.ListColumn(idp_remote_ids) IDENTITY_PROVIDER = { 'id': idp_id, diff --git a/openstackclient/tests/unit/identity/v3/test_catalog.py b/openstackclient/tests/unit/identity/v3/test_catalog.py index 53008e8..ba076db 100644 --- a/openstackclient/tests/unit/identity/v3/test_catalog.py +++ b/openstackclient/tests/unit/identity/v3/test_catalog.py @@ -91,12 +91,9 @@ class TestCatalogList(TestCatalog): datalist = (( 'supernova', 'compute', - 'onlyone\n public: https://public.example.com\n' - 'onlyone\n admin: https://admin.example.com\n' - '<none>\n internal: https://internal.example.com\n' - '<none>\n none: https://none.example.com\n', + catalog.EndpointsColumn(self.fake_service['endpoints']), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) class TestCatalogShow(TestCatalog): @@ -131,12 +128,20 @@ class TestCatalogShow(TestCatalog): collist = ('endpoints', 'id', 'name', 'type') self.assertEqual(collist, columns) datalist = ( - 'onlyone\n public: https://public.example.com\nonlyone\n' - ' admin: https://admin.example.com\n' - '<none>\n internal: https://internal.example.com\n' - '<none>\n none: https://none.example.com\n', + catalog.EndpointsColumn(self.fake_service['endpoints']), 'qwertyuiop', 'supernova', 'compute', ) - self.assertEqual(datalist, data) + self.assertItemEqual(datalist, data) + + +class TestFormatColumns(TestCatalog): + def test_endpoints_column_human_readabale(self): + col = catalog.EndpointsColumn(self.fake_service['endpoints']) + self.assertEqual( + 'onlyone\n public: https://public.example.com\n' + 'onlyone\n admin: https://admin.example.com\n' + '<none>\n internal: https://internal.example.com\n' + '<none>\n none: https://none.example.com\n', + col.human_readable()) diff --git a/openstackclient/tests/unit/identity/v3/test_identity_provider.py b/openstackclient/tests/unit/identity/v3/test_identity_provider.py index dc82ab7..0163c6c 100644 --- a/openstackclient/tests/unit/identity/v3/test_identity_provider.py +++ b/openstackclient/tests/unit/identity/v3/test_identity_provider.py @@ -90,7 +90,7 @@ class TestIdentityProviderCreate(TestIdentityProvider): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_create_identity_provider_description(self): arglist = [ @@ -118,7 +118,7 @@ class TestIdentityProviderCreate(TestIdentityProvider): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_create_identity_provider_remote_id(self): arglist = [ @@ -146,7 +146,7 @@ class TestIdentityProviderCreate(TestIdentityProvider): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_create_identity_provider_remote_ids_multiple(self): arglist = [ @@ -175,7 +175,7 @@ class TestIdentityProviderCreate(TestIdentityProvider): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_create_identity_provider_remote_ids_file(self): arglist = [ @@ -208,7 +208,7 @@ class TestIdentityProviderCreate(TestIdentityProvider): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_create_identity_provider_disabled(self): @@ -251,7 +251,7 @@ class TestIdentityProviderCreate(TestIdentityProvider): identity_fakes.idp_id, identity_fakes.formatted_idp_remote_ids ) - self.assertEqual(datalist, data) + self.assertItemEqual(datalist, data) def test_create_identity_provider_domain_name(self): arglist = [ @@ -279,7 +279,7 @@ class TestIdentityProviderCreate(TestIdentityProvider): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_create_identity_provider_domain_id(self): arglist = [ @@ -307,7 +307,7 @@ class TestIdentityProviderCreate(TestIdentityProvider): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) class TestIdentityProviderDelete(TestIdentityProvider): @@ -383,7 +383,7 @@ class TestIdentityProviderList(TestIdentityProvider): identity_fakes.domain_id, identity_fakes.idp_description, ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) class TestIdentityProviderSet(TestIdentityProvider): @@ -668,4 +668,4 @@ class TestIdentityProviderShow(TestIdentityProvider): identity_fakes.idp_id, identity_fakes.formatted_idp_remote_ids ) - self.assertEqual(datalist, data) + self.assertItemEqual(datalist, data) diff --git a/openstackclient/tests/unit/identity/v3/test_role.py b/openstackclient/tests/unit/identity/v3/test_role.py index 281d530..99f3a2d 100644 --- a/openstackclient/tests/unit/identity/v3/test_role.py +++ b/openstackclient/tests/unit/identity/v3/test_role.py @@ -508,21 +508,6 @@ class TestRoleList(TestRole): copy.deepcopy(identity_fakes.DOMAIN), loaded=True, ) - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) - self.users_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.USER), - loaded=True, - ) - self.groups_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.GROUP), - loaded=True, - ) # Get the command object to test self.cmd = role.ListRole(self.app, None) @@ -542,212 +527,6 @@ class TestRoleList(TestRole): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) - def test_user_list_inherited(self): - arglist = [ - '--user', identity_fakes.user_id, - '--inherited', - ] - verifylist = [ - ('user', identity_fakes.user_id), - ('inherited', True), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. - columns, data = self.cmd.take_action(parsed_args) - - # Set expected values - kwargs = { - 'domain': 'default', - 'user': self.users_mock.get(), - 'os_inherit_extension_inherited': True, - } - # RoleManager.list(user=, group=, domain=, project=, **kwargs) - self.roles_mock.list.assert_called_with( - **kwargs - ) - - self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, tuple(data)) - - def test_user_list_user(self): - arglist = [ - '--user', identity_fakes.user_id, - ] - verifylist = [ - ('user', identity_fakes.user_id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. - columns, data = self.cmd.take_action(parsed_args) - - # Set expected values - kwargs = { - 'domain': 'default', - 'user': self.users_mock.get(), - 'os_inherit_extension_inherited': False - } - # RoleManager.list(user=, group=, domain=, project=, **kwargs) - self.roles_mock.list.assert_called_with( - **kwargs - ) - - self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, tuple(data)) - - def test_role_list_domain_user(self): - arglist = [ - '--domain', identity_fakes.domain_name, - '--user', identity_fakes.user_id, - ] - verifylist = [ - ('domain', identity_fakes.domain_name), - ('user', identity_fakes.user_id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. - columns, data = self.cmd.take_action(parsed_args) - - # Set expected values - kwargs = { - 'domain': self.domains_mock.get(), - 'user': self.users_mock.get(), - 'os_inherit_extension_inherited': False - } - # RoleManager.list(user=, group=, domain=, project=, **kwargs) - self.roles_mock.list.assert_called_with( - **kwargs - ) - - collist = ('ID', 'Name', 'Domain', 'User') - self.assertEqual(collist, columns) - datalist = (( - identity_fakes.role_id, - identity_fakes.role_name, - identity_fakes.domain_name, - identity_fakes.user_name, - ), ) - self.assertEqual(datalist, tuple(data)) - - def test_role_list_domain_group(self): - arglist = [ - '--domain', identity_fakes.domain_name, - '--group', identity_fakes.group_id, - ] - verifylist = [ - ('domain', identity_fakes.domain_name), - ('group', identity_fakes.group_id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. - columns, data = self.cmd.take_action(parsed_args) - - # Set expected values - kwargs = { - 'domain': self.domains_mock.get(), - 'group': self.groups_mock.get(), - 'os_inherit_extension_inherited': False - } - # RoleManager.list(user=, group=, domain=, project=, **kwargs) - self.roles_mock.list.assert_called_with( - **kwargs - ) - - collist = ('ID', 'Name', 'Domain', 'Group') - self.assertEqual(collist, columns) - datalist = (( - identity_fakes.role_id, - identity_fakes.role_name, - identity_fakes.domain_name, - identity_fakes.group_name, - ), ) - self.assertEqual(datalist, tuple(data)) - - def test_role_list_project_user(self): - arglist = [ - '--project', identity_fakes.project_name, - '--user', identity_fakes.user_id, - ] - verifylist = [ - ('project', identity_fakes.project_name), - ('user', identity_fakes.user_id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. - columns, data = self.cmd.take_action(parsed_args) - - # Set expected values - kwargs = { - 'project': self.projects_mock.get(), - 'user': self.users_mock.get(), - 'os_inherit_extension_inherited': False - } - # RoleManager.list(user=, group=, domain=, project=, **kwargs) - self.roles_mock.list.assert_called_with( - **kwargs - ) - - collist = ('ID', 'Name', 'Project', 'User') - self.assertEqual(collist, columns) - datalist = (( - identity_fakes.role_id, - identity_fakes.role_name, - identity_fakes.project_name, - identity_fakes.user_name, - ), ) - self.assertEqual(datalist, tuple(data)) - - def test_role_list_project_group(self): - arglist = [ - '--project', identity_fakes.project_name, - '--group', identity_fakes.group_id, - ] - verifylist = [ - ('project', identity_fakes.project_name), - ('group', identity_fakes.group_id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. - columns, data = self.cmd.take_action(parsed_args) - - # Set expected values - kwargs = { - 'project': self.projects_mock.get(), - 'group': self.groups_mock.get(), - 'os_inherit_extension_inherited': False - } - # RoleManager.list(user=, group=, domain=, project=, **kwargs) - self.roles_mock.list.assert_called_with( - **kwargs - ) - - collist = ('ID', 'Name', 'Project', 'Group') - self.assertEqual(collist, columns) - datalist = (( - identity_fakes.role_id, - identity_fakes.role_name, - identity_fakes.project_name, - identity_fakes.group_name, - ), ) - self.assertEqual(datalist, tuple(data)) - def test_role_list_domain_role(self): self.roles_mock.list.return_value = [ fakes.FakeResource( @@ -787,17 +566,6 @@ class TestRoleList(TestRole): ), ) self.assertEqual(datalist, tuple(data)) - def test_role_list_group_with_error(self): - arglist = [ - '--group', identity_fakes.group_id, - ] - verifylist = [ - ('group', identity_fakes.group_id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, - self.cmd.take_action, parsed_args) - class TestRoleRemove(TestRole): diff --git a/openstackclient/tests/unit/image/v1/test_image.py b/openstackclient/tests/unit/image/v1/test_image.py index 6babd4f..0997d76 100644 --- a/openstackclient/tests/unit/image/v1/test_image.py +++ b/openstackclient/tests/unit/image/v1/test_image.py @@ -16,8 +16,9 @@ import copy import mock + +from osc_lib.cli import format_columns from osc_lib import exceptions -from osc_lib import utils from openstackclient.image.v1 import image from openstackclient.tests.unit import fakes @@ -58,7 +59,7 @@ class TestImageCreate(TestImage): new_image.min_ram, new_image.name, new_image.owner, - utils.format_dict(new_image.properties), + format_columns.DictColumn(new_image.properties), new_image.protected, ) @@ -106,7 +107,7 @@ class TestImageCreate(TestImage): self.assertEqual(self.images_mock.update.call_args_list, []) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_image_reserve_options(self): mock_exception = { @@ -160,7 +161,7 @@ class TestImageCreate(TestImage): self.assertEqual(self.images_mock.update.call_args_list, []) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) @mock.patch('openstackclient.image.v1.image.io.open', name='Open') def test_image_create_file(self, mock_open): @@ -224,7 +225,7 @@ class TestImageCreate(TestImage): self.assertEqual(self.images_mock.update.call_args_list, []) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) class TestImageDelete(TestImage): @@ -410,12 +411,13 @@ class TestImageList(TestImage): '', '', '', - 'public', + image.VisibilityColumn(True), False, self._image.owner, - "Alpha='a', Beta='b', Gamma='g'", + format_columns.DictColumn( + {'Alpha': 'a', 'Beta': 'b', 'Gamma': 'g'}), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) @mock.patch('osc_lib.api.utils.simple_filter') def test_image_list_property_option(self, sf_mock): @@ -742,7 +744,7 @@ class TestImageShow(TestImage): _image.min_ram, _image.name, _image.owner, - utils.format_dict(_image.properties), + format_columns.DictColumn(_image.properties), _image.protected, _image.size, ) @@ -773,7 +775,7 @@ class TestImageShow(TestImage): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_image_show_human_readable(self): arglist = [ diff --git a/openstackclient/tests/unit/image/v2/fakes.py b/openstackclient/tests/unit/image/v2/fakes.py index d82d811..f69a2bc 100644 --- a/openstackclient/tests/unit/image/v2/fakes.py +++ b/openstackclient/tests/unit/image/v2/fakes.py @@ -19,7 +19,7 @@ import uuid from glanceclient.v2 import schemas import mock -from osc_lib import utils as common_utils +from osc_lib.cli import format_columns import warlock from openstackclient.tests.unit import fakes @@ -48,7 +48,7 @@ IMAGE_columns = tuple(sorted(IMAGE)) IMAGE_data = tuple((IMAGE[x] for x in sorted(IMAGE))) IMAGE_SHOW = copy.copy(IMAGE) -IMAGE_SHOW['tags'] = '' +IMAGE_SHOW['tags'] = format_columns.ListColumn(IMAGE_SHOW['tags']) IMAGE_SHOW_data = tuple((IMAGE_SHOW[x] for x in sorted(IMAGE_SHOW))) # Just enough v2 schema to do some testing @@ -281,7 +281,7 @@ class FakeImage(object): if x == 'tags': # The 'tags' should be format_list data_list.append( - common_utils.format_list(getattr(image, x))) + format_columns.ListColumn(getattr(image, x))) else: data_list.append(getattr(image, x)) return tuple(data_list) diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index 4cc8f44..748a61a 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -18,8 +18,8 @@ import copy from glanceclient.common import utils as glanceclient_utils from glanceclient.v2 import schemas import mock +from osc_lib.cli import format_columns from osc_lib import exceptions -from osc_lib import utils as common_utils import warlock from openstackclient.image.v2 import image @@ -116,7 +116,7 @@ class TestImageCreate(TestImage): self.assertEqual( image_fakes.FakeImage.get_image_columns(self.new_image), columns) - self.assertEqual( + self.assertItemEqual( image_fakes.FakeImage.get_image_data(self.new_image), data) @@ -184,44 +184,10 @@ class TestImageCreate(TestImage): self.assertEqual( image_fakes.FakeImage.get_image_columns(self.new_image), columns) - self.assertEqual( + self.assertItemEqual( image_fakes.FakeImage.get_image_data(self.new_image), data) - def test_image_create_with_unexist_owner(self): - self.project_mock.get.side_effect = exceptions.NotFound(None) - self.project_mock.find.side_effect = exceptions.NotFound(None) - - arglist = [ - '--container-format', 'ovf', - '--disk-format', 'ami', - '--min-disk', '10', - '--min-ram', '4', - '--owner', 'unexist_owner', - '--protected', - '--private', - image_fakes.image_name, - ] - verifylist = [ - ('container_format', 'ovf'), - ('disk_format', 'ami'), - ('min_disk', 10), - ('min_ram', 4), - ('owner', 'unexist_owner'), - ('protected', True), - ('unprotected', False), - ('public', False), - ('private', True), - ('name', image_fakes.image_name), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args, - ) - def test_image_create_with_unexist_project(self): self.project_mock.get.side_effect = exceptions.NotFound(None) self.project_mock.find.side_effect = exceptions.NotFound(None) @@ -318,7 +284,7 @@ class TestImageCreate(TestImage): self.assertEqual( image_fakes.FakeImage.get_image_columns(self.new_image), columns) - self.assertEqual( + self.assertItemEqual( image_fakes.FakeImage.get_image_data(self.new_image), data) @@ -542,7 +508,7 @@ class TestImageList(TestImage): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, tuple(data)) + self.assertListItemEqual(self.datalist, tuple(data)) def test_image_list_public_option(self): arglist = [ @@ -567,7 +533,7 @@ class TestImageList(TestImage): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, tuple(data)) + self.assertListItemEqual(self.datalist, tuple(data)) def test_image_list_private_option(self): arglist = [ @@ -592,7 +558,7 @@ class TestImageList(TestImage): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, tuple(data)) + self.assertListItemEqual(self.datalist, tuple(data)) def test_image_list_community_option(self): arglist = [ @@ -642,7 +608,7 @@ class TestImageList(TestImage): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, tuple(data)) + self.assertListItemEqual(self.datalist, tuple(data)) def test_image_list_shared_member_status_option(self): arglist = [ @@ -730,9 +696,9 @@ class TestImageList(TestImage): self._image.visibility, self._image.protected, self._image.owner, - common_utils.format_list(self._image.tags), + format_columns.ListColumn(self._image.tags), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) @mock.patch('osc_lib.api.utils.simple_filter') def test_image_list_property_option(self, sf_mock): @@ -761,7 +727,7 @@ class TestImageList(TestImage): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, tuple(data)) + self.assertListItemEqual(self.datalist, tuple(data)) @mock.patch('osc_lib.utils.sort_items') def test_image_list_sort_option(self, si_mock): @@ -784,7 +750,7 @@ class TestImageList(TestImage): str, ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, tuple(data)) + self.assertListItemEqual(self.datalist, tuple(data)) def test_image_list_limit_option(self): ret_limit = 1 @@ -1146,24 +1112,6 @@ class TestImageSet(TestImage): image_fakes.image_id, **kwargs) self.assertIsNone(result) - def test_image_set_with_unexist_owner(self): - self.project_mock.get.side_effect = exceptions.NotFound(None) - self.project_mock.find.side_effect = exceptions.NotFound(None) - - arglist = [ - '--owner', 'unexist_owner', - image_fakes.image_id, - ] - verifylist = [ - ('owner', 'unexist_owner'), - ('image', image_fakes.image_id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, parsed_args) - def test_image_set_with_unexist_project(self): self.project_mock.get.side_effect = exceptions.NotFound(None) self.project_mock.find.side_effect = exceptions.NotFound(None) @@ -1512,7 +1460,7 @@ class TestImageShow(TestImage): ) self.assertEqual(image_fakes.IMAGE_columns, columns) - self.assertEqual(image_fakes.IMAGE_SHOW_data, data) + self.assertItemEqual(image_fakes.IMAGE_SHOW_data, data) def test_image_show_human_readable(self): self.images_mock.get.return_value = self.new_image diff --git a/openstackclient/tests/unit/integ/cli/test_project.py b/openstackclient/tests/unit/integ/cli/test_project.py index 6a7c6d1..4e707a3 100644 --- a/openstackclient/tests/unit/integ/cli/test_project.py +++ b/openstackclient/tests/unit/integ/cli/test_project.py @@ -36,10 +36,10 @@ class TestIntegV2ProjectID(test_base.TestInteg): def test_project_id_env(self): _shell = shell.OpenStackShell() - _shell.run("configuration show".split()) + _shell.run("extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check discovery request self.assertEqual( @@ -57,10 +57,10 @@ class TestIntegV2ProjectID(test_base.TestInteg): def test_project_id_arg(self): _shell = shell.OpenStackShell() - _shell.run("--os-project-id wsx configuration show".split()) + _shell.run("--os-project-id wsx extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check discovery request self.assertEqual( @@ -94,10 +94,10 @@ class TestIntegV2ProjectName(test_base.TestInteg): def test_project_name_env(self): _shell = shell.OpenStackShell() - _shell.run("configuration show".split()) + _shell.run("extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check discovery request self.assertEqual( @@ -115,10 +115,10 @@ class TestIntegV2ProjectName(test_base.TestInteg): def test_project_name_arg(self): _shell = shell.OpenStackShell() - _shell.run("--os-project-name qaz configuration show".split()) + _shell.run("--os-project-name qaz extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check discovery request self.assertEqual( @@ -154,10 +154,10 @@ class TestIntegV3ProjectID(test_base.TestInteg): def test_project_id_env(self): _shell = shell.OpenStackShell() - _shell.run("configuration show".split()) + _shell.run("extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check discovery request self.assertEqual( @@ -173,10 +173,10 @@ class TestIntegV3ProjectID(test_base.TestInteg): def test_project_id_arg(self): _shell = shell.OpenStackShell() - _shell.run("--os-project-id wsx configuration show".split()) + _shell.run("--os-project-id wsx extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check discovery request self.assertEqual( @@ -210,10 +210,10 @@ class TestIntegV3ProjectName(test_base.TestInteg): def test_project_name_env(self): _shell = shell.OpenStackShell() - _shell.run("configuration show".split()) + _shell.run("extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check discovery request self.assertEqual( @@ -234,10 +234,10 @@ class TestIntegV3ProjectName(test_base.TestInteg): def test_project_name_arg(self): _shell = shell.OpenStackShell() - _shell.run("--os-project-name wsx configuration show".split()) + _shell.run("--os-project-name wsx extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check discovery request self.assertEqual( diff --git a/openstackclient/tests/unit/integ/cli/test_shell.py b/openstackclient/tests/unit/integ/cli/test_shell.py index 200f9b1..2598517 100644 --- a/openstackclient/tests/unit/integ/cli/test_shell.py +++ b/openstackclient/tests/unit/integ/cli/test_shell.py @@ -31,6 +31,51 @@ except ImportError: CONFIG_MOCK_BASE = "os_client_config.config" +class TestIntegShellCliNoAuth(test_base.TestInteg): + + def setUp(self): + super(TestIntegShellCliNoAuth, self).setUp() + env = {} + self.useFixture(osc_lib_utils.EnvFixture(copy.deepcopy(env))) + + # self.token = test_base.make_v2_token(self.requests_mock) + + def test_shell_args_no_options(self): + _shell = shell.OpenStackShell() + _shell.run("configuration show".split()) + + # Check general calls + self.assertEqual(len(self.requests_mock.request_history), 0) + + def test_shell_args_verify(self): + _shell = shell.OpenStackShell() + _shell.run("--verify configuration show".split()) + + # Check general calls + self.assertEqual(len(self.requests_mock.request_history), 0) + + def test_shell_args_insecure(self): + _shell = shell.OpenStackShell() + _shell.run("--insecure configuration show".split()) + + # Check general calls + self.assertEqual(len(self.requests_mock.request_history), 0) + + def test_shell_args_cacert(self): + _shell = shell.OpenStackShell() + _shell.run("--os-cacert xyzpdq configuration show".split()) + + # Check general calls + self.assertEqual(len(self.requests_mock.request_history), 0) + + def test_shell_args_cacert_insecure(self): + _shell = shell.OpenStackShell() + _shell.run("--os-cacert xyzpdq --insecure configuration show".split()) + + # Check general calls + self.assertEqual(len(self.requests_mock.request_history), 0) + + class TestIntegShellCliV2(test_base.TestInteg): def setUp(self): @@ -48,10 +93,10 @@ class TestIntegShellCliV2(test_base.TestInteg): def test_shell_args_no_options(self): _shell = shell.OpenStackShell() - _shell.run("configuration show".split()) + _shell.run("extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check discovery request self.assertEqual( @@ -77,30 +122,30 @@ class TestIntegShellCliV2(test_base.TestInteg): def test_shell_args_verify(self): _shell = shell.OpenStackShell() - _shell.run("--verify configuration show".split()) + _shell.run("--verify extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check verify self.assertTrue(self.requests_mock.request_history[0].verify) def test_shell_args_insecure(self): _shell = shell.OpenStackShell() - _shell.run("--insecure configuration show".split()) + _shell.run("--insecure extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check verify self.assertFalse(self.requests_mock.request_history[0].verify) def test_shell_args_cacert(self): _shell = shell.OpenStackShell() - _shell.run("--os-cacert xyzpdq configuration show".split()) + _shell.run("--os-cacert xyzpdq extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check verify self.assertEqual( @@ -110,10 +155,10 @@ class TestIntegShellCliV2(test_base.TestInteg): def test_shell_args_cacert_insecure(self): _shell = shell.OpenStackShell() - _shell.run("--os-cacert xyzpdq --insecure configuration show".split()) + _shell.run("--os-cacert xyzpdq --insecure extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check verify self.assertFalse(self.requests_mock.request_history[0].verify) @@ -138,10 +183,10 @@ class TestIntegShellCliV2Ignore(test_base.TestInteg): def test_shell_args_ignore_v3(self): _shell = shell.OpenStackShell() - _shell.run("configuration show".split()) + _shell.run("extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check discovery request self.assertEqual( @@ -184,10 +229,10 @@ class TestIntegShellCliV3(test_base.TestInteg): def test_shell_args_no_options(self): _shell = shell.OpenStackShell() - _shell.run("configuration show".split()) + _shell.run("extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check discovery request self.assertEqual( @@ -213,30 +258,30 @@ class TestIntegShellCliV3(test_base.TestInteg): def test_shell_args_verify(self): _shell = shell.OpenStackShell() - _shell.run("--verify configuration show".split()) + _shell.run("--verify extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check verify self.assertTrue(self.requests_mock.request_history[0].verify) def test_shell_args_insecure(self): _shell = shell.OpenStackShell() - _shell.run("--insecure configuration show".split()) + _shell.run("--insecure extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check verify self.assertFalse(self.requests_mock.request_history[0].verify) def test_shell_args_cacert(self): _shell = shell.OpenStackShell() - _shell.run("--os-cacert xyzpdq configuration show".split()) + _shell.run("--os-cacert xyzpdq extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check verify self.assertEqual( @@ -248,10 +293,10 @@ class TestIntegShellCliV3(test_base.TestInteg): # This test verifies the outcome of bug 1447784 # https://bugs.launchpad.net/python-openstackclient/+bug/1447784 _shell = shell.OpenStackShell() - _shell.run("--os-cacert xyzpdq --insecure configuration show".split()) + _shell.run("--os-cacert xyzpdq --insecure extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check verify self.assertFalse(self.requests_mock.request_history[0].verify) @@ -276,10 +321,10 @@ class TestIntegShellCliV3Prompt(test_base.TestInteg): def test_shell_callback(self, mock_prompt): mock_prompt.return_value = "qaz" _shell = shell.OpenStackShell() - _shell.run("configuration show".split()) + _shell.run("extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check password callback set correctly self.assertEqual( @@ -332,11 +377,11 @@ class TestIntegShellCliPrecedence(test_base.TestInteg): _shell = shell.OpenStackShell() _shell.run( "--os-username zarquon --os-password qaz " - "configuration show".split(), + "extension list".split(), ) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check discovery request self.assertEqual( @@ -431,11 +476,11 @@ class TestIntegShellCliPrecedenceOCC(test_base.TestInteg): print("CONFIG_MOCK_BASE=%s" % CONFIG_MOCK_BASE) _shell = shell.OpenStackShell() _shell.run( - "--os-password qaz configuration show".split(), + "--os-password qaz extension list".split(), ) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check discovery request self.assertEqual( @@ -504,11 +549,11 @@ class TestIntegShellCliPrecedenceOCC(test_base.TestInteg): _shell = shell.OpenStackShell() _shell.run( "--os-username zarquon --os-password qaz " - "--os-project-domain-id 5678 configuration show".split(), + "--os-project-domain-id 5678 extension list".split(), ) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check discovery request self.assertEqual( diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 100ea2b..35f0b1a 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -1305,7 +1305,7 @@ class FakeSecurityGroupRule(object): 'id': 'security-group-rule-id-' + uuid.uuid4().hex, 'port_range_max': None, 'port_range_min': None, - 'protocol': 'tcp', + 'protocol': None, 'remote_group_id': None, 'remote_ip_prefix': '0.0.0.0/0', 'security_group_id': 'security-group-id-' + uuid.uuid4().hex, @@ -1344,7 +1344,7 @@ class FakeSecurityGroupRule(object): @staticmethod def get_security_group_rules(security_group_rules=None, count=2): - """Get an iterable Mock object with a list of faked security group rules. + """Get an iterable Mock with a list of faked security group rules. If security group rules list is provided, then initialize the Mock object with the list. Otherwise create one. @@ -1816,3 +1816,80 @@ class FakeQuota(object): info=copy.deepcopy(quota_attrs), loaded=True) return quota + + +class FakeFloatingIPPortForwarding(object): + """"Fake one or more Port forwarding""" + + @staticmethod + def create_one_port_forwarding(attrs=None): + """Create a fake Port Forwarding. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with name, id, etc. + """ + attrs = attrs or {} + floatingip_id = ( + attrs.get('floatingip_id') or'floating-ip-id-' + uuid.uuid4().hex + ) + # Set default attributes. + port_forwarding_attrs = { + 'id': uuid.uuid4().hex, + 'floatingip_id': floatingip_id, + 'internal_port_id': 'internal-port-id-' + uuid.uuid4().hex, + 'internal_ip_address': '192.168.1.2', + 'internal_port': randint(1, 65535), + 'external_port': randint(1, 65535), + 'protocol': 'tcp', + } + + # Overwrite default attributes. + port_forwarding_attrs.update(attrs) + + port_forwarding = fakes.FakeResource( + info=copy.deepcopy(port_forwarding_attrs), + loaded=True + ) + return port_forwarding + + @staticmethod + def create_port_forwardings(attrs=None, count=2): + """Create multiple fake Port Forwarding. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of Port Forwarding rule to fake + :return: + A list of FakeResource objects faking the Port Forwardings + """ + port_forwardings = [] + for i in range(0, count): + port_forwardings.append( + FakeFloatingIPPortForwarding.create_one_port_forwarding(attrs) + ) + return port_forwardings + + @staticmethod + def get_port_forwardings(port_forwardings=None, count=2): + """Get a list of faked Port Forwardings. + + If port forwardings list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List port forwardings: + A list of FakeResource objects faking port forwardings + :param int count: + The number of Port Forwardings to fake + :return: + An iterable Mock object with side_effect set to a list of faked + Port Forwardings + """ + if port_forwardings is None: + port_forwardings = ( + FakeFloatingIPPortForwarding.create_port_forwardings(count) + ) + + return mock.Mock(side_effect=port_forwardings) diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py b/openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py new file mode 100644 index 0000000..b51158b --- /dev/null +++ b/openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py @@ -0,0 +1,502 @@ +# Copyright (c) 2018 China Telecom Corporation +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock +from mock import call + +from osc_lib import exceptions + +from openstackclient.network.v2 import floating_ip_port_forwarding +from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes_v2 +from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit import utils as tests_utils + + +class TestFloatingIPPortForwarding(network_fakes.TestNetworkV2): + + def setUp(self): + super(TestFloatingIPPortForwarding, self).setUp() + self.network = self.app.client_manager.network + self.floating_ip = (network_fakes.FakeFloatingIP. + create_one_floating_ip()) + self.port = network_fakes.FakePort.create_one_port() + self.project = identity_fakes_v2.FakeProject.create_one_project() + self.network.find_port = mock.Mock(return_value=self.port) + + +class TestCreateFloatingIPPortForwarding(TestFloatingIPPortForwarding): + + def setUp(self): + project_id = '' + super(TestCreateFloatingIPPortForwarding, self).setUp() + self.new_port_forwarding = ( + network_fakes.FakeFloatingIPPortForwarding. + create_one_port_forwarding( + attrs={ + 'internal_port_id': self.port.id, + 'floatingip_id': self.floating_ip.id, + } + ) + ) + self.network.create_floating_ip_port_forwarding = mock.Mock( + return_value=self.new_port_forwarding) + + self.network.find_ip = mock.Mock( + return_value=self.floating_ip + ) + + # Get the command object to test + self.cmd = floating_ip_port_forwarding.CreateFloatingIPPortForwarding( + self.app, self.namespace) + + self.columns = ( + 'external_port', + 'floatingip_id', + 'id', + 'internal_ip_address', + 'internal_port', + 'internal_port_id', + 'project_id', + 'protocol' + ) + + self.data = ( + self.new_port_forwarding.external_port, + self.new_port_forwarding.floatingip_id, + self.new_port_forwarding.id, + self.new_port_forwarding.internal_ip_address, + self.new_port_forwarding.internal_port, + self.new_port_forwarding.internal_port_id, + project_id, + self.new_port_forwarding.protocol, + ) + + def test_create_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_create_all_options(self): + arglist = [ + '--port', self.new_port_forwarding.internal_port_id, + '--internal-protocol-port', + str(self.new_port_forwarding.internal_port), + '--external-protocol-port', + str(self.new_port_forwarding.external_port), + '--protocol', self.new_port_forwarding.protocol, + self.new_port_forwarding.floatingip_id, + '--internal-ip-address', + self.new_port_forwarding.internal_ip_address, + ] + verifylist = [ + ('port', self.new_port_forwarding.internal_port_id), + ('internal_protocol_port', self.new_port_forwarding.internal_port), + ('external_protocol_port', self.new_port_forwarding.external_port), + ('protocol', self.new_port_forwarding.protocol), + ('floating_ip', self.new_port_forwarding.floatingip_id), + ('internal_ip_address', self.new_port_forwarding. + internal_ip_address), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_floating_ip_port_forwarding.\ + assert_called_once_with( + self.new_port_forwarding.floatingip_id, + **{ + 'external_port': self.new_port_forwarding.external_port, + 'internal_ip_address': self.new_port_forwarding. + internal_ip_address, + 'internal_port': self.new_port_forwarding.internal_port, + 'internal_port_id': self.new_port_forwarding. + internal_port_id, + 'protocol': self.new_port_forwarding.protocol, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestDeleteFloatingIPPortForwarding(TestFloatingIPPortForwarding): + + def setUp(self): + super(TestDeleteFloatingIPPortForwarding, self).setUp() + self._port_forwarding = ( + network_fakes.FakeFloatingIPPortForwarding.create_port_forwardings( + count=2, attrs={ + 'floatingip_id': self.floating_ip.id, + } + ) + ) + self.network.delete_floating_ip_port_forwarding = mock.Mock( + return_value=None + ) + + self.network.find_ip = mock.Mock( + return_value=self.floating_ip + ) + # Get the command object to test + self.cmd = floating_ip_port_forwarding.DeleteFloatingIPPortForwarding( + self.app, self.namespace) + + def test_port_forwarding_delete(self): + arglist = [ + self.floating_ip.id, + self._port_forwarding[0].id, + ] + verifylist = [ + ('floating_ip', self.floating_ip.id), + ('port_forwarding_id', [self._port_forwarding[0].id]), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.network.delete_floating_ip_port_forwarding.\ + assert_called_once_with( + self.floating_ip.id, + self._port_forwarding[0].id, + ignore_missing=False + ) + + self.assertIsNone(result) + + def test_multi_port_forwardings_delete(self): + arglist = [] + pf_id = [] + + arglist.append(str(self.floating_ip)) + + for a in self._port_forwarding: + arglist.append(a.id) + pf_id.append(a.id) + + verifylist = [ + ('floating_ip', str(self.floating_ip)), + ('port_forwarding_id', pf_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for a in self._port_forwarding: + calls.append(call(a.floatingip_id, a.id, ignore_missing=False)) + + self.network.delete_floating_ip_port_forwarding.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multi_port_forwarding_delete_with_exception(self): + arglist = [ + self.floating_ip.id, + self._port_forwarding[0].id, + 'unexist_port_forwarding_id', + ] + verifylist = [ + ('floating_ip', self.floating_ip.id), + ('port_forwarding_id', + [self._port_forwarding[0].id, 'unexist_port_forwarding_id']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + delete_mock_result = [None, exceptions.CommandError] + + self.network.delete_floating_ip_port_forwarding = ( + mock.MagicMock(side_effect=delete_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual( + '1 of 2 Port forwarding failed to delete.', + str(e) + ) + + self.network.delete_floating_ip_port_forwarding.\ + assert_any_call( + self.floating_ip.id, + 'unexist_port_forwarding_id', + ignore_missing=False + ) + self.network.delete_floating_ip_port_forwarding.\ + assert_any_call( + self.floating_ip.id, + self._port_forwarding[0].id, + ignore_missing=False + ) + + +class TestListFloatingIPPortForwarding(TestFloatingIPPortForwarding): + + columns = ( + 'ID', + 'Internal Port ID', + 'Internal IP Address', + 'Internal Port', + 'External Port', + 'Protocol' + ) + + def setUp(self): + super(TestListFloatingIPPortForwarding, self).setUp() + self.port_forwardings = ( + network_fakes.FakeFloatingIPPortForwarding.create_port_forwardings( + count=3, attrs={ + 'internal_port_id': self.port.id, + 'floatingip_id': self.floating_ip.id, + } + ) + ) + self.data = [] + for port_forwarding in self.port_forwardings: + self.data.append(( + port_forwarding.id, + port_forwarding.internal_port_id, + port_forwarding.internal_ip_address, + port_forwarding.internal_port, + port_forwarding.external_port, + port_forwarding.protocol, + )) + self.network.floating_ip_port_forwardings = mock.Mock( + return_value=self.port_forwardings + ) + self.network.find_ip = mock.Mock( + return_value=self.floating_ip + ) + # Get the command object to test + self.cmd = floating_ip_port_forwarding.ListFloatingIPPortForwarding( + self.app, + self.namespace + ) + + def test_port_forwarding_list(self): + arglist = [ + self.floating_ip.id + ] + verifylist = [ + ('floating_ip', self.floating_ip.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.floating_ip_port_forwardings.assert_called_once_with( + self.floating_ip, + **{} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_port_forwarding_list_all_options(self): + arglist = [ + '--port', self.port_forwardings[0].internal_port_id, + '--external-protocol-port', + str(self.port_forwardings[0].external_port), + '--protocol', self.port_forwardings[0].protocol, + self.port_forwardings[0].floatingip_id, + ] + + verifylist = [ + ('port', self.port_forwardings[0].internal_port_id), + ('external_protocol_port', + str(self.port_forwardings[0].external_port)), + ('protocol', self.port_forwardings[0].protocol), + ('floating_ip', self.port_forwardings[0].floatingip_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + query = { + 'internal_port_id': self.port_forwardings[0].internal_port_id, + 'external_port': str(self.port_forwardings[0].external_port), + 'protocol': self.port_forwardings[0].protocol, + } + + self.network.floating_ip_port_forwardings.assert_called_once_with( + self.floating_ip, + **query + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + +class TestSetFloatingIPPortForwarding(TestFloatingIPPortForwarding): + + # The Port Forwarding to set. + def setUp(self): + super(TestSetFloatingIPPortForwarding, self).setUp() + self._port_forwarding = ( + network_fakes.FakeFloatingIPPortForwarding. + create_one_port_forwarding( + attrs={ + 'floatingip_id': self.floating_ip.id, + } + ) + ) + self.network.update_floating_ip_port_forwarding = mock.Mock( + return_value=None + ) + + self.network.find_floating_ip_port_forwarding = mock.Mock( + return_value=self._port_forwarding) + self.network.find_ip = mock.Mock( + return_value=self.floating_ip + ) + # Get the command object to test + self.cmd = floating_ip_port_forwarding.SetFloatingIPPortForwarding( + self.app, + self.namespace + ) + + def test_set_nothing(self): + arglist = [ + self._port_forwarding.floatingip_id, + self._port_forwarding.id, + ] + verifylist = [ + ('floating_ip', self._port_forwarding.floatingip_id), + ('port_forwarding_id', self._port_forwarding.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = {} + self.network.update_floating_ip_port_forwarding.assert_called_with( + self._port_forwarding.floatingip_id, + self._port_forwarding.id, + **attrs + ) + self.assertIsNone(result) + + def test_set_all_thing(self): + arglist = [ + '--port', self.port.id, + '--internal-ip-address', 'new_internal_ip_address', + '--internal-protocol-port', '100', + '--external-protocol-port', '200', + '--protocol', 'tcp', + self._port_forwarding.floatingip_id, + self._port_forwarding.id, + ] + verifylist = [ + ('port', self.port.id), + ('internal_ip_address', 'new_internal_ip_address'), + ('internal_protocol_port', 100), + ('external_protocol_port', 200), + ('protocol', 'tcp'), + ('floating_ip', self._port_forwarding.floatingip_id), + ('port_forwarding_id', self._port_forwarding.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + attrs = { + 'internal_port_id': self.port.id, + 'internal_ip_address': 'new_internal_ip_address', + 'internal_port': 100, + 'external_port': 200, + 'protocol': 'tcp', + } + self.network.update_floating_ip_port_forwarding.assert_called_with( + self._port_forwarding.floatingip_id, + self._port_forwarding.id, + **attrs + ) + self.assertIsNone(result) + + +class TestShowFloatingIPPortForwarding(TestFloatingIPPortForwarding): + + # The port forwarding to show. + columns = ( + 'external_port', + 'floatingip_id', + 'id', + 'internal_ip_address', + 'internal_port', + 'internal_port_id', + 'project_id', + 'protocol', + ) + + def setUp(self): + project_id = '' + super(TestShowFloatingIPPortForwarding, self).setUp() + self._port_forwarding = ( + network_fakes.FakeFloatingIPPortForwarding. + create_one_port_forwarding( + attrs={ + 'floatingip_id': self.floating_ip.id, + } + ) + ) + self.data = ( + self._port_forwarding.external_port, + self._port_forwarding.floatingip_id, + self._port_forwarding.id, + self._port_forwarding.internal_ip_address, + self._port_forwarding.internal_port, + self._port_forwarding.internal_port_id, + project_id, + self._port_forwarding.protocol, + ) + self.network.find_floating_ip_port_forwarding = mock.Mock( + return_value=self._port_forwarding + ) + self.network.find_ip = mock.Mock( + return_value=self.floating_ip + ) + # Get the command object to test + self.cmd = floating_ip_port_forwarding.ShowFloatingIPPortForwarding( + self.app, + self.namespace + ) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_show_default_options(self): + arglist = [ + self._port_forwarding.floatingip_id, + self._port_forwarding.id, + ] + verifylist = [ + ('floating_ip', self._port_forwarding.floatingip_id), + ('port_forwarding_id', self._port_forwarding.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.find_floating_ip_port_forwarding.assert_called_once_with( + self.floating_ip, + self._port_forwarding.id, + ignore_missing=False + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(list(self.data), list(data)) diff --git a/openstackclient/tests/unit/network/v2/test_ip_availability.py b/openstackclient/tests/unit/network/v2/test_ip_availability.py index c7c5a9b..21508a8 100644 --- a/openstackclient/tests/unit/network/v2/test_ip_availability.py +++ b/openstackclient/tests/unit/network/v2/test_ip_availability.py @@ -13,7 +13,7 @@ import mock -from osc_lib import utils as common_utils +from osc_lib.cli import format_columns from openstackclient.network.v2 import ip_availability from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes @@ -75,7 +75,7 @@ class TestListIPAvailability(TestIPAvailability): self.network.network_ip_availabilities.assert_called_once_with( **filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_list_ip_version(self): arglist = [ @@ -93,7 +93,7 @@ class TestListIPAvailability(TestIPAvailability): self.network.network_ip_availabilities.assert_called_once_with( **filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_list_project(self): arglist = [ @@ -113,7 +113,7 @@ class TestListIPAvailability(TestIPAvailability): self.network.network_ip_availabilities.assert_called_once_with( **filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) class TestShowIPAvailability(TestIPAvailability): @@ -135,7 +135,7 @@ class TestShowIPAvailability(TestIPAvailability): _ip_availability.network_id, _ip_availability.network_name, _ip_availability.tenant_id, - common_utils.format_list( + format_columns.ListDictColumn( _ip_availability.subnet_ip_availability), _ip_availability.total_ips, _ip_availability.used_ips, @@ -176,4 +176,4 @@ class TestShowIPAvailability(TestIPAvailability): self._ip_availability.network_name, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) diff --git a/openstackclient/tests/unit/network/v2/test_network.py b/openstackclient/tests/unit/network/v2/test_network.py index 0f57f0e..5c97c36 100644 --- a/openstackclient/tests/unit/network/v2/test_network.py +++ b/openstackclient/tests/unit/network/v2/test_network.py @@ -15,8 +15,9 @@ import random import mock from mock import call + +from osc_lib.cli import format_columns from osc_lib import exceptions -from osc_lib import utils from openstackclient.network.v2 import network from openstackclient.tests.unit import fakes @@ -81,9 +82,9 @@ class TestCreateNetworkIdentityV3(TestNetwork): ) data = ( - network._format_admin_state(_network.admin_state_up), - utils.format_list(_network.availability_zone_hints), - utils.format_list(_network.availability_zones), + network.AdminStateColumn(_network.admin_state_up), + format_columns.ListColumn(_network.availability_zone_hints), + format_columns.ListColumn(_network.availability_zones), _network.description, _network.dns_domain, _network.id, @@ -98,11 +99,11 @@ class TestCreateNetworkIdentityV3(TestNetwork): _network.provider_physical_network, _network.provider_segmentation_id, _network.qos_policy_id, - network._format_router_external(_network.is_router_external), + network.RouterExternalColumn(_network.is_router_external), _network.shared, _network.status, - utils.format_list(_network.subnets), - utils.format_list(_network.tags), + format_columns.ListColumn(_network.subnets), + format_columns.ListColumn(_network.tags), ) def setUp(self): @@ -146,7 +147,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_all_options(self): arglist = [ @@ -211,7 +212,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): 'dns_domain': 'example.org.', }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_other_options(self): arglist = [ @@ -238,7 +239,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): 'port_security_enabled': False, }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def _test_create_with_tag(self, add_tags=True): arglist = [self._network.name] @@ -270,7 +271,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): else: self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_with_tags(self): self._test_create_with_tag(add_tags=True) @@ -313,9 +314,9 @@ class TestCreateNetworkIdentityV2(TestNetwork): ) data = ( - network._format_admin_state(_network.admin_state_up), - utils.format_list(_network.availability_zone_hints), - utils.format_list(_network.availability_zones), + network.AdminStateColumn(_network.admin_state_up), + format_columns.ListColumn(_network.availability_zone_hints), + format_columns.ListColumn(_network.availability_zones), _network.description, _network.dns_domain, _network.id, @@ -330,11 +331,11 @@ class TestCreateNetworkIdentityV2(TestNetwork): _network.provider_physical_network, _network.provider_segmentation_id, _network.qos_policy_id, - network._format_router_external(_network.is_router_external), + network.RouterExternalColumn(_network.is_router_external), _network.shared, _network.status, - utils.format_list(_network.subnets), - utils.format_list(_network.tags), + format_columns.ListColumn(_network.subnets), + format_columns.ListColumn(_network.tags), ) def setUp(self): @@ -385,7 +386,7 @@ class TestCreateNetworkIdentityV2(TestNetwork): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_with_domain_identityv2(self): arglist = [ @@ -525,7 +526,7 @@ class TestListNetwork(TestNetwork): data.append(( net.id, net.name, - utils.format_list(net.subnets), + format_columns.ListColumn(net.subnets), )) data_long = [] @@ -535,13 +536,13 @@ class TestListNetwork(TestNetwork): net.name, net.status, net.project_id, - network._format_admin_state(net.admin_state_up), + network.AdminStateColumn(net.admin_state_up), net.shared, - utils.format_list(net.subnets), + format_columns.ListColumn(net.subnets), net.provider_network_type, - network._format_router_external(net.is_router_external), - utils.format_list(net.availability_zones), - utils.format_list(net.tags), + network.RouterExternalColumn(net.is_router_external), + format_columns.ListColumn(net.availability_zones), + format_columns.ListColumn(net.tags), )) def setUp(self): @@ -577,7 +578,7 @@ class TestListNetwork(TestNetwork): self.network.networks.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_list_external(self): arglist = [ @@ -598,7 +599,7 @@ class TestListNetwork(TestNetwork): **{'router:external': True, 'is_router_external': True} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_list_internal(self): arglist = [ @@ -615,7 +616,7 @@ class TestListNetwork(TestNetwork): **{'router:external': False, 'is_router_external': False} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_network_list_long(self): arglist = [ @@ -634,7 +635,7 @@ class TestListNetwork(TestNetwork): self.network.networks.assert_called_once_with() self.assertEqual(self.columns_long, columns) - self.assertEqual(self.data_long, list(data)) + self.assertListItemEqual(self.data_long, list(data)) def test_list_name(self): test_name = "fakename" @@ -653,7 +654,7 @@ class TestListNetwork(TestNetwork): **{'name': test_name} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_network_list_enable(self): arglist = [ @@ -671,7 +672,7 @@ class TestListNetwork(TestNetwork): **{'admin_state_up': True, 'is_admin_state_up': True} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_network_list_disable(self): arglist = [ @@ -689,7 +690,7 @@ class TestListNetwork(TestNetwork): **{'admin_state_up': False, 'is_admin_state_up': False} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_network_list_project(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -708,7 +709,7 @@ class TestListNetwork(TestNetwork): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_network_list_project_domain(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -727,7 +728,7 @@ class TestListNetwork(TestNetwork): self.network.networks.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_network_list_share(self): arglist = [ @@ -744,7 +745,7 @@ class TestListNetwork(TestNetwork): **{'shared': True, 'is_shared': True} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_network_list_no_share(self): arglist = [ @@ -761,7 +762,7 @@ class TestListNetwork(TestNetwork): **{'shared': False, 'is_shared': False} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_network_list_status(self): choices = ['ACTIVE', 'BUILD', 'DOWN', 'ERROR'] @@ -780,7 +781,7 @@ class TestListNetwork(TestNetwork): **{'status': test_status} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_network_list_provider_network_type(self): network_type = self._network[0].provider_network_type @@ -798,7 +799,7 @@ class TestListNetwork(TestNetwork): 'provider_network_type': network_type} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_network_list_provider_physical_network(self): physical_network = self._network[0].provider_physical_network @@ -816,7 +817,7 @@ class TestListNetwork(TestNetwork): 'provider_physical_network': physical_network} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_network_list_provider_segment(self): segmentation_id = self._network[0].provider_segmentation_id @@ -834,7 +835,7 @@ class TestListNetwork(TestNetwork): 'provider_segmentation_id': segmentation_id} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_network_list_dhcp_agent(self): arglist = [ @@ -853,7 +854,7 @@ class TestListNetwork(TestNetwork): *attrs) self.assertEqual(self.columns, columns) - self.assertEqual(list(data), list(self.data)) + self.assertListItemEqual(list(data), list(self.data)) def test_list_with_tag_options(self): arglist = [ @@ -878,7 +879,7 @@ class TestListNetwork(TestNetwork): 'not_any_tags': 'black,white'} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) class TestSetNetwork(TestNetwork): @@ -1057,9 +1058,9 @@ class TestShowNetwork(TestNetwork): ) data = ( - network._format_admin_state(_network.admin_state_up), - utils.format_list(_network.availability_zone_hints), - utils.format_list(_network.availability_zones), + network.AdminStateColumn(_network.admin_state_up), + format_columns.ListColumn(_network.availability_zone_hints), + format_columns.ListColumn(_network.availability_zones), _network.description, _network.dns_domain, _network.id, @@ -1074,11 +1075,11 @@ class TestShowNetwork(TestNetwork): _network.provider_physical_network, _network.provider_segmentation_id, _network.qos_policy_id, - network._format_router_external(_network.is_router_external), + network.RouterExternalColumn(_network.is_router_external), _network.shared, _network.status, - utils.format_list(_network.subnets), - utils.format_list(_network.tags), + format_columns.ListColumn(_network.subnets), + format_columns.ListColumn(_network.tags), ) def setUp(self): @@ -1111,7 +1112,7 @@ class TestShowNetwork(TestNetwork): self._network.name, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) class TestUnsetNetwork(TestNetwork): diff --git a/openstackclient/tests/unit/network/v2/test_network_agent.py b/openstackclient/tests/unit/network/v2/test_network_agent.py index 709fb1c..8500d08 100644 --- a/openstackclient/tests/unit/network/v2/test_network_agent.py +++ b/openstackclient/tests/unit/network/v2/test_network_agent.py @@ -14,8 +14,8 @@ import mock from mock import call +from osc_lib.cli import format_columns from osc_lib import exceptions -from osc_lib import utils from openstackclient.network.v2 import network_agent from openstackclient.tests.unit.network.v2 import fakes as network_fakes @@ -207,8 +207,8 @@ class TestListNetworkAgent(TestNetworkAgent): agent.agent_type, agent.host, agent.availability_zone, - network_agent._format_alive(agent.alive), - network_agent._format_admin_state(agent.admin_state_up), + network_agent.AliveColumn(agent.alive), + network_agent.AdminStateColumn(agent.admin_state_up), agent.binary, )) @@ -246,7 +246,7 @@ class TestListNetworkAgent(TestNetworkAgent): self.network.agents.assert_called_once_with(**{}) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_network_agents_list_agent_type(self): arglist = [ @@ -263,7 +263,7 @@ class TestListNetworkAgent(TestNetworkAgent): 'agent_type': 'DHCP agent', }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_network_agents_list_host(self): arglist = [ @@ -280,7 +280,7 @@ class TestListNetworkAgent(TestNetworkAgent): 'host': self.network_agents[0].host, }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_network_agents_list_networks(self): arglist = [ @@ -298,7 +298,7 @@ class TestListNetworkAgent(TestNetworkAgent): self.network.network_hosting_dhcp_agents.assert_called_once_with( *attrs) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_network_agents_list_routers(self): arglist = [ @@ -318,7 +318,7 @@ class TestListNetworkAgent(TestNetworkAgent): *attrs) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_network_agents_list_routers_with_long_option(self): arglist = [ @@ -343,7 +343,7 @@ class TestListNetworkAgent(TestNetworkAgent): router_agent_data = [d + ('',) for d in self.data] self.assertEqual(router_agent_columns, columns) - self.assertEqual(router_agent_data, list(data)) + self.assertListItemEqual(router_agent_data, list(data)) class TestRemoveNetworkFromAgent(TestNetworkAgent): @@ -531,12 +531,12 @@ class TestShowNetworkAgent(TestNetworkAgent): 'id', ) data = ( - network_agent._format_admin_state(_network_agent.is_admin_state_up), + network_agent.AdminStateColumn(_network_agent.admin_state_up), _network_agent.agent_type, - network_agent._format_alive(_network_agent.is_alive), + network_agent.AliveColumn(_network_agent.is_alive), _network_agent.availability_zone, _network_agent.binary, - utils.format_dict(_network_agent.configurations), + format_columns.DictColumn(_network_agent.configurations), _network_agent.host, _network_agent.id, ) @@ -571,4 +571,4 @@ class TestShowNetworkAgent(TestNetworkAgent): self.network.get_agent.assert_called_once_with( self._network_agent.id) self.assertEqual(self.columns, columns) - self.assertEqual(list(self.data), list(data)) + self.assertItemEqual(list(self.data), list(data)) diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index 8ac3e54..c30d682 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -15,6 +15,7 @@ import argparse import mock from mock import call +from osc_lib.cli import format_columns from osc_lib import exceptions from osc_lib import utils @@ -68,22 +69,22 @@ class TestPort(network_fakes.TestNetworkV2): ) data = ( - port._format_admin_state(fake_port.admin_state_up), - utils.format_list_of_dicts(fake_port.allowed_address_pairs), + port.AdminStateColumn(fake_port.admin_state_up), + format_columns.ListDictColumn(fake_port.allowed_address_pairs), fake_port.binding_host_id, - utils.format_dict(fake_port.binding_profile), - utils.format_dict(fake_port.binding_vif_details), + format_columns.DictColumn(fake_port.binding_profile), + format_columns.DictColumn(fake_port.binding_vif_details), fake_port.binding_vif_type, fake_port.binding_vnic_type, fake_port.data_plane_status, fake_port.description, fake_port.device_id, fake_port.device_owner, - utils.format_list_of_dicts(fake_port.dns_assignment), + format_columns.ListDictColumn(fake_port.dns_assignment), fake_port.dns_domain, fake_port.dns_name, - utils.format_list_of_dicts(fake_port.extra_dhcp_opts), - utils.format_list_of_dicts(fake_port.fixed_ips), + format_columns.ListDictColumn(fake_port.extra_dhcp_opts), + format_columns.ListDictColumn(fake_port.fixed_ips), fake_port.id, fake_port.mac_address, fake_port.name, @@ -91,9 +92,9 @@ class TestPort(network_fakes.TestNetworkV2): fake_port.port_security_enabled, fake_port.project_id, fake_port.qos_policy_id, - utils.format_list(fake_port.security_group_ids), + format_columns.ListColumn(fake_port.security_group_ids), fake_port.status, - utils.format_list(fake_port.tags), + format_columns.ListColumn(fake_port.tags), fake_port.uplink_status_propagation, ) @@ -141,7 +142,7 @@ class TestCreatePort(TestPort): self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_full_options(self): arglist = [ @@ -199,7 +200,7 @@ class TestCreatePort(TestPort): }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_invalid_json_binding_profile(self): arglist = [ @@ -250,7 +251,7 @@ class TestCreatePort(TestPort): }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_with_security_group(self): secgroup = network_fakes.FakeSecurityGroup.create_one_security_group() @@ -279,7 +280,7 @@ class TestCreatePort(TestPort): }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_port_with_dns_name(self): arglist = [ @@ -305,7 +306,7 @@ class TestCreatePort(TestPort): }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_with_security_groups(self): sg_1 = network_fakes.FakeSecurityGroup.create_one_security_group() @@ -335,7 +336,7 @@ class TestCreatePort(TestPort): }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_with_no_security_groups(self): arglist = [ @@ -361,7 +362,7 @@ class TestCreatePort(TestPort): }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_with_no_fixed_ips(self): arglist = [ @@ -387,7 +388,7 @@ class TestCreatePort(TestPort): }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_port_with_allowed_address_pair_ipaddr(self): pairs = [{'ip_address': '192.168.1.123'}, @@ -417,7 +418,7 @@ class TestCreatePort(TestPort): }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_port_with_allowed_address_pair(self): pairs = [{'ip_address': '192.168.1.123', @@ -453,7 +454,7 @@ class TestCreatePort(TestPort): }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_port_with_qos(self): qos_policy = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy() @@ -481,7 +482,7 @@ class TestCreatePort(TestPort): }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_port_security_enabled(self): arglist = [ @@ -565,7 +566,7 @@ class TestCreatePort(TestPort): else: self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_with_tags(self): self._test_create_with_tag(add_tags=True) @@ -602,7 +603,7 @@ class TestCreatePort(TestPort): }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_with_uplink_status_propagation_enabled(self): self._test_create_with_uplink_status_propagation(enable=True) @@ -610,6 +611,48 @@ class TestCreatePort(TestPort): def test_create_with_uplink_status_propagation_disabled(self): self._test_create_with_uplink_status_propagation(enable=False) + def test_create_port_with_extra_dhcp_option(self): + extra_dhcp_options = [{'opt_name': 'classless-static-route', + 'opt_value': '169.254.169.254/32,22.2.0.2,' + '0.0.0.0/0,22.2.0.1', + 'ip_version': '4'}, + {'opt_name': 'dns-server', + 'opt_value': '240C::6666', + 'ip_version': '6'}] + arglist = [ + '--network', self._port.network_id, + '--extra-dhcp-option', 'name=classless-static-route,' + 'value=169.254.169.254/32,22.2.0.2,' + '0.0.0.0/0,22.2.0.1,' + 'ip-version=4', + '--extra-dhcp-option', 'name=dns-server,value=240C::6666,' + 'ip-version=6', + 'test-port', + ] + + verifylist = [ + ('network', self._port.network_id,), + ('extra_dhcp_options', [{'name': 'classless-static-route', + 'value': '169.254.169.254/32,22.2.0.2,' + '0.0.0.0/0,22.2.0.1', + 'ip-version': '4'}, + {'name': 'dns-server', + 'value': '240C::6666', + 'ip-version': '6'}]), + ('name', 'test-port'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.network.create_port.assert_called_once_with(**{ + 'admin_state_up': True, + 'network_id': self._port.network_id, + 'extra_dhcp_opts': extra_dhcp_options, + 'name': 'test-port', + }) + class TestDeletePort(TestPort): @@ -719,7 +762,7 @@ class TestListPort(TestPort): prt.id, prt.name, prt.mac_address, - utils.format_list_of_dicts(prt.fixed_ips), + format_columns.ListDictColumn(prt.fixed_ips), prt.status, )) @@ -729,11 +772,11 @@ class TestListPort(TestPort): prt.id, prt.name, prt.mac_address, - utils.format_list_of_dicts(prt.fixed_ips), + format_columns.ListDictColumn(prt.fixed_ips), prt.status, - utils.format_list(prt.security_group_ids), + format_columns.ListColumn(prt.security_group_ids), prt.device_owner, - utils.format_list(prt.tags), + format_columns.ListColumn(prt.tags), )) def setUp(self): @@ -762,7 +805,7 @@ class TestListPort(TestPort): self.network.ports.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_port_list_router_opt(self): arglist = [ @@ -781,7 +824,7 @@ class TestListPort(TestPort): 'device_id': 'fake-router-id' }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) @mock.patch.object(utils, 'find_resource') def test_port_list_with_server_option(self, mock_find): @@ -801,7 +844,7 @@ class TestListPort(TestPort): device_id=fake_server.id) mock_find.assert_called_once_with(mock.ANY, 'fake-server-name') self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_port_list_device_id_opt(self): arglist = [ @@ -820,7 +863,7 @@ class TestListPort(TestPort): 'device_id': self._ports[0].device_id }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_port_list_device_owner_opt(self): arglist = [ @@ -839,7 +882,7 @@ class TestListPort(TestPort): 'device_owner': self._ports[0].device_owner }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_port_list_all_opt(self): arglist = [ @@ -867,7 +910,7 @@ class TestListPort(TestPort): 'mac_address': self._ports[0].mac_address }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_port_list_mac_address_opt(self): arglist = [ @@ -886,7 +929,7 @@ class TestListPort(TestPort): 'mac_address': self._ports[0].mac_address }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_port_list_fixed_ip_opt_ip_address(self): ip_address = self._ports[0].fixed_ips[0]['ip_address'] @@ -904,7 +947,7 @@ class TestListPort(TestPort): self.network.ports.assert_called_once_with(**{ 'fixed_ips': ['ip_address=%s' % ip_address]}) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_port_list_fixed_ip_opt_ip_address_substr(self): ip_address_ss = self._ports[0].fixed_ips[0]['ip_address'][:-1] @@ -922,7 +965,7 @@ class TestListPort(TestPort): self.network.ports.assert_called_once_with(**{ 'fixed_ips': ['ip_address_substr=%s' % ip_address_ss]}) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_port_list_fixed_ip_opt_subnet_id(self): subnet_id = self._ports[0].fixed_ips[0]['subnet_id'] @@ -942,7 +985,7 @@ class TestListPort(TestPort): self.network.ports.assert_called_once_with(**{ 'fixed_ips': ['subnet_id=%s' % subnet_id]}) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_port_list_fixed_ip_opts(self): subnet_id = self._ports[0].fixed_ips[0]['subnet_id'] @@ -966,7 +1009,7 @@ class TestListPort(TestPort): 'fixed_ips': ['subnet_id=%s' % subnet_id, 'ip_address=%s' % ip_address]}) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_port_list_fixed_ips(self): subnet_id = self._ports[0].fixed_ips[0]['subnet_id'] @@ -990,7 +1033,7 @@ class TestListPort(TestPort): 'fixed_ips': ['subnet_id=%s' % subnet_id, 'ip_address=%s' % ip_address]}) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_list_port_with_long(self): arglist = [ @@ -1007,7 +1050,7 @@ class TestListPort(TestPort): self.network.ports.assert_called_once_with() self.assertEqual(self.columns_long, columns) - self.assertEqual(self.data_long, list(data)) + self.assertListItemEqual(self.data_long, list(data)) def test_port_list_project(self): project = identity_fakes.FakeProject.create_one_project() @@ -1025,7 +1068,7 @@ class TestListPort(TestPort): self.network.ports.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_port_list_project_domain(self): project = identity_fakes.FakeProject.create_one_project() @@ -1045,7 +1088,7 @@ class TestListPort(TestPort): self.network.ports.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_list_with_tag_options(self): arglist = [ @@ -1070,7 +1113,7 @@ class TestListPort(TestPort): 'not_any_tags': 'black,white'} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) class TestSetPort(TestPort): @@ -1644,7 +1687,7 @@ class TestShowPort(TestPort): self._port.name, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) class TestUnsetPort(TestPort): diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index 618adf3..079b974 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -14,8 +14,8 @@ import mock from mock import call +from osc_lib.cli import format_columns from osc_lib import exceptions -from osc_lib import utils as osc_utils from openstackclient.network.v2 import router from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 @@ -132,19 +132,19 @@ class TestCreateRouter(TestRouter): 'tags', ) data = ( - router._format_admin_state(new_router.admin_state_up), - osc_utils.format_list(new_router.availability_zone_hints), - osc_utils.format_list(new_router.availability_zones), + router.AdminStateColumn(new_router.admin_state_up), + format_columns.ListColumn(new_router.availability_zone_hints), + format_columns.ListColumn(new_router.availability_zones), new_router.description, new_router.distributed, - router._format_router_info(new_router.external_gateway_info), + router.RouterInfoColumn(new_router.external_gateway_info), new_router.ha, new_router.id, new_router.name, new_router.tenant_id, - router._format_routes(new_router.routes), + router.RoutesColumn(new_router.routes), new_router.status, - osc_utils.format_list(new_router.tags), + format_columns.ListColumn(new_router.tags), ) def setUp(self): @@ -184,7 +184,7 @@ class TestCreateRouter(TestRouter): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def _test_create_with_ha_options(self, option, ha): arglist = [ @@ -208,7 +208,7 @@ class TestCreateRouter(TestRouter): 'ha': ha, }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_with_ha_option(self): self._test_create_with_ha_options('--ha', True) @@ -237,7 +237,7 @@ class TestCreateRouter(TestRouter): 'distributed': distributed, }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_with_distributed_option(self): self._test_create_with_distributed_options('--distributed', True) @@ -268,7 +268,7 @@ class TestCreateRouter(TestRouter): }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def _test_create_with_tag(self, add_tags=True): arglist = [self.new_router.name] @@ -301,7 +301,7 @@ class TestCreateRouter(TestRouter): else: self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_with_tags(self): self._test_create_with_tag(add_tags=True) @@ -422,7 +422,7 @@ class TestListRouter(TestRouter): r.id, r.name, r.status, - router._format_admin_state(r.admin_state_up), + router.AdminStateColumn(r.admin_state_up), r.tenant_id, r.distributed, r.ha, @@ -447,10 +447,10 @@ class TestListRouter(TestRouter): r = routers[i] data_long.append( data[i] + ( - router._format_routes(r.routes), - router._format_router_info(r.external_gateway_info), - osc_utils.format_list(r.availability_zones), - osc_utils.format_list(r.tags), + router.RoutesColumn(r.routes), + router.RouterInfoColumn(r.external_gateway_info), + format_columns.ListColumn(r.availability_zones), + format_columns.ListColumn(r.tags), ) ) data_long_no_az = [] @@ -458,9 +458,9 @@ class TestListRouter(TestRouter): r = routers[i] data_long_no_az.append( data[i] + ( - router._format_routes(r.routes), - router._format_router_info(r.external_gateway_info), - osc_utils.format_list(r.tags), + router.RoutesColumn(r.routes), + router.RouterInfoColumn(r.external_gateway_info), + format_columns.ListColumn(r.tags), ) ) @@ -494,7 +494,7 @@ class TestListRouter(TestRouter): self.network.routers.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_router_list_no_ha_no_distributed(self): _routers = network_fakes.FakeRouter.create_routers({ @@ -531,7 +531,7 @@ class TestListRouter(TestRouter): self.network.routers.assert_called_once_with() self.assertEqual(self.columns_long, columns) - self.assertEqual(self.data_long, list(data)) + self.assertListItemEqual(self.data_long, list(data)) def test_router_list_long_no_az(self): arglist = [ @@ -552,7 +552,7 @@ class TestListRouter(TestRouter): self.network.routers.assert_called_once_with() self.assertEqual(self.columns_long_no_az, columns) - self.assertEqual(self.data_long_no_az, list(data)) + self.assertListItemEqual(self.data_long_no_az, list(data)) def test_list_name(self): test_name = "fakename" @@ -570,7 +570,7 @@ class TestListRouter(TestRouter): **{'name': test_name} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_router_list_enable(self): arglist = [ @@ -587,7 +587,7 @@ class TestListRouter(TestRouter): **{'admin_state_up': True, 'is_admin_state_up': True} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_router_list_disable(self): arglist = [ @@ -605,7 +605,7 @@ class TestListRouter(TestRouter): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_router_list_project(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -623,7 +623,7 @@ class TestListRouter(TestRouter): self.network.routers.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_router_list_project_domain(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -643,7 +643,7 @@ class TestListRouter(TestRouter): self.network.routers.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_router_list_agents_no_args(self): arglist = [ @@ -671,7 +671,7 @@ class TestListRouter(TestRouter): self.network.agent_hosted_routers( *attrs) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_list_with_tag_options(self): arglist = [ @@ -696,7 +696,7 @@ class TestListRouter(TestRouter): 'not_any_tags': 'black,white'} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) class TestRemovePortFromRouter(TestRouter): @@ -939,52 +939,6 @@ class TestSetRouter(TestRouter): _testrouter, **attrs) self.assertIsNone(result) - def test_set_clear_routes(self): - arglist = [ - self._router.name, - '--clear-routes', - ] - verifylist = [ - ('router', self._router.name), - ('clear_routes', True), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - attrs = { - 'routes': [], - } - self.network.update_router.assert_called_once_with( - self._router, **attrs) - self.assertIsNone(result) - - def test_overwrite_route_clear_routes(self): - _testrouter = network_fakes.FakeRouter.create_one_router( - {'routes': [{"destination": "10.0.0.2", - "nexthop": "1.1.1.1"}]}) - self.network.find_router = mock.Mock(return_value=_testrouter) - arglist = [ - _testrouter.name, - '--route', 'destination=10.20.30.0/24,gateway=10.20.30.1', - '--clear-routes', - ] - verifylist = [ - ('router', _testrouter.name), - ('routes', [{'destination': '10.20.30.0/24', - 'gateway': '10.20.30.1'}]), - ('clear_routes', True), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) - attrs = { - 'routes': [{'destination': '10.20.30.0/24', - 'nexthop': '10.20.30.1'}] - } - self.network.update_router.assert_called_once_with( - _testrouter, **attrs) - self.assertIsNone(result) - def test_set_nothing(self): arglist = [ self._router.name, @@ -1260,20 +1214,20 @@ class TestShowRouter(TestRouter): 'tags', ) data = ( - router._format_admin_state(_router.admin_state_up), - osc_utils.format_list(_router.availability_zone_hints), - osc_utils.format_list(_router.availability_zones), + router.AdminStateColumn(_router.admin_state_up), + format_columns.ListColumn(_router.availability_zone_hints), + format_columns.ListColumn(_router.availability_zones), _router.description, _router.distributed, - router._format_router_info(_router.external_gateway_info), + router.RouterInfoColumn(_router.external_gateway_info), _router.ha, _router.id, - router._format_router_info(_router.interfaces_info), + router.RouterInfoColumn(_router.interfaces_info), _router.name, _router.tenant_id, - router._format_routes(_router.routes), + router.RoutesColumn(_router.routes), _router.status, - osc_utils.format_list(_router.tags), + format_columns.ListColumn(_router.tags), ) def setUp(self): @@ -1309,7 +1263,7 @@ class TestShowRouter(TestRouter): 'device_id': self._router.id }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_show_no_ha_no_distributed(self): _router = network_fakes.FakeRouter.create_one_router({ diff --git a/openstackclient/tests/unit/network/v2/test_security_group_compute.py b/openstackclient/tests/unit/network/v2/test_security_group_compute.py index c949e2c..df36006 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_compute.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_compute.py @@ -56,7 +56,7 @@ class TestCreateSecurityGroupCompute(TestSecurityGroupCompute): _security_group['id'], _security_group['name'], _security_group['tenant_id'], - '', + security_group.ComputeSecurityGroupRulesColumn([]), ) def setUp(self): @@ -88,7 +88,7 @@ class TestCreateSecurityGroupCompute(TestSecurityGroupCompute): self._security_group['name'], ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_security_group_create_all_options(self, sg_mock): sg_mock.return_value = self._security_group @@ -109,7 +109,7 @@ class TestCreateSecurityGroupCompute(TestSecurityGroupCompute): self._security_group['description'], ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) @mock.patch( @@ -255,7 +255,7 @@ class TestListSecurityGroupCompute(TestSecurityGroupCompute): kwargs = {'search_opts': {'all_tenants': False}} sg_mock.assert_called_once_with(**kwargs) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_security_group_list_all_projects(self, sg_mock): sg_mock.return_value = self._security_groups @@ -272,7 +272,7 @@ class TestListSecurityGroupCompute(TestSecurityGroupCompute): kwargs = {'search_opts': {'all_tenants': True}} sg_mock.assert_called_once_with(**kwargs) self.assertEqual(self.columns_all_projects, columns) - self.assertEqual(self.data_all_projects, list(data)) + self.assertListItemEqual(self.data_all_projects, list(data)) @mock.patch( @@ -372,8 +372,7 @@ class TestShowSecurityGroupCompute(TestSecurityGroupCompute): _security_group['id'], _security_group['name'], _security_group['tenant_id'], - security_group._format_compute_security_group_rules( - [_security_group_rule]), + security_group.ComputeSecurityGroupRulesColumn([_security_group_rule]), ) def setUp(self): @@ -402,4 +401,4 @@ class TestShowSecurityGroupCompute(TestSecurityGroupCompute): sg_mock.assert_called_once_with(self._security_group['id']) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) diff --git a/openstackclient/tests/unit/network/v2/test_security_group_network.py b/openstackclient/tests/unit/network/v2/test_security_group_network.py index 8320828..57698ec 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_network.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_network.py @@ -57,7 +57,7 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): _security_group.id, _security_group.name, _security_group.project_id, - '', + security_group.NetworkSecurityGroupRulesColumn([]), _security_group.tags, ) @@ -94,7 +94,7 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): 'name': self._security_group.name, }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_all_options(self): arglist = [ @@ -119,7 +119,7 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): 'tenant_id': self.project.id, }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def _test_create_with_tag(self, add_tags=True): arglist = [self._security_group.name] @@ -150,7 +150,7 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): else: self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_with_tags(self): self._test_create_with_tag(add_tags=True) @@ -287,7 +287,7 @@ class TestListSecurityGroupNetwork(TestSecurityGroupNetwork): self.network.security_groups.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_security_group_list_all_projects(self): arglist = [ @@ -302,7 +302,7 @@ class TestListSecurityGroupNetwork(TestSecurityGroupNetwork): self.network.security_groups.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_security_group_list_project(self): project = identity_fakes.FakeProject.create_one_project() @@ -320,7 +320,7 @@ class TestListSecurityGroupNetwork(TestSecurityGroupNetwork): self.network.security_groups.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_security_group_list_project_domain(self): project = identity_fakes.FakeProject.create_one_project() @@ -340,7 +340,7 @@ class TestListSecurityGroupNetwork(TestSecurityGroupNetwork): self.network.security_groups.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_list_with_tag_options(self): arglist = [ @@ -490,7 +490,7 @@ class TestShowSecurityGroupNetwork(TestSecurityGroupNetwork): _security_group.id, _security_group.name, _security_group.project_id, - security_group._format_network_security_group_rules( + security_group.NetworkSecurityGroupRulesColumn( [_security_group_rule._info]), _security_group.tags, ) @@ -522,7 +522,7 @@ class TestShowSecurityGroupNetwork(TestSecurityGroupNetwork): self.network.find_security_group.assert_called_once_with( self._security_group.id, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) class TestUnsetSecurityGroupNetwork(TestSecurityGroupNetwork): diff --git a/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py b/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py index 5c1937e..cf5261b 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py @@ -72,15 +72,6 @@ class TestCreateSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, [], []) - def test_security_group_rule_create_all_source_options(self, sgr_mock): - arglist = [ - '--src-ip', '10.10.0.0/24', - '--src-group', self._security_group['id'], - self._security_group['id'], - ] - self.assertRaises(tests_utils.ParserException, - self.check_parser, self.cmd, arglist, []) - def test_security_group_rule_create_all_remote_options(self, sgr_mock): arglist = [ '--remote-ip', '10.10.0.0/24', @@ -151,41 +142,6 @@ class TestCreateSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): self.assertEqual(expected_columns, columns) self.assertEqual(expected_data, data) - def test_security_group_rule_create_source_group(self, sgr_mock): - expected_columns, expected_data = self._setup_security_group_rule({ - 'from_port': 22, - 'to_port': 22, - 'group': {'name': self._security_group['name']}, - }) - sgr_mock.return_value = self._security_group_rule - arglist = [ - '--dst-port', str(self._security_group_rule['from_port']), - '--src-group', self._security_group['name'], - self._security_group['id'], - ] - verifylist = [ - ('dst_port', (self._security_group_rule['from_port'], - self._security_group_rule['to_port'])), - ('src_group', self._security_group['name']), - ('group', self._security_group['id']), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - # TODO(dtroyer): save this for the security group rule changes - # self.compute.api.security_group_rule_create.assert_called_once_with( - sgr_mock.assert_called_once_with( - security_group_id=self._security_group['id'], - ip_protocol=self._security_group_rule['ip_protocol'], - from_port=self._security_group_rule['from_port'], - to_port=self._security_group_rule['to_port'], - remote_ip=self._security_group_rule['ip_range']['cidr'], - remote_group=self._security_group['id'], - ) - self.assertEqual(expected_columns, columns) - self.assertEqual(expected_data, data) - def test_security_group_rule_create_remote_group(self, sgr_mock): expected_columns, expected_data = self._setup_security_group_rule({ 'from_port': 22, @@ -221,41 +177,6 @@ class TestCreateSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): self.assertEqual(expected_columns, columns) self.assertEqual(expected_data, data) - def test_security_group_rule_create_source_ip(self, sgr_mock): - expected_columns, expected_data = self._setup_security_group_rule({ - 'ip_protocol': 'icmp', - 'from_port': -1, - 'to_port': -1, - 'ip_range': {'cidr': '10.0.2.0/24'}, - }) - sgr_mock.return_value = self._security_group_rule - arglist = [ - '--protocol', self._security_group_rule['ip_protocol'], - '--src-ip', self._security_group_rule['ip_range']['cidr'], - self._security_group['id'], - ] - verifylist = [ - ('protocol', self._security_group_rule['ip_protocol']), - ('src_ip', self._security_group_rule['ip_range']['cidr']), - ('group', self._security_group['id']), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - # TODO(dtroyer): save this for the security group rule changes - # self.compute.api.security_group_rule_create.assert_called_once_with( - sgr_mock.assert_called_once_with( - security_group_id=self._security_group['id'], - ip_protocol=self._security_group_rule['ip_protocol'], - from_port=self._security_group_rule['from_port'], - to_port=self._security_group_rule['to_port'], - remote_ip=self._security_group_rule['ip_range']['cidr'], - remote_group=None, - ) - self.assertEqual(expected_columns, columns) - self.assertEqual(expected_data, data) - def test_security_group_rule_create_remote_ip(self, sgr_mock): expected_columns, expected_data = self._setup_security_group_rule({ 'ip_protocol': 'icmp', @@ -301,13 +222,13 @@ class TestCreateSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): sgr_mock.return_value = self._security_group_rule arglist = [ '--proto', self._security_group_rule['ip_protocol'], - '--src-ip', self._security_group_rule['ip_range']['cidr'], + '--remote-ip', self._security_group_rule['ip_range']['cidr'], self._security_group['id'], ] verifylist = [ ('proto', self._security_group_rule['ip_protocol']), ('protocol', None), - ('src_ip', self._security_group_rule['ip_range']['cidr']), + ('remote_ip', self._security_group_rule['ip_range']['cidr']), ('group', self._security_group['id']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -416,6 +337,7 @@ class TestListSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): _security_group_rule_tcp = \ compute_fakes.FakeSecurityGroupRule.create_one_security_group_rule({ 'ip_protocol': 'tcp', + 'ethertype': 'IPv4', 'from_port': 80, 'to_port': 80, 'group': {'name': _security_group['name']}, @@ -423,6 +345,7 @@ class TestListSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): _security_group_rule_icmp = \ compute_fakes.FakeSecurityGroupRule.create_one_security_group_rule({ 'ip_protocol': 'icmp', + 'ethertype': 'IPv4', 'from_port': -1, 'to_port': -1, 'ip_range': {'cidr': '10.0.2.0/24'}, @@ -436,6 +359,7 @@ class TestListSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): expected_columns_with_group = ( 'ID', 'IP Protocol', + 'Ethertype', 'IP Range', 'Port Range', 'Remote Security Group', @@ -452,6 +376,7 @@ class TestListSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): expected_rule_with_group = ( rule['id'], rule['ip_protocol'], + rule['ethertype'], rule['ip_range'], rule['port_range'], rule['remote_security_group'], diff --git a/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py b/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py index b070ab6..49c3d5d 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py @@ -99,15 +99,6 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, [], []) - def test_create_all_source_options(self): - arglist = [ - '--src-ip', '10.10.0.0/24', - '--src-group', self._security_group.id, - self._security_group.id, - ] - self.assertRaises(tests_utils.ParserException, - self.check_parser, self.cmd, arglist, []) - def test_create_all_remote_options(self): arglist = [ '--remote-ip', '10.10.0.0/24', @@ -177,10 +168,12 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): def test_create_default_rule(self): self._setup_security_group_rule({ + 'protocol': 'tcp', 'port_range_max': 443, 'port_range_min': 443, }) arglist = [ + '--protocol', 'tcp', '--dst-port', str(self._security_group_rule.port_range_min), self._security_group.id, ] @@ -212,13 +205,13 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): }) arglist = [ '--proto', self._security_group_rule.protocol, - '--src-ip', self._security_group_rule.remote_ip_prefix, + '--remote-ip', self._security_group_rule.remote_ip_prefix, self._security_group.id, ] verifylist = [ ('proto', self._security_group_rule.protocol), ('protocol', None), - ('src_ip', self._security_group_rule.remote_ip_prefix), + ('remote_ip', self._security_group_rule.remote_ip_prefix), ('group', self._security_group.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -242,13 +235,13 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): }) arglist = [ '--proto', 'any', - '--src-ip', self._security_group_rule.remote_ip_prefix, + '--remote-ip', self._security_group_rule.remote_ip_prefix, self._security_group.id, ] verifylist = [ ('proto', 'any'), ('protocol', None), - ('src_ip', self._security_group_rule.remote_ip_prefix), + ('remote_ip', self._security_group_rule.remote_ip_prefix), ('group', self._security_group.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -267,21 +260,22 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): def test_create_remote_group(self): self._setup_security_group_rule({ + 'protocol': 'tcp', 'port_range_max': 22, 'port_range_min': 22, - 'remote_group_id': self._security_group.id, }) arglist = [ + '--protocol', 'tcp', '--dst-port', str(self._security_group_rule.port_range_min), '--ingress', - '--src-group', self._security_group.name, + '--remote-group', self._security_group.name, self._security_group.id, ] verifylist = [ ('dst_port', (self._security_group_rule.port_range_min, self._security_group_rule.port_range_max)), ('ingress', True), - ('src_group', self._security_group.name), + ('remote_group', self._security_group.name), ('group', self._security_group.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -294,7 +288,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): 'port_range_max': self._security_group_rule.port_range_max, 'port_range_min': self._security_group_rule.port_range_min, 'protocol': self._security_group_rule.protocol, - 'remote_group_id': self._security_group_rule.remote_group_id, + 'remote_group_id': self._security_group.id, 'security_group_id': self._security_group.id, }) self.assertEqual(self.expected_columns, columns) @@ -306,12 +300,12 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): }) arglist = [ '--ingress', - '--src-group', self._security_group.name, + '--remote-group', self._security_group.name, self._security_group.id, ] verifylist = [ ('ingress', True), - ('src_group', self._security_group.name), + ('remote_group', self._security_group.name), ('group', self._security_group.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -335,12 +329,12 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): }) arglist = [ '--protocol', self._security_group_rule.protocol, - '--src-ip', self._security_group_rule.remote_ip_prefix, + '--remote-ip', self._security_group_rule.remote_ip_prefix, self._security_group.id, ] verifylist = [ ('protocol', self._security_group_rule.protocol), - ('src_ip', self._security_group_rule.remote_ip_prefix), + ('remote_ip', self._security_group_rule.remote_ip_prefix), ('group', self._security_group.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -394,7 +388,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): 'port_range_min': 443, 'protocol': '6', 'remote_group_id': None, - 'remote_ip_prefix': None, + 'remote_ip_prefix': '::/0', }) arglist = [ '--dst-port', str(self._security_group_rule.port_range_min), @@ -425,6 +419,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): 'port_range_max': self._security_group_rule.port_range_max, 'port_range_min': self._security_group_rule.port_range_min, 'protocol': self._security_group_rule.protocol, + 'remote_ip_prefix': self._security_group_rule.remote_ip_prefix, 'security_group_id': self._security_group.id, 'tenant_id': self.project.id, }) @@ -670,6 +665,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): 'port_range_min': 139, 'port_range_max': 2, 'protocol': 'ipv6-icmp', + 'remote_ip_prefix': '::/0', }) arglist = [ '--icmp-type', str(self._security_group_rule.port_range_min), @@ -694,6 +690,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): 'port_range_min': self._security_group_rule.port_range_min, 'port_range_max': self._security_group_rule.port_range_max, 'protocol': self._security_group_rule.protocol, + 'remote_ip_prefix': self._security_group_rule.remote_ip_prefix, 'security_group_id': self._security_group.id, }) self.assertEqual(self.expected_columns, columns) @@ -704,6 +701,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): 'ether_type': 'IPv6', 'port_range_min': 139, 'protocol': 'icmpv6', + 'remote_ip_prefix': '::/0', }) arglist = [ '--icmp-type', str(self._security_group_rule.port_range_min), @@ -726,6 +724,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): 'ethertype': self._security_group_rule.ether_type, 'port_range_min': self._security_group_rule.port_range_min, 'protocol': self._security_group_rule.protocol, + 'remote_ip_prefix': self._security_group_rule.remote_ip_prefix, 'security_group_id': self._security_group.id, }) self.assertEqual(self.expected_columns, columns) @@ -874,15 +873,16 @@ class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): expected_columns_with_group_and_long = ( 'ID', 'IP Protocol', + 'Ethertype', 'IP Range', 'Port Range', 'Direction', - 'Ethertype', 'Remote Security Group', ) expected_columns_no_group = ( 'ID', 'IP Protocol', + 'Ethertype', 'IP Range', 'Port Range', 'Remote Security Group', @@ -895,16 +895,17 @@ class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): expected_data_with_group_and_long.append(( _security_group_rule.id, _security_group_rule.protocol, + _security_group_rule.ether_type, _security_group_rule.remote_ip_prefix, security_group_rule._format_network_port_range( _security_group_rule), _security_group_rule.direction, - _security_group_rule.ether_type, _security_group_rule.remote_group_id, )) expected_data_no_group.append(( _security_group_rule.id, _security_group_rule.protocol, + _security_group_rule.ether_type, _security_group_rule.remote_ip_prefix, security_group_rule._format_network_port_range( _security_group_rule), diff --git a/openstackclient/tests/unit/network/v2/test_subnet.py b/openstackclient/tests/unit/network/v2/test_subnet.py index 39cb4f5..9903b04 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet.py +++ b/openstackclient/tests/unit/network/v2/test_subnet.py @@ -14,8 +14,8 @@ import mock from mock import call +from osc_lib.cli import format_columns from osc_lib import exceptions -from osc_lib import utils from openstackclient.network.v2 import subnet as subnet_v2 from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 @@ -131,13 +131,13 @@ class TestCreateSubnet(TestSubnet): ) self.data = ( - subnet_v2._format_allocation_pools(self._subnet.allocation_pools), + subnet_v2.AllocationPoolsColumn(self._subnet.allocation_pools), self._subnet.cidr, self._subnet.description, - utils.format_list(self._subnet.dns_nameservers), + format_columns.ListColumn(self._subnet.dns_nameservers), self._subnet.enable_dhcp, self._subnet.gateway_ip, - subnet_v2._format_host_routes(self._subnet.host_routes), + subnet_v2.HostRoutesColumn(self._subnet.host_routes), self._subnet.id, self._subnet.ip_version, self._subnet.ipv6_address_mode, @@ -146,20 +146,20 @@ class TestCreateSubnet(TestSubnet): self._subnet.network_id, self._subnet.project_id, self._subnet.segment_id, - utils.format_list(self._subnet.service_types), + format_columns.ListColumn(self._subnet.service_types), self._subnet.subnetpool_id, - utils.format_list(self._subnet.tags), + format_columns.ListColumn(self._subnet.tags), ) self.data_subnet_pool = ( - subnet_v2._format_allocation_pools( + subnet_v2.AllocationPoolsColumn( self._subnet_from_pool.allocation_pools), self._subnet_from_pool.cidr, self._subnet_from_pool.description, - utils.format_list(self._subnet_from_pool.dns_nameservers), + format_columns.ListColumn(self._subnet_from_pool.dns_nameservers), self._subnet_from_pool.enable_dhcp, self._subnet_from_pool.gateway_ip, - subnet_v2._format_host_routes(self._subnet_from_pool.host_routes), + subnet_v2.HostRoutesColumn(self._subnet_from_pool.host_routes), self._subnet_from_pool.id, self._subnet_from_pool.ip_version, self._subnet_from_pool.ipv6_address_mode, @@ -168,20 +168,20 @@ class TestCreateSubnet(TestSubnet): self._subnet_from_pool.network_id, self._subnet_from_pool.project_id, self._subnet_from_pool.segment_id, - utils.format_list(self._subnet_from_pool.service_types), + format_columns.ListColumn(self._subnet_from_pool.service_types), self._subnet_from_pool.subnetpool_id, - utils.format_list(self._subnet.tags), + format_columns.ListColumn(self._subnet_from_pool.tags), ) self.data_ipv6 = ( - subnet_v2._format_allocation_pools( + subnet_v2.AllocationPoolsColumn( self._subnet_ipv6.allocation_pools), self._subnet_ipv6.cidr, self._subnet_ipv6.description, - utils.format_list(self._subnet_ipv6.dns_nameservers), + format_columns.ListColumn(self._subnet_ipv6.dns_nameservers), self._subnet_ipv6.enable_dhcp, self._subnet_ipv6.gateway_ip, - subnet_v2._format_host_routes(self._subnet_ipv6.host_routes), + subnet_v2.HostRoutesColumn(self._subnet_ipv6.host_routes), self._subnet_ipv6.id, self._subnet_ipv6.ip_version, self._subnet_ipv6.ipv6_address_mode, @@ -190,9 +190,9 @@ class TestCreateSubnet(TestSubnet): self._subnet_ipv6.network_id, self._subnet_ipv6.project_id, self._subnet_ipv6.segment_id, - utils.format_list(self._subnet_ipv6.service_types), + format_columns.ListColumn(self._subnet_ipv6.service_types), self._subnet_ipv6.subnetpool_id, - utils.format_list(self._subnet.tags), + format_columns.ListColumn(self._subnet_ipv6.tags), ) def setUp(self): @@ -255,7 +255,7 @@ class TestCreateSubnet(TestSubnet): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_from_subnet_pool_options(self): # Mock SDK calls for this test. @@ -317,7 +317,7 @@ class TestCreateSubnet(TestSubnet): 'service_types': self._subnet_from_pool.service_types, }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data_subnet_pool, data) + self.assertItemEqual(self.data_subnet_pool, data) def test_create_options_subnet_range_ipv6(self): # Mock SDK calls for this test. @@ -390,7 +390,7 @@ class TestCreateSubnet(TestSubnet): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertEqual(self.data_ipv6, data) + self.assertItemEqual(self.data_ipv6, data) def test_create_with_network_segment(self): # Mock SDK calls for this test. @@ -424,7 +424,7 @@ class TestCreateSubnet(TestSubnet): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_with_description(self): # Mock SDK calls for this test. @@ -458,7 +458,7 @@ class TestCreateSubnet(TestSubnet): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def _test_create_with_tag(self, add_tags=True): arglist = [ @@ -497,7 +497,7 @@ class TestCreateSubnet(TestSubnet): else: self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_with_tags(self): self._test_create_with_tag(add_tags=True) @@ -625,13 +625,13 @@ class TestListSubnet(TestSubnet): subnet.cidr, subnet.tenant_id, subnet.enable_dhcp, - utils.format_list(subnet.dns_nameservers), - subnet_v2._format_allocation_pools(subnet.allocation_pools), - utils.format_list(subnet.host_routes), + format_columns.ListColumn(subnet.dns_nameservers), + subnet_v2.AllocationPoolsColumn(subnet.allocation_pools), + subnet_v2.HostRoutesColumn(subnet.host_routes), subnet.ip_version, subnet.gateway_ip, - utils.format_list(subnet.service_types), - utils.format_list(subnet.tags), + format_columns.ListColumn(subnet.service_types), + format_columns.ListColumn(subnet.tags), )) def setUp(self): @@ -653,7 +653,7 @@ class TestListSubnet(TestSubnet): self.network.subnets.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_list_long(self): arglist = [ @@ -668,7 +668,7 @@ class TestListSubnet(TestSubnet): self.network.subnets.assert_called_once_with() self.assertEqual(self.columns_long, columns) - self.assertEqual(self.data_long, list(data)) + self.assertListItemEqual(self.data_long, list(data)) def test_subnet_list_ip_version(self): arglist = [ @@ -684,7 +684,7 @@ class TestListSubnet(TestSubnet): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_list_dhcp(self): arglist = [ @@ -700,7 +700,7 @@ class TestListSubnet(TestSubnet): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_list_no_dhcp(self): arglist = [ @@ -716,7 +716,7 @@ class TestListSubnet(TestSubnet): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_list_service_type(self): arglist = [ @@ -731,7 +731,7 @@ class TestListSubnet(TestSubnet): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_list_project(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -749,7 +749,7 @@ class TestListSubnet(TestSubnet): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_list_service_type_multiple(self): arglist = [ @@ -767,7 +767,7 @@ class TestListSubnet(TestSubnet): 'network:floatingip_agent_gateway']} self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_list_project_domain(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -787,7 +787,7 @@ class TestListSubnet(TestSubnet): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_list_network(self): network = network_fakes.FakeNetwork.create_one_network() @@ -805,7 +805,7 @@ class TestListSubnet(TestSubnet): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_list_gateway(self): subnet = network_fakes.FakeSubnet.create_one_subnet() @@ -823,7 +823,7 @@ class TestListSubnet(TestSubnet): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_list_name(self): subnet = network_fakes.FakeSubnet.create_one_subnet() @@ -841,7 +841,7 @@ class TestListSubnet(TestSubnet): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_list_subnet_range(self): subnet = network_fakes.FakeSubnet.create_one_subnet() @@ -859,7 +859,7 @@ class TestListSubnet(TestSubnet): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_list_with_tag_options(self): arglist = [ @@ -1154,13 +1154,13 @@ class TestShowSubnet(TestSubnet): ) data = ( - subnet_v2._format_allocation_pools(_subnet.allocation_pools), + subnet_v2.AllocationPoolsColumn(_subnet.allocation_pools), _subnet.cidr, _subnet.description, - utils.format_list(_subnet.dns_nameservers), + format_columns.ListColumn(_subnet.dns_nameservers), _subnet.enable_dhcp, _subnet.gateway_ip, - utils.format_list(_subnet.host_routes), + subnet_v2.HostRoutesColumn(_subnet.host_routes), _subnet.id, _subnet.ip_version, _subnet.ipv6_address_mode, @@ -1169,9 +1169,9 @@ class TestShowSubnet(TestSubnet): _subnet.network_id, _subnet.tenant_id, _subnet.segment_id, - utils.format_list(_subnet.service_types), + format_columns.ListColumn(_subnet.service_types), _subnet.subnetpool_id, - utils.format_list(_subnet.tags), + format_columns.ListColumn(_subnet.tags), ) def setUp(self): @@ -1206,7 +1206,7 @@ class TestShowSubnet(TestSubnet): self._subnet.name, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) class TestUnsetSubnet(TestSubnet): diff --git a/openstackclient/tests/unit/network/v2/test_subnet_pool.py b/openstackclient/tests/unit/network/v2/test_subnet_pool.py index 3d8f102..2271c08 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/unit/network/v2/test_subnet_pool.py @@ -15,8 +15,9 @@ import argparse import mock from mock import call + +from osc_lib.cli import format_columns from osc_lib import exceptions -from osc_lib import utils from openstackclient.network.v2 import subnet_pool from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 @@ -73,10 +74,10 @@ class TestCreateSubnetPool(TestSubnetPool): _subnet_pool.max_prefixlen, _subnet_pool.min_prefixlen, _subnet_pool.name, - utils.format_list(_subnet_pool.prefixes), + format_columns.ListColumn(_subnet_pool.prefixes), _subnet_pool.project_id, _subnet_pool.shared, - utils.format_list(_subnet_pool.tags), + format_columns.ListColumn(_subnet_pool.tags), ) def setUp(self): @@ -133,7 +134,7 @@ class TestCreateSubnetPool(TestSubnetPool): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_prefixlen_options(self): arglist = [ @@ -163,7 +164,7 @@ class TestCreateSubnetPool(TestSubnetPool): 'name': self._subnet_pool.name, }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_len_negative(self): arglist = [ @@ -201,7 +202,7 @@ class TestCreateSubnetPool(TestSubnetPool): 'name': self._subnet_pool.name, }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_address_scope_option(self): arglist = [ @@ -224,7 +225,7 @@ class TestCreateSubnetPool(TestSubnetPool): 'name': self._subnet_pool.name, }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_default_and_shared_options(self): arglist = [ @@ -250,7 +251,7 @@ class TestCreateSubnetPool(TestSubnetPool): 'shared': True, }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_with_description(self): arglist = [ @@ -273,7 +274,7 @@ class TestCreateSubnetPool(TestSubnetPool): 'description': self._subnet_pool.description, }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_with_default_quota(self): arglist = [ @@ -294,7 +295,7 @@ class TestCreateSubnetPool(TestSubnetPool): 'default_quota': 10, }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def _test_create_with_tag(self, add_tags=True): arglist = [ @@ -328,7 +329,7 @@ class TestCreateSubnetPool(TestSubnetPool): else: self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_with_tags(self): self._test_create_with_tag(add_tags=True) @@ -441,7 +442,7 @@ class TestListSubnetPool(TestSubnetPool): data.append(( pool.id, pool.name, - utils.format_list(pool.prefixes), + format_columns.ListColumn(pool.prefixes), )) data_long = [] @@ -449,12 +450,12 @@ class TestListSubnetPool(TestSubnetPool): data_long.append(( pool.id, pool.name, - utils.format_list(pool.prefixes), + format_columns.ListColumn(pool.prefixes), pool.default_prefixlen, pool.address_scope_id, pool.is_default, pool.shared, - utils.format_list(pool.tags), + format_columns.ListColumn(pool.tags), )) def setUp(self): @@ -476,7 +477,7 @@ class TestListSubnetPool(TestSubnetPool): self.network.subnet_pools.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_pool_list_long(self): arglist = [ @@ -491,7 +492,7 @@ class TestListSubnetPool(TestSubnetPool): self.network.subnet_pools.assert_called_once_with() self.assertEqual(self.columns_long, columns) - self.assertEqual(self.data_long, list(data)) + self.assertListItemEqual(self.data_long, list(data)) def test_subnet_pool_list_no_share(self): arglist = [ @@ -507,7 +508,7 @@ class TestListSubnetPool(TestSubnetPool): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_pool_list_share(self): arglist = [ @@ -523,7 +524,7 @@ class TestListSubnetPool(TestSubnetPool): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_pool_list_no_default(self): arglist = [ @@ -539,7 +540,7 @@ class TestListSubnetPool(TestSubnetPool): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_pool_list_default(self): arglist = [ @@ -555,7 +556,7 @@ class TestListSubnetPool(TestSubnetPool): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_pool_list_project(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -573,7 +574,7 @@ class TestListSubnetPool(TestSubnetPool): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_pool_list_project_domain(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -593,7 +594,7 @@ class TestListSubnetPool(TestSubnetPool): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_pool_list_name(self): subnet_pool = network_fakes.FakeSubnetPool.create_one_subnet_pool() @@ -611,7 +612,7 @@ class TestListSubnetPool(TestSubnetPool): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_pool_list_address_scope(self): addr_scope = network_fakes.FakeAddressScope.create_one_address_scope() @@ -629,7 +630,7 @@ class TestListSubnetPool(TestSubnetPool): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_list_with_tag_options(self): arglist = [ @@ -654,7 +655,7 @@ class TestListSubnetPool(TestSubnetPool): 'not_any_tags': 'black,white'} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) class TestSetSubnetPool(TestSubnetPool): @@ -969,10 +970,10 @@ class TestShowSubnetPool(TestSubnetPool): _subnet_pool.max_prefixlen, _subnet_pool.min_prefixlen, _subnet_pool.name, - utils.format_list(_subnet_pool.prefixes), + format_columns.ListColumn(_subnet_pool.prefixes), _subnet_pool.tenant_id, _subnet_pool.shared, - utils.format_list(_subnet_pool.tags), + format_columns.ListColumn(_subnet_pool.tags), ) def setUp(self): @@ -1008,7 +1009,7 @@ class TestShowSubnetPool(TestSubnetPool): ignore_missing=False ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) class TestUnsetSubnetPool(TestSubnetPool): diff --git a/openstackclient/tests/unit/test_shell.py b/openstackclient/tests/unit/test_shell.py index 5d413e7..31675c4 100644 --- a/openstackclient/tests/unit/test_shell.py +++ b/openstackclient/tests/unit/test_shell.py @@ -153,7 +153,7 @@ class TestShell(osc_lib_test_utils.TestShell): # released in osc-lib self.shell_class = importutils.import_class(self.shell_class_name) - def _assert_token_endpoint_auth(self, cmd_options, default_args): + def _assert_admin_token_auth(self, cmd_options, default_args): with mock.patch( self.shell_class_name + ".initialize_app", self.app, @@ -172,9 +172,9 @@ class TestShell(osc_lib_test_utils.TestShell): "token", ) self.assertEqual( - default_args.get("url", ''), - _shell.options.url, - "url", + default_args.get("endpoint", ''), + _shell.options.endpoint, + "endpoint", ) def _assert_token_auth(self, cmd_options, default_args): @@ -338,7 +338,7 @@ class TestShellTokenEndpointAuthEnv(TestShell): super(TestShellTokenEndpointAuthEnv, self).setUp() env = { "OS_TOKEN": DEFAULT_TOKEN, - "OS_URL": DEFAULT_SERVICE_URL, + "OS_ENDPOINT": DEFAULT_SERVICE_URL, } self.useFixture(osc_lib_test_utils.EnvFixture(env.copy())) @@ -346,23 +346,23 @@ class TestShellTokenEndpointAuthEnv(TestShell): flag = "" kwargs = { "token": DEFAULT_TOKEN, - "url": DEFAULT_SERVICE_URL, + "endpoint": DEFAULT_SERVICE_URL, } - self._assert_token_endpoint_auth(flag, kwargs) + self._assert_admin_token_auth(flag, kwargs) def test_only_token(self): flag = "--os-token xyzpdq" kwargs = { "token": "xyzpdq", - "url": DEFAULT_SERVICE_URL, + "endpoint": DEFAULT_SERVICE_URL, } self._assert_token_auth(flag, kwargs) def test_only_url(self): - flag = "--os-url http://cloud.local:555" + flag = "--os-endpoint http://cloud.local:555" kwargs = { "token": DEFAULT_TOKEN, - "url": "http://cloud.local:555", + "endpoint": "http://cloud.local:555", } self._assert_token_auth(flag, kwargs) @@ -371,7 +371,7 @@ class TestShellTokenEndpointAuthEnv(TestShell): flag = "" kwargs = { "token": '', - "url": '', + "endpoint": '', } self._assert_token_auth(flag, kwargs) diff --git a/openstackclient/tests/unit/utils.py b/openstackclient/tests/unit/utils.py index 926dad8..c15d8bb 100644 --- a/openstackclient/tests/unit/utils.py +++ b/openstackclient/tests/unit/utils.py @@ -19,6 +19,8 @@ import os import fixtures import testtools +from cliff import columns as cliff_columns + from openstackclient.tests.unit import fakes @@ -80,3 +82,18 @@ class TestCommand(TestCase): self.assertIn(attr, parsed_args) self.assertEqual(value, getattr(parsed_args, attr)) return parsed_args + + def assertListItemEqual(self, expected, actual): + self.assertEqual(len(expected), len(actual)) + for item_expected, item_actual in zip(expected, actual): + self.assertItemEqual(item_expected, item_actual) + + def assertItemEqual(self, expected, actual): + self.assertEqual(len(expected), len(actual)) + for col_expected, col_actual in zip(expected, actual): + if isinstance(col_expected, cliff_columns.FormattableColumn): + self.assertIsInstance(col_actual, col_expected.__class__) + self.assertEqual(col_expected.human_readable(), + col_actual.human_readable()) + else: + self.assertEqual(col_expected, col_actual) diff --git a/openstackclient/tests/unit/volume/v1/test_qos_specs.py b/openstackclient/tests/unit/volume/v1/test_qos_specs.py index 442840f..11dc808 100644 --- a/openstackclient/tests/unit/volume/v1/test_qos_specs.py +++ b/openstackclient/tests/unit/volume/v1/test_qos_specs.py @@ -17,6 +17,8 @@ import copy import mock from mock import call + +from osc_lib.cli import format_columns from osc_lib import exceptions from osc_lib import utils @@ -85,7 +87,7 @@ class TestQosCreate(TestQos): self.new_qos_spec.consumer, self.new_qos_spec.id, self.new_qos_spec.name, - utils.format_dict(self.new_qos_spec.specs) + format_columns.DictColumn(self.new_qos_spec.specs) ) self.qos_mock.create.return_value = self.new_qos_spec # Get the command object to test @@ -108,7 +110,7 @@ class TestQosCreate(TestQos): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_qos_create_with_consumer(self): arglist = [ @@ -128,7 +130,7 @@ class TestQosCreate(TestQos): {'consumer': self.new_qos_spec.consumer} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_qos_create_with_properties(self): arglist = [ @@ -154,7 +156,7 @@ class TestQosCreate(TestQos): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) class TestQosDelete(TestQos): @@ -326,8 +328,8 @@ class TestQosList(TestQos): q.id, q.name, q.consumer, - qos_association.name, - utils.format_dict(q.specs), + format_columns.ListColumn([qos_association.name]), + format_columns.DictColumn(q.specs), )) def setUp(self): @@ -349,7 +351,7 @@ class TestQosList(TestQos): self.qos_mock.list.assert_called_with() self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_qos_list_no_association(self): self.qos_mock.reset_mock() @@ -373,10 +375,10 @@ class TestQosList(TestQos): self.qos_specs[1].id, self.qos_specs[1].name, self.qos_specs[1].consumer, - None, - utils.format_dict(self.qos_specs[1].specs), + format_columns.ListColumn(None), + format_columns.DictColumn(self.qos_specs[1].specs), ) - self.assertEqual(ex_data, list(data)) + self.assertListItemEqual(ex_data, list(data)) class TestQosSet(TestQos): @@ -447,13 +449,13 @@ class TestQosShow(TestQos): ) self.assertEqual(collist, columns) datalist = ( - self.qos_association.name, + format_columns.ListColumn([self.qos_association.name]), self.qos_spec.consumer, self.qos_spec.id, self.qos_spec.name, - utils.format_dict(self.qos_spec.specs), + format_columns.DictColumn(self.qos_spec.specs), ) - self.assertEqual(datalist, tuple(data)) + self.assertItemEqual(datalist, tuple(data)) class TestQosUnset(TestQos): diff --git a/openstackclient/tests/unit/volume/v1/test_snapshot.py b/openstackclient/tests/unit/volume/v1/test_snapshot.py deleted file mode 100644 index 70b55ce..0000000 --- a/openstackclient/tests/unit/volume/v1/test_snapshot.py +++ /dev/null @@ -1,580 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -import mock -from mock import call - -from osc_lib import exceptions -from osc_lib import utils - -from openstackclient.tests.unit import utils as tests_utils -from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes -from openstackclient.volume.v1 import volume_snapshot - - -class TestSnapshot(volume_fakes.TestVolumev1): - - def setUp(self): - super(TestSnapshot, self).setUp() - - self.snapshots_mock = self.app.client_manager.volume.volume_snapshots - self.snapshots_mock.reset_mock() - self.volumes_mock = self.app.client_manager.volume.volumes - self.volumes_mock.reset_mock() - - -class TestSnapshotCreate(TestSnapshot): - - columns = ( - 'created_at', - 'display_description', - 'display_name', - 'id', - 'properties', - 'size', - 'status', - 'volume_id', - ) - - def setUp(self): - super(TestSnapshotCreate, self).setUp() - - self.volume = volume_fakes.FakeVolume.create_one_volume() - self.new_snapshot = volume_fakes.FakeSnapshot.create_one_snapshot( - attrs={'volume_id': self.volume.id}) - - self.data = ( - self.new_snapshot.created_at, - self.new_snapshot.display_description, - self.new_snapshot.display_name, - self.new_snapshot.id, - utils.format_dict(self.new_snapshot.metadata), - self.new_snapshot.size, - self.new_snapshot.status, - self.new_snapshot.volume_id, - ) - - self.volumes_mock.get.return_value = self.volume - self.snapshots_mock.create.return_value = self.new_snapshot - # Get the command object to test - self.cmd = volume_snapshot.CreateVolumeSnapshot(self.app, None) - - def test_snapshot_create(self): - arglist = [ - "--volume", self.new_snapshot.volume_id, - "--description", self.new_snapshot.display_description, - "--force", - self.new_snapshot.display_name, - ] - verifylist = [ - ("volume", self.new_snapshot.volume_id), - ("description", self.new_snapshot.display_description), - ("force", True), - ("snapshot_name", self.new_snapshot.display_name), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.snapshots_mock.create.assert_called_with( - self.new_snapshot.volume_id, - True, - self.new_snapshot.display_name, - self.new_snapshot.display_description, - ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) - - def test_snapshot_create_without_name(self): - arglist = [ - "--volume", self.new_snapshot.volume_id, - ] - verifylist = [ - ("volume", self.new_snapshot.volume_id), - ] - self.assertRaises( - tests_utils.ParserException, - self.check_parser, - self.cmd, - arglist, - verifylist, - ) - - def test_snapshot_create_without_volume(self): - arglist = [ - "--description", self.new_snapshot.display_description, - "--force", - self.new_snapshot.display_name - ] - verifylist = [ - ("description", self.new_snapshot.display_description), - ("force", True), - ("snapshot_name", self.new_snapshot.display_name) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.volumes_mock.get.assert_called_once_with( - self.new_snapshot.display_name) - self.snapshots_mock.create.assert_called_once_with( - self.new_snapshot.volume_id, - True, - self.new_snapshot.display_name, - self.new_snapshot.display_description, - ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) - - -class TestSnapshotDelete(TestSnapshot): - - snapshots = volume_fakes.FakeSnapshot.create_snapshots(count=2) - - def setUp(self): - super(TestSnapshotDelete, self).setUp() - - self.snapshots_mock.get = ( - volume_fakes.FakeSnapshot.get_snapshots(self.snapshots)) - self.snapshots_mock.delete.return_value = None - - # Get the command object to mock - self.cmd = volume_snapshot.DeleteVolumeSnapshot(self.app, None) - - def test_snapshot_delete(self): - arglist = [ - self.snapshots[0].id - ] - verifylist = [ - ("snapshots", [self.snapshots[0].id]) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.snapshots_mock.delete.assert_called_with( - self.snapshots[0].id) - self.assertIsNone(result) - - def test_delete_multiple_snapshots(self): - arglist = [] - for s in self.snapshots: - arglist.append(s.id) - verifylist = [ - ('snapshots', arglist), - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) - - calls = [] - for s in self.snapshots: - calls.append(call(s.id)) - self.snapshots_mock.delete.assert_has_calls(calls) - self.assertIsNone(result) - - def test_delete_multiple_snapshots_with_exception(self): - arglist = [ - self.snapshots[0].id, - 'unexist_snapshot', - ] - verifylist = [ - ('snapshots', arglist), - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - find_mock_result = [self.snapshots[0], exceptions.CommandError] - with mock.patch.object(utils, 'find_resource', - side_effect=find_mock_result) as find_mock: - try: - self.cmd.take_action(parsed_args) - self.fail('CommandError should be raised.') - except exceptions.CommandError as e: - self.assertEqual('1 of 2 snapshots failed to delete.', - str(e)) - - find_mock.assert_any_call( - self.snapshots_mock, self.snapshots[0].id) - find_mock.assert_any_call(self.snapshots_mock, 'unexist_snapshot') - - self.assertEqual(2, find_mock.call_count) - self.snapshots_mock.delete.assert_called_once_with( - self.snapshots[0].id - ) - - -class TestSnapshotList(TestSnapshot): - - volume = volume_fakes.FakeVolume.create_one_volume() - snapshots = volume_fakes.FakeSnapshot.create_snapshots( - attrs={'volume_id': volume.display_name}, count=3) - - columns = [ - "ID", - "Name", - "Description", - "Status", - "Size" - ] - columns_long = columns + [ - "Created At", - "Volume", - "Properties" - ] - - data = [] - for s in snapshots: - data.append(( - s.id, - s.display_name, - s.display_description, - s.status, - s.size, - )) - data_long = [] - for s in snapshots: - data_long.append(( - s.id, - s.display_name, - s.display_description, - s.status, - s.size, - s.created_at, - s.volume_id, - utils.format_dict(s.metadata), - )) - - def setUp(self): - super(TestSnapshotList, self).setUp() - - self.volumes_mock.list.return_value = [self.volume] - self.volumes_mock.get.return_value = self.volume - self.snapshots_mock.list.return_value = self.snapshots - # Get the command to test - self.cmd = volume_snapshot.ListVolumeSnapshot(self.app, None) - - def test_snapshot_list_without_options(self): - arglist = [] - verifylist = [ - ('all_projects', False), - ("long", False) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.snapshots_mock.list.assert_called_once_with( - search_opts={ - 'all_tenants': False, - 'display_name': None, - 'status': None, - 'volume_id': None - } - ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) - - def test_snapshot_list_with_long(self): - arglist = [ - "--long", - ] - verifylist = [ - ("long", True), - ('all_projects', False), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.snapshots_mock.list.assert_called_once_with( - search_opts={ - 'all_tenants': False, - 'display_name': None, - 'status': None, - 'volume_id': None - } - ) - self.assertEqual(self.columns_long, columns) - self.assertEqual(self.data_long, list(data)) - - def test_snapshot_list_name_option(self): - arglist = [ - '--name', self.snapshots[0].display_name, - ] - verifylist = [ - ('all_projects', False), - ('long', False), - ('name', self.snapshots[0].display_name), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.snapshots_mock.list.assert_called_once_with( - search_opts={ - 'all_tenants': False, - 'display_name': self.snapshots[0].display_name, - 'status': None, - 'volume_id': None - } - ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) - - def test_snapshot_list_status_option(self): - arglist = [ - '--status', self.snapshots[0].status, - ] - verifylist = [ - ('all_projects', False), - ('long', False), - ('status', self.snapshots[0].status), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.snapshots_mock.list.assert_called_once_with( - search_opts={ - 'all_tenants': False, - 'display_name': None, - 'status': self.snapshots[0].status, - 'volume_id': None - } - ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) - - def test_snapshot_list_volumeid_option(self): - arglist = [ - '--volume', self.volume.id, - ] - verifylist = [ - ('all_projects', False), - ('long', False), - ('volume', self.volume.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.snapshots_mock.list.assert_called_once_with( - search_opts={ - 'all_tenants': False, - 'display_name': None, - 'status': None, - 'volume_id': self.volume.id - } - ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) - - def test_snapshot_list_all_projects(self): - arglist = [ - '--all-projects', - ] - verifylist = [ - ('long', False), - ('all_projects', True) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.snapshots_mock.list.assert_called_once_with( - search_opts={ - 'all_tenants': True, - 'display_name': None, - 'status': None, - 'volume_id': None - } - ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) - - -class TestSnapshotSet(TestSnapshot): - - snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() - - def setUp(self): - super(TestSnapshotSet, self).setUp() - - self.snapshots_mock.get.return_value = self.snapshot - self.snapshots_mock.set_metadata.return_value = None - # Get the command object to mock - self.cmd = volume_snapshot.SetVolumeSnapshot(self.app, None) - - def test_snapshot_set_all(self): - arglist = [ - "--name", "new_snapshot", - "--description", "new_description", - "--property", "foo_1=foo_1", - "--property", "foo_2=foo_2", - "--no-property", - self.snapshot.id, - ] - new_property = {"foo_1": "foo_1", "foo_2": "foo_2"} - verifylist = [ - ("name", "new_snapshot"), - ("description", "new_description"), - ("property", new_property), - ("no_property", True), - ("snapshot", self.snapshot.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - kwargs = { - "display_name": "new_snapshot", - "display_description": "new_description", - } - self.snapshot.update.assert_called_with(**kwargs) - self.snapshots_mock.delete_metadata.assert_called_with( - self.snapshot.id, ["foo"] - ) - self.snapshots_mock.set_metadata.assert_called_with( - self.snapshot.id, {"foo_2": "foo_2", "foo_1": "foo_1"} - ) - self.assertIsNone(result) - - def test_snapshot_set_nothing(self): - arglist = [ - self.snapshot.id, - ] - verifylist = [ - ("snapshot", self.snapshot.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - self.assertIsNone(result) - - def test_snapshot_set_fail(self): - self.snapshots_mock.set_metadata.side_effect = ( - exceptions.CommandError()) - arglist = [ - "--name", "new_snapshot", - "--description", "new_description", - "--property", "x=y", - "--property", "foo=foo", - self.snapshot.id, - ] - new_property = {"x": "y", "foo": "foo"} - verifylist = [ - ("name", "new_snapshot"), - ("description", "new_description"), - ("property", new_property), - ("snapshot", self.snapshot.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.assertRaises(exceptions.CommandError, - self.cmd.take_action, parsed_args) - - -class TestSnapshotShow(TestSnapshot): - - columns = ( - 'created_at', - 'display_description', - 'display_name', - 'id', - 'properties', - 'size', - 'status', - 'volume_id', - ) - - def setUp(self): - super(TestSnapshotShow, self).setUp() - - self.snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() - - self.data = ( - self.snapshot.created_at, - self.snapshot.display_description, - self.snapshot.display_name, - self.snapshot.id, - utils.format_dict(self.snapshot.metadata), - self.snapshot.size, - self.snapshot.status, - self.snapshot.volume_id, - ) - - self.snapshots_mock.get.return_value = self.snapshot - # Get the command object to test - self.cmd = volume_snapshot.ShowVolumeSnapshot(self.app, None) - - def test_snapshot_show(self): - arglist = [ - self.snapshot.id - ] - verifylist = [ - ("snapshot", self.snapshot.id) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - self.snapshots_mock.get.assert_called_with(self.snapshot.id) - - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) - - -class TestSnapshotUnset(TestSnapshot): - - snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() - - def setUp(self): - super(TestSnapshotUnset, self).setUp() - - self.snapshots_mock.get.return_value = self.snapshot - self.snapshots_mock.delete_metadata.return_value = None - # Get the command object to mock - self.cmd = volume_snapshot.UnsetVolumeSnapshot(self.app, None) - - def test_snapshot_unset(self): - arglist = [ - "--property", "foo", - self.snapshot.id, - ] - verifylist = [ - ("property", ["foo"]), - ("snapshot", self.snapshot.id), - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.snapshots_mock.delete_metadata.assert_called_with( - self.snapshot.id, ["foo"] - ) - self.assertIsNone(result) - - def test_snapshot_unset_nothing(self): - arglist = [ - self.snapshot.id, - ] - verifylist = [ - ("snapshot", self.snapshot.id), - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - self.assertIsNone(result) diff --git a/openstackclient/tests/unit/volume/v1/test_transfer_request.py b/openstackclient/tests/unit/volume/v1/test_transfer_request.py index 4c013dc..680561d 100644 --- a/openstackclient/tests/unit/volume/v1/test_transfer_request.py +++ b/openstackclient/tests/unit/volume/v1/test_transfer_request.py @@ -85,26 +85,6 @@ class TestTransferAccept(TestTransfer): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) - def test_transfer_accept_deprecated(self): - arglist = [ - self.volume_transfer.id, - 'key_value', - ] - verifylist = [ - ('transfer_request', self.volume_transfer.id), - ('old_auth_key', 'key_value'), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.transfer_mock.accept.assert_called_once_with( - self.volume_transfer.id, - 'key_value', - ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) - def test_transfer_accept_no_option(self): arglist = [ self.volume_transfer.id, diff --git a/openstackclient/tests/unit/volume/v1/test_type.py b/openstackclient/tests/unit/volume/v1/test_type.py index dcdd3d5..beff833 100644 --- a/openstackclient/tests/unit/volume/v1/test_type.py +++ b/openstackclient/tests/unit/volume/v1/test_type.py @@ -15,6 +15,7 @@ import mock from mock import call +from osc_lib.cli import format_columns from osc_lib import exceptions from osc_lib import utils @@ -77,7 +78,7 @@ class TestTypeCreate(TestType): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_type_create_with_encryption(self): encryption_info = { @@ -102,7 +103,7 @@ class TestTypeCreate(TestType): ) encryption_data = ( self.new_volume_type.description, - utils.format_dict(encryption_info), + format_columns.DictColumn(encryption_info), self.new_volume_type.id, True, self.new_volume_type.name, @@ -138,7 +139,7 @@ class TestTypeCreate(TestType): body, ) self.assertEqual(encryption_columns, columns) - self.assertEqual(encryption_data, data) + self.assertItemEqual(encryption_data, data) class TestTypeDelete(TestType): @@ -246,7 +247,7 @@ class TestTypeList(TestType): t.id, t.name, t.is_public, - utils.format_dict(t.extra_specs), + format_columns.DictColumn(t.extra_specs), )) def setUp(self): @@ -269,7 +270,7 @@ class TestTypeList(TestType): columns, data = self.cmd.take_action(parsed_args) self.types_mock.list.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_type_list_with_options(self): arglist = [ @@ -283,7 +284,7 @@ class TestTypeList(TestType): columns, data = self.cmd.take_action(parsed_args) self.types_mock.list.assert_called_once_with() self.assertEqual(self.columns_long, columns) - self.assertEqual(self.data_long, list(data)) + self.assertListItemEqual(self.data_long, list(data)) def test_type_list_with_encryption(self): encryption_type = volume_fakes.FakeType.create_one_encryption_type( @@ -302,13 +303,16 @@ class TestTypeList(TestType): self.volume_types[0].id, self.volume_types[0].name, self.volume_types[0].is_public, - utils.format_dict(encryption_info), + volume_type.EncryptionInfoColumn( + self.volume_types[0].id, + {self.volume_types[0].id: encryption_info}), )) encryption_data.append(( self.volume_types[1].id, self.volume_types[1].name, self.volume_types[1].is_public, - '-', + volume_type.EncryptionInfoColumn( + self.volume_types[1].id, {}), )) self.encryption_types_mock.list.return_value = [encryption_type] @@ -324,7 +328,7 @@ class TestTypeList(TestType): self.encryption_types_mock.list.assert_called_once_with() self.types_mock.list.assert_called_once_with() self.assertEqual(encryption_columns, columns) - self.assertEqual(encryption_data, list(data)) + self.assertListItemEqual(encryption_data, list(data)) class TestTypeSet(TestType): @@ -443,7 +447,7 @@ class TestTypeShow(TestType): self.volume_type.id, True, self.volume_type.name, - utils.format_dict(self.volume_type.extra_specs) + format_columns.DictColumn(self.volume_type.extra_specs) ) self.types_mock.get.return_value = self.volume_type @@ -465,7 +469,7 @@ class TestTypeShow(TestType): self.types_mock.get.assert_called_with(self.volume_type.id) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_type_show_with_encryption(self): encryption_type = volume_fakes.FakeType.create_one_encryption_type() @@ -489,11 +493,11 @@ class TestTypeShow(TestType): ) encryption_data = ( self.volume_type.description, - utils.format_dict(encryption_info), + format_columns.DictColumn(encryption_info), self.volume_type.id, True, self.volume_type.name, - utils.format_dict(self.volume_type.extra_specs) + format_columns.DictColumn(self.volume_type.extra_specs) ) arglist = [ '--encryption-type', @@ -509,7 +513,7 @@ class TestTypeShow(TestType): self.types_mock.get.assert_called_with(self.volume_type.id) self.encryption_types_mock.get.assert_called_with(self.volume_type.id) self.assertEqual(encryption_columns, columns) - self.assertEqual(encryption_data, data) + self.assertItemEqual(encryption_data, data) class TestTypeUnset(TestType): @@ -587,3 +591,30 @@ class TestTypeUnset(TestType): result = self.cmd.take_action(parsed_args) self.encryption_types_mock.delete.assert_called_with(self.volume_type) self.assertIsNone(result) + + +class TestColumns(TestType): + + def test_encryption_info_column_with_info(self): + fake_volume_type = volume_fakes.FakeType.create_one_type() + type_id = fake_volume_type.id + + encryption_info = { + 'provider': 'LuksEncryptor', + 'cipher': None, + 'key_size': None, + 'control_location': 'front-end', + } + col = volume_type.EncryptionInfoColumn(type_id, + {type_id: encryption_info}) + self.assertEqual(utils.format_dict(encryption_info), + col.human_readable()) + self.assertEqual(encryption_info, col.machine_readable()) + + def test_encryption_info_column_without_info(self): + fake_volume_type = volume_fakes.FakeType.create_one_type() + type_id = fake_volume_type.id + + col = volume_type.EncryptionInfoColumn(type_id, {}) + self.assertEqual('-', col.human_readable()) + self.assertIsNone(col.machine_readable()) diff --git a/openstackclient/tests/unit/volume/v1/test_volume.py b/openstackclient/tests/unit/volume/v1/test_volume.py index eee5acd..c415455 100644 --- a/openstackclient/tests/unit/volume/v1/test_volume.py +++ b/openstackclient/tests/unit/volume/v1/test_volume.py @@ -17,6 +17,8 @@ import argparse import mock from mock import call + +from osc_lib.cli import format_columns from osc_lib import exceptions from osc_lib import utils @@ -88,7 +90,7 @@ class TestVolumeCreate(TestVolume): self.new_volume.display_description, self.new_volume.id, self.new_volume.display_name, - utils.format_dict(self.new_volume.metadata), + format_columns.DictColumn(self.new_volume.metadata), self.new_volume.size, self.new_volume.snapshot_id, self.new_volume.status, @@ -134,7 +136,7 @@ class TestVolumeCreate(TestVolume): None, ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_volume_create_options(self): arglist = [ @@ -178,7 +180,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_volume_create_user_project_id(self): # Return a project @@ -225,7 +227,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_volume_create_user_project_name(self): # Return a project @@ -272,7 +274,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_volume_create_properties(self): arglist = [ @@ -313,7 +315,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_volume_create_image_id(self): image = image_fakes.FakeImage.create_one_image() @@ -356,7 +358,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_volume_create_image_name(self): image = image_fakes.FakeImage.create_one_image() @@ -399,7 +401,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_volume_create_with_source(self): self.volumes_mock.get.return_value = self.new_volume @@ -429,7 +431,7 @@ class TestVolumeCreate(TestVolume): None, ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_volume_create_with_bootable_and_readonly(self): arglist = [ @@ -467,7 +469,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) self.volumes_mock.set_bootable.assert_called_with( self.new_volume.id, True) self.volumes_mock.update_readonly_flag.assert_called_with( @@ -509,7 +511,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) self.volumes_mock.set_bootable.assert_called_with( self.new_volume.id, False) self.volumes_mock.update_readonly_flag.assert_called_with( @@ -561,7 +563,7 @@ class TestVolumeCreate(TestVolume): self.assertEqual(2, mock_error.call_count) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) self.volumes_mock.set_bootable.assert_called_with( self.new_volume.id, True) self.volumes_mock.update_readonly_flag.assert_called_with( @@ -732,16 +734,13 @@ class TestVolumeList(TestVolume): 'Size', 'Attached to', ) - server = _volume.attachments[0]['server_id'] - device = _volume.attachments[0]['device'] - msg = 'Attached to %s on %s ' % (server, device) datalist = ( ( _volume.id, _volume.display_name, _volume.status, _volume.size, - msg, + volume.AttachmentsColumn(_volume.attachments), ), ) @@ -767,7 +766,7 @@ class TestVolumeList(TestVolume): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, tuple(data)) + self.assertListItemEqual(self.datalist, tuple(data)) def test_volume_list_name(self): arglist = [ @@ -784,7 +783,7 @@ class TestVolumeList(TestVolume): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, tuple(columns)) - self.assertEqual(self.datalist, tuple(data)) + self.assertListItemEqual(self.datalist, tuple(data)) def test_volume_list_status(self): arglist = [ @@ -801,7 +800,7 @@ class TestVolumeList(TestVolume): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, tuple(columns)) - self.assertEqual(self.datalist, tuple(data)) + self.assertListItemEqual(self.datalist, tuple(data)) def test_volume_list_all_projects(self): arglist = [ @@ -818,7 +817,7 @@ class TestVolumeList(TestVolume): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, tuple(columns)) - self.assertEqual(self.datalist, tuple(data)) + self.assertListItemEqual(self.datalist, tuple(data)) def test_volume_list_long(self): arglist = [ @@ -855,10 +854,10 @@ class TestVolumeList(TestVolume): self._volume.size, self._volume.volume_type, self._volume.bootable, - self.msg, - utils.format_dict(self._volume.metadata), + volume.AttachmentsColumn(self._volume.attachments), + format_columns.DictColumn(self._volume.metadata), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) def test_volume_list_with_limit(self): arglist = [ @@ -883,7 +882,7 @@ class TestVolumeList(TestVolume): 'all_tenants': False, } ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, tuple(data)) + self.assertListItemEqual(self.datalist, tuple(data)) def test_volume_list_negative_limit(self): arglist = [ @@ -1251,7 +1250,7 @@ class TestVolumeShow(TestVolume): self._volume.display_description, self._volume.id, self._volume.display_name, - utils.format_dict(self._volume.metadata), + format_columns.DictColumn(self._volume.metadata), self._volume.size, self._volume.snapshot_id, self._volume.status, @@ -1274,7 +1273,7 @@ class TestVolumeShow(TestVolume): self.volumes_mock.get.assert_called_with(self._volume.id) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_volume_show_backward_compatibility(self): arglist = [ @@ -1339,3 +1338,31 @@ class TestVolumeUnset(TestVolume): self._volume.id, ['myprop'] ) self.assertIsNone(result) + + +class TestColumns(TestVolume): + + def test_attachments_column_without_server_cache(self): + _volume = volume_fakes.FakeVolume.create_one_volume() + server_id = _volume.attachments[0]['server_id'] + device = _volume.attachments[0]['device'] + + col = volume.AttachmentsColumn(_volume.attachments, {}) + self.assertEqual('Attached to %s on %s ' % (server_id, device), + col.human_readable()) + self.assertEqual(_volume.attachments, col.machine_readable()) + + def test_attachments_column_with_server_cache(self): + _volume = volume_fakes.FakeVolume.create_one_volume() + + server_id = _volume.attachments[0]['server_id'] + device = _volume.attachments[0]['device'] + fake_server = mock.Mock() + fake_server.name = 'fake-server-name' + server_cache = {server_id: fake_server} + + col = volume.AttachmentsColumn(_volume.attachments, server_cache) + self.assertEqual( + 'Attached to %s on %s ' % ('fake-server-name', device), + col.human_readable()) + self.assertEqual(_volume.attachments, col.machine_readable()) diff --git a/openstackclient/tests/unit/volume/v1/test_backup.py b/openstackclient/tests/unit/volume/v1/test_volume_backup.py index 1097d3f..20be6e4 100644 --- a/openstackclient/tests/unit/volume/v1/test_backup.py +++ b/openstackclient/tests/unit/volume/v1/test_volume_backup.py @@ -19,7 +19,7 @@ from osc_lib import exceptions from osc_lib import utils from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes -from openstackclient.volume.v1 import backup +from openstackclient.volume.v1 import volume_backup class TestBackup(volume_fakes.TestVolumev1): @@ -74,7 +74,7 @@ class TestBackupCreate(TestBackup): self.backups_mock.create.return_value = self.new_backup # Get the command object to test - self.cmd = backup.CreateVolumeBackup(self.app, None) + self.cmd = volume_backup.CreateVolumeBackup(self.app, None) def test_backup_create(self): arglist = [ @@ -100,7 +100,7 @@ class TestBackupCreate(TestBackup): self.new_backup.description, ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_backup_create_without_name(self): arglist = [ @@ -124,7 +124,7 @@ class TestBackupCreate(TestBackup): self.new_backup.description, ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) class TestBackupDelete(TestBackup): @@ -139,7 +139,7 @@ class TestBackupDelete(TestBackup): self.backups_mock.delete.return_value = None # Get the command object to mock - self.cmd = backup.DeleteVolumeBackup(self.app, None) + self.cmd = volume_backup.DeleteVolumeBackup(self.app, None) def test_backup_delete(self): arglist = [ @@ -240,7 +240,7 @@ class TestBackupList(TestBackup): b.status, b.size, b.availability_zone, - b.volume_id, + volume_backup.VolumeIdColumn(b.volume_id), b.container, )) @@ -251,7 +251,7 @@ class TestBackupList(TestBackup): self.backups_mock.list.return_value = self.backups self.volumes_mock.get.return_value = self.volume # Get the command to test - self.cmd = backup.ListVolumeBackup(self.app, None) + self.cmd = volume_backup.ListVolumeBackup(self.app, None) def test_backup_list_without_options(self): arglist = [] @@ -277,7 +277,7 @@ class TestBackupList(TestBackup): search_opts=search_opts, ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_backup_list_with_options(self): arglist = [ @@ -309,7 +309,7 @@ class TestBackupList(TestBackup): search_opts=search_opts, ) self.assertEqual(self.columns_long, columns) - self.assertEqual(self.data_long, list(data)) + self.assertListItemEqual(self.data_long, list(data)) class TestBackupRestore(TestBackup): @@ -325,7 +325,7 @@ class TestBackupRestore(TestBackup): self.volumes_mock.get.return_value = self.volume self.restores_mock.restore.return_value = None # Get the command object to mock - self.cmd = backup.RestoreVolumeBackup(self.app, None) + self.cmd = volume_backup.RestoreVolumeBackup(self.app, None) def test_backup_restore(self): arglist = [ @@ -376,7 +376,7 @@ class TestBackupShow(TestBackup): ) self.backups_mock.get.return_value = self.backup # Get the command object to test - self.cmd = backup.ShowVolumeBackup(self.app, None) + self.cmd = volume_backup.ShowVolumeBackup(self.app, None) def test_backup_show(self): arglist = [ @@ -391,4 +391,4 @@ class TestBackupShow(TestBackup): self.backups_mock.get.assert_called_with(self.backup.id) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index c245cbf..5f976b0 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -17,7 +17,8 @@ import random import uuid import mock -from osc_lib import utils as common_utils + +from osc_lib.cli import format_columns from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes @@ -468,7 +469,7 @@ class FakeVolume(object): if x == 'tags': # The 'tags' should be format_list data_list.append( - common_utils.format_list(volume.info.get(x))) + format_columns.ListColumn(volume.info.get(x))) else: data_list.append(volume.info.get(x)) return tuple(data_list) diff --git a/openstackclient/tests/unit/volume/v2/test_consistency_group.py b/openstackclient/tests/unit/volume/v2/test_consistency_group.py index 6eeeae3..d238818 100644 --- a/openstackclient/tests/unit/volume/v2/test_consistency_group.py +++ b/openstackclient/tests/unit/volume/v2/test_consistency_group.py @@ -15,6 +15,7 @@ import mock from mock import call +from osc_lib.cli import format_columns from osc_lib import exceptions from osc_lib import utils @@ -250,7 +251,7 @@ class TestConsistencyGroupCreate(TestConsistencyGroup): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_consistency_group_create_from_source(self): arglist = [ @@ -278,7 +279,7 @@ class TestConsistencyGroupCreate(TestConsistencyGroup): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_consistency_group_create_from_snapshot(self): arglist = [ @@ -306,7 +307,7 @@ class TestConsistencyGroupCreate(TestConsistencyGroup): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) class TestConsistencyGroupDelete(TestConsistencyGroup): @@ -439,7 +440,7 @@ class TestConsistencyGroupList(TestConsistencyGroup): c.availability_zone, c.name, c.description, - utils.format_list(c.volume_types) + format_columns.ListColumn(c.volume_types) )) def setUp(self): @@ -462,7 +463,7 @@ class TestConsistencyGroupList(TestConsistencyGroup): self.consistencygroups_mock.list.assert_called_once_with( detailed=True, search_opts={'all_tenants': False}) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_consistency_group_list_with_all_project(self): arglist = [ @@ -479,7 +480,7 @@ class TestConsistencyGroupList(TestConsistencyGroup): self.consistencygroups_mock.list.assert_called_once_with( detailed=True, search_opts={'all_tenants': True}) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_consistency_group_list_with_long(self): arglist = [ @@ -496,7 +497,7 @@ class TestConsistencyGroupList(TestConsistencyGroup): self.consistencygroups_mock.list.assert_called_once_with( detailed=True, search_opts={'all_tenants': False}) self.assertEqual(self.columns_long, columns) - self.assertEqual(self.data_long, list(data)) + self.assertListItemEqual(self.data_long, list(data)) class TestConsistencyGroupRemoveVolume(TestConsistencyGroup): @@ -704,4 +705,4 @@ class TestConsistencyGroupShow(TestConsistencyGroup): self.consistencygroups_mock.get.assert_called_once_with( self.consistency_group.id) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) diff --git a/openstackclient/tests/unit/volume/v2/test_qos_specs.py b/openstackclient/tests/unit/volume/v2/test_qos_specs.py index 2b935e2..454747f 100644 --- a/openstackclient/tests/unit/volume/v2/test_qos_specs.py +++ b/openstackclient/tests/unit/volume/v2/test_qos_specs.py @@ -17,6 +17,8 @@ import copy import mock from mock import call + +from osc_lib.cli import format_columns from osc_lib import exceptions from osc_lib import utils @@ -88,7 +90,7 @@ class TestQosCreate(TestQos): self.new_qos_spec.consumer, self.new_qos_spec.id, self.new_qos_spec.name, - utils.format_dict(self.new_qos_spec.specs) + format_columns.DictColumn(self.new_qos_spec.specs) ) # Get the command object to test @@ -111,7 +113,7 @@ class TestQosCreate(TestQos): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_qos_create_with_consumer(self): arglist = [ @@ -132,7 +134,7 @@ class TestQosCreate(TestQos): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_qos_create_with_properties(self): arglist = [ @@ -158,7 +160,7 @@ class TestQosCreate(TestQos): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) class TestQosDelete(TestQos): @@ -318,8 +320,8 @@ class TestQosList(TestQos): q.id, q.name, q.consumer, - qos_association.name, - utils.format_dict(q.specs), + format_columns.ListColumn([qos_association.name]), + format_columns.DictColumn(q.specs), )) def setUp(self): @@ -341,7 +343,7 @@ class TestQosList(TestQos): self.qos_mock.list.assert_called_with() self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_qos_list_no_association(self): self.qos_mock.reset_mock() @@ -365,10 +367,10 @@ class TestQosList(TestQos): self.qos_specs[1].id, self.qos_specs[1].name, self.qos_specs[1].consumer, - None, - utils.format_dict(self.qos_specs[1].specs), + format_columns.ListColumn(None), + format_columns.DictColumn(self.qos_specs[1].specs), ) - self.assertEqual(ex_data, list(data)) + self.assertListItemEqual(ex_data, list(data)) class TestQosSet(TestQos): @@ -416,11 +418,11 @@ class TestQosShow(TestQos): 'properties' ) data = ( - qos_association.name, + format_columns.ListColumn([qos_association.name]), qos_spec.consumer, qos_spec.id, qos_spec.name, - utils.format_dict(qos_spec.specs), + format_columns.DictColumn(qos_spec.specs), ) def setUp(self): @@ -448,7 +450,7 @@ class TestQosShow(TestQos): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, tuple(data)) + self.assertItemEqual(self.data, tuple(data)) class TestQosUnset(TestQos): diff --git a/openstackclient/tests/unit/volume/v2/test_snapshot.py b/openstackclient/tests/unit/volume/v2/test_snapshot.py deleted file mode 100644 index e8f4ae5..0000000 --- a/openstackclient/tests/unit/volume/v2/test_snapshot.py +++ /dev/null @@ -1,741 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# 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 mock -from mock import call -from osc_lib import exceptions -from osc_lib import utils - -from openstackclient.tests.unit.identity.v3 import fakes as project_fakes -from openstackclient.tests.unit import utils as tests_utils -from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes -from openstackclient.volume.v2 import volume_snapshot - - -class TestSnapshot(volume_fakes.TestVolume): - - def setUp(self): - super(TestSnapshot, self).setUp() - - self.snapshots_mock = self.app.client_manager.volume.volume_snapshots - self.snapshots_mock.reset_mock() - self.volumes_mock = self.app.client_manager.volume.volumes - self.volumes_mock.reset_mock() - self.project_mock = self.app.client_manager.identity.projects - self.project_mock.reset_mock() - - -class TestSnapshotCreate(TestSnapshot): - - columns = ( - 'created_at', - 'description', - 'id', - 'name', - 'properties', - 'size', - 'status', - 'volume_id', - ) - - def setUp(self): - super(TestSnapshotCreate, self).setUp() - - self.volume = volume_fakes.FakeVolume.create_one_volume() - self.new_snapshot = volume_fakes.FakeSnapshot.create_one_snapshot( - attrs={'volume_id': self.volume.id}) - - self.data = ( - self.new_snapshot.created_at, - self.new_snapshot.description, - self.new_snapshot.id, - self.new_snapshot.name, - utils.format_dict(self.new_snapshot.metadata), - self.new_snapshot.size, - self.new_snapshot.status, - self.new_snapshot.volume_id, - ) - - self.volumes_mock.get.return_value = self.volume - self.snapshots_mock.create.return_value = self.new_snapshot - self.snapshots_mock.manage.return_value = self.new_snapshot - # Get the command object to test - self.cmd = volume_snapshot.CreateVolumeSnapshot(self.app, None) - - def test_snapshot_create(self): - arglist = [ - "--volume", self.new_snapshot.volume_id, - "--description", self.new_snapshot.description, - "--force", - '--property', 'Alpha=a', - '--property', 'Beta=b', - self.new_snapshot.name, - ] - verifylist = [ - ("volume", self.new_snapshot.volume_id), - ("description", self.new_snapshot.description), - ("force", True), - ('property', {'Alpha': 'a', 'Beta': 'b'}), - ("snapshot_name", self.new_snapshot.name), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.snapshots_mock.create.assert_called_with( - self.new_snapshot.volume_id, - force=True, - name=self.new_snapshot.name, - description=self.new_snapshot.description, - metadata={'Alpha': 'a', 'Beta': 'b'}, - ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) - - def test_snapshot_create_without_name(self): - arglist = [ - "--volume", self.new_snapshot.volume_id, - ] - verifylist = [ - ("volume", self.new_snapshot.volume_id), - ] - self.assertRaises( - tests_utils.ParserException, - self.check_parser, - self.cmd, - arglist, - verifylist, - ) - - def test_snapshot_create_without_volume(self): - arglist = [ - "--description", self.new_snapshot.description, - "--force", - self.new_snapshot.name - ] - verifylist = [ - ("description", self.new_snapshot.description), - ("force", True), - ("snapshot_name", self.new_snapshot.name) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.volumes_mock.get.assert_called_once_with( - self.new_snapshot.name) - self.snapshots_mock.create.assert_called_once_with( - self.new_snapshot.volume_id, - force=True, - name=self.new_snapshot.name, - description=self.new_snapshot.description, - metadata=None, - ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) - - def test_snapshot_create_with_remote_source(self): - arglist = [ - '--remote-source', 'source-name=test_source_name', - '--remote-source', 'source-id=test_source_id', - '--volume', self.new_snapshot.volume_id, - self.new_snapshot.name, - ] - ref_dict = {'source-name': 'test_source_name', - 'source-id': 'test_source_id'} - verifylist = [ - ('remote_source', ref_dict), - ('volume', self.new_snapshot.volume_id), - ("snapshot_name", self.new_snapshot.name), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.snapshots_mock.manage.assert_called_with( - volume_id=self.new_snapshot.volume_id, - ref=ref_dict, - name=self.new_snapshot.name, - description=None, - metadata=None, - ) - self.snapshots_mock.create.assert_not_called() - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) - - -class TestSnapshotDelete(TestSnapshot): - - snapshots = volume_fakes.FakeSnapshot.create_snapshots(count=2) - - def setUp(self): - super(TestSnapshotDelete, self).setUp() - - self.snapshots_mock.get = ( - volume_fakes.FakeSnapshot.get_snapshots(self.snapshots)) - self.snapshots_mock.delete.return_value = None - - # Get the command object to mock - self.cmd = volume_snapshot.DeleteVolumeSnapshot(self.app, None) - - def test_snapshot_delete(self): - arglist = [ - self.snapshots[0].id - ] - verifylist = [ - ("snapshots", [self.snapshots[0].id]) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.snapshots_mock.delete.assert_called_with( - self.snapshots[0].id, False) - self.assertIsNone(result) - - def test_snapshot_delete_with_force(self): - arglist = [ - '--force', - self.snapshots[0].id - ] - verifylist = [ - ('force', True), - ("snapshots", [self.snapshots[0].id]) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.snapshots_mock.delete.assert_called_with( - self.snapshots[0].id, True) - self.assertIsNone(result) - - def test_delete_multiple_snapshots(self): - arglist = [] - for s in self.snapshots: - arglist.append(s.id) - verifylist = [ - ('snapshots', arglist), - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) - - calls = [] - for s in self.snapshots: - calls.append(call(s.id, False)) - self.snapshots_mock.delete.assert_has_calls(calls) - self.assertIsNone(result) - - def test_delete_multiple_snapshots_with_exception(self): - arglist = [ - self.snapshots[0].id, - 'unexist_snapshot', - ] - verifylist = [ - ('snapshots', arglist), - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - find_mock_result = [self.snapshots[0], exceptions.CommandError] - with mock.patch.object(utils, 'find_resource', - side_effect=find_mock_result) as find_mock: - try: - self.cmd.take_action(parsed_args) - self.fail('CommandError should be raised.') - except exceptions.CommandError as e: - self.assertEqual('1 of 2 snapshots failed to delete.', - str(e)) - - find_mock.assert_any_call( - self.snapshots_mock, self.snapshots[0].id) - find_mock.assert_any_call(self.snapshots_mock, 'unexist_snapshot') - - self.assertEqual(2, find_mock.call_count) - self.snapshots_mock.delete.assert_called_once_with( - self.snapshots[0].id, False - ) - - -class TestSnapshotList(TestSnapshot): - - volume = volume_fakes.FakeVolume.create_one_volume() - project = project_fakes.FakeProject.create_one_project() - snapshots = volume_fakes.FakeSnapshot.create_snapshots( - attrs={'volume_id': volume.name}, count=3) - - columns = [ - "ID", - "Name", - "Description", - "Status", - "Size" - ] - columns_long = columns + [ - "Created At", - "Volume", - "Properties" - ] - - data = [] - for s in snapshots: - data.append(( - s.id, - s.name, - s.description, - s.status, - s.size, - )) - data_long = [] - for s in snapshots: - data_long.append(( - s.id, - s.name, - s.description, - s.status, - s.size, - s.created_at, - s.volume_id, - utils.format_dict(s.metadata), - )) - - def setUp(self): - super(TestSnapshotList, self).setUp() - - self.volumes_mock.list.return_value = [self.volume] - self.volumes_mock.get.return_value = self.volume - self.project_mock.get.return_value = self.project - self.snapshots_mock.list.return_value = self.snapshots - # Get the command to test - self.cmd = volume_snapshot.ListVolumeSnapshot(self.app, None) - - def test_snapshot_list_without_options(self): - arglist = [] - verifylist = [ - ('all_projects', False), - ('long', False) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.snapshots_mock.list.assert_called_once_with( - limit=None, marker=None, - search_opts={ - 'all_tenants': False, - 'name': None, - 'status': None, - 'project_id': None, - 'volume_id': None - } - ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) - - def test_snapshot_list_with_options(self): - arglist = [ - "--long", - "--limit", "2", - "--project", self.project.id, - "--marker", self.snapshots[0].id, - ] - verifylist = [ - ("long", True), - ("limit", 2), - ("project", self.project.id), - ("marker", self.snapshots[0].id), - ('all_projects', False), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.snapshots_mock.list.assert_called_once_with( - limit=2, - marker=self.snapshots[0].id, - search_opts={ - 'all_tenants': True, - 'project_id': self.project.id, - 'name': None, - 'status': None, - 'volume_id': None - } - ) - self.assertEqual(self.columns_long, columns) - self.assertEqual(self.data_long, list(data)) - - def test_snapshot_list_all_projects(self): - arglist = [ - '--all-projects', - ] - verifylist = [ - ('long', False), - ('all_projects', True) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.snapshots_mock.list.assert_called_once_with( - limit=None, marker=None, - search_opts={ - 'all_tenants': True, - 'name': None, - 'status': None, - 'project_id': None, - 'volume_id': None - } - ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) - - def test_snapshot_list_name_option(self): - arglist = [ - '--name', self.snapshots[0].name, - ] - verifylist = [ - ('all_projects', False), - ('long', False), - ('name', self.snapshots[0].name), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.snapshots_mock.list.assert_called_once_with( - limit=None, marker=None, - search_opts={ - 'all_tenants': False, - 'name': self.snapshots[0].name, - 'status': None, - 'project_id': None, - 'volume_id': None - } - ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) - - def test_snapshot_list_status_option(self): - arglist = [ - '--status', self.snapshots[0].status, - ] - verifylist = [ - ('all_projects', False), - ('long', False), - ('status', self.snapshots[0].status), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.snapshots_mock.list.assert_called_once_with( - limit=None, marker=None, - search_opts={ - 'all_tenants': False, - 'name': None, - 'status': self.snapshots[0].status, - 'project_id': None, - 'volume_id': None - } - ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) - - def test_snapshot_list_volumeid_option(self): - arglist = [ - '--volume', self.volume.id, - ] - verifylist = [ - ('all_projects', False), - ('long', False), - ('volume', self.volume.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.snapshots_mock.list.assert_called_once_with( - limit=None, marker=None, - search_opts={ - 'all_tenants': False, - 'name': None, - 'status': None, - 'project_id': None, - 'volume_id': self.volume.id - } - ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) - - def test_snapshot_list_negative_limit(self): - arglist = [ - "--limit", "-2", - ] - verifylist = [ - ("limit", -2), - ] - self.assertRaises(argparse.ArgumentTypeError, self.check_parser, - self.cmd, arglist, verifylist) - - -class TestSnapshotSet(TestSnapshot): - - snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() - - def setUp(self): - super(TestSnapshotSet, self).setUp() - - self.snapshots_mock.get.return_value = self.snapshot - self.snapshots_mock.set_metadata.return_value = None - self.snapshots_mock.update.return_value = None - # Get the command object to mock - self.cmd = volume_snapshot.SetVolumeSnapshot(self.app, None) - - def test_snapshot_set_no_option(self): - arglist = [ - self.snapshot.id, - ] - verifylist = [ - ("snapshot", self.snapshot.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - self.snapshots_mock.get.assert_called_once_with(parsed_args.snapshot) - self.assertNotCalled(self.snapshots_mock.reset_state) - self.assertNotCalled(self.snapshots_mock.update) - self.assertNotCalled(self.snapshots_mock.set_metadata) - self.assertIsNone(result) - - def test_snapshot_set_name_and_property(self): - arglist = [ - "--name", "new_snapshot", - "--property", "x=y", - "--property", "foo=foo", - self.snapshot.id, - ] - new_property = {"x": "y", "foo": "foo"} - verifylist = [ - ("name", "new_snapshot"), - ("property", new_property), - ("snapshot", self.snapshot.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - kwargs = { - "name": "new_snapshot", - } - self.snapshots_mock.update.assert_called_with( - self.snapshot.id, **kwargs) - self.snapshots_mock.set_metadata.assert_called_with( - self.snapshot.id, new_property - ) - self.assertIsNone(result) - - def test_snapshot_set_with_no_property(self): - arglist = [ - "--no-property", - self.snapshot.id, - ] - verifylist = [ - ("no_property", True), - ("snapshot", self.snapshot.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - self.snapshots_mock.get.assert_called_once_with(parsed_args.snapshot) - self.assertNotCalled(self.snapshots_mock.reset_state) - self.assertNotCalled(self.snapshots_mock.update) - self.assertNotCalled(self.snapshots_mock.set_metadata) - self.snapshots_mock.delete_metadata.assert_called_with( - self.snapshot.id, ["foo"] - ) - self.assertIsNone(result) - - def test_snapshot_set_with_no_property_and_property(self): - arglist = [ - "--no-property", - "--property", "foo_1=bar_1", - self.snapshot.id, - ] - verifylist = [ - ("no_property", True), - ("property", {"foo_1": "bar_1"}), - ("snapshot", self.snapshot.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - self.snapshots_mock.get.assert_called_once_with(parsed_args.snapshot) - self.assertNotCalled(self.snapshots_mock.reset_state) - self.assertNotCalled(self.snapshots_mock.update) - self.snapshots_mock.delete_metadata.assert_called_with( - self.snapshot.id, ["foo"] - ) - self.snapshots_mock.set_metadata.assert_called_once_with( - self.snapshot.id, {"foo_1": "bar_1"}) - self.assertIsNone(result) - - def test_snapshot_set_state_to_error(self): - arglist = [ - "--state", "error", - self.snapshot.id - ] - verifylist = [ - ("state", "error"), - ("snapshot", self.snapshot.id) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.snapshots_mock.reset_state.assert_called_with( - self.snapshot.id, "error") - self.assertIsNone(result) - - def test_volume_set_state_failed(self): - self.snapshots_mock.reset_state.side_effect = exceptions.CommandError() - arglist = [ - '--state', 'error', - self.snapshot.id - ] - verifylist = [ - ('state', 'error'), - ('snapshot', self.snapshot.id) - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - try: - self.cmd.take_action(parsed_args) - self.fail('CommandError should be raised.') - except exceptions.CommandError as e: - self.assertEqual('One or more of the set operations failed', - str(e)) - self.snapshots_mock.reset_state.assert_called_once_with( - self.snapshot.id, 'error') - - def test_volume_set_name_and_state_failed(self): - self.snapshots_mock.reset_state.side_effect = exceptions.CommandError() - arglist = [ - '--state', 'error', - "--name", "new_snapshot", - self.snapshot.id - ] - verifylist = [ - ('state', 'error'), - ("name", "new_snapshot"), - ('snapshot', self.snapshot.id) - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - try: - self.cmd.take_action(parsed_args) - self.fail('CommandError should be raised.') - except exceptions.CommandError as e: - self.assertEqual('One or more of the set operations failed', - str(e)) - kwargs = { - "name": "new_snapshot", - } - self.snapshots_mock.update.assert_called_once_with( - self.snapshot.id, **kwargs) - self.snapshots_mock.reset_state.assert_called_once_with( - self.snapshot.id, 'error') - - -class TestSnapshotShow(TestSnapshot): - - columns = ( - 'created_at', - 'description', - 'id', - 'name', - 'properties', - 'size', - 'status', - 'volume_id', - ) - - def setUp(self): - super(TestSnapshotShow, self).setUp() - - self.snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() - - self.data = ( - self.snapshot.created_at, - self.snapshot.description, - self.snapshot.id, - self.snapshot.name, - utils.format_dict(self.snapshot.metadata), - self.snapshot.size, - self.snapshot.status, - self.snapshot.volume_id, - ) - - self.snapshots_mock.get.return_value = self.snapshot - # Get the command object to test - self.cmd = volume_snapshot.ShowVolumeSnapshot(self.app, None) - - def test_snapshot_show(self): - arglist = [ - self.snapshot.id - ] - verifylist = [ - ("snapshot", self.snapshot.id) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - self.snapshots_mock.get.assert_called_with(self.snapshot.id) - - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) - - -class TestSnapshotUnset(TestSnapshot): - - snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() - - def setUp(self): - super(TestSnapshotUnset, self).setUp() - - self.snapshots_mock.get.return_value = self.snapshot - self.snapshots_mock.delete_metadata.return_value = None - # Get the command object to mock - self.cmd = volume_snapshot.UnsetVolumeSnapshot(self.app, None) - - def test_snapshot_unset(self): - arglist = [ - "--property", "foo", - self.snapshot.id, - ] - verifylist = [ - ("property", ["foo"]), - ("snapshot", self.snapshot.id), - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.snapshots_mock.delete_metadata.assert_called_with( - self.snapshot.id, ["foo"] - ) - self.assertIsNone(result) diff --git a/openstackclient/tests/unit/volume/v2/test_transfer_request.py b/openstackclient/tests/unit/volume/v2/test_transfer_request.py index 37eed11..1ea6648 100644 --- a/openstackclient/tests/unit/volume/v2/test_transfer_request.py +++ b/openstackclient/tests/unit/volume/v2/test_transfer_request.py @@ -18,6 +18,7 @@ from mock import call from osc_lib import exceptions from osc_lib import utils +from openstackclient.tests.unit import utils as test_utils from openstackclient.tests.unit.volume.v2 import fakes as transfer_fakes from openstackclient.volume.v2 import volume_transfer_request @@ -85,26 +86,6 @@ class TestTransferAccept(TestTransfer): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) - def test_transfer_accept_deprecated(self): - arglist = [ - self.volume_transfer.id, - 'key_value', - ] - verifylist = [ - ('transfer_request', self.volume_transfer.id), - ('old_auth_key', 'key_value'), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.transfer_mock.accept.assert_called_once_with( - self.volume_transfer.id, - 'key_value', - ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) - def test_transfer_accept_no_option(self): arglist = [ self.volume_transfer.id, @@ -112,12 +93,13 @@ class TestTransferAccept(TestTransfer): verifylist = [ ('transfer_request', self.volume_transfer.id), ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args, + test_utils.ParserException, + self.check_parser, + self.cmd, + arglist, + verifylist, ) diff --git a/openstackclient/tests/unit/volume/v2/test_type.py b/openstackclient/tests/unit/volume/v2/test_type.py index 4023d55..1791592 100644 --- a/openstackclient/tests/unit/volume/v2/test_type.py +++ b/openstackclient/tests/unit/volume/v2/test_type.py @@ -15,6 +15,7 @@ import mock from mock import call +from osc_lib.cli import format_columns from osc_lib import exceptions from osc_lib import utils @@ -92,7 +93,7 @@ class TestTypeCreate(TestType): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_type_create_private(self): arglist = [ @@ -118,7 +119,7 @@ class TestTypeCreate(TestType): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_public_type_create_with_project(self): arglist = [ @@ -158,7 +159,7 @@ class TestTypeCreate(TestType): ) encryption_data = ( self.new_volume_type.description, - utils.format_dict(encryption_info), + format_columns.DictColumn(encryption_info), self.new_volume_type.id, True, self.new_volume_type.name, @@ -195,7 +196,7 @@ class TestTypeCreate(TestType): body, ) self.assertEqual(encryption_columns, columns) - self.assertEqual(encryption_data, data) + self.assertItemEqual(encryption_data, data) class TestTypeDelete(TestType): @@ -305,7 +306,7 @@ class TestTypeList(TestType): t.name, t.is_public, t.description, - utils.format_dict(t.extra_specs), + format_columns.DictColumn(t.extra_specs), )) def setUp(self): @@ -329,7 +330,7 @@ class TestTypeList(TestType): columns, data = self.cmd.take_action(parsed_args) self.types_mock.list.assert_called_once_with(is_public=None) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_type_list_with_options(self): arglist = [ @@ -347,7 +348,7 @@ class TestTypeList(TestType): columns, data = self.cmd.take_action(parsed_args) self.types_mock.list.assert_called_once_with(is_public=True) self.assertEqual(self.columns_long, columns) - self.assertEqual(self.data_long, list(data)) + self.assertListItemEqual(self.data_long, list(data)) def test_type_list_with_private_option(self): arglist = [ @@ -364,7 +365,7 @@ class TestTypeList(TestType): columns, data = self.cmd.take_action(parsed_args) self.types_mock.list.assert_called_once_with(is_public=False) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_type_list_with_default_option(self): arglist = [ @@ -382,7 +383,7 @@ class TestTypeList(TestType): columns, data = self.cmd.take_action(parsed_args) self.types_mock.default.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertEqual(self.data_with_default_type, list(data)) + self.assertListItemEqual(self.data_with_default_type, list(data)) def test_type_list_with_encryption(self): encryption_type = volume_fakes.FakeType.create_one_encryption_type( @@ -401,13 +402,16 @@ class TestTypeList(TestType): self.volume_types[0].id, self.volume_types[0].name, self.volume_types[0].is_public, - utils.format_dict(encryption_info), + volume_type.EncryptionInfoColumn( + self.volume_types[0].id, + {self.volume_types[0].id: encryption_info}), )) encryption_data.append(( self.volume_types[1].id, self.volume_types[1].name, self.volume_types[1].is_public, - '-', + volume_type.EncryptionInfoColumn( + self.volume_types[1].id, {}), )) self.encryption_types_mock.list.return_value = [encryption_type] @@ -423,7 +427,7 @@ class TestTypeList(TestType): self.encryption_types_mock.list.assert_called_once_with() self.types_mock.list.assert_called_once_with(is_public=None) self.assertEqual(encryption_columns, columns) - self.assertEqual(encryption_data, list(data)) + self.assertListItemEqual(encryption_data, list(data)) class TestTypeSet(TestType): @@ -687,7 +691,7 @@ class TestTypeShow(TestType): self.volume_type.id, True, self.volume_type.name, - utils.format_dict(self.volume_type.extra_specs) + format_columns.DictColumn(self.volume_type.extra_specs) ) self.types_mock.get.return_value = self.volume_type @@ -709,7 +713,7 @@ class TestTypeShow(TestType): self.types_mock.get.assert_called_with(self.volume_type.id) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_type_show_with_access(self): arglist = [ @@ -735,14 +739,14 @@ class TestTypeShow(TestType): self.assertEqual(self.columns, columns) private_type_data = ( - utils.format_list([type_access_list.project_id]), + format_columns.ListColumn([type_access_list.project_id]), private_type.description, private_type.id, private_type.is_public, private_type.name, - utils.format_dict(private_type.extra_specs) + format_columns.DictColumn(private_type.extra_specs) ) - self.assertEqual(private_type_data, data) + self.assertItemEqual(private_type_data, data) def test_type_show_with_list_access_exec(self): arglist = [ @@ -772,9 +776,9 @@ class TestTypeShow(TestType): private_type.id, private_type.is_public, private_type.name, - utils.format_dict(private_type.extra_specs) + format_columns.DictColumn(private_type.extra_specs) ) - self.assertEqual(private_type_data, data) + self.assertItemEqual(private_type_data, data) def test_type_show_with_encryption(self): encryption_type = volume_fakes.FakeType.create_one_encryption_type() @@ -800,11 +804,11 @@ class TestTypeShow(TestType): encryption_data = ( None, self.volume_type.description, - utils.format_dict(encryption_info), + format_columns.DictColumn(encryption_info), self.volume_type.id, True, self.volume_type.name, - utils.format_dict(self.volume_type.extra_specs) + format_columns.DictColumn(self.volume_type.extra_specs) ) arglist = [ '--encryption-type', @@ -820,7 +824,7 @@ class TestTypeShow(TestType): self.types_mock.get.assert_called_with(self.volume_type.id) self.encryption_types_mock.get.assert_called_with(self.volume_type.id) self.assertEqual(encryption_columns, columns) - self.assertEqual(encryption_data, data) + self.assertItemEqual(encryption_data, data) class TestTypeUnset(TestType): @@ -923,3 +927,30 @@ class TestTypeUnset(TestType): result = self.cmd.take_action(parsed_args) self.encryption_types_mock.delete.assert_called_with(self.volume_type) self.assertIsNone(result) + + +class TestColumns(TestType): + + def test_encryption_info_column_with_info(self): + fake_volume_type = volume_fakes.FakeType.create_one_type() + type_id = fake_volume_type.id + + encryption_info = { + 'provider': 'LuksEncryptor', + 'cipher': None, + 'key_size': None, + 'control_location': 'front-end', + } + col = volume_type.EncryptionInfoColumn(type_id, + {type_id: encryption_info}) + self.assertEqual(utils.format_dict(encryption_info), + col.human_readable()) + self.assertEqual(encryption_info, col.machine_readable()) + + def test_encryption_info_column_without_info(self): + fake_volume_type = volume_fakes.FakeType.create_one_type() + type_id = fake_volume_type.id + + col = volume_type.EncryptionInfoColumn(type_id, {}) + self.assertEqual('-', col.human_readable()) + self.assertIsNone(col.machine_readable()) diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index dbe69ea..332b50a 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -16,6 +16,8 @@ import argparse import mock from mock import call + +from osc_lib.cli import format_columns from osc_lib import exceptions from osc_lib import utils @@ -94,7 +96,7 @@ class TestVolumeCreate(TestVolume): self.new_volume.description, self.new_volume.id, self.new_volume.name, - utils.format_dict(self.new_volume.metadata), + format_columns.DictColumn(self.new_volume.metadata), self.new_volume.size, self.new_volume.snapshot_id, self.new_volume.status, @@ -135,7 +137,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_volume_create_options(self): consistency_group = ( @@ -181,41 +183,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) - - def test_volume_create_user(self): - arglist = [ - '--size', str(self.new_volume.size), - '--user', self.user.id, - self.new_volume.name, - ] - verifylist = [ - ('size', self.new_volume.size), - ('user', self.user.id), - ('name', self.new_volume.name), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) - self.volumes_mock.create.assert_not_called() - - def test_volume_create_project(self): - arglist = [ - '--size', str(self.new_volume.size), - '--project', self.project.id, - self.new_volume.name, - ] - verifylist = [ - ('size', self.new_volume.size), - ('project', self.project.id), - ('name', self.new_volume.name), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) - self.volumes_mock.create.assert_not_called() + self.assertItemEqual(self.datalist, data) def test_volume_create_properties(self): arglist = [ @@ -251,7 +219,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_volume_create_image_id(self): image = image_fakes.FakeImage.create_one_image() @@ -289,7 +257,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_volume_create_image_name(self): image = image_fakes.FakeImage.create_one_image() @@ -327,7 +295,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_volume_create_with_snapshot(self): snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() @@ -364,7 +332,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_volume_create_with_bootable_and_readonly(self): arglist = [ @@ -402,7 +370,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) self.volumes_mock.set_bootable.assert_called_with( self.new_volume.id, True) self.volumes_mock.update_readonly_flag.assert_called_with( @@ -444,7 +412,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) self.volumes_mock.set_bootable.assert_called_with( self.new_volume.id, False) self.volumes_mock.update_readonly_flag.assert_called_with( @@ -496,7 +464,7 @@ class TestVolumeCreate(TestVolume): self.assertEqual(2, mock_error.call_count) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) self.volumes_mock.set_bootable.assert_called_with( self.new_volume.id, True) self.volumes_mock.update_readonly_flag.assert_called_with( @@ -706,17 +674,14 @@ class TestVolumeList(TestVolume): self.assertEqual(self.columns, columns) - server = self.mock_volume.attachments[0]['server_id'] - device = self.mock_volume.attachments[0]['device'] - msg = 'Attached to %s on %s ' % (server, device) datalist = (( self.mock_volume.id, self.mock_volume.name, self.mock_volume.status, self.mock_volume.size, - msg, + volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) def test_volume_list_project(self): arglist = [ @@ -749,17 +714,14 @@ class TestVolumeList(TestVolume): self.assertEqual(self.columns, columns) - server = self.mock_volume.attachments[0]['server_id'] - device = self.mock_volume.attachments[0]['device'] - msg = 'Attached to %s on %s ' % (server, device) datalist = (( self.mock_volume.id, self.mock_volume.name, self.mock_volume.status, self.mock_volume.size, - msg, + volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) def test_volume_list_project_domain(self): arglist = [ @@ -794,17 +756,14 @@ class TestVolumeList(TestVolume): self.assertEqual(self.columns, columns) - server = self.mock_volume.attachments[0]['server_id'] - device = self.mock_volume.attachments[0]['device'] - msg = 'Attached to %s on %s ' % (server, device) datalist = (( self.mock_volume.id, self.mock_volume.name, self.mock_volume.status, self.mock_volume.size, - msg, + volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) def test_volume_list_user(self): arglist = [ @@ -834,19 +793,16 @@ class TestVolumeList(TestVolume): marker=None, limit=None, ) - self.assertEqual(self.columns, columns) - server = self.mock_volume.attachments[0]['server_id'] - device = self.mock_volume.attachments[0]['device'] - msg = 'Attached to %s on %s ' % (server, device) + datalist = (( self.mock_volume.id, self.mock_volume.name, self.mock_volume.status, self.mock_volume.size, - msg, + volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) def test_volume_list_user_domain(self): arglist = [ @@ -881,17 +837,14 @@ class TestVolumeList(TestVolume): self.assertEqual(self.columns, columns) - server = self.mock_volume.attachments[0]['server_id'] - device = self.mock_volume.attachments[0]['device'] - msg = 'Attached to %s on %s ' % (server, device) datalist = (( self.mock_volume.id, self.mock_volume.name, self.mock_volume.status, self.mock_volume.size, - msg, + volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) def test_volume_list_name(self): arglist = [ @@ -924,17 +877,14 @@ class TestVolumeList(TestVolume): self.assertEqual(self.columns, columns) - server = self.mock_volume.attachments[0]['server_id'] - device = self.mock_volume.attachments[0]['device'] - msg = 'Attached to %s on %s ' % (server, device) datalist = (( self.mock_volume.id, self.mock_volume.name, self.mock_volume.status, self.mock_volume.size, - msg, + volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) def test_volume_list_status(self): arglist = [ @@ -967,17 +917,14 @@ class TestVolumeList(TestVolume): self.assertEqual(self.columns, columns) - server = self.mock_volume.attachments[0]['server_id'] - device = self.mock_volume.attachments[0]['device'] - msg = 'Attached to %s on %s ' % (server, device) datalist = (( self.mock_volume.id, self.mock_volume.name, self.mock_volume.status, self.mock_volume.size, - msg, + volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) def test_volume_list_all_projects(self): arglist = [ @@ -1010,17 +957,14 @@ class TestVolumeList(TestVolume): self.assertEqual(self.columns, columns) - server = self.mock_volume.attachments[0]['server_id'] - device = self.mock_volume.attachments[0]['device'] - msg = 'Attached to %s on %s ' % (server, device) datalist = (( self.mock_volume.id, self.mock_volume.name, self.mock_volume.status, self.mock_volume.size, - msg, + volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) def test_volume_list_long(self): arglist = [ @@ -1064,9 +1008,6 @@ class TestVolumeList(TestVolume): ] self.assertEqual(collist, columns) - server = self.mock_volume.attachments[0]['server_id'] - device = self.mock_volume.attachments[0]['device'] - msg = 'Attached to %s on %s ' % (server, device) datalist = (( self.mock_volume.id, self.mock_volume.name, @@ -1074,10 +1015,10 @@ class TestVolumeList(TestVolume): self.mock_volume.size, self.mock_volume.volume_type, self.mock_volume.bootable, - msg, - utils.format_dict(self.mock_volume.metadata), + volume.AttachmentsColumn(self.mock_volume.attachments), + format_columns.DictColumn(self.mock_volume.metadata), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) def test_volume_list_with_marker_and_limit(self): arglist = [ @@ -1098,15 +1039,12 @@ class TestVolumeList(TestVolume): self.assertEqual(self.columns, columns) - server = self.mock_volume.attachments[0]['server_id'] - device = self.mock_volume.attachments[0]['device'] - msg = 'Attached to %s on %s ' % (server, device) datalist = (( self.mock_volume.id, self.mock_volume.name, self.mock_volume.status, self.mock_volume.size, - msg, + volume.AttachmentsColumn(self.mock_volume.attachments), ), ) self.volumes_mock.list.assert_called_once_with( @@ -1119,7 +1057,7 @@ class TestVolumeList(TestVolume): 'name': None, 'all_tenants': False, } ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) def test_volume_list_negative_limit(self): arglist = [ @@ -1513,7 +1451,7 @@ class TestVolumeShow(TestVolume): volume_fakes.FakeVolume.get_volume_columns(self._volume), columns) - self.assertEqual( + self.assertItemEqual( volume_fakes.FakeVolume.get_volume_data(self._volume), data) @@ -1596,3 +1534,31 @@ class TestVolumeUnset(TestVolume): self.new_volume.id, parsed_args.image_property) self.volumes_mock.delete_metadata.assert_called_with( self.new_volume.id, parsed_args.property) + + +class TestColumns(TestVolume): + + def test_attachments_column_without_server_cache(self): + _volume = volume_fakes.FakeVolume.create_one_volume() + server_id = _volume.attachments[0]['server_id'] + device = _volume.attachments[0]['device'] + + col = volume.AttachmentsColumn(_volume.attachments, {}) + self.assertEqual('Attached to %s on %s ' % (server_id, device), + col.human_readable()) + self.assertEqual(_volume.attachments, col.machine_readable()) + + def test_attachments_column_with_server_cache(self): + _volume = volume_fakes.FakeVolume.create_one_volume() + + server_id = _volume.attachments[0]['server_id'] + device = _volume.attachments[0]['device'] + fake_server = mock.Mock() + fake_server.name = 'fake-server-name' + server_cache = {server_id: fake_server} + + col = volume.AttachmentsColumn(_volume.attachments, server_cache) + self.assertEqual( + 'Attached to %s on %s ' % ('fake-server-name', device), + col.human_readable()) + self.assertEqual(_volume.attachments, col.machine_readable()) diff --git a/openstackclient/tests/unit/volume/v2/test_backup.py b/openstackclient/tests/unit/volume/v2/test_volume_backup.py index 9a2ce71..30c7915 100644 --- a/openstackclient/tests/unit/volume/v2/test_backup.py +++ b/openstackclient/tests/unit/volume/v2/test_volume_backup.py @@ -19,7 +19,7 @@ from osc_lib import exceptions from osc_lib import utils from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes -from openstackclient.volume.v2 import backup +from openstackclient.volume.v2 import volume_backup class TestBackup(volume_fakes.TestVolume): @@ -77,7 +77,7 @@ class TestBackupCreate(TestBackup): self.backups_mock.create.return_value = self.new_backup # Get the command object to test - self.cmd = backup.CreateVolumeBackup(self.app, None) + self.cmd = volume_backup.CreateVolumeBackup(self.app, None) def test_backup_create(self): arglist = [ @@ -154,7 +154,7 @@ class TestBackupDelete(TestBackup): self.backups_mock.delete.return_value = None # Get the command object to mock - self.cmd = backup.DeleteVolumeBackup(self.app, None) + self.cmd = volume_backup.DeleteVolumeBackup(self.app, None) def test_backup_delete(self): arglist = [ @@ -271,7 +271,7 @@ class TestBackupList(TestBackup): b.status, b.size, b.availability_zone, - b.volume_id, + volume_backup.VolumeIdColumn(b.volume_id), b.container, )) @@ -283,7 +283,7 @@ class TestBackupList(TestBackup): self.volumes_mock.get.return_value = self.volume self.backups_mock.get.return_value = self.backups[0] # Get the command to test - self.cmd = backup.ListVolumeBackup(self.app, None) + self.cmd = volume_backup.ListVolumeBackup(self.app, None) def test_backup_list_without_options(self): arglist = [] @@ -314,7 +314,7 @@ class TestBackupList(TestBackup): limit=None, ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_backup_list_with_options(self): arglist = [ @@ -353,7 +353,7 @@ class TestBackupList(TestBackup): limit=3, ) self.assertEqual(self.columns_long, columns) - self.assertEqual(self.data_long, list(data)) + self.assertListItemEqual(self.data_long, list(data)) class TestBackupRestore(TestBackup): @@ -371,7 +371,7 @@ class TestBackupRestore(TestBackup): volume_fakes.FakeVolume.create_one_volume( {'id': self.volume['id']})) # Get the command object to mock - self.cmd = backup.RestoreVolumeBackup(self.app, None) + self.cmd = volume_backup.RestoreVolumeBackup(self.app, None) def test_backup_restore(self): arglist = [ @@ -400,7 +400,7 @@ class TestBackupSet(TestBackup): self.backups_mock.get.return_value = self.backup # Get the command object to test - self.cmd = backup.SetVolumeBackup(self.app, None) + self.cmd = volume_backup.SetVolumeBackup(self.app, None) def test_backup_set_name(self): arglist = [ @@ -517,7 +517,7 @@ class TestBackupShow(TestBackup): self.backups_mock.get.return_value = self.backup # Get the command object to test - self.cmd = backup.ShowVolumeBackup(self.app, None) + self.cmd = volume_backup.ShowVolumeBackup(self.app, None) def test_backup_show(self): arglist = [ diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py index e0e670a..fdd1794 100644 --- a/openstackclient/volume/client.py +++ b/openstackclient/volume/client.py @@ -22,7 +22,7 @@ from openstackclient.i18n import _ LOG = logging.getLogger(__name__) -DEFAULT_API_VERSION = '2' +DEFAULT_API_VERSION = '3' API_VERSION_OPTION = 'os_volume_api_version' API_NAME = "volume" API_VERSIONS = { diff --git a/openstackclient/volume/v1/qos_specs.py b/openstackclient/volume/v1/qos_specs.py index 26c9182..0b6a7fa 100644 --- a/openstackclient/volume/v1/qos_specs.py +++ b/openstackclient/volume/v1/qos_specs.py @@ -17,6 +17,7 @@ import logging +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -95,7 +96,8 @@ class CreateQos(command.ShowOne): qos_spec = volume_client.qos_specs.create(parsed_args.name, specs) qos_spec._info.update( - {'properties': utils.format_dict(qos_spec._info.pop('specs'))} + {'properties': + format_columns.DictColumn(qos_spec._info.pop('specs'))} ) return zip(*sorted(six.iteritems(qos_spec._info))) @@ -208,8 +210,8 @@ class ListQos(command.Lister): (utils.get_dict_properties( s._info, columns, formatters={ - 'Specs': utils.format_dict, - 'Associations': utils.format_list + 'Specs': format_columns.DictColumn, + 'Associations': format_columns.ListColumn }, ) for s in qos_specs_list)) @@ -265,10 +267,11 @@ class ShowQos(command.ShowOne): associations = [association.name for association in qos_associations] qos_spec._info.update({ - 'associations': utils.format_list(associations) + 'associations': format_columns.ListColumn(associations) }) qos_spec._info.update( - {'properties': utils.format_dict(qos_spec._info.pop('specs'))}) + {'properties': + format_columns.DictColumn(qos_spec._info.pop('specs'))}) return zip(*sorted(six.iteritems(qos_spec._info))) diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py deleted file mode 100644 index e9e3894..0000000 --- a/openstackclient/volume/v1/snapshot.py +++ /dev/null @@ -1,318 +0,0 @@ -# Copyright 2012-2013 OpenStack Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -# TODO(Huanxuan Ao): Remove this file and "snapshot create", "snapshot delete", -# "snapshot set", "snapshot show" and "snapshot unset" -# commands two cycles after Ocata. - -"""Volume v1 Snapshot action implementations""" - -import copy -import logging - -from osc_lib.cli import parseractions -from osc_lib.command import command -from osc_lib import exceptions -from osc_lib import utils -import six - -from openstackclient.i18n import _ - - -deprecated = True -LOG_DEP = logging.getLogger('deprecated') -LOG = logging.getLogger(__name__) - - -class CreateSnapshot(command.ShowOne): - _description = _("Create new snapshot") - - def get_parser(self, prog_name): - parser = super(CreateSnapshot, self).get_parser(prog_name) - parser.add_argument( - 'volume', - metavar='<volume>', - help=_('Volume to snapshot (name or ID)'), - ) - parser.add_argument( - '--name', - metavar='<name>', - help=_('Name of the snapshot'), - ) - parser.add_argument( - '--description', - metavar='<description>', - help=_('Description of the snapshot'), - ) - parser.add_argument( - '--force', - dest='force', - action='store_true', - default=False, - help=_('Create a snapshot attached to an instance. ' - 'Default is False'), - ) - return parser - - def take_action(self, parsed_args): - LOG_DEP.warning(_('This command has been deprecated. ' - 'Please use "volume snapshot create" instead.')) - volume_client = self.app.client_manager.volume - volume_id = utils.find_resource(volume_client.volumes, - parsed_args.volume).id - snapshot = volume_client.volume_snapshots.create( - volume_id, - parsed_args.force, - parsed_args.name, - parsed_args.description - ) - - snapshot._info.update( - {'properties': utils.format_dict(snapshot._info.pop('metadata'))} - ) - - return zip(*sorted(six.iteritems(snapshot._info))) - - -class DeleteSnapshot(command.Command): - _description = _("Delete snapshot(s)") - - def get_parser(self, prog_name): - parser = super(DeleteSnapshot, self).get_parser(prog_name) - parser.add_argument( - 'snapshots', - metavar='<snapshot>', - nargs="+", - help=_('Snapshot(s) to delete (name or ID)'), - ) - return parser - - def take_action(self, parsed_args): - LOG_DEP.warning(_('This command has been deprecated. ' - 'Please use "volume snapshot delete" instead.')) - volume_client = self.app.client_manager.volume - result = 0 - - for i in parsed_args.snapshots: - try: - snapshot_id = utils.find_resource( - volume_client.volume_snapshots, i).id - volume_client.volume_snapshots.delete(snapshot_id) - except Exception as e: - result += 1 - LOG.error(_("Failed to delete snapshot with " - "name or ID '%(snapshot)s': %(e)s"), - {'snapshot': i, 'e': e}) - - if result > 0: - total = len(parsed_args.snapshots) - msg = (_("%(result)s of %(total)s snapshots failed " - "to delete.") % {'result': result, 'total': total}) - raise exceptions.CommandError(msg) - - -class ListSnapshot(command.Lister): - _description = _("List snapshots") - - def get_parser(self, prog_name): - parser = super(ListSnapshot, self).get_parser(prog_name) - parser.add_argument( - '--all-projects', - action='store_true', - default=False, - help=_('Include all projects (admin only)'), - ) - parser.add_argument( - '--long', - action='store_true', - default=False, - help=_('List additional fields in output'), - ) - return parser - - def take_action(self, parsed_args): - LOG_DEP.warning(_('This command has been deprecated. ' - 'Please use "volume snapshot list" instead.')) - - def _format_volume_id(volume_id): - """Return a volume name if available - - :param volume_id: a volume ID - :rtype: either the volume ID or name - """ - - volume = volume_id - if volume_id in volume_cache.keys(): - volume = volume_cache[volume_id].display_name - return volume - - if parsed_args.long: - columns = ['ID', 'Display Name', 'Display Description', 'Status', - 'Size', 'Created At', 'Volume ID', 'Metadata'] - column_headers = copy.deepcopy(columns) - column_headers[6] = 'Volume' - column_headers[7] = 'Properties' - else: - columns = ['ID', 'Display Name', 'Display Description', 'Status', - 'Size'] - column_headers = copy.deepcopy(columns) - - # Always update Name and Description - column_headers[1] = 'Name' - column_headers[2] = 'Description' - - # Cache the volume list - volume_cache = {} - try: - for s in self.app.client_manager.volume.volumes.list(): - volume_cache[s.id] = s - except Exception: - # Just forget it if there's any trouble - pass - - search_opts = { - 'all_tenants': parsed_args.all_projects, - } - - data = self.app.client_manager.volume.volume_snapshots.list( - search_opts=search_opts) - return (column_headers, - (utils.get_item_properties( - s, columns, - formatters={'Metadata': utils.format_dict, - 'Volume ID': _format_volume_id}, - ) for s in data)) - - -class SetSnapshot(command.Command): - _description = _("Set snapshot properties") - - def get_parser(self, prog_name): - parser = super(SetSnapshot, self).get_parser(prog_name) - parser.add_argument( - 'snapshot', - metavar='<snapshot>', - help=_('Snapshot to modify (name or ID)') - ) - parser.add_argument( - '--name', - metavar='<name>', - help=_('New snapshot name') - ) - parser.add_argument( - '--description', - metavar='<description>', - help=_('New snapshot description') - ) - parser.add_argument( - '--property', - metavar='<key=value>', - action=parseractions.KeyValueAction, - help=_('Property to add/change for this snapshot ' - '(repeat option to set multiple properties)'), - ) - return parser - - def take_action(self, parsed_args): - LOG_DEP.warning(_('This command has been deprecated. ' - 'Please use "volume snapshot set" instead.')) - volume_client = self.app.client_manager.volume - snapshot = utils.find_resource(volume_client.volume_snapshots, - parsed_args.snapshot) - - result = 0 - if parsed_args.property: - try: - volume_client.volume_snapshots.set_metadata( - snapshot.id, parsed_args.property) - except Exception as e: - LOG.error(_("Failed to set snapshot property: %s"), e) - result += 1 - - kwargs = {} - if parsed_args.name: - kwargs['display_name'] = parsed_args.name - if parsed_args.description: - kwargs['display_description'] = parsed_args.description - if kwargs: - try: - snapshot.update(**kwargs) - except Exception as e: - LOG.error(_("Failed to update snapshot display name " - "or display description: %s"), e) - result += 1 - - if result > 0: - raise exceptions.CommandError(_("One or more of the " - "set operations failed")) - - -class ShowSnapshot(command.ShowOne): - _description = _("Display snapshot details") - - def get_parser(self, prog_name): - parser = super(ShowSnapshot, self).get_parser(prog_name) - parser.add_argument( - 'snapshot', - metavar='<snapshot>', - help=_('Snapshot to display (name or ID)') - ) - return parser - - def take_action(self, parsed_args): - LOG_DEP.warning(_('This command has been deprecated. ' - 'Please use "volume snapshot show" instead.')) - volume_client = self.app.client_manager.volume - snapshot = utils.find_resource(volume_client.volume_snapshots, - parsed_args.snapshot) - - snapshot._info.update( - {'properties': utils.format_dict(snapshot._info.pop('metadata'))} - ) - - return zip(*sorted(six.iteritems(snapshot._info))) - - -class UnsetSnapshot(command.Command): - _description = _("Unset snapshot properties") - - def get_parser(self, prog_name): - parser = super(UnsetSnapshot, self).get_parser(prog_name) - parser.add_argument( - 'snapshot', - metavar='<snapshot>', - help=_('Snapshot to modify (name or ID)'), - ) - parser.add_argument( - '--property', - metavar='<key>', - action='append', - help=_('Property to remove from snapshot ' - '(repeat option to remove multiple properties)'), - ) - return parser - - def take_action(self, parsed_args): - LOG_DEP.warning(_('This command has been deprecated. ' - 'Please use "volume snapshot unset" instead.')) - volume_client = self.app.client_manager.volume - snapshot = utils.find_resource( - volume_client.volume_snapshots, parsed_args.snapshot) - - if parsed_args.property: - volume_client.volume_snapshots.delete_metadata( - snapshot.id, - parsed_args.property, - ) diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index b29429e..36c7ef8 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -16,8 +16,11 @@ """Volume v1 Volume action implementations""" import argparse +import functools import logging +from cliff import columns as cliff_columns +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -30,6 +33,37 @@ from openstackclient.i18n import _ LOG = logging.getLogger(__name__) +class AttachmentsColumn(cliff_columns.FormattableColumn): + """Formattable column for attachments column. + + Unlike the parent FormattableColumn class, the initializer of the + class takes server_cache as the second argument. + osc_lib.utils.get_item_properties instantiate cliff FormattableColumn + object with a single parameter "column value", so you need to pass + a partially initialized class like + ``functools.partial(AttachmentsColumn, server_cache)``. + """ + + def __init__(self, value, server_cache=None): + super(AttachmentsColumn, self).__init__(value) + self._server_cache = server_cache or {} + + def human_readable(self): + """Return a formatted string of a volume's attached instances + + :rtype: a string of formatted instances + """ + + msg = '' + for attachment in self._value: + server = attachment['server_id'] + if server in self._server_cache.keys(): + server = self._server_cache[server].name + device = attachment['device'] + msg += 'Attached to %s on %s ' % (server, device) + return msg + + def _check_size_arg(args): """Check whether --size option is required or not. @@ -207,7 +241,8 @@ class CreateVolume(command.ShowOne): # Map 'metadata' column to 'properties' volume._info.update( { - 'properties': utils.format_dict(volume._info.pop('metadata')), + 'properties': + format_columns.DictColumn(volume._info.pop('metadata')), 'type': volume._info.pop('volume_type'), }, ) @@ -307,22 +342,6 @@ class ListVolume(command.Lister): volume_client = self.app.client_manager.volume compute_client = self.app.client_manager.compute - def _format_attach(attachments): - """Return a formatted string of a volume's attached instances - - :param attachments: a volume.attachments field - :rtype: a string of formatted instances - """ - - msg = '' - for attachment in attachments: - server = attachment['server_id'] - if server in server_cache.keys(): - server = server_cache[server].name - device = attachment['device'] - msg += 'Attached to %s on %s ' % (server, device) - return msg - if parsed_args.long: columns = ( 'ID', @@ -368,6 +387,8 @@ class ListVolume(command.Lister): except Exception: # Just forget it if there's any trouble pass + AttachmentsColumnWithCache = functools.partial( + AttachmentsColumn, server_cache=server_cache) search_opts = { 'all_tenants': parsed_args.all_projects, @@ -385,8 +406,8 @@ class ListVolume(command.Lister): return (column_headers, (utils.get_item_properties( s, columns, - formatters={'Metadata': utils.format_dict, - 'Attachments': _format_attach}, + formatters={'Metadata': format_columns.DictColumn, + 'Attachments': AttachmentsColumnWithCache}, ) for s in data)) @@ -575,7 +596,8 @@ class ShowVolume(command.ShowOne): # Map 'metadata' column to 'properties' volume._info.update( { - 'properties': utils.format_dict(volume._info.pop('metadata')), + 'properties': + format_columns.DictColumn(volume._info.pop('metadata')), 'type': volume._info.pop('volume_type'), }, ) diff --git a/openstackclient/volume/v1/backup.py b/openstackclient/volume/v1/volume_backup.py index 9ac1302..2daa23c 100644 --- a/openstackclient/volume/v1/backup.py +++ b/openstackclient/volume/v1/volume_backup.py @@ -16,8 +16,10 @@ """Volume v1 Backup action implementations""" import copy +import functools import logging +from cliff import columns as cliff_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -29,6 +31,33 @@ from openstackclient.i18n import _ LOG = logging.getLogger(__name__) +class VolumeIdColumn(cliff_columns.FormattableColumn): + """Formattable column for volume ID column. + + Unlike the parent FormattableColumn class, the initializer of the + class takes volume_cache as the second argument. + osc_lib.utils.get_item_properties instantiate cliff FormattableColumn + object with a single parameter "column value", so you need to pass + a partially initialized class like + ``functools.partial(VolumeIdColumn, volume_cache)``. + """ + + def __init__(self, value, volume_cache=None): + super(VolumeIdColumn, self).__init__(value) + self._volume_cache = volume_cache or {} + + def human_readable(self): + """Return a volume name if available + + :rtype: either the volume ID or name + """ + volume_id = self._value + volume = volume_id + if volume_id in self._volume_cache.keys(): + volume = self._volume_cache[volume_id].display_name + return volume + + class CreateVolumeBackup(command.ShowOne): _description = _("Create new volume backup") @@ -72,23 +101,6 @@ class CreateVolumeBackup(command.ShowOne): return zip(*sorted(six.iteritems(backup._info))) -class CreateBackup(CreateVolumeBackup): - _description = _("Create new backup") - - # TODO(Huanxuan Ao): Remove this class and ``backup create`` command - # two cycles after Newton. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def take_action(self, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "volume backup create" instead.')) - return super(CreateBackup, self).take_action(parsed_args) - - class DeleteVolumeBackup(command.Command): _description = _("Delete volume backup(s)") @@ -124,23 +136,6 @@ class DeleteVolumeBackup(command.Command): raise exceptions.CommandError(msg) -class DeleteBackup(DeleteVolumeBackup): - _description = _("Delete backup(s)") - - # TODO(Huanxuan Ao): Remove this class and ``backup delete`` command - # two cycles after Newton. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def take_action(self, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "volume backup delete" instead.')) - return super(DeleteBackup, self).take_action(parsed_args) - - class ListVolumeBackup(command.Lister): _description = _("List volume backups") @@ -183,18 +178,6 @@ class ListVolumeBackup(command.Lister): def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - def _format_volume_id(volume_id): - """Return a volume name if available - - :param volume_id: a volume ID - :rtype: either the volume ID or name - """ - - volume = volume_id - if volume_id in volume_cache.keys(): - volume = volume_cache[volume_id].display_name - return volume - if parsed_args.long: columns = ['ID', 'Name', 'Description', 'Status', 'Size', 'Availability Zone', 'Volume ID', 'Container'] @@ -212,6 +195,8 @@ class ListVolumeBackup(command.Lister): except Exception: # Just forget it if there's any trouble pass + VolumeIdColumnWithCache = functools.partial(VolumeIdColumn, + volume_cache=volume_cache) filter_volume_id = None if parsed_args.volume: @@ -230,27 +215,10 @@ class ListVolumeBackup(command.Lister): return (column_headers, (utils.get_item_properties( s, columns, - formatters={'Volume ID': _format_volume_id}, + formatters={'Volume ID': VolumeIdColumnWithCache}, ) for s in data)) -class ListBackup(ListVolumeBackup): - _description = _("List backups") - - # TODO(Huanxuan Ao): Remove this class and ``backup list`` command - # two cycles after Newton. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def take_action(self, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "volume backup list" instead.')) - return super(ListBackup, self).take_action(parsed_args) - - class RestoreVolumeBackup(command.Command): _description = _("Restore volume backup") @@ -278,23 +246,6 @@ class RestoreVolumeBackup(command.Command): destination_volume.id) -class RestoreBackup(RestoreVolumeBackup): - _description = _("Restore backup") - - # TODO(Huanxuan Ao): Remove this class and ``backup restore`` command - # two cycles after Newton. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def take_action(self, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "volume backup restore" instead.')) - return super(RestoreBackup, self).take_action(parsed_args) - - class ShowVolumeBackup(command.ShowOne): _description = _("Display volume backup details") @@ -313,20 +264,3 @@ class ShowVolumeBackup(command.ShowOne): parsed_args.backup) backup._info.pop('links') return zip(*sorted(six.iteritems(backup._info))) - - -class ShowBackup(ShowVolumeBackup): - _description = _("Display backup details") - - # TODO(Huanxuan Ao): Remove this class and ``backup show`` command - # two cycles after Newton. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def take_action(self, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "volume backup show" instead.')) - return super(ShowBackup, self).take_action(parsed_args) diff --git a/openstackclient/volume/v1/volume_snapshot.py b/openstackclient/volume/v1/volume_snapshot.py index 3e83da5..50f8177 100644 --- a/openstackclient/volume/v1/volume_snapshot.py +++ b/openstackclient/volume/v1/volume_snapshot.py @@ -16,8 +16,11 @@ """Volume v1 Snapshot action implementations""" import copy +import functools import logging +from cliff import columns as cliff_columns +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -30,6 +33,33 @@ from openstackclient.i18n import _ LOG = logging.getLogger(__name__) +class VolumeIdColumn(cliff_columns.FormattableColumn): + """Formattable column for volume ID column. + + Unlike the parent FormattableColumn class, the initializer of the + class takes volume_cache as the second argument. + osc_lib.utils.get_item_properties instantiate cliff FormattableColumn + object with a single parameter "column value", so you need to pass + a partially initialized class like + ``functools.partial(VolumeIdColumn, volume_cache)``. + """ + + def __init__(self, value, volume_cache=None): + super(VolumeIdColumn, self).__init__(value) + self._volume_cache = volume_cache or {} + + def human_readable(self): + """Return a volume name if available + + :rtype: either the volume ID or name + """ + volume_id = self._value + volume = volume_id + if volume_id in self._volume_cache.keys(): + volume = self._volume_cache[volume_id].display_name + return volume + + class CreateVolumeSnapshot(command.ShowOne): _description = _("Create new volume snapshot") @@ -76,7 +106,8 @@ class CreateVolumeSnapshot(command.ShowOne): ) snapshot._info.update( - {'properties': utils.format_dict(snapshot._info.pop('metadata'))} + {'properties': + format_columns.DictColumn(snapshot._info.pop('metadata'))} ) return zip(*sorted(six.iteritems(snapshot._info))) @@ -160,18 +191,6 @@ class ListVolumeSnapshot(command.Lister): def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - def _format_volume_id(volume_id): - """Return a volume name if available - - :param volume_id: a volume ID - :rtype: either the volume ID or name - """ - - volume = volume_id - if volume_id in volume_cache.keys(): - volume = volume_cache[volume_id].display_name - return volume - if parsed_args.long: columns = ['ID', 'Display Name', 'Display Description', 'Status', 'Size', 'Created At', 'Volume ID', 'Metadata'] @@ -195,6 +214,8 @@ class ListVolumeSnapshot(command.Lister): except Exception: # Just forget it if there's any trouble pass + VolumeIdColumnWithCache = functools.partial(VolumeIdColumn, + volume_cache=volume_cache) volume_id = None if parsed_args.volume: @@ -213,8 +234,8 @@ class ListVolumeSnapshot(command.Lister): return (column_headers, (utils.get_item_properties( s, columns, - formatters={'Metadata': utils.format_dict, - 'Volume ID': _format_volume_id}, + formatters={'Metadata': format_columns.DictColumn, + 'Volume ID': VolumeIdColumnWithCache}, ) for s in data)) @@ -317,7 +338,8 @@ class ShowVolumeSnapshot(command.ShowOne): parsed_args.snapshot) snapshot._info.update( - {'properties': utils.format_dict(snapshot._info.pop('metadata'))} + {'properties': + format_columns.DictColumn(snapshot._info.pop('metadata'))} ) return zip(*sorted(six.iteritems(snapshot._info))) diff --git a/openstackclient/volume/v1/volume_transfer_request.py b/openstackclient/volume/v1/volume_transfer_request.py index f5b567b..6f79658 100644 --- a/openstackclient/volume/v1/volume_transfer_request.py +++ b/openstackclient/volume/v1/volume_transfer_request.py @@ -14,7 +14,6 @@ """Volume v1 transfer action implementations""" -import argparse import logging from osc_lib.command import command @@ -39,12 +38,6 @@ class AcceptTransferRequest(command.ShowOne): help=_('Volume transfer request to accept (ID only)'), ) parser.add_argument( - 'old_auth_key', - metavar="<key>", - nargs="?", - help=argparse.SUPPRESS, - ) - parser.add_argument( '--auth-key', metavar="<key>", help=_('Volume transfer request authentication key'), @@ -64,20 +57,9 @@ class AcceptTransferRequest(command.ShowOne): # move on and attempt with the user-supplied information transfer_request_id = parsed_args.transfer_request - # Remain backward-compatible for the previous command layout - # TODO(dtroyer): Remove this back-compat in 4.0 or Oct 2017 if not parsed_args.auth_key: - if parsed_args.old_auth_key: - # Move the old one into the correct place - parsed_args.auth_key = parsed_args.old_auth_key - self.log.warning(_( - 'Specifying the auth-key as a positional argument ' - 'has been deprecated. Please use the --auth-key ' - 'option in the future.' - )) - else: - msg = _("argument --auth-key is required") - raise exceptions.CommandError(msg) + msg = _("argument --auth-key is required") + raise exceptions.CommandError(msg) transfer_accept = volume_client.transfers.accept( transfer_request_id, diff --git a/openstackclient/volume/v1/volume_type.py b/openstackclient/volume/v1/volume_type.py index b4d8eac..e744e92 100644 --- a/openstackclient/volume/v1/volume_type.py +++ b/openstackclient/volume/v1/volume_type.py @@ -15,8 +15,11 @@ """Volume v1 Type action implementations""" +import functools import logging +from cliff import columns as cliff_columns +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -29,6 +32,36 @@ from openstackclient.i18n import _ LOG = logging.getLogger(__name__) +class EncryptionInfoColumn(cliff_columns.FormattableColumn): + """Formattable column for encryption info column. + + Unlike the parent FormattableColumn class, the initializer of the + class takes encryption_data as the second argument. + osc_lib.utils.get_item_properties instantiate cliff FormattableColumn + object with a single parameter "column value", so you need to pass + a partially initialized class like + ``functools.partial(EncryptionInfoColumn encryption_data)``. + """ + + def __init__(self, value, encryption_data=None): + super(EncryptionInfoColumn, self).__init__(value) + self._encryption_data = encryption_data or {} + + def _get_encryption_info(self): + type_id = self._value + return self._encryption_data.get(type_id) + + def human_readable(self): + encryption_info = self._get_encryption_info() + if encryption_info: + return utils.format_dict(encryption_info) + else: + return '-' + + def machine_readable(self): + return self._get_encryption_info() + + def _create_encryption_type(volume_client, volume_type, parsed_args): if not parsed_args.encryption_provider: msg = _("'--encryption-provider' should be specified while " @@ -110,7 +143,8 @@ class CreateVolumeType(command.ShowOne): volume_type._info.pop('extra_specs') if parsed_args.property: result = volume_type.set_keys(parsed_args.property) - volume_type._info.update({'properties': utils.format_dict(result)}) + volume_type._info.update( + {'properties': format_columns.DictColumn(result)}) if (parsed_args.encryption_provider or parsed_args.encryption_cipher or parsed_args.encryption_key_size or @@ -125,7 +159,7 @@ class CreateVolumeType(command.ShowOne): # add encryption info in result encryption._info.pop("volume_type_id", None) volume_type._info.update( - {'encryption': utils.format_dict(encryption._info)}) + {'encryption': format_columns.DictColumn(encryption._info)}) volume_type._info.pop("os-volume-type-access:is_public", None) return zip(*sorted(six.iteritems(volume_type._info))) @@ -196,12 +230,7 @@ class ListVolumeType(command.Lister): column_headers = ['ID', 'Name', 'Is Public'] data = volume_client.volume_types.list() - def _format_encryption_info(type_id, encryption_data=None): - encryption_data = encryption - encryption_info = '-' - if type_id in encryption_data.keys(): - encryption_info = encryption_data[type_id] - return encryption_info + formatters = {'Extra Specs': format_columns.DictColumn} if parsed_args.encryption_type: encryption = {} @@ -218,18 +247,21 @@ class ListVolumeType(command.Lister): for key in del_key: d._info.pop(key, None) # save the encryption information with their volume type ID - encryption[volume_type_id] = utils.format_dict(d._info) + encryption[volume_type_id] = d._info # We need to get volume type ID, then show encryption # information according to the ID, so use "id" to keep # difference to the real "ID" column. columns += ['id'] column_headers += ['Encryption'] + _EncryptionInfoColumn = functools.partial( + EncryptionInfoColumn, encryption_data=encryption) + formatters['id'] = _EncryptionInfoColumn + return (column_headers, (utils.get_item_properties( s, columns, - formatters={'Extra Specs': utils.format_dict, - 'id': _format_encryption_info}, + formatters=formatters, ) for s in data)) @@ -340,7 +372,8 @@ class ShowVolumeType(command.ShowOne): volume_client = self.app.client_manager.volume volume_type = utils.find_resource( volume_client.volume_types, parsed_args.volume_type) - properties = utils.format_dict(volume_type._info.pop('extra_specs')) + properties = format_columns.DictColumn( + volume_type._info.pop('extra_specs')) volume_type._info.update({'properties': properties}) if parsed_args.encryption_type: # show encryption type information for this volume type @@ -349,7 +382,8 @@ class ShowVolumeType(command.ShowOne): volume_type.id) encryption._info.pop("volume_type_id", None) volume_type._info.update( - {'encryption': utils.format_dict(encryption._info)}) + {'encryption': + format_columns.DictColumn(encryption._info)}) except Exception as e: LOG.error(_("Failed to display the encryption information " "of this volume type: %s"), e) diff --git a/openstackclient/volume/v2/consistency_group.py b/openstackclient/volume/v2/consistency_group.py index 0a932f8..26dd8ff 100644 --- a/openstackclient/volume/v2/consistency_group.py +++ b/openstackclient/volume/v2/consistency_group.py @@ -16,6 +16,7 @@ import logging +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -238,7 +239,7 @@ class ListConsistencyGroup(command.Lister): return (columns, ( utils.get_item_properties( s, columns, - formatters={'Volume Types': utils.format_list}) + formatters={'Volume Types': format_columns.ListColumn}) for s in consistency_groups)) diff --git a/openstackclient/volume/v2/qos_specs.py b/openstackclient/volume/v2/qos_specs.py index c716058..3037d34 100644 --- a/openstackclient/volume/v2/qos_specs.py +++ b/openstackclient/volume/v2/qos_specs.py @@ -17,6 +17,7 @@ import logging +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -96,7 +97,8 @@ class CreateQos(command.ShowOne): qos_spec = volume_client.qos_specs.create(parsed_args.name, specs) qos_spec._info.update( - {'properties': utils.format_dict(qos_spec._info.pop('specs'))} + {'properties': + format_columns.DictColumn(qos_spec._info.pop('specs'))} ) return zip(*sorted(six.iteritems(qos_spec._info))) @@ -210,8 +212,8 @@ class ListQos(command.Lister): (utils.get_dict_properties( s._info, columns, formatters={ - 'Specs': utils.format_dict, - 'Associations': utils.format_list + 'Specs': format_columns.DictColumn, + 'Associations': format_columns.ListColumn }, ) for s in qos_specs_list)) @@ -267,10 +269,11 @@ class ShowQos(command.ShowOne): associations = [association.name for association in qos_associations] qos_spec._info.update({ - 'associations': utils.format_list(associations) + 'associations': format_columns.ListColumn(associations) }) qos_spec._info.update( - {'properties': utils.format_dict(qos_spec._info.pop('specs'))}) + {'properties': + format_columns.DictColumn(qos_spec._info.pop('specs'))}) return zip(*sorted(six.iteritems(qos_spec._info))) diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py deleted file mode 100644 index 82b3103..0000000 --- a/openstackclient/volume/v2/snapshot.py +++ /dev/null @@ -1,351 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -# TODO(Huanxuan Ao): Remove this file and "snapshot create", "snapshot delete", -# "snapshot set", "snapshot show" and "snapshot unset" -# commands two cycles after Ocata. - -"""Volume v2 snapshot action implementations""" - -import copy -import logging - -from osc_lib.cli import parseractions -from osc_lib.command import command -from osc_lib import exceptions -from osc_lib import utils -import six - -from openstackclient.i18n import _ - - -deprecated = True -LOG_DEP = logging.getLogger('deprecated') -LOG = logging.getLogger(__name__) - - -class CreateSnapshot(command.ShowOne): - _description = _("Create new snapshot") - - def get_parser(self, prog_name): - parser = super(CreateSnapshot, self).get_parser(prog_name) - parser.add_argument( - "volume", - metavar="<volume>", - help=_("Volume to snapshot (name or ID)") - ) - parser.add_argument( - "--name", - metavar="<name>", - help=_("Name of the snapshot") - ) - parser.add_argument( - "--description", - metavar="<description>", - help=_("Description of the snapshot") - ) - parser.add_argument( - "--force", - action="store_true", - default=False, - help=_("Create a snapshot attached to an instance. " - "Default is False") - ) - parser.add_argument( - "--property", - metavar="<key=value>", - action=parseractions.KeyValueAction, - help=_("Set a property to this snapshot " - "(repeat option to set multiple properties)"), - ) - return parser - - def take_action(self, parsed_args): - LOG_DEP.warning(_('This command has been deprecated. ' - 'Please use "volume snapshot create" instead.')) - volume_client = self.app.client_manager.volume - volume_id = utils.find_resource( - volume_client.volumes, parsed_args.volume).id - snapshot = volume_client.volume_snapshots.create( - volume_id, - force=parsed_args.force, - name=parsed_args.name, - description=parsed_args.description, - metadata=parsed_args.property, - ) - snapshot._info.update( - {'properties': utils.format_dict(snapshot._info.pop('metadata'))} - ) - return zip(*sorted(six.iteritems(snapshot._info))) - - -class DeleteSnapshot(command.Command): - _description = _("Delete volume snapshot(s)") - - def get_parser(self, prog_name): - parser = super(DeleteSnapshot, self).get_parser(prog_name) - parser.add_argument( - "snapshots", - metavar="<snapshot>", - nargs="+", - help=_("Snapshot(s) to delete (name or ID)") - ) - return parser - - def take_action(self, parsed_args): - LOG_DEP.warning(_('This command has been deprecated. ' - 'Please use "volume snapshot delete" instead.')) - volume_client = self.app.client_manager.volume - result = 0 - - for i in parsed_args.snapshots: - try: - snapshot_id = utils.find_resource( - volume_client.volume_snapshots, i).id - volume_client.volume_snapshots.delete(snapshot_id) - except Exception as e: - result += 1 - LOG.error(_("Failed to delete snapshot with " - "name or ID '%(snapshot)s': %(e)s") - % {'snapshot': i, 'e': e}) - - if result > 0: - total = len(parsed_args.snapshots) - msg = (_("%(result)s of %(total)s snapshots failed " - "to delete.") % {'result': result, 'total': total}) - raise exceptions.CommandError(msg) - - -class ListSnapshot(command.Lister): - _description = _("List snapshots") - - def get_parser(self, prog_name): - parser = super(ListSnapshot, self).get_parser(prog_name) - parser.add_argument( - '--all-projects', - action='store_true', - default=False, - help=_('Include all projects (admin only)'), - ) - parser.add_argument( - '--long', - action='store_true', - default=False, - help=_('List additional fields in output'), - ) - parser.add_argument( - '--marker', - metavar='<snapshot>', - help=_('The last snapshot ID of the previous page'), - ) - parser.add_argument( - '--limit', - type=int, - action=parseractions.NonNegativeAction, - metavar='<num-snapshots>', - help=_('Maximum number of snapshots to display'), - ) - return parser - - def take_action(self, parsed_args): - LOG_DEP.warning(_('This command has been deprecated. ' - 'Please use "volume snapshot list" instead.')) - - def _format_volume_id(volume_id): - """Return a volume name if available - - :param volume_id: a volume ID - :rtype: either the volume ID or name - """ - - volume = volume_id - if volume_id in volume_cache.keys(): - volume = volume_cache[volume_id].name - return volume - - if parsed_args.long: - columns = ['ID', 'Name', 'Description', 'Status', - 'Size', 'Created At', 'Volume ID', 'Metadata'] - column_headers = copy.deepcopy(columns) - column_headers[6] = 'Volume' - column_headers[7] = 'Properties' - else: - columns = ['ID', 'Name', 'Description', 'Status', 'Size'] - column_headers = copy.deepcopy(columns) - - # Cache the volume list - volume_cache = {} - try: - for s in self.app.client_manager.volume.volumes.list(): - volume_cache[s.id] = s - except Exception: - # Just forget it if there's any trouble - pass - - search_opts = { - 'all_tenants': parsed_args.all_projects, - } - - data = self.app.client_manager.volume.volume_snapshots.list( - search_opts=search_opts, - marker=parsed_args.marker, - limit=parsed_args.limit, - ) - return (column_headers, - (utils.get_item_properties( - s, columns, - formatters={'Metadata': utils.format_dict, - 'Volume ID': _format_volume_id}, - ) for s in data)) - - -class SetSnapshot(command.Command): - _description = _("Set snapshot properties") - - def get_parser(self, prog_name): - parser = super(SetSnapshot, self).get_parser(prog_name) - parser.add_argument( - 'snapshot', - metavar='<snapshot>', - help=_('Snapshot to modify (name or ID)') - ) - parser.add_argument( - '--name', - metavar='<name>', - help=_('New snapshot name') - ) - parser.add_argument( - '--description', - metavar='<description>', - help=_('New snapshot description') - ) - parser.add_argument( - '--property', - metavar='<key=value>', - action=parseractions.KeyValueAction, - help=_('Property to add/change for this snapshot ' - '(repeat option to set multiple properties)'), - ) - parser.add_argument( - '--state', - metavar='<state>', - choices=['available', 'error', 'creating', 'deleting', - 'error-deleting'], - help=_('New snapshot state. ("available", "error", "creating", ' - '"deleting", or "error_deleting") (admin only) ' - '(This option simply changes the state of the snapshot ' - 'in the database with no regard to actual status, ' - 'exercise caution when using)'), - ) - return parser - - def take_action(self, parsed_args): - LOG_DEP.warning(_('This command has been deprecated. ' - 'Please use "volume snapshot set" instead.')) - volume_client = self.app.client_manager.volume - snapshot = utils.find_resource(volume_client.volume_snapshots, - parsed_args.snapshot) - - result = 0 - if parsed_args.property: - try: - volume_client.volume_snapshots.set_metadata( - snapshot.id, parsed_args.property) - except Exception as e: - LOG.error(_("Failed to set snapshot property: %s"), e) - result += 1 - - if parsed_args.state: - try: - volume_client.volume_snapshots.reset_state( - snapshot.id, parsed_args.state) - except Exception as e: - LOG.error(_("Failed to set snapshot state: %s"), e) - result += 1 - - kwargs = {} - if parsed_args.name: - kwargs['name'] = parsed_args.name - if parsed_args.description: - kwargs['description'] = parsed_args.description - if kwargs: - try: - volume_client.volume_snapshots.update( - snapshot.id, **kwargs) - except Exception as e: - LOG.error(_("Failed to update snapshot name " - "or description: %s"), e) - result += 1 - - if result > 0: - raise exceptions.CommandError(_("One or more of the " - "set operations failed")) - - -class ShowSnapshot(command.ShowOne): - _description = _("Display snapshot details") - - def get_parser(self, prog_name): - parser = super(ShowSnapshot, self).get_parser(prog_name) - parser.add_argument( - "snapshot", - metavar="<snapshot>", - help=_("Snapshot to display (name or ID)") - ) - return parser - - def take_action(self, parsed_args): - LOG_DEP.warning(_('This command has been deprecated. ' - 'Please use "volume snapshot show" instead.')) - volume_client = self.app.client_manager.volume - snapshot = utils.find_resource( - volume_client.volume_snapshots, parsed_args.snapshot) - snapshot._info.update( - {'properties': utils.format_dict(snapshot._info.pop('metadata'))} - ) - return zip(*sorted(six.iteritems(snapshot._info))) - - -class UnsetSnapshot(command.Command): - _description = _("Unset snapshot properties") - - def get_parser(self, prog_name): - parser = super(UnsetSnapshot, self).get_parser(prog_name) - parser.add_argument( - 'snapshot', - metavar='<snapshot>', - help=_('Snapshot to modify (name or ID)'), - ) - parser.add_argument( - '--property', - metavar='<key>', - action='append', - default=[], - help=_('Property to remove from snapshot ' - '(repeat option to remove multiple properties)'), - ) - return parser - - def take_action(self, parsed_args): - LOG_DEP.warning(_('This command has been deprecated. ' - 'Please use "volume snapshot unset" instead.')) - volume_client = self.app.client_manager.volume - snapshot = utils.find_resource( - volume_client.volume_snapshots, parsed_args.snapshot) - - if parsed_args.property: - volume_client.volume_snapshots.delete_metadata( - snapshot.id, - parsed_args.property, - ) diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index fa587b5..17ccd3d 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -16,8 +16,11 @@ import argparse import copy +import functools import logging +from cliff import columns as cliff_columns +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -31,6 +34,37 @@ from openstackclient.identity import common as identity_common LOG = logging.getLogger(__name__) +class AttachmentsColumn(cliff_columns.FormattableColumn): + """Formattable column for attachments column. + + Unlike the parent FormattableColumn class, the initializer of the + class takes server_cache as the second argument. + osc_lib.utils.get_item_properties instantiate cliff FormattableColumn + object with a single parameter "column value", so you need to pass + a partially initialized class like + ``functools.partial(AttachmentsColumn, server_cache)``. + """ + + def __init__(self, value, server_cache=None): + super(AttachmentsColumn, self).__init__(value) + self._server_cache = server_cache or {} + + def human_readable(self): + """Return a formatted string of a volume's attached instances + + :rtype: a string of formatted instances + """ + + msg = '' + for attachment in self._value: + server = attachment['server_id'] + if server in self._server_cache.keys(): + server = self._server_cache[server].name + device = attachment['device'] + msg += 'Attached to %s on %s ' % (server, device) + return msg + + def _check_size_arg(args): """Check whether --size option is required or not. @@ -94,16 +128,6 @@ class CreateVolume(command.ShowOne): help=_("Volume description"), ) parser.add_argument( - '--user', - metavar='<user>', - help=argparse.SUPPRESS, - ) - parser.add_argument( - '--project', - metavar='<project>', - help=argparse.SUPPRESS, - ) - parser.add_argument( "--availability-zone", metavar="<availability-zone>", help=_("Create volume in <availability-zone>"), @@ -127,12 +151,6 @@ class CreateVolume(command.ShowOne): help=_("Arbitrary scheduler hint key-value pairs to help boot " "an instance (repeat option to set multiple hints)"), ) - parser.add_argument( - "--multi-attach", - action="store_true", - help=_("Allow volume to be attached more than once " - "(default to False)") - ) bootable_group = parser.add_mutually_exclusive_group() bootable_group.add_argument( "--bootable", @@ -196,26 +214,6 @@ class CreateVolume(command.ShowOne): # snapshot size. size = max(size or 0, snapshot_obj.size) - # NOTE(abishop): Cinder's volumes.create() has 'project_id' and - # 'user_id' args, but they're not wired up to anything. The only way - # to specify an alternate project or user for the volume is to use - # the identity overrides (e.g. "--os-project-id"). - # - # Now, if the project or user arg is specified then the command is - # rejected. Otherwise, Cinder would actually create a volume, but - # without the specified property. - if parsed_args.project: - raise exceptions.CommandError( - _("ERROR: --project is deprecated, please use" - " --os-project-name or --os-project-id instead.")) - if parsed_args.user: - raise exceptions.CommandError( - _("ERROR: --user is deprecated, please use" - " --os-username instead.")) - if parsed_args.multi_attach: - LOG.warning(_("'--multi-attach' option is no longer supported by " - "the block storage service.")) - volume = volume_client.volumes.create( size=size, snapshot_id=snapshot, @@ -248,7 +246,8 @@ class CreateVolume(command.ShowOne): # Remove key links from being displayed volume._info.update( { - 'properties': utils.format_dict(volume._info.pop('metadata')), + 'properties': + format_columns.DictColumn(volume._info.pop('metadata')), 'type': volume._info.pop('volume_type') } ) @@ -367,22 +366,6 @@ class ListVolume(command.Lister): compute_client = self.app.client_manager.compute identity_client = self.app.client_manager.identity - def _format_attach(attachments): - """Return a formatted string of a volume's attached instances - - :param attachments: a volume.attachments field - :rtype: a string of formatted instances - """ - - msg = '' - for attachment in attachments: - server = attachment['server_id'] - if server in server_cache: - server = server_cache[server].name - device = attachment['device'] - msg += 'Attached to %s on %s ' % (server, device) - return msg - if parsed_args.long: columns = [ 'ID', @@ -417,6 +400,8 @@ class ListVolume(command.Lister): except Exception: # Just forget it if there's any trouble pass + AttachmentsColumnWithCache = functools.partial( + AttachmentsColumn, server_cache=server_cache) project_id = None if parsed_args.project: @@ -453,8 +438,8 @@ class ListVolume(command.Lister): return (column_headers, (utils.get_item_properties( s, columns, - formatters={'Metadata': utils.format_dict, - 'Attachments': _format_attach}, + formatters={'Metadata': format_columns.DictColumn, + 'Attachments': AttachmentsColumnWithCache}, ) for s in data)) @@ -758,7 +743,8 @@ class ShowVolume(command.ShowOne): # 'volume_type' --> 'type' volume._info.update( { - 'properties': utils.format_dict(volume._info.pop('metadata')), + 'properties': + format_columns.DictColumn(volume._info.pop('metadata')), 'type': volume._info.pop('volume_type'), }, ) diff --git a/openstackclient/volume/v2/backup.py b/openstackclient/volume/v2/volume_backup.py index d4aec8d..4d0d54c 100644 --- a/openstackclient/volume/v2/backup.py +++ b/openstackclient/volume/v2/volume_backup.py @@ -15,8 +15,10 @@ """Volume v2 Backup action implementations""" import copy +import functools import logging +from cliff import columns as cliff_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -29,6 +31,33 @@ from openstackclient.i18n import _ LOG = logging.getLogger(__name__) +class VolumeIdColumn(cliff_columns.FormattableColumn): + """Formattable column for volume ID column. + + Unlike the parent FormattableColumn class, the initializer of the + class takes volume_cache as the second argument. + osc_lib.utils.get_item_properties instantiate cliff FormattableColumn + object with a single parameter "column value", so you need to pass + a partially initialized class like + ``functools.partial(VolumeIdColumn, volume_cache)``. + """ + + def __init__(self, value, volume_cache=None): + super(VolumeIdColumn, self).__init__(value) + self._volume_cache = volume_cache or {} + + def human_readable(self): + """Return a volume name if available + + :rtype: either the volume ID or name + """ + volume_id = self._value + volume = volume_id + if volume_id in self._volume_cache.keys(): + volume = self._volume_cache[volume_id].name + return volume + + class CreateVolumeBackup(command.ShowOne): _description = _("Create new volume backup") @@ -94,23 +123,6 @@ class CreateVolumeBackup(command.ShowOne): return zip(*sorted(six.iteritems(backup._info))) -class CreateBackup(CreateVolumeBackup): - _description = _("Create new backup") - - # TODO(Huanxuan Ao): Remove this class and ``backup create`` command - # two cycles after Newton. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def take_action(self, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "volume backup create" instead.')) - return super(CreateBackup, self).take_action(parsed_args) - - class DeleteVolumeBackup(command.Command): _description = _("Delete volume backup(s)") @@ -152,23 +164,6 @@ class DeleteVolumeBackup(command.Command): raise exceptions.CommandError(msg) -class DeleteBackup(DeleteVolumeBackup): - _description = _("Delete backup(s)") - - # TODO(Huanxuan Ao): Remove this class and ``backup delete`` command - # two cycles after Newton. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def take_action(self, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "volume backup delete" instead.')) - return super(DeleteBackup, self).take_action(parsed_args) - - class ListVolumeBackup(command.Lister): _description = _("List volume backups") @@ -223,18 +218,6 @@ class ListVolumeBackup(command.Lister): def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - def _format_volume_id(volume_id): - """Return a volume name if available - - :param volume_id: a volume ID - :rtype: either the volume ID or name - """ - - volume = volume_id - if volume_id in volume_cache.keys(): - volume = volume_cache[volume_id].name - return volume - if parsed_args.long: columns = ['ID', 'Name', 'Description', 'Status', 'Size', 'Availability Zone', 'Volume ID', 'Container'] @@ -252,6 +235,8 @@ class ListVolumeBackup(command.Lister): except Exception: # Just forget it if there's any trouble pass + _VolumeIdColumn = functools.partial(VolumeIdColumn, + volume_cache=volume_cache) filter_volume_id = None if parsed_args.volume: @@ -276,27 +261,10 @@ class ListVolumeBackup(command.Lister): return (column_headers, (utils.get_item_properties( s, columns, - formatters={'Volume ID': _format_volume_id}, + formatters={'Volume ID': _VolumeIdColumn}, ) for s in data)) -class ListBackup(ListVolumeBackup): - _description = _("List backups") - - # TODO(Huanxuan Ao): Remove this class and ``backup list`` command - # two cycles after Newton. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def take_action(self, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "volume backup list" instead.')) - return super(ListBackup, self).take_action(parsed_args) - - class RestoreVolumeBackup(command.ShowOne): _description = _("Restore volume backup") @@ -324,23 +292,6 @@ class RestoreVolumeBackup(command.ShowOne): return zip(*sorted(six.iteritems(backup._info))) -class RestoreBackup(RestoreVolumeBackup): - _description = _("Restore backup") - - # TODO(Huanxuan Ao): Remove this class and ``backup restore`` command - # two cycles after Newton. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def take_action(self, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "volume backup restore" instead.')) - return super(RestoreBackup, self).take_action(parsed_args) - - class SetVolumeBackup(command.Command): _description = _("Set volume backup properties") @@ -421,20 +372,3 @@ class ShowVolumeBackup(command.ShowOne): parsed_args.backup) backup._info.pop("links", None) return zip(*sorted(six.iteritems(backup._info))) - - -class ShowBackup(ShowVolumeBackup): - _description = _("Display backup details") - - # TODO(Huanxuan Ao): Remove this class and ``backup show`` command - # two cycles after Newton. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def take_action(self, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "volume backup show" instead.')) - return super(ShowBackup, self).take_action(parsed_args) diff --git a/openstackclient/volume/v2/volume_snapshot.py b/openstackclient/volume/v2/volume_snapshot.py index fe96941..2b26ae3 100644 --- a/openstackclient/volume/v2/volume_snapshot.py +++ b/openstackclient/volume/v2/volume_snapshot.py @@ -15,8 +15,11 @@ """Volume v2 snapshot action implementations""" import copy +import functools import logging +from cliff import columns as cliff_columns +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -30,6 +33,33 @@ from openstackclient.identity import common as identity_common LOG = logging.getLogger(__name__) +class VolumeIdColumn(cliff_columns.FormattableColumn): + """Formattable column for volume ID column. + + Unlike the parent FormattableColumn class, the initializer of the + class takes volume_cache as the second argument. + osc_lib.utils.get_item_properties instantiate cliff FormattableColumn + object with a single parameter "column value", so you need to pass + a partially initialized class like + ``functools.partial(VolumeIdColumn, volume_cache)``. + """ + + def __init__(self, value, volume_cache=None): + super(VolumeIdColumn, self).__init__(value) + self._volume_cache = volume_cache or {} + + def human_readable(self): + """Return a volume name if available + + :rtype: either the volume ID or name + """ + volume_id = self._value + volume = volume_id + if volume_id in self._volume_cache.keys(): + volume = self._volume_cache[volume_id].name + return volume + + class CreateVolumeSnapshot(command.ShowOne): _description = _("Create new volume snapshot") @@ -107,7 +137,8 @@ class CreateVolumeSnapshot(command.ShowOne): metadata=parsed_args.property, ) snapshot._info.update( - {'properties': utils.format_dict(snapshot._info.pop('metadata'))} + {'properties': + format_columns.DictColumn(snapshot._info.pop('metadata'))} ) return zip(*sorted(six.iteritems(snapshot._info))) @@ -216,18 +247,6 @@ class ListVolumeSnapshot(command.Lister): volume_client = self.app.client_manager.volume identity_client = self.app.client_manager.identity - def _format_volume_id(volume_id): - """Return a volume name if available - - :param volume_id: a volume ID - :rtype: either the volume ID or name - """ - - volume = volume_id - if volume_id in volume_cache.keys(): - volume = volume_cache[volume_id].name - return volume - if parsed_args.long: columns = ['ID', 'Name', 'Description', 'Status', 'Size', 'Created At', 'Volume ID', 'Metadata'] @@ -246,6 +265,8 @@ class ListVolumeSnapshot(command.Lister): except Exception: # Just forget it if there's any trouble pass + _VolumeIdColumn = functools.partial(VolumeIdColumn, + volume_cache=volume_cache) volume_id = None if parsed_args.volume: @@ -279,8 +300,8 @@ class ListVolumeSnapshot(command.Lister): return (column_headers, (utils.get_item_properties( s, columns, - formatters={'Metadata': utils.format_dict, - 'Volume ID': _format_volume_id}, + formatters={'Metadata': format_columns.DictColumn, + 'Volume ID': _VolumeIdColumn}, ) for s in data)) @@ -402,7 +423,8 @@ class ShowVolumeSnapshot(command.ShowOne): snapshot = utils.find_resource( volume_client.volume_snapshots, parsed_args.snapshot) snapshot._info.update( - {'properties': utils.format_dict(snapshot._info.pop('metadata'))} + {'properties': + format_columns.DictColumn(snapshot._info.pop('metadata'))} ) return zip(*sorted(six.iteritems(snapshot._info))) diff --git a/openstackclient/volume/v2/volume_transfer_request.py b/openstackclient/volume/v2/volume_transfer_request.py index 2f531dc..4c4741b 100644 --- a/openstackclient/volume/v2/volume_transfer_request.py +++ b/openstackclient/volume/v2/volume_transfer_request.py @@ -14,7 +14,6 @@ """Volume v2 transfer action implementations""" -import argparse import logging from osc_lib.command import command @@ -39,14 +38,9 @@ class AcceptTransferRequest(command.ShowOne): help=_('Volume transfer request to accept (ID only)'), ) parser.add_argument( - 'old_auth_key', - metavar="<key>", - nargs="?", - help=argparse.SUPPRESS, - ) - parser.add_argument( '--auth-key', metavar="<key>", + required=True, help=_('Volume transfer request authentication key'), ) return parser @@ -64,21 +58,6 @@ class AcceptTransferRequest(command.ShowOne): # move on and attempt with the user-supplied information transfer_request_id = parsed_args.transfer_request - # Remain backward-compatible for the previous command layout - # TODO(dtroyer): Remove this back-compat in 4.0 or Oct 2017 - if not parsed_args.auth_key: - if parsed_args.old_auth_key: - # Move the old one into the correct place - parsed_args.auth_key = parsed_args.old_auth_key - self.log.warning(_( - 'Specifying the auth-key as a positional argument ' - 'has been deprecated. Please use the --auth-key ' - 'option in the future.' - )) - else: - msg = _("argument --auth-key is required") - raise exceptions.CommandError(msg) - transfer_accept = volume_client.transfers.accept( transfer_request_id, parsed_args.auth_key, diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 71e94a2..54b1f49 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -14,8 +14,11 @@ """Volume v2 Type action implementations""" +import functools import logging +from cliff import columns as cliff_columns +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -29,6 +32,36 @@ from openstackclient.identity import common as identity_common LOG = logging.getLogger(__name__) +class EncryptionInfoColumn(cliff_columns.FormattableColumn): + """Formattable column for encryption info column. + + Unlike the parent FormattableColumn class, the initializer of the + class takes encryption_data as the second argument. + osc_lib.utils.get_item_properties instantiate cliff FormattableColumn + object with a single parameter "column value", so you need to pass + a partially initialized class like + ``functools.partial(EncryptionInfoColumn encryption_data)``. + """ + + def __init__(self, value, encryption_data=None): + super(EncryptionInfoColumn, self).__init__(value) + self._encryption_data = encryption_data or {} + + def _get_encryption_info(self): + type_id = self._value + return self._encryption_data.get(type_id) + + def human_readable(self): + encryption_info = self._get_encryption_info() + if encryption_info: + return utils.format_dict(encryption_info) + else: + return '-' + + def machine_readable(self): + return self._get_encryption_info() + + def _create_encryption_type(volume_client, volume_type, parsed_args): if not parsed_args.encryption_provider: msg = _("'--encryption-provider' should be specified while " @@ -183,7 +216,8 @@ class CreateVolumeType(command.ShowOne): LOG.error(msg % {'project': parsed_args.project, 'e': e}) if parsed_args.property: result = volume_type.set_keys(parsed_args.property) - volume_type._info.update({'properties': utils.format_dict(result)}) + volume_type._info.update( + {'properties': format_columns.DictColumn(result)}) if (parsed_args.encryption_provider or parsed_args.encryption_cipher or parsed_args.encryption_key_size or @@ -198,7 +232,7 @@ class CreateVolumeType(command.ShowOne): # add encryption info in result encryption._info.pop("volume_type_id", None) volume_type._info.update( - {'encryption': utils.format_dict(encryption._info)}) + {'encryption': format_columns.DictColumn(encryption._info)}) volume_type._info.pop("os-volume-type-access:is_public", None) return zip(*sorted(six.iteritems(volume_type._info))) @@ -296,12 +330,7 @@ class ListVolumeType(command.Lister): data = volume_client.volume_types.list( is_public=is_public) - def _format_encryption_info(type_id, encryption_data=None): - encryption_data = encryption - encryption_info = '-' - if type_id in encryption_data.keys(): - encryption_info = encryption_data[type_id] - return encryption_info + formatters = {'Extra Specs': format_columns.DictColumn} if parsed_args.encryption_type: encryption = {} @@ -318,18 +347,21 @@ class ListVolumeType(command.Lister): for key in del_key: d._info.pop(key, None) # save the encryption information with their volume type ID - encryption[volume_type_id] = utils.format_dict(d._info) + encryption[volume_type_id] = d._info # We need to get volume type ID, then show encryption # information according to the ID, so use "id" to keep # difference to the real "ID" column. columns += ['id'] column_headers += ['Encryption'] + _EncryptionInfoColumn = functools.partial( + EncryptionInfoColumn, encryption_data=encryption) + formatters['id'] = _EncryptionInfoColumn + return (column_headers, (utils.get_item_properties( s, columns, - formatters={'Extra Specs': utils.format_dict, - 'id': _format_encryption_info}, + formatters=formatters, ) for s in data)) @@ -490,7 +522,7 @@ class ShowVolumeType(command.ShowOne): volume_client = self.app.client_manager.volume volume_type = utils.find_resource( volume_client.volume_types, parsed_args.volume_type) - properties = utils.format_dict( + properties = format_columns.DictColumn( volume_type._info.pop('extra_specs', {})) volume_type._info.update({'properties': properties}) access_project_ids = None @@ -501,8 +533,8 @@ class ShowVolumeType(command.ShowOne): project_ids = [utils.get_field(item, 'project_id') for item in volume_type_access] # TODO(Rui Chen): This format list case can be removed after - # patch https://review.openstack.org/#/c/330223/ merged. - access_project_ids = utils.format_list(project_ids) + # patch https://review.opendev.org/#/c/330223/ merged. + access_project_ids = format_columns.ListColumn(project_ids) except Exception as e: msg = _('Failed to get access project list for volume type ' '%(type)s: %(e)s') @@ -515,7 +547,8 @@ class ShowVolumeType(command.ShowOne): volume_type.id) encryption._info.pop("volume_type_id", None) volume_type._info.update( - {'encryption': utils.format_dict(encryption._info)}) + {'encryption': + format_columns.DictColumn(encryption._info)}) except Exception as e: LOG.error(_("Failed to display the encryption information " "of this volume type: %s"), e) diff --git a/python_openstackclient.egg-info/PKG-INFO b/python_openstackclient.egg-info/PKG-INFO index 4b18353..74dc726 100644 --- a/python_openstackclient.egg-info/PKG-INFO +++ b/python_openstackclient.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: python-openstackclient -Version: 3.19.0 +Version: 4.0.0 Summary: OpenStack Command-line Client Home-page: https://docs.openstack.org/python-openstackclient/latest/ Author: OpenStack @@ -24,8 +24,8 @@ Description: ======================== :alt: Latest Version OpenStackClient (aka OSC) is a command-line client for OpenStack that brings - the command set for Compute, Identity, Image, Object Store and Block Storage - APIs together in a single shell with a uniform command structure. + the command set for Compute, Identity, Image, Network, Object Store and Block + Storage APIs together in a single shell with a uniform command structure. The primary goal is to provide a unified shell command structure and a common language to describe operations in OpenStack. @@ -109,18 +109,6 @@ Description: ======================== If a password is not provided above (in plaintext), you will be interactively prompted to provide one securely. - Authentication may also be performed using an already-acquired token - and a URL pointing directly to the service API that presumably was acquired - from the Service Catalog:: - - export OS_TOKEN=<token> - export OS_URL=<url-to-openstack-service> - - The corresponding command-line options look very similar:: - - --os-token <token> - --os-url <url-to-openstack-service> - Platform: UNKNOWN Classifier: Environment :: OpenStack @@ -133,3 +121,4 @@ Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 diff --git a/python_openstackclient.egg-info/SOURCES.txt b/python_openstackclient.egg-info/SOURCES.txt index 3519ced..18ad6b9 100644 --- a/python_openstackclient.egg-info/SOURCES.txt +++ b/python_openstackclient.egg-info/SOURCES.txt @@ -33,7 +33,6 @@ doc/source/cli/command-objects/address-scope.rst doc/source/cli/command-objects/aggregate.rst doc/source/cli/command-objects/application-credentials.rst doc/source/cli/command-objects/availability-zone.rst -doc/source/cli/command-objects/backup.rst doc/source/cli/command-objects/catalog.rst doc/source/cli/command-objects/command.rst doc/source/cli/command-objects/complete.rst @@ -55,6 +54,7 @@ doc/source/cli/command-objects/extension.rst doc/source/cli/command-objects/federation-protocol.rst doc/source/cli/command-objects/flavor.rst doc/source/cli/command-objects/floating-ip-pool.rst +doc/source/cli/command-objects/floating-ip-port-forwarding.rst doc/source/cli/command-objects/floating-ip.rst doc/source/cli/command-objects/group.rst doc/source/cli/command-objects/host.rst @@ -64,7 +64,6 @@ doc/source/cli/command-objects/identity-provider.rst doc/source/cli/command-objects/image.rst doc/source/cli/command-objects/implied_role.rst doc/source/cli/command-objects/ip-availability.rst -doc/source/cli/command-objects/ip-fixed.rst doc/source/cli/command-objects/keypair.rst doc/source/cli/command-objects/limit.rst doc/source/cli/command-objects/limits.rst @@ -106,13 +105,11 @@ doc/source/cli/command-objects/server-image.rst doc/source/cli/command-objects/server.rst doc/source/cli/command-objects/service-provider.rst doc/source/cli/command-objects/service.rst -doc/source/cli/command-objects/snapshot.rst doc/source/cli/command-objects/subnet-pool.rst doc/source/cli/command-objects/subnet.rst doc/source/cli/command-objects/token.rst doc/source/cli/command-objects/trust.rst doc/source/cli/command-objects/usage.rst -doc/source/cli/command-objects/user-role.rst doc/source/cli/command-objects/user.rst doc/source/cli/command-objects/versions.rst doc/source/cli/command-objects/volume-backend.rst @@ -152,14 +149,12 @@ openstackclient/i18n.py openstackclient/shell.py openstackclient/api/__init__.py openstackclient/api/api.py -openstackclient/api/auth_plugin.py openstackclient/api/compute_v2.py openstackclient/api/image_v1.py openstackclient/api/image_v2.py openstackclient/api/object_store_v1.py openstackclient/common/__init__.py openstackclient/common/availability_zone.py -openstackclient/common/client_config.py openstackclient/common/clientmanager.py openstackclient/common/configuration.py openstackclient/common/extension.py @@ -174,9 +169,7 @@ openstackclient/compute/v2/__init__.py openstackclient/compute/v2/agent.py openstackclient/compute/v2/aggregate.py openstackclient/compute/v2/console.py -openstackclient/compute/v2/fixedip.py openstackclient/compute/v2/flavor.py -openstackclient/compute/v2/floatingip.py openstackclient/compute/v2/host.py openstackclient/compute/v2/hypervisor.py openstackclient/compute/v2/hypervisor_stats.py @@ -246,6 +239,7 @@ openstackclient/network/v2/_tag.py openstackclient/network/v2/address_scope.py openstackclient/network/v2/floating_ip.py openstackclient/network/v2/floating_ip_pool.py +openstackclient/network/v2/floating_ip_port_forwarding.py openstackclient/network/v2/ip_availability.py openstackclient/network/v2/network.py openstackclient/network/v2/network_agent.py @@ -277,6 +271,7 @@ openstackclient/tests/__init__.py openstackclient/tests/functional/__init__.py openstackclient/tests/functional/base.py openstackclient/tests/functional/common/__init__.py +openstackclient/tests/functional/common/test_args.py openstackclient/tests/functional/common/test_availability_zone.py openstackclient/tests/functional/common/test_configuration.py openstackclient/tests/functional/common/test_extension.py @@ -372,19 +367,19 @@ openstackclient/tests/functional/volume/v1/test_volume.py openstackclient/tests/functional/volume/v1/test_volume_type.py openstackclient/tests/functional/volume/v2/__init__.py openstackclient/tests/functional/volume/v2/common.py -openstackclient/tests/functional/volume/v2/test_backup.py openstackclient/tests/functional/volume/v2/test_qos.py openstackclient/tests/functional/volume/v2/test_service.py -openstackclient/tests/functional/volume/v2/test_snapshot.py openstackclient/tests/functional/volume/v2/test_transfer_request.py openstackclient/tests/functional/volume/v2/test_volume.py +openstackclient/tests/functional/volume/v2/test_volume_backup.py +openstackclient/tests/functional/volume/v2/test_volume_snapshot.py openstackclient/tests/functional/volume/v2/test_volume_type.py openstackclient/tests/functional/volume/v3/__init__.py openstackclient/tests/functional/volume/v3/common.py openstackclient/tests/functional/volume/v3/test_qos.py -openstackclient/tests/functional/volume/v3/test_snapshot.py openstackclient/tests/functional/volume/v3/test_transfer_request.py openstackclient/tests/functional/volume/v3/test_volume.py +openstackclient/tests/functional/volume/v3/test_volume_snapshot.py openstackclient/tests/functional/volume/v3/test_volume_type.py openstackclient/tests/unit/__init__.py openstackclient/tests/unit/fakes.py @@ -487,6 +482,7 @@ openstackclient/tests/unit/network/v2/test_floating_ip_compute.py openstackclient/tests/unit/network/v2/test_floating_ip_network.py openstackclient/tests/unit/network/v2/test_floating_ip_pool_compute.py openstackclient/tests/unit/network/v2/test_floating_ip_pool_network.py +openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py openstackclient/tests/unit/network/v2/test_ip_availability.py openstackclient/tests/unit/network/v2/test_network.py openstackclient/tests/unit/network/v2/test_network_agent.py @@ -522,49 +518,45 @@ openstackclient/tests/unit/volume/__init__.py openstackclient/tests/unit/volume/test_find_resource.py openstackclient/tests/unit/volume/v1/__init__.py openstackclient/tests/unit/volume/v1/fakes.py -openstackclient/tests/unit/volume/v1/test_backup.py openstackclient/tests/unit/volume/v1/test_qos_specs.py openstackclient/tests/unit/volume/v1/test_service.py -openstackclient/tests/unit/volume/v1/test_snapshot.py openstackclient/tests/unit/volume/v1/test_transfer_request.py openstackclient/tests/unit/volume/v1/test_type.py openstackclient/tests/unit/volume/v1/test_volume.py +openstackclient/tests/unit/volume/v1/test_volume_backup.py openstackclient/tests/unit/volume/v2/__init__.py openstackclient/tests/unit/volume/v2/fakes.py -openstackclient/tests/unit/volume/v2/test_backup.py openstackclient/tests/unit/volume/v2/test_backup_record.py openstackclient/tests/unit/volume/v2/test_consistency_group.py openstackclient/tests/unit/volume/v2/test_consistency_group_snapshot.py openstackclient/tests/unit/volume/v2/test_qos_specs.py openstackclient/tests/unit/volume/v2/test_service.py -openstackclient/tests/unit/volume/v2/test_snapshot.py openstackclient/tests/unit/volume/v2/test_transfer_request.py openstackclient/tests/unit/volume/v2/test_type.py openstackclient/tests/unit/volume/v2/test_volume.py openstackclient/tests/unit/volume/v2/test_volume_backend.py +openstackclient/tests/unit/volume/v2/test_volume_backup.py openstackclient/tests/unit/volume/v2/test_volume_host.py openstackclient/tests/unit/volume/v3/__init__.py openstackclient/volume/__init__.py openstackclient/volume/client.py openstackclient/volume/v1/__init__.py -openstackclient/volume/v1/backup.py openstackclient/volume/v1/qos_specs.py openstackclient/volume/v1/service.py -openstackclient/volume/v1/snapshot.py openstackclient/volume/v1/volume.py +openstackclient/volume/v1/volume_backup.py openstackclient/volume/v1/volume_snapshot.py openstackclient/volume/v1/volume_transfer_request.py openstackclient/volume/v1/volume_type.py openstackclient/volume/v2/__init__.py -openstackclient/volume/v2/backup.py openstackclient/volume/v2/backup_record.py openstackclient/volume/v2/consistency_group.py openstackclient/volume/v2/consistency_group_snapshot.py openstackclient/volume/v2/qos_specs.py openstackclient/volume/v2/service.py -openstackclient/volume/v2/snapshot.py openstackclient/volume/v2/volume.py openstackclient/volume/v2/volume_backend.py +openstackclient/volume/v2/volume_backup.py openstackclient/volume/v2/volume_host.py openstackclient/volume/v2/volume_snapshot.py openstackclient/volume/v2/volume_transfer_request.py @@ -584,7 +576,9 @@ releasenotes/notes/add-community-option-to-image-list-ac0651eb2e5d632f.yaml releasenotes/notes/add-device_id-to-port-list-0c658db51ce43c9e.yaml releasenotes/notes/add-disable-reason-6e0f28459a09a60d.yaml releasenotes/notes/add-dns-nameserver-overwrite-option-b866baeae12f9460.yaml +releasenotes/notes/add-fip-portforwarding-commands-6e4d8ace698ee308.yaml releasenotes/notes/add-ha-to-router-update-6a38a73cc112b2fc.yaml +releasenotes/notes/add-host-and-hypervisor-hostname-flag-to-create-server-cb8b39a9f9311d42.yaml releasenotes/notes/add-image-member-list-1630ead5988348c2.yaml releasenotes/notes/add-image-tag-filter-support-5cb039416b07caab.yaml releasenotes/notes/add-implied-role-0cdafb131fbd7453.yaml @@ -615,18 +609,23 @@ releasenotes/notes/add-server-add-network-98ede8ff6079eb23.yaml releasenotes/notes/add-server-backup-e63feaebb6140f83.yaml releasenotes/notes/add-server-create-image-property-ef76af26233b472b.yaml releasenotes/notes/add-server-group-quotas-b67fcba98619f0c9.yaml +releasenotes/notes/add-server-migrate-with-host-4884a71903c5c8a9.yaml releasenotes/notes/add-server-remove-network-fb09c53d5b0c0068.yaml +releasenotes/notes/add-server-resize-confirm-revert-commands-98854ca98965432a.yaml releasenotes/notes/add-subnet-list-command-970f4b397469bdc6.yaml releasenotes/notes/add-virtio-forwarder-vnic-type-bad939c6a868b9e9.yaml releasenotes/notes/add-volume-host-failover-8fc77b24533b7fed.yaml releasenotes/notes/address-scope-delete-multi-31c3af73feb31265.yaml releasenotes/notes/allow-custom-logging-12d55f8ed859ff8e.yaml +releasenotes/notes/allow-port-create-with-extra-dhcp-options-c2c40e4002b52e2a.yaml releasenotes/notes/allow-port-list-with-ip-address-substr-14c5805b241e402f.yaml releasenotes/notes/allow-to-add-remove-vm-ports-273593d7cc1982de.yaml releasenotes/notes/allow-to-create-legacy-router-cb4dcb44dde74684.yaml releasenotes/notes/allow-to-specify-vm-ip-to-publish-85f7207740c0cc8d.yaml releasenotes/notes/allow-to-vm-ip-to-add-7721ba64b863fa77.yaml releasenotes/notes/backup_list_all-projects_option-4bb23e0b9b075cac.yaml +releasenotes/notes/better-ipv6-address-suppport-security-group-rules-95272847349982e5.yaml +releasenotes/notes/bp-add-locked-reason-425efd2def1144f1.yaml releasenotes/notes/bp-allow-overwrite-set-options-190a9c6904d53dab.yaml releasenotes/notes/bp-application-credential-a7031a043efc4a25.yaml releasenotes/notes/bp-backup-snapshot-renamed-for-volume-resource-2d2d13ea8489a61f.yaml @@ -659,6 +658,7 @@ releasenotes/notes/bp-routed-networks-3eea4978c93aa126.yaml releasenotes/notes/bp-routed-networks-86a24f34d86fca53.yaml releasenotes/notes/bp-support-multi-add-remove-9516f72cfacea11a.yaml releasenotes/notes/bp-support-no-property-in-aggregate-b74a42e00a65d14a.yaml +releasenotes/notes/bp-support-specifying-az-when-restore-shelved-server-16e864223d51b50a.yaml releasenotes/notes/bp-unified-limits-58f166401534a4ff.yaml releasenotes/notes/bp-unified-limits-6c5fdb1c26805d86.yaml releasenotes/notes/bug-1204956-af47c7f34ecc19c3.yaml @@ -887,6 +887,7 @@ releasenotes/notes/bug-1704097-8ff1ce1444b81b04.yaml releasenotes/notes/bug-1711301-472b577f074edd43.yaml releasenotes/notes/bug-1712242-934bbe2f2378f5bd.yaml releasenotes/notes/bug-1714878-46806jv2yv13q054.yaml +releasenotes/notes/bug-1716789-abfae897b7e61246.yaml releasenotes/notes/bug-1717130-029211b60f74b4c4.yaml releasenotes/notes/bug-1717829-c1de1d777d3abaf9.yaml releasenotes/notes/bug-1719413-0401d05c91cc9094.yaml @@ -913,6 +914,7 @@ releasenotes/notes/bug-volume-list-with-project-2dc867c5e8346a66.yaml releasenotes/notes/change-098377fd53cce7a0.yaml releasenotes/notes/cliff-2.3.0-7ead18fae9ceea80.yaml releasenotes/notes/compute-agent-deff48988e81b30e.yaml +releasenotes/notes/config-show-00512dc60882e5c0.yaml releasenotes/notes/credential_list_user_type-c809e5b8014d6275.yaml releasenotes/notes/fix-network-rbac-create-d1f4de77ad2dd421.yaml releasenotes/notes/flavor-add-description-b618abd4a7fb6545.yaml @@ -942,6 +944,11 @@ releasenotes/notes/neutron_mtu-d87e53e2d76f8612.yaml releasenotes/notes/no-project-usage-list-e88eb49aa2e96cf7.yaml releasenotes/notes/object-stdout-db76cc500948b0e8.yaml releasenotes/notes/osc-included-image-signing-a7021a4dbdcf6336.yaml +releasenotes/notes/osc4-compute-09246008eff260cb.yaml +releasenotes/notes/osc4-identity-6564257c67d43106.yaml +releasenotes/notes/osc4-image-e922ee6f8e028648.yaml +releasenotes/notes/osc4-network-db2aed696d964ca6.yaml +releasenotes/notes/osc4-volume-470422e5a453310e.yaml releasenotes/notes/rbac-add-security-group-35370701a06ac906.yaml releasenotes/notes/recursive-container-delete-983361aa9c35ffed.yaml releasenotes/notes/remove-ip-floating-commands-d5363f313e09249a.yaml @@ -961,6 +968,9 @@ releasenotes/notes/server-set-state-214b12ec2161de4d.yaml releasenotes/notes/service-set-option-61772a8940ad0778.yaml releasenotes/notes/skip-name-lookups-9f499927173c1eee.yaml releasenotes/notes/speedup-object-save-6bd59e678a31c3e8.yaml +releasenotes/notes/story-2005349-compute-service-set-2.53-3d2db875154e633a.yaml +releasenotes/notes/story-2006302-add-boot-from-volume-cd411b1ca776bb3c.yaml +releasenotes/notes/story-2006302-support-bdm-type-image-0becfb63bd4fb969.yaml releasenotes/notes/subnet-service-type-8d9c414732e474a4.yaml releasenotes/notes/subnet-set-bbc26ecc16929302.yaml releasenotes/notes/subnet-set-segment-id-4440e433b170f9f3.yaml @@ -972,10 +982,12 @@ releasenotes/notes/support-uplink_status_propagation-4d37452bcf03e0f8.yaml releasenotes/notes/unlock-volume-a6990fc3bf1f5f67.yaml releasenotes/notes/unset-router-7b0cbd9518bb1de6.yaml releasenotes/notes/unset-subnet-pool-333052dd85b95653.yaml +releasenotes/notes/usage-list-all-49ca7a62c50e71d3.yaml releasenotes/notes/versions-show-12a2443624c83048.yaml releasenotes/notes/volume-backend-c5faae0b31556a24.yaml releasenotes/notes/volume-backup-record-9f5987c45e294dc6.yaml releasenotes/notes/volume-migrate-command-52cf6edd62fe17a7.yaml +releasenotes/notes/volume-v3-default-0ffa9bebb43b5057.yaml releasenotes/notes/volume_snapshot_list_project-e7dcc07f98d44182.yaml releasenotes/source/20_releases.rst releasenotes/source/conf.py diff --git a/python_openstackclient.egg-info/entry_points.txt b/python_openstackclient.egg-info/entry_points.txt index 85f69b0..98221fb 100644 --- a/python_openstackclient.egg-info/entry_points.txt +++ b/python_openstackclient.egg-info/entry_points.txt @@ -1,9 +1,6 @@ [console_scripts] openstack = openstackclient.shell:main -[keystoneauth1.plugin] -token_endpoint = openstackclient.api.auth_plugin:TokenEndpoint - [openstack.cli] command_list = openstackclient.common.module:ListCommand module_list = openstackclient.common.module:ListModule @@ -58,10 +55,6 @@ host_show = openstackclient.compute.v2.host:ShowHost hypervisor_list = openstackclient.compute.v2.hypervisor:ListHypervisor hypervisor_show = openstackclient.compute.v2.hypervisor:ShowHypervisor hypervisor_stats_show = openstackclient.compute.v2.hypervisor_stats:ShowHypervisorStats -ip_fixed_add = openstackclient.compute.v2.fixedip:AddFixedIP -ip_fixed_remove = openstackclient.compute.v2.fixedip:RemoveFixedIP -ip_floating_add = openstackclient.compute.v2.floatingip:AddFloatingIP -ip_floating_remove = openstackclient.compute.v2.floatingip:RemoveFloatingIP keypair_create = openstackclient.compute.v2.keypair:CreateKeypair keypair_delete = openstackclient.compute.v2.keypair:DeleteKeypair keypair_list = openstackclient.compute.v2.keypair:ListKeypair @@ -97,6 +90,8 @@ server_remove_security_group = openstackclient.compute.v2.server:RemoveServerSec server_remove_volume = openstackclient.compute.v2.server:RemoveServerVolume server_rescue = openstackclient.compute.v2.server:RescueServer server_resize = openstackclient.compute.v2.server:ResizeServer +server_resize_confirm = openstackclient.compute.v2.server:ResizeConfirm +server_resize_revert = openstackclient.compute.v2.server:ResizeRevert server_restore = openstackclient.compute.v2.server:RestoreServer server_resume = openstackclient.compute.v2.server:ResumeServer server_set = openstackclient.compute.v2.server:SetServer @@ -147,7 +142,6 @@ token_revoke = openstackclient.identity.v2_0.token:RevokeToken user_create = openstackclient.identity.v2_0.user:CreateUser user_delete = openstackclient.identity.v2_0.user:DeleteUser user_list = openstackclient.identity.v2_0.user:ListUser -user_role_list = openstackclient.identity.v2_0.role:ListUserRole user_set = openstackclient.identity.v2_0.user:SetUser user_show = openstackclient.identity.v2_0.user:ShowUser @@ -308,6 +302,11 @@ floating_ip_create = openstackclient.network.v2.floating_ip:CreateFloatingIP floating_ip_delete = openstackclient.network.v2.floating_ip:DeleteFloatingIP floating_ip_list = openstackclient.network.v2.floating_ip:ListFloatingIP floating_ip_pool_list = openstackclient.network.v2.floating_ip_pool:ListFloatingIPPool +floating_ip_port_forwarding_create = openstackclient.network.v2.floating_ip_port_forwarding:CreateFloatingIPPortForwarding +floating_ip_port_forwarding_delete = openstackclient.network.v2.floating_ip_port_forwarding:DeleteFloatingIPPortForwarding +floating_ip_port_forwarding_list = openstackclient.network.v2.floating_ip_port_forwarding:ListFloatingIPPortForwarding +floating_ip_port_forwarding_set = openstackclient.network.v2.floating_ip_port_forwarding:SetFloatingIPPortForwarding +floating_ip_port_forwarding_show = openstackclient.network.v2.floating_ip_port_forwarding:ShowFloatingIPPortForwarding floating_ip_set = openstackclient.network.v2.floating_ip:SetFloatingIP floating_ip_show = openstackclient.network.v2.floating_ip:ShowFloatingIP floating_ip_unset = openstackclient.network.v2.floating_ip:UnsetFloatingIP @@ -436,22 +435,11 @@ object_store_account_unset = openstackclient.object.v1.account:UnsetAccount object_unset = openstackclient.object.v1.object:UnsetObject [openstack.volume.v1] -backup_create = openstackclient.volume.v1.backup:CreateBackup -backup_delete = openstackclient.volume.v1.backup:DeleteBackup -backup_list = openstackclient.volume.v1.backup:ListBackup -backup_restore = openstackclient.volume.v1.backup:RestoreBackup -backup_show = openstackclient.volume.v1.backup:ShowBackup -snapshot_create = openstackclient.volume.v1.snapshot:CreateSnapshot -snapshot_delete = openstackclient.volume.v1.snapshot:DeleteSnapshot -snapshot_list = openstackclient.volume.v1.snapshot:ListSnapshot -snapshot_set = openstackclient.volume.v1.snapshot:SetSnapshot -snapshot_show = openstackclient.volume.v1.snapshot:ShowSnapshot -snapshot_unset = openstackclient.volume.v1.snapshot:UnsetSnapshot -volume_backup_create = openstackclient.volume.v1.backup:CreateVolumeBackup -volume_backup_delete = openstackclient.volume.v1.backup:DeleteVolumeBackup -volume_backup_list = openstackclient.volume.v1.backup:ListVolumeBackup -volume_backup_restore = openstackclient.volume.v1.backup:RestoreVolumeBackup -volume_backup_show = openstackclient.volume.v1.backup:ShowVolumeBackup +volume_backup_create = openstackclient.volume.v1.volume_backup:CreateVolumeBackup +volume_backup_delete = openstackclient.volume.v1.volume_backup:DeleteVolumeBackup +volume_backup_list = openstackclient.volume.v1.volume_backup:ListVolumeBackup +volume_backup_restore = openstackclient.volume.v1.volume_backup:RestoreVolumeBackup +volume_backup_show = openstackclient.volume.v1.volume_backup:ShowVolumeBackup volume_create = openstackclient.volume.v1.volume:CreateVolume volume_delete = openstackclient.volume.v1.volume:DeleteVolume volume_list = openstackclient.volume.v1.volume:ListVolume @@ -488,11 +476,6 @@ volume_type_unset = openstackclient.volume.v1.volume_type:UnsetVolumeType volume_unset = openstackclient.volume.v1.volume:UnsetVolume [openstack.volume.v2] -backup_create = openstackclient.volume.v2.backup:CreateBackup -backup_delete = openstackclient.volume.v2.backup:DeleteBackup -backup_list = openstackclient.volume.v2.backup:ListBackup -backup_restore = openstackclient.volume.v2.backup:RestoreBackup -backup_show = openstackclient.volume.v2.backup:ShowBackup consistency_group_add_volume = openstackclient.volume.v2.consistency_group:AddVolumeToConsistencyGroup consistency_group_create = openstackclient.volume.v2.consistency_group:CreateConsistencyGroup consistency_group_delete = openstackclient.volume.v2.consistency_group:DeleteConsistencyGroup @@ -504,22 +487,16 @@ consistency_group_snapshot_create = openstackclient.volume.v2.consistency_group_ consistency_group_snapshot_delete = openstackclient.volume.v2.consistency_group_snapshot:DeleteConsistencyGroupSnapshot consistency_group_snapshot_list = openstackclient.volume.v2.consistency_group_snapshot:ListConsistencyGroupSnapshot consistency_group_snapshot_show = openstackclient.volume.v2.consistency_group_snapshot:ShowConsistencyGroupSnapshot -snapshot_create = openstackclient.volume.v2.snapshot:CreateSnapshot -snapshot_delete = openstackclient.volume.v2.snapshot:DeleteSnapshot -snapshot_list = openstackclient.volume.v2.snapshot:ListSnapshot -snapshot_set = openstackclient.volume.v2.snapshot:SetSnapshot -snapshot_show = openstackclient.volume.v2.snapshot:ShowSnapshot -snapshot_unset = openstackclient.volume.v2.snapshot:UnsetSnapshot volume_backend_capability_show = openstackclient.volume.v2.volume_backend:ShowCapability volume_backend_pool_list = openstackclient.volume.v2.volume_backend:ListPool -volume_backup_create = openstackclient.volume.v2.backup:CreateVolumeBackup -volume_backup_delete = openstackclient.volume.v2.backup:DeleteVolumeBackup -volume_backup_list = openstackclient.volume.v2.backup:ListVolumeBackup +volume_backup_create = openstackclient.volume.v2.volume_backup:CreateVolumeBackup +volume_backup_delete = openstackclient.volume.v2.volume_backup:DeleteVolumeBackup +volume_backup_list = openstackclient.volume.v2.volume_backup:ListVolumeBackup volume_backup_record_export = openstackclient.volume.v2.backup_record:ExportBackupRecord volume_backup_record_import = openstackclient.volume.v2.backup_record:ImportBackupRecord -volume_backup_restore = openstackclient.volume.v2.backup:RestoreVolumeBackup -volume_backup_set = openstackclient.volume.v2.backup:SetVolumeBackup -volume_backup_show = openstackclient.volume.v2.backup:ShowVolumeBackup +volume_backup_restore = openstackclient.volume.v2.volume_backup:RestoreVolumeBackup +volume_backup_set = openstackclient.volume.v2.volume_backup:SetVolumeBackup +volume_backup_show = openstackclient.volume.v2.volume_backup:ShowVolumeBackup volume_create = openstackclient.volume.v2.volume:CreateVolume volume_delete = openstackclient.volume.v2.volume:DeleteVolume volume_host_failover = openstackclient.volume.v2.volume_host:FailoverVolumeHost @@ -569,12 +546,12 @@ consistency_group_snapshot_create = openstackclient.volume.v2.consistency_group_ consistency_group_snapshot_delete = openstackclient.volume.v2.consistency_group_snapshot:DeleteConsistencyGroupSnapshot consistency_group_snapshot_list = openstackclient.volume.v2.consistency_group_snapshot:ListConsistencyGroupSnapshot consistency_group_snapshot_show = openstackclient.volume.v2.consistency_group_snapshot:ShowConsistencyGroupSnapshot -volume_backup_create = openstackclient.volume.v2.backup:CreateVolumeBackup -volume_backup_delete = openstackclient.volume.v2.backup:DeleteVolumeBackup -volume_backup_list = openstackclient.volume.v2.backup:ListVolumeBackup -volume_backup_restore = openstackclient.volume.v2.backup:RestoreVolumeBackup -volume_backup_set = openstackclient.volume.v2.backup:SetVolumeBackup -volume_backup_show = openstackclient.volume.v2.backup:ShowVolumeBackup +volume_backup_create = openstackclient.volume.v2.volume_backup:CreateVolumeBackup +volume_backup_delete = openstackclient.volume.v2.volume_backup:DeleteVolumeBackup +volume_backup_list = openstackclient.volume.v2.volume_backup:ListVolumeBackup +volume_backup_restore = openstackclient.volume.v2.volume_backup:RestoreVolumeBackup +volume_backup_set = openstackclient.volume.v2.volume_backup:SetVolumeBackup +volume_backup_show = openstackclient.volume.v2.volume_backup:ShowVolumeBackup volume_create = openstackclient.volume.v2.volume:CreateVolume volume_delete = openstackclient.volume.v2.volume:DeleteVolume volume_host_set = openstackclient.volume.v2.volume_host:SetVolumeHost diff --git a/python_openstackclient.egg-info/pbr.json b/python_openstackclient.egg-info/pbr.json index 2ad4f18..4742cf4 100644 --- a/python_openstackclient.egg-info/pbr.json +++ b/python_openstackclient.egg-info/pbr.json @@ -1 +1 @@ -{"git_version": "57dec524", "is_release": true}
\ No newline at end of file +{"git_version": "aa64eb6b", "is_release": true}
\ No newline at end of file diff --git a/python_openstackclient.egg-info/requires.txt b/python_openstackclient.egg-info/requires.txt index 5918252..fe6b8f8 100644 --- a/python_openstackclient.egg-info/requires.txt +++ b/python_openstackclient.egg-info/requires.txt @@ -4,10 +4,10 @@ Babel!=2.4.0,>=2.3.4 cliff!=2.9.0,>=2.8.0 keystoneauth1>=3.6.2 openstacksdk>=0.17.0 -osc-lib>=1.10.0 +osc-lib>=1.14.0 oslo.i18n>=3.15.3 oslo.utils>=3.33.0 python-glanceclient>=2.8.0 python-keystoneclient>=3.17.0 -python-novaclient>=10.0.0 +python-novaclient>=15.0.0 python-cinderclient>=3.3.0 diff --git a/releasenotes/notes/add-fip-portforwarding-commands-6e4d8ace698ee308.yaml b/releasenotes/notes/add-fip-portforwarding-commands-6e4d8ace698ee308.yaml new file mode 100644 index 0000000..87175e4 --- /dev/null +++ b/releasenotes/notes/add-fip-portforwarding-commands-6e4d8ace698ee308.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Add floating IP Port Forwarding commands: + ``floating ip port forwarding create``, + ``floating ip port forwarding delete``, + ``floating ip port forwarding list``, + ``floating ip port forwarding set`` and + ``floating ip port forwarding show``. diff --git a/releasenotes/notes/add-host-and-hypervisor-hostname-flag-to-create-server-cb8b39a9f9311d42.yaml b/releasenotes/notes/add-host-and-hypervisor-hostname-flag-to-create-server-cb8b39a9f9311d42.yaml new file mode 100644 index 0000000..fbd4c70 --- /dev/null +++ b/releasenotes/notes/add-host-and-hypervisor-hostname-flag-to-create-server-cb8b39a9f9311d42.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--host`` and ``--hypervisor-hostname`` options to + ``server create`` command. + [Blueprint `add-host-and-hypervisor-hostname-flag-to-create-server <https://blueprints.launchpad.net/nova/+spec/add-host-and-hypervisor-hostname-flag-to-create-server>`_]
\ No newline at end of file diff --git a/releasenotes/notes/add-server-migrate-with-host-4884a71903c5c8a9.yaml b/releasenotes/notes/add-server-migrate-with-host-4884a71903c5c8a9.yaml new file mode 100644 index 0000000..8da704a --- /dev/null +++ b/releasenotes/notes/add-server-migrate-with-host-4884a71903c5c8a9.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Add ``--host`` option to ``server migrate`` command + (cold migration) to specify the target host of the migration. + Requires ``--os-compute-api-version`` 2.56 or greater to target a + specific host for the (cold) migration. + [Story `2003325 <https://storyboard.openstack.org/#!/story/2003325>`_] diff --git a/releasenotes/notes/add-server-resize-confirm-revert-commands-98854ca98965432a.yaml b/releasenotes/notes/add-server-resize-confirm-revert-commands-98854ca98965432a.yaml new file mode 100644 index 0000000..2399a1a --- /dev/null +++ b/releasenotes/notes/add-server-resize-confirm-revert-commands-98854ca98965432a.yaml @@ -0,0 +1,12 @@ +--- +features: + - | + Add ``server resize confirm`` and ``server resize revert`` commands. + These replace the now deprecated ``--confirm`` and ``--revert`` + options to the ``server resize`` commands, respectively. +deprecations: + - | + Deprecate the ``--confirm`` and ``--revert`` options for the + ``server resize`` command. They have been replaced with the + ``server resize confirm`` and `server resize revert`` commands, + respectively. diff --git a/releasenotes/notes/allow-port-create-with-extra-dhcp-options-c2c40e4002b52e2a.yaml b/releasenotes/notes/allow-port-create-with-extra-dhcp-options-c2c40e4002b52e2a.yaml new file mode 100644 index 0000000..ed4fa1b --- /dev/null +++ b/releasenotes/notes/allow-port-create-with-extra-dhcp-options-c2c40e4002b52e2a.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--extra-dhcp-options`` parameter to the ``port create`` command. The + neutronclient ``port-create`` command can accept extra DHCP options, add + it to the openstackclient in order to be consistent.
\ No newline at end of file diff --git a/releasenotes/notes/better-ipv6-address-suppport-security-group-rules-95272847349982e5.yaml b/releasenotes/notes/better-ipv6-address-suppport-security-group-rules-95272847349982e5.yaml new file mode 100644 index 0000000..7177873 --- /dev/null +++ b/releasenotes/notes/better-ipv6-address-suppport-security-group-rules-95272847349982e5.yaml @@ -0,0 +1,14 @@ +--- +features: + - | + Add ``--ethertype`` option to ``security group rule list`` command. + Valid values are ``ipv4`` and ``ipv6``. +upgrade: + - | + Security group rule listings now have the ``Ethertype`` field displayed + by default to more easily differentiate between IPv4 and IPv6 rules. + In addition, the ``IP Range`` field of a security group will be + changed to ``0.0.0.0/0`` for IPv4 and ``::/0`` for IPv6 if no + value is returned for the address, based on the Ethertype field of + the rule. For further information see + [Bug `1735575 <https://bugs.launchpad.net/bugs/1735575>`_] diff --git a/releasenotes/notes/bp-add-locked-reason-425efd2def1144f1.yaml b/releasenotes/notes/bp-add-locked-reason-425efd2def1144f1.yaml new file mode 100644 index 0000000..e9f6cc6 --- /dev/null +++ b/releasenotes/notes/bp-add-locked-reason-425efd2def1144f1.yaml @@ -0,0 +1,13 @@ +--- +features: + - Add ``--reason`` option to the ``server lock`` command to specify a reason + when locking a server. + Requires ``–os-compute-api-version`` 2.73 or greater. + - Add ``--locked`` option to the ``server list`` command to list only locked + servers. + Requires ``–os-compute-api-version`` 2.73 or greater. + - Add ``--unlocked`` option to the ``server list`` command list only unlocked + servers. + Requires ``–os-compute-api-version`` 2.73 or greater. + [Blueprint `add-locked-reason <https://blueprints.launchpad.net/nova/+spec/add-locked-reason>`_] +
\ No newline at end of file diff --git a/releasenotes/notes/bp-support-specifying-az-when-restore-shelved-server-16e864223d51b50a.yaml b/releasenotes/notes/bp-support-specifying-az-when-restore-shelved-server-16e864223d51b50a.yaml new file mode 100644 index 0000000..adb55b1 --- /dev/null +++ b/releasenotes/notes/bp-support-specifying-az-when-restore-shelved-server-16e864223d51b50a.yaml @@ -0,0 +1,7 @@ +--- +features: + - Add ``--availability-zone`` option to ``server unshelve`` + command to enable users to specify an availability zone during + unshelve of a shelved offloaded server. Note that it requires + ``--os-compute-api-version 2.77`` or greater. + [Blueprint `support-specifying-az-when-restore-shelved-server <https://blueprints.launchpad.net/nova/+spec/support-specifying-az-when-restore-shelved-server>`_] diff --git a/releasenotes/notes/bug-1716789-abfae897b7e61246.yaml b/releasenotes/notes/bug-1716789-abfae897b7e61246.yaml new file mode 100644 index 0000000..4177f80 --- /dev/null +++ b/releasenotes/notes/bug-1716789-abfae897b7e61246.yaml @@ -0,0 +1,10 @@ +--- +fixes: + - | + Change the default value of ``--protocol`` option to ``any`` in + ``security group rule create`` command when using the Neutron v2 API. + [Bug `1716789 <https://bugs.launchpad.net/bugs/1716789>`_] +upgrade: + - | + Commands that assumed the default value of ``--protocol`` to be ``tcp`` + now must include ``--protocol tcp`` explicitly in Network commands. diff --git a/releasenotes/notes/config-show-00512dc60882e5c0.yaml b/releasenotes/notes/config-show-00512dc60882e5c0.yaml new file mode 100644 index 0000000..da57dff --- /dev/null +++ b/releasenotes/notes/config-show-00512dc60882e5c0.yaml @@ -0,0 +1,6 @@ +--- +other: + - | + The ``configuration show`` command no longer requires authentication. + This is useful in debugging cloud configurations, particularly + auth configurations. diff --git a/releasenotes/notes/osc4-compute-09246008eff260cb.yaml b/releasenotes/notes/osc4-compute-09246008eff260cb.yaml new file mode 100644 index 0000000..ab7baa6 --- /dev/null +++ b/releasenotes/notes/osc4-compute-09246008eff260cb.yaml @@ -0,0 +1,8 @@ +--- +upgrade: + - | + Remove deprecated ``ip fixed add|remove`` commands. + Use ``server add|remove fixed ip`` commands instead. + - | + Remove deprecated ``ip floating add|remove`` commands. + Use ``server add|remove floating ip`` commands instead. diff --git a/releasenotes/notes/osc4-identity-6564257c67d43106.yaml b/releasenotes/notes/osc4-identity-6564257c67d43106.yaml new file mode 100644 index 0000000..a5105c8 --- /dev/null +++ b/releasenotes/notes/osc4-identity-6564257c67d43106.yaml @@ -0,0 +1,12 @@ +--- +upgrade: + - | + Remove deprecated ``role list`` options ``--project`` and ``--user``. + Use ``role assignment list`` options ``--project`` and ``--user`` instead. + - | + Remove deprecated ``user role list`` command. + Use ``role assignment list`` options ``--project`` and ``--user`` instead. + - | + Remove deprecated ``service create`` option ``--type``. + The type is supplied as a positional argument in The + ``service create --name <service-name> type`` command. diff --git a/releasenotes/notes/osc4-image-e922ee6f8e028648.yaml b/releasenotes/notes/osc4-image-e922ee6f8e028648.yaml new file mode 100644 index 0000000..eab3f70 --- /dev/null +++ b/releasenotes/notes/osc4-image-e922ee6f8e028648.yaml @@ -0,0 +1,5 @@ +--- +upgrade: + - | + Remove ``image create|set`` option ``--owner``. + Use ``--project`` option instead. diff --git a/releasenotes/notes/osc4-network-db2aed696d964ca6.yaml b/releasenotes/notes/osc4-network-db2aed696d964ca6.yaml new file mode 100644 index 0000000..7ae3001 --- /dev/null +++ b/releasenotes/notes/osc4-network-db2aed696d964ca6.yaml @@ -0,0 +1,12 @@ +--- +upgrade: + - | + Remove deprecated ``port create|set`` options ``--device-id`` and ``--host-id``. + Use ``--device`` and ``--host`` options instead. + - | + Remove deprecated ``router set`` option ``--clear-routes``. + Use ``--no-route`` option instead. + - | + Remove deprecated ``security group rule create`` options ``--src-ip`` and + ``--src-group``. + Use ``--remote-ip`` and ``--remote-group`` options instead. diff --git a/releasenotes/notes/osc4-volume-470422e5a453310e.yaml b/releasenotes/notes/osc4-volume-470422e5a453310e.yaml new file mode 100644 index 0000000..e03f0fd --- /dev/null +++ b/releasenotes/notes/osc4-volume-470422e5a453310e.yaml @@ -0,0 +1,14 @@ +--- +upgrade: + - | + Remove deprecated ``backup`` commands. + Use ``volume backup`` commands instead. + - | + Remove deprecated ``snapshot`` commands. + Use ``volume snapshot`` commands instead. + - | + Remove deprecated ``volume create`` options ``--project``, ``--user`` + and ``--multi-attach``. + - | + Change ``volume transfer request accept`` to use new required option + ``--auth-key`` rather than a second positional argument. diff --git a/releasenotes/notes/story-2005349-compute-service-set-2.53-3d2db875154e633a.yaml b/releasenotes/notes/story-2005349-compute-service-set-2.53-3d2db875154e633a.yaml new file mode 100644 index 0000000..cff3196 --- /dev/null +++ b/releasenotes/notes/story-2005349-compute-service-set-2.53-3d2db875154e633a.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + The ``compute service set`` command now properly handles + ``--os-compute-api-version`` 2.53 and greater. + [Story `2005349 <https://storyboard.openstack.org/#!/story/2005349>`_] diff --git a/releasenotes/notes/story-2006302-add-boot-from-volume-cd411b1ca776bb3c.yaml b/releasenotes/notes/story-2006302-add-boot-from-volume-cd411b1ca776bb3c.yaml new file mode 100644 index 0000000..1176c88 --- /dev/null +++ b/releasenotes/notes/story-2006302-add-boot-from-volume-cd411b1ca776bb3c.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Add ``--boot-from-volume`` option to the ``server create`` command + to create a volume-backed server from the specified image with the + specified size when used in conjunction with the ``--image`` or + ``--image-property`` options. + [Story `2006302 <https://storyboard.openstack.org/#!/story/2006302>`_] diff --git a/releasenotes/notes/story-2006302-support-bdm-type-image-0becfb63bd4fb969.yaml b/releasenotes/notes/story-2006302-support-bdm-type-image-0becfb63bd4fb969.yaml new file mode 100644 index 0000000..6e8e8ba --- /dev/null +++ b/releasenotes/notes/story-2006302-support-bdm-type-image-0becfb63bd4fb969.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + The ``server create --block-device-mapping`` option now supports + an ``image`` type in addition to ``volume`` and ``snapshot``. When + specifying an image block device the compute service will create a volume + from the image of the specified size and attach it to the server. + [Story `2006302 <https://storyboard.openstack.org/#!/story/2006302>`_] diff --git a/releasenotes/notes/usage-list-all-49ca7a62c50e71d3.yaml b/releasenotes/notes/usage-list-all-49ca7a62c50e71d3.yaml new file mode 100644 index 0000000..76a09cc --- /dev/null +++ b/releasenotes/notes/usage-list-all-49ca7a62c50e71d3.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Compute API v2.40+ returns all matching entities rather than being + limited to the API server configured maximum (``CONF.api.max_limit``). + [Story `2005099 <https://storyboard.openstack.org/#!/story/2005099>`_] diff --git a/releasenotes/notes/volume-v3-default-0ffa9bebb43b5057.yaml b/releasenotes/notes/volume-v3-default-0ffa9bebb43b5057.yaml new file mode 100644 index 0000000..6b08407 --- /dev/null +++ b/releasenotes/notes/volume-v3-default-0ffa9bebb43b5057.yaml @@ -0,0 +1,7 @@ +--- +upgrade: + - | + Volume commands now default to Volume API 3. On older clouds + that do not support Volume 3.x ``--os-volume-api-version 2`` + or the adition of ``volume_api_version: '2'`` in ``clouds.yaml`` + will be required. diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index 9e2d8ce..c9cc7a2 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -27,6 +27,8 @@ OpenStack release was made is shown below: ================= ======================= OpenStack Release OpenStackClient Release ================= ======================= +Train 4.0.0 +Stein 3.18.0 Rocky 3.16.0 Queens 3.14.0 Pike 3.12.0 diff --git a/requirements.txt b/requirements.txt index aa5debf..67139bb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,10 +8,10 @@ Babel!=2.4.0,>=2.3.4 # BSD cliff!=2.9.0,>=2.8.0 # Apache-2.0 keystoneauth1>=3.6.2 # Apache-2.0 openstacksdk>=0.17.0 # Apache-2.0 -osc-lib>=1.10.0 # Apache-2.0 +osc-lib>=1.14.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 python-glanceclient>=2.8.0 # Apache-2.0 python-keystoneclient>=3.17.0 # Apache-2.0 -python-novaclient>=10.0.0 # Apache-2.0 +python-novaclient>=15.0.0 # Apache-2.0 python-cinderclient>=3.3.0 # Apache-2.0 @@ -17,6 +17,7 @@ classifier = Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 [files] packages = @@ -25,8 +26,6 @@ packages = [entry_points] console_scripts = openstack = openstackclient.shell:main -keystoneauth1.plugin = - token_endpoint = openstackclient.api.auth_plugin:TokenEndpoint openstack.cli = command_list = openstackclient.common.module:ListCommand module_list = openstackclient.common.module:ListModule @@ -86,12 +85,6 @@ openstack.compute.v2 = hypervisor_stats_show = openstackclient.compute.v2.hypervisor_stats:ShowHypervisorStats - ip_fixed_add = openstackclient.compute.v2.fixedip:AddFixedIP - ip_fixed_remove = openstackclient.compute.v2.fixedip:RemoveFixedIP - - ip_floating_add = openstackclient.compute.v2.floatingip:AddFloatingIP - ip_floating_remove = openstackclient.compute.v2.floatingip:RemoveFloatingIP - keypair_create = openstackclient.compute.v2.keypair:CreateKeypair keypair_delete = openstackclient.compute.v2.keypair:DeleteKeypair keypair_list = openstackclient.compute.v2.keypair:ListKeypair @@ -119,6 +112,8 @@ openstack.compute.v2 = server_remove_volume = openstackclient.compute.v2.server:RemoveServerVolume server_rescue = openstackclient.compute.v2.server:RescueServer server_resize = openstackclient.compute.v2.server:ResizeServer + server_resize_confirm = openstackclient.compute.v2.server:ResizeConfirm + server_resize_revert = openstackclient.compute.v2.server:ResizeRevert server_restore = openstackclient.compute.v2.server:RestoreServer server_resume = openstackclient.compute.v2.server:ResumeServer server_set = openstackclient.compute.v2.server:SetServer @@ -191,8 +186,6 @@ openstack.identity.v2 = user_list = openstackclient.identity.v2_0.user:ListUser user_set = openstackclient.identity.v2_0.user:SetUser user_show = openstackclient.identity.v2_0.user:ShowUser - - user_role_list = openstackclient.identity.v2_0.role:ListUserRole openstack.identity.v3 = access_token_create = openstackclient.identity.v3.token:CreateAccessToken @@ -379,6 +372,12 @@ openstack.network.v2 = floating_ip_pool_list = openstackclient.network.v2.floating_ip_pool:ListFloatingIPPool + floating_ip_port_forwarding_create = openstackclient.network.v2.floating_ip_port_forwarding:CreateFloatingIPPortForwarding + floating_ip_port_forwarding_delete = openstackclient.network.v2.floating_ip_port_forwarding:DeleteFloatingIPPortForwarding + floating_ip_port_forwarding_list = openstackclient.network.v2.floating_ip_port_forwarding:ListFloatingIPPortForwarding + floating_ip_port_forwarding_set = openstackclient.network.v2.floating_ip_port_forwarding:SetFloatingIPPortForwarding + floating_ip_port_forwarding_show = openstackclient.network.v2.floating_ip_port_forwarding:ShowFloatingIPPortForwarding + ip_availability_list = openstackclient.network.v2.ip_availability:ListIPAvailability ip_availability_show = openstackclient.network.v2.ip_availability:ShowIPAvailability @@ -522,19 +521,6 @@ openstack.object_store.v1 = object_show = openstackclient.object.v1.object:ShowObject object_unset = openstackclient.object.v1.object:UnsetObject openstack.volume.v1 = - backup_create = openstackclient.volume.v1.backup:CreateBackup - backup_delete = openstackclient.volume.v1.backup:DeleteBackup - backup_list = openstackclient.volume.v1.backup:ListBackup - backup_restore = openstackclient.volume.v1.backup:RestoreBackup - backup_show = openstackclient.volume.v1.backup:ShowBackup - - snapshot_create = openstackclient.volume.v1.snapshot:CreateSnapshot - snapshot_delete = openstackclient.volume.v1.snapshot:DeleteSnapshot - snapshot_list = openstackclient.volume.v1.snapshot:ListSnapshot - snapshot_set = openstackclient.volume.v1.snapshot:SetSnapshot - snapshot_show = openstackclient.volume.v1.snapshot:ShowSnapshot - snapshot_unset = openstackclient.volume.v1.snapshot:UnsetSnapshot - volume_create = openstackclient.volume.v1.volume:CreateVolume volume_delete = openstackclient.volume.v1.volume:DeleteVolume volume_list = openstackclient.volume.v1.volume:ListVolume @@ -543,11 +529,11 @@ openstack.volume.v1 = volume_show = openstackclient.volume.v1.volume:ShowVolume volume_unset = openstackclient.volume.v1.volume:UnsetVolume - volume_backup_create = openstackclient.volume.v1.backup:CreateVolumeBackup - volume_backup_delete = openstackclient.volume.v1.backup:DeleteVolumeBackup - volume_backup_list = openstackclient.volume.v1.backup:ListVolumeBackup - volume_backup_restore = openstackclient.volume.v1.backup:RestoreVolumeBackup - volume_backup_show = openstackclient.volume.v1.backup:ShowVolumeBackup + volume_backup_create = openstackclient.volume.v1.volume_backup:CreateVolumeBackup + volume_backup_delete = openstackclient.volume.v1.volume_backup:DeleteVolumeBackup + volume_backup_list = openstackclient.volume.v1.volume_backup:ListVolumeBackup + volume_backup_restore = openstackclient.volume.v1.volume_backup:RestoreVolumeBackup + volume_backup_show = openstackclient.volume.v1.volume_backup:ShowVolumeBackup volume_snapshot_create = openstackclient.volume.v1.volume_snapshot:CreateVolumeSnapshot volume_snapshot_delete = openstackclient.volume.v1.volume_snapshot:DeleteVolumeSnapshot @@ -581,12 +567,6 @@ openstack.volume.v1 = volume_transfer_request_list = openstackclient.volume.v1.volume_transfer_request:ListTransferRequest volume_transfer_request_show = openstackclient.volume.v1.volume_transfer_request:ShowTransferRequest openstack.volume.v2 = - backup_create = openstackclient.volume.v2.backup:CreateBackup - backup_delete = openstackclient.volume.v2.backup:DeleteBackup - backup_list = openstackclient.volume.v2.backup:ListBackup - backup_restore = openstackclient.volume.v2.backup:RestoreBackup - backup_show = openstackclient.volume.v2.backup:ShowBackup - consistency_group_add_volume = openstackclient.volume.v2.consistency_group:AddVolumeToConsistencyGroup consistency_group_create = openstackclient.volume.v2.consistency_group:CreateConsistencyGroup consistency_group_delete = openstackclient.volume.v2.consistency_group:DeleteConsistencyGroup @@ -600,13 +580,6 @@ openstack.volume.v2 = consistency_group_snapshot_list = openstackclient.volume.v2.consistency_group_snapshot:ListConsistencyGroupSnapshot consistency_group_snapshot_show = openstackclient.volume.v2.consistency_group_snapshot:ShowConsistencyGroupSnapshot - snapshot_create = openstackclient.volume.v2.snapshot:CreateSnapshot - snapshot_delete = openstackclient.volume.v2.snapshot:DeleteSnapshot - snapshot_list = openstackclient.volume.v2.snapshot:ListSnapshot - snapshot_set = openstackclient.volume.v2.snapshot:SetSnapshot - snapshot_show = openstackclient.volume.v2.snapshot:ShowSnapshot - snapshot_unset = openstackclient.volume.v2.snapshot:UnsetSnapshot - volume_create = openstackclient.volume.v2.volume:CreateVolume volume_delete = openstackclient.volume.v2.volume:DeleteVolume volume_list = openstackclient.volume.v2.volume:ListVolume @@ -615,12 +588,12 @@ openstack.volume.v2 = volume_show = openstackclient.volume.v2.volume:ShowVolume volume_unset = openstackclient.volume.v2.volume:UnsetVolume - volume_backup_create = openstackclient.volume.v2.backup:CreateVolumeBackup - volume_backup_delete = openstackclient.volume.v2.backup:DeleteVolumeBackup - volume_backup_list = openstackclient.volume.v2.backup:ListVolumeBackup - volume_backup_restore = openstackclient.volume.v2.backup:RestoreVolumeBackup - volume_backup_set = openstackclient.volume.v2.backup:SetVolumeBackup - volume_backup_show = openstackclient.volume.v2.backup:ShowVolumeBackup + volume_backup_create = openstackclient.volume.v2.volume_backup:CreateVolumeBackup + volume_backup_delete = openstackclient.volume.v2.volume_backup:DeleteVolumeBackup + volume_backup_list = openstackclient.volume.v2.volume_backup:ListVolumeBackup + volume_backup_restore = openstackclient.volume.v2.volume_backup:RestoreVolumeBackup + volume_backup_set = openstackclient.volume.v2.volume_backup:SetVolumeBackup + volume_backup_show = openstackclient.volume.v2.volume_backup:ShowVolumeBackup volume_backup_record_export = openstackclient.volume.v2.backup_record:ExportBackupRecord volume_backup_record_import = openstackclient.volume.v2.backup_record:ImportBackupRecord @@ -684,12 +657,12 @@ openstack.volume.v3 = volume_show = openstackclient.volume.v2.volume:ShowVolume volume_unset = openstackclient.volume.v2.volume:UnsetVolume - volume_backup_create = openstackclient.volume.v2.backup:CreateVolumeBackup - volume_backup_delete = openstackclient.volume.v2.backup:DeleteVolumeBackup - volume_backup_list = openstackclient.volume.v2.backup:ListVolumeBackup - volume_backup_restore = openstackclient.volume.v2.backup:RestoreVolumeBackup - volume_backup_set = openstackclient.volume.v2.backup:SetVolumeBackup - volume_backup_show = openstackclient.volume.v2.backup:ShowVolumeBackup + volume_backup_create = openstackclient.volume.v2.volume_backup:CreateVolumeBackup + volume_backup_delete = openstackclient.volume.v2.volume_backup:DeleteVolumeBackup + volume_backup_list = openstackclient.volume.v2.volume_backup:ListVolumeBackup + volume_backup_restore = openstackclient.volume.v2.volume_backup:RestoreVolumeBackup + volume_backup_set = openstackclient.volume.v2.volume_backup:SetVolumeBackup + volume_backup_show = openstackclient.volume.v2.volume_backup:ShowVolumeBackup volume_host_set = openstackclient.volume.v2.volume_host:SetVolumeHost diff --git a/test-requirements.txt b/test-requirements.txt index 4cb66cf..4db30c7 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,8 +1,7 @@ # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0 - +hacking>=1.1.0,<1.2.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD flake8-import-order==0.13 # LGPLv3 @@ -39,4 +38,4 @@ python-searchlightclient>=1.0.0 #Apache-2.0 python-senlinclient>=1.1.0 # Apache-2.0 python-troveclient>=2.2.0 # Apache-2.0 python-zaqarclient>=1.0.0 # Apache-2.0 -python-zunclient>=1.3.0 # Apache-2.0 +python-zunclient>=3.4.0 # Apache-2.0 @@ -1,6 +1,6 @@ [tox] minversion = 2.3 -envlist = py36,py27,pep8 +envlist = py27,py37,pep8 skipdist = True [testenv] @@ -11,7 +11,7 @@ setenv = VIRTUAL_ENV={envdir} OS_STDERR_CAPTURE=1 OS_TEST_TIMEOUT=60 deps = - -c{env:UPPER_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/upper-constraints.txt} + -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt commands = stestr run {posargs} @@ -88,7 +88,7 @@ commands = [testenv:venv] basepython = python3 deps = - -c{env:UPPER_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/upper-constraints.txt} + -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = {posargs} @@ -112,7 +112,7 @@ commands = [testenv:docs] basepython = python3 deps = - -c{env:UPPER_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/upper-constraints.txt} + -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = @@ -122,7 +122,7 @@ commands = [testenv:releasenotes] basepython = python3 deps = - -c{env:UPPER_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/upper-constraints.txt} + -c{env:UPPER_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 @@ -132,9 +132,8 @@ show-source = True # H203: Use assertIs(Not)None to check for None enable-extensions = H203 exclude = .git,.tox,dist,doc,*lib/python*,*egg,build,tools -# If 'ignore' is not set there are default errors and warnings that are set -# Doc: http://flake8.readthedocs.org/en/latest/config.html#default -ignore = __ +# W504 is disabled since you must choose between this or W503 +ignore = W504 import-order-style = pep8 application_import_names = openstackclient |
