diff -Nru python-openstackclient-5.5.0/AUTHORS python-openstackclient-5.6.0/AUTHORS --- python-openstackclient-5.5.0/AUTHORS 2021-03-20 09:18:11.000000000 +0000 +++ python-openstackclient-5.6.0/AUTHORS 2021-09-01 19:16:30.000000000 +0000 @@ -55,6 +55,7 @@ Brian Haley Brian Haley Brian Rosmaita +Brian Rosmaita Brianna Poulos Cao Xuan Hoang Carl Baldwin @@ -77,6 +78,7 @@ Colleen Murphy Corey Bryant Cyril Roelandt +Cyril Roelandt Dag Stenstad Daisuke Fujita Daniel Bengtsson @@ -159,6 +161,7 @@ JP Parkin Jackie Yuan Jake Yip +James Denton James E. Blair James E. Blair Jamie Lennox @@ -273,6 +276,7 @@ Petr Blaho Pierre Hanselmann Pierre Prinetti +Pierre Riteau Qiu Yu Radoslaw Smigielski Radosław Piliszek @@ -322,6 +326,7 @@ Simon Merrick Sindhu Devale Sirushti Murugesan +Slawek Kaplonski SongmingYan Stephen Finucane Stephen Finucane @@ -351,6 +356,7 @@ Tuan Do Anh Tytus Kurek Ukesh Kumar Vasudevan +Valery Tschopp Vasyl Saienko Victor Silva Vijendra Soni @@ -370,6 +376,7 @@ Yejia Xu Yi Zhao Yongli He +YuehuiLei Yunpeng Li Zane Bitter ZhaoBo diff -Nru python-openstackclient-5.5.0/ChangeLog python-openstackclient-5.6.0/ChangeLog --- python-openstackclient-5.5.0/ChangeLog 2021-03-20 09:18:11.000000000 +0000 +++ python-openstackclient-5.6.0/ChangeLog 2021-09-01 19:16:29.000000000 +0000 @@ -1,11 +1,62 @@ CHANGES ======= +5.6.0 +----- + +* [community goal] Update contributor documentation +* Show "Forced Down" compute service status with --long +* Correct REST API response fields for /os-migrations API +* volume: Add missing 'volume list --offset' parameter +* Fix TestListMigrationV223 test class MIGRATION\_COLUMNS +* tests: Handle removal of block-storage v2 API +* volume: Add 'volume transfer request create --(no-)snapshots' option +* Moving IRC network reference to OFTC +* cinder: Remove redundant command +* Correct the tox option for skipping sdist generation +* compute: Better help text for 'openstack server set --state' +* compute: Note that '--password' is deployment-specific +* volume: Add more missing 'volume backup \*' options +* volume: Add 'volume group snapshot \*' commands +* L3 conntrack helper: Use singular name consistently +* Add support for Neutron's L3 conntrack helper resource +* volume: Add 'volume group type \*' commands +* volume: Add 'volume group \*' commands +* tests: Rename 'FakeType' -> 'FakeVolumeType' +* volume: Add missing 'volume backup \*' options +* volume: Add 'volume message \*' commands +* volume: Add 'volume attachment \*' commands +* volume: Allow more versions +* docs: Update neutronclient comparison doc +* docs: Update glanceclient comparison doc +* docs: Update novaclient comparison doc +* docs: Update cinderclient comparison doc +* compute: Fix typo +* setup.cfg: Replace dashes with underscores +* Make functional Neutron tests running fine on ML2/OVN environments +* Allow to send extra attributes in Neutron related commands +* compute: Update 'server resize --revert', '--confirm' help +* Add check for cinderclient.v2 support +* Set ML2/OVS backend explicitly in the devstack jobs +* openstack image create: honor protection/visibility flags +* Replace assertItemsEqual with assertCountEqual +* requirements: Drop os-testr +* hacking: Remove references to encoding +* Implements hide image +* Add Python3 xena unit tests +* Update master for stable/wallaby +* Hides prefix\_length column in subnet show output +* network: Make 'network qos rule create --type' option required +* volume: Re-add accidentally deleted test +* network: Add support for vnic-type vdpa + 5.5.0 ----- * Update the file paths mentioned in README.rst +* Add pre-commit * compute: Remove 'file://' prefix from '--block-device' +* Update volume create documentation * network: Add missing subnet unset --gateway * compute: Add support for loading BDMs from files * compute: Add functional tests for --block-device @@ -64,6 +115,7 @@ * Remove retired Searchlight support * Update lower-constraints * Fix lower-constraints job +* Add support for token caching * trivial: Cleanup docs for 'server rebuild' * Add documentation about login with federation * Switch compute aggregate functions to SDK @@ -79,6 +131,7 @@ * Resolve issues with 'server migration list' * Switch 'openstack keypair' ops to use SDK * functional: Remove test for 'quota set --force' +* Remove unnecessary test * Remove references to Python 2.7 * Add a few selectable fields to the "openstack server list" output * Add 'openstack server evacuate' command diff -Nru python-openstackclient-5.5.0/CONTRIBUTING.rst python-openstackclient-5.6.0/CONTRIBUTING.rst --- python-openstackclient-5.5.0/CONTRIBUTING.rst 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/CONTRIBUTING.rst 2021-09-01 19:16:00.000000000 +0000 @@ -1,16 +1,27 @@ -If you would like to contribute to the development of OpenStack, -you must follow the steps documented at: +The source repository for this project can be found at: - https://docs.openstack.org/infra/manual/developers.html + https://opendev.org/openstack/python-openstackclient -Once those steps have been completed, changes to OpenStack -should be submitted for review via the Gerrit tool, following -the workflow documented at: +Pull requests submitted through GitHub are not monitored. - https://docs.openstack.org/infra/manual/developers.html#development-workflow +To start contributing to OpenStack, follow the steps in the contribution guide +to set up and use Gerrit: -Pull requests submitted through GitHub will be ignored. + https://docs.openstack.org/contributors/code-and-documentation/quick-start.html -Bugs should be filed on Storyboard, not GitHub or Launchpad: +Bugs should be filed on StoryBoard: https://storyboard.openstack.org/#!/project/openstack/python-openstackclient + +Developers should also join the discussion on the mailing list, at: + + http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-discuss + +or join the IRC channel on + + #openstack-sdks on OFTC (irc.oftc.net) + +For more specific information about contributing to this repository, see the +openstacksdk contributor guide: + + https://docs.openstack.org/openstacksdk/latest/contributor/index.html diff -Nru python-openstackclient-5.5.0/debian/changelog python-openstackclient-5.6.0/debian/changelog --- python-openstackclient-5.5.0/debian/changelog 2021-04-12 19:39:36.000000000 +0000 +++ python-openstackclient-5.6.0/debian/changelog 2021-09-07 18:06:22.000000000 +0000 @@ -1,3 +1,10 @@ +python-openstackclient (5.6.0-0ubuntu1) impish; urgency=medium + + * New upstream release for OpenStack Xena. + * d/control: Align (Build-)Depends with upstream. + + -- Corey Bryant Tue, 07 Sep 2021 14:06:22 -0400 + python-openstackclient (5.5.0-0ubuntu1) hirsute; urgency=medium * New upstream release for OpenStack Wallaby. diff -Nru python-openstackclient-5.5.0/debian/control python-openstackclient-5.6.0/debian/control --- python-openstackclient-5.5.0/debian/control 2021-04-12 19:39:36.000000000 +0000 +++ python-openstackclient-5.6.0/debian/control 2021-09-07 18:06:22.000000000 +0000 @@ -40,7 +40,7 @@ python3-novaclient (>= 2:17.0.0), python3-octaviaclient (>= 1.11.0), python3-openstackdocstheme (>= 2.2.1), - python3-openstacksdk (>= 0.53.0), + python3-openstacksdk (>= 0.56.0), python3-os-client-config (>= 1.28.0), python3-osc-lib (>= 2.3.0), python3-osc-placement (>= 1.7.0), @@ -101,7 +101,7 @@ python3-iso8601 (>= 0.1.11), python3-keystoneclient (>= 1:3.22.0), python3-novaclient (>= 2:17.0.0), - python3-openstacksdk (>= 0.53.0), + python3-openstacksdk (>= 0.56.0), python3-osc-lib (>= 2.3.0), python3-oslo.i18n (>= 3.15.3), python3-oslo.serialization (>= 1.2.0), diff -Nru python-openstackclient-5.5.0/doc/source/cli/command-objects/network-l3-conntrack-helper.rst python-openstackclient-5.6.0/doc/source/cli/command-objects/network-l3-conntrack-helper.rst --- python-openstackclient-5.5.0/doc/source/cli/command-objects/network-l3-conntrack-helper.rst 1970-01-01 00:00:00.000000000 +0000 +++ python-openstackclient-5.6.0/doc/source/cli/command-objects/network-l3-conntrack-helper.rst 2021-09-01 19:16:00.000000000 +0000 @@ -0,0 +1,8 @@ +=========================== +network l3 conntrack helper +=========================== + +Network v2 + +.. autoprogram-cliff:: openstack.network.v2 + :command: network l3 conntrack helper * diff -Nru python-openstackclient-5.5.0/doc/source/cli/command-objects/volume-attachment.rst python-openstackclient-5.6.0/doc/source/cli/command-objects/volume-attachment.rst --- python-openstackclient-5.5.0/doc/source/cli/command-objects/volume-attachment.rst 1970-01-01 00:00:00.000000000 +0000 +++ python-openstackclient-5.6.0/doc/source/cli/command-objects/volume-attachment.rst 2021-09-01 19:16:00.000000000 +0000 @@ -0,0 +1,8 @@ +================= +volume attachment +================= + +Block Storage v3 + +.. autoprogram-cliff:: openstack.volume.v3 + :command: volume attachment * diff -Nru python-openstackclient-5.5.0/doc/source/cli/command-objects/volume-group.rst python-openstackclient-5.6.0/doc/source/cli/command-objects/volume-group.rst --- python-openstackclient-5.5.0/doc/source/cli/command-objects/volume-group.rst 1970-01-01 00:00:00.000000000 +0000 +++ python-openstackclient-5.6.0/doc/source/cli/command-objects/volume-group.rst 2021-09-01 19:16:00.000000000 +0000 @@ -0,0 +1,23 @@ +============ +volume group +============ + +Block Storage v3 + +.. autoprogram-cliff:: openstack.volume.v3 + :command: volume group create + +.. autoprogram-cliff:: openstack.volume.v3 + :command: volume group delete + +.. autoprogram-cliff:: openstack.volume.v3 + :command: volume group list + +.. autoprogram-cliff:: openstack.volume.v3 + :command: volume group failover + +.. autoprogram-cliff:: openstack.volume.v3 + :command: volume group set + +.. autoprogram-cliff:: openstack.volume.v3 + :command: volume group show diff -Nru python-openstackclient-5.5.0/doc/source/cli/command-objects/volume-group-snapshot.rst python-openstackclient-5.6.0/doc/source/cli/command-objects/volume-group-snapshot.rst --- python-openstackclient-5.5.0/doc/source/cli/command-objects/volume-group-snapshot.rst 1970-01-01 00:00:00.000000000 +0000 +++ python-openstackclient-5.6.0/doc/source/cli/command-objects/volume-group-snapshot.rst 2021-09-01 19:16:00.000000000 +0000 @@ -0,0 +1,8 @@ +===================== +volume group snapshot +===================== + +Block Storage v3 + +.. autoprogram-cliff:: openstack.volume.v3 + :command: volume group snapshot * diff -Nru python-openstackclient-5.5.0/doc/source/cli/command-objects/volume-group-type.rst python-openstackclient-5.6.0/doc/source/cli/command-objects/volume-group-type.rst --- python-openstackclient-5.5.0/doc/source/cli/command-objects/volume-group-type.rst 1970-01-01 00:00:00.000000000 +0000 +++ python-openstackclient-5.6.0/doc/source/cli/command-objects/volume-group-type.rst 2021-09-01 19:16:00.000000000 +0000 @@ -0,0 +1,8 @@ +================= +volume group type +================= + +Block Storage v3 + +.. autoprogram-cliff:: openstack.volume.v3 + :command: volume group type * diff -Nru python-openstackclient-5.5.0/doc/source/cli/command-objects/volume-message.rst python-openstackclient-5.6.0/doc/source/cli/command-objects/volume-message.rst --- python-openstackclient-5.5.0/doc/source/cli/command-objects/volume-message.rst 1970-01-01 00:00:00.000000000 +0000 +++ python-openstackclient-5.6.0/doc/source/cli/command-objects/volume-message.rst 2021-09-01 19:16:00.000000000 +0000 @@ -0,0 +1,8 @@ +============== +volume message +============== + +Block Storage v3 + +.. autoprogram-cliff:: openstack.volume.v3 + :command: volume message * diff -Nru python-openstackclient-5.5.0/doc/source/cli/command-objects/volume.rst python-openstackclient-5.6.0/doc/source/cli/command-objects/volume.rst --- python-openstackclient-5.5.0/doc/source/cli/command-objects/volume.rst 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/doc/source/cli/command-objects/volume.rst 2021-09-01 19:16:00.000000000 +0000 @@ -17,13 +17,10 @@ [--type ] [--image | --snapshot | --source ] [--description ] - [--user ] - [--project ] [--availability-zone ] [--consistency-group ] [--property [...] ] [--hint [...] ] - [--multi-attach] [--bootable | --non-bootable] [--read-only | --read-write] @@ -58,14 +55,6 @@ Volume description -.. option:: --user - - Specify an alternate user (name or ID) - -.. option:: --project - - Specify an alternate project (name or ID) - .. option:: --availability-zone Create volume in ```` @@ -83,10 +72,6 @@ Arbitrary scheduler hint key-value pairs to help boot an instance (repeat option to set multiple hints) -.. option:: --multi-attach - - Allow volume to be attached more than once (default to False) - .. option:: --bootable Mark volume as bootable @@ -108,10 +93,6 @@ Volume name -The :option:`--project` and :option:`--user` options are typically only -useful for admin users, but may be allowed for other users depending on -the policy of the cloud and the roles granted to the user. - volume delete ------------- diff -Nru python-openstackclient-5.5.0/doc/source/cli/commands.rst python-openstackclient-5.6.0/doc/source/cli/commands.rst --- python-openstackclient-5.5.0/doc/source/cli/commands.rst 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/doc/source/cli/commands.rst 2021-09-01 19:16:00.000000000 +0000 @@ -153,12 +153,17 @@ * ``user``: (**Identity**) individual cloud resources users * ``user role``: (**Identity**) roles assigned to a user * ``volume``: (**Volume**) block volumes +* ``volume attachment``: (**Volume**) an attachment of a volumes to a server * ``volume backup``: (**Volume**) backup for volumes -* ``volume backend capability``: (**volume**) volume backend storage capabilities -* ``volume backend pool``: (**volume**) volume backend storage pools +* ``volume backend capability``: (**Volume**) volume backend storage capabilities +* ``volume backend pool``: (**Volume**) volume backend storage pools * ``volume backup record``: (**Volume**) volume record that can be imported or exported -* ``volume backend``: (**volume**) volume backend storage +* ``volume backend``: (**Volume**) volume backend storage +* ``volume group``: (**Volume**) group of volumes +* ``volume group snapshot``: (**Volume**) a point-in-time copy of a volume group +* ``volume group type``: (**Volume**) deployment-specific types of volumes groups available * ``volume host``: (**Volume**) the physical computer for volumes +* ``volume message``: (**Volume**) volume API internal messages detailing volume failure messages * ``volume qos``: (**Volume**) quality-of-service (QoS) specification for volumes * ``volume snapshot``: (**Volume**) a point-in-time copy of a volume * ``volume type``: (**Volume**) deployment-specific types of volumes available diff -Nru python-openstackclient-5.5.0/doc/source/cli/data/cinder.csv python-openstackclient-5.6.0/doc/source/cli/data/cinder.csv --- python-openstackclient-5.5.0/doc/source/cli/data/cinder.csv 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/doc/source/cli/data/cinder.csv 2021-09-01 19:16:00.000000000 +0000 @@ -1,104 +1,146 @@ -absolute-limits,limits show --absolute,Lists absolute limits for a user. -availability-zone-list,availability zone list --volume,Lists all availability zones. -backup-create,volume backup create,Creates a volume backup. -backup-delete,volume backup delete,Removes a backup. -backup-export,volume backup record export,Export backup metadata record. -backup-import,volume backup record import,Import backup metadata record. -backup-list,volume backup list,Lists all backups. -backup-reset-state,volume backup set --state,Explicitly updates the backup state. -backup-restore,volume backup restore,Restores a backup. -backup-show,volume backup show,Show backup details. -cgsnapshot-create,consistency group snapshot create,Creates a cgsnapshot. -cgsnapshot-delete,consistency group snapshot delete,Removes one or more cgsnapshots. -cgsnapshot-list,consistency group snapshot list,Lists all cgsnapshots. -cgsnapshot-show,consistency group snapshot show,Shows cgsnapshot details. -consisgroup-create,consistency group create,Creates a consistency group. -consisgroup-create-from-src,consistency group create --consistency-group-snapshot,Creates a consistency group from a cgsnapshot or a source CG -consisgroup-delete,consistency group delete,Removes one or more consistency groups. -consisgroup-list,consistency group list,Lists all consistencygroups. -consisgroup-show,consistency group show,Shows details of a consistency group. -consisgroup-update,consistency group set,Updates a consistencygroup. -create,volume create,Creates a volume. -credentials,WONTFIX,Shows user credentials returned from auth. -delete,volume delete,Removes one or more volumes. -encryption-type-create,volume type create --encryption-provider --enc..,Creates encryption type for a volume type. Admin only. -encryption-type-delete,volume type delete,Deletes encryption type for a volume type. Admin only. -encryption-type-list,volume type list --encryption-type,Shows encryption type details for volume types. Admin only. -encryption-type-show,volume type list --encryption-show,Shows encryption type details for volume type. Admin only. -encryption-type-update,volume type set --encryption-provider --enc..,Update encryption type information for a volume type (Admin Only). -endpoints,catalog list,Discovers endpoints registered by authentication service. -extend,volume set --size,Attempts to extend size of an existing volume. -extra-specs-list,volume type list --long,Lists current volume types and extra specs. -failover-host,volume host failover,Failover a replicating cinder-volume host. -force-delete,volume delete --force,"Attempts force-delete of volume, regardless of state." -freeze-host,volume host set --disable,Freeze and disable the specified cinder-volume host. -get-capabilities,volume backend capability show,Show capabilities of a volume backend. Admin only. -get-pools,volume backend pool list,Show pool information for backends. Admin only. -image-metadata,volume set --image-property,Sets or deletes volume image metadata. -image-metadata-show,volume show,Shows volume image metadata. -list,volume list,Lists all volumes. -manage,volume create --remote-source k=v,Manage an existing volume. -metadata,volume set --property k=v / volume unset --property k,Sets or deletes volume metadata. -metadata-show,volume show,Shows volume metadata. -metadata-update-all,volume set --property k=v,Updates volume metadata. -migrate,volume migrate --host --force-copy --lock-volume ,Migrates volume to a new host. -qos-associate,volume qos associate,Associates qos specs with specified volume type. -qos-create,volume qos create,Creates a qos specs. -qos-delete,volume qos delete,Deletes a specified qos specs. -qos-disassociate,volume qos disassociate,Disassociates qos specs from specified volume type. -qos-disassociate-all,volume qos disassociate --all,Disassociates qos specs from all associations. -qos-get-association,volume qos show,Gets all associations for specified qos specs. -qos-key,volume qos set --property k=v / volume qos unset --property k,Sets or unsets specifications for a qos spec -qos-list,volume qos list,Lists qos specs. -qos-show,volume qos show,Shows a specified qos specs. -quota-class-show,quota show --class,Lists quotas for a quota class. -quota-class-update,quota set --class,Updates quotas for a quota class. -quota-defaults,quota show --default,Lists default quotas for a tenant. -quota-delete,,Delete the quotas for a tenant. -quota-show,quota show,Lists quotas for a tenant. -quota-update,quota set,Updates quotas for a tenant. -quota-usage,,Lists quota usage for a tenant. -rate-limits,limits show --rate,Lists rate limits for a user. -readonly-mode-update,volume set --read-only-mode | --read-write-mode,Updates volume read-only access-mode flag. -rename,volume set --name,Renames a volume. -replication-promote,WONTFIX,Promote a secondary volume to primary for a relationship -replication-reenable,WONTFIX,Sync the secondary volume with primary for a relationship -reset-state,volume set --state,Explicitly updates the volume state. -retype,volume type set --type,Changes the volume type for a volume. -service-disable,volume service set --disable,Disables the service. -service-enable,volume service set --enable,Enables the service. -service-list,volume service list,Lists all services. Filter by host and service binary. -set-bootable,volume set --bootable / --not-bootable,Update bootable status of a volume. -show,volume show,Shows volume details. -snapshot-create,snapshot create,Creates a snapshot. -snapshot-delete,snapshot delete,Remove one or more snapshots. -snapshot-list,snapshot list,Lists all snapshots. -snapshot-manage,volume snapshot create --remote-source ,Manage an existing snapshot. -snapshot-metadata,snapshot set --property k=v / snapshot unset --property k,Sets or deletes snapshot metadata. -snapshot-metadata-show,snapshot show,Shows snapshot metadata. -snapshot-metadata-update-all,snapshot set --property k=v,Updates snapshot metadata. -snapshot-rename,snapshot set --name,Renames a snapshot. -snapshot-reset-state,snapshot set --state,Explicitly updates the snapshot state. -snapshot-show,snapshot show,Shows snapshot details. -snapshot-unmanage,volume snapshot delete --remote,Stop managing a snapshot. -thaw-host,volume host set --enable,Thaw and enable the specified cinder-volume host. -transfer-accept,volume transfer accept,Accepts a volume transfer. -transfer-create,volume transfer create,Creates a volume transfer. -transfer-delete,volume transfer delete,Undoes a transfer. -transfer-list,volume transfer list,Lists all transfers. -transfer-show,volume transfer show,Show transfer details. -type-access-add,volume type set --project,Adds volume type access for the given project. -type-access-list,volume type show,Print access information about the given volume type. -type-access-remove,volume type unset --project,Removes volume type access for the given project. -type-create,volume type create,Creates a volume type. -type-default,volume type list --default,List the default volume type. -type-delete,volume type delete,Deletes a specified volume type. -type-key,volume type set --property k=v / volume type unset --property k,Sets or unsets extra_spec for a volume type. -type-list,volume type list,Lists available 'volume types'. -type-show,volume type show,Show volume type details. -type-update,volume type set,"Updates volume type name, description, and/or is_public." -unmanage,volume delete --remote,Stop managing a volume. -upload-to-image,image create --volume,Uploads volume to Image Service as an image. -bash-completion,complete,Prints arguments for bash_completion. -help,help,Shows help about this program or one of its subcommands. -list-extensions,extension list --volume,Lists all available os-api extensions. +absolute-limits,limits show --absolute,Lists absolute limits for a user. +api-version,WONTFIX,Display the server API version information. +availability-zone-list,availability zone list --volume,Lists all availability zones. +attachment-complete,volume attachment complete,Complete an attachment for a cinder volume. (Supported by API versions 3.44 - 3.latest) +attachment-create,volume attachment create,Create an attachment for a cinder volume. (Supported by API versions 3.27 - 3.latest) +attachment-delete,volume attachment delete,Delete an attachment for a cinder volume. (Supported by API versions 3.27 - 3.latest) +attachment-list,volume attachment list,Lists all attachments. (Supported by API versions 3.27 - 3.latest) +attachment-show,volume attachment show,Show detailed information for attachment. (Supported by API versions 3.27 - 3.latest) +attachment-update,volume attachment update,Update an attachment for a cinder volume. (Supported by API versions 3.27 - 3.latest) +backup-create,volume backup create,Creates a volume backup. +backup-delete,volume backup delete,Removes a backup. +backup-export,volume backup record export,Export backup metadata record. +backup-import,volume backup record import,Import backup metadata record. +backup-list,volume backup list,Lists all backups. +backup-reset-state,volume backup set --state,Explicitly updates the backup state. +backup-restore,volume backup restore,Restores a backup. +backup-show,volume backup show,Show backup details. +backup-update,volume backup set,Updates a backup. (Supported by API versions 3.9 - 3.latest) +cgsnapshot-create,consistency group snapshot create,Creates a cgsnapshot. +cgsnapshot-delete,consistency group snapshot delete,Removes one or more cgsnapshots. +cgsnapshot-list,consistency group snapshot list,Lists all cgsnapshots. +cgsnapshot-show,consistency group snapshot show,Shows cgsnapshot details. +cluster-disable,,Disables clustered services. (Supported by API versions 3.7 - 3.latest) +cluster-enable,,Enables clustered services. (Supported by API versions 3.7 - 3.latest) +cluster-list,,Lists clustered services with optional filtering. (Supported by API versions 3.7 - 3.latest) +cluster-show,,Show detailed information on a clustered service. (Supported by API versions 3.7 - 3.latest) +consisgroup-create,consistency group create,Creates a consistency group. +consisgroup-create-from-src,consistency group create --consistency-group-snapshot,Creates a consistency group from a cgsnapshot or a source CG +consisgroup-delete,consistency group delete,Removes one or more consistency groups. +consisgroup-list,consistency group list,Lists all consistencygroups. +consisgroup-show,consistency group show,Shows details of a consistency group. +consisgroup-update,consistency group set,Updates a consistencygroup. +create,volume create,Creates a volume. +delete,volume delete,Removes one or more volumes. +encryption-type-create,volume type create --encryption-provider --enc..,Creates encryption type for a volume type. Admin only. +encryption-type-delete,volume type delete,Deletes encryption type for a volume type. Admin only. +encryption-type-list,volume type list --encryption-type,Shows encryption type details for volume types. Admin only. +encryption-type-show,volume type list --encryption-show,Shows encryption type details for volume type. Admin only. +encryption-type-update,volume type set --encryption-provider --enc..,Update encryption type information for a volume type (Admin Only). +extend,volume set --size,Attempts to extend size of an existing volume. +extra-specs-list,volume type list --long,Lists current volume types and extra specs. +failover-host,volume host failover,Failover a replicating cinder-volume host. +force-delete,volume delete --force,"Attempts force-delete of volume regardless of state." +freeze-host,volume host set --disable,Freeze and disable the specified cinder-volume host. +get-capabilities,volume backend capability show,Show capabilities of a volume backend. Admin only. +get-pools,volume backend pool list,Show pool information for backends. Admin only. +group-create,volume group create,Creates a group. (Supported by API versions 3.13 - 3.latest) +group-create-from-src,,Creates a group from a group snapshot or a source group. (Supported by API versions 3.14 - 3.latest) +group-delete,volume group delete,Removes one or more groups. (Supported by API versions 3.13 - 3.latest) +group-disable-replication,volume group set --disable-replication,Disables replication for group. (Supported by API versions 3.38 - 3.latest) +group-enable-replication,volume group set --enable-replication,Enables replication for group. (Supported by API versions 3.38 - 3.latest) +group-failover-replication,volume group failover,Fails over replication for group. (Supported by API versions 3.38 - 3.latest) +group-list,volume group list,Lists all groups. (Supported by API versions 3.13 - 3.latest) +group-list-replication-targets,volume group list --replication-targets,Lists replication targets for group. (Supported by API versions 3.38 - 3.latest) +group-show,volume group show,Shows details of a group. (Supported by API versions 3.13 - 3.latest) +group-snapshot-create,volume group snapshot create,Creates a group snapshot. (Supported by API versions 3.14 - 3.latest) +group-snapshot-delete,volume group snapshot delete,Removes one or more group snapshots. (Supported by API versions 3.14 - 3.latest) +group-snapshot-list,volume group snapshot list,Lists all group snapshots. (Supported by API versions 3.14 - 3.latest) +group-snapshot-show,volume group snapshot show,Shows group snapshot details. (Supported by API versions 3.14 - 3.latest) +group-specs-list,volume group type list,Lists current group types and specs. (Supported by API versions 3.11 - 3.latest) +group-type-create,volume group type create,Creates a group type. (Supported by API versions 3.11 - 3.latest) +group-type-default,volume group type list --default,List the default group type. (Supported by API versions 3.11 - 3.latest) +group-type-delete,volume group type delete,Deletes group type or types. (Supported by API versions 3.11 - 3.latest) +group-type-key,volume group type set,Sets or unsets group_spec for a group type. (Supported by API versions 3.11 - 3.latest) +group-type-list,volume group type set,Lists available 'group types'. (Admin only will see private types) (Supported by API versions 3.11 - 3.latest) +group-type-show,volume group type show,Show group type details. (Supported by API versions 3.11 - 3.latest) +group-type-update,volume group type set,Updates group type name description and/or is_public. (Supported by API versions 3.11 - 3.latest) +group-update,volume group set,Updates a group. (Supported by API versions 3.13 - 3.latest) +image-metadata,volume set --image-property,Sets or deletes volume image metadata. +image-metadata-show,volume show,Shows volume image metadata. +list,volume list,Lists all volumes. +list-filters,,List enabled filters. (Supported by API versions 3.33 - 3.latest) +manage,volume create --remote-source k=v,Manage an existing volume. +manageable-list,,Lists all manageable volumes. (Supported by API versions 3.8 - 3.latest) +message-delete,volume message delete,Removes one or more messages. (Supported by API versions 3.3 - 3.latest) +message-list,volume message list,Lists all messages. (Supported by API versions 3.3 - 3.latest) +message-show,volume message show,Shows message details. (Supported by API versions 3.3 - 3.latest) +metadata,volume set --property k=v / volume unset --property k,Sets or deletes volume metadata. +metadata-show,volume show,Shows volume metadata. +metadata-update-all,volume set --property k=v,Updates volume metadata. +migrate,volume migrate --host --force-copy --lock-volume ,Migrates volume to a new host. +qos-associate,volume qos associate,Associates qos specs with specified volume type. +qos-create,volume qos create,Creates a qos specs. +qos-delete,volume qos delete,Deletes a specified qos specs. +qos-disassociate,volume qos disassociate,Disassociates qos specs from specified volume type. +qos-disassociate-all,volume qos disassociate --all,Disassociates qos specs from all associations. +qos-get-association,volume qos show,Gets all associations for specified qos specs. +qos-key,volume qos set --property k=v / volume qos unset --property k,Sets or unsets specifications for a qos spec +qos-list,volume qos list,Lists qos specs. +qos-show,volume qos show,Shows a specified qos specs. +quota-class-show,quota show --class,Lists quotas for a quota class. +quota-class-update,quota set --class,Updates quotas for a quota class. +quota-defaults,quota show --default,Lists default quotas for a tenant. +quota-delete,,Delete the quotas for a tenant. +quota-show,quota show,Lists quotas for a tenant. +quota-update,quota set,Updates quotas for a tenant. +quota-usage,,Lists quota usage for a tenant. +rate-limits,limits show --rate,Lists rate limits for a user. +readonly-mode-update,volume set --read-only-mode | --read-write-mode,Updates volume read-only access-mode flag. +rename,volume set --name,Renames a volume. +reset-state,volume set --state,Explicitly updates the volume state. +retype,volume type set --type,Changes the volume type for a volume. +revert-to-snapshot,,Revert a volume to the specified snapshot. (Supported by API versions 3.40 - 3.latest) +service-disable,volume service set --disable,Disables the service. +service-enable,volume service set --enable,Enables the service. +service-get-log,,(Supported by API versions 3.32 - 3.latest) +service-list,volume service list,Lists all services. Filter by host and service binary. +service-set-log,,(Supported by API versions 3.32 - 3.latest) +set-bootable,volume set --bootable / --not-bootable,Update bootable status of a volume. +show,volume show,Shows volume details. +snapshot-create,snapshot create,Creates a snapshot. +snapshot-delete,snapshot delete,Remove one or more snapshots. +snapshot-list,snapshot list,Lists all snapshots. +snapshot-manage,volume snapshot create --remote-source ,Manage an existing snapshot. +snapshot-manageable-list,,Lists all manageable snapshots. (Supported by API versions 3.8 - 3.latest) +snapshot-metadata,snapshot set --property k=v / snapshot unset --property k,Sets or deletes snapshot metadata. +snapshot-metadata-show,snapshot show,Shows snapshot metadata. +snapshot-metadata-update-all,snapshot set --property k=v,Updates snapshot metadata. +snapshot-rename,snapshot set --name,Renames a snapshot. +snapshot-reset-state,snapshot set --state,Explicitly updates the snapshot state. +snapshot-show,snapshot show,Shows snapshot details. +snapshot-unmanage,volume snapshot delete --remote,Stop managing a snapshot. +summary,,Get volumes summary. (Supported by API versions 3.12 - 3.latest) +thaw-host,volume host set --enable,Thaw and enable the specified cinder-volume host. +transfer-accept,volume transfer accept,Accepts a volume transfer. +transfer-create,volume transfer create,Creates a volume transfer. +transfer-delete,volume transfer delete,Undoes a transfer. +transfer-list,volume transfer list,Lists all transfers. +transfer-show,volume transfer show,Show transfer details. +type-access-add,volume type set --project,Adds volume type access for the given project. +type-access-list,volume type show,Print access information about the given volume type. +type-access-remove,volume type unset --project,Removes volume type access for the given project. +type-create,volume type create,Creates a volume type. +type-default,volume type list --default,List the default volume type. +type-delete,volume type delete,Deletes a specified volume type. +type-key,volume type set --property k=v / volume type unset --property k,Sets or unsets extra_spec for a volume type. +type-list,volume type list,Lists available 'volume types'. +type-show,volume type show,Show volume type details. +type-update,volume type set,"Updates volume type name description and/or is_public." +unmanage,volume delete --remote,Stop managing a volume. +upload-to-image,image create --volume,Uploads volume to Image Service as an image. +version-list,,List all API versions. (Supported by API versions 3.0 - 3.latest) +work-cleanup,,Request cleanup of services with optional filtering. (Supported by API versions 3.24 - 3.latest) +bash-completion,complete,Prints arguments for bash_completion. +help,help,Shows help about this program or one of its subcommands. +list-extensions,extension list --volume,Lists all available os-api extensions. diff -Nru python-openstackclient-5.5.0/doc/source/cli/data/glance.csv python-openstackclient-5.6.0/doc/source/cli/data/glance.csv --- python-openstackclient-5.5.0/doc/source/cli/data/glance.csv 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/doc/source/cli/data/glance.csv 2021-09-01 19:16:00.000000000 +0000 @@ -1,22 +1,58 @@ explain,WONTFIX,Describe a specific model. image-create,image create,Create a new image. +image-create-via-import,,EXPERIMENTAL: Create a new image via image import. image-deactivate,image set --deactivate,Deactivate specified image. image-delete,image delete,Delete specified image. image-download,image save,Download a specific image. +image-import,,Initiate the image import taskflow. 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-stage,,Upload data for a specific image to staging. image-tag-delete,image unset --tag ,Delete the tag associated with the given image. image-tag-update,image set --tag ,Update an image with the given tag. image-update,image set,Update an existing image. image-upload,,Upload data for a specific image. +import-info,,Print import methods available from Glance. 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. +md-namespace-create,,Create a new metadata definitions namespace. +md-namespace-delete,,Delete specified metadata definitions namespace with its contents. +md-namespace-import,,Import a metadata definitions namespace from file or standard input. +md-namespace-list,,List metadata definitions namespaces. +md-namespace-objects-delete,,Delete all metadata definitions objects inside a specific namespace. +md-namespace-properties-delete,,Delete all metadata definitions property inside a specific namespace. +md-namespace-resource-type-list,,List resource types associated to specific namespace. +md-namespace-show,,Describe a specific metadata definitions namespace. +md-namespace-tags-delete,,Delete all metadata definitions tags inside a specific namespace. +md-namespace-update,,Update an existing metadata definitions namespace. +md-object-create,,Create a new metadata definitions object inside a namespace. +md-object-delete,,Delete a specific metadata definitions object inside a namespace. +md-object-list,,List metadata definitions objects inside a specific namespace. +md-object-property-show,,Describe a specific metadata definitions property inside an object. +md-object-show,,Describe a specific metadata definitions object inside a namespace. +md-object-update,,Update metadata definitions object inside a namespace. +md-property-create,,Create a new metadata definitions property inside a namespace. +md-property-delete,,Delete a specific metadata definitions property inside a namespace. +md-property-list,,List metadata definitions properties inside a specific namespace. +md-property-show,,Describe a specific metadata definitions property inside a namespace. +md-property-update,,Update metadata definitions property inside a namespace. +md-resource-type-associate,,Associate resource type with a metadata definitions namespace. +md-resource-type-deassociate,,Deassociate resource type with a metadata definitions namespace. +md-resource-type-list,,List available resource type names. +md-tag-create,,Add a new metadata definitions tag inside a namespace. +md-tag-create-multiple,,Create new metadata definitions tags inside a namespace. +md-tag-delete,,Delete a specific metadata definitions tag inside a namespace. +md-tag-list,,List metadata definitions tags inside a specific namespace. +md-tag-show,,Describe a specific metadata definitions tag inside a namespace. +md-tag-update,,Rename a metadata definitions tag inside a namespace. 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. +stores-delete,,Delete image from specific store. +stores-info,,Print available backends from Glance. task-create,,Create a new task. task-list,,List tasks you can access. task-show,,Describe a specific task. diff -Nru python-openstackclient-5.5.0/doc/source/cli/data/neutron.csv python-openstackclient-5.6.0/doc/source/cli/data/neutron.csv --- python-openstackclient-5.5.0/doc/source/cli/data/neutron.csv 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/doc/source/cli/data/neutron.csv 2021-09-01 19:16:00.000000000 +0000 @@ -35,6 +35,23 @@ dhcp-agent-network-remove,network agent remove network,Remove a network from a DHCP agent. ext-list,extension list,List all extensions. ext-show,extension show,Show information of a given resource. +firewall-create,,Create a firewall. +firewall-delete,,Delete a given firewall. +firewall-list,,List firewalls that belong to a given tenant. +firewall-policy-create,,Create a firewall policy. +firewall-policy-delete,,Delete a given firewall policy. +firewall-policy-insert-rule,,Insert a rule into a given firewall policy. +firewall-policy-list,,List firewall policies that belong to a given tenant. +firewall-policy-remove-rule,,Remove a rule from a given firewall policy. +firewall-policy-show,,Show information of a given firewall policy. +firewall-policy-update,,Update a given firewall policy. +firewall-rule-create,,Create a firewall rule. +firewall-rule-delete,,Delete a given firewall rule. +firewall-rule-list,,List firewall rules that belong to a given tenant. +firewall-rule-show,,Show information of a given firewall rule. +firewall-rule-update,,Update a given firewall rule. +firewall-show,,Show information of a given firewall. +firewall-update,,Update a given firewall. flavor-associate,network flavor add profile,Add a Neutron service flavor with a flavor profile. flavor-create,network flavor create,Create a Neutron service flavor. flavor-delete,network flavor delete,Delete a given Neutron service flavor. @@ -214,14 +231,6 @@ tag-add,network set --tag,Add a tag into the resource. tag-remove,network unset --tag,Remove a tag on the resource. tag-replace,,Replace all tags on the resource. -tap-flow-create,tapflow create,Create a tap flow -tap-flow-delete,tapflow delete,Delete a tap flow -tap-flow-list,tapflow list,List all tap flows -tap-flow-show,tapflow show,Show details of the tap flow -tap-service-create,tapservice create,Create a tap service -tap-service-delete,tapservice delete,Delete a tap service -tap-service-list,tapservice list,List all tap services -tap-service-show,tapservice show,Show details of the tap service vpn-endpoint-group-create,,Create a VPN endpoint group. vpn-endpoint-group-delete,,Delete a given VPN endpoint group. vpn-endpoint-group-list,,List VPN endpoint groups that belong to a given tenant. @@ -242,3 +251,12 @@ vpn-service-list,,List VPN service configurations that belong to a given tenant. vpn-service-show,,Show information of a given VPN service. vpn-service-update,,Update a given VPN service. + +tap-flow-create,tapflow create,Create a tap flow +tap-flow-delete,tapflow delete,Delete a tap flow +tap-flow-list,tapflow list,List all tap flows +tap-flow-show,tapflow show,Show details of the tap flow +tap-service-create,tapservice create,Create a tap service +tap-service-delete,tapservice delete,Delete a tap service +tap-service-list,tapservice list,List all tap services +tap-service-show,tapservice show,Show details of the tap service diff -Nru python-openstackclient-5.5.0/doc/source/cli/data/nova.csv python-openstackclient-5.6.0/doc/source/cli/data/nova.csv --- python-openstackclient-5.5.0/doc/source/cli/data/nova.csv 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/doc/source/cli/data/nova.csv 2021-09-01 19:16:00.000000000 +0000 @@ -1,10 +1,10 @@ -add-fixed-ip,server add fixed ip,Add new IP address on a network to server. add-secgroup,server add security group,Add a Security Group to a server. agent-create,compute agent create,Create new agent build. agent-delete,compute agent delete,Delete existing agent build. agent-list,compute agent list,List all builds. agent-modify,compute agent set,Modify existing agent build. aggregate-add-host,aggregate add host,Add the host to the specified aggregate. +aggregate-cache-images,,Request images be cached. (Supported by API versions '2.81' - '2.latest') [hint: use '-- os-compute-api-version' flag to show help message for proper version] aggregate-create,aggregate create,Create a new aggregate with the specified details. aggregate-delete,aggregate delete,Delete the aggregate. aggregate-list,aggregate list,Print a list of all aggregates. @@ -15,12 +15,7 @@ availability-zone-list,availability zone list,List all the availability zones. backup,server backup create,Backup a server by creating a 'backup' type snapshot. boot,server create,Boot a new server. -cell-capacities,,Get cell capacities for all cells or a given cell. -cell-show,,Show details of a given cell. -clear-password,server set --root-password,Clear the admin password for a server from the metadata server. -cloudpipe-configure,WONTFIX,Update the VPN IP/port of a cloudpipe instance. -cloudpipe-create,WONTFIX,Create a cloudpipe instance for the given project. -cloudpipe-list,WONTFIX,Print a list of all cloudpipe instances. +clear-password,server set --root-password,Clear the admin password for a server from the metadata server. This action does not actually change the instance server password. console-log,console log show,Get console log output of a server. delete,server delete,Immediately shut down and delete specified server(s). diagnostics,openstack server show --diagnostics,Retrieve server diagnostics. @@ -33,24 +28,19 @@ flavor-key,flavor set / unset,Set or unset extra_spec for a flavor. flavor-list,flavor list,Print a list of available 'flavors' flavor-show,flavor show,Show details about the given flavor. -floating-ip-associate,server add floating ip,Associate a floating IP address to a server. -floating-ip-disassociate,server remove floating ip,Disassociate a floating IP address from a server. +flavor-update,,Update the description of an existing flavor. (Supported by API versions '2.55' - '2.latest') [hint: use '--os-compute-api- version' flag to show help message for proper version] force-delete,server delete,Force delete a server. -get-mks-console,console url show --mks,Get an MKS console to a server. -get-password,WONTFIX,Get the admin password for a server. +get-mks-console,console url show --mks,Get an MKS console to a server. (Supported by API versions '2.8' - '2.latest') [hint: use ' --os-compute-api-version' flag to show help message for proper version] +get-password,WONTFIX,Get the admin password for a server. This operation calls the metadata service to query metadata information and does not read password information from the server itself. get-rdp-console,console url show --rdp,Get a rdp console to a server. get-serial-console,console url show --serial,Get a serial console to a server. get-spice-console,console url show --spice,Get a spice console to a server. -get-vnc-console,console url show --novnc | --xvpvnc,Get a vnc console to a server. -host-action,,Perform a power action on a host. -host-describe,host show,Describe a specific host. +get-vnc-console,console url show --novnc,Get a vnc console to a server. host-evacuate,,Evacuate all instances from failed host. host-evacuate-live,,Live migrate all instances off the specified host to other available hosts. -host-list,host list,List all hosts by service. host-meta,,Set or Delete metadata on all instances of a host. host-servers-migrate,,Cold migrate all instances off the specified host to other available hosts. -host-update,host set,Update host settings. -hypervisor-list,hypervisor list,List hypervisors. +hypervisor-list,hypervisor list,List hypervisors. (Supported by API versions '2.0' - '2.latest') hypervisor-servers,,List servers belonging to specific hypervisors. hypervisor-show,hypervisor show,Display the details of the specified hypervisor. hypervisor-stats,hypervisor stats show,Get hypervisor statistics over all compute nodes. @@ -58,6 +48,7 @@ image-create,server image create,Create a new image by taking a snapshot of a running server. instance-action,,Show an action. instance-action-list,,List actions on a server. +instance-usage-audit-log,,List/Get server usage audits. interface-attach,,Attach a network interface to a server. interface-detach,,Detach a network interface from a server. interface-list,port list --server,List interfaces attached to a server. @@ -67,7 +58,6 @@ keypair-show,keypair show,Show details about the given keypair. limits,limits show,Print rate and absolute limits. list,server list,List active servers. -list-extensions,extension list,List all the os-api extensions that are available. list-secgroup,security group list,List Security Group(s) of a server. live-migration,,Migrate running server to a new machine. live-migration-abort,,Abort an on-going live migration. @@ -86,7 +76,6 @@ reboot,server reboot,Reboot a server. rebuild,server rebuild,"Shutdown, re-image, and re-boot a server." refresh-network,WONTFIX,Refresh server network information. -remove-fixed-ip,server remove fixed ip,Remove an IP address from a server. remove-secgroup,server remove security group,Remove a Security Group from a server. rescue,server rescue,Reboots a server into rescue mode. reset-network,WONTFIX,Reset network of a server. @@ -107,6 +96,7 @@ server-tag-delete-all,,Delete all tags from a server. server-tag-list,,Get list of tags from a server. server-tag-set,,Set list of tags to a server. +server-topology,openstack server show --topology,Retrieve server topology. (Supported by API versions '2.78' - '2.latest') [hint: use '-- os-compute-api-version' flag to show help message for proper version] service-delete,compute service delete,Delete the service. service-disable,compute service set --disable,Disable the service. service-enable,compute service set --enable,Enable the service. @@ -121,22 +111,17 @@ stop,server stop,Stop the server(s). suspend,server suspend,Suspend a server. trigger-crash-dump,server dump create,Trigger crash dump in an instance. -topology,openstack server show --topology,Retrieve server NUMA topology. unlock,server unlock,Unlock a server. unpause,server unpause,Unpause a server. unrescue,server unrescue,Restart the server from normal boot disk again. unshelve,server unshelve,Unshelve a server. -update,server set / unset --description,Update or unset the description for a server. -update,server set --name,Update the name for a server. +update,server set / unset,Update the name or the description for a server. usage,usage show,Show usage data for a single tenant. usage-list,usage list,List usage data for all tenants. version-list,,List all API versions. -virtual-interface-list,,Show virtual interface info about the given server. volume-attach,server add volume,Attach a volume to a server. volume-attachments,server show,List all the volumes attached to a server. volume-detach,server remove volume,Detach a volume from a server. volume-update,,Update volume attachment. -x509-create-cert,WONTFIX,Create x509 cert for a user in tenant. -x509-get-root-cert,WONTFIX,Fetch the x509 root cert. -bash-completion,complete,Prints all of the commands and options to +bash-completion,complete,Prints all of the commands and options to stdout so that the nova.bash_completion script doesn't have to hard code them. help,help,Display help about this program or one of its subcommands. diff -Nru python-openstackclient-5.5.0/doc/source/contributor/developing.rst python-openstackclient-5.6.0/doc/source/contributor/developing.rst --- python-openstackclient-5.5.0/doc/source/contributor/developing.rst 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/doc/source/contributor/developing.rst 2021-09-01 19:16:00.000000000 +0000 @@ -6,16 +6,18 @@ ------------- IRC Channel -=========== +~~~~~~~~~~~ + The OpenStackClient team doesn't have regular meetings so if you have questions or anything you want to discuss, come to our channel: #openstack-sdks + Testing ------- Tox prerequisites and installation -================================== +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Install the prerequisites for Tox: @@ -23,23 +25,22 @@ .. code-block:: bash - $ apt-get install gcc gettext python-dev libxml2-dev libxslt1-dev \ + $ apt-get install gcc gettext python3-dev libxml2-dev libxslt1-dev \ zlib1g-dev You may need to use pip install for some packages. - * On RHEL or CentOS including Fedora: .. code-block:: bash - $ yum install gcc python-devel libxml2-devel libxslt-devel + $ yum install gcc python3-devel libxml2-devel libxslt-devel * On openSUSE or SUSE linux Enterprise: .. code-block:: bash - $ zypper install gcc python-devel libxml2-devel libxslt-devel + $ zypper install gcc python3-devel libxml2-devel libxslt-devel Install python-tox: @@ -59,7 +60,7 @@ virtualenvs. You can later use the ``-r`` option with ``tox`` to rebuild your virtualenv in a similar manner. -To run tests for one or more specific test environments(for example, the most +To run tests for one or more specific test environments (for example, the most common configuration of the latest Python version and PEP-8), list the environments with the ``-e`` option, separated by spaces: @@ -70,7 +71,7 @@ See ``tox.ini`` for the full list of available test environments. Running functional tests -======================== +~~~~~~~~~~~~~~~~~~~~~~~~ OpenStackClient also maintains a set of functional tests that are optimally designed to be run against OpenStack's gate. Optionally, a developer may @@ -90,7 +91,7 @@ $ tox -e functional -- --regex functional.tests.compute.v2.test_server Running with PDB -================ +~~~~~~~~~~~~~~~~ Using PDB breakpoints with ``tox`` and ``testr`` normally does not work since the tests fail with a `BdbQuit` exception rather than stopping at the @@ -109,8 +110,32 @@ .. _`debug`: https://wiki.openstack.org/wiki/Testr#Debugging_.28pdb.29_Tests -Building the Documentation --------------------------- +Coding Style +------------ + +OpenStackClient uses `flake8`__ along with `hacking`__, an OpenStack-specific +superset of ``flake8`` rules, to enforce coding style. This can be run manually +using ``tox``: + +.. code-block:: bash + + $ tox -e pep8 + +Alternatively, you can use the `pre-commit framework`__ to allow running of +some linters on each commit. This must be enabled locally to function: + +.. code-block:: bash + + $ pip install --user pre-commit + $ pre-commit install --allow-missing-config + +.. __: https://flake8.pycqa.org/en/latest/ +.. __: https://docs.openstack.org/hacking/latest/user/hacking.html +.. __: https://pre-commit.com/ + + +Documentation +------------- The documentation is generated with Sphinx using the ``tox`` command. To create HTML docs, run the commands: @@ -121,6 +146,7 @@ The resultant HTML will be in the ``doc/build/html`` directory. + Release Notes ------------- @@ -156,6 +182,7 @@ At last, look at the generated release notes files in ``releasenotes/build/html`` in your browser. + Testing new code ---------------- @@ -174,7 +201,7 @@ $ pip install -e . Standardize Import Format -========================= +~~~~~~~~~~~~~~~~~~~~~~~~~ More information about Import Format, see `Import Order Guide `__. @@ -193,7 +220,7 @@ {{begin your code}} Example -~~~~~~~ +^^^^^^^ .. code-block:: python diff -Nru python-openstackclient-5.5.0/HACKING.rst python-openstackclient-5.6.0/HACKING.rst --- python-openstackclient-5.5.0/HACKING.rst 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/HACKING.rst 2021-09-01 19:16:00.000000000 +0000 @@ -32,7 +32,7 @@ line all arguments will then be vertically aligned. Use the same convention used with other data structure literals and terminate the method call with the last argument line ending with a comma and the closing paren on its own -line indented to the starting line level. +line indented to the starting line level. :: unnecessarily_long_function_name( 'string one', @@ -40,49 +40,3 @@ kwarg1=constants.ACTIVE, kwarg2=['a', 'b', 'c'], ) - -Text encoding -------------- - -Note: this section clearly has not been implemented in this project yet, it is -the intention to do so. - -All text within python code should be of type 'unicode'. - - WRONG: - - >>> s = 'foo' - >>> s - 'foo' - >>> type(s) - - - RIGHT: - - >>> u = u'foo' - >>> u - u'foo' - >>> type(u) - - -Transitions between internal unicode and external strings should always -be immediately and explicitly encoded or decoded. - -All external text that is not explicitly encoded (database storage, -commandline arguments, etc.) should be presumed to be encoded as utf-8. - - WRONG: - - infile = open('testfile', 'r') - mystring = infile.readline() - myreturnstring = do_some_magic_with(mystring) - outfile.write(myreturnstring) - - RIGHT: - - infile = open('testfile', 'r') - mystring = infile.readline() - mytext = mystring.decode('utf-8') - returntext = do_some_magic_with(mytext) - returnstring = returntext.encode('utf-8') - outfile.write(returnstring) diff -Nru python-openstackclient-5.5.0/lower-constraints.txt python-openstackclient-5.6.0/lower-constraints.txt --- python-openstackclient-5.5.0/lower-constraints.txt 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/lower-constraints.txt 2021-09-01 19:16:00.000000000 +0000 @@ -38,10 +38,9 @@ munch==2.1.0 netaddr==0.7.18 netifaces==0.10.4 -openstacksdk==0.53.0 +openstacksdk==0.56.0 os-client-config==2.1.0 os-service-types==1.7.0 -os-testr==1.0.0 osc-lib==2.3.0 oslo.concurrency==3.26.0 oslo.config==5.2.0 diff -Nru python-openstackclient-5.5.0/openstackclient/common/clientmanager.py python-openstackclient-5.6.0/openstackclient/common/clientmanager.py --- python-openstackclient-5.5.0/openstackclient/common/clientmanager.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/common/clientmanager.py 2021-09-01 19:16:00.000000000 +0000 @@ -83,10 +83,12 @@ self._cli_options._openstack_config._pw_callback = \ shell.prompt_for_password try: - self._cli_options._auth = \ - self._cli_options._openstack_config.load_auth_plugin( - self._cli_options.config, - ) + # We might already get auth from SDK caching + if not self._cli_options._auth: + self._cli_options._auth = \ + self._cli_options._openstack_config.load_auth_plugin( + self._cli_options.config, + ) except TypeError as e: self._fallback_load_auth_plugin(e) diff -Nru python-openstackclient-5.5.0/openstackclient/compute/v2/server.py python-openstackclient-5.6.0/openstackclient/compute/v2/server.py --- python-openstackclient-5.5.0/openstackclient/compute/v2/server.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/compute/v2/server.py 2021-09-01 19:16:00.000000000 +0000 @@ -903,7 +903,7 @@ '(required if using source image, snapshot or volume),\n' 'source_type=: source type ' '(one of: image, snapshot, volume, blank),\n' - 'destination_typ=: destination type ' + 'destination_type=: destination type ' '(one of: volume, local) (optional),\n' 'disk_bus=: device bus ' '(one of: uml, lxc, virtio, ...) (optional),\n' @@ -1012,7 +1012,10 @@ parser.add_argument( '--password', metavar='', - help=_("Set the password to this server"), + help=_( + 'Set the password to this server. ' + 'This option requires cloud support.' + ), ) parser.add_argument( '--security-group', @@ -2778,28 +2781,41 @@ return parser def print_migrations(self, parsed_args, compute_client, migrations): - columns = [ + column_headers = [ 'Source Node', 'Dest Node', 'Source Compute', 'Dest Compute', 'Dest Host', 'Status', 'Server UUID', 'Old Flavor', 'New Flavor', 'Created At', 'Updated At', ] + # Response fields coming back from the REST API are not always exactly + # the same as the column header names. + columns = [ + 'source_node', 'dest_node', 'source_compute', 'dest_compute', + 'dest_host', 'status', 'instance_uuid', 'old_instance_type_id', + 'new_instance_type_id', 'created_at', 'updated_at', + ] + # Insert migrations UUID after ID if compute_client.api_version >= api_versions.APIVersion("2.59"): - columns.insert(0, "UUID") + column_headers.insert(0, "UUID") + columns.insert(0, "uuid") if compute_client.api_version >= api_versions.APIVersion("2.23"): - columns.insert(0, "Id") - columns.insert(len(columns) - 2, "Type") + column_headers.insert(0, "Id") + columns.insert(0, "id") + column_headers.insert(len(column_headers) - 2, "Type") + columns.insert(len(columns) - 2, "migration_type") if compute_client.api_version >= api_versions.APIVersion("2.80"): if parsed_args.project: - columns.insert(len(columns) - 2, "Project") + column_headers.insert(len(column_headers) - 2, "Project") + columns.insert(len(columns) - 2, "project_id") if parsed_args.user: - columns.insert(len(columns) - 2, "User") + column_headers.insert(len(column_headers) - 2, "User") + columns.insert(len(columns) - 2, "user_id") return ( - columns, + column_headers, (utils.get_item_properties(mig, columns) for mig in migrations), ) @@ -3142,7 +3158,10 @@ parser.add_argument( '--password', metavar='', - help=_('Set the password on the rebuilt server'), + help=_( + 'Set the password on the rebuilt server. ' + 'This option requires cloud support.' + ), ) parser.add_argument( '--property', @@ -3435,7 +3454,8 @@ '--password', metavar='', default=None, help=_( 'Set the password on the evacuated instance. This option is ' - 'mutually exclusive with the --shared-storage option' + 'mutually exclusive with the --shared-storage option. ' + 'This option requires cloud support.' ), ) shared_storage_group.add_argument( @@ -3725,7 +3745,10 @@ parser.add_argument( '--password', metavar='', - help=_("Set the password on the rescued instance"), + help=_( + 'Set the password on the rescued instance. ' + 'This option requires cloud support.' + ), ) return parser @@ -3769,12 +3792,20 @@ phase_group.add_argument( '--confirm', action="store_true", - help=_('Confirm server resize is complete'), + help=_( + "**Deprecated** Confirm server resize is complete. " + "Replaced by the 'openstack server resize confirm' and " + "'openstack server migration confirm' commands" + ), ) phase_group.add_argument( '--revert', action="store_true", - help=_('Restore server state before resize'), + help=_( + '**Deprecated** Restore server state before resize' + "Replaced by the 'openstack server resize revert' and " + "'openstack server migration revert' commands" + ), ) parser.add_argument( '--wait', @@ -3984,7 +4015,10 @@ password_group = parser.add_mutually_exclusive_group() password_group.add_argument( '--password', - help=_('Set the server password'), + help=_( + 'Set the server password. ' + 'This option requires cloud support.' + ), ) password_group.add_argument( '--no-password', @@ -4015,7 +4049,12 @@ '--state', metavar='', choices=['active', 'error'], - help=_('New server state (valid value: active, error)'), + help=_( + 'New server state ' + '**WARNING** This can result in instances that are no longer ' + 'usable and should be used with caution ' + '(admin only)' + ), ) parser.add_argument( '--description', diff -Nru python-openstackclient-5.5.0/openstackclient/compute/v2/service.py python-openstackclient-5.6.0/openstackclient/compute/v2/service.py --- python-openstackclient-5.5.0/openstackclient/compute/v2/service.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/compute/v2/service.py 2021-09-01 19:16:00.000000000 +0000 @@ -103,6 +103,10 @@ "Updated At", "Disabled Reason" ) + has_forced_down = ( + compute_client.api_version >= api_versions.APIVersion('2.11')) + if has_forced_down: + columns += ("Forced Down",) else: columns = ( "ID", diff -Nru python-openstackclient-5.5.0/openstackclient/image/v2/image.py python-openstackclient-5.6.0/openstackclient/image/v2/image.py --- python-openstackclient-5.5.0/openstackclient/image/v2/image.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/image/v2/image.py 2021-09-01 19:16:00.000000000 +0000 @@ -490,6 +490,8 @@ parsed_args.name, parsed_args.container_format, parsed_args.disk_format, + visibility=kwargs.get('visibility', 'private'), + protected=True if parsed_args.protected else False ) info = body['os-volume_upload_image'] try: @@ -616,6 +618,12 @@ help=_('Filter images based on tag.'), ) parser.add_argument( + '--hidden', + action='store_true', + default=False, + help=_('List hidden images'), + ) + parser.add_argument( '--long', action='store_true', default=False, @@ -686,6 +694,8 @@ parsed_args.project_domain, ).id kwargs['owner'] = project_id + if parsed_args.hidden: + kwargs['is_hidden'] = True if parsed_args.long: columns = ( 'ID', @@ -1016,6 +1026,22 @@ action="store_true", help=_("Reset the image membership to 'pending'"), ) + + hidden_group = parser.add_mutually_exclusive_group() + hidden_group.add_argument( + "--hidden", + dest='hidden', + default=None, + action="store_true", + help=_("Hide the image"), + ) + hidden_group.add_argument( + "--unhidden", + dest='hidden', + default=None, + action="store_false", + help=_("Unhide the image"), + ) return parser def take_action(self, parsed_args): @@ -1106,6 +1132,9 @@ # Tags should be extended, but duplicates removed kwargs['tags'] = list(set(image.tags).union(set(parsed_args.tags))) + if parsed_args.hidden is not None: + kwargs['is_hidden'] = parsed_args.hidden + try: image = image_client.update_image(image.id, **kwargs) except Exception: diff -Nru python-openstackclient-5.5.0/openstackclient/network/common.py python-openstackclient-5.6.0/openstackclient/network/common.py --- python-openstackclient-5.5.0/openstackclient/network/common.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/network/common.py 2021-09-01 19:16:00.000000000 +0000 @@ -16,10 +16,12 @@ import logging import openstack.exceptions +from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions from openstackclient.i18n import _ +from openstackclient.network import utils LOG = logging.getLogger(__name__) @@ -75,7 +77,6 @@ """ # Have we set it up yet for this command? if not hasattr(self, '_net_type'): - # import pdb; pdb.set_trace() try: if self.app.client_manager.is_network_endpoint_enabled(): net_type = _NET_TYPE_NEUTRON @@ -255,3 +256,74 @@ if exc.details: msg += ", " + str(exc.details) raise exceptions.CommandError(msg) + + +class NeutronCommandWithExtraArgs(command.Command): + """Create and Update commands with additional extra properties. + + Extra properties can be passed to the command and are then send to the + Neutron as given to the command. + """ + + # dict of allowed types + _allowed_types_dict = { + 'bool': utils.str2bool, + 'dict': utils.str2dict, + 'list': utils.str2list, + 'int': int, + 'str': str, + } + + def _get_property_converter(self, _property): + if 'type' not in _property: + converter = str + else: + converter = self._allowed_types_dict.get(_property['type']) + + if not converter: + raise exceptions.CommandError( + _("Type {property_type} of property {name} " + "is not supported").format( + property_type=_property['type'], + name=_property['name'])) + return converter + + def _parse_extra_properties(self, extra_properties): + result = {} + if extra_properties: + for _property in extra_properties: + converter = self._get_property_converter(_property) + result[_property['name']] = converter(_property['value']) + return result + + def get_parser(self, prog_name): + parser = super(NeutronCommandWithExtraArgs, self).get_parser(prog_name) + parser.add_argument( + '--extra-property', + metavar='type=,name=,' + 'value=', + dest='extra_properties', + action=parseractions.MultiKeyValueAction, + required_keys=['name', 'value'], + optional_keys=['type'], + help=_("Additional parameters can be passed using this property. " + "Default type of the extra property is string ('str'), but " + "other types can be used as well. Available types are: " + "'dict', 'list', 'str', 'bool', 'int'. " + "In case of 'list' type, 'value' can be " + "semicolon-separated list of values. " + "For 'dict' value is semicolon-separated list of the " + "key:value pairs.") + ) + return parser + + +class NeutronUnsetCommandWithExtraArgs(NeutronCommandWithExtraArgs): + + def _parse_extra_properties(self, extra_properties): + result = {} + if extra_properties: + for _property in extra_properties: + result[_property['name']] = None + + return result diff -Nru python-openstackclient-5.5.0/openstackclient/network/utils.py python-openstackclient-5.6.0/openstackclient/network/utils.py --- python-openstackclient-5.5.0/openstackclient/network/utils.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/network/utils.py 2021-09-01 19:16:00.000000000 +0000 @@ -11,6 +11,10 @@ # under the License. # +from osc_lib import exceptions + +from openstackclient.i18n import _ + # Transform compute security group rule for display. def transform_compute_security_group_rule(sg_rule): @@ -39,3 +43,41 @@ else: info['remote_security_group'] = '' return info + + +def str2bool(strbool): + if strbool is None: + return None + return strbool.lower() == 'true' + + +def str2list(strlist): + result = [] + if strlist: + result = strlist.split(';') + return result + + +def str2dict(strdict): + """Convert key1:value1;key2:value2;... string into dictionary. + + :param strdict: string in the form of key1:value1;key2:value2 + """ + result = {} + if not strdict: + return result + i = 0 + kvlist = [] + for kv in strdict.split(';'): + if ':' in kv: + kvlist.append(kv) + i += 1 + elif i == 0: + msg = _("missing value for key '%s'") + raise exceptions.CommandError(msg % kv) + else: + kvlist[i - 1] = "%s;%s" % (kvlist[i - 1], kv) + for kv in kvlist: + key, sep, value = kv.partition(':') + result[key] = value + return result diff -Nru python-openstackclient-5.5.0/openstackclient/network/v2/address_group.py python-openstackclient-5.6.0/openstackclient/network/v2/address_group.py --- python-openstackclient-5.5.0/openstackclient/network/v2/address_group.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/network/v2/address_group.py 2021-09-01 19:16:00.000000000 +0000 @@ -22,6 +22,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -57,7 +58,7 @@ return attrs -class CreateAddressGroup(command.ShowOne): +class CreateAddressGroup(command.ShowOne, common.NeutronCommandWithExtraArgs): _description = _("Create a new Address Group") def get_parser(self, prog_name): @@ -93,6 +94,9 @@ client = self.app.client_manager.network attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + obj = client.create_address_group(**attrs) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters={}) @@ -191,7 +195,7 @@ ) for s in data)) -class SetAddressGroup(command.Command): +class SetAddressGroup(common.NeutronCommandWithExtraArgs): _description = _("Set address group properties") def get_parser(self, prog_name): @@ -231,6 +235,9 @@ attrs['name'] = parsed_args.name if parsed_args.description is not None: attrs['description'] = parsed_args.description + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + if attrs: client.update_address_group(obj, **attrs) if parsed_args.address: diff -Nru python-openstackclient-5.5.0/openstackclient/network/v2/address_scope.py python-openstackclient-5.6.0/openstackclient/network/v2/address_scope.py --- python-openstackclient-5.5.0/openstackclient/network/v2/address_scope.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/network/v2/address_scope.py 2021-09-01 19:16:00.000000000 +0000 @@ -21,6 +21,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -57,7 +58,7 @@ # TODO(rtheis): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class CreateAddressScope(command.ShowOne): +class CreateAddressScope(command.ShowOne, common.NeutronCommandWithExtraArgs): _description = _("Create a new Address Scope") def get_parser(self, prog_name): @@ -98,6 +99,8 @@ def take_action(self, parsed_args): client = self.app.client_manager.network attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) obj = client.create_address_scope(**attrs) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters={}) @@ -226,7 +229,7 @@ # TODO(rtheis): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class SetAddressScope(command.Command): +class SetAddressScope(common.NeutronCommandWithExtraArgs): _description = _("Set address scope properties") def get_parser(self, prog_name): @@ -267,6 +270,8 @@ attrs['shared'] = True if parsed_args.no_share: attrs['shared'] = False + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) client.update_address_scope(obj, **attrs) diff -Nru python-openstackclient-5.5.0/openstackclient/network/v2/floating_ip_port_forwarding.py python-openstackclient-5.6.0/openstackclient/network/v2/floating_ip_port_forwarding.py --- python-openstackclient-5.5.0/openstackclient/network/v2/floating_ip_port_forwarding.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/network/v2/floating_ip_port_forwarding.py 2021-09-01 19:16:00.000000000 +0000 @@ -19,6 +19,7 @@ from osc_lib import utils from openstackclient.i18n import _ +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -32,7 +33,8 @@ return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) -class CreateFloatingIPPortForwarding(command.ShowOne): +class CreateFloatingIPPortForwarding(command.ShowOne, + common.NeutronCommandWithExtraArgs): _description = _("Create floating IP port forwarding") def get_parser(self, prog_name): @@ -122,6 +124,9 @@ if parsed_args.description is not None: attrs['description'] = parsed_args.description + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + obj = client.create_floating_ip_port_forwarding( floating_ip.id, **attrs @@ -258,7 +263,7 @@ ) for s in data)) -class SetFloatingIPPortForwarding(command.Command): +class SetFloatingIPPortForwarding(common.NeutronCommandWithExtraArgs): _description = _("Set floating IP Port Forwarding Properties") def get_parser(self, prog_name): @@ -352,6 +357,9 @@ if parsed_args.description is not None: attrs['description'] = parsed_args.description + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + client.update_floating_ip_port_forwarding( floating_ip.id, parsed_args.port_forwarding_id, **attrs) diff -Nru python-openstackclient-5.5.0/openstackclient/network/v2/floating_ip.py python-openstackclient-5.6.0/openstackclient/network/v2/floating_ip.py --- python-openstackclient-5.5.0/openstackclient/network/v2/floating_ip.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/network/v2/floating_ip.py 2021-09-01 19:16:00.000000000 +0000 @@ -13,7 +13,6 @@ """IP Floating action implementations""" -from osc_lib.command import command from osc_lib import utils from osc_lib.utils import tags as _tag @@ -94,7 +93,8 @@ return attrs -class CreateFloatingIP(common.NetworkAndComputeShowOne): +class CreateFloatingIP(common.NetworkAndComputeShowOne, + common.NeutronCommandWithExtraArgs): _description = _("Create floating IP") def update_parser_common(self, parser): @@ -175,6 +175,8 @@ def take_action_network(self, client, parsed_args): attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) with common.check_missing_extension_if_error( self.app.client_manager.network, attrs): obj = client.create_ip(**attrs) @@ -390,7 +392,7 @@ ) for s in data)) -class SetFloatingIP(command.Command): +class SetFloatingIP(common.NeutronCommandWithExtraArgs): _description = _("Set floating IP Properties") def get_parser(self, prog_name): @@ -456,6 +458,9 @@ if 'no_qos_policy' in parsed_args and parsed_args.no_qos_policy: attrs['qos_policy_id'] = None + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + if attrs: client.update_ip(obj, **attrs) @@ -490,7 +495,7 @@ return (columns, data) -class UnsetFloatingIP(command.Command): +class UnsetFloatingIP(common.NeutronCommandWithExtraArgs): _description = _("Unset floating IP Properties") def get_parser(self, prog_name): @@ -526,6 +531,8 @@ attrs['port_id'] = None if parsed_args.qos_policy: attrs['qos_policy_id'] = None + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) if attrs: client.update_ip(obj, **attrs) diff -Nru python-openstackclient-5.5.0/openstackclient/network/v2/l3_conntrack_helper.py python-openstackclient-5.6.0/openstackclient/network/v2/l3_conntrack_helper.py --- python-openstackclient-5.5.0/openstackclient/network/v2/l3_conntrack_helper.py 1970-01-01 00:00:00.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/network/v2/l3_conntrack_helper.py 2021-09-01 19:16:00.000000000 +0000 @@ -0,0 +1,255 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +"""L3 Conntrack Helper 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 = {} + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + + +def _get_attrs(client, parsed_args): + router = client.find_router(parsed_args.router, ignore_missing=False) + attrs = {'router_id': router.id} + if parsed_args.helper: + attrs['helper'] = parsed_args.helper + if parsed_args.protocol: + attrs['protocol'] = parsed_args.protocol + if parsed_args.port: + attrs['port'] = parsed_args.port + + return attrs + + +class CreateConntrackHelper(command.ShowOne): + _description = _("Create a new L3 conntrack helper") + + def get_parser(self, prog_name): + parser = super(CreateConntrackHelper, self).get_parser(prog_name) + parser.add_argument( + 'router', + metavar='', + help=_('Router for which conntrack helper will be created') + ) + parser.add_argument( + '--helper', + required=True, + metavar='', + help=_('The netfilter conntrack helper module') + ) + parser.add_argument( + '--protocol', + required=True, + metavar='', + help=_('The network protocol for the netfilter conntrack target ' + 'rule') + ) + parser.add_argument( + '--port', + required=True, + metavar='', + type=int, + help=_('The network port for the netfilter conntrack target rule') + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + + attrs = _get_attrs(client, parsed_args) + obj = client.create_conntrack_helper(attrs.pop('router_id'), **attrs) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters={}) + + return (display_columns, data) + + +class DeleteConntrackHelper(command.Command): + _description = _("Delete L3 conntrack helper") + + def get_parser(self, prog_name): + parser = super(DeleteConntrackHelper, self).get_parser(prog_name) + parser.add_argument( + 'router', + metavar='', + help=_('Router that the conntrack helper belong to') + ) + parser.add_argument( + 'conntrack_helper_id', + metavar='', + nargs='+', + help=_('The ID of the conntrack helper(s) to delete') + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + result = 0 + + router = client.find_router(parsed_args.router, ignore_missing=False) + for ct_helper in parsed_args.conntrack_helper_id: + try: + client.delete_conntrack_helper( + ct_helper, router.id, ignore_missing=False) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete L3 conntrack helper with " + "ID '%(ct_helper)s': %(e)s"), + {'ct_helper': ct_helper, 'e': e}) + + if result > 0: + total = len(parsed_args.conntrack_helper_id) + msg = (_("%(result)s of %(total)s L3 conntrack helpers failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) + + +class ListConntrackHelper(command.Lister): + _description = _("List L3 conntrack helpers") + + def get_parser(self, prog_name): + parser = super(ListConntrackHelper, self).get_parser(prog_name) + parser.add_argument( + 'router', + metavar='', + help=_('Router that the conntrack helper belong to') + ) + parser.add_argument( + '--helper', + metavar='', + help=_('The netfilter conntrack helper module') + ) + parser.add_argument( + '--protocol', + metavar='', + help=_('The network protocol for the netfilter conntrack target ' + 'rule') + ) + parser.add_argument( + '--port', + metavar='', + help=_('The network port for the netfilter conntrack target rule') + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + columns = ( + 'id', + 'router_id', + 'helper', + 'protocol', + 'port', + ) + column_headers = ( + 'ID', + 'Router ID', + 'Helper', + 'Protocol', + 'Port', + ) + attrs = _get_attrs(client, parsed_args) + data = client.conntrack_helpers(attrs.pop('router_id'), **attrs) + + return (column_headers, + (utils.get_item_properties( + s, columns, formatters={}, + ) for s in data)) + + +class SetConntrackHelper(command.Command): + _description = _("Set L3 conntrack helper properties") + + def get_parser(self, prog_name): + parser = super(SetConntrackHelper, self).get_parser(prog_name) + parser.add_argument( + 'router', + metavar='', + help=_('Router that the conntrack helper belong to') + ) + parser.add_argument( + 'conntrack_helper_id', + metavar='', + help=_('The ID of the conntrack helper(s)') + ) + parser.add_argument( + '--helper', + metavar='', + help=_('The netfilter conntrack helper module') + ) + parser.add_argument( + '--protocol', + metavar='', + help=_('The network protocol for the netfilter conntrack target ' + 'rule') + ) + parser.add_argument( + '--port', + metavar='', + type=int, + help=_('The network port for the netfilter conntrack target rule') + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + attrs = _get_attrs(client, parsed_args) + if attrs: + client.update_conntrack_helper( + parsed_args.conntrack_helper_id, attrs.pop('router_id'), + **attrs) + + +class ShowConntrackHelper(command.ShowOne): + _description = _("Display L3 conntrack helper details") + + def get_parser(self, prog_name): + parser = super(ShowConntrackHelper, self).get_parser(prog_name) + parser.add_argument( + 'router', + metavar='', + help=_('Router that the conntrack helper belong to') + ) + parser.add_argument( + 'conntrack_helper_id', + metavar='', + help=_('The ID of the conntrack helper') + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + router = client.find_router(parsed_args.router, ignore_missing=False) + obj = client.get_conntrack_helper( + parsed_args.conntrack_helper_id, router.id) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters={}) + + return (display_columns, data) diff -Nru python-openstackclient-5.5.0/openstackclient/network/v2/network_flavor_profile.py python-openstackclient-5.6.0/openstackclient/network/v2/network_flavor_profile.py --- python-openstackclient-5.5.0/openstackclient/network/v2/network_flavor_profile.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/network/v2/network_flavor_profile.py 2021-09-01 19:16:00.000000000 +0000 @@ -19,6 +19,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -60,7 +61,8 @@ # TODO(ndahiwade): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class CreateNetworkFlavorProfile(command.ShowOne): +class CreateNetworkFlavorProfile(command.ShowOne, + common.NeutronCommandWithExtraArgs): _description = _("Create new network flavor profile") def get_parser(self, prog_name): @@ -103,6 +105,8 @@ def take_action(self, parsed_args): client = self.app.client_manager.network attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) if parsed_args.driver is None and parsed_args.metainfo is None: msg = _("Either --driver or --metainfo or both are required") @@ -180,7 +184,7 @@ # TODO(ndahiwade): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class SetNetworkFlavorProfile(command.Command): +class SetNetworkFlavorProfile(common.NeutronCommandWithExtraArgs): _description = _("Set network flavor profile properties") def get_parser(self, prog_name): @@ -225,6 +229,8 @@ obj = client.find_service_profile(parsed_args.flavor_profile, ignore_missing=False) attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) client.update_service_profile(obj, **attrs) diff -Nru python-openstackclient-5.5.0/openstackclient/network/v2/network_flavor.py python-openstackclient-5.6.0/openstackclient/network/v2/network_flavor.py --- python-openstackclient-5.5.0/openstackclient/network/v2/network_flavor.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/network/v2/network_flavor.py 2021-09-01 19:16:00.000000000 +0000 @@ -21,6 +21,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -88,7 +89,7 @@ # TODO(dasanind): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class CreateNetworkFlavor(command.ShowOne): +class CreateNetworkFlavor(command.ShowOne, common.NeutronCommandWithExtraArgs): _description = _("Create new network flavor") def get_parser(self, prog_name): @@ -134,6 +135,8 @@ def take_action(self, parsed_args): client = self.app.client_manager.network attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) obj = client.create_flavor(**attrs) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters={}) @@ -234,7 +237,7 @@ # TODO(dasanind): Use only the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class SetNetworkFlavor(command.Command): +class SetNetworkFlavor(common.NeutronCommandWithExtraArgs): _description = _("Set network flavor properties") def get_parser(self, prog_name): @@ -281,6 +284,8 @@ attrs['enabled'] = True if parsed_args.disable: attrs['enabled'] = False + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) client.update_flavor(obj, **attrs) diff -Nru python-openstackclient-5.5.0/openstackclient/network/v2/network_meter.py python-openstackclient-5.6.0/openstackclient/network/v2/network_meter.py --- python-openstackclient-5.5.0/openstackclient/network/v2/network_meter.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/network/v2/network_meter.py 2021-09-01 19:16:00.000000000 +0000 @@ -21,6 +21,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils LOG = logging.getLogger(__name__) @@ -59,7 +60,7 @@ # TODO(ankur-gupta-f): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class CreateMeter(command.ShowOne): +class CreateMeter(command.ShowOne, common.NeutronCommandWithExtraArgs): _description = _("Create network meter") def get_parser(self, prog_name): @@ -100,6 +101,8 @@ def take_action(self, parsed_args): client = self.app.client_manager.network attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) obj = client.create_metering_label(**attrs) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters={}) diff -Nru python-openstackclient-5.5.0/openstackclient/network/v2/network_meter_rule.py python-openstackclient-5.6.0/openstackclient/network/v2/network_meter_rule.py --- python-openstackclient-5.5.0/openstackclient/network/v2/network_meter_rule.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/network/v2/network_meter_rule.py 2021-09-01 19:16:00.000000000 +0000 @@ -21,6 +21,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils LOG = logging.getLogger(__name__) @@ -64,7 +65,7 @@ return attrs -class CreateMeterRule(command.ShowOne): +class CreateMeterRule(command.ShowOne, common.NeutronCommandWithExtraArgs): _description = _("Create a new meter rule") def get_parser(self, prog_name): @@ -130,6 +131,8 @@ ignore_missing=False) parsed_args.meter = _meter.id attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) obj = client.create_metering_label_rule(**attrs) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters={}) diff -Nru python-openstackclient-5.5.0/openstackclient/network/v2/network.py python-openstackclient-5.6.0/openstackclient/network/v2/network.py --- python-openstackclient-5.5.0/openstackclient/network/v2/network.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/network/v2/network.py 2021-09-01 19:16:00.000000000 +0000 @@ -15,7 +15,6 @@ 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 from osc_lib.utils import tags as _tag @@ -189,7 +188,8 @@ # TODO(sindhu): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class CreateNetwork(common.NetworkAndComputeShowOne): +class CreateNetwork(common.NetworkAndComputeShowOne, + common.NeutronCommandWithExtraArgs): _description = _("Create new network") def update_parser_common(self, parser): @@ -334,6 +334,8 @@ attrs['vlan_transparent'] = True if parsed_args.no_transparent_vlan: attrs['vlan_transparent'] = False + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) with common.check_missing_extension_if_error( self.app.client_manager.network, attrs): obj = client.create_network(**attrs) @@ -623,7 +625,7 @@ # TODO(sindhu): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class SetNetwork(command.Command): +class SetNetwork(common.NeutronCommandWithExtraArgs): _description = _("Set network properties") def get_parser(self, prog_name): @@ -728,6 +730,8 @@ obj = client.find_network(parsed_args.network, ignore_missing=False) attrs = _get_attrs_network(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) if attrs: with common.check_missing_extension_if_error( self.app.client_manager.network, attrs): @@ -761,7 +765,7 @@ return (display_columns, data) -class UnsetNetwork(command.Command): +class UnsetNetwork(common.NeutronUnsetCommandWithExtraArgs): _description = _("Unset network properties") def get_parser(self, prog_name): @@ -778,8 +782,9 @@ client = self.app.client_manager.network obj = client.find_network(parsed_args.network, ignore_missing=False) - # NOTE: As of now, UnsetNetwork has no attributes which need - # to be updated by update_network(). + attrs = self._parse_extra_properties(parsed_args.extra_properties) + if attrs: + client.update_network(obj, **attrs) # tags is a subresource and it needs to be updated separately. _tag.update_tags_for_unset(client, obj, parsed_args) diff -Nru python-openstackclient-5.5.0/openstackclient/network/v2/network_qos_policy.py python-openstackclient-5.6.0/openstackclient/network/v2/network_qos_policy.py --- python-openstackclient-5.5.0/openstackclient/network/v2/network_qos_policy.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/network/v2/network_qos_policy.py 2021-09-01 19:16:00.000000000 +0000 @@ -21,6 +21,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -67,7 +68,8 @@ # TODO(abhiraut): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class CreateNetworkQosPolicy(command.ShowOne): +class CreateNetworkQosPolicy(command.ShowOne, + common.NeutronCommandWithExtraArgs): _description = _("Create a QoS policy") def get_parser(self, prog_name): @@ -117,6 +119,8 @@ def take_action(self, parsed_args): client = self.app.client_manager.network attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) obj = client.create_qos_policy(**attrs) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters={}) @@ -209,7 +213,7 @@ # TODO(abhiraut): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class SetNetworkQosPolicy(command.Command): +class SetNetworkQosPolicy(common.NeutronCommandWithExtraArgs): _description = _("Set QoS policy properties") def get_parser(self, prog_name): @@ -259,6 +263,8 @@ parsed_args.policy, ignore_missing=False) attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) client.update_qos_policy(obj, **attrs) diff -Nru python-openstackclient-5.5.0/openstackclient/network/v2/network_qos_rule.py python-openstackclient-5.6.0/openstackclient/network/v2/network_qos_rule.py --- python-openstackclient-5.5.0/openstackclient/network/v2/network_qos_rule.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/network/v2/network_qos_rule.py 2021-09-01 19:16:00.000000000 +0000 @@ -20,6 +20,7 @@ from osc_lib import utils from openstackclient.i18n import _ +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -84,9 +85,6 @@ {'rule_id': parsed_args.id}) raise exceptions.CommandError(msg) else: - if not parsed_args.type: - msg = _('"Create" rule command requires argument "type"') - raise exceptions.CommandError(msg) rule_type = parsed_args.type if parsed_args.max_kbps is not None: attrs['max_kbps'] = parsed_args.max_kbps @@ -174,7 +172,8 @@ ) -class CreateNetworkQosRule(command.ShowOne): +class CreateNetworkQosRule(command.ShowOne, + common.NeutronCommandWithExtraArgs): _description = _("Create new Network QoS rule") def get_parser(self, prog_name): @@ -188,6 +187,7 @@ parser.add_argument( '--type', metavar='', + required=True, choices=[RULE_TYPE_MINIMUM_BANDWIDTH, RULE_TYPE_DSCP_MARKING, RULE_TYPE_BANDWIDTH_LIMIT], @@ -200,6 +200,8 @@ def take_action(self, parsed_args): network_client = self.app.client_manager.network attrs = _get_attrs(network_client, parsed_args, is_create=True) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) try: obj = _rule_action_call( network_client, ACTION_CREATE, parsed_args.type)( @@ -287,7 +289,7 @@ (_get_item_properties(s, columns) for s in data)) -class SetNetworkQosRule(command.Command): +class SetNetworkQosRule(common.NeutronCommandWithExtraArgs): _description = _("Set Network QoS rule properties") def get_parser(self, prog_name): @@ -314,6 +316,8 @@ if not rule_type: raise Exception('Rule not found') attrs = _get_attrs(network_client, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) qos_id = attrs.pop('qos_policy_id') qos_rule = _rule_action_call(network_client, ACTION_FIND, rule_type)(attrs.pop('id'), qos_id) diff -Nru python-openstackclient-5.5.0/openstackclient/network/v2/network_rbac.py python-openstackclient-5.6.0/openstackclient/network/v2/network_rbac.py --- python-openstackclient-5.5.0/openstackclient/network/v2/network_rbac.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/network/v2/network_rbac.py 2021-09-01 19:16:00.000000000 +0000 @@ -21,6 +21,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -90,7 +91,7 @@ # TODO(abhiraut): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class CreateNetworkRBAC(command.ShowOne): +class CreateNetworkRBAC(command.ShowOne, common.NeutronCommandWithExtraArgs): _description = _("Create network RBAC policy") def get_parser(self, prog_name): @@ -150,6 +151,8 @@ def take_action(self, parsed_args): client = self.app.client_manager.network attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) obj = client.create_rbac_policy(**attrs) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns) @@ -253,7 +256,7 @@ # TODO(abhiraut): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class SetNetworkRBAC(command.Command): +class SetNetworkRBAC(common.NeutronCommandWithExtraArgs): _description = _("Set network RBAC policy properties") def get_parser(self, prog_name): @@ -291,6 +294,8 @@ parsed_args.target_project_domain, ).id attrs['target_tenant'] = project_id + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) client.update_rbac_policy(obj, **attrs) diff -Nru python-openstackclient-5.5.0/openstackclient/network/v2/network_segment.py python-openstackclient-5.6.0/openstackclient/network/v2/network_segment.py --- python-openstackclient-5.5.0/openstackclient/network/v2/network_segment.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/network/v2/network_segment.py 2021-09-01 19:16:00.000000000 +0000 @@ -20,6 +20,7 @@ from osc_lib import utils from openstackclient.i18n import _ +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -30,7 +31,8 @@ return sdk_utils.get_osc_show_columns_for_sdk_resource(item, {}) -class CreateNetworkSegment(command.ShowOne): +class CreateNetworkSegment(command.ShowOne, + common.NeutronCommandWithExtraArgs): _description = _("Create new network segment") def get_parser(self, prog_name): @@ -88,6 +90,8 @@ attrs['physical_network'] = parsed_args.physical_network if parsed_args.segment is not None: attrs['segmentation_id'] = parsed_args.segment + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) obj = client.create_segment(**attrs) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns) @@ -189,7 +193,7 @@ ) for s in data)) -class SetNetworkSegment(command.Command): +class SetNetworkSegment(common.NeutronCommandWithExtraArgs): _description = _("Set network segment properties") def get_parser(self, prog_name): @@ -220,6 +224,8 @@ attrs['description'] = parsed_args.description if parsed_args.name is not None: attrs['name'] = parsed_args.name + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) client.update_segment(obj, **attrs) diff -Nru python-openstackclient-5.5.0/openstackclient/network/v2/network_segment_range.py python-openstackclient-5.6.0/openstackclient/network/v2/network_segment_range.py --- python-openstackclient-5.5.0/openstackclient/network/v2/network_segment_range.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/network/v2/network_segment_range.py 2021-09-01 19:16:00.000000000 +0000 @@ -25,6 +25,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -87,7 +88,8 @@ return props -class CreateNetworkSegmentRange(command.ShowOne): +class CreateNetworkSegmentRange(command.ShowOne, + common.NeutronCommandWithExtraArgs): _description = _("Create new network segment range") def get_parser(self, prog_name): @@ -209,6 +211,10 @@ if parsed_args.physical_network: attrs['physical_network'] = parsed_args.physical_network + + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + obj = network_client.create_network_segment_range(**attrs) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns) @@ -365,7 +371,7 @@ return headers, display_props -class SetNetworkSegmentRange(command.Command): +class SetNetworkSegmentRange(common.NeutronCommandWithExtraArgs): _description = _("Set network segment range properties") def get_parser(self, prog_name): @@ -419,6 +425,8 @@ attrs['minimum'] = parsed_args.minimum if parsed_args.maximum: attrs['maximum'] = parsed_args.maximum + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) network_client.update_network_segment_range(obj, **attrs) diff -Nru python-openstackclient-5.5.0/openstackclient/network/v2/port.py python-openstackclient-5.6.0/openstackclient/network/v2/port.py --- python-openstackclient-5.5.0/openstackclient/network/v2/port.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/network/v2/port.py 2021-09-01 19:16:00.000000000 +0000 @@ -255,11 +255,15 @@ parser.add_argument( '--vnic-type', metavar='', - choices=['direct', 'direct-physical', 'macvtap', - 'normal', 'baremetal', 'virtio-forwarder'], - help=_("VNIC type for this port (direct | direct-physical | " - "macvtap | normal | baremetal | virtio-forwarder, " - "default: normal)") + choices=( + 'direct', 'direct-physical', 'macvtap', + 'normal', 'baremetal', 'virtio-forwarder', 'vdpa' + ), + help=_( + "VNIC type for this port (direct | direct-physical | " + "macvtap | normal | baremetal | virtio-forwarder | vdpa, " + "default: normal)" + ), ) parser.add_argument( '--host', @@ -322,7 +326,7 @@ return dhcp_options -class CreatePort(command.ShowOne): +class CreatePort(command.ShowOne, common.NeutronCommandWithExtraArgs): _description = _("Create a new port") def get_parser(self, prog_name): @@ -497,6 +501,9 @@ if parsed_args.tags: attrs['tags'] = list(set(parsed_args.tags)) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + with common.check_missing_extension_if_error( self.app.client_manager.network, attrs): obj = client.create_port(**attrs) @@ -693,7 +700,7 @@ # TODO(abhiraut): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class SetPort(command.Command): +class SetPort(common.NeutronCommandWithExtraArgs): _description = _("Set port properties") def get_parser(self, prog_name): @@ -867,6 +874,9 @@ if parsed_args.data_plane_status: attrs['data_plane_status'] = parsed_args.data_plane_status + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + if attrs: with common.check_missing_extension_if_error( self.app.client_manager.network, attrs): @@ -898,7 +908,7 @@ # TODO(abhiraut): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class UnsetPort(command.Command): +class UnsetPort(common.NeutronUnsetCommandWithExtraArgs): _description = _("Unset port properties") def get_parser(self, prog_name): @@ -1019,6 +1029,9 @@ if parsed_args.numa_policy: attrs['numa_affinity_policy'] = None + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + if attrs: client.update_port(obj, **attrs) diff -Nru python-openstackclient-5.5.0/openstackclient/network/v2/router.py python-openstackclient-5.6.0/openstackclient/network/v2/router.py --- python-openstackclient-5.5.0/openstackclient/network/v2/router.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/network/v2/router.py 2021-09-01 19:16:00.000000000 +0000 @@ -27,6 +27,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -256,7 +257,7 @@ # TODO(yanxing'an): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class CreateRouter(command.ShowOne): +class CreateRouter(command.ShowOne, common.NeutronCommandWithExtraArgs): _description = _("Create a new router") def get_parser(self, prog_name): @@ -332,6 +333,9 @@ attrs['ha'] = True if parsed_args.no_ha: attrs['ha'] = False + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + obj = client.create_router(**attrs) # tags cannot be set when created, so tags need to be set later. _tag.update_tags_for_set(client, obj, parsed_args) @@ -576,7 +580,7 @@ # TODO(yanxing'an): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class SetRouter(command.Command): +class SetRouter(common.NeutronCommandWithExtraArgs): _description = _("Set router properties") def get_parser(self, prog_name): @@ -767,6 +771,10 @@ if 'no_qos_policy' in parsed_args and parsed_args.no_qos_policy: attrs['external_gateway_info']['qos_policy_id'] = None + + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + if attrs: client.update_router(obj, **attrs) # tags is a subresource and it needs to be updated separately. @@ -809,7 +817,7 @@ return (display_columns, data) -class UnsetRouter(command.Command): +class UnsetRouter(common.NeutronUnsetCommandWithExtraArgs): _description = _("Unset router properties") def get_parser(self, prog_name): @@ -875,6 +883,10 @@ if parsed_args.external_gateway: attrs['external_gateway_info'] = {} + + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + if attrs: client.update_router(obj, **attrs) # tags is a subresource and it needs to be updated separately. diff -Nru python-openstackclient-5.5.0/openstackclient/network/v2/security_group.py python-openstackclient-5.6.0/openstackclient/network/v2/security_group.py --- python-openstackclient-5.5.0/openstackclient/network/v2/security_group.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/network/v2/security_group.py 2021-09-01 19:16:00.000000000 +0000 @@ -95,7 +95,8 @@ # TODO(abhiraut): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class CreateSecurityGroup(common.NetworkAndComputeShowOne): +class CreateSecurityGroup(common.NetworkAndComputeShowOne, + common.NeutronCommandWithExtraArgs): _description = _("Create a new security group") def update_parser_common(self, parser): @@ -160,6 +161,8 @@ parsed_args.project_domain, ).id attrs['tenant_id'] = project_id + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) # Create the security group and display the results. obj = client.create_security_group(**attrs) @@ -310,7 +313,8 @@ ) for s in data)) -class SetSecurityGroup(common.NetworkAndComputeCommand): +class SetSecurityGroup(common.NetworkAndComputeCommand, + common.NeutronCommandWithExtraArgs): _description = _("Set security group properties") def update_parser_common(self, parser): @@ -362,6 +366,8 @@ attrs['stateful'] = True if parsed_args.stateless: attrs['stateful'] = False + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) # NOTE(rtheis): Previous behavior did not raise a CommandError # if there were no updates. Maintain this behavior and issue # the update. diff -Nru python-openstackclient-5.5.0/openstackclient/network/v2/security_group_rule.py python-openstackclient-5.6.0/openstackclient/network/v2/security_group_rule.py --- python-openstackclient-5.5.0/openstackclient/network/v2/security_group_rule.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/network/v2/security_group_rule.py 2021-09-01 19:16:00.000000000 +0000 @@ -104,7 +104,8 @@ # TODO(abhiraut): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class CreateSecurityGroupRule(common.NetworkAndComputeShowOne): +class CreateSecurityGroupRule(common.NetworkAndComputeShowOne, + common.NeutronCommandWithExtraArgs): _description = _("Create a new security group rule") def update_parser_common(self, parser): @@ -355,6 +356,9 @@ ).id attrs['tenant_id'] = project_id + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + # Create and show the security group rule. obj = client.create_security_group_rule(**attrs) display_columns, columns = _get_columns(obj) diff -Nru python-openstackclient-5.5.0/openstackclient/network/v2/subnet_pool.py python-openstackclient-5.6.0/openstackclient/network/v2/subnet_pool.py --- python-openstackclient-5.5.0/openstackclient/network/v2/subnet_pool.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/network/v2/subnet_pool.py 2021-09-01 19:16:00.000000000 +0000 @@ -24,6 +24,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -146,7 +147,7 @@ # TODO(rtheis): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class CreateSubnetPool(command.ShowOne): +class CreateSubnetPool(command.ShowOne, common.NeutronCommandWithExtraArgs): _description = _("Create subnet pool") def get_parser(self, prog_name): @@ -203,6 +204,8 @@ # NeutronServer expects prefixes to be a List if "prefixes" not in attrs: attrs['prefixes'] = [] + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) obj = client.create_subnet_pool(**attrs) # tags cannot be set when created, so tags need to be set later. _tag.update_tags_for_set(client, obj, parsed_args) @@ -351,7 +354,7 @@ # TODO(rtheis): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class SetSubnetPool(command.Command): +class SetSubnetPool(common.NeutronCommandWithExtraArgs): _description = _("Set subnet pool properties") def get_parser(self, prog_name): @@ -408,6 +411,9 @@ if 'prefixes' in attrs: attrs['prefixes'].extend(obj.prefixes) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + if attrs: client.update_subnet_pool(obj, **attrs) # tags is a subresource and it needs to be updated separately. diff -Nru python-openstackclient-5.5.0/openstackclient/network/v2/subnet.py python-openstackclient-5.6.0/openstackclient/network/v2/subnet.py --- python-openstackclient-5.5.0/openstackclient/network/v2/subnet.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/network/v2/subnet.py 2021-09-01 19:16:00.000000000 +0000 @@ -26,6 +26,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -141,7 +142,7 @@ 'tenant_id': 'project_id', } # Do not show this column when displaying a subnet - invisible_columns = ['use_default_subnet_pool'] + invisible_columns = ['use_default_subnet_pool', 'prefix_length'] return sdk_utils.get_osc_show_columns_for_sdk_resource( item, column_map, @@ -250,7 +251,7 @@ # TODO(abhiraut): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class CreateSubnet(command.ShowOne): +class CreateSubnet(command.ShowOne, common.NeutronCommandWithExtraArgs): _description = _("Create a subnet") def get_parser(self, prog_name): @@ -373,6 +374,8 @@ def take_action(self, parsed_args): client = self.app.client_manager.network attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) obj = client.create_subnet(**attrs) # tags cannot be set when created, so tags need to be set later. _tag.update_tags_for_set(client, obj, parsed_args) @@ -545,7 +548,7 @@ # TODO(abhiraut): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class SetSubnet(command.Command): +class SetSubnet(common.NeutronCommandWithExtraArgs): _description = _("Set subnet properties") def get_parser(self, prog_name): @@ -630,6 +633,8 @@ attrs['allocation_pools'] = [] if 'service_types' in attrs: attrs['service_types'] += obj.service_types + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) if attrs: client.update_subnet(obj, **attrs) # tags is a subresource and it needs to be updated separately. @@ -657,7 +662,7 @@ return (display_columns, data) -class UnsetSubnet(command.Command): +class UnsetSubnet(common.NeutronUnsetCommandWithExtraArgs): _description = _("Unset subnet properties") def get_parser(self, prog_name): @@ -744,6 +749,9 @@ _update_arguments(attrs['service_types'], parsed_args.service_types, 'service-type') + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + if attrs: client.update_subnet(obj, **attrs) diff -Nru python-openstackclient-5.5.0/openstackclient/tests/functional/base.py python-openstackclient-5.6.0/openstackclient/tests/functional/base.py --- python-openstackclient-5.5.0/openstackclient/tests/functional/base.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/functional/base.py 2021-09-01 19:16:00.000000000 +0000 @@ -68,17 +68,24 @@ ) @classmethod - def is_service_enabled(cls, service): - """Ask client cloud if service is available""" - cmd = ('service show -f value -c enabled {service}' - .format(service=service)) - try: - return "True" in cls.openstack(cmd) - except exceptions.CommandFailed as e: - if "No service with a type, name or ID of" in str(e): - return False - else: - raise # Unable to determine if service is enabled + def is_service_enabled(cls, service, version=None): + """Ask client cloud if service is available + + :param service: The service name or type. This should be either an + exact match to what is in the catalog or a known official value or + alias from service-types-authority + :param version: Optional version. This should be a major version, e.g. + '2.0' + :returns: True if the service is enabled and optionally provides the + specified API version, else False + """ + ret = cls.openstack( + f'versions show --service {service} -f value -c Version' + ).splitlines() + if version: + return version in ret + + return bool(ret) @classmethod def is_extension_enabled(cls, alias): diff -Nru python-openstackclient-5.5.0/openstackclient/tests/functional/network/v2/test_l3_conntrack_helper.py python-openstackclient-5.6.0/openstackclient/tests/functional/network/v2/test_l3_conntrack_helper.py --- python-openstackclient-5.5.0/openstackclient/tests/functional/network/v2/test_l3_conntrack_helper.py 1970-01-01 00:00:00.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/functional/network/v2/test_l3_conntrack_helper.py 2021-09-01 19:16:00.000000000 +0000 @@ -0,0 +1,145 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + + +import json +import uuid + +from openstackclient.tests.functional.network.v2 import common + + +class L3ConntrackHelperTests(common.NetworkTests): + + def setUp(self): + super(L3ConntrackHelperTests, self).setUp() + # Nothing in this class works with Nova Network + if not self.haz_network: + self.skipTest("No Network service present") + if not self.is_extension_enabled('l3-conntrack-helper'): + self.skipTest("No l3-conntrack-helper extension present") + if not self.is_extension_enabled('expose-l3-conntrack-helper'): + self.skipTest("No expose-l3-conntrack-helper extension present") + + def _create_router(self): + router_name = uuid.uuid4().hex + json_output = json.loads(self.openstack( + 'router create -f json ' + router_name + )) + self.assertIsNotNone(json_output['id']) + router_id = json_output['id'] + self.addCleanup(self.openstack, 'router delete ' + router_id) + return router_id + + def _create_helpers(self, router_id, helpers): + created_helpers = [] + for helper in helpers: + output = json.loads(self.openstack( + 'network l3 conntrack helper create %(router)s ' + '--helper %(helper)s --protocol %(protocol)s --port %(port)s ' + '-f json' % {'router': router_id, + 'helper': helper['helper'], + 'protocol': helper['protocol'], + 'port': helper['port']})) + self.assertEqual(helper['helper'], output['helper']) + self.assertEqual(helper['protocol'], output['protocol']) + self.assertEqual(helper['port'], output['port']) + created_helpers.append(output) + return created_helpers + + def test_l3_conntrack_helper_create_and_delete(self): + """Test create, delete multiple""" + + helpers = [ + { + 'helper': 'tftp', + 'protocol': 'udp', + 'port': 69 + }, { + 'helper': 'ftp', + 'protocol': 'tcp', + 'port': 21 + } + ] + router_id = self._create_router() + created_helpers = self._create_helpers(router_id, helpers) + ct_ids = " ".join([ct['id'] for ct in created_helpers]) + + raw_output = self.openstack( + '--debug network l3 conntrack helper delete %(router)s ' + '%(ct_ids)s' % { + 'router': router_id, 'ct_ids': ct_ids}) + self.assertOutput('', raw_output) + + def test_l3_conntrack_helper_list(self): + helpers = [ + { + 'helper': 'tftp', + 'protocol': 'udp', + 'port': 69 + }, { + 'helper': 'ftp', + 'protocol': 'tcp', + 'port': 21 + } + ] + expected_helpers = [ + { + 'Helper': 'tftp', + 'Protocol': 'udp', + 'Port': 69 + }, { + 'Helper': 'ftp', + 'Protocol': 'tcp', + 'Port': 21 + } + ] + router_id = self._create_router() + self._create_helpers(router_id, helpers) + output = json.loads(self.openstack( + 'network l3 conntrack helper list %s -f json ' % router_id + )) + for ct in output: + self.assertEqual(router_id, ct.pop('Router ID')) + ct.pop("ID") + self.assertIn(ct, expected_helpers) + + def test_l3_conntrack_helper_set_and_show(self): + helper = { + 'helper': 'tftp', + 'protocol': 'udp', + 'port': 69} + router_id = self._create_router() + created_helper = self._create_helpers(router_id, [helper])[0] + output = json.loads(self.openstack( + 'network l3 conntrack helper show %(router_id)s %(ct_id)s ' + '-f json' % { + 'router_id': router_id, 'ct_id': created_helper['id']})) + self.assertEqual(helper['helper'], output['helper']) + self.assertEqual(helper['protocol'], output['protocol']) + self.assertEqual(helper['port'], output['port']) + + raw_output = self.openstack( + 'network l3 conntrack helper set %(router_id)s %(ct_id)s ' + '--port %(port)s ' % { + 'router_id': router_id, + 'ct_id': created_helper['id'], + 'port': helper['port'] + 1}) + self.assertOutput('', raw_output) + + output = json.loads(self.openstack( + 'network l3 conntrack helper show %(router_id)s %(ct_id)s ' + '-f json' % { + 'router_id': router_id, 'ct_id': created_helper['id']})) + self.assertEqual(helper['port'] + 1, output['port']) + self.assertEqual(helper['helper'], output['helper']) + self.assertEqual(helper['protocol'], output['protocol']) diff -Nru python-openstackclient-5.5.0/openstackclient/tests/functional/network/v2/test_network_agent.py python-openstackclient-5.6.0/openstackclient/tests/functional/network/v2/test_network_agent.py --- python-openstackclient-5.5.0/openstackclient/tests/functional/network/v2/test_network_agent.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/functional/network/v2/test_network_agent.py 2021-09-01 19:16:00.000000000 +0000 @@ -49,6 +49,11 @@ cmd_output['id'], ) + if 'ovn' in agent_list[0]['Agent Type'].lower(): + # NOTE(slaweq): OVN Neutron agents can't be updated so test can be + # finished here + return + # agent set raw_output = self.openstack( 'network agent set --disable %s' % agent_ids[0] @@ -89,6 +94,9 @@ def test_network_dhcp_agent_list(self): """Test network agent list""" + if not self.is_extension_enabled("dhcp_agent_scheduler"): + self.skipTest("No dhcp_agent_scheduler extension present") + name1 = uuid.uuid4().hex cmd_output1 = json.loads(self.openstack( 'network create -f json --description aaaa %s' % name1 @@ -131,6 +139,10 @@ def test_network_agent_list_routers(self): """Add agent to router, list agents on router, delete.""" + + if not self.is_extension_enabled("l3_agent_scheduler"): + self.skipTest("No l3_agent_scheduler extension present") + name = uuid.uuid4().hex cmd_output = json.loads(self.openstack( 'router create -f json %s' % name)) diff -Nru python-openstackclient-5.5.0/openstackclient/tests/functional/network/v2/test_network.py python-openstackclient-5.6.0/openstackclient/tests/functional/network/v2/test_network.py --- python-openstackclient-5.5.0/openstackclient/tests/functional/network/v2/test_network.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/functional/network/v2/test_network.py 2021-09-01 19:16:00.000000000 +0000 @@ -335,6 +335,8 @@ def test_network_dhcp_agent(self): if not self.haz_network: self.skipTest("No Network service present") + if not self.is_extension_enabled("dhcp_agent_scheduler"): + self.skipTest("No dhcp_agent_scheduler extension present") name1 = uuid.uuid4().hex cmd_output1 = json.loads(self.openstack( diff -Nru python-openstackclient-5.5.0/openstackclient/tests/functional/network/v2/test_network_service_provider.py python-openstackclient-5.6.0/openstackclient/tests/functional/network/v2/test_network_service_provider.py --- python-openstackclient-5.5.0/openstackclient/tests/functional/network/v2/test_network_service_provider.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/functional/network/v2/test_network_service_provider.py 2021-09-01 19:16:00.000000000 +0000 @@ -26,6 +26,14 @@ # Nothing in this class works with Nova Network if not self.haz_network: self.skipTest("No Network service present") + # NOTE(slaweq): + # that tests should works only when "standard" Neutron L3 agent is + # used, as e.g. OVN L3 plugin don't supports that. + l3_agent_list = json.loads(self.openstack( + 'network agent list -f json --agent-type l3 -c ID' + )) + if not l3_agent_list: + self.skipTest("No Neutron L3 Agents present") def test_network_service_provider_list(self): cmd_output = json.loads(self.openstack( diff -Nru python-openstackclient-5.5.0/openstackclient/tests/functional/network/v2/test_router.py python-openstackclient-5.6.0/openstackclient/tests/functional/network/v2/test_router.py --- python-openstackclient-5.5.0/openstackclient/tests/functional/network/v2/test_router.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/functional/network/v2/test_router.py 2021-09-01 19:16:00.000000000 +0000 @@ -155,6 +155,10 @@ def test_router_list_l3_agent(self): """Tests create router, add l3 agent, list, delete""" + + if not self.is_extension_enabled("l3_agent_scheduler"): + self.skipTest("No l3_agent_scheduler extension present") + name = uuid.uuid4().hex cmd_output = json.loads(self.openstack( 'router create -f json ' + name)) @@ -235,32 +239,38 @@ ) # Test set --ha --distributed + self._test_set_router_distributed(new_name) + + # Test unset cmd_output = self.openstack( - 'router set ' + - '--distributed ' + - '--external-gateway public ' + + 'router unset ' + + '--external-gateway ' + new_name ) - self.assertOutput('', cmd_output) - cmd_output = json.loads(self.openstack( 'router show -f json ' + new_name )) - self.assertTrue(cmd_output["distributed"]) - self.assertIsNotNone(cmd_output["external_gateway_info"]) + self.assertIsNone(cmd_output["external_gateway_info"]) + + def _test_set_router_distributed(self, router_name): + if not self.is_extension_enabled("dvr"): + return - # Test unset cmd_output = self.openstack( - 'router unset ' + - '--external-gateway ' + - new_name + 'router set ' + + '--distributed ' + + '--external-gateway public ' + + router_name ) + self.assertOutput('', cmd_output) + cmd_output = json.loads(self.openstack( 'router show -f json ' + - new_name + router_name )) - self.assertIsNone(cmd_output["external_gateway_info"]) + self.assertTrue(cmd_output["distributed"]) + self.assertIsNotNone(cmd_output["external_gateway_info"]) def test_router_add_remove_route(self): network_name = uuid.uuid4().hex diff -Nru python-openstackclient-5.5.0/openstackclient/tests/functional/volume/v1/common.py python-openstackclient-5.6.0/openstackclient/tests/functional/volume/v1/common.py --- python-openstackclient-5.5.0/openstackclient/tests/functional/volume/v1/common.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/functional/volume/v1/common.py 2021-09-01 19:16:00.000000000 +0000 @@ -20,25 +20,14 @@ @classmethod def setUpClass(cls): - super(BaseVolumeTests, cls).setUpClass() - # TODO(dtroyer): This needs to be updated to specifically check for - # Volume v1 rather than just 'volume', but for now - # that is enough until we get proper version negotiation - cls.haz_volume_v1 = cls.is_service_enabled('volume') + super().setUpClass() + cls.haz_volume_v1 = cls.is_service_enabled('block-storage', '1.0') def setUp(self): - super(BaseVolumeTests, self).setUp() + super().setUp() - # This class requires Volume v1 - # if not self.haz_volume_v1: - # self.skipTest("No Volume v1 service present") - - # TODO(dtroyer): We really want the above to work but right now - # (12Sep2017) DevStack still creates a 'volume' - # service type even though there is no service behind - # it. Until that is fixed we need to just skip the - # volume v1 functional tests in master. - self.skipTest("No Volume v1 service present") + if not self.haz_volume_v1: + self.skipTest("No Volume v1 service present") ver_fixture = fixtures.EnvironmentVariable( 'OS_VOLUME_API_VERSION', '1' diff -Nru python-openstackclient-5.5.0/openstackclient/tests/functional/volume/v2/common.py python-openstackclient-5.6.0/openstackclient/tests/functional/volume/v2/common.py --- python-openstackclient-5.5.0/openstackclient/tests/functional/volume/v2/common.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/functional/volume/v2/common.py 2021-09-01 19:16:00.000000000 +0000 @@ -18,8 +18,17 @@ class BaseVolumeTests(base.BaseVolumeTests): """Base class for Volume functional tests. """ + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.haz_volume_v2 = cls.is_service_enabled('block-storage', '2.0') + def setUp(self): - super(BaseVolumeTests, self).setUp() + super().setUp() + + if not self.haz_volume_v2: + self.skipTest("No Volume v2 service present") + ver_fixture = fixtures.EnvironmentVariable( 'OS_VOLUME_API_VERSION', '2' ) diff -Nru python-openstackclient-5.5.0/openstackclient/tests/functional/volume/v3/common.py python-openstackclient-5.6.0/openstackclient/tests/functional/volume/v3/common.py --- python-openstackclient-5.5.0/openstackclient/tests/functional/volume/v3/common.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/functional/volume/v3/common.py 2021-09-01 19:16:00.000000000 +0000 @@ -18,8 +18,17 @@ class BaseVolumeTests(base.BaseVolumeTests): """Base class for Volume functional tests. """ + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.haz_volume_v3 = cls.is_service_enabled('block-storage', '3.0') + def setUp(self): - super(BaseVolumeTests, self).setUp() + super().setUp() + + if not self.haz_volume_v3: + self.skipTest("No Volume v3 service present") + ver_fixture = fixtures.EnvironmentVariable( 'OS_VOLUME_API_VERSION', '3' ) diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/compute/v2/fakes.py python-openstackclient-5.6.0/openstackclient/tests/unit/compute/v2/fakes.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/compute/v2/fakes.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/compute/v2/fakes.py 2021-09-01 19:16:00.000000000 +0000 @@ -722,6 +722,8 @@ 'state': 'state-' + uuid.uuid4().hex, 'updated_at': 'time-' + uuid.uuid4().hex, 'disabled_reason': 'earthquake', + # Introduced in API microversion 2.11 + 'forced_down': False, } # Overwrite default attributes. @@ -1587,20 +1589,20 @@ migration_info = { "dest_host": "10.0.2.15", "status": "migrating", - "type": "migration", + "migration_type": "migration", "updated_at": "2017-01-31T08:03:25.000000", "created_at": "2017-01-31T08:03:21.000000", "dest_compute": "compute-" + uuid.uuid4().hex, "id": random.randint(1, 999), "source_node": "node-" + uuid.uuid4().hex, - "server": uuid.uuid4().hex, + "instance_uuid": uuid.uuid4().hex, "dest_node": "node-" + uuid.uuid4().hex, "source_compute": "compute-" + uuid.uuid4().hex, "uuid": uuid.uuid4().hex, "old_instance_type_id": uuid.uuid4().hex, "new_instance_type_id": uuid.uuid4().hex, - "project": uuid.uuid4().hex, - "user": uuid.uuid4().hex + "project_id": uuid.uuid4().hex, + "user_id": uuid.uuid4().hex } # Overwrite default attributes. diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/compute/v2/test_aggregate.py python-openstackclient-5.6.0/openstackclient/tests/unit/compute/v2/test_aggregate.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/compute/v2/test_aggregate.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/compute/v2/test_aggregate.py 2021-09-01 19:16:00.000000000 +0000 @@ -88,7 +88,7 @@ self.sdk_client.add_host_to_aggregate.assert_called_once_with( self.fake_ag.id, parsed_args.host) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) class TestAggregateCreate(TestAggregate): @@ -112,7 +112,7 @@ self.sdk_client.create_aggregate.assert_called_once_with( name=parsed_args.name) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_aggregate_create_with_zone(self): arglist = [ @@ -129,7 +129,7 @@ self.sdk_client.create_aggregate.assert_called_once_with( name=parsed_args.name, availability_zone=parsed_args.zone) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_aggregate_create_with_property(self): arglist = [ @@ -148,7 +148,7 @@ self.sdk_client.set_aggregate_metadata.assert_called_once_with( self.fake_ag.id, parsed_args.properties) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) class TestAggregateDelete(TestAggregate): @@ -265,7 +265,7 @@ columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.list_columns, columns) - self.assertItemsEqual(self.list_data, tuple(data)) + self.assertCountEqual(self.list_data, tuple(data)) def test_aggregate_list_with_long(self): arglist = [ @@ -278,7 +278,7 @@ columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.list_columns_long, columns) - self.assertItemsEqual(self.list_data_long, tuple(data)) + self.assertCountEqual(self.list_data_long, tuple(data)) class TestAggregateRemoveHost(TestAggregate): @@ -306,7 +306,7 @@ self.sdk_client.remove_host_from_aggregate.assert_called_once_with( self.fake_ag.id, parsed_args.host) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) class TestAggregateSet(TestAggregate): @@ -492,7 +492,7 @@ parsed_args.aggregate, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, tuple(data)) + self.assertCountEqual(self.data, tuple(data)) class TestAggregateUnset(TestAggregate): diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/compute/v2/test_flavor.py python-openstackclient-5.6.0/openstackclient/tests/unit/compute/v2/test_flavor.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/compute/v2/test_flavor.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/compute/v2/test_flavor.py 2021-09-01 19:16:00.000000000 +0000 @@ -133,7 +133,7 @@ self.sdk_client.create_flavor.assert_called_once_with(**default_args) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_flavor_create_all_options(self): @@ -202,7 +202,7 @@ self.sdk_client.get_flavor_access.assert_not_called() self.assertEqual(self.columns, columns) - self.assertItemsEqual(tuple(cmp_data), data) + self.assertCountEqual(tuple(cmp_data), data) def test_flavor_create_other_options(self): @@ -277,7 +277,7 @@ self.sdk_client.create_flavor_extra_specs.assert_called_with( create_flavor, props) self.assertEqual(self.columns, columns) - self.assertItemsEqual(cmp_data, data) + self.assertCountEqual(cmp_data, data) def test_public_flavor_create_with_project(self): arglist = [ @@ -350,7 +350,7 @@ self.sdk_client.create_flavor.assert_called_once_with(**args) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data_private, data) + self.assertCountEqual(self.data_private, data) def test_flavor_create_with_description_api_older(self): arglist = [ @@ -633,7 +633,7 @@ ) self.assertEqual(self.columns_long, columns) - self.assertItemsEqual(self.data_long, tuple(data)) + self.assertCountEqual(self.data_long, tuple(data)) def test_flavor_list_min_disk_min_ram(self): arglist = [ @@ -951,7 +951,7 @@ columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_private_flavor_show(self): private_flavor = compute_fakes.FakeFlavor.create_one_flavor( @@ -991,7 +991,7 @@ self.sdk_client.get_flavor_access.assert_called_with( flavor=private_flavor.id) self.assertEqual(self.columns, columns) - self.assertItemsEqual(data_with_project, data) + self.assertCountEqual(data_with_project, data) class TestFlavorUnset(TestFlavor): diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/compute/v2/test_hypervisor.py python-openstackclient-5.6.0/openstackclient/tests/unit/compute/v2/test_hypervisor.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/compute/v2/test_hypervisor.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/compute/v2/test_hypervisor.py 2021-09-01 19:16:00.000000000 +0000 @@ -394,7 +394,7 @@ columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_hypervisor_show_pre_v228(self): self.app.client_manager.compute.api_version = \ @@ -420,7 +420,7 @@ columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_hypervisor_show_uptime_not_implemented(self): self.app.client_manager.compute.api_version = \ @@ -492,4 +492,4 @@ ) self.assertEqual(expected_columns, columns) - self.assertItemsEqual(expected_data, data) + self.assertCountEqual(expected_data, data) diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/compute/v2/test_server_backup.py python-openstackclient-5.6.0/openstackclient/tests/unit/compute/v2/test_server_backup.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/compute/v2/test_server_backup.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/compute/v2/test_server_backup.py 2021-09-01 19:16:00.000000000 +0000 @@ -139,7 +139,7 @@ ) self.assertEqual(self.image_columns(images[0]), columns) - self.assertItemsEqual(self.image_data(images[0]), data) + self.assertCountEqual(self.image_data(images[0]), data) def test_server_backup_create_options(self): servers = self.setup_servers_mock(count=1) @@ -173,7 +173,7 @@ ) self.assertEqual(self.image_columns(images[0]), columns) - self.assertItemsEqual(self.image_data(images[0]), data) + self.assertCountEqual(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): @@ -269,4 +269,4 @@ ) self.assertEqual(self.image_columns(images[0]), columns) - self.assertItemsEqual(self.image_data(images[0]), data) + self.assertCountEqual(self.image_data(images[0]), data) diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/compute/v2/test_server_image.py python-openstackclient-5.6.0/openstackclient/tests/unit/compute/v2/test_server_image.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/compute/v2/test_server_image.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/compute/v2/test_server_image.py 2021-09-01 19:16:00.000000000 +0000 @@ -134,7 +134,7 @@ ) self.assertEqual(self.image_columns(images[0]), columns) - self.assertItemsEqual(self.image_data(images[0]), data) + self.assertCountEqual(self.image_data(images[0]), data) def test_server_image_create_options(self): servers = self.setup_servers_mock(count=1) @@ -165,7 +165,7 @@ ) self.assertEqual(self.image_columns(images[0]), columns) - self.assertItemsEqual(self.image_data(images[0]), data) + self.assertCountEqual(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): @@ -235,4 +235,4 @@ ) self.assertEqual(self.image_columns(images[0]), columns) - self.assertItemsEqual(self.image_data(images[0]), data) + self.assertCountEqual(self.image_data(images[0]), data) diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/compute/v2/test_server.py python-openstackclient-5.6.0/openstackclient/tests/unit/compute/v2/test_server.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/compute/v2/test_server.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/compute/v2/test_server.py 2021-09-01 19:16:00.000000000 +0000 @@ -4909,6 +4909,13 @@ 'Old Flavor', 'New Flavor', 'Created At', 'Updated At' ] + # These are the fields that come back in the response from the REST API. + MIGRATION_FIELDS = [ + 'source_node', 'dest_node', 'source_compute', 'dest_compute', + 'dest_host', 'status', 'instance_uuid', 'old_instance_type_id', + 'new_instance_type_id', 'created_at', 'updated_at' + ] + def setUp(self): super(TestListMigration, self).setUp() @@ -4920,7 +4927,7 @@ self.migrations_mock.list.return_value = self.migrations self.data = (common_utils.get_item_properties( - s, self.MIGRATION_COLUMNS) for s in self.migrations) + s, self.MIGRATION_FIELDS) for s in self.migrations) # Get the command object to test self.cmd = server.ListMigration(self.app, None) @@ -4977,9 +4984,16 @@ """Test fetch all migrations. """ MIGRATION_COLUMNS = [ - 'Source Node', 'Dest Node', 'Source Compute', - 'Dest Compute', 'Dest Host', 'Status', 'Server UUID', - 'Old Flavor', 'New Flavor', 'Created At', 'Updated At' + 'Id', 'Source Node', 'Dest Node', 'Source Compute', 'Dest Compute', + 'Dest Host', 'Status', 'Server UUID', 'Old Flavor', 'New Flavor', + 'Type', 'Created At', 'Updated At' + ] + + # These are the fields that come back in the response from the REST API. + MIGRATION_FIELDS = [ + 'id', 'source_node', 'dest_node', 'source_compute', 'dest_compute', + 'dest_host', 'status', 'instance_uuid', 'old_instance_type_id', + 'new_instance_type_id', 'migration_type', 'created_at', 'updated_at' ] def setUp(self): @@ -5006,9 +5020,6 @@ self.migrations_mock.list.assert_called_with(**kwargs) - self.MIGRATION_COLUMNS.insert(0, "Id") - self.MIGRATION_COLUMNS.insert( - len(self.MIGRATION_COLUMNS) - 2, 'Type') self.assertEqual(self.MIGRATION_COLUMNS, columns) self.assertEqual(tuple(self.data), tuple(data)) @@ -5022,6 +5033,14 @@ 'Old Flavor', 'New Flavor', 'Type', 'Created At', 'Updated At' ] + # These are the fields that come back in the response from the REST API. + MIGRATION_FIELDS = [ + 'id', 'uuid', 'source_node', 'dest_node', 'source_compute', + 'dest_compute', 'dest_host', 'status', 'instance_uuid', + 'old_instance_type_id', 'new_instance_type_id', 'migration_type', + 'created_at', 'updated_at' + ] + def setUp(self): super(TestListMigrationV259, self).setUp() @@ -5128,6 +5147,14 @@ 'Old Flavor', 'New Flavor', 'Type', 'Created At', 'Updated At' ] + # These are the fields that come back in the response from the REST API. + MIGRATION_FIELDS = [ + 'id', 'uuid', 'source_node', 'dest_node', 'source_compute', + 'dest_compute', 'dest_host', 'status', 'instance_uuid', + 'old_instance_type_id', 'new_instance_type_id', 'migration_type', + 'created_at', 'updated_at' + ] + def setUp(self): super(TestListMigrationV266, self).setUp() @@ -5197,6 +5224,14 @@ 'Old Flavor', 'New Flavor', 'Type', 'Created At', 'Updated At' ] + # These are the fields that come back in the response from the REST API. + MIGRATION_FIELDS = [ + 'id', 'uuid', 'source_node', 'dest_node', 'source_compute', + 'dest_compute', 'dest_host', 'status', 'instance_uuid', + 'old_instance_type_id', 'new_instance_type_id', 'migration_type', + 'created_at', 'updated_at' + ] + project = identity_fakes.FakeProject.create_one_project() user = identity_fakes.FakeUser.create_one_user() @@ -5250,10 +5285,14 @@ self.MIGRATION_COLUMNS.insert( len(self.MIGRATION_COLUMNS) - 2, "Project") + self.MIGRATION_FIELDS.insert( + len(self.MIGRATION_FIELDS) - 2, "project_id") self.assertEqual(self.MIGRATION_COLUMNS, columns) self.assertEqual(tuple(self.data), tuple(data)) # Clean up global variables MIGRATION_COLUMNS self.MIGRATION_COLUMNS.remove('Project') + # Clean up global variables MIGRATION_FIELDS + self.MIGRATION_FIELDS.remove('project_id') def test_get_migrations_with_project_pre_v280(self): self.app.client_manager.compute.api_version = api_versions.APIVersion( @@ -5312,10 +5351,14 @@ self.MIGRATION_COLUMNS.insert( len(self.MIGRATION_COLUMNS) - 2, "User") + self.MIGRATION_FIELDS.insert( + len(self.MIGRATION_FIELDS) - 2, "user_id") self.assertEqual(self.MIGRATION_COLUMNS, columns) self.assertEqual(tuple(self.data), tuple(data)) # Clean up global variables MIGRATION_COLUMNS self.MIGRATION_COLUMNS.remove('User') + # Clean up global variables MIGRATION_FIELDS + self.MIGRATION_FIELDS.remove('user_id') def test_get_migrations_with_user_pre_v280(self): self.app.client_manager.compute.api_version = api_versions.APIVersion( @@ -5374,13 +5417,19 @@ self.MIGRATION_COLUMNS.insert( len(self.MIGRATION_COLUMNS) - 2, "Project") + self.MIGRATION_FIELDS.insert( + len(self.MIGRATION_FIELDS) - 2, "project_id") self.MIGRATION_COLUMNS.insert( len(self.MIGRATION_COLUMNS) - 2, "User") + self.MIGRATION_FIELDS.insert( + len(self.MIGRATION_FIELDS) - 2, "user_id") self.assertEqual(self.MIGRATION_COLUMNS, columns) self.assertEqual(tuple(self.data), tuple(data)) # Clean up global variables MIGRATION_COLUMNS self.MIGRATION_COLUMNS.remove('Project') + self.MIGRATION_FIELDS.remove('project_id') self.MIGRATION_COLUMNS.remove('User') + self.MIGRATION_FIELDS.remove('user_id') def test_get_migrations_with_project_and_user_pre_v280(self): self.app.client_manager.compute.api_version = api_versions.APIVersion( diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/compute/v2/test_service.py python-openstackclient-5.6.0/openstackclient/tests/unit/compute/v2/test_service.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/compute/v2/test_service.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/compute/v2/test_service.py 2021-09-01 19:16:00.000000000 +0000 @@ -190,6 +190,38 @@ self.assertEqual(self.columns_long, columns) self.assertEqual(self.data_long, list(data)) + def test_service_list_with_long_option_2_11(self): + arglist = [ + '--host', self.service.host, + '--service', self.service.binary, + '--long' + ] + verifylist = [ + ('host', self.service.host), + ('service', self.service.binary), + ('long', True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.11') + + # 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.service_mock.list.assert_called_with( + self.service.host, + self.service.binary, + ) + + # In 2.11 there is also a forced_down column. + columns_long = self.columns_long + ('Forced Down',) + data_long = [self.data_long[0] + (self.service.forced_down,)] + + self.assertEqual(columns_long, columns) + self.assertEqual(data_long, list(data)) + class TestServiceSet(TestService): diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/identity/v2_0/test_catalog.py python-openstackclient-5.6.0/openstackclient/tests/unit/identity/v2_0/test_catalog.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/identity/v2_0/test_catalog.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/identity/v2_0/test_catalog.py 2021-09-01 19:16:00.000000000 +0000 @@ -74,7 +74,7 @@ catalog.EndpointsColumn( auth_ref.service_catalog.catalog[0]['endpoints']), ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) def test_catalog_list_with_endpoint_url(self): attr = { @@ -117,7 +117,7 @@ catalog.EndpointsColumn( auth_ref.service_catalog.catalog[0]['endpoints']), ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) class TestCatalogShow(TestCatalog): @@ -158,7 +158,7 @@ 'supernova', 'compute', ) - self.assertItemsEqual(datalist, data) + self.assertCountEqual(datalist, data) class TestFormatColumns(TestCatalog): diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/identity/v2_0/test_project.py python-openstackclient-5.6.0/openstackclient/tests/unit/identity/v2_0/test_project.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/identity/v2_0/test_project.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/identity/v2_0/test_project.py 2021-09-01 19:16:00.000000000 +0000 @@ -643,7 +643,7 @@ self.fake_proj_show.name, format_columns.DictColumn({}), ) - self.assertItemsEqual(datalist, data) + self.assertCountEqual(datalist, data) class TestProjectUnset(TestProject): diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/identity/v2_0/test_user.py python-openstackclient-5.6.0/openstackclient/tests/unit/identity/v2_0/test_user.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/identity/v2_0/test_user.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/identity/v2_0/test_user.py 2021-09-01 19:16:00.000000000 +0000 @@ -482,7 +482,7 @@ self.users_mock.list.assert_called_with(tenant_id=None) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, tuple(data)) + self.assertCountEqual(self.datalist, tuple(data)) def test_user_list_project(self): arglist = [ @@ -502,7 +502,7 @@ self.users_mock.list.assert_called_with(tenant_id=project_id) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, tuple(data)) + self.assertCountEqual(self.datalist, tuple(data)) def test_user_list_long(self): arglist = [ @@ -531,7 +531,7 @@ self.fake_user_l.email, True, ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) class TestUserSet(TestUser): @@ -819,4 +819,4 @@ self.fake_user.name, self.fake_project.id, ) - self.assertItemsEqual(datalist, data) + self.assertCountEqual(datalist, data) diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/identity/v3/test_catalog.py python-openstackclient-5.6.0/openstackclient/tests/unit/identity/v3/test_catalog.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/identity/v3/test_catalog.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/identity/v3/test_catalog.py 2021-09-01 19:16:00.000000000 +0000 @@ -94,7 +94,7 @@ catalog.EndpointsColumn( auth_ref.service_catalog.catalog[0]['endpoints']), ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) class TestCatalogShow(TestCatalog): @@ -135,7 +135,7 @@ 'supernova', 'compute', ) - self.assertItemsEqual(datalist, data) + self.assertCountEqual(datalist, data) class TestFormatColumns(TestCatalog): diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/identity/v3/test_identity_provider.py python-openstackclient-5.6.0/openstackclient/tests/unit/identity/v3/test_identity_provider.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/identity/v3/test_identity_provider.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/identity/v3/test_identity_provider.py 2021-09-01 19:16:00.000000000 +0000 @@ -89,7 +89,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_create_identity_provider_description(self): arglist = [ @@ -117,7 +117,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_create_identity_provider_remote_id(self): arglist = [ @@ -145,7 +145,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_create_identity_provider_remote_ids_multiple(self): arglist = [ @@ -174,7 +174,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_create_identity_provider_remote_ids_file(self): arglist = [ @@ -207,7 +207,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_create_identity_provider_disabled(self): @@ -250,7 +250,7 @@ identity_fakes.idp_id, identity_fakes.formatted_idp_remote_ids ) - self.assertItemsEqual(datalist, data) + self.assertCountEqual(datalist, data) def test_create_identity_provider_domain_name(self): arglist = [ @@ -278,7 +278,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_create_identity_provider_domain_id(self): arglist = [ @@ -306,7 +306,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) class TestIdentityProviderDelete(TestIdentityProvider): @@ -382,7 +382,7 @@ identity_fakes.domain_id, identity_fakes.idp_description, ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) def test_identity_provider_list_ID_option(self): arglist = ['--id', @@ -410,7 +410,7 @@ identity_fakes.domain_id, identity_fakes.idp_description, ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) def test_identity_provider_list_enabled_option(self): arglist = ['--enabled'] @@ -437,7 +437,7 @@ identity_fakes.domain_id, identity_fakes.idp_description, ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) class TestIdentityProviderSet(TestIdentityProvider): @@ -722,4 +722,4 @@ identity_fakes.idp_id, identity_fakes.formatted_idp_remote_ids ) - self.assertItemsEqual(datalist, data) + self.assertCountEqual(datalist, data) diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/image/v1/test_image.py python-openstackclient-5.6.0/openstackclient/tests/unit/image/v1/test_image.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/image/v1/test_image.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/image/v1/test_image.py 2021-09-01 19:16:00.000000000 +0000 @@ -100,7 +100,7 @@ self.assertEqual(self.client.update_image.call_args_list, []) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) @mock.patch('sys.stdin', side_effect=[None]) def test_image_reserve_options(self, raw_input): @@ -149,7 +149,7 @@ self.assertEqual(self.client.update_image.call_args_list, []) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) @mock.patch('openstackclient.image.v1.image.io.open', name='Open') def test_image_create_file(self, mock_open): @@ -205,7 +205,7 @@ self.assertEqual(self.client.update_image.call_args_list, []) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) class TestImageDelete(TestImage): @@ -386,7 +386,7 @@ format_columns.DictColumn( {'Alpha': 'a', 'Beta': 'b', 'Gamma': 'g'}), ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) @mock.patch('osc_lib.api.utils.simple_filter') def test_image_list_property_option(self, sf_mock): @@ -737,7 +737,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_image_show_human_readable(self): arglist = [ diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/image/v2/test_image.py python-openstackclient-5.6.0/openstackclient/tests/unit/image/v2/test_image.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/image/v2/test_image.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/image/v2/test_image.py 2021-09-01 19:16:00.000000000 +0000 @@ -111,7 +111,7 @@ self.assertEqual( self.expected_columns, columns) - self.assertItemsEqual( + self.assertCountEqual( self.expected_data, data) @@ -166,7 +166,7 @@ self.assertEqual( self.expected_columns, columns) - self.assertItemsEqual( + self.assertCountEqual( self.expected_data, data) @@ -255,7 +255,7 @@ self.assertEqual( self.expected_columns, columns) - self.assertItemsEqual( + self.assertCountEqual( self.expected_data, data) @@ -513,7 +513,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, tuple(data)) + self.assertCountEqual(self.datalist, tuple(data)) def test_image_list_public_option(self): arglist = [ @@ -537,7 +537,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, tuple(data)) + self.assertCountEqual(self.datalist, tuple(data)) def test_image_list_private_option(self): arglist = [ @@ -561,7 +561,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, tuple(data)) + self.assertCountEqual(self.datalist, tuple(data)) def test_image_list_community_option(self): arglist = [ @@ -609,7 +609,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, tuple(data)) + self.assertCountEqual(self.datalist, tuple(data)) def test_image_list_shared_member_status_option(self): arglist = [ @@ -697,7 +697,7 @@ self._image.owner_id, format_columns.ListColumn(self._image.tags), ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) @mock.patch('osc_lib.api.utils.simple_filter') def test_image_list_property_option(self, sf_mock): @@ -725,7 +725,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, tuple(data)) + self.assertCountEqual(self.datalist, tuple(data)) @mock.patch('osc_lib.utils.sort_items') def test_image_list_sort_option(self, si_mock): @@ -747,7 +747,7 @@ str, ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, tuple(data)) + self.assertCountEqual(self.datalist, tuple(data)) def test_image_list_limit_option(self): ret_limit = 1 @@ -782,7 +782,7 @@ columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, tuple(data)) + self.assertCountEqual(self.datalist, tuple(data)) @mock.patch('osc_lib.utils.find_resource') def test_image_list_marker_option(self, fr_mock): @@ -837,6 +837,20 @@ status='active' ) + def test_image_list_hidden_option(self): + arglist = [ + '--hidden', + ] + verifylist = [ + ('hidden', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.client.images.assert_called_with( + is_hidden=True + ) + def test_image_list_tag_option(self): arglist = [ '--tag', 'abc', @@ -1439,6 +1453,60 @@ ) self.assertIsNone(result) + def test_image_set_hidden(self): + arglist = [ + '--hidden', + '--public', + image_fakes.image_name, + ] + verifylist = [ + ('hidden', True), + ('public', True), + ('private', False), + ('image', image_fakes.image_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + kwargs = { + 'is_hidden': True, + 'visibility': 'public', + } + # ImageManager.update(image, **kwargs) + self.client.update_image.assert_called_with( + self._image.id, + **kwargs + ) + self.assertIsNone(result) + + def test_image_set_unhidden(self): + arglist = [ + '--unhidden', + '--public', + image_fakes.image_name, + ] + verifylist = [ + ('hidden', False), + ('public', True), + ('private', False), + ('image', image_fakes.image_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + kwargs = { + 'is_hidden': False, + 'visibility': 'public', + } + # ImageManager.update(image, **kwargs) + self.client.update_image.assert_called_with( + self._image.id, + **kwargs + ) + self.assertIsNone(result) + class TestImageShow(TestImage): @@ -1487,7 +1555,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_image_show_human_readable(self): self.client.find_image.return_value = self.new_image diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/network/test_common.py python-openstackclient-5.6.0/openstackclient/tests/unit/network/test_common.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/network/test_common.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/network/test_common.py 2021-09-01 19:16:00.000000000 +0000 @@ -102,6 +102,27 @@ return client.compute_action(parsed_args) +class FakeCreateNeutronCommandWithExtraArgs( + common.NeutronCommandWithExtraArgs): + + def get_parser(self, prog_name): + parser = super(FakeCreateNeutronCommandWithExtraArgs, + self).get_parser(prog_name) + parser.add_argument( + '--known-attribute', + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + attrs = {} + if 'known_attribute' in parsed_args: + attrs['known_attribute'] = parsed_args.known_attribute + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + client.test_create_action(**attrs) + + class TestNetworkAndCompute(utils.TestCommand): def setUp(self): @@ -187,3 +208,121 @@ m_action.side_effect = openstack.exceptions.HttpException("bar") self.assertRaisesRegex(exceptions.CommandError, "bar", self.cmd.take_action, mock.Mock()) + + +class TestNeutronCommandWithExtraArgs(utils.TestCommand): + + def setUp(self): + super(TestNeutronCommandWithExtraArgs, self).setUp() + + self.namespace = argparse.Namespace() + + self.app.client_manager.network = mock.Mock() + self.network = self.app.client_manager.network + self.network.test_create_action = mock.Mock() + + # Subclasses can override the command object to test. + self.cmd = FakeCreateNeutronCommandWithExtraArgs( + self.app, self.namespace) + + def test_create_extra_attributes_default_type(self): + arglist = [ + '--known-attribute', 'known-value', + '--extra-property', 'name=extra_name,value=extra_value' + ] + verifylist = [ + ('known_attribute', 'known-value'), + ('extra_properties', [{'name': 'extra_name', + 'value': 'extra_value'}]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.network.test_create_action.assert_called_with( + known_attribute='known-value', extra_name='extra_value') + + def test_create_extra_attributes_string(self): + arglist = [ + '--known-attribute', 'known-value', + '--extra-property', 'type=str,name=extra_name,value=extra_value' + ] + verifylist = [ + ('known_attribute', 'known-value'), + ('extra_properties', [{'name': 'extra_name', + 'type': 'str', + 'value': 'extra_value'}]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.network.test_create_action.assert_called_with( + known_attribute='known-value', extra_name='extra_value') + + def test_create_extra_attributes_bool(self): + arglist = [ + '--known-attribute', 'known-value', + '--extra-property', 'type=bool,name=extra_name,value=TrUe' + ] + verifylist = [ + ('known_attribute', 'known-value'), + ('extra_properties', [{'name': 'extra_name', + 'type': 'bool', + 'value': 'TrUe'}]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.network.test_create_action.assert_called_with( + known_attribute='known-value', extra_name=True) + + def test_create_extra_attributes_int(self): + arglist = [ + '--known-attribute', 'known-value', + '--extra-property', 'type=int,name=extra_name,value=8' + ] + verifylist = [ + ('known_attribute', 'known-value'), + ('extra_properties', [{'name': 'extra_name', + 'type': 'int', + 'value': '8'}]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.network.test_create_action.assert_called_with( + known_attribute='known-value', extra_name=8) + + def test_create_extra_attributes_list(self): + arglist = [ + '--known-attribute', 'known-value', + '--extra-property', 'type=list,name=extra_name,value=v_1;v_2' + ] + verifylist = [ + ('known_attribute', 'known-value'), + ('extra_properties', [{'name': 'extra_name', + 'type': 'list', + 'value': 'v_1;v_2'}]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.network.test_create_action.assert_called_with( + known_attribute='known-value', extra_name=['v_1', 'v_2']) + + def test_create_extra_attributes_dict(self): + arglist = [ + '--known-attribute', 'known-value', + '--extra-property', 'type=dict,name=extra_name,value=n1:v1;n2:v2' + ] + verifylist = [ + ('known_attribute', 'known-value'), + ('extra_properties', [{'name': 'extra_name', + 'type': 'dict', + 'value': 'n1:v1;n2:v2'}]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.network.test_create_action.assert_called_with( + known_attribute='known-value', + extra_name={'n1': 'v1', 'n2': 'v2'}) diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/network/test_utils.py python-openstackclient-5.6.0/openstackclient/tests/unit/network/test_utils.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/network/test_utils.py 1970-01-01 00:00:00.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/network/test_utils.py 2021-09-01 19:16:00.000000000 +0000 @@ -0,0 +1,59 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +from osc_lib import exceptions + +from openstackclient.network import utils +from openstackclient.tests.unit import utils as tests_utils + + +class TestUtils(tests_utils.TestCase): + + def test_str2bool(self): + self.assertTrue(utils.str2bool("true")) + self.assertTrue(utils.str2bool("True")) + self.assertTrue(utils.str2bool("TRUE")) + self.assertTrue(utils.str2bool("TrUe")) + + self.assertFalse(utils.str2bool("false")) + self.assertFalse(utils.str2bool("False")) + self.assertFalse(utils.str2bool("FALSE")) + self.assertFalse(utils.str2bool("FaLsE")) + self.assertFalse(utils.str2bool("Something else")) + self.assertFalse(utils.str2bool("")) + + self.assertIsNone(utils.str2bool(None)) + + def test_str2list(self): + self.assertEqual( + ['a', 'b', 'c'], utils.str2list("a;b;c")) + self.assertEqual( + ['abc'], utils.str2list("abc")) + + self.assertEqual([], utils.str2list("")) + self.assertEqual([], utils.str2list(None)) + + def test_str2dict(self): + self.assertEqual( + {'a': 'aaa', 'b': '2'}, + utils.str2dict('a:aaa;b:2')) + self.assertEqual( + {'a': 'aaa;b;c', 'd': 'ddd'}, + utils.str2dict('a:aaa;b;c;d:ddd')) + + self.assertEqual({}, utils.str2dict("")) + self.assertEqual({}, utils.str2dict(None)) + + self.assertRaises( + exceptions.CommandError, + utils.str2dict, "aaa;b:2") diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/network/v2/fakes.py python-openstackclient-5.6.0/openstackclient/tests/unit/network/v2/fakes.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/network/v2/fakes.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/network/v2/fakes.py 2021-09-01 19:16:00.000000000 +0000 @@ -1973,3 +1973,78 @@ ) return mock.Mock(side_effect=port_forwardings) + + +class FakeL3ConntrackHelper(object): + """"Fake one or more L3 conntrack helper""" + + @staticmethod + def create_one_l3_conntrack_helper(attrs=None): + """Create a fake L3 conntrack helper. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with protocol, port, etc. + """ + attrs = attrs or {} + router_id = ( + attrs.get('router_id') or 'router-id-' + uuid.uuid4().hex + ) + # Set default attributes. + ct_attrs = { + 'id': uuid.uuid4().hex, + 'router_id': router_id, + 'helper': 'tftp', + 'protocol': 'tcp', + 'port': randint(1, 65535), + } + + # Overwrite default attributes. + ct_attrs.update(attrs) + + ct = fakes.FakeResource( + info=copy.deepcopy(ct_attrs), + loaded=True + ) + return ct + + @staticmethod + def create_l3_conntrack_helpers(attrs=None, count=2): + """Create multiple fake L3 Conntrack helpers. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of L3 Conntrack helper rule to fake + :return: + A list of FakeResource objects faking the Conntrack helpers + """ + ct_helpers = [] + for i in range(0, count): + ct_helpers.append( + FakeL3ConntrackHelper.create_one_l3_conntrack_helper(attrs) + ) + return ct_helpers + + @staticmethod + def get_l3_conntrack_helpers(ct_helpers=None, count=2): + """Get a list of faked L3 Conntrack helpers. + + If ct_helpers list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List ct_helpers: + A list of FakeResource objects faking conntrack helpers + :param int count: + The number of L3 conntrack helpers to fake + :return: + An iterable Mock object with side_effect set to a list of faked + L3 conntrack helpers + """ + if ct_helpers is None: + ct_helpers = ( + FakeL3ConntrackHelper.create_l3_conntrack_helpers(count) + ) + + return mock.Mock(side_effect=ct_helpers) diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/network/v2/test_address_group.py python-openstackclient-5.6.0/openstackclient/tests/unit/network/v2/test_address_group.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/network/v2/test_address_group.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/network/v2/test_address_group.py 2021-09-01 19:16:00.000000000 +0000 @@ -99,7 +99,7 @@ 'addresses': [], }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_all_options(self): arglist = [ @@ -127,7 +127,7 @@ 'description': self.new_address_group.description, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) class TestDeleteAddressGroup(TestAddressGroup): @@ -252,7 +252,7 @@ self.network.address_groups.assert_called_once_with(**{}) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_address_group_list_name(self): arglist = [ @@ -267,7 +267,7 @@ self.network.address_groups.assert_called_once_with( **{'name': self.address_groups[0].name}) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_address_group_list_project(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -284,7 +284,7 @@ self.network.address_groups.assert_called_once_with( project_id=project.id) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_address_group_project_domain(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -302,7 +302,7 @@ self.network.address_groups.assert_called_once_with( project_id=project.id) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) class TestSetAddressGroup(TestAddressGroup): @@ -438,7 +438,7 @@ self.network.find_address_group.assert_called_once_with( self._address_group.name, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) class TestUnsetAddressGroup(TestAddressGroup): diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/network/v2/test_ip_availability.py python-openstackclient-5.6.0/openstackclient/tests/unit/network/v2/test_ip_availability.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/network/v2/test_ip_availability.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/network/v2/test_ip_availability.py 2021-09-01 19:16:00.000000000 +0000 @@ -75,7 +75,7 @@ self.network.network_ip_availabilities.assert_called_once_with( **filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_list_ip_version(self): arglist = [ @@ -93,7 +93,7 @@ self.network.network_ip_availabilities.assert_called_once_with( **filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_list_project(self): arglist = [ @@ -113,7 +113,7 @@ self.network.network_ip_availabilities.assert_called_once_with( **filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) class TestShowIPAvailability(TestIPAvailability): @@ -176,4 +176,4 @@ self._ip_availability.network_name, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/network/v2/test_l3_conntrack_helper.py python-openstackclient-5.6.0/openstackclient/tests/unit/network/v2/test_l3_conntrack_helper.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/network/v2/test_l3_conntrack_helper.py 1970-01-01 00:00:00.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/network/v2/test_l3_conntrack_helper.py 2021-09-01 19:16:00.000000000 +0000 @@ -0,0 +1,316 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +from unittest import mock + +from osc_lib import exceptions + +from openstackclient.network.v2 import l3_conntrack_helper +from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit import utils as tests_utils + + +class TestConntrackHelper(network_fakes.TestNetworkV2): + + def setUp(self): + super(TestConntrackHelper, self).setUp() + # Get a shortcut to the network client + self.network = self.app.client_manager.network + self.router = network_fakes.FakeRouter.create_one_router() + self.network.find_router = mock.Mock(return_value=self.router) + + +class TestCreateL3ConntrackHelper(TestConntrackHelper): + + def setUp(self): + super(TestCreateL3ConntrackHelper, self).setUp() + attrs = {'router_id': self.router.id} + self.ct_helper = ( + network_fakes.FakeL3ConntrackHelper.create_one_l3_conntrack_helper( + attrs)) + self.columns = ( + 'helper', + 'id', + 'port', + 'protocol', + 'router_id' + ) + + self.data = ( + self.ct_helper.helper, + self.ct_helper.id, + self.ct_helper.port, + self.ct_helper.protocol, + self.ct_helper.router_id + ) + self.network.create_conntrack_helper = mock.Mock( + return_value=self.ct_helper) + + # Get the command object to test + self.cmd = l3_conntrack_helper.CreateConntrackHelper(self.app, + self.namespace) + + 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_default_options(self): + arglist = [ + '--helper', 'tftp', + '--protocol', 'udp', + '--port', '69', + self.router.id, + ] + + verifylist = [ + ('helper', 'tftp'), + ('protocol', 'udp'), + ('port', 69), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_conntrack_helper.assert_called_once_with( + self.router.id, + **{'helper': 'tftp', 'protocol': 'udp', + 'port': 69} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_wrong_options(self): + arglist = [ + '--protocol', 'udp', + '--port', '69', + self.router.id, + ] + + self.assertRaises( + tests_utils.ParserException, + self.check_parser, + self.cmd, arglist, None) + + +class TestDeleteL3ConntrackHelper(TestConntrackHelper): + + def setUp(self): + super(TestDeleteL3ConntrackHelper, self).setUp() + attrs = {'router_id': self.router.id} + self.ct_helper = ( + network_fakes.FakeL3ConntrackHelper.create_one_l3_conntrack_helper( + attrs)) + self.network.delete_conntrack_helper = mock.Mock( + return_value=None) + + # Get the command object to test + self.cmd = l3_conntrack_helper.DeleteConntrackHelper(self.app, + self.namespace) + + def test_delete(self): + arglist = [ + self.ct_helper.router_id, + self.ct_helper.id + ] + verifylist = [ + ('conntrack_helper_id', [self.ct_helper.id]), + ('router', self.ct_helper.router_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.network.delete_conntrack_helper.assert_called_once_with( + self.ct_helper.id, self.router.id, + ignore_missing=False) + self.assertIsNone(result) + + def test_delete_error(self): + arglist = [ + self.router.id, + self.ct_helper.id + ] + verifylist = [ + ('conntrack_helper_id', [self.ct_helper.id]), + ('router', self.router.id), + ] + self.network.delete_conntrack_helper.side_effect = Exception( + 'Error message') + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, parsed_args) + + +class TestListL3ConntrackHelper(TestConntrackHelper): + + def setUp(self): + super(TestListL3ConntrackHelper, self).setUp() + attrs = {'router_id': self.router.id} + ct_helpers = ( + network_fakes.FakeL3ConntrackHelper.create_l3_conntrack_helpers( + attrs, count=3)) + self.columns = ( + 'ID', + 'Router ID', + 'Helper', + 'Protocol', + 'Port', + ) + self.data = [] + for ct_helper in ct_helpers: + self.data.append(( + ct_helper.id, + ct_helper.router_id, + ct_helper.helper, + ct_helper.protocol, + ct_helper.port, + )) + self.network.conntrack_helpers = mock.Mock( + return_value=ct_helpers) + + # Get the command object to test + self.cmd = l3_conntrack_helper.ListConntrackHelper(self.app, + self.namespace) + + def test_conntrack_helpers_list(self): + arglist = [ + self.router.id + ] + verifylist = [ + ('router', self.router.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.conntrack_helpers.assert_called_once_with( + self.router.id) + self.assertEqual(self.columns, columns) + list_data = list(data) + self.assertEqual(len(self.data), len(list_data)) + for index in range(len(list_data)): + self.assertEqual(self.data[index], list_data[index]) + + +class TestSetL3ConntrackHelper(TestConntrackHelper): + + def setUp(self): + super(TestSetL3ConntrackHelper, self).setUp() + attrs = {'router_id': self.router.id} + self.ct_helper = ( + network_fakes.FakeL3ConntrackHelper.create_one_l3_conntrack_helper( + attrs)) + self.network.update_conntrack_helper = mock.Mock(return_value=None) + + # Get the command object to test + self.cmd = l3_conntrack_helper.SetConntrackHelper(self.app, + self.namespace) + + def test_set_nothing(self): + arglist = [ + self.router.id, + self.ct_helper.id, + ] + verifylist = [ + ('router', self.router.id), + ('conntrack_helper_id', self.ct_helper.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = (self.cmd.take_action(parsed_args)) + + self.network.update_conntrack_helper.assert_called_once_with( + self.ct_helper.id, self.router.id + ) + self.assertIsNone(result) + + def test_set_port(self): + arglist = [ + self.router.id, + self.ct_helper.id, + '--port', '124', + ] + verifylist = [ + ('router', self.router.id), + ('conntrack_helper_id', self.ct_helper.id), + ('port', 124), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = (self.cmd.take_action(parsed_args)) + + self.network.update_conntrack_helper.assert_called_once_with( + self.ct_helper.id, self.router.id, port=124 + ) + self.assertIsNone(result) + + +class TestShowL3ConntrackHelper(TestConntrackHelper): + + def setUp(self): + super(TestShowL3ConntrackHelper, self).setUp() + attrs = {'router_id': self.router.id} + self.ct_helper = ( + network_fakes.FakeL3ConntrackHelper.create_one_l3_conntrack_helper( + attrs)) + self.columns = ( + 'helper', + 'id', + 'port', + 'protocol', + 'router_id' + ) + + self.data = ( + self.ct_helper.helper, + self.ct_helper.id, + self.ct_helper.port, + self.ct_helper.protocol, + self.ct_helper.router_id + ) + self.network.get_conntrack_helper = mock.Mock( + return_value=self.ct_helper) + + # Get the command object to test + self.cmd = l3_conntrack_helper.ShowConntrackHelper(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.router.id, + self.ct_helper.id, + ] + verifylist = [ + ('router', self.router.id), + ('conntrack_helper_id', self.ct_helper.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.get_conntrack_helper.assert_called_once_with( + self.ct_helper.id, self.router.id + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/network/v2/test_network_agent.py python-openstackclient-5.6.0/openstackclient/tests/unit/network/v2/test_network_agent.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/network/v2/test_network_agent.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/network/v2/test_network_agent.py 2021-09-01 19:16:00.000000000 +0000 @@ -246,7 +246,7 @@ self.network.agents.assert_called_once_with(**{}) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_network_agents_list_agent_type(self): arglist = [ @@ -263,7 +263,7 @@ 'agent_type': 'DHCP agent', }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_network_agents_list_host(self): arglist = [ @@ -280,7 +280,7 @@ 'host': self.network_agents[0].host, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_network_agents_list_networks(self): arglist = [ @@ -298,7 +298,7 @@ self.network.network_hosting_dhcp_agents.assert_called_once_with( *attrs) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_network_agents_list_routers(self): arglist = [ @@ -318,7 +318,7 @@ *attrs) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_network_agents_list_routers_with_long_option(self): arglist = [ @@ -343,7 +343,7 @@ router_agent_data = [d + ('',) for d in self.data] self.assertEqual(router_agent_columns, columns) - self.assertItemsEqual(router_agent_data, list(data)) + self.assertCountEqual(router_agent_data, list(data)) class TestRemoveNetworkFromAgent(TestNetworkAgent): @@ -571,4 +571,4 @@ self.network.get_agent.assert_called_once_with( self._network_agent.id) self.assertEqual(self.columns, columns) - self.assertItemsEqual(list(self.data), list(data)) + self.assertCountEqual(list(self.data), list(data)) diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/network/v2/test_network.py python-openstackclient-5.6.0/openstackclient/tests/unit/network/v2/test_network.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/network/v2/test_network.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/network/v2/test_network.py 2021-09-01 19:16:00.000000000 +0000 @@ -146,7 +146,7 @@ }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_all_options(self): arglist = [ @@ -211,7 +211,7 @@ 'dns_domain': 'example.org.', }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_other_options(self): arglist = [ @@ -238,7 +238,7 @@ 'port_security_enabled': False, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def _test_create_with_tag(self, add_tags=True): arglist = [self._network.name] @@ -270,7 +270,7 @@ else: self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_tags(self): self._test_create_with_tag(add_tags=True) @@ -385,7 +385,7 @@ }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_domain_identityv2(self): arglist = [ @@ -577,7 +577,7 @@ self.network.networks.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_list_external(self): arglist = [ @@ -598,7 +598,7 @@ **{'router:external': True, 'is_router_external': True} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_list_internal(self): arglist = [ @@ -615,7 +615,7 @@ **{'router:external': False, 'is_router_external': False} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_network_list_long(self): arglist = [ @@ -634,7 +634,7 @@ self.network.networks.assert_called_once_with() self.assertEqual(self.columns_long, columns) - self.assertItemsEqual(self.data_long, list(data)) + self.assertCountEqual(self.data_long, list(data)) def test_list_name(self): test_name = "fakename" @@ -653,7 +653,7 @@ **{'name': test_name} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_network_list_enable(self): arglist = [ @@ -671,7 +671,7 @@ **{'admin_state_up': True, 'is_admin_state_up': True} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_network_list_disable(self): arglist = [ @@ -689,7 +689,7 @@ **{'admin_state_up': False, 'is_admin_state_up': False} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_network_list_project(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -708,7 +708,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_network_list_project_domain(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -727,7 +727,7 @@ self.network.networks.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_network_list_share(self): arglist = [ @@ -744,7 +744,7 @@ **{'shared': True, 'is_shared': True} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_network_list_no_share(self): arglist = [ @@ -761,7 +761,7 @@ **{'shared': False, 'is_shared': False} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_network_list_status(self): choices = ['ACTIVE', 'BUILD', 'DOWN', 'ERROR'] @@ -780,7 +780,7 @@ **{'status': test_status} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_network_list_provider_network_type(self): network_type = self._network[0].provider_network_type @@ -798,7 +798,7 @@ 'provider_network_type': network_type} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_network_list_provider_physical_network(self): physical_network = self._network[0].provider_physical_network @@ -816,7 +816,7 @@ 'provider_physical_network': physical_network} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_network_list_provider_segment(self): segmentation_id = self._network[0].provider_segmentation_id @@ -834,7 +834,7 @@ 'provider_segmentation_id': segmentation_id} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_network_list_dhcp_agent(self): arglist = [ @@ -853,7 +853,7 @@ *attrs) self.assertEqual(self.columns, columns) - self.assertItemsEqual(list(data), list(self.data)) + self.assertCountEqual(list(data), list(self.data)) def test_list_with_tag_options(self): arglist = [ @@ -878,7 +878,7 @@ 'not_any_tags': 'black,white'} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) class TestSetNetwork(TestNetwork): @@ -1111,7 +1111,7 @@ self._network.name, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) class TestUnsetNetwork(TestNetwork): diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/network/v2/test_port.py python-openstackclient-5.6.0/openstackclient/tests/unit/network/v2/test_port.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/network/v2/test_port.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/network/v2/test_port.py 2021-09-01 19:16:00.000000000 +0000 @@ -153,7 +153,7 @@ self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_full_options(self): arglist = [ @@ -211,7 +211,7 @@ }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_invalid_json_binding_profile(self): arglist = [ @@ -262,7 +262,7 @@ }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_security_group(self): secgroup = network_fakes.FakeSecurityGroup.create_one_security_group() @@ -291,7 +291,7 @@ }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_port_with_dns_name(self): arglist = [ @@ -317,7 +317,7 @@ }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_security_groups(self): sg_1 = network_fakes.FakeSecurityGroup.create_one_security_group() @@ -347,7 +347,7 @@ }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_no_security_groups(self): arglist = [ @@ -373,7 +373,7 @@ }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_no_fixed_ips(self): arglist = [ @@ -399,7 +399,7 @@ }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_port_with_allowed_address_pair_ipaddr(self): pairs = [{'ip_address': '192.168.1.123'}, @@ -429,7 +429,7 @@ }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_port_with_allowed_address_pair(self): pairs = [{'ip_address': '192.168.1.123', @@ -465,7 +465,7 @@ }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_port_with_qos(self): qos_policy = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy() @@ -493,7 +493,7 @@ }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_port_security_enabled(self): arglist = [ @@ -602,7 +602,7 @@ self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_tags(self): self._test_create_with_tag(add_tags=True, add_tags_in_post=True) @@ -645,7 +645,7 @@ }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_uplink_status_propagation_enabled(self): self._test_create_with_uplink_status_propagation(enable=True) @@ -725,7 +725,7 @@ self.network.create_port.assert_called_once_with(**create_args) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_numa_affinity_policy_required(self): self._test_create_with_numa_affinity_policy(policy='required') @@ -764,7 +764,7 @@ } self.network.create_port.assert_called_once_with(**create_args) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) class TestDeletePort(TestPort): @@ -919,7 +919,7 @@ self.network.ports.assert_called_once_with( fields=LIST_FIELDS_TO_RETRIEVE) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_port_list_router_opt(self): arglist = [ @@ -939,7 +939,7 @@ 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) @mock.patch.object(utils, 'find_resource') def test_port_list_with_server_option(self, mock_find): @@ -960,7 +960,7 @@ fields=LIST_FIELDS_TO_RETRIEVE) mock_find.assert_called_once_with(mock.ANY, 'fake-server-name') self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_port_list_device_id_opt(self): arglist = [ @@ -980,7 +980,7 @@ 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_port_list_device_owner_opt(self): arglist = [ @@ -1000,7 +1000,7 @@ 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_port_list_all_opt(self): arglist = [ @@ -1029,7 +1029,7 @@ 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_port_list_mac_address_opt(self): arglist = [ @@ -1049,7 +1049,7 @@ 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_port_list_fixed_ip_opt_ip_address(self): ip_address = self._ports[0].fixed_ips[0]['ip_address'] @@ -1069,7 +1069,7 @@ 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(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] @@ -1089,7 +1089,7 @@ 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_port_list_fixed_ip_opt_subnet_id(self): subnet_id = self._ports[0].fixed_ips[0]['subnet_id'] @@ -1111,7 +1111,7 @@ 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_port_list_fixed_ip_opts(self): subnet_id = self._ports[0].fixed_ips[0]['subnet_id'] @@ -1137,7 +1137,7 @@ 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_port_list_fixed_ips(self): subnet_id = self._ports[0].fixed_ips[0]['subnet_id'] @@ -1165,7 +1165,7 @@ 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_list_port_with_long(self): arglist = [ @@ -1183,7 +1183,7 @@ self.network.ports.assert_called_once_with( fields=LIST_FIELDS_TO_RETRIEVE + LIST_FIELDS_TO_RETRIEVE_LONG) self.assertEqual(self.columns_long, columns) - self.assertItemsEqual(self.data_long, list(data)) + self.assertCountEqual(self.data_long, list(data)) def test_port_list_host(self): arglist = [ @@ -1202,7 +1202,7 @@ self.network.ports.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_port_list_project(self): project = identity_fakes.FakeProject.create_one_project() @@ -1224,7 +1224,7 @@ self.network.ports.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_port_list_project_domain(self): project = identity_fakes.FakeProject.create_one_project() @@ -1248,7 +1248,7 @@ self.network.ports.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_port_list_name(self): test_name = "fakename" @@ -1268,7 +1268,7 @@ self.network.ports.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_list_with_tag_options(self): arglist = [ @@ -1294,7 +1294,7 @@ 'fields': LIST_FIELDS_TO_RETRIEVE} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) class TestSetPort(TestPort): @@ -1894,7 +1894,7 @@ self._port.name, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) class TestUnsetPort(TestPort): diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/network/v2/test_router.py python-openstackclient-5.6.0/openstackclient/tests/unit/network/v2/test_router.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/network/v2/test_router.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/network/v2/test_router.py 2021-09-01 19:16:00.000000000 +0000 @@ -184,7 +184,7 @@ }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def _test_create_with_ha_options(self, option, ha): arglist = [ @@ -208,7 +208,7 @@ 'ha': ha, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_ha_option(self): self._test_create_with_ha_options('--ha', True) @@ -237,7 +237,7 @@ 'distributed': distributed, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_distributed_option(self): self._test_create_with_distributed_options('--distributed', True) @@ -268,7 +268,7 @@ }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def _test_create_with_tag(self, add_tags=True): arglist = [self.new_router.name] @@ -301,7 +301,7 @@ else: self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_tags(self): self._test_create_with_tag(add_tags=True) @@ -494,7 +494,7 @@ self.network.routers.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_router_list_no_ha_no_distributed(self): _routers = network_fakes.FakeRouter.create_routers({ @@ -531,7 +531,7 @@ self.network.routers.assert_called_once_with() self.assertEqual(self.columns_long, columns) - self.assertItemsEqual(self.data_long, list(data)) + self.assertCountEqual(self.data_long, list(data)) def test_router_list_long_no_az(self): arglist = [ @@ -552,7 +552,7 @@ self.network.routers.assert_called_once_with() self.assertEqual(self.columns_long_no_az, columns) - self.assertItemsEqual(self.data_long_no_az, list(data)) + self.assertCountEqual(self.data_long_no_az, list(data)) def test_list_name(self): test_name = "fakename" @@ -570,7 +570,7 @@ **{'name': test_name} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_router_list_enable(self): arglist = [ @@ -587,7 +587,7 @@ **{'admin_state_up': True, 'is_admin_state_up': True} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_router_list_disable(self): arglist = [ @@ -605,7 +605,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_router_list_project(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -623,7 +623,7 @@ self.network.routers.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_router_list_project_domain(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -643,7 +643,7 @@ self.network.routers.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_router_list_agents_no_args(self): arglist = [ @@ -671,7 +671,7 @@ self.network.agent_hosted_routers( *attrs) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_list_with_tag_options(self): arglist = [ @@ -696,7 +696,7 @@ 'not_any_tags': 'black,white'} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) class TestRemovePortFromRouter(TestRouter): @@ -1403,7 +1403,7 @@ 'device_id': self._router.id }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_show_no_ha_no_distributed(self): _router = network_fakes.FakeRouter.create_one_router({ diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/network/v2/test_security_group_compute.py python-openstackclient-5.6.0/openstackclient/tests/unit/network/v2/test_security_group_compute.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/network/v2/test_security_group_compute.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/network/v2/test_security_group_compute.py 2021-09-01 19:16:00.000000000 +0000 @@ -88,7 +88,7 @@ self._security_group['name'], ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_security_group_create_all_options(self, sg_mock): sg_mock.return_value = self._security_group @@ -109,7 +109,7 @@ self._security_group['description'], ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) @mock.patch( @@ -255,7 +255,7 @@ kwargs = {'search_opts': {'all_tenants': False}} sg_mock.assert_called_once_with(**kwargs) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(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 @@ kwargs = {'search_opts': {'all_tenants': True}} sg_mock.assert_called_once_with(**kwargs) self.assertEqual(self.columns_all_projects, columns) - self.assertItemsEqual(self.data_all_projects, list(data)) + self.assertCountEqual(self.data_all_projects, list(data)) @mock.patch( @@ -401,4 +401,4 @@ sg_mock.assert_called_once_with(self._security_group['id']) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/network/v2/test_security_group_network.py python-openstackclient-5.6.0/openstackclient/tests/unit/network/v2/test_security_group_network.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/network/v2/test_security_group_network.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/network/v2/test_security_group_network.py 2021-09-01 19:16:00.000000000 +0000 @@ -96,7 +96,7 @@ 'name': self._security_group.name, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_all_options(self): arglist = [ @@ -124,7 +124,7 @@ 'tenant_id': self.project.id, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def _test_create_with_tag(self, add_tags=True): arglist = [self._security_group.name] @@ -155,7 +155,7 @@ else: self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_tags(self): self._test_create_with_tag(add_tags=True) @@ -293,7 +293,7 @@ self.network.security_groups.assert_called_once_with( fields=security_group.ListSecurityGroup.FIELDS_TO_RETRIEVE) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_security_group_list_all_projects(self): arglist = [ @@ -309,7 +309,7 @@ self.network.security_groups.assert_called_once_with( fields=security_group.ListSecurityGroup.FIELDS_TO_RETRIEVE) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_security_group_list_project(self): project = identity_fakes.FakeProject.create_one_project() @@ -329,7 +329,7 @@ self.network.security_groups.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_security_group_list_project_domain(self): project = identity_fakes.FakeProject.create_one_project() @@ -351,7 +351,7 @@ self.network.security_groups.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_list_with_tag_options(self): arglist = [ @@ -539,7 +539,7 @@ self.network.find_security_group.assert_called_once_with( self._security_group.id, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) class TestUnsetSecurityGroupNetwork(TestSecurityGroupNetwork): diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/network/v2/test_subnet_pool.py python-openstackclient-5.6.0/openstackclient/tests/unit/network/v2/test_subnet_pool.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/network/v2/test_subnet_pool.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/network/v2/test_subnet_pool.py 2021-09-01 19:16:00.000000000 +0000 @@ -133,7 +133,7 @@ }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_prefixlen_options(self): arglist = [ @@ -163,7 +163,7 @@ 'name': self._subnet_pool.name, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_len_negative(self): arglist = [ @@ -201,7 +201,7 @@ 'name': self._subnet_pool.name, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_address_scope_option(self): arglist = [ @@ -224,7 +224,7 @@ 'name': self._subnet_pool.name, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_default_and_shared_options(self): arglist = [ @@ -250,7 +250,7 @@ 'shared': True, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_description(self): arglist = [ @@ -273,7 +273,7 @@ 'description': self._subnet_pool.description, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_default_quota(self): arglist = [ @@ -294,7 +294,7 @@ 'default_quota': 10, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def _test_create_with_tag(self, add_tags=True): arglist = [ @@ -328,7 +328,7 @@ else: self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_tags(self): self._test_create_with_tag(add_tags=True) @@ -476,7 +476,7 @@ self.network.subnet_pools.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_pool_list_long(self): arglist = [ @@ -491,7 +491,7 @@ self.network.subnet_pools.assert_called_once_with() self.assertEqual(self.columns_long, columns) - self.assertItemsEqual(self.data_long, list(data)) + self.assertCountEqual(self.data_long, list(data)) def test_subnet_pool_list_no_share(self): arglist = [ @@ -507,7 +507,7 @@ self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_pool_list_share(self): arglist = [ @@ -523,7 +523,7 @@ self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_pool_list_no_default(self): arglist = [ @@ -539,7 +539,7 @@ self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_pool_list_default(self): arglist = [ @@ -555,7 +555,7 @@ self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_pool_list_project(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -573,7 +573,7 @@ self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_pool_list_project_domain(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -593,7 +593,7 @@ self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_pool_list_name(self): subnet_pool = network_fakes.FakeSubnetPool.create_one_subnet_pool() @@ -611,7 +611,7 @@ self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_pool_list_address_scope(self): addr_scope = network_fakes.FakeAddressScope.create_one_address_scope() @@ -629,7 +629,7 @@ self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_list_with_tag_options(self): arglist = [ @@ -654,7 +654,7 @@ 'not_any_tags': 'black,white'} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) class TestSetSubnetPool(TestSubnetPool): @@ -1008,7 +1008,7 @@ ignore_missing=False ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) class TestUnsetSubnetPool(TestSubnetPool): diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/network/v2/test_subnet.py python-openstackclient-5.6.0/openstackclient/tests/unit/network/v2/test_subnet.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/network/v2/test_subnet.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/network/v2/test_subnet.py 2021-09-01 19:16:00.000000000 +0000 @@ -255,7 +255,7 @@ }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_from_subnet_pool_options(self): # Mock SDK calls for this test. @@ -317,7 +317,7 @@ 'service_types': self._subnet_from_pool.service_types, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data_subnet_pool, data) + self.assertCountEqual(self.data_subnet_pool, data) def test_create_options_subnet_range_ipv6(self): # Mock SDK calls for this test. @@ -390,7 +390,7 @@ }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data_ipv6, data) + self.assertCountEqual(self.data_ipv6, data) def test_create_with_network_segment(self): # Mock SDK calls for this test. @@ -424,7 +424,7 @@ }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_description(self): # Mock SDK calls for this test. @@ -458,7 +458,7 @@ }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def _test_create_with_dns(self, publish_dns=True): arglist = [ @@ -490,7 +490,7 @@ dns_publish_fixed_ip=publish_dns, ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_dns(self): self._test_create_with_dns(publish_dns=True) @@ -535,7 +535,7 @@ else: self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_tags(self): self._test_create_with_tag(add_tags=True) @@ -691,7 +691,7 @@ self.network.subnets.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_list_long(self): arglist = [ @@ -706,7 +706,7 @@ self.network.subnets.assert_called_once_with() self.assertEqual(self.columns_long, columns) - self.assertItemsEqual(self.data_long, list(data)) + self.assertCountEqual(self.data_long, list(data)) def test_subnet_list_ip_version(self): arglist = [ @@ -722,7 +722,7 @@ self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_list_dhcp(self): arglist = [ @@ -738,7 +738,7 @@ self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_list_no_dhcp(self): arglist = [ @@ -754,7 +754,7 @@ self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_list_service_type(self): arglist = [ @@ -769,7 +769,7 @@ self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_list_project(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -787,7 +787,7 @@ self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_list_service_type_multiple(self): arglist = [ @@ -805,7 +805,7 @@ 'network:floatingip_agent_gateway']} self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_list_project_domain(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -825,7 +825,7 @@ self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_list_network(self): network = network_fakes.FakeNetwork.create_one_network() @@ -843,7 +843,7 @@ self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_list_gateway(self): subnet = network_fakes.FakeSubnet.create_one_subnet() @@ -861,7 +861,7 @@ self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_list_name(self): subnet = network_fakes.FakeSubnet.create_one_subnet() @@ -879,7 +879,7 @@ self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_list_subnet_range(self): subnet = network_fakes.FakeSubnet.create_one_subnet() @@ -897,7 +897,7 @@ self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_list_with_tag_options(self): arglist = [ @@ -1244,7 +1244,7 @@ self._subnet.name, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) class TestUnsetSubnet(TestSubnet): diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/test_shell.py python-openstackclient-5.6.0/openstackclient/tests/unit/test_shell.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/test_shell.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/test_shell.py 2021-09-01 19:16:00.000000000 +0000 @@ -15,7 +15,6 @@ import importlib import os -import sys from unittest import mock from osc_lib.tests import utils as osc_lib_test_utils @@ -412,32 +411,3 @@ "network_api_version": LIB_NETWORK_API_VERSION } self._assert_cli(flag, kwargs) - - -class TestShellArgV(TestShell): - """Test the deferred help flag""" - - def test_shell_argv(self): - """Test argv decoding - - Python 2 does nothing with argv while Python 3 decodes it into - Unicode before we ever see it. We manually decode when running - under Python 2 so verify that we get the right argv types. - - Use the argv supplied by the test runner so we get actual Python - runtime behaviour; we only need to check the type of argv[0] - which will always be present. - """ - - with mock.patch( - self.shell_class_name + ".run", - self.app, - ): - # Ensure type gets through unmolested through shell.main() - argv = sys.argv - shell.main(sys.argv) - self.assertEqual(type(argv[0]), type(self.app.call_args[0][0][0])) - - # When shell.main() gets sys.argv itself it should be decoded - shell.main() - self.assertEqual(type(u'x'), type(self.app.call_args[0][0][0])) diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v1/fakes.py python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v1/fakes.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v1/fakes.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v1/fakes.py 2021-09-01 19:16:00.000000000 +0000 @@ -400,12 +400,12 @@ ) -class FakeType(object): +class FakeVolumeType(object): """Fake one or more type.""" @staticmethod - def create_one_type(attrs=None, methods=None): - """Create a fake type. + def create_one_volume_type(attrs=None, methods=None): + """Create a fake volume type. :param Dictionary attrs: A dictionary with all attributes @@ -418,7 +418,7 @@ methods = methods or {} # Set default attributes. - type_info = { + volume_type_info = { "id": 'type-id-' + uuid.uuid4().hex, "name": 'type-name-' + uuid.uuid4().hex, "description": 'type-description-' + uuid.uuid4().hex, @@ -427,16 +427,16 @@ } # Overwrite default attributes. - type_info.update(attrs) + volume_type_info.update(attrs) volume_type = fakes.FakeResource( - info=copy.deepcopy(type_info), + info=copy.deepcopy(volume_type_info), methods=methods, loaded=True) return volume_type @staticmethod - def create_types(attrs=None, count=2): + def create_volume_types(attrs=None, count=2): """Create multiple fake types. :param Dictionary attrs: @@ -448,19 +448,19 @@ """ volume_types = [] for i in range(0, count): - volume_type = FakeType.create_one_type(attrs) + volume_type = FakeVolumeType.create_one_volume_type(attrs) volume_types.append(volume_type) return volume_types @staticmethod - def get_types(types=None, count=2): + def get_volume_types(volume_types=None, count=2): """Get an iterable MagicMock object with a list of faked types. If types list is provided, then initialize the Mock object with the list. Otherwise create one. - :param List types: + :param List volume_types: A list of FakeResource objects faking types :param Integer count: The number of types to be faked @@ -468,14 +468,14 @@ An iterable Mock object with side_effect set to a list of faked types """ - if types is None: - types = FakeType.create_types(count) + if volume_types is None: + volume_types = FakeVolumeType.create_volume_types(count) - return mock.Mock(side_effect=types) + return mock.Mock(side_effect=volume_types) @staticmethod - def create_one_encryption_type(attrs=None): - """Create a fake encryption type. + def create_one_encryption_volume_type(attrs=None): + """Create a fake encryption volume type. :param Dictionary attrs: A dictionary with all attributes diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v1/test_qos_specs.py python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v1/test_qos_specs.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v1/test_qos_specs.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v1/test_qos_specs.py 2021-09-01 19:16:00.000000000 +0000 @@ -39,7 +39,7 @@ class TestQosAssociate(TestQos): - volume_type = volume_fakes.FakeType.create_one_type() + volume_type = volume_fakes.FakeVolumeType.create_one_volume_type() qos_spec = volume_fakes.FakeQos.create_one_qos() def setUp(self): @@ -109,7 +109,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_qos_create_with_consumer(self): arglist = [ @@ -129,7 +129,7 @@ {'consumer': self.new_qos_spec.consumer} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_qos_create_with_properties(self): arglist = [ @@ -155,7 +155,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) class TestQosDelete(TestQos): @@ -263,7 +263,7 @@ class TestQosDisassociate(TestQos): - volume_type = volume_fakes.FakeType.create_one_type() + volume_type = volume_fakes.FakeVolumeType.create_one_volume_type() qos_spec = volume_fakes.FakeQos.create_one_qos() def setUp(self): @@ -350,7 +350,7 @@ self.qos_mock.list.assert_called_with() self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_qos_list_no_association(self): self.qos_mock.reset_mock() @@ -377,7 +377,7 @@ format_columns.ListColumn(None), format_columns.DictColumn(self.qos_specs[1].specs), ) - self.assertItemsEqual(ex_data, list(data)) + self.assertCountEqual(ex_data, list(data)) class TestQosSet(TestQos): @@ -454,7 +454,7 @@ self.qos_spec.name, format_columns.DictColumn(self.qos_spec.specs), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) class TestQosUnset(TestQos): diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v1/test_type.py python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v1/test_type.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v1/test_type.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v1/test_type.py 2021-09-01 19:16:00.000000000 +0000 @@ -49,9 +49,9 @@ def setUp(self): super(TestTypeCreate, self).setUp() - self.new_volume_type = volume_fakes.FakeType.create_one_type( - methods={'set_keys': {'myprop': 'myvalue'}} - ) + self.new_volume_type = \ + volume_fakes.FakeVolumeType.create_one_volume_type( + methods={'set_keys': {'myprop': 'myvalue'}}) self.data = ( self.new_volume_type.description, self.new_volume_type.id, @@ -78,7 +78,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_type_create_with_encryption(self): encryption_info = { @@ -87,11 +87,12 @@ 'key_size': '128', 'control_location': 'front-end', } - encryption_type = volume_fakes.FakeType.create_one_encryption_type( - attrs=encryption_info - ) - self.new_volume_type = volume_fakes.FakeType.create_one_type( - attrs={'encryption': encryption_info}) + encryption_type = \ + volume_fakes.FakeVolumeType.create_one_encryption_volume_type( + attrs=encryption_info) + self.new_volume_type = \ + volume_fakes.FakeVolumeType.create_one_volume_type( + attrs={'encryption': encryption_info}) self.types_mock.create.return_value = self.new_volume_type self.encryption_types_mock.create.return_value = encryption_type encryption_columns = ( @@ -139,17 +140,17 @@ body, ) self.assertEqual(encryption_columns, columns) - self.assertItemsEqual(encryption_data, data) + self.assertCountEqual(encryption_data, data) class TestTypeDelete(TestType): - volume_types = volume_fakes.FakeType.create_types(count=2) + volume_types = volume_fakes.FakeVolumeType.create_volume_types(count=2) def setUp(self): super(TestTypeDelete, self).setUp() - self.types_mock.get = volume_fakes.FakeType.get_types( + self.types_mock.get = volume_fakes.FakeVolumeType.get_volume_types( self.volume_types) self.types_mock.delete.return_value = None @@ -220,7 +221,7 @@ class TestTypeList(TestType): - volume_types = volume_fakes.FakeType.create_types() + volume_types = volume_fakes.FakeVolumeType.create_volume_types() columns = [ "ID", @@ -270,7 +271,7 @@ columns, data = self.cmd.take_action(parsed_args) self.types_mock.list.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_type_list_with_options(self): arglist = [ @@ -284,11 +285,12 @@ columns, data = self.cmd.take_action(parsed_args) self.types_mock.list.assert_called_once_with() self.assertEqual(self.columns_long, columns) - self.assertItemsEqual(self.data_long, list(data)) + self.assertCountEqual(self.data_long, list(data)) def test_type_list_with_encryption(self): - encryption_type = volume_fakes.FakeType.create_one_encryption_type( - attrs={'volume_type_id': self.volume_types[0].id}) + encryption_type = \ + volume_fakes.FakeVolumeType.create_one_encryption_volume_type( + attrs={'volume_type_id': self.volume_types[0].id}) encryption_info = { 'provider': 'LuksEncryptor', 'cipher': None, @@ -328,12 +330,12 @@ self.encryption_types_mock.list.assert_called_once_with() self.types_mock.list.assert_called_once_with() self.assertEqual(encryption_columns, columns) - self.assertItemsEqual(encryption_data, list(data)) + self.assertCountEqual(encryption_data, list(data)) class TestTypeSet(TestType): - volume_type = volume_fakes.FakeType.create_one_type( + volume_type = volume_fakes.FakeVolumeType.create_one_volume_type( methods={'set_keys': None}) def setUp(self): @@ -441,7 +443,7 @@ def setUp(self): super(TestTypeShow, self).setUp() - self.volume_type = volume_fakes.FakeType.create_one_type() + self.volume_type = volume_fakes.FakeVolumeType.create_one_volume_type() self.data = ( self.volume_type.description, self.volume_type.id, @@ -469,17 +471,18 @@ self.types_mock.get.assert_called_with(self.volume_type.id) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_type_show_with_encryption(self): - encryption_type = volume_fakes.FakeType.create_one_encryption_type() + encryption_type = \ + volume_fakes.FakeVolumeType.create_one_encryption_volume_type() encryption_info = { 'provider': 'LuksEncryptor', 'cipher': None, 'key_size': None, 'control_location': 'front-end', } - self.volume_type = volume_fakes.FakeType.create_one_type( + self.volume_type = volume_fakes.FakeVolumeType.create_one_volume_type( attrs={'encryption': encryption_info}) self.types_mock.get.return_value = self.volume_type self.encryption_types_mock.get.return_value = encryption_type @@ -513,12 +516,12 @@ 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.assertItemsEqual(encryption_data, data) + self.assertCountEqual(encryption_data, data) class TestTypeUnset(TestType): - volume_type = volume_fakes.FakeType.create_one_type( + volume_type = volume_fakes.FakeVolumeType.create_one_volume_type( methods={'unset_keys': None}) def setUp(self): @@ -596,7 +599,7 @@ class TestColumns(TestType): def test_encryption_info_column_with_info(self): - fake_volume_type = volume_fakes.FakeType.create_one_type() + fake_volume_type = volume_fakes.FakeVolumeType.create_one_volume_type() type_id = fake_volume_type.id encryption_info = { @@ -612,7 +615,7 @@ self.assertEqual(encryption_info, col.machine_readable()) def test_encryption_info_column_without_info(self): - fake_volume_type = volume_fakes.FakeType.create_one_type() + fake_volume_type = volume_fakes.FakeVolumeType.create_one_volume_type() type_id = fake_volume_type.id col = volume_type.EncryptionInfoColumn(type_id, {}) diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v1/test_volume_backup.py python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v1/test_volume_backup.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v1/test_volume_backup.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v1/test_volume_backup.py 2021-09-01 19:16:00.000000000 +0000 @@ -100,7 +100,7 @@ self.new_backup.description, ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_backup_create_without_name(self): arglist = [ @@ -124,7 +124,7 @@ self.new_backup.description, ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) class TestBackupDelete(TestBackup): @@ -277,7 +277,7 @@ search_opts=search_opts, ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_backup_list_with_options(self): arglist = [ @@ -309,7 +309,7 @@ search_opts=search_opts, ) self.assertEqual(self.columns_long, columns) - self.assertItemsEqual(self.data_long, list(data)) + self.assertCountEqual(self.data_long, list(data)) class TestBackupRestore(TestBackup): @@ -391,4 +391,4 @@ self.backups_mock.get.assert_called_with(self.backup.id) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v1/test_volume.py python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v1/test_volume.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v1/test_volume.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v1/test_volume.py 2021-09-01 19:16:00.000000000 +0000 @@ -135,7 +135,7 @@ None, ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_volume_create_options(self): arglist = [ @@ -179,7 +179,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_volume_create_user_project_id(self): # Return a project @@ -226,7 +226,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_volume_create_user_project_name(self): # Return a project @@ -273,7 +273,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_volume_create_properties(self): arglist = [ @@ -314,7 +314,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_volume_create_image_id(self): image = image_fakes.FakeImage.create_one_image() @@ -357,7 +357,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_volume_create_image_name(self): image = image_fakes.FakeImage.create_one_image() @@ -400,7 +400,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_volume_create_with_source(self): self.volumes_mock.get.return_value = self.new_volume @@ -430,7 +430,7 @@ None, ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_volume_create_with_bootable_and_readonly(self): arglist = [ @@ -468,7 +468,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(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( @@ -510,7 +510,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(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( @@ -562,7 +562,7 @@ self.assertEqual(2, mock_error.call_count) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(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( @@ -765,7 +765,7 @@ columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, tuple(data)) + self.assertCountEqual(self.datalist, tuple(data)) def test_volume_list_name(self): arglist = [ @@ -782,7 +782,7 @@ columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, tuple(columns)) - self.assertItemsEqual(self.datalist, tuple(data)) + self.assertCountEqual(self.datalist, tuple(data)) def test_volume_list_status(self): arglist = [ @@ -799,7 +799,7 @@ columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, tuple(columns)) - self.assertItemsEqual(self.datalist, tuple(data)) + self.assertCountEqual(self.datalist, tuple(data)) def test_volume_list_all_projects(self): arglist = [ @@ -816,7 +816,7 @@ columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, tuple(columns)) - self.assertItemsEqual(self.datalist, tuple(data)) + self.assertCountEqual(self.datalist, tuple(data)) def test_volume_list_long(self): arglist = [ @@ -856,11 +856,12 @@ volume.AttachmentsColumn(self._volume.attachments), format_columns.DictColumn(self._volume.metadata), ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) - def test_volume_list_with_limit(self): + def test_volume_list_with_limit_and_offset(self): arglist = [ '--limit', '2', + '--offset', '5', ] verifylist = [ ('long', False), @@ -868,6 +869,7 @@ ('name', None), ('status', None), ('limit', 2), + ('offset', 5), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -876,12 +878,14 @@ self.volumes_mock.list.assert_called_once_with( limit=2, search_opts={ + 'offset': 5, 'status': None, 'display_name': None, - 'all_tenants': False, } + 'all_tenants': False, + }, ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, tuple(data)) + self.assertCountEqual(self.datalist, tuple(data)) def test_volume_list_negative_limit(self): arglist = [ @@ -1272,7 +1276,7 @@ self.volumes_mock.get.assert_called_with(self._volume.id) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_volume_show_backward_compatibility(self): arglist = [ diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v2/fakes.py python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v2/fakes.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v2/fakes.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v2/fakes.py 2021-09-01 19:16:00.000000000 +0000 @@ -17,6 +17,7 @@ from unittest import mock import uuid +from cinderclient import api_versions from osc_lib.cli import format_columns from openstackclient.tests.unit import fakes @@ -292,6 +293,8 @@ def __init__(self, **kwargs): self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] + self.api_version = api_versions.APIVersion('2.0') + self.availability_zones = mock.Mock() self.availability_zones.resource_class = fakes.FakeResource(None, {}) self.backups = mock.Mock() @@ -983,12 +986,12 @@ return mock.Mock(side_effect=snapshots) -class FakeType(object): - """Fake one or more type.""" +class FakeVolumeType(object): + """Fake one or more volume type.""" @staticmethod - def create_one_type(attrs=None, methods=None): - """Create a fake type. + def create_one_volume_type(attrs=None, methods=None): + """Create a fake volume type. :param Dictionary attrs: A dictionary with all attributes @@ -1001,7 +1004,7 @@ methods = methods or {} # Set default attributes. - type_info = { + volume_type_info = { "id": 'type-id-' + uuid.uuid4().hex, "name": 'type-name-' + uuid.uuid4().hex, "description": 'type-description-' + uuid.uuid4().hex, @@ -1010,17 +1013,17 @@ } # Overwrite default attributes. - type_info.update(attrs) + volume_type_info.update(attrs) volume_type = fakes.FakeResource( - info=copy.deepcopy(type_info), + info=copy.deepcopy(volume_type_info), methods=methods, loaded=True) return volume_type @staticmethod - def create_types(attrs=None, count=2): - """Create multiple fake types. + def create_volume_types(attrs=None, count=2): + """Create multiple fake volume_types. :param Dictionary attrs: A dictionary with all attributes @@ -1031,34 +1034,34 @@ """ volume_types = [] for i in range(0, count): - volume_type = FakeType.create_one_type(attrs) + volume_type = FakeVolumeType.create_one_volume_type(attrs) volume_types.append(volume_type) return volume_types @staticmethod - def get_types(types=None, count=2): - """Get an iterable MagicMock object with a list of faked types. + def get_volume_types(volume_types=None, count=2): + """Get an iterable MagicMock object with a list of faked volume types. - If types list is provided, then initialize the Mock object with the - list. Otherwise create one. + If volume_types list is provided, then initialize the Mock object with + the list. Otherwise create one. - :param List types: - A list of FakeResource objects faking types + :param List volume_types: + A list of FakeResource objects faking volume types :param Integer count: - The number of types to be faked + The number of volume types to be faked :return An iterable Mock object with side_effect set to a list of faked - types + volume types """ - if types is None: - types = FakeType.create_types(count) + if volume_types is None: + volume_types = FakeVolumeType.create_volume_types(count) - return mock.Mock(side_effect=types) + return mock.Mock(side_effect=volume_types) @staticmethod - def create_one_encryption_type(attrs=None): - """Create a fake encryption type. + def create_one_encryption_volume_type(attrs=None): + """Create a fake encryption volume type. :param Dictionary attrs: A dictionary with all attributes diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v2/test_consistency_group.py python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v2/test_consistency_group.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v2/test_consistency_group.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v2/test_consistency_group.py 2021-09-01 19:16:00.000000000 +0000 @@ -148,7 +148,7 @@ class TestConsistencyGroupCreate(TestConsistencyGroup): - volume_type = volume_fakes.FakeType.create_one_type() + volume_type = volume_fakes.FakeVolumeType.create_one_volume_type() new_consistency_group = ( volume_fakes.FakeConsistencyGroup.create_one_consistency_group()) consistency_group_snapshot = ( @@ -251,7 +251,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_consistency_group_create_from_source(self): arglist = [ @@ -279,7 +279,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_consistency_group_create_from_snapshot(self): arglist = [ @@ -307,7 +307,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) class TestConsistencyGroupDelete(TestConsistencyGroup): @@ -463,7 +463,7 @@ self.consistencygroups_mock.list.assert_called_once_with( detailed=True, search_opts={'all_tenants': False}) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_consistency_group_list_with_all_project(self): arglist = [ @@ -480,7 +480,7 @@ self.consistencygroups_mock.list.assert_called_once_with( detailed=True, search_opts={'all_tenants': True}) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_consistency_group_list_with_long(self): arglist = [ @@ -497,7 +497,7 @@ self.consistencygroups_mock.list.assert_called_once_with( detailed=True, search_opts={'all_tenants': False}) self.assertEqual(self.columns_long, columns) - self.assertItemsEqual(self.data_long, list(data)) + self.assertCountEqual(self.data_long, list(data)) class TestConsistencyGroupRemoveVolume(TestConsistencyGroup): @@ -705,4 +705,4 @@ self.consistencygroups_mock.get.assert_called_once_with( self.consistency_group.id) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v2/test_qos_specs.py python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v2/test_qos_specs.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v2/test_qos_specs.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v2/test_qos_specs.py 2021-09-01 19:16:00.000000000 +0000 @@ -39,7 +39,7 @@ class TestQosAssociate(TestQos): - volume_type = volume_fakes.FakeType.create_one_type() + volume_type = volume_fakes.FakeVolumeType.create_one_volume_type() qos_spec = volume_fakes.FakeQos.create_one_qos() def setUp(self): @@ -112,7 +112,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_qos_create_with_consumer(self): arglist = [ @@ -133,7 +133,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_qos_create_with_properties(self): arglist = [ @@ -159,7 +159,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) class TestQosDelete(TestQos): @@ -255,7 +255,7 @@ class TestQosDisassociate(TestQos): - volume_type = volume_fakes.FakeType.create_one_type() + volume_type = volume_fakes.FakeVolumeType.create_one_volume_type() qos_spec = volume_fakes.FakeQos.create_one_qos() def setUp(self): @@ -342,7 +342,7 @@ self.qos_mock.list.assert_called_with() self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_qos_list_no_association(self): self.qos_mock.reset_mock() @@ -369,7 +369,7 @@ format_columns.ListColumn(None), format_columns.DictColumn(self.qos_specs[1].specs), ) - self.assertItemsEqual(ex_data, list(data)) + self.assertCountEqual(ex_data, list(data)) class TestQosSet(TestQos): @@ -449,7 +449,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, tuple(data)) + self.assertCountEqual(self.data, tuple(data)) class TestQosUnset(TestQos): diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v2/test_transfer_request.py python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v2/test_transfer_request.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v2/test_transfer_request.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v2/test_transfer_request.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,384 +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. -# - -from unittest import mock -from unittest.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 - - -class TestTransfer(transfer_fakes.TestVolume): - - def setUp(self): - super(TestTransfer, self).setUp() - - # Get a shortcut to the TransferManager Mock - self.transfer_mock = self.app.client_manager.volume.transfers - self.transfer_mock.reset_mock() - - # Get a shortcut to the VolumeManager Mock - self.volumes_mock = self.app.client_manager.volume.volumes - self.volumes_mock.reset_mock() - - -class TestTransferAccept(TestTransfer): - - columns = ( - 'id', - 'name', - 'volume_id', - ) - - def setUp(self): - super(TestTransferAccept, self).setUp() - - self.volume_transfer = ( - transfer_fakes.FakeTransfer.create_one_transfer()) - self.data = ( - self.volume_transfer.id, - self.volume_transfer.name, - self.volume_transfer.volume_id, - ) - - self.transfer_mock.get.return_value = self.volume_transfer - self.transfer_mock.accept.return_value = self.volume_transfer - - # Get the command object to test - self.cmd = volume_transfer_request.AcceptTransferRequest( - self.app, None) - - def test_transfer_accept(self): - arglist = [ - '--auth-key', 'key_value', - self.volume_transfer.id, - ] - verifylist = [ - ('transfer_request', self.volume_transfer.id), - ('auth_key', 'key_value'), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.transfer_mock.get.assert_called_once_with( - self.volume_transfer.id, - ) - 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, - ] - verifylist = [ - ('transfer_request', self.volume_transfer.id), - ] - - self.assertRaises( - test_utils.ParserException, - self.check_parser, - self.cmd, - arglist, - verifylist, - ) - - -class TestTransferCreate(TestTransfer): - - volume = transfer_fakes.FakeVolume.create_one_volume() - - columns = ( - 'auth_key', - 'created_at', - 'id', - 'name', - 'volume_id', - ) - - def setUp(self): - super(TestTransferCreate, self).setUp() - - self.volume_transfer = transfer_fakes.FakeTransfer.create_one_transfer( - attrs={'volume_id': self.volume.id, - 'auth_key': 'key', - 'created_at': 'time'} - ) - self.data = ( - self.volume_transfer.auth_key, - self.volume_transfer.created_at, - self.volume_transfer.id, - self.volume_transfer.name, - self.volume_transfer.volume_id, - ) - - self.transfer_mock.create.return_value = self.volume_transfer - self.volumes_mock.get.return_value = self.volume - - # Get the command object to test - self.cmd = volume_transfer_request.CreateTransferRequest( - self.app, None) - - def test_transfer_create_without_name(self): - arglist = [ - self.volume.id, - ] - verifylist = [ - ('volume', self.volume.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.transfer_mock.create.assert_called_once_with( - self.volume.id, None) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) - - def test_transfer_create_with_name(self): - arglist = [ - '--name', self.volume_transfer.name, - self.volume.id, - ] - verifylist = [ - ('name', self.volume_transfer.name), - ('volume', self.volume.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.transfer_mock.create.assert_called_once_with( - self.volume.id, self.volume_transfer.name,) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) - - -class TestTransferDelete(TestTransfer): - - volume_transfers = transfer_fakes.FakeTransfer.create_transfers(count=2) - - def setUp(self): - super(TestTransferDelete, self).setUp() - - self.transfer_mock.get = ( - transfer_fakes.FakeTransfer.get_transfers(self.volume_transfers)) - self.transfer_mock.delete.return_value = None - - # Get the command object to mock - self.cmd = volume_transfer_request.DeleteTransferRequest( - self.app, None) - - def test_transfer_delete(self): - arglist = [ - self.volume_transfers[0].id - ] - verifylist = [ - ("transfer_request", [self.volume_transfers[0].id]) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.transfer_mock.delete.assert_called_with( - self.volume_transfers[0].id) - self.assertIsNone(result) - - def test_delete_multiple_transfers(self): - arglist = [] - for v in self.volume_transfers: - arglist.append(v.id) - verifylist = [ - ('transfer_request', arglist), - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) - - calls = [] - for v in self.volume_transfers: - calls.append(call(v.id)) - self.transfer_mock.delete.assert_has_calls(calls) - self.assertIsNone(result) - - def test_delete_multiple_transfers_with_exception(self): - arglist = [ - self.volume_transfers[0].id, - 'unexist_transfer', - ] - verifylist = [ - ('transfer_request', arglist), - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - find_mock_result = [self.volume_transfers[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 volume transfer requests failed ' - 'to delete', str(e)) - - find_mock.assert_any_call( - self.transfer_mock, self.volume_transfers[0].id) - find_mock.assert_any_call(self.transfer_mock, 'unexist_transfer') - - self.assertEqual(2, find_mock.call_count) - self.transfer_mock.delete.assert_called_once_with( - self.volume_transfers[0].id, - ) - - -class TestTransferList(TestTransfer): - - # The Transfers to be listed - volume_transfers = transfer_fakes.FakeTransfer.create_one_transfer() - - def setUp(self): - super(TestTransferList, self).setUp() - - self.transfer_mock.list.return_value = [self.volume_transfers] - - # Get the command object to test - self.cmd = volume_transfer_request.ListTransferRequest(self.app, None) - - def test_transfer_list_without_argument(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) - - expected_columns = [ - 'ID', - 'Name', - 'Volume', - ] - - # confirming if all expected columns are present in the result. - self.assertEqual(expected_columns, columns) - - datalist = (( - self.volume_transfers.id, - self.volume_transfers.name, - self.volume_transfers.volume_id, - ), ) - - # confirming if all expected values are present in the result. - self.assertEqual(datalist, tuple(data)) - - # checking if proper call was made to list volume_transfers - self.transfer_mock.list.assert_called_with( - detailed=True, - search_opts={'all_tenants': 0} - ) - - def test_transfer_list_with_argument(self): - arglist = [ - "--all-projects" - ] - verifylist = [ - ("all_projects", 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) - - expected_columns = [ - 'ID', - 'Name', - 'Volume', - ] - - # confirming if all expected columns are present in the result. - self.assertEqual(expected_columns, columns) - - datalist = (( - self.volume_transfers.id, - self.volume_transfers.name, - self.volume_transfers.volume_id, - ), ) - - # confirming if all expected values are present in the result. - self.assertEqual(datalist, tuple(data)) - - # checking if proper call was made to list volume_transfers - self.transfer_mock.list.assert_called_with( - detailed=True, - search_opts={'all_tenants': 1} - ) - - -class TestTransferShow(TestTransfer): - - columns = ( - 'created_at', - 'id', - 'name', - 'volume_id', - ) - - def setUp(self): - super(TestTransferShow, self).setUp() - - self.volume_transfer = ( - transfer_fakes.FakeTransfer.create_one_transfer( - attrs={'created_at': 'time'}) - ) - self.data = ( - self.volume_transfer.created_at, - self.volume_transfer.id, - self.volume_transfer.name, - self.volume_transfer.volume_id, - ) - - self.transfer_mock.get.return_value = self.volume_transfer - - # Get the command object to test - self.cmd = volume_transfer_request.ShowTransferRequest( - self.app, None) - - def test_transfer_show(self): - arglist = [ - self.volume_transfer.id, - ] - verifylist = [ - ('transfer_request', self.volume_transfer.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.transfer_mock.get.assert_called_once_with( - self.volume_transfer.id) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v2/test_type.py python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v2/test_type.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v2/test_type.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v2/test_type.py 2021-09-01 19:16:00.000000000 +0000 @@ -58,7 +58,8 @@ def setUp(self): super(TestTypeCreate, self).setUp() - self.new_volume_type = volume_fakes.FakeType.create_one_type() + self.new_volume_type = \ + volume_fakes.FakeVolumeType.create_one_volume_type() self.data = ( self.new_volume_type.description, self.new_volume_type.id, @@ -93,7 +94,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_type_create_private(self): arglist = [ @@ -119,7 +120,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_public_type_create_with_project(self): arglist = [ @@ -143,11 +144,12 @@ 'key_size': '128', 'control_location': 'front-end', } - encryption_type = volume_fakes.FakeType.create_one_encryption_type( - attrs=encryption_info - ) - self.new_volume_type = volume_fakes.FakeType.create_one_type( - attrs={'encryption': encryption_info}) + encryption_type = \ + volume_fakes.FakeVolumeType.create_one_encryption_volume_type( + attrs=encryption_info) + self.new_volume_type = \ + volume_fakes.FakeVolumeType.create_one_volume_type( + attrs={'encryption': encryption_info}) self.types_mock.create.return_value = self.new_volume_type self.encryption_types_mock.create.return_value = encryption_type encryption_columns = ( @@ -196,17 +198,17 @@ body, ) self.assertEqual(encryption_columns, columns) - self.assertItemsEqual(encryption_data, data) + self.assertCountEqual(encryption_data, data) class TestTypeDelete(TestType): - volume_types = volume_fakes.FakeType.create_types(count=2) + volume_types = volume_fakes.FakeVolumeType.create_volume_types(count=2) def setUp(self): super(TestTypeDelete, self).setUp() - self.types_mock.get = volume_fakes.FakeType.get_types( + self.types_mock.get = volume_fakes.FakeVolumeType.get_volume_types( self.volume_types) self.types_mock.delete.return_value = None @@ -276,7 +278,7 @@ class TestTypeList(TestType): - volume_types = volume_fakes.FakeType.create_types() + volume_types = volume_fakes.FakeVolumeType.create_volume_types() columns = [ "ID", @@ -330,7 +332,7 @@ 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.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_type_list_with_options(self): arglist = [ @@ -348,7 +350,7 @@ 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.assertItemsEqual(self.data_long, list(data)) + self.assertCountEqual(self.data_long, list(data)) def test_type_list_with_private_option(self): arglist = [ @@ -365,7 +367,7 @@ 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.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_type_list_with_default_option(self): arglist = [ @@ -383,11 +385,12 @@ columns, data = self.cmd.take_action(parsed_args) self.types_mock.default.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data_with_default_type, list(data)) + self.assertCountEqual(self.data_with_default_type, list(data)) def test_type_list_with_encryption(self): - encryption_type = volume_fakes.FakeType.create_one_encryption_type( - attrs={'volume_type_id': self.volume_types[0].id}) + encryption_type = \ + volume_fakes.FakeVolumeType.create_one_encryption_volume_type( + attrs={'volume_type_id': self.volume_types[0].id}) encryption_info = { 'provider': 'LuksEncryptor', 'cipher': None, @@ -427,13 +430,13 @@ 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.assertItemsEqual(encryption_data, list(data)) + self.assertCountEqual(encryption_data, list(data)) class TestTypeSet(TestType): project = identity_fakes.FakeProject.create_one_project() - volume_type = volume_fakes.FakeType.create_one_type( + volume_type = volume_fakes.FakeVolumeType.create_one_volume_type( methods={'set_keys': None}) def setUp(self): @@ -684,7 +687,7 @@ def setUp(self): super(TestTypeShow, self).setUp() - self.volume_type = volume_fakes.FakeType.create_one_type() + self.volume_type = volume_fakes.FakeVolumeType.create_one_volume_type() self.data = ( None, self.volume_type.description, @@ -713,7 +716,7 @@ self.types_mock.get.assert_called_with(self.volume_type.id) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_type_show_with_access(self): arglist = [ @@ -724,7 +727,7 @@ ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - private_type = volume_fakes.FakeType.create_one_type( + private_type = volume_fakes.FakeVolumeType.create_one_volume_type( attrs={'is_public': False}) type_access_list = volume_fakes.FakeTypeAccess.create_one_type_access() with mock.patch.object(self.types_mock, 'get', @@ -746,7 +749,7 @@ private_type.name, format_columns.DictColumn(private_type.extra_specs) ) - self.assertItemsEqual(private_type_data, data) + self.assertCountEqual(private_type_data, data) def test_type_show_with_list_access_exec(self): arglist = [ @@ -757,7 +760,7 @@ ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - private_type = volume_fakes.FakeType.create_one_type( + private_type = volume_fakes.FakeVolumeType.create_one_volume_type( attrs={'is_public': False}) with mock.patch.object(self.types_mock, 'get', return_value=private_type): @@ -778,17 +781,18 @@ private_type.name, format_columns.DictColumn(private_type.extra_specs) ) - self.assertItemsEqual(private_type_data, data) + self.assertCountEqual(private_type_data, data) def test_type_show_with_encryption(self): - encryption_type = volume_fakes.FakeType.create_one_encryption_type() + encryption_type = \ + volume_fakes.FakeVolumeType.create_one_encryption_volume_type() encryption_info = { 'provider': 'LuksEncryptor', 'cipher': None, 'key_size': None, 'control_location': 'front-end', } - self.volume_type = volume_fakes.FakeType.create_one_type( + self.volume_type = volume_fakes.FakeVolumeType.create_one_volume_type( attrs={'encryption': encryption_info}) self.types_mock.get.return_value = self.volume_type self.encryption_types_mock.get.return_value = encryption_type @@ -824,13 +828,13 @@ 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.assertItemsEqual(encryption_data, data) + self.assertCountEqual(encryption_data, data) class TestTypeUnset(TestType): project = identity_fakes.FakeProject.create_one_project() - volume_type = volume_fakes.FakeType.create_one_type( + volume_type = volume_fakes.FakeVolumeType.create_one_volume_type( methods={'unset_keys': None}) def setUp(self): @@ -932,7 +936,7 @@ class TestColumns(TestType): def test_encryption_info_column_with_info(self): - fake_volume_type = volume_fakes.FakeType.create_one_type() + fake_volume_type = volume_fakes.FakeVolumeType.create_one_volume_type() type_id = fake_volume_type.id encryption_info = { @@ -948,7 +952,7 @@ self.assertEqual(encryption_info, col.machine_readable()) def test_encryption_info_column_without_info(self): - fake_volume_type = volume_fakes.FakeType.create_one_type() + fake_volume_type = volume_fakes.FakeVolumeType.create_one_volume_type() type_id = fake_volume_type.id col = volume_type.EncryptionInfoColumn(type_id, {}) diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v2/test_volume_backup.py python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v2/test_volume_backup.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v2/test_volume_backup.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v2/test_volume_backup.py 2021-09-01 19:16:00.000000000 +0000 @@ -15,6 +15,7 @@ from unittest import mock from unittest.mock import call +from cinderclient import api_versions from osc_lib import exceptions from osc_lib import utils @@ -114,6 +115,104 @@ self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_backup_create_with_properties(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.43') + + arglist = [ + "--property", "foo=bar", + "--property", "wow=much-cool", + self.new_backup.volume_id, + ] + verifylist = [ + ("properties", {"foo": "bar", "wow": "much-cool"}), + ("volume", self.new_backup.volume_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.backups_mock.create.assert_called_with( + self.new_backup.volume_id, + container=None, + name=None, + description=None, + force=False, + incremental=False, + metadata={"foo": "bar", "wow": "much-cool"}, + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_backup_create_with_properties_pre_v343(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.42') + + arglist = [ + "--property", "foo=bar", + "--property", "wow=much-cool", + self.new_backup.volume_id, + ] + verifylist = [ + ("properties", {"foo": "bar", "wow": "much-cool"}), + ("volume", self.new_backup.volume_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn("--os-volume-api-version 3.43 or greater", str(exc)) + + def test_backup_create_with_availability_zone(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.51') + + arglist = [ + "--availability-zone", "my-az", + self.new_backup.volume_id, + ] + verifylist = [ + ("availability_zone", "my-az"), + ("volume", self.new_backup.volume_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.backups_mock.create.assert_called_with( + self.new_backup.volume_id, + container=None, + name=None, + description=None, + force=False, + incremental=False, + availability_zone="my-az", + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_backup_create_with_availability_zone_pre_v351(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.50') + + arglist = [ + "--availability-zone", "my-az", + self.new_backup.volume_id, + ] + verifylist = [ + ("availability_zone", "my-az"), + ("volume", self.new_backup.volume_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn("--os-volume-api-version 3.51 or greater", str(exc)) + def test_backup_create_without_name(self): arglist = [ "--description", self.new_backup.description, @@ -136,7 +235,6 @@ description=self.new_backup.description, force=False, incremental=False, - snapshot_id=None, ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -240,18 +338,18 @@ backups = volume_fakes.FakeBackup.create_backups( attrs={'volume_id': volume.name}, count=3) - columns = [ + columns = ( 'ID', 'Name', 'Description', 'Status', 'Size', - ] - columns_long = columns + [ + ) + columns_long = columns + ( 'Availability Zone', 'Volume', 'Container', - ] + ) data = [] for b in backups: @@ -314,7 +412,7 @@ limit=None, ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_backup_list_with_options(self): arglist = [ @@ -353,7 +451,7 @@ limit=3, ) self.assertEqual(self.columns_long, columns) - self.assertItemsEqual(self.data_long, list(data)) + self.assertCountEqual(self.data_long, list(data)) class TestBackupRestore(TestBackup): @@ -392,7 +490,9 @@ class TestBackupSet(TestBackup): - backup = volume_fakes.FakeBackup.create_one_backup() + backup = volume_fakes.FakeBackup.create_one_backup( + attrs={'metadata': {'wow': 'cool'}}, + ) def setUp(self): super(TestBackupSet, self).setUp() @@ -403,6 +503,9 @@ self.cmd = volume_backup.SetVolumeBackup(self.app, None) def test_backup_set_name(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.9') + arglist = [ '--name', 'new_name', self.backup.id, @@ -420,7 +523,30 @@ self.backup.id, **{'name': 'new_name'}) self.assertIsNone(result) + def test_backup_set_name_pre_v39(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.8') + + arglist = [ + '--name', 'new_name', + self.backup.id, + ] + verifylist = [ + ('name', 'new_name'), + ('backup', self.backup.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn("--os-volume-api-version 3.9 or greater", str(exc)) + def test_backup_set_description(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.9') + arglist = [ '--description', 'new_description', self.backup.id, @@ -444,6 +570,27 @@ ) self.assertIsNone(result) + def test_backup_set_description_pre_v39(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.8') + + arglist = [ + '--description', 'new_description', + self.backup.id, + ] + verifylist = [ + ('name', None), + ('description', 'new_description'), + ('backup', self.backup.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn("--os-volume-api-version 3.9 or greater", str(exc)) + def test_backup_set_state(self): arglist = [ '--state', 'error', @@ -482,6 +629,159 @@ self.backups_mock.reset_state.assert_called_with( self.backup.id, 'error') + def test_backup_set_no_property(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.43') + + arglist = [ + '--no-property', + self.backup.id, + ] + verifylist = [ + ('no_property', True), + ('backup', self.backup.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'metadata': {}, + } + self.backups_mock.update.assert_called_once_with( + self.backup.id, + **kwargs + ) + self.assertIsNone(result) + + def test_backup_set_no_property_pre_v343(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.42') + + arglist = [ + '--no-property', + self.backup.id, + ] + verifylist = [ + ('no_property', True), + ('backup', self.backup.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn("--os-volume-api-version 3.43 or greater", str(exc)) + + def test_backup_set_property(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.43') + + arglist = [ + '--property', 'foo=bar', + self.backup.id, + ] + verifylist = [ + ('properties', {'foo': 'bar'}), + ('backup', self.backup.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'metadata': {'wow': 'cool', 'foo': 'bar'}, + } + self.backups_mock.update.assert_called_once_with( + self.backup.id, + **kwargs + ) + self.assertIsNone(result) + + def test_backup_set_property_pre_v343(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.42') + + arglist = [ + '--property', 'foo=bar', + self.backup.id, + ] + verifylist = [ + ('properties', {'foo': 'bar'}), + ('backup', self.backup.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn("--os-volume-api-version 3.43 or greater", str(exc)) + + +class TestBackupUnset(TestBackup): + + backup = volume_fakes.FakeBackup.create_one_backup( + attrs={'metadata': {'foo': 'bar'}}, + ) + + def setUp(self): + super().setUp() + + self.backups_mock.get.return_value = self.backup + + # Get the command object to test + self.cmd = volume_backup.UnsetVolumeBackup(self.app, None) + + def test_backup_unset_property(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.43') + + arglist = [ + '--property', 'foo', + self.backup.id, + ] + verifylist = [ + ('properties', ['foo']), + ('backup', self.backup.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'metadata': {}, + } + self.backups_mock.update.assert_called_once_with( + self.backup.id, + **kwargs + ) + self.assertIsNone(result) + + def test_backup_unset_property_pre_v343(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.42') + + arglist = [ + '--property', 'foo', + self.backup.id, + ] + verifylist = [ + ('properties', ['foo']), + ('backup', self.backup.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn("--os-volume-api-version 3.43 or greater", str(exc)) + class TestBackupShow(TestBackup): diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v2/test_volume.py python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v2/test_volume.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v2/test_volume.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v2/test_volume.py 2021-09-01 19:16:00.000000000 +0000 @@ -136,7 +136,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_volume_create_options(self): consistency_group = ( @@ -182,7 +182,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_volume_create_properties(self): arglist = [ @@ -218,7 +218,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_volume_create_image_id(self): image = image_fakes.FakeImage.create_one_image() @@ -256,7 +256,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_volume_create_image_name(self): image = image_fakes.FakeImage.create_one_image() @@ -294,7 +294,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_volume_create_with_snapshot(self): snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() @@ -331,7 +331,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_volume_create_with_bootable_and_readonly(self): arglist = [ @@ -369,7 +369,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(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( @@ -411,7 +411,7 @@ ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(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( @@ -463,7 +463,7 @@ self.assertEqual(2, mock_error.call_count) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(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( @@ -680,7 +680,7 @@ self.mock_volume.size, volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) def test_volume_list_project(self): arglist = [ @@ -720,7 +720,7 @@ self.mock_volume.size, volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) def test_volume_list_project_domain(self): arglist = [ @@ -762,7 +762,7 @@ self.mock_volume.size, volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) def test_volume_list_user(self): arglist = [ @@ -801,7 +801,7 @@ self.mock_volume.size, volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) def test_volume_list_user_domain(self): arglist = [ @@ -843,7 +843,7 @@ self.mock_volume.size, volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) def test_volume_list_name(self): arglist = [ @@ -883,7 +883,7 @@ self.mock_volume.size, volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) def test_volume_list_status(self): arglist = [ @@ -923,7 +923,7 @@ self.mock_volume.size, volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) def test_volume_list_all_projects(self): arglist = [ @@ -963,7 +963,7 @@ self.mock_volume.size, volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) def test_volume_list_long(self): arglist = [ @@ -1017,7 +1017,7 @@ volume.AttachmentsColumn(self.mock_volume.attachments), format_columns.DictColumn(self.mock_volume.metadata), ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) def test_volume_list_with_marker_and_limit(self): arglist = [ @@ -1056,7 +1056,7 @@ 'name': None, 'all_tenants': False, } ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) def test_volume_list_negative_limit(self): arglist = [ @@ -1173,7 +1173,7 @@ class TestVolumeSet(TestVolume): - volume_type = volume_fakes.FakeType.create_one_type() + volume_type = volume_fakes.FakeVolumeType.create_one_volume_type() def setUp(self): super(TestVolumeSet, self).setUp() @@ -1450,7 +1450,7 @@ volume_fakes.FakeVolume.get_volume_columns(self._volume), columns) - self.assertItemsEqual( + self.assertCountEqual( volume_fakes.FakeVolume.get_volume_data(self._volume), data) diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v2/test_volume_snapshot.py python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v2/test_volume_snapshot.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v2/test_volume_snapshot.py 1970-01-01 00:00:00.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v2/test_volume_snapshot.py 2021-09-01 19:16:00.000000000 +0000 @@ -0,0 +1,742 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# 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 +from unittest import mock + +from osc_lib.cli import format_columns +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 TestVolumeSnapshot(volume_fakes.TestVolume): + + def setUp(self): + super().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 TestVolumeSnapshotCreate(TestVolumeSnapshot): + + columns = ( + 'created_at', + 'description', + 'id', + 'name', + 'properties', + 'size', + 'status', + 'volume_id', + ) + + def setUp(self): + super().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, + format_columns.DictColumn(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 TestVolumeSnapshotDelete(TestVolumeSnapshot): + + snapshots = volume_fakes.FakeSnapshot.create_snapshots(count=2) + + def setUp(self): + super().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(mock.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 TestVolumeSnapshotList(TestVolumeSnapshot): + + 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, + volume_snapshot.VolumeIdColumn( + s.volume_id, volume_cache={volume.id: volume}), + format_columns.DictColumn(s.metadata), + )) + + def setUp(self): + super().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 TestVolumeSnapshotSet(TestVolumeSnapshot): + + snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() + + def setUp(self): + super().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 TestVolumeSnapshotShow(TestVolumeSnapshot): + + columns = ( + 'created_at', + 'description', + 'id', + 'name', + 'properties', + 'size', + 'status', + 'volume_id', + ) + + def setUp(self): + super().setUp() + + self.snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() + + self.data = ( + self.snapshot.created_at, + self.snapshot.description, + self.snapshot.id, + self.snapshot.name, + format_columns.DictColumn(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.assertCountEqual(self.data, data) + + +class TestVolumeSnapshotUnset(TestVolumeSnapshot): + + snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() + + def setUp(self): + super().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 -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v2/test_volume_transfer_request.py python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v2/test_volume_transfer_request.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v2/test_volume_transfer_request.py 1970-01-01 00:00:00.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v2/test_volume_transfer_request.py 2021-09-01 19:16:00.000000000 +0000 @@ -0,0 +1,430 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +from unittest import mock +from unittest.mock import call + +from cinderclient import api_versions +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 + + +class TestTransfer(transfer_fakes.TestVolume): + + def setUp(self): + super(TestTransfer, self).setUp() + + # Get a shortcut to the TransferManager Mock + self.transfer_mock = self.app.client_manager.volume.transfers + self.transfer_mock.reset_mock() + + # Get a shortcut to the VolumeManager Mock + self.volumes_mock = self.app.client_manager.volume.volumes + self.volumes_mock.reset_mock() + + +class TestTransferAccept(TestTransfer): + + columns = ( + 'id', + 'name', + 'volume_id', + ) + + def setUp(self): + super(TestTransferAccept, self).setUp() + + self.volume_transfer = ( + transfer_fakes.FakeTransfer.create_one_transfer()) + self.data = ( + self.volume_transfer.id, + self.volume_transfer.name, + self.volume_transfer.volume_id, + ) + + self.transfer_mock.get.return_value = self.volume_transfer + self.transfer_mock.accept.return_value = self.volume_transfer + + # Get the command object to test + self.cmd = volume_transfer_request.AcceptTransferRequest( + self.app, None) + + def test_transfer_accept(self): + arglist = [ + '--auth-key', 'key_value', + self.volume_transfer.id, + ] + verifylist = [ + ('transfer_request', self.volume_transfer.id), + ('auth_key', 'key_value'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.transfer_mock.get.assert_called_once_with( + self.volume_transfer.id, + ) + 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, + ] + verifylist = [ + ('transfer_request', self.volume_transfer.id), + ] + + self.assertRaises( + test_utils.ParserException, + self.check_parser, + self.cmd, + arglist, + verifylist, + ) + + +class TestTransferCreate(TestTransfer): + + volume = transfer_fakes.FakeVolume.create_one_volume() + + columns = ( + 'auth_key', + 'created_at', + 'id', + 'name', + 'volume_id', + ) + + def setUp(self): + super(TestTransferCreate, self).setUp() + + self.volume_transfer = transfer_fakes.FakeTransfer.create_one_transfer( + attrs={'volume_id': self.volume.id, + 'auth_key': 'key', + 'created_at': 'time'} + ) + self.data = ( + self.volume_transfer.auth_key, + self.volume_transfer.created_at, + self.volume_transfer.id, + self.volume_transfer.name, + self.volume_transfer.volume_id, + ) + + self.transfer_mock.create.return_value = self.volume_transfer + self.volumes_mock.get.return_value = self.volume + + # Get the command object to test + self.cmd = volume_transfer_request.CreateTransferRequest( + self.app, None) + + def test_transfer_create_without_name(self): + arglist = [ + self.volume.id, + ] + verifylist = [ + ('volume', self.volume.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.transfer_mock.create.assert_called_once_with( + self.volume.id, None) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_transfer_create_with_name(self): + arglist = [ + '--name', self.volume_transfer.name, + self.volume.id, + ] + verifylist = [ + ('name', self.volume_transfer.name), + ('volume', self.volume.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.transfer_mock.create.assert_called_once_with( + self.volume.id, self.volume_transfer.name,) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_transfer_create_with_no_snapshots(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.55') + + arglist = [ + '--no-snapshots', + self.volume.id, + ] + verifylist = [ + ('name', None), + ('snapshots', False), + ('volume', self.volume.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.transfer_mock.create.assert_called_once_with( + self.volume.id, None, no_snapshots=True) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_transfer_create_pre_v355(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.54') + + arglist = [ + '--no-snapshots', + self.volume.id, + ] + verifylist = [ + ('name', None), + ('snapshots', False), + ('volume', self.volume.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.55 or greater is required', + str(exc)) + + +class TestTransferDelete(TestTransfer): + + volume_transfers = transfer_fakes.FakeTransfer.create_transfers(count=2) + + def setUp(self): + super(TestTransferDelete, self).setUp() + + self.transfer_mock.get = ( + transfer_fakes.FakeTransfer.get_transfers(self.volume_transfers)) + self.transfer_mock.delete.return_value = None + + # Get the command object to mock + self.cmd = volume_transfer_request.DeleteTransferRequest( + self.app, None) + + def test_transfer_delete(self): + arglist = [ + self.volume_transfers[0].id + ] + verifylist = [ + ("transfer_request", [self.volume_transfers[0].id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.transfer_mock.delete.assert_called_with( + self.volume_transfers[0].id) + self.assertIsNone(result) + + def test_delete_multiple_transfers(self): + arglist = [] + for v in self.volume_transfers: + arglist.append(v.id) + verifylist = [ + ('transfer_request', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for v in self.volume_transfers: + calls.append(call(v.id)) + self.transfer_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_delete_multiple_transfers_with_exception(self): + arglist = [ + self.volume_transfers[0].id, + 'unexist_transfer', + ] + verifylist = [ + ('transfer_request', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.volume_transfers[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 volume transfer requests failed ' + 'to delete', str(e)) + + find_mock.assert_any_call( + self.transfer_mock, self.volume_transfers[0].id) + find_mock.assert_any_call(self.transfer_mock, 'unexist_transfer') + + self.assertEqual(2, find_mock.call_count) + self.transfer_mock.delete.assert_called_once_with( + self.volume_transfers[0].id, + ) + + +class TestTransferList(TestTransfer): + + # The Transfers to be listed + volume_transfers = transfer_fakes.FakeTransfer.create_one_transfer() + + def setUp(self): + super(TestTransferList, self).setUp() + + self.transfer_mock.list.return_value = [self.volume_transfers] + + # Get the command object to test + self.cmd = volume_transfer_request.ListTransferRequest(self.app, None) + + def test_transfer_list_without_argument(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) + + expected_columns = [ + 'ID', + 'Name', + 'Volume', + ] + + # confirming if all expected columns are present in the result. + self.assertEqual(expected_columns, columns) + + datalist = (( + self.volume_transfers.id, + self.volume_transfers.name, + self.volume_transfers.volume_id, + ), ) + + # confirming if all expected values are present in the result. + self.assertEqual(datalist, tuple(data)) + + # checking if proper call was made to list volume_transfers + self.transfer_mock.list.assert_called_with( + detailed=True, + search_opts={'all_tenants': 0} + ) + + def test_transfer_list_with_argument(self): + arglist = [ + "--all-projects" + ] + verifylist = [ + ("all_projects", 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) + + expected_columns = [ + 'ID', + 'Name', + 'Volume', + ] + + # confirming if all expected columns are present in the result. + self.assertEqual(expected_columns, columns) + + datalist = (( + self.volume_transfers.id, + self.volume_transfers.name, + self.volume_transfers.volume_id, + ), ) + + # confirming if all expected values are present in the result. + self.assertEqual(datalist, tuple(data)) + + # checking if proper call was made to list volume_transfers + self.transfer_mock.list.assert_called_with( + detailed=True, + search_opts={'all_tenants': 1} + ) + + +class TestTransferShow(TestTransfer): + + columns = ( + 'created_at', + 'id', + 'name', + 'volume_id', + ) + + def setUp(self): + super(TestTransferShow, self).setUp() + + self.volume_transfer = ( + transfer_fakes.FakeTransfer.create_one_transfer( + attrs={'created_at': 'time'}) + ) + self.data = ( + self.volume_transfer.created_at, + self.volume_transfer.id, + self.volume_transfer.name, + self.volume_transfer.volume_id, + ) + + self.transfer_mock.get.return_value = self.volume_transfer + + # Get the command object to test + self.cmd = volume_transfer_request.ShowTransferRequest( + self.app, None) + + def test_transfer_show(self): + arglist = [ + self.volume_transfer.id, + ] + verifylist = [ + ('transfer_request', self.volume_transfer.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.transfer_mock.get.assert_called_once_with( + self.volume_transfer.id) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v3/fakes.py python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v3/fakes.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v3/fakes.py 1970-01-01 00:00:00.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v3/fakes.py 2021-09-01 19:16:00.000000000 +0000 @@ -0,0 +1,389 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import random +from unittest import mock +import uuid + +from cinderclient import api_versions + +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit import utils +from openstackclient.tests.unit.volume.v2 import fakes as volume_v2_fakes + + +class FakeVolumeClient(object): + + def __init__(self, **kwargs): + self.auth_token = kwargs['token'] + self.management_url = kwargs['endpoint'] + self.api_version = api_versions.APIVersion('3.0') + + self.attachments = mock.Mock() + self.attachments.resource_class = fakes.FakeResource(None, {}) + self.groups = mock.Mock() + self.groups.resource_class = fakes.FakeResource(None, {}) + self.group_snapshots = mock.Mock() + self.group_snapshots.resource_class = fakes.FakeResource(None, {}) + self.group_types = mock.Mock() + self.group_types.resource_class = fakes.FakeResource(None, {}) + self.messages = mock.Mock() + self.messages.resource_class = fakes.FakeResource(None, {}) + self.volumes = mock.Mock() + self.volumes.resource_class = fakes.FakeResource(None, {}) + self.volume_types = mock.Mock() + self.volume_types.resource_class = fakes.FakeResource(None, {}) + + +class TestVolume(utils.TestCommand): + + def setUp(self): + super().setUp() + + self.app.client_manager.volume = FakeVolumeClient( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN + ) + self.app.client_manager.identity = identity_fakes.FakeIdentityv3Client( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN + ) + self.app.client_manager.compute = compute_fakes.FakeComputev2Client( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) + + +# TODO(stephenfin): Check if the responses are actually the same +FakeVolume = volume_v2_fakes.FakeVolume +FakeVolumeType = volume_v2_fakes.FakeVolumeType + + +class FakeVolumeGroup: + """Fake one or more volume groups.""" + + @staticmethod + def create_one_volume_group(attrs=None): + """Create a fake group. + + :param attrs: A dictionary with all attributes of group + :return: A FakeResource object with id, name, status, etc. + """ + attrs = attrs or {} + + group_type = attrs.pop('group_type', None) or uuid.uuid4().hex + volume_types = attrs.pop('volume_types', None) or [uuid.uuid4().hex] + + # Set default attribute + group_info = { + 'id': uuid.uuid4().hex, + 'status': random.choice([ + 'available', + ]), + 'availability_zone': f'az-{uuid.uuid4().hex}', + 'created_at': '2015-09-16T09:28:52.000000', + 'name': 'first_group', + 'description': f'description-{uuid.uuid4().hex}', + 'group_type': group_type, + 'volume_types': volume_types, + 'volumes': [f'volume-{uuid.uuid4().hex}'], + 'group_snapshot_id': None, + 'source_group_id': None, + 'project_id': f'project-{uuid.uuid4().hex}', + } + + # Overwrite default attributes if there are some attributes set + group_info.update(attrs) + + group = fakes.FakeResource( + None, + group_info, + loaded=True) + return group + + @staticmethod + def create_volume_groups(attrs=None, count=2): + """Create multiple fake groups. + + :param attrs: A dictionary with all attributes of group + :param count: The number of groups to be faked + :return: A list of FakeResource objects + """ + groups = [] + for n in range(0, count): + groups.append(FakeVolumeGroup.create_one_volume_group(attrs)) + + return groups + + +class FakeVolumeGroupSnapshot: + """Fake one or more volume group snapshots.""" + + @staticmethod + def create_one_volume_group_snapshot(attrs=None, methods=None): + """Create a fake group snapshot. + + :param attrs: A dictionary with all attributes + :param methods: A dictionary with all methods + :return: A FakeResource object with id, name, description, etc. + """ + attrs = attrs or {} + + # Set default attribute + group_snapshot_info = { + 'id': uuid.uuid4().hex, + 'name': f'group-snapshot-{uuid.uuid4().hex}', + 'description': f'description-{uuid.uuid4().hex}', + 'status': random.choice(['available']), + 'group_id': uuid.uuid4().hex, + 'group_type_id': uuid.uuid4().hex, + 'project_id': uuid.uuid4().hex, + } + + # Overwrite default attributes if there are some attributes set + group_snapshot_info.update(attrs) + + group_snapshot = fakes.FakeResource( + None, + group_snapshot_info, + methods=methods, + loaded=True) + return group_snapshot + + @staticmethod + def create_volume_group_snapshots(attrs=None, count=2): + """Create multiple fake group snapshots. + + :param attrs: A dictionary with all attributes of group snapshot + :param count: The number of group snapshots to be faked + :return: A list of FakeResource objects + """ + group_snapshots = [] + for n in range(0, count): + group_snapshots.append( + FakeVolumeGroupSnapshot.create_one_volume_group_snapshot(attrs) + ) + + return group_snapshots + + +class FakeVolumeGroupType: + """Fake one or more volume group types.""" + + @staticmethod + def create_one_volume_group_type(attrs=None, methods=None): + """Create a fake group type. + + :param attrs: A dictionary with all attributes of group type + :param methods: A dictionary with all methods + :return: A FakeResource object with id, name, description, etc. + """ + attrs = attrs or {} + + # Set default attribute + group_type_info = { + 'id': uuid.uuid4().hex, + 'name': f'group-type-{uuid.uuid4().hex}', + 'description': f'description-{uuid.uuid4().hex}', + 'is_public': random.choice([True, False]), + 'group_specs': {}, + } + + # Overwrite default attributes if there are some attributes set + group_type_info.update(attrs) + + group_type = fakes.FakeResource( + None, + group_type_info, + methods=methods, + loaded=True) + return group_type + + @staticmethod + def create_volume_group_types(attrs=None, count=2): + """Create multiple fake group types. + + :param attrs: A dictionary with all attributes of group type + :param count: The number of group types to be faked + :return: A list of FakeResource objects + """ + group_types = [] + for n in range(0, count): + group_types.append( + FakeVolumeGroupType.create_one_volume_group_type(attrs) + ) + + return group_types + + +class FakeVolumeMessage: + """Fake one or more volume messages.""" + + @staticmethod + def create_one_volume_message(attrs=None): + """Create a fake message. + + :param attrs: A dictionary with all attributes of message + :return: A FakeResource object with id, name, status, etc. + """ + attrs = attrs or {} + + # Set default attribute + message_info = { + 'created_at': '2016-02-11T11:17:37.000000', + 'event_id': f'VOLUME_{random.randint(1, 999999):06d}', + 'guaranteed_until': '2016-02-11T11:17:37.000000', + 'id': uuid.uuid4().hex, + 'message_level': 'ERROR', + 'request_id': f'req-{uuid.uuid4().hex}', + 'resource_type': 'VOLUME', + 'resource_uuid': uuid.uuid4().hex, + 'user_message': f'message-{uuid.uuid4().hex}', + } + + # Overwrite default attributes if there are some attributes set + message_info.update(attrs) + + message = fakes.FakeResource( + None, + message_info, + loaded=True) + return message + + @staticmethod + def create_volume_messages(attrs=None, count=2): + """Create multiple fake messages. + + :param attrs: A dictionary with all attributes of message + :param count: The number of messages to be faked + :return: A list of FakeResource objects + """ + messages = [] + for n in range(0, count): + messages.append(FakeVolumeMessage.create_one_volume_message(attrs)) + + return messages + + @staticmethod + def get_volume_messages(messages=None, count=2): + """Get an iterable MagicMock object with a list of faked messages. + + If messages list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param messages: A list of FakeResource objects faking messages + :param count: The number of messages to be faked + :return An iterable Mock object with side_effect set to a list of faked + messages + """ + if messages is None: + messages = FakeVolumeMessage.create_messages(count) + + return mock.Mock(side_effect=messages) + + +class FakeVolumeAttachment: + """Fake one or more volume attachments.""" + + @staticmethod + def create_one_volume_attachment(attrs=None): + """Create a fake volume attachment. + + :param attrs: A dictionary with all attributes of volume attachment + :return: A FakeResource object with id, status, etc. + """ + attrs = attrs or {} + + attachment_id = uuid.uuid4().hex + volume_id = attrs.pop('volume_id', None) or uuid.uuid4().hex + server_id = attrs.pop('instance', None) or uuid.uuid4().hex + + # Set default attribute + attachment_info = { + 'id': attachment_id, + 'volume_id': volume_id, + 'instance': server_id, + 'status': random.choice([ + 'attached', + 'attaching', + 'detached', + 'reserved', + 'error_attaching', + 'error_detaching', + 'deleted', + ]), + 'attach_mode': random.choice(['ro', 'rw']), + 'attached_at': '2015-09-16T09:28:52.000000', + 'detached_at': None, + 'connection_info': { + 'access_mode': 'rw', + 'attachment_id': attachment_id, + 'auth_method': 'CHAP', + 'auth_password': 'AcUZ8PpxLHwzypMC', + 'auth_username': '7j3EZQWT3rbE6pcSGKvK', + 'cacheable': False, + 'driver_volume_type': 'iscsi', + 'encrypted': False, + 'qos_specs': None, + 'target_discovered': False, + 'target_iqn': + f'iqn.2010-10.org.openstack:volume-{attachment_id}', + 'target_lun': '1', + 'target_portal': '192.168.122.170:3260', + 'volume_id': volume_id, + }, + } + + # Overwrite default attributes if there are some attributes set + attachment_info.update(attrs) + + attachment = fakes.FakeResource( + None, + attachment_info, + loaded=True) + return attachment + + @staticmethod + def create_volume_attachments(attrs=None, count=2): + """Create multiple fake volume attachments. + + :param attrs: A dictionary with all attributes of volume attachment + :param count: The number of volume attachments to be faked + :return: A list of FakeResource objects + """ + attachments = [] + + for n in range(0, count): + attachments.append( + FakeVolumeAttachment.create_one_volume_attachment(attrs)) + + return attachments + + @staticmethod + def get_volume_attachments(attachments=None, count=2): + """Get an iterable MagicMock object with a list of faked volumes. + + If attachments list is provided, then initialize the Mock object with + the list. Otherwise create one. + + :param attachments: A list of FakeResource objects faking volume + attachments + :param count: The number of volume attachments to be faked + :return An iterable Mock object with side_effect set to a list of faked + volume attachments + """ + if attachments is None: + attachments = FakeVolumeAttachment.create_volume_attachments(count) + + return mock.Mock(side_effect=attachments) diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v3/test_volume_attachment.py python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v3/test_volume_attachment.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v3/test_volume_attachment.py 1970-01-01 00:00:00.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v3/test_volume_attachment.py 2021-09-01 19:16:00.000000000 +0000 @@ -0,0 +1,560 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from cinderclient import api_versions +from osc_lib.cli import format_columns +from osc_lib import exceptions + +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit.volume.v3 import fakes as volume_fakes +from openstackclient.volume.v3 import volume_attachment + + +class TestVolumeAttachment(volume_fakes.TestVolume): + + def setUp(self): + super().setUp() + + self.volumes_mock = self.app.client_manager.volume.volumes + self.volumes_mock.reset_mock() + + self.volume_attachments_mock = \ + self.app.client_manager.volume.attachments + self.volume_attachments_mock.reset_mock() + + self.projects_mock = self.app.client_manager.identity.projects + self.projects_mock.reset_mock() + + self.servers_mock = self.app.client_manager.compute.servers + self.servers_mock.reset_mock() + + +class TestVolumeAttachmentCreate(TestVolumeAttachment): + + volume = volume_fakes.FakeVolume.create_one_volume() + server = compute_fakes.FakeServer.create_one_server() + volume_attachment = \ + volume_fakes.FakeVolumeAttachment.create_one_volume_attachment( + attrs={'instance': server.id, 'volume_id': volume.id}) + + columns = ( + 'ID', + 'Volume ID', + 'Instance ID', + 'Status', + 'Attach Mode', + 'Attached At', + 'Detached At', + 'Properties', + ) + data = ( + volume_attachment.id, + volume_attachment.volume_id, + volume_attachment.instance, + volume_attachment.status, + volume_attachment.attach_mode, + volume_attachment.attached_at, + volume_attachment.detached_at, + format_columns.DictColumn(volume_attachment.connection_info), + ) + + def setUp(self): + super().setUp() + + self.volumes_mock.get.return_value = self.volume + self.servers_mock.get.return_value = self.server + self.volume_attachments_mock.create.return_value = \ + self.volume_attachment + + self.cmd = volume_attachment.CreateVolumeAttachment(self.app, None) + + def test_volume_attachment_create(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.27') + + arglist = [ + self.volume.id, + self.server.id, + ] + verifylist = [ + ('volume', self.volume.id), + ('server', self.server.id), + ('connect', False), + ('initiator', None), + ('ip', None), + ('host', None), + ('platform', None), + ('os_type', None), + ('multipath', False), + ('mountpoint', None), + ('mode', None), + ] + 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.volume.id) + self.servers_mock.get.assert_called_once_with(self.server.id) + self.volume_attachments_mock.create.assert_called_once_with( + self.volume.id, {}, self.server.id, None, + ) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_volume_attachment_create_with_connect(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.54') + + arglist = [ + self.volume.id, + self.server.id, + '--connect', + '--initiator', 'iqn.1993-08.org.debian:01:cad181614cec', + '--ip', '192.168.1.20', + '--host', 'my-host', + '--platform', 'x86_64', + '--os-type', 'linux2', + '--multipath', + '--mountpoint', '/dev/vdb', + '--mode', 'null', + ] + verifylist = [ + ('volume', self.volume.id), + ('server', self.server.id), + ('connect', True), + ('initiator', 'iqn.1993-08.org.debian:01:cad181614cec'), + ('ip', '192.168.1.20'), + ('host', 'my-host'), + ('platform', 'x86_64'), + ('os_type', 'linux2'), + ('multipath', True), + ('mountpoint', '/dev/vdb'), + ('mode', 'null'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + connect_info = dict([ + ('initiator', 'iqn.1993-08.org.debian:01:cad181614cec'), + ('ip', '192.168.1.20'), + ('host', 'my-host'), + ('platform', 'x86_64'), + ('os_type', 'linux2'), + ('multipath', True), + ('mountpoint', '/dev/vdb'), + ]) + + self.volumes_mock.get.assert_called_once_with(self.volume.id) + self.servers_mock.get.assert_called_once_with(self.server.id) + self.volume_attachments_mock.create.assert_called_once_with( + self.volume.id, connect_info, self.server.id, 'null', + ) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_volume_attachment_create_pre_v327(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.26') + + arglist = [ + self.volume.id, + self.server.id, + ] + verifylist = [ + ('volume', self.volume.id), + ('server', self.server.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.27 or greater is required', + str(exc)) + + def test_volume_attachment_create_with_mode_pre_v354(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.53') + + arglist = [ + self.volume.id, + self.server.id, + '--mode', 'rw', + ] + verifylist = [ + ('volume', self.volume.id), + ('server', self.server.id), + ('mode', 'rw'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.54 or greater is required', + str(exc)) + + def test_volume_attachment_create_with_connect_missing_arg(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.54') + + arglist = [ + self.volume.id, + self.server.id, + '--initiator', 'iqn.1993-08.org.debian:01:cad181614cec', + ] + verifylist = [ + ('volume', self.volume.id), + ('server', self.server.id), + ('connect', False), + ('initiator', 'iqn.1993-08.org.debian:01:cad181614cec'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + 'You must specify the --connect option for any', + str(exc)) + + +class TestVolumeAttachmentDelete(TestVolumeAttachment): + + volume_attachment = \ + volume_fakes.FakeVolumeAttachment.create_one_volume_attachment() + + def setUp(self): + super().setUp() + + self.volume_attachments_mock.delete.return_value = None + + self.cmd = volume_attachment.DeleteVolumeAttachment(self.app, None) + + def test_volume_attachment_delete(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.27') + + arglist = [ + self.volume_attachment.id, + ] + verifylist = [ + ('attachment', self.volume_attachment.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.volume_attachments_mock.delete.assert_called_once_with( + self.volume_attachment.id, + ) + self.assertIsNone(result) + + def test_volume_attachment_delete_pre_v327(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.26') + + arglist = [ + self.volume_attachment.id, + ] + verifylist = [ + ('attachment', self.volume_attachment.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.27 or greater is required', + str(exc)) + + +class TestVolumeAttachmentSet(TestVolumeAttachment): + + volume_attachment = \ + volume_fakes.FakeVolumeAttachment.create_one_volume_attachment() + + columns = ( + 'ID', + 'Volume ID', + 'Instance ID', + 'Status', + 'Attach Mode', + 'Attached At', + 'Detached At', + 'Properties', + ) + data = ( + volume_attachment.id, + volume_attachment.volume_id, + volume_attachment.instance, + volume_attachment.status, + volume_attachment.attach_mode, + volume_attachment.attached_at, + volume_attachment.detached_at, + format_columns.DictColumn(volume_attachment.connection_info), + ) + + def setUp(self): + super().setUp() + + self.volume_attachments_mock.update.return_value = \ + self.volume_attachment + + self.cmd = volume_attachment.SetVolumeAttachment(self.app, None) + + def test_volume_attachment_set(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.27') + + arglist = [ + self.volume_attachment.id, + '--initiator', 'iqn.1993-08.org.debian:01:cad181614cec', + '--ip', '192.168.1.20', + '--host', 'my-host', + '--platform', 'x86_64', + '--os-type', 'linux2', + '--multipath', + '--mountpoint', '/dev/vdb', + ] + verifylist = [ + ('attachment', self.volume_attachment.id), + ('initiator', 'iqn.1993-08.org.debian:01:cad181614cec'), + ('ip', '192.168.1.20'), + ('host', 'my-host'), + ('platform', 'x86_64'), + ('os_type', 'linux2'), + ('multipath', True), + ('mountpoint', '/dev/vdb'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + connect_info = dict([ + ('initiator', 'iqn.1993-08.org.debian:01:cad181614cec'), + ('ip', '192.168.1.20'), + ('host', 'my-host'), + ('platform', 'x86_64'), + ('os_type', 'linux2'), + ('multipath', True), + ('mountpoint', '/dev/vdb'), + ]) + + self.volume_attachments_mock.update.assert_called_once_with( + self.volume_attachment.id, connect_info, + ) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_volume_attachment_set_pre_v327(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.26') + + arglist = [ + self.volume_attachment.id, + '--initiator', 'iqn.1993-08.org.debian:01:cad181614cec', + ] + verifylist = [ + ('attachment', self.volume_attachment.id), + ('initiator', 'iqn.1993-08.org.debian:01:cad181614cec'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.27 or greater is required', + str(exc)) + + +class TestVolumeAttachmentComplete(TestVolumeAttachment): + + volume_attachment = \ + volume_fakes.FakeVolumeAttachment.create_one_volume_attachment() + + def setUp(self): + super().setUp() + + self.volume_attachments_mock.complete.return_value = None + + self.cmd = volume_attachment.CompleteVolumeAttachment(self.app, None) + + def test_volume_attachment_complete(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.44') + + arglist = [ + self.volume_attachment.id, + ] + verifylist = [ + ('attachment', self.volume_attachment.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.volume_attachments_mock.complete.assert_called_once_with( + self.volume_attachment.id, + ) + self.assertIsNone(result) + + def test_volume_attachment_complete_pre_v344(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.43') + + arglist = [ + self.volume_attachment.id, + ] + verifylist = [ + ('attachment', self.volume_attachment.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.44 or greater is required', + str(exc)) + + +class TestVolumeAttachmentList(TestVolumeAttachment): + + project = identity_fakes.FakeProject.create_one_project() + volume_attachments = \ + volume_fakes.FakeVolumeAttachment.create_volume_attachments() + + columns = ( + 'ID', + 'Volume ID', + 'Server ID', + 'Status', + ) + data = [ + ( + volume_attachment.id, + volume_attachment.volume_id, + volume_attachment.instance, + volume_attachment.status, + ) for volume_attachment in volume_attachments + ] + + def setUp(self): + super().setUp() + + self.projects_mock.get.return_value = self.project + self.volume_attachments_mock.list.return_value = \ + self.volume_attachments + + self.cmd = volume_attachment.ListVolumeAttachment(self.app, None) + + def test_volume_attachment_list(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.27') + + arglist = [] + verifylist = [ + ('project', None), + ('all_projects', False), + ('volume_id', None), + ('status', None), + ('marker', None), + ('limit', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_attachments_mock.list.assert_called_once_with( + search_opts={ + 'all_tenants': False, + 'project_id': None, + 'status': None, + 'volume_id': None, + }, + marker=None, + limit=None, + ) + self.assertEqual(self.columns, columns) + self.assertCountEqual(tuple(self.data), data) + + def test_volume_attachment_list_with_options(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.27') + + arglist = [ + '--project', self.project.name, + '--volume-id', 'volume-id', + '--status', 'attached', + '--marker', 'volume-attachment-id', + '--limit', '2', + ] + verifylist = [ + ('project', self.project.name), + ('all_projects', False), + ('volume_id', 'volume-id'), + ('status', 'attached'), + ('marker', 'volume-attachment-id'), + ('limit', 2), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_attachments_mock.list.assert_called_once_with( + search_opts={ + 'all_tenants': True, + 'project_id': self.project.id, + 'status': 'attached', + 'volume_id': 'volume-id', + }, + marker='volume-attachment-id', + limit=2, + ) + self.assertEqual(self.columns, columns) + self.assertCountEqual(tuple(self.data), data) + + def test_volume_attachment_list_pre_v327(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.26') + + arglist = [] + verifylist = [ + ('project', None), + ('all_projects', False), + ('volume_id', None), + ('status', None), + ('marker', None), + ('limit', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.27 or greater is required', + str(exc)) diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v3/test_volume_group.py python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v3/test_volume_group.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v3/test_volume_group.py 1970-01-01 00:00:00.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v3/test_volume_group.py 2021-09-01 19:16:00.000000000 +0000 @@ -0,0 +1,497 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from cinderclient import api_versions +from osc_lib import exceptions + +from openstackclient.tests.unit.volume.v3 import fakes as volume_fakes +from openstackclient.volume.v3 import volume_group + + +class TestVolumeGroup(volume_fakes.TestVolume): + + def setUp(self): + super().setUp() + + self.volume_groups_mock = self.app.client_manager.volume.groups + self.volume_groups_mock.reset_mock() + + self.volume_group_types_mock = \ + self.app.client_manager.volume.group_types + self.volume_group_types_mock.reset_mock() + + self.volume_types_mock = self.app.client_manager.volume.volume_types + self.volume_types_mock.reset_mock() + + +class TestVolumeGroupCreate(TestVolumeGroup): + + fake_volume_type = volume_fakes.FakeVolumeType.create_one_volume_type() + fake_volume_group_type = \ + volume_fakes.FakeVolumeGroupType.create_one_volume_group_type() + fake_volume_group = volume_fakes.FakeVolumeGroup.create_one_volume_group( + attrs={ + 'group_type': fake_volume_group_type.id, + 'volume_types': [fake_volume_type.id], + }, + ) + + columns = ( + 'ID', + 'Status', + 'Name', + 'Description', + 'Group Type', + 'Volume Types', + 'Availability Zone', + 'Created At', + 'Volumes', + 'Group Snapshot ID', + 'Source Group ID', + ) + data = ( + fake_volume_group.id, + fake_volume_group.status, + fake_volume_group.name, + fake_volume_group.description, + fake_volume_group.group_type, + fake_volume_group.volume_types, + fake_volume_group.availability_zone, + fake_volume_group.created_at, + fake_volume_group.volumes, + fake_volume_group.group_snapshot_id, + fake_volume_group.source_group_id, + ) + + def setUp(self): + super().setUp() + + self.volume_types_mock.get.return_value = self.fake_volume_type + self.volume_group_types_mock.get.return_value = \ + self.fake_volume_group_type + self.volume_groups_mock.create.return_value = self.fake_volume_group + self.volume_groups_mock.get.return_value = self.fake_volume_group + + self.cmd = volume_group.CreateVolumeGroup(self.app, None) + + def test_volume_group_create(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.13') + + arglist = [ + self.fake_volume_group_type.id, + self.fake_volume_type.id, + ] + verifylist = [ + ('volume_group_type', self.fake_volume_group_type.id), + ('volume_types', [self.fake_volume_type.id]), + ('name', None), + ('description', None), + ('availability_zone', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_group_types_mock.get.assert_called_once_with( + self.fake_volume_group_type.id) + self.volume_types_mock.get.assert_called_once_with( + self.fake_volume_type.id) + self.volume_groups_mock.create.assert_called_once_with( + self.fake_volume_group_type.id, + self.fake_volume_type.id, + None, + None, + availability_zone=None, + ) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_volume_group_create_with_options(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.13') + + arglist = [ + self.fake_volume_group_type.id, + self.fake_volume_type.id, + '--name', 'foo', + '--description', 'hello, world', + '--availability-zone', 'bar', + ] + verifylist = [ + ('volume_group_type', self.fake_volume_group_type.id), + ('volume_types', [self.fake_volume_type.id]), + ('name', 'foo'), + ('description', 'hello, world'), + ('availability_zone', 'bar'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_group_types_mock.get.assert_called_once_with( + self.fake_volume_group_type.id) + self.volume_types_mock.get.assert_called_once_with( + self.fake_volume_type.id) + self.volume_groups_mock.create.assert_called_once_with( + self.fake_volume_group_type.id, + self.fake_volume_type.id, + 'foo', + 'hello, world', + availability_zone='bar', + ) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_volume_group_create_pre_v313(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.12') + + arglist = [ + self.fake_volume_group_type.id, + self.fake_volume_type.id, + ] + verifylist = [ + ('volume_group_type', self.fake_volume_group_type.id), + ('volume_types', [self.fake_volume_type.id]), + ('name', None), + ('description', None), + ('availability_zone', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.13 or greater is required', + str(exc)) + + +class TestVolumeGroupDelete(TestVolumeGroup): + + fake_volume_group = \ + volume_fakes.FakeVolumeGroup.create_one_volume_group() + + def setUp(self): + super().setUp() + + self.volume_groups_mock.get.return_value = self.fake_volume_group + self.volume_groups_mock.delete.return_value = None + + self.cmd = volume_group.DeleteVolumeGroup(self.app, None) + + def test_volume_group_delete(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.13') + + arglist = [ + self.fake_volume_group.id, + '--force', + ] + verifylist = [ + ('group', self.fake_volume_group.id), + ('force', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.volume_groups_mock.delete.assert_called_once_with( + self.fake_volume_group.id, delete_volumes=True, + ) + self.assertIsNone(result) + + def test_volume_group_delete_pre_v313(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.12') + + arglist = [ + self.fake_volume_group.id, + ] + verifylist = [ + ('group', self.fake_volume_group.id), + ('force', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.13 or greater is required', + str(exc)) + + +class TestVolumeGroupSet(TestVolumeGroup): + + fake_volume_group = \ + volume_fakes.FakeVolumeGroup.create_one_volume_group() + + columns = ( + 'ID', + 'Status', + 'Name', + 'Description', + 'Group Type', + 'Volume Types', + 'Availability Zone', + 'Created At', + 'Volumes', + 'Group Snapshot ID', + 'Source Group ID', + ) + data = ( + fake_volume_group.id, + fake_volume_group.status, + fake_volume_group.name, + fake_volume_group.description, + fake_volume_group.group_type, + fake_volume_group.volume_types, + fake_volume_group.availability_zone, + fake_volume_group.created_at, + fake_volume_group.volumes, + fake_volume_group.group_snapshot_id, + fake_volume_group.source_group_id, + ) + + def setUp(self): + super().setUp() + + self.volume_groups_mock.get.return_value = self.fake_volume_group + self.volume_groups_mock.update.return_value = self.fake_volume_group + + self.cmd = volume_group.SetVolumeGroup(self.app, None) + + def test_volume_group_set(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.13') + + arglist = [ + self.fake_volume_group.id, + '--name', 'foo', + '--description', 'hello, world', + ] + verifylist = [ + ('group', self.fake_volume_group.id), + ('name', 'foo'), + ('description', 'hello, world'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_groups_mock.update.assert_called_once_with( + self.fake_volume_group.id, name='foo', description='hello, world', + ) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_volume_group_with_enable_replication_option(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.38') + + arglist = [ + self.fake_volume_group.id, + '--enable-replication', + ] + verifylist = [ + ('group', self.fake_volume_group.id), + ('enable_replication', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_groups_mock.enable_replication.assert_called_once_with( + self.fake_volume_group.id) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_volume_group_set_pre_v313(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.12') + + arglist = [ + self.fake_volume_group.id, + '--name', 'foo', + '--description', 'hello, world', + ] + verifylist = [ + ('group', self.fake_volume_group.id), + ('name', 'foo'), + ('description', 'hello, world'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.13 or greater is required', + str(exc)) + + def test_volume_group_with_enable_replication_option_pre_v338(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.37') + + arglist = [ + self.fake_volume_group.id, + '--enable-replication', + ] + verifylist = [ + ('group', self.fake_volume_group.id), + ('enable_replication', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.38 or greater is required', + str(exc)) + + +class TestVolumeGroupList(TestVolumeGroup): + + fake_volume_groups = \ + volume_fakes.FakeVolumeGroup.create_volume_groups() + + columns = ( + 'ID', + 'Status', + 'Name', + ) + data = [ + ( + fake_volume_group.id, + fake_volume_group.status, + fake_volume_group.name, + ) for fake_volume_group in fake_volume_groups + ] + + def setUp(self): + super().setUp() + + self.volume_groups_mock.list.return_value = self.fake_volume_groups + + self.cmd = volume_group.ListVolumeGroup(self.app, None) + + def test_volume_group_list(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.13') + + arglist = [ + '--all-projects', + ] + verifylist = [ + ('all_projects', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_groups_mock.list.assert_called_once_with( + search_opts={ + 'all_tenants': True, + }, + ) + self.assertEqual(self.columns, columns) + self.assertCountEqual(tuple(self.data), data) + + def test_volume_group_list_pre_v313(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.12') + + arglist = [ + '--all-projects', + ] + verifylist = [ + ('all_projects', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.13 or greater is required', + str(exc)) + + +class TestVolumeGroupFailover(TestVolumeGroup): + + fake_volume_group = \ + volume_fakes.FakeVolumeGroup.create_one_volume_group() + + def setUp(self): + super().setUp() + + self.volume_groups_mock.get.return_value = self.fake_volume_group + self.volume_groups_mock.failover_replication.return_value = None + + self.cmd = volume_group.FailoverVolumeGroup(self.app, None) + + def test_volume_group_failover(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.38') + + arglist = [ + self.fake_volume_group.id, + '--allow-attached-volume', + '--secondary-backend-id', 'foo', + ] + verifylist = [ + ('group', self.fake_volume_group.id), + ('allow_attached_volume', True), + ('secondary_backend_id', 'foo'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.volume_groups_mock.failover_replication.assert_called_once_with( + self.fake_volume_group.id, + allow_attached_volume=True, + secondary_backend_id='foo', + ) + self.assertIsNone(result) + + def test_volume_group_failover_pre_v338(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.37') + + arglist = [ + self.fake_volume_group.id, + '--allow-attached-volume', + '--secondary-backend-id', 'foo', + ] + verifylist = [ + ('group', self.fake_volume_group.id), + ('allow_attached_volume', True), + ('secondary_backend_id', 'foo'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.38 or greater is required', + str(exc)) diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v3/test_volume_group_snapshot.py python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v3/test_volume_group_snapshot.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v3/test_volume_group_snapshot.py 1970-01-01 00:00:00.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v3/test_volume_group_snapshot.py 2021-09-01 19:16:00.000000000 +0000 @@ -0,0 +1,262 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from cinderclient import api_versions +from osc_lib import exceptions + +from openstackclient.tests.unit.volume.v3 import fakes as volume_fakes +from openstackclient.volume.v3 import volume_group_snapshot + + +class TestVolumeGroupSnapshot(volume_fakes.TestVolume): + + def setUp(self): + super().setUp() + + self.volume_groups_mock = self.app.client_manager.volume.groups + self.volume_groups_mock.reset_mock() + + self.volume_group_snapshots_mock = \ + self.app.client_manager.volume.group_snapshots + self.volume_group_snapshots_mock.reset_mock() + + +class TestVolumeGroupSnapshotCreate(TestVolumeGroupSnapshot): + + fake_volume_group = volume_fakes.FakeVolumeGroup.create_one_volume_group() + fake_volume_group_snapshot = \ + volume_fakes.FakeVolumeGroupSnapshot.create_one_volume_group_snapshot() + + columns = ( + 'ID', + 'Status', + 'Name', + 'Description', + 'Group', + 'Group Type', + ) + data = ( + fake_volume_group_snapshot.id, + fake_volume_group_snapshot.status, + fake_volume_group_snapshot.name, + fake_volume_group_snapshot.description, + fake_volume_group_snapshot.group_id, + fake_volume_group_snapshot.group_type_id, + ) + + def setUp(self): + super().setUp() + + self.volume_groups_mock.get.return_value = self.fake_volume_group + self.volume_group_snapshots_mock.create.return_value = \ + self.fake_volume_group_snapshot + self.volume_group_snapshots_mock.get.return_value = \ + self.fake_volume_group_snapshot + + self.cmd = volume_group_snapshot.CreateVolumeGroupSnapshot( + self.app, None) + + def test_volume_group_snapshot_create(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.14') + + arglist = [ + self.fake_volume_group.id, + ] + verifylist = [ + ('volume_group', self.fake_volume_group.id), + ('name', None), + ('description', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_groups_mock.get.assert_called_once_with( + self.fake_volume_group.id) + self.volume_group_snapshots_mock.create.assert_called_once_with( + self.fake_volume_group.id, None, None, + ) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_volume_group_snapshot_create_with_options(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.14') + + arglist = [ + self.fake_volume_group.id, + '--name', 'foo', + '--description', 'hello, world', + ] + verifylist = [ + ('volume_group', self.fake_volume_group.id), + ('name', 'foo'), + ('description', 'hello, world'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_groups_mock.get.assert_called_once_with( + self.fake_volume_group.id) + self.volume_group_snapshots_mock.create.assert_called_once_with( + self.fake_volume_group.id, 'foo', 'hello, world', + ) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_volume_group_snapshot_create_pre_v314(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.13') + + arglist = [ + self.fake_volume_group.id, + ] + verifylist = [ + ('volume_group', self.fake_volume_group.id), + ('name', None), + ('description', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.14 or greater is required', + str(exc)) + + +class TestVolumeGroupSnapshotDelete(TestVolumeGroupSnapshot): + + fake_volume_group_snapshot = \ + volume_fakes.FakeVolumeGroupSnapshot.create_one_volume_group_snapshot() + + def setUp(self): + super().setUp() + + self.volume_group_snapshots_mock.get.return_value = \ + self.fake_volume_group_snapshot + self.volume_group_snapshots_mock.delete.return_value = None + + self.cmd = volume_group_snapshot.DeleteVolumeGroupSnapshot( + self.app, None) + + def test_volume_group_snapshot_delete(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.14') + + arglist = [ + self.fake_volume_group_snapshot.id, + ] + verifylist = [ + ('snapshot', self.fake_volume_group_snapshot.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.volume_group_snapshots_mock.delete.assert_called_once_with( + self.fake_volume_group_snapshot.id, + ) + self.assertIsNone(result) + + def test_volume_group_snapshot_delete_pre_v314(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.13') + + arglist = [ + self.fake_volume_group_snapshot.id, + ] + verifylist = [ + ('snapshot', self.fake_volume_group_snapshot.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.14 or greater is required', + str(exc)) + + +class TestVolumeGroupSnapshotList(TestVolumeGroupSnapshot): + + fake_volume_group_snapshots = \ + volume_fakes.FakeVolumeGroupSnapshot.create_volume_group_snapshots() + + columns = ( + 'ID', + 'Status', + 'Name', + ) + data = [ + ( + fake_volume_group_snapshot.id, + fake_volume_group_snapshot.status, + fake_volume_group_snapshot.name, + ) for fake_volume_group_snapshot in fake_volume_group_snapshots + ] + + def setUp(self): + super().setUp() + + self.volume_group_snapshots_mock.list.return_value = \ + self.fake_volume_group_snapshots + + self.cmd = volume_group_snapshot.ListVolumeGroupSnapshot( + self.app, None) + + def test_volume_group_snapshot_list(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.14') + + arglist = [ + '--all-projects', + ] + verifylist = [ + ('all_projects', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_group_snapshots_mock.list.assert_called_once_with( + search_opts={ + 'all_tenants': True, + }, + ) + self.assertEqual(self.columns, columns) + self.assertCountEqual(tuple(self.data), data) + + def test_volume_group_snapshot_list_pre_v314(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.13') + + arglist = [ + ] + verifylist = [ + ('all_projects', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.14 or greater is required', + str(exc)) diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v3/test_volume_group_type.py python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v3/test_volume_group_type.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v3/test_volume_group_type.py 1970-01-01 00:00:00.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v3/test_volume_group_type.py 2021-09-01 19:16:00.000000000 +0000 @@ -0,0 +1,475 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from unittest import mock + +from cinderclient import api_versions +from osc_lib.cli import format_columns +from osc_lib import exceptions + +from openstackclient.tests.unit.volume.v3 import fakes as volume_fakes +from openstackclient.volume.v3 import volume_group_type + + +class TestVolumeGroupType(volume_fakes.TestVolume): + + def setUp(self): + super().setUp() + + self.volume_group_types_mock = \ + self.app.client_manager.volume.group_types + self.volume_group_types_mock.reset_mock() + + +class TestVolumeGroupTypeCreate(TestVolumeGroupType): + + maxDiff = 2000 + + fake_volume_group_type = \ + volume_fakes.FakeVolumeGroupType.create_one_volume_group_type() + + columns = ( + 'ID', + 'Name', + 'Description', + 'Is Public', + 'Properties', + ) + data = ( + fake_volume_group_type.id, + fake_volume_group_type.name, + fake_volume_group_type.description, + fake_volume_group_type.is_public, + format_columns.DictColumn(fake_volume_group_type.group_specs), + ) + + def setUp(self): + super().setUp() + + self.volume_group_types_mock.create.return_value = \ + self.fake_volume_group_type + + self.cmd = volume_group_type.CreateVolumeGroupType(self.app, None) + + def test_volume_group_type_create(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.11') + + arglist = [ + self.fake_volume_group_type.name, + ] + verifylist = [ + ('name', self.fake_volume_group_type.name), + ('description', None), + ('is_public', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_group_types_mock.create.assert_called_once_with( + self.fake_volume_group_type.name, + None, + True) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_volume_group_type_create_with_options(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.11') + + arglist = [ + self.fake_volume_group_type.name, + '--description', 'foo', + '--private', + ] + verifylist = [ + ('name', self.fake_volume_group_type.name), + ('description', 'foo'), + ('is_public', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_group_types_mock.create.assert_called_once_with( + self.fake_volume_group_type.name, + 'foo', + False) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_volume_group_type_create_pre_v311(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.10') + + arglist = [ + self.fake_volume_group_type.name, + ] + verifylist = [ + ('name', self.fake_volume_group_type.name), + ('description', None), + ('is_public', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.11 or greater is required', + str(exc)) + + +class TestVolumeGroupTypeDelete(TestVolumeGroupType): + + fake_volume_group_type = \ + volume_fakes.FakeVolumeGroupType.create_one_volume_group_type() + + def setUp(self): + super().setUp() + + self.volume_group_types_mock.get.return_value = \ + self.fake_volume_group_type + self.volume_group_types_mock.delete.return_value = None + + self.cmd = volume_group_type.DeleteVolumeGroupType(self.app, None) + + def test_volume_group_type_delete(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.11') + + arglist = [ + self.fake_volume_group_type.id, + ] + verifylist = [ + ('group_type', self.fake_volume_group_type.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.volume_group_types_mock.delete.assert_called_once_with( + self.fake_volume_group_type.id, + ) + self.assertIsNone(result) + + def test_volume_group_type_delete_pre_v311(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.10') + + arglist = [ + self.fake_volume_group_type.id, + ] + verifylist = [ + ('group_type', self.fake_volume_group_type.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.11 or greater is required', + str(exc)) + + +class TestVolumeGroupTypeSet(TestVolumeGroupType): + + fake_volume_group_type = \ + volume_fakes.FakeVolumeGroupType.create_one_volume_group_type( + methods={ + 'get_keys': {'foo': 'bar'}, + 'set_keys': None, + 'unset_keys': None, + }) + + columns = ( + 'ID', + 'Name', + 'Description', + 'Is Public', + 'Properties', + ) + data = ( + fake_volume_group_type.id, + fake_volume_group_type.name, + fake_volume_group_type.description, + fake_volume_group_type.is_public, + format_columns.DictColumn(fake_volume_group_type.group_specs), + ) + + def setUp(self): + super().setUp() + + self.volume_group_types_mock.get.return_value = \ + self.fake_volume_group_type + self.volume_group_types_mock.update.return_value = \ + self.fake_volume_group_type + + self.cmd = volume_group_type.SetVolumeGroupType(self.app, None) + + def test_volume_group_type_set(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.11') + + self.fake_volume_group_type.set_keys.return_value = None + + arglist = [ + self.fake_volume_group_type.id, + '--name', 'foo', + '--description', 'hello, world', + '--public', + '--property', 'fizz=buzz', + ] + verifylist = [ + ('group_type', self.fake_volume_group_type.id), + ('name', 'foo'), + ('description', 'hello, world'), + ('is_public', True), + ('no_property', False), + ('properties', {'fizz': 'buzz'}), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_group_types_mock.update.assert_called_once_with( + self.fake_volume_group_type.id, + name='foo', + description='hello, world', + is_public=True, + ) + self.fake_volume_group_type.set_keys.assert_called_once_with( + {'fizz': 'buzz'}, + ) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_volume_group_type_with_no_property_option(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.11') + + arglist = [ + self.fake_volume_group_type.id, + '--no-property', + '--property', 'fizz=buzz', + ] + verifylist = [ + ('group_type', self.fake_volume_group_type.id), + ('name', None), + ('description', None), + ('is_public', None), + ('no_property', True), + ('properties', {'fizz': 'buzz'}), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_group_types_mock.get.assert_called_once_with( + self.fake_volume_group_type.id) + self.fake_volume_group_type.get_keys.assert_called_once_with() + self.fake_volume_group_type.unset_keys.assert_called_once_with( + {'foo': 'bar'}.keys()) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_volume_group_type_set_pre_v311(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.10') + + arglist = [ + self.fake_volume_group_type.id, + '--name', 'foo', + '--description', 'hello, world', + ] + verifylist = [ + ('group_type', self.fake_volume_group_type.id), + ('name', 'foo'), + ('description', 'hello, world'), + ('is_public', None), + ('no_property', False), + ('properties', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.11 or greater is required', + str(exc)) + + +class TestVolumeGroupTypeUnset(TestVolumeGroupType): + + fake_volume_group_type = \ + volume_fakes.FakeVolumeGroupType.create_one_volume_group_type( + methods={'unset_keys': None}) + + columns = ( + 'ID', + 'Name', + 'Description', + 'Is Public', + 'Properties', + ) + data = ( + fake_volume_group_type.id, + fake_volume_group_type.name, + fake_volume_group_type.description, + fake_volume_group_type.is_public, + format_columns.DictColumn(fake_volume_group_type.group_specs), + ) + + def setUp(self): + super().setUp() + + self.volume_group_types_mock.get.return_value = \ + self.fake_volume_group_type + + self.cmd = volume_group_type.UnsetVolumeGroupType(self.app, None) + + def test_volume_group_type_unset(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.11') + + arglist = [ + self.fake_volume_group_type.id, + '--property', 'fizz', + ] + verifylist = [ + ('group_type', self.fake_volume_group_type.id), + ('properties', ['fizz']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_group_types_mock.get.assert_has_calls([ + mock.call(self.fake_volume_group_type.id), + mock.call(self.fake_volume_group_type.id), + ]) + self.fake_volume_group_type.unset_keys.assert_called_once_with( + ['fizz']) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_volume_group_type_unset_pre_v311(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.10') + + arglist = [ + self.fake_volume_group_type.id, + '--property', 'fizz', + ] + verifylist = [ + ('group_type', self.fake_volume_group_type.id), + ('properties', ['fizz']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.11 or greater is required', + str(exc)) + + +class TestVolumeGroupTypeList(TestVolumeGroupType): + + fake_volume_group_types = \ + volume_fakes.FakeVolumeGroupType.create_volume_group_types() + + columns = ( + 'ID', + 'Name', + 'Is Public', + 'Properties', + ) + data = [ + ( + fake_volume_group_type.id, + fake_volume_group_type.name, + fake_volume_group_type.is_public, + fake_volume_group_type.group_specs, + ) for fake_volume_group_type in fake_volume_group_types + ] + + def setUp(self): + super().setUp() + + self.volume_group_types_mock.list.return_value = \ + self.fake_volume_group_types + self.volume_group_types_mock.default.return_value = \ + self.fake_volume_group_types[0] + + self.cmd = volume_group_type.ListVolumeGroupType(self.app, None) + + def test_volume_group_type_list(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.11') + + arglist = [ + ] + verifylist = [ + ('show_default', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_group_types_mock.list.assert_called_once_with() + self.assertEqual(self.columns, columns) + self.assertCountEqual(tuple(self.data), data) + + def test_volume_group_type_list_with_default_option(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.11') + + arglist = [ + '--default', + ] + verifylist = [ + ('show_default', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_group_types_mock.default.assert_called_once_with() + self.assertEqual(self.columns, columns) + self.assertCountEqual(tuple([self.data[0]]), data) + + def test_volume_group_type_list_pre_v311(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.10') + + arglist = [ + ] + verifylist = [ + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.11 or greater is required', + str(exc)) diff -Nru python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v3/test_volume_message.py python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v3/test_volume_message.py --- python-openstackclient-5.5.0/openstackclient/tests/unit/volume/v3/test_volume_message.py 1970-01-01 00:00:00.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/tests/unit/volume/v3/test_volume_message.py 2021-09-01 19:16:00.000000000 +0000 @@ -0,0 +1,324 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from unittest.mock import call + +from cinderclient import api_versions +from osc_lib import exceptions + +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit.volume.v3 import fakes as volume_fakes +from openstackclient.volume.v3 import volume_message + + +class TestVolumeMessage(volume_fakes.TestVolume): + + def setUp(self): + super().setUp() + + self.projects_mock = self.app.client_manager.identity.projects + self.projects_mock.reset_mock() + + self.volume_messages_mock = self.app.client_manager.volume.messages + self.volume_messages_mock.reset_mock() + + +class TestVolumeMessageDelete(TestVolumeMessage): + + fake_messages = volume_fakes.FakeVolumeMessage.create_volume_messages( + count=2) + + def setUp(self): + super().setUp() + + self.volume_messages_mock.get = \ + volume_fakes.FakeVolumeMessage.get_volume_messages( + self.fake_messages) + self.volume_messages_mock.delete.return_value = None + + # Get the command object to mock + self.cmd = volume_message.DeleteMessage(self.app, None) + + def test_message_delete(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.3') + + arglist = [ + self.fake_messages[0].id, + ] + verifylist = [ + ('message_ids', [self.fake_messages[0].id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.volume_messages_mock.delete.assert_called_with( + self.fake_messages[0].id) + self.assertIsNone(result) + + def test_message_delete_multiple_messages(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.3') + + arglist = [ + self.fake_messages[0].id, + self.fake_messages[1].id, + ] + verifylist = [ + ('message_ids', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for m in self.fake_messages: + calls.append(call(m.id)) + self.volume_messages_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_message_delete_multiple_messages_with_exception(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.3') + + arglist = [ + self.fake_messages[0].id, + 'invalid_message', + ] + verifylist = [ + ('message_ids', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.volume_messages_mock.delete.side_effect = [ + self.fake_messages[0], exceptions.CommandError] + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, parsed_args) + self.assertEqual('Failed to delete 1 of 2 messages.', str(exc)) + + self.volume_messages_mock.delete.assert_any_call( + self.fake_messages[0].id) + self.volume_messages_mock.delete.assert_any_call('invalid_message') + + self.assertEqual(2, self.volume_messages_mock.delete.call_count) + + def test_message_delete_pre_v33(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.2') + + arglist = [ + self.fake_messages[0].id, + ] + verifylist = [ + ('message_ids', [self.fake_messages[0].id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.3 or greater is required', + str(exc)) + + +class TestVolumeMessageList(TestVolumeMessage): + + fake_project = identity_fakes.FakeProject.create_one_project() + fake_messages = volume_fakes.FakeVolumeMessage.create_volume_messages( + count=3) + + columns = ( + 'ID', + 'Event ID', + 'Resource Type', + 'Resource UUID', + 'Message Level', + 'User Message', + 'Request ID', + 'Created At', + 'Guaranteed Until', + ) + data = [] + for fake_message in fake_messages: + data.append(( + fake_message.id, + fake_message.event_id, + fake_message.resource_type, + fake_message.resource_uuid, + fake_message.message_level, + fake_message.user_message, + fake_message.request_id, + fake_message.created_at, + fake_message.guaranteed_until, + )) + + def setUp(self): + super().setUp() + + self.projects_mock.get.return_value = self.fake_project + self.volume_messages_mock.list.return_value = self.fake_messages + # Get the command to test + self.cmd = volume_message.ListMessages(self.app, None) + + def test_message_list(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.3') + + arglist = [] + verifylist = [ + ('project', None), + ('marker', None), + ('limit', None), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + search_opts = { + 'project_id': None, + } + self.volume_messages_mock.list.assert_called_with( + search_opts=search_opts, + marker=None, + limit=None, + ) + self.assertEqual(self.columns, columns) + self.assertItemsEqual(self.data, list(data)) + + def test_message_list_with_options(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.3') + + arglist = [ + '--project', self.fake_project.name, + '--marker', self.fake_messages[0].id, + '--limit', '3', + ] + verifylist = [ + ('project', self.fake_project.name), + ('marker', self.fake_messages[0].id), + ('limit', 3), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + search_opts = { + 'project_id': self.fake_project.id, + } + self.volume_messages_mock.list.assert_called_with( + search_opts=search_opts, + marker=self.fake_messages[0].id, + limit=3, + ) + self.assertEqual(self.columns, columns) + self.assertItemsEqual(self.data, list(data)) + + def test_message_list_pre_v33(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.2') + + arglist = [] + verifylist = [ + ('project', None), + ('marker', None), + ('limit', None), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.3 or greater is required', + str(exc)) + + +class TestVolumeMessageShow(TestVolumeMessage): + + fake_message = volume_fakes.FakeVolumeMessage.create_one_volume_message() + + columns = ( + 'created_at', + 'event_id', + 'guaranteed_until', + 'id', + 'message_level', + 'request_id', + 'resource_type', + 'resource_uuid', + 'user_message', + ) + data = ( + fake_message.created_at, + fake_message.event_id, + fake_message.guaranteed_until, + fake_message.id, + fake_message.message_level, + fake_message.request_id, + fake_message.resource_type, + fake_message.resource_uuid, + fake_message.user_message, + ) + + def setUp(self): + super().setUp() + + self.volume_messages_mock.get.return_value = self.fake_message + # Get the command object to test + self.cmd = volume_message.ShowMessage(self.app, None) + + def test_message_show(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.3') + + arglist = [ + self.fake_message.id + ] + verifylist = [ + ('message_id', self.fake_message.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.volume_messages_mock.get.assert_called_with(self.fake_message.id) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_message_show_pre_v33(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.2') + + arglist = [ + self.fake_message.id + ] + verifylist = [ + ('message_id', self.fake_message.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.3 or greater is required', + str(exc)) diff -Nru python-openstackclient-5.5.0/openstackclient/volume/client.py python-openstackclient-5.6.0/openstackclient/volume/client.py --- python-openstackclient-5.5.0/openstackclient/volume/client.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/volume/client.py 2021-09-01 19:16:00.000000000 +0000 @@ -15,6 +15,7 @@ import logging +from osc_lib import exceptions from osc_lib import utils from openstackclient.i18n import _ @@ -29,9 +30,11 @@ "1": "cinderclient.v1.client.Client", "2": "cinderclient.v2.client.Client", "3": "cinderclient.v3.client.Client", - "3.42": "cinderclient.v3.client.Client", } +# Save the microversion if in use +_volume_api_version = None + def make_client(instance): """Returns a volume service client.""" @@ -42,16 +45,23 @@ from cinderclient.v3 import volume_snapshots from cinderclient.v3 import volumes - # Try a small import to check if cinderclient v1 is supported + # Check whether the available cinderclient supports v1 or v2 try: from cinderclient.v1 import services # noqa except Exception: del API_VERSIONS['1'] + try: + from cinderclient.v2 import services # noqa + except Exception: + del API_VERSIONS['2'] - version = instance._api_version[API_NAME] - from cinderclient import api_versions - # convert to APIVersion object - version = api_versions.get_api_version(version) + if _volume_api_version is not None: + version = _volume_api_version + else: + version = instance._api_version[API_NAME] + from cinderclient import api_versions + # convert to APIVersion object + version = api_versions.get_api_version(version) if version.ver_major == '1': # Monkey patch for v1 cinderclient @@ -99,3 +109,42 @@ '(Env: OS_VOLUME_API_VERSION)') % DEFAULT_API_VERSION ) return parser + + +def check_api_version(check_version): + """Validate version supplied by user + + Returns: + + * True if version is OK + * False if the version has not been checked and the previous plugin + check should be performed + * throws an exception if the version is no good + """ + + # Defer client imports until we actually need them + from cinderclient import api_versions + + global _volume_api_version + + # Copy some logic from novaclient 3.3.0 for basic version detection + # NOTE(dtroyer): This is only enough to resume operations using API + # version 3.0 or any valid version supplied by the user. + _volume_api_version = api_versions.get_api_version(check_version) + + # Bypass X.latest format microversion + if not _volume_api_version.is_latest(): + if _volume_api_version > api_versions.APIVersion("3.0"): + if not _volume_api_version.matches( + api_versions.MIN_VERSION, + api_versions.MAX_VERSION, + ): + msg = _("versions supported by client: %(min)s - %(max)s") % { + "min": api_versions.MIN_VERSION, + "max": api_versions.MAX_VERSION, + } + raise exceptions.CommandError(msg) + + return True + + return False diff -Nru python-openstackclient-5.5.0/openstackclient/volume/v1/volume.py python-openstackclient-5.6.0/openstackclient/volume/v1/volume.py --- python-openstackclient-5.5.0/openstackclient/volume/v1/volume.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/volume/v1/volume.py 2021-09-01 19:16:00.000000000 +0000 @@ -328,6 +328,13 @@ help=_('List additional fields in output'), ) parser.add_argument( + '--offset', + type=int, + action=parseractions.NonNegativeAction, + metavar='', + help=_('Index from which to start listing volumes'), + ) + parser.add_argument( '--limit', type=int, action=parseractions.NonNegativeAction, @@ -395,6 +402,9 @@ 'status': parsed_args.status, } + if parsed_args.offset: + search_opts['offset'] = parsed_args.offset + data = volume_client.volumes.list( search_opts=search_opts, limit=parsed_args.limit, diff -Nru python-openstackclient-5.5.0/openstackclient/volume/v2/volume_backup.py python-openstackclient-5.6.0/openstackclient/volume/v2/volume_backup.py --- python-openstackclient-5.5.0/openstackclient/volume/v2/volume_backup.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/volume/v2/volume_backup.py 2021-09-01 19:16:00.000000000 +0000 @@ -18,6 +18,7 @@ import functools import logging +from cinderclient import api_versions from cliff import columns as cliff_columns from osc_lib.cli import parseractions from osc_lib.command import command @@ -61,7 +62,7 @@ _description = _("Create new volume backup") def get_parser(self, prog_name): - parser = super(CreateVolumeBackup, self).get_parser(prog_name) + parser = super().get_parser(prog_name) parser.add_argument( "volume", metavar="", @@ -99,16 +100,67 @@ default=False, help=_("Perform an incremental backup") ) + parser.add_argument( + '--no-incremental', + action='store_false', + help=_("Do not perform an incremental backup") + ) + parser.add_argument( + '--property', + metavar='', + action=parseractions.KeyValueAction, + dest='properties', + help=_( + 'Set a property on this backup ' + '(repeat option to remove multiple values) ' + '(supported by --os-volume-api-version 3.43 or above)' + ), + ) + parser.add_argument( + '--availability-zone', + metavar='', + help=_( + 'AZ where the backup should be stored; by default it will be ' + 'the same as the source ' + '(supported by --os-volume-api-version 3.51 or above)' + ), + ) return parser def take_action(self, parsed_args): volume_client = self.app.client_manager.volume + volume_id = utils.find_resource( - volume_client.volumes, parsed_args.volume).id - snapshot_id = None + volume_client.volumes, parsed_args.volume, + ).id + + kwargs = {} + if parsed_args.snapshot: - snapshot_id = utils.find_resource( - volume_client.volume_snapshots, parsed_args.snapshot).id + kwargs['snapshot_id'] = utils.find_resource( + volume_client.volume_snapshots, parsed_args.snapshot, + ).id + + if parsed_args.properties: + if volume_client.api_version < api_versions.APIVersion('3.43'): + msg = _( + '--os-volume-api-version 3.43 or greater is required to ' + 'support the --property option' + ) + raise exceptions.CommandError(msg) + + kwargs['metadata'] = parsed_args.properties + + if parsed_args.availability_zone: + if volume_client.api_version < api_versions.APIVersion('3.51'): + msg = _( + '--os-volume-api-version 3.51 or greater is required to ' + 'support the --availability-zone option' + ) + raise exceptions.CommandError(msg) + + kwargs['availability_zone'] = parsed_args.availability_zone + backup = volume_client.backups.create( volume_id, container=parsed_args.container, @@ -116,7 +168,7 @@ description=parsed_args.description, force=parsed_args.force, incremental=parsed_args.incremental, - snapshot_id=snapshot_id, + **kwargs, ) backup._info.pop("links", None) return zip(*sorted(backup._info.items())) @@ -148,7 +200,8 @@ for i in parsed_args.backups: try: backup_id = utils.find_resource( - volume_client.backups, i).id + volume_client.backups, i, + ).id volume_client.backups.delete(backup_id, parsed_args.force) except Exception as e: result += 1 @@ -158,8 +211,9 @@ if result > 0: total = len(parsed_args.backups) - msg = (_("%(result)s of %(total)s backups failed " - "to delete.") % {'result': result, 'total': total}) + msg = _("%(result)s of %(total)s backups failed to delete.") % { + 'result': result, 'total': total, + } raise exceptions.CommandError(msg) @@ -182,17 +236,22 @@ parser.add_argument( "--status", metavar="", - choices=['creating', 'available', 'deleting', - 'error', 'restoring', 'error_restoring'], - help=_("Filters results by the backup status " - "('creating', 'available', 'deleting', " - "'error', 'restoring' or 'error_restoring')") + choices=[ + 'creating', 'available', 'deleting', + 'error', 'restoring', 'error_restoring', + ], + help=_( + "Filters results by the backup status, one of: " + "creating, available, deleting, error, restoring or " + "error_restoring" + ), ) parser.add_argument( "--volume", metavar="", - help=_("Filters results by the volume which they " - "backup (name or ID)") + help=_( + "Filters results by the volume which they backup (name or ID)" + ), ) parser.add_argument( '--marker', @@ -212,19 +271,30 @@ default=False, help=_('Include all projects (admin only)'), ) + # TODO(stephenfin): Add once we have an equivalent command for + # 'cinder list-filters' + # parser.add_argument( + # '--filter', + # metavar='', + # action=parseractions.KeyValueAction, + # dest='filters', + # help=_( + # "Filter key and value pairs. Use 'foo' to " + # "check enabled filters from server. Use 'key~=value' for " + # "inexact filtering if the key supports " + # "(supported by --os-volume-api-version 3.33 or above)" + # ), + # ) return parser def take_action(self, parsed_args): volume_client = self.app.client_manager.volume + columns = ('id', 'name', 'description', 'status', 'size') + column_headers = ('ID', 'Name', 'Description', 'Status', 'Size') if parsed_args.long: - columns = ['ID', 'Name', 'Description', 'Status', 'Size', - 'Availability Zone', 'Volume ID', 'Container'] - column_headers = copy.deepcopy(columns) - column_headers[6] = 'Volume' - else: - columns = ['ID', 'Name', 'Description', 'Status', 'Size'] - column_headers = columns + columns += ('availability_zone', 'volume_id', 'container') + column_headers += ('Availability Zone', 'Volume', 'Container') # Cache the volume list volume_cache = {} @@ -234,17 +304,22 @@ except Exception: # Just forget it if there's any trouble pass - _VolumeIdColumn = functools.partial(VolumeIdColumn, - volume_cache=volume_cache) + + _VolumeIdColumn = functools.partial( + VolumeIdColumn, volume_cache=volume_cache) filter_volume_id = None if parsed_args.volume: - filter_volume_id = utils.find_resource(volume_client.volumes, - parsed_args.volume).id + filter_volume_id = utils.find_resource( + volume_client.volumes, parsed_args.volume, + ).id + marker_backup_id = None if parsed_args.marker: - marker_backup_id = utils.find_resource(volume_client.backups, - parsed_args.marker).id + marker_backup_id = utils.find_resource( + volume_client.backups, parsed_args.marker, + ).id + search_opts = { 'name': parsed_args.name, 'status': parsed_args.status, @@ -257,11 +332,14 @@ limit=parsed_args.limit, ) - return (column_headers, - (utils.get_item_properties( - s, columns, - formatters={'Volume ID': _VolumeIdColumn}, - ) for s in data)) + return ( + column_headers, + ( + utils.get_item_properties( + s, columns, formatters={'volume_id': _VolumeIdColumn}, + ) for s in data + ), + ) class RestoreVolumeBackup(command.ShowOne): @@ -295,7 +373,7 @@ _description = _("Set volume backup properties") def get_parser(self, prog_name): - parser = super(SetVolumeBackup, self).get_parser(prog_name) + parser = super().get_parser(prog_name) parser.add_argument( "backup", metavar="", @@ -304,28 +382,58 @@ parser.add_argument( '--name', metavar='', - help=_('New backup name') + help=_( + 'New backup name' + '(supported by --os-volume-api-version 3.9 or above)' + ), ) parser.add_argument( '--description', metavar='', - help=_('New backup description') + help=_( + 'New backup description ' + '(supported by --os-volume-api-version 3.9 or above)' + ), ) parser.add_argument( '--state', metavar='', choices=['available', 'error'], - help=_('New backup state ("available" or "error") (admin only) ' - '(This option simply changes the state of the backup ' - 'in the database with no regard to actual status, ' - 'exercise caution when using)'), + help=_( + 'New backup state ("available" or "error") (admin only) ' + '(This option simply changes the state of the backup ' + 'in the database with no regard to actual status; ' + 'exercise caution when using)' + ), + ) + parser.add_argument( + '--no-property', + action='store_true', + help=_( + 'Remove all properties from this backup ' + '(specify both --no-property and --property to remove the ' + 'current properties before setting new properties)' + ), + ) + parser.add_argument( + '--property', + metavar='', + action=parseractions.KeyValueAction, + dest='properties', + default={}, + help=_( + 'Set a property on this backup ' + '(repeat option to set multiple values) ' + '(supported by --os-volume-api-version 3.43 or above)' + ), ) return parser def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - backup = utils.find_resource(volume_client.backups, - parsed_args.backup) + backup = utils.find_resource( + volume_client.backups, parsed_args.backup) + result = 0 if parsed_args.state: try: @@ -336,21 +444,119 @@ result += 1 kwargs = {} + if parsed_args.name: + if volume_client.api_version < api_versions.APIVersion('3.9'): + msg = _( + '--os-volume-api-version 3.9 or greater is required to ' + 'support the --name option' + ) + raise exceptions.CommandError(msg) + kwargs['name'] = parsed_args.name + if parsed_args.description: + if volume_client.api_version < api_versions.APIVersion('3.9'): + msg = _( + '--os-volume-api-version 3.9 or greater is required to ' + 'support the --description option' + ) + raise exceptions.CommandError(msg) + kwargs['description'] = parsed_args.description + + if parsed_args.no_property: + if volume_client.api_version < api_versions.APIVersion('3.43'): + msg = _( + '--os-volume-api-version 3.43 or greater is required to ' + 'support the --no-property option' + ) + raise exceptions.CommandError(msg) + + if parsed_args.properties: + if volume_client.api_version < api_versions.APIVersion('3.43'): + msg = _( + '--os-volume-api-version 3.43 or greater is required to ' + 'support the --property option' + ) + raise exceptions.CommandError(msg) + + if volume_client.api_version >= api_versions.APIVersion('3.43'): + metadata = copy.deepcopy(backup.metadata) + + if parsed_args.no_property: + metadata = {} + + metadata.update(parsed_args.properties) + kwargs['metadata'] = metadata + if kwargs: try: volume_client.backups.update(backup.id, **kwargs) except Exception as e: - LOG.error(_("Failed to update backup name " - "or description: %s"), e) + LOG.error("Failed to update backup: %s", e) result += 1 if result > 0: - raise exceptions.CommandError(_("One or more of the " - "set operations failed")) + msg = _("One or more of the set operations failed") + raise exceptions.CommandError(msg) + + +class UnsetVolumeBackup(command.Command): + """Unset volume backup properties. + + This command requires ``--os-volume-api-version`` 3.43 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'backup', + metavar='', + help=_('Backup to modify (name or ID)') + ) + parser.add_argument( + '--property', + metavar='', + action='append', + dest='properties', + help=_( + 'Property to remove from this backup ' + '(repeat option to unset multiple values) ' + ), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.43'): + msg = _( + '--os-volume-api-version 3.43 or greater is required to ' + 'support the --property option' + ) + raise exceptions.CommandError(msg) + + backup = utils.find_resource( + volume_client.backups, parsed_args.backup) + metadata = copy.deepcopy(backup.metadata) + + for key in parsed_args.properties: + if key not in metadata: + # ignore invalid properties but continue + LOG.warning( + "'%s' is not a valid property for backup '%s'", + key, parsed_args.backup, + ) + continue + + del metadata[key] + + kwargs = { + 'metadata': metadata, + } + + volume_client.backups.update(backup.id, **kwargs) class ShowVolumeBackup(command.ShowOne): diff -Nru python-openstackclient-5.5.0/openstackclient/volume/v2/volume_transfer_request.py python-openstackclient-5.6.0/openstackclient/volume/v2/volume_transfer_request.py --- python-openstackclient-5.5.0/openstackclient/volume/v2/volume_transfer_request.py 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/volume/v2/volume_transfer_request.py 2021-09-01 19:16:00.000000000 +0000 @@ -16,6 +16,7 @@ import logging +from cinderclient import api_versions from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -77,6 +78,25 @@ help=_('New transfer request name (default to None)'), ) parser.add_argument( + '--snapshots', + action='store_true', + dest='snapshots', + help=_( + 'Allow transfer volumes without snapshots (default) ' + '(supported by --os-volume-api-version 3.55 or later)' + ), + default=None, + ) + parser.add_argument( + '--no-snapshots', + action='store_false', + dest='snapshots', + help=_( + 'Disallow transfer volumes without snapshots ' + '(supported by --os-volume-api-version 3.55 or later)' + ), + ) + parser.add_argument( 'volume', metavar="", help=_('Volume to transfer (name or ID)'), @@ -85,6 +105,21 @@ def take_action(self, parsed_args): volume_client = self.app.client_manager.volume + + kwargs = {} + + if parsed_args.snapshots is not None: + if volume_client.api_version < api_versions.APIVersion('3.55'): + msg = _( + "--os-volume-api-version 3.55 or greater is required to " + "support the '--(no-)snapshots' option" + ) + raise exceptions.CommandError(msg) + + # unfortunately this option is negative so we have to reverse + # things + kwargs['no_snapshots'] = not parsed_args.snapshots + volume_id = utils.find_resource( volume_client.volumes, parsed_args.volume, @@ -92,6 +127,7 @@ volume_transfer_request = volume_client.transfers.create( volume_id, parsed_args.name, + **kwargs, ) volume_transfer_request._info.pop("links", None) diff -Nru python-openstackclient-5.5.0/openstackclient/volume/v3/volume_attachment.py python-openstackclient-5.6.0/openstackclient/volume/v3/volume_attachment.py --- python-openstackclient-5.5.0/openstackclient/volume/v3/volume_attachment.py 1970-01-01 00:00:00.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/volume/v3/volume_attachment.py 2021-09-01 19:16:00.000000000 +0000 @@ -0,0 +1,511 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging + +from cinderclient import api_versions +from osc_lib.cli import format_columns +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.i18n import _ +from openstackclient.identity import common as identity_common + +LOG = logging.getLogger(__name__) + +_FILTER_DEPRECATED = _( + "This option is deprecated. Consider using the '--filters' option which " + "was introduced in microversion 3.33 instead." +) + + +def _format_attachment(attachment): + columns = ( + 'id', + 'volume_id', + 'instance', + 'status', + 'attach_mode', + 'attached_at', + 'detached_at', + 'connection_info', + ) + column_headers = ( + 'ID', + 'Volume ID', + 'Instance ID', + 'Status', + 'Attach Mode', + 'Attached At', + 'Detached At', + 'Properties', + ) + + # TODO(stephenfin): Improve output with the nested connection_info + # field - cinderclient printed two things but that's equally ugly + return ( + column_headers, + utils.get_item_properties( + attachment, + columns, + formatters={ + 'connection_info': format_columns.DictColumn, + }, + ), + ) + + +class CreateVolumeAttachment(command.ShowOne): + """Create an attachment for a volume. + + This command will only create a volume attachment in the Volume service. It + will not invoke the necessary Compute service actions to actually attach + the volume to the server at the hypervisor level. As a result, it should + typically only be used for troubleshooting issues with an existing server + in combination with other tooling. For all other use cases, the 'server + volume add' command should be preferred. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'volume', + metavar='', + help=_('Name or ID of volume to attach to server.'), + ) + parser.add_argument( + 'server', + metavar='', + help=_('Name or ID of server to attach volume to.'), + ) + parser.add_argument( + '--connect', + action='store_true', + dest='connect', + default=False, + help=_('Make an active connection using provided connector info'), + ) + parser.add_argument( + '--no-connect', + action='store_false', + dest='connect', + help=_( + 'Do not make an active connection using provided connector ' + 'info' + ), + ) + parser.add_argument( + '--initiator', + metavar='', + help=_('IQN of the initiator attaching to'), + ) + parser.add_argument( + '--ip', + metavar='', + help=_('IP of the system attaching to'), + ) + parser.add_argument( + '--host', + metavar='', + help=_('Name of the host attaching to'), + ) + parser.add_argument( + '--platform', + metavar='', + help=_('Platform type'), + ) + parser.add_argument( + '--os-type', + metavar='', + help=_('OS type'), + ) + parser.add_argument( + '--multipath', + action='store_true', + dest='multipath', + default=False, + help=_('Use multipath'), + ) + parser.add_argument( + '--no-multipath', + action='store_false', + dest='multipath', + help=_('Use multipath'), + ) + parser.add_argument( + '--mountpoint', + metavar='', + help=_('Mountpoint volume will be attached at'), + ) + parser.add_argument( + '--mode', + metavar='', + help=_( + 'Mode of volume attachment, rw, ro and null, where null ' + 'indicates we want to honor any existing admin-metadata ' + 'settings ' + '(supported by --os-volume-api-version 3.54 or later)' + ), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + compute_client = self.app.client_manager.compute + + if volume_client.api_version < api_versions.APIVersion('3.27'): + msg = _( + "--os-volume-api-version 3.27 or greater is required to " + "support the 'volume attachment create' command" + ) + raise exceptions.CommandError(msg) + + if parsed_args.mode: + if volume_client.api_version < api_versions.APIVersion('3.54'): + msg = _( + "--os-volume-api-version 3.54 or greater is required to " + "support the '--mode' option" + ) + raise exceptions.CommandError(msg) + + connector = {} + if parsed_args.connect: + connector = { + 'initiator': parsed_args.initiator, + 'ip': parsed_args.ip, + 'platform': parsed_args.platform, + 'host': parsed_args.host, + 'os_type': parsed_args.os_type, + 'multipath': parsed_args.multipath, + 'mountpoint': parsed_args.mountpoint, + } + else: + if any({ + parsed_args.initiator, + parsed_args.ip, + parsed_args.platform, + parsed_args.host, + parsed_args.host, + parsed_args.multipath, + parsed_args.mountpoint, + }): + msg = _( + 'You must specify the --connect option for any of the ' + 'connection-specific options such as --initiator to be ' + 'valid' + ) + raise exceptions.CommandError(msg) + + volume = utils.find_resource( + volume_client.volumes, + parsed_args.volume, + ) + server = utils.find_resource( + compute_client.servers, + parsed_args.server, + ) + + attachment = volume_client.attachments.create( + volume.id, connector, server.id, parsed_args.mode) + + return _format_attachment(attachment) + + +class DeleteVolumeAttachment(command.Command): + """Delete an attachment for a volume. + + Similarly to the 'volume attachment create' command, this command will only + delete the volume attachment record in the Volume service. It will not + invoke the necessary Compute service actions to actually attach the volume + to the server at the hypervisor level. As a result, it should typically + only be used for troubleshooting issues with an existing server in + combination with other tooling. For all other use cases, the 'server volume + remove' command should be preferred. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'attachment', + metavar='', + help=_('ID of volume attachment to delete'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.27'): + msg = _( + "--os-volume-api-version 3.27 or greater is required to " + "support the 'volume attachment delete' command" + ) + raise exceptions.CommandError(msg) + + volume_client.attachments.delete(parsed_args.attachment) + + +class SetVolumeAttachment(command.ShowOne): + """Update an attachment for a volume. + + This call is designed to be more of an volume attachment completion than + anything else. It expects the value of a connector object to notify the + driver that the volume is going to be connected and where it's being + connected to. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'attachment', + metavar='', + help=_('ID of volume attachment.'), + ) + parser.add_argument( + '--initiator', + metavar='', + help=_('IQN of the initiator attaching to'), + ) + parser.add_argument( + '--ip', + metavar='', + help=_('IP of the system attaching to'), + ) + parser.add_argument( + '--host', + metavar='', + help=_('Name of the host attaching to'), + ) + parser.add_argument( + '--platform', + metavar='', + help=_('Platform type'), + ) + parser.add_argument( + '--os-type', + metavar='', + help=_('OS type'), + ) + parser.add_argument( + '--multipath', + action='store_true', + dest='multipath', + default=False, + help=_('Use multipath'), + ) + parser.add_argument( + '--no-multipath', + action='store_false', + dest='multipath', + help=_('Use multipath'), + ) + parser.add_argument( + '--mountpoint', + metavar='', + help=_('Mountpoint volume will be attached at'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.27'): + msg = _( + "--os-volume-api-version 3.27 or greater is required to " + "support the 'volume attachment set' command" + ) + raise exceptions.CommandError(msg) + + connector = { + 'initiator': parsed_args.initiator, + 'ip': parsed_args.ip, + 'platform': parsed_args.platform, + 'host': parsed_args.host, + 'os_type': parsed_args.os_type, + 'multipath': parsed_args.multipath, + 'mountpoint': parsed_args.mountpoint, + } + + attachment = volume_client.attachments.update( + parsed_args.attachment, connector) + + return _format_attachment(attachment) + + +class CompleteVolumeAttachment(command.Command): + """Complete an attachment for a volume.""" + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'attachment', + metavar='', + help=_('ID of volume attachment to mark as completed'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.44'): + msg = _( + "--os-volume-api-version 3.44 or greater is required to " + "support the 'volume attachment complete' command" + ) + raise exceptions.CommandError(msg) + + volume_client.attachments.complete(parsed_args.attachment) + + +class ListVolumeAttachment(command.Lister): + """Lists all volume attachments.""" + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + '--project', + dest='project', + metavar='', + help=_('Filter results by project (name or ID) (admin only)'), + ) + identity_common.add_project_domain_option_to_parser(parser) + parser.add_argument( + '--all-projects', + dest='all_projects', + action='store_true', + default=utils.env('ALL_PROJECTS', default=False), + help=_('Shows details for all projects (admin only).'), + ) + parser.add_argument( + '--volume-id', + metavar='', + default=None, + help=_('Filters results by a volume ID. ') + _FILTER_DEPRECATED, + ) + parser.add_argument( + '--status', + metavar='', + help=_('Filters results by a status. ') + _FILTER_DEPRECATED, + ) + parser.add_argument( + '--marker', + metavar='', + help=_( + 'Begin returning volume attachments that appear later in ' + 'volume attachment list than that represented by this ID.' + ), + ) + parser.add_argument( + '--limit', + type=int, + metavar='', + help=_('Maximum number of volume attachments to return.'), + ) + # TODO(stephenfin): Add once we have an equivalent command for + # 'cinder list-filters' + # parser.add_argument( + # '--filter', + # metavar='', + # action=parseractions.KeyValueAction, + # dest='filters', + # help=_( + # "Filter key and value pairs. Use 'foo' to " + # "check enabled filters from server. Use 'key~=value' for " + # "inexact filtering if the key supports " + # "(supported by --os-volume-api-version 3.33 or above)" + # ), + # ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + identity_client = self.app.client_manager.identity + + if volume_client.api_version < api_versions.APIVersion('3.27'): + msg = _( + "--os-volume-api-version 3.27 or greater is required to " + "support the 'volume attachment list' command" + ) + raise exceptions.CommandError(msg) + + project_id = None + if parsed_args.project: + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + + search_opts = { + 'all_tenants': True if project_id else parsed_args.all_projects, + 'project_id': project_id, + 'status': parsed_args.status, + 'volume_id': parsed_args.volume_id, + } + # Update search option with `filters` + # if AppendFilters.filters: + # search_opts.update(shell_utils.extract_filters(AppendFilters.filters)) + + # TODO(stephenfin): Implement sorting + attachments = volume_client.attachments.list( + search_opts=search_opts, + marker=parsed_args.marker, + limit=parsed_args.limit) + + column_headers = ( + 'ID', + 'Volume ID', + 'Server ID', + 'Status', + ) + columns = ( + 'id', + 'volume_id', + 'instance', + 'status', + ) + + return ( + column_headers, + ( + utils.get_item_properties(a, columns) + for a in attachments + ), + ) + + +class ShowVolumeAttachment(command.ShowOne): + """Show detailed information for a volume attachment.""" + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'attachment', + metavar='', + help=_('ID of volume attachment.'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.27'): + msg = _( + "--os-volume-api-version 3.27 or greater is required to " + "support the 'volume attachment show' command" + ) + raise exceptions.CommandError(msg) + + attachment = volume_client.attachments.show(parsed_args.attachment) + + return _format_attachment(attachment) diff -Nru python-openstackclient-5.5.0/openstackclient/volume/v3/volume_group.py python-openstackclient-5.6.0/openstackclient/volume/v3/volume_group.py --- python-openstackclient-5.5.0/openstackclient/volume/v3/volume_group.py 1970-01-01 00:00:00.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/volume/v3/volume_group.py 2021-09-01 19:16:00.000000000 +0000 @@ -0,0 +1,506 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging + +from cinderclient import api_versions +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.i18n import _ + +LOG = logging.getLogger(__name__) + + +def _format_group(group): + columns = ( + 'id', + 'status', + 'name', + 'description', + 'group_type', + 'volume_types', + 'availability_zone', + 'created_at', + 'volumes', + 'group_snapshot_id', + 'source_group_id', + ) + column_headers = ( + 'ID', + 'Status', + 'Name', + 'Description', + 'Group Type', + 'Volume Types', + 'Availability Zone', + 'Created At', + 'Volumes', + 'Group Snapshot ID', + 'Source Group ID', + ) + + # TODO(stephenfin): Consider using a formatter for volume_types since it's + # a list + return ( + column_headers, + utils.get_item_properties( + group, + columns, + ), + ) + + +class CreateVolumeGroup(command.ShowOne): + """Create a volume group. + + Generic volume groups enable you to create a group of volumes and manage + them together. + + Generic volume groups are more flexible than consistency groups. Currently + volume consistency groups only support consistent group snapshot. It + cannot be extended easily to serve other purposes. A project may want to + put volumes used in the same application together in a group so that it is + easier to manage them together, and this group of volumes may or may not + support consistent group snapshot. Generic volume group solve this problem. + By decoupling the tight relationship between the group construct and the + consistency concept, generic volume groups can be extended to support other + features in the future. + + This command requires ``--os-volume-api-version`` 3.13 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'volume_group_type', + metavar='', + help=_('Name or ID of volume group type to use.'), + ) + parser.add_argument( + 'volume_types', + metavar='', + nargs='+', + default=[], + help=_('Name or ID of volume type(s) to use.'), + ) + parser.add_argument( + '--name', + metavar='', + help=_('Name of the volume group.'), + ) + parser.add_argument( + '--description', + metavar='', + help=_('Description of a volume group.') + ) + parser.add_argument( + '--availability-zone', + metavar='', + help=_('Availability zone for volume group.'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.13'): + msg = _( + "--os-volume-api-version 3.13 or greater is required to " + "support the 'volume group create' command" + ) + raise exceptions.CommandError(msg) + + volume_group_type = utils.find_resource( + volume_client.group_types, + parsed_args.volume_group_type, + ) + + volume_types = [] + for volume_type in parsed_args.volume_types: + volume_types.append( + utils.find_resource( + volume_client.volume_types, + volume_type, + ) + ) + + group = volume_client.groups.create( + volume_group_type.id, + ','.join(x.id for x in volume_types), + parsed_args.name, + parsed_args.description, + availability_zone=parsed_args.availability_zone) + + group = volume_client.groups.get(group.id) + + return _format_group(group) + + +class DeleteVolumeGroup(command.Command): + """Delete a volume group. + + This command requires ``--os-volume-api-version`` 3.13 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'group', + metavar='', + help=_('Name or ID of volume group to delete'), + ) + parser.add_argument( + '--force', + action='store_true', + default=False, + help=_( + 'Delete the volume group even if it contains volumes. ' + 'This will delete any remaining volumes in the group.', + ) + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.13'): + msg = _( + "--os-volume-api-version 3.13 or greater is required to " + "support the 'volume group delete' command" + ) + raise exceptions.CommandError(msg) + + group = utils.find_resource( + volume_client.groups, + parsed_args.group, + ) + + volume_client.groups.delete( + group.id, delete_volumes=parsed_args.force) + + +class SetVolumeGroup(command.ShowOne): + """Update a volume group. + + This command requires ``--os-volume-api-version`` 3.13 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'group', + metavar='', + help=_('Name or ID of volume group.'), + ) + parser.add_argument( + '--name', + metavar='', + help=_('New name for group.'), + ) + parser.add_argument( + '--description', + metavar='', + help=_('New description for group.'), + ) + parser.add_argument( + '--enable-replication', + action='store_true', + dest='enable_replication', + default=None, + help=_( + 'Enable replication for group. ' + '(supported by --os-volume-api-version 3.38 or above)' + ), + ) + parser.add_argument( + '--disable-replication', + action='store_false', + dest='enable_replication', + help=_( + 'Disable replication for group. ' + '(supported by --os-volume-api-version 3.38 or above)' + ), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.13'): + msg = _( + "--os-volume-api-version 3.13 or greater is required to " + "support the 'volume group set' command" + ) + raise exceptions.CommandError(msg) + + group = utils.find_resource( + volume_client.groups, + parsed_args.group, + ) + + if parsed_args.enable_replication is not None: + if volume_client.api_version < api_versions.APIVersion('3.38'): + msg = _( + "--os-volume-api-version 3.38 or greater is required to " + "support the '--enable-replication' or " + "'--disable-replication' options" + ) + raise exceptions.CommandError(msg) + + if parsed_args.enable_replication: + volume_client.groups.enable_replication(group.id) + else: + volume_client.groups.disable_replication(group.id) + + kwargs = {} + + if parsed_args.name is not None: + kwargs['name'] = parsed_args.name + + if parsed_args.description is not None: + kwargs['description'] = parsed_args.description + + if kwargs: + group = volume_client.groups.update(group.id, **kwargs) + + return _format_group(group) + + +class ListVolumeGroup(command.Lister): + """Lists all volume groups. + + This command requires ``--os-volume-api-version`` 3.13 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + '--all-projects', + dest='all_projects', + action='store_true', + default=utils.env('ALL_PROJECTS', default=False), + help=_('Shows details for all projects (admin only).'), + ) + # TODO(stephenfin): Add once we have an equivalent command for + # 'cinder list-filters' + # parser.add_argument( + # '--filter', + # metavar='', + # action=parseractions.KeyValueAction, + # dest='filters', + # help=_( + # "Filter key and value pairs. Use 'foo' to " + # "check enabled filters from server. Use 'key~=value' for " + # "inexact filtering if the key supports " + # "(supported by --os-volume-api-version 3.33 or above)" + # ), + # ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.13'): + msg = _( + "--os-volume-api-version 3.13 or greater is required to " + "support the 'volume group list' command" + ) + raise exceptions.CommandError(msg) + + search_opts = { + 'all_tenants': parsed_args.all_projects, + } + + groups = volume_client.groups.list( + search_opts=search_opts) + + column_headers = ( + 'ID', + 'Status', + 'Name', + ) + columns = ( + 'id', + 'status', + 'name', + ) + + return ( + column_headers, + ( + utils.get_item_properties(a, columns) + for a in groups + ), + ) + + +class ShowVolumeGroup(command.ShowOne): + """Show detailed information for a volume group. + + This command requires ``--os-volume-api-version`` 3.13 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'group', + metavar='', + help=_('Name or ID of volume group.'), + ) + parser.add_argument( + '--volumes', + action='store_true', + dest='show_volumes', + default=None, + help=_( + 'Show volumes included in the group. ' + '(supported by --os-volume-api-version 3.25 or above)' + ), + ) + parser.add_argument( + '--no-volumes', + action='store_false', + dest='show_volumes', + help=_( + 'Do not show volumes included in the group. ' + '(supported by --os-volume-api-version 3.25 or above)' + ), + ) + parser.add_argument( + '--replication-targets', + action='store_true', + dest='show_replication_targets', + default=None, + help=_( + 'Show replication targets for the group. ' + '(supported by --os-volume-api-version 3.38 or above)' + ), + ) + parser.add_argument( + '--no-replication-targets', + action='store_false', + dest='show_replication_targets', + help=_( + 'Do not show replication targets for the group. ' + '(supported by --os-volume-api-version 3.38 or above)' + ), + ) + + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.13'): + msg = _( + "--os-volume-api-version 3.13 or greater is required to " + "support the 'volume group show' command" + ) + raise exceptions.CommandError(msg) + + kwargs = {} + + if parsed_args.show_volumes is not None: + if volume_client.api_version < api_versions.APIVersion('3.25'): + msg = _( + "--os-volume-api-version 3.25 or greater is required to " + "support the '--(no-)volumes' option" + ) + raise exceptions.CommandError(msg) + + kwargs['list_volume'] = parsed_args.show_volumes + + if parsed_args.show_replication_targets is not None: + if volume_client.api_version < api_versions.APIVersion('3.38'): + msg = _( + "--os-volume-api-version 3.38 or greater is required to " + "support the '--(no-)replication-targets' option" + ) + raise exceptions.CommandError(msg) + + group = utils.find_resource( + volume_client.groups, + parsed_args.group, + ) + + group = volume_client.groups.show(group.id, **kwargs) + + if parsed_args.show_replication_targets: + replication_targets = \ + volume_client.groups.list_replication_targets(group.id) + + group.replication_targets = replication_targets + + # TODO(stephenfin): Show replication targets + return _format_group(group) + + +class FailoverVolumeGroup(command.Command): + """Failover replication for a volume group. + + This command requires ``--os-volume-api-version`` 3.38 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'group', + metavar='', + help=_('Name or ID of volume group to failover replication for.'), + ) + parser.add_argument( + '--allow-attached-volume', + action='store_true', + dest='allow_attached_volume', + default=False, + help=_( + 'Allow group with attached volumes to be failed over.', + ) + ) + parser.add_argument( + '--disallow-attached-volume', + action='store_false', + dest='allow_attached_volume', + default=False, + help=_( + 'Disallow group with attached volumes to be failed over.', + ) + ) + parser.add_argument( + '--secondary-backend-id', + metavar='', + help=_('Secondary backend ID.'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.38'): + msg = _( + "--os-volume-api-version 3.38 or greater is required to " + "support the 'volume group failover' command" + ) + raise exceptions.CommandError(msg) + + group = utils.find_resource( + volume_client.groups, + parsed_args.group, + ) + + volume_client.groups.failover_replication( + group.id, + allow_attached_volume=parsed_args.allow_attached_volume, + secondary_backend_id=parsed_args.secondary_backend_id, + ) diff -Nru python-openstackclient-5.5.0/openstackclient/volume/v3/volume_group_snapshot.py python-openstackclient-5.6.0/openstackclient/volume/v3/volume_group_snapshot.py --- python-openstackclient-5.5.0/openstackclient/volume/v3/volume_group_snapshot.py 1970-01-01 00:00:00.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/volume/v3/volume_group_snapshot.py 2021-09-01 19:16:00.000000000 +0000 @@ -0,0 +1,234 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging + +from cinderclient import api_versions +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.i18n import _ + +LOG = logging.getLogger(__name__) + + +def _format_group_snapshot(snapshot): + columns = ( + 'id', + 'status', + 'name', + 'description', + 'group_id', + 'group_type_id', + ) + column_headers = ( + 'ID', + 'Status', + 'Name', + 'Description', + 'Group', + 'Group Type', + ) + + return ( + column_headers, + utils.get_item_properties( + snapshot, + columns, + ), + ) + + +class CreateVolumeGroupSnapshot(command.ShowOne): + """Create a volume group snapshot. + + This command requires ``--os-volume-api-version`` 3.13 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'volume_group', + metavar='', + help=_('Name or ID of volume group to create a snapshot of.'), + ) + parser.add_argument( + '--name', + metavar='', + help=_('Name of the volume group snapshot.'), + ) + parser.add_argument( + '--description', + metavar='', + help=_('Description of a volume group snapshot.') + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.14'): + msg = _( + "--os-volume-api-version 3.14 or greater is required to " + "support the 'volume group snapshot create' command" + ) + raise exceptions.CommandError(msg) + + volume_group = utils.find_resource( + volume_client.groups, + parsed_args.volume_group, + ) + + snapshot = volume_client.group_snapshots.create( + volume_group.id, + parsed_args.name, + parsed_args.description) + + return _format_group_snapshot(snapshot) + + +class DeleteVolumeGroupSnapshot(command.Command): + """Delete a volume group snapshot. + + This command requires ``--os-volume-api-version`` 3.14 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'snapshot', + metavar='', + help=_('Name or ID of volume group snapshot to delete'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.14'): + msg = _( + "--os-volume-api-version 3.14 or greater is required to " + "support the 'volume group snapshot delete' command" + ) + raise exceptions.CommandError(msg) + + snapshot = utils.find_resource( + volume_client.group_snapshots, + parsed_args.snapshot, + ) + + volume_client.group_snapshots.delete(snapshot.id) + + +class ListVolumeGroupSnapshot(command.Lister): + """Lists all volume group snapshot. + + This command requires ``--os-volume-api-version`` 3.14 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + '--all-projects', + dest='all_projects', + action='store_true', + default=utils.env('ALL_PROJECTS', default=False), + help=_('Shows details for all projects (admin only).'), + ) + # TODO(stephenfin): Add once we have an equivalent command for + # 'cinder list-filters' + # parser.add_argument( + # '--filter', + # metavar='', + # action=parseractions.KeyValueAction, + # dest='filters', + # help=_( + # "Filter key and value pairs. Use 'foo' to " + # "check enabled filters from server. Use 'key~=value' for " + # "inexact filtering if the key supports " + # "(supported by --os-volume-api-version 3.33 or above)" + # ), + # ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.14'): + msg = _( + "--os-volume-api-version 3.14 or greater is required to " + "support the 'volume group snapshot list' command" + ) + raise exceptions.CommandError(msg) + + search_opts = { + 'all_tenants': parsed_args.all_projects, + } + + groups = volume_client.group_snapshots.list( + search_opts=search_opts) + + column_headers = ( + 'ID', + 'Status', + 'Name', + ) + columns = ( + 'id', + 'status', + 'name', + ) + + return ( + column_headers, + ( + utils.get_item_properties(a, columns) + for a in groups + ), + ) + + +class ShowVolumeGroupSnapshot(command.ShowOne): + """Show detailed information for a volume group snapshot. + + This command requires ``--os-volume-api-version`` 3.14 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'snapshot', + metavar='', + help=_('Name or ID of volume group snapshot.'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.14'): + msg = _( + "--os-volume-api-version 3.14 or greater is required to " + "support the 'volume group snapshot show' command" + ) + raise exceptions.CommandError(msg) + + snapshot = utils.find_resource( + volume_client.group_snapshots, + parsed_args.snapshot, + ) + + # TODO(stephenfin): Do we need this? + snapshot = volume_client.groups.show(snapshot.id) + + return _format_group_snapshot(snapshot) diff -Nru python-openstackclient-5.5.0/openstackclient/volume/v3/volume_group_type.py python-openstackclient-5.6.0/openstackclient/volume/v3/volume_group_type.py --- python-openstackclient-5.5.0/openstackclient/volume/v3/volume_group_type.py 1970-01-01 00:00:00.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/volume/v3/volume_group_type.py 2021-09-01 19:16:00.000000000 +0000 @@ -0,0 +1,410 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging + +from cinderclient import api_versions +from osc_lib.cli import format_columns +from osc_lib.cli import parseractions +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.i18n import _ + +LOG = logging.getLogger(__name__) + + +def _format_group_type(group): + columns = ( + 'id', + 'name', + 'description', + 'is_public', + 'group_specs', + ) + column_headers = ( + 'ID', + 'Name', + 'Description', + 'Is Public', + 'Properties', + ) + + # TODO(stephenfin): Consider using a formatter for volume_types since it's + # a list + return ( + column_headers, + utils.get_item_properties( + group, + columns, + formatters={ + 'group_specs': format_columns.DictColumn, + }, + ), + ) + + +class CreateVolumeGroupType(command.ShowOne): + """Create a volume group type. + + This command requires ``--os-volume-api-version`` 3.11 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'name', + metavar='', + help=_('Name of new volume group type.'), + ) + parser.add_argument( + '--description', + metavar='', + help=_('Description of the volume group type.') + ) + type_group = parser.add_mutually_exclusive_group() + type_group.add_argument( + '--public', + dest='is_public', + action='store_true', + default=True, + help=_( + 'Volume group type is available to other projects (default)' + ), + ) + type_group.add_argument( + '--private', + dest='is_public', + action='store_false', + help=_('Volume group type is not available to other projects') + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.11'): + msg = _( + "--os-volume-api-version 3.11 or greater is required to " + "support the 'volume group type create' command" + ) + raise exceptions.CommandError(msg) + + group_type = volume_client.group_types.create( + parsed_args.name, + parsed_args.description, + parsed_args.is_public) + + return _format_group_type(group_type) + + +class DeleteVolumeGroupType(command.Command): + """Delete a volume group type. + + This command requires ``--os-volume-api-version`` 3.11 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'group_type', + metavar='', + help=_('Name or ID of volume group type to delete'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.11'): + msg = _( + "--os-volume-api-version 3.11 or greater is required to " + "support the 'volume group type delete' command" + ) + raise exceptions.CommandError(msg) + + group_type = utils.find_resource( + volume_client.group_types, + parsed_args.group_type, + ) + + volume_client.group_types.delete(group_type.id) + + +class SetVolumeGroupType(command.ShowOne): + """Update a volume group type. + + This command requires ``--os-volume-api-version`` 3.11 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'group_type', + metavar='', + help=_('Name or ID of volume group type.'), + ) + parser.add_argument( + '--name', + metavar='', + help=_('New name for volume group type.'), + ) + parser.add_argument( + '--description', + metavar='', + help=_('New description for volume group type.'), + ) + type_group = parser.add_mutually_exclusive_group() + type_group.add_argument( + '--public', + dest='is_public', + action='store_true', + default=None, + help=_('Make volume group type available to other projects.'), + ) + type_group.add_argument( + '--private', + dest='is_public', + action='store_false', + help=_('Make volume group type unavailable to other projects.') + ) + parser.add_argument( + '--no-property', + action='store_true', + help=_( + 'Remove all properties from this volume group type ' + '(specify both --no-property and --property ' + 'to remove the current properties before setting ' + 'new properties)' + ), + ) + parser.add_argument( + '--property', + metavar='', + action=parseractions.KeyValueAction, + dest='properties', + help=_( + 'Property to add or modify for this volume group type ' + '(repeat option to set multiple properties)' + ), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.11'): + msg = _( + "--os-volume-api-version 3.11 or greater is required to " + "support the 'volume group type set' command" + ) + raise exceptions.CommandError(msg) + + group_type = utils.find_resource( + volume_client.group_types, + parsed_args.group_type, + ) + + kwargs = {} + errors = 0 + + if parsed_args.name is not None: + kwargs['name'] = parsed_args.name + + if parsed_args.description is not None: + kwargs['description'] = parsed_args.description + + if parsed_args.is_public is not None: + kwargs['is_public'] = parsed_args.is_public + + if kwargs: + try: + group_type = volume_client.group_types.update( + group_type.id, **kwargs) + except Exception as e: + LOG.error(_("Failed to update group type: %s"), e) + errors += 1 + + if parsed_args.no_property: + try: + keys = group_type.get_keys().keys() + group_type.unset_keys(keys) + except Exception as e: + LOG.error(_("Failed to clear group type properties: %s"), e) + errors += 1 + + if parsed_args.properties: + try: + group_type.set_keys(parsed_args.properties) + except Exception as e: + LOG.error(_("Failed to set group type properties: %s"), e) + errors += 1 + + if errors > 0: + msg = _( + "Command Failed: One or more of the operations failed" + ) + raise exceptions.CommandError() + + return _format_group_type(group_type) + + +class UnsetVolumeGroupType(command.ShowOne): + """Unset properties of a volume group type. + + This command requires ``--os-volume-api-version`` 3.11 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'group_type', + metavar='', + help=_('Name or ID of volume group type.'), + ) + parser.add_argument( + '--property', + metavar='', + action='append', + dest='properties', + help=_( + 'Property to remove from this volume group type ' + '(repeat option to unset multiple properties)' + ), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.11'): + msg = _( + "--os-volume-api-version 3.11 or greater is required to " + "support the 'volume group type unset' command" + ) + raise exceptions.CommandError(msg) + + group_type = utils.find_resource( + volume_client.group_types, + parsed_args.group_type, + ) + + group_type.unset_keys(parsed_args.properties) + + group_type = utils.find_resource( + volume_client.group_types, + parsed_args.group_type, + ) + + return _format_group_type(group_type) + + +class ListVolumeGroupType(command.Lister): + """Lists all volume group types. + + This command requires ``--os-volume-api-version`` 3.11 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + '--default', + action='store_true', + dest='show_default', + default=False, + help=_('List the default volume group type.'), + ) + # TODO(stephenfin): Add once we have an equivalent command for + # 'cinder list-filters' + # parser.add_argument( + # '--filter', + # metavar='', + # action=parseractions.KeyValueAction, + # dest='filters', + # help=_( + # "Filter key and value pairs. Use 'foo' to " + # "check enabled filters from server. Use 'key~=value' for " + # "inexact filtering if the key supports " + # "(supported by --os-volume-api-version 3.33 or above)" + # ), + # ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.11'): + msg = _( + "--os-volume-api-version 3.11 or greater is required to " + "support the 'volume group type list' command" + ) + raise exceptions.CommandError(msg) + + if parsed_args.show_default: + group_types = [volume_client.group_types.default()] + else: + group_types = volume_client.group_types.list() + + column_headers = ( + 'ID', + 'Name', + 'Is Public', + 'Properties', + ) + columns = ( + 'id', + 'name', + 'is_public', + 'group_specs', + ) + + return ( + column_headers, + ( + utils.get_item_properties(a, columns) + for a in group_types + ), + ) + + +class ShowVolumeGroupType(command.ShowOne): + """Show detailed information for a volume group type. + + This command requires ``--os-volume-api-version`` 3.11 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'group_type', + metavar='', + help=_('Name or ID of volume group type.'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.11'): + msg = _( + "--os-volume-api-version 3.11 or greater is required to " + "support the 'volume group type show' command" + ) + raise exceptions.CommandError(msg) + + group_type = utils.find_resource( + volume_client.group_types, + parsed_args.group, + ) + + return _format_group_type(group_type) diff -Nru python-openstackclient-5.5.0/openstackclient/volume/v3/volume_message.py python-openstackclient-5.6.0/openstackclient/volume/v3/volume_message.py --- python-openstackclient-5.5.0/openstackclient/volume/v3/volume_message.py 1970-01-01 00:00:00.000000000 +0000 +++ python-openstackclient-5.6.0/openstackclient/volume/v3/volume_message.py 2021-09-01 19:16:00.000000000 +0000 @@ -0,0 +1,165 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +"""Volume V3 Messages implementations""" + +import logging as LOG + +from cinderclient import api_versions +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.i18n import _ +from openstackclient.identity import common as identity_common + + +class DeleteMessage(command.Command): + _description = _('Delete a volume failure message') + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'message_ids', + metavar='', + nargs='+', + help=_('Message(s) to delete (ID)') + ) + + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.3'): + msg = _( + "--os-volume-api-version 3.3 or greater is required to " + "support the 'volume message delete' command" + ) + raise exceptions.CommandError(msg) + + errors = 0 + for message_id in parsed_args.message_ids: + try: + volume_client.messages.delete(message_id) + except Exception: + LOG.error(_('Failed to delete message: %s'), message_id) + errors += 1 + + if errors > 0: + total = len(parsed_args.message_ids) + msg = _('Failed to delete %(errors)s of %(total)s messages.') % { + 'errors': errors, 'total': total, + } + raise exceptions.CommandError(msg) + + +class ListMessages(command.Lister): + _description = _('List volume failure messages') + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + + parser.add_argument( + '--project', + metavar='', + help=_('Filter results by project (name or ID) (admin only)'), + ) + identity_common.add_project_domain_option_to_parser(parser) + parser.add_argument( + '--marker', + metavar='', + help=_('The last message ID of the previous page'), + default=None, + ) + parser.add_argument( + '--limit', + type=int, + metavar='', + help=_('Maximum number of messages to display'), + default=None, + ) + + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + identity_client = self.app.client_manager.identity + + if volume_client.api_version < api_versions.APIVersion('3.3'): + msg = _( + "--os-volume-api-version 3.3 or greater is required to " + "support the 'volume message list' command" + ) + raise exceptions.CommandError(msg) + + column_headers = ( + 'ID', + 'Event ID', + 'Resource Type', + 'Resource UUID', + 'Message Level', + 'User Message', + 'Request ID', + 'Created At', + 'Guaranteed Until', + ) + + project_id = None + if parsed_args.project: + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain).id + + search_opts = { + 'project_id': project_id, + } + data = volume_client.messages.list( + search_opts=search_opts, + marker=parsed_args.marker, + limit=parsed_args.limit) + + return ( + column_headers, + (utils.get_item_properties(s, column_headers) for s in data) + ) + + +class ShowMessage(command.ShowOne): + _description = _('Show a volume failure message') + + def get_parser(self, prog_name): + parser = super(ShowMessage, self).get_parser(prog_name) + parser.add_argument( + 'message_id', + metavar='', + help=_('Message to show (ID).') + ) + + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.3'): + msg = _( + "--os-volume-api-version 3.3 or greater is required to " + "support the 'volume message show' command" + ) + raise exceptions.CommandError(msg) + + message = volume_client.messages.get(parsed_args.message_id) + + return zip(*sorted(message._info.items())) diff -Nru python-openstackclient-5.5.0/PKG-INFO python-openstackclient-5.6.0/PKG-INFO --- python-openstackclient-5.5.0/PKG-INFO 2021-03-20 09:18:12.284908000 +0000 +++ python-openstackclient-5.6.0/PKG-INFO 2021-09-01 19:16:30.694420000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.2 Name: python-openstackclient -Version: 5.5.0 +Version: 5.6.0 Summary: OpenStack Command-line Client Home-page: https://docs.openstack.org/python-openstackclient/latest/ Author: OpenStack @@ -38,7 +38,7 @@ * `Developer`_ - getting started as a developer * `Contributing`_ - contributing code * `Testing`_ - testing code - * IRC: #openstack-sdks on Freenode (irc.freenode.net) + * IRC: #openstack-sdks on OFTC (irc.oftc.net) * License: Apache 2.0 .. _PyPi: https://pypi.org/project/python-openstackclient diff -Nru python-openstackclient-5.5.0/.pre-commit-config.yaml python-openstackclient-5.6.0/.pre-commit-config.yaml --- python-openstackclient-5.5.0/.pre-commit-config.yaml 1970-01-01 00:00:00.000000000 +0000 +++ python-openstackclient-5.6.0/.pre-commit-config.yaml 2021-09-01 19:16:00.000000000 +0000 @@ -0,0 +1,29 @@ +--- +default_language_version: + # force all unspecified python hooks to run python3 + python: python3 +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.4.0 + hooks: + - id: trailing-whitespace + - id: mixed-line-ending + args: ['--fix', 'lf'] + exclude: '.*\.(svg)$' + - id: check-byte-order-marker + - id: check-executables-have-shebangs + - id: check-merge-conflict + - id: debug-statements + - id: check-yaml + files: .*\.(yaml|yml)$ + - repo: local + hooks: + - id: flake8 + name: flake8 + additional_dependencies: + - hacking>=2.0.0 + - flake8-import-order>=0.13 + language: python + entry: flake8 + files: '^.*\.py$' + exclude: '^(doc|releasenotes|tools)/.*$' diff -Nru python-openstackclient-5.5.0/python_openstackclient.egg-info/entry_points.txt python-openstackclient-5.6.0/python_openstackclient.egg-info/entry_points.txt --- python-openstackclient-5.5.0/python_openstackclient.egg-info/entry_points.txt 2021-03-20 09:18:11.000000000 +0000 +++ python-openstackclient-5.6.0/python_openstackclient.egg-info/entry_points.txt 2021-09-01 19:16:30.000000000 +0000 @@ -358,6 +358,11 @@ network_flavor_remove_profile = openstackclient.network.v2.network_flavor:RemoveNetworkFlavorFromProfile network_flavor_set = openstackclient.network.v2.network_flavor:SetNetworkFlavor network_flavor_show = openstackclient.network.v2.network_flavor:ShowNetworkFlavor +network_l3_conntrack_helper_create = openstackclient.network.v2.l3_conntrack_helper:CreateConntrackHelper +network_l3_conntrack_helper_delete = openstackclient.network.v2.l3_conntrack_helper:DeleteConntrackHelper +network_l3_conntrack_helper_list = openstackclient.network.v2.l3_conntrack_helper:ListConntrackHelper +network_l3_conntrack_helper_set = openstackclient.network.v2.l3_conntrack_helper:SetConntrackHelper +network_l3_conntrack_helper_show = openstackclient.network.v2.l3_conntrack_helper:ShowConntrackHelper network_list = openstackclient.network.v2.network:ListNetwork network_meter_create = openstackclient.network.v2.network_meter:CreateMeter network_meter_delete = openstackclient.network.v2.network_meter:DeleteMeter @@ -570,6 +575,12 @@ 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_attachment_complete = openstackclient.volume.v3.volume_attachment:CompleteVolumeAttachment +volume_attachment_create = openstackclient.volume.v3.volume_attachment:CreateVolumeAttachment +volume_attachment_delete = openstackclient.volume.v3.volume_attachment:DeleteVolumeAttachment +volume_attachment_list = openstackclient.volume.v3.volume_attachment:ListVolumeAttachment +volume_attachment_set = openstackclient.volume.v3.volume_attachment:SetVolumeAttachment +volume_attachment_show = openstackclient.volume.v3.volume_attachment:ShowVolumeAttachment 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 @@ -578,10 +589,29 @@ 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_unset = openstackclient.volume.v2.volume_backup:UnsetVolumeBackup volume_create = openstackclient.volume.v2.volume:CreateVolume volume_delete = openstackclient.volume.v2.volume:DeleteVolume +volume_group_create = openstackclient.volume.v3.volume_group:CreateVolumeGroup +volume_group_delete = openstackclient.volume.v3.volume_group:DeleteVolumeGroup +volume_group_failover = openstackclient.volume.v3.volume_group:FailoverVolumeGroup +volume_group_list = openstackclient.volume.v3.volume_group:ListVolumeGroup +volume_group_set = openstackclient.volume.v3.volume_group:SetVolumeGroup +volume_group_show = openstackclient.volume.v3.volume_group:ShowVolumeGroup +volume_group_snapshot_create = openstackclient.volume.v3.volume_group_snapshot:CreateVolumeGroupSnapshot +volume_group_snapshot_delete = openstackclient.volume.v3.volume_group_snapshot:DeleteVolumeGroupSnapshot +volume_group_snapshot_list = openstackclient.volume.v3.volume_group_snapshot:ListVolumeGroupSnapshot +volume_group_snapshot_show = openstackclient.volume.v3.volume_group_snapshot:ShowVolumeGroupSnapshot +volume_group_type_create = openstackclient.volume.v3.volume_group_type:CreateVolumeGroupType +volume_group_type_delete = openstackclient.volume.v3.volume_group_type:DeleteVolumeGroupType +volume_group_type_list = openstackclient.volume.v3.volume_group_type:ListVolumeGroupType +volume_group_type_set = openstackclient.volume.v3.volume_group_type:SetVolumeGroupType +volume_group_type_show = openstackclient.volume.v3.volume_group_type:ShowVolumeGroupType volume_host_set = openstackclient.volume.v2.volume_host:SetVolumeHost volume_list = openstackclient.volume.v2.volume:ListVolume +volume_message_delete = openstackclient.volume.v3.volume_message:DeleteMessage +volume_message_list = openstackclient.volume.v3.volume_message:ListMessages +volume_message_show = openstackclient.volume.v3.volume_message:ShowMessage volume_migrate = openstackclient.volume.v2.volume:MigrateVolume volume_qos_associate = openstackclient.volume.v2.qos_specs:AssociateQos volume_qos_create = openstackclient.volume.v2.qos_specs:CreateQos diff -Nru python-openstackclient-5.5.0/python_openstackclient.egg-info/pbr.json python-openstackclient-5.6.0/python_openstackclient.egg-info/pbr.json --- python-openstackclient-5.5.0/python_openstackclient.egg-info/pbr.json 2021-03-20 09:18:11.000000000 +0000 +++ python-openstackclient-5.6.0/python_openstackclient.egg-info/pbr.json 2021-09-01 19:16:30.000000000 +0000 @@ -1 +1 @@ -{"git_version": "86bca18b", "is_release": true} \ No newline at end of file +{"git_version": "6ce7da8a", "is_release": true} \ No newline at end of file diff -Nru python-openstackclient-5.5.0/python_openstackclient.egg-info/PKG-INFO python-openstackclient-5.6.0/python_openstackclient.egg-info/PKG-INFO --- python-openstackclient-5.5.0/python_openstackclient.egg-info/PKG-INFO 2021-03-20 09:18:11.000000000 +0000 +++ python-openstackclient-5.6.0/python_openstackclient.egg-info/PKG-INFO 2021-09-01 19:16:30.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.2 Name: python-openstackclient -Version: 5.5.0 +Version: 5.6.0 Summary: OpenStack Command-line Client Home-page: https://docs.openstack.org/python-openstackclient/latest/ Author: OpenStack @@ -38,7 +38,7 @@ * `Developer`_ - getting started as a developer * `Contributing`_ - contributing code * `Testing`_ - testing code - * IRC: #openstack-sdks on Freenode (irc.freenode.net) + * IRC: #openstack-sdks on OFTC (irc.oftc.net) * License: Apache 2.0 .. _PyPi: https://pypi.org/project/python-openstackclient diff -Nru python-openstackclient-5.5.0/python_openstackclient.egg-info/requires.txt python-openstackclient-5.6.0/python_openstackclient.egg-info/requires.txt --- python-openstackclient-5.5.0/python_openstackclient.egg-info/requires.txt 2021-03-20 09:18:11.000000000 +0000 +++ python-openstackclient-5.6.0/python_openstackclient.egg-info/requires.txt 2021-09-01 19:16:30.000000000 +0000 @@ -1,6 +1,6 @@ cliff>=3.5.0 iso8601>=0.1.11 -openstacksdk>=0.53.0 +openstacksdk>=0.56.0 osc-lib>=2.3.0 oslo.i18n>=3.15.3 oslo.utils>=3.33.0 diff -Nru python-openstackclient-5.5.0/python_openstackclient.egg-info/SOURCES.txt python-openstackclient-5.6.0/python_openstackclient.egg-info/SOURCES.txt --- python-openstackclient-5.5.0/python_openstackclient.egg-info/SOURCES.txt 2021-03-20 09:18:11.000000000 +0000 +++ python-openstackclient-5.6.0/python_openstackclient.egg-info/SOURCES.txt 2021-09-01 19:16:30.000000000 +0000 @@ -1,5 +1,6 @@ .coveragerc .mailmap +.pre-commit-config.yaml .stestr.conf .zuul.yaml AUTHORS @@ -87,6 +88,7 @@ doc/source/cli/command-objects/network-auto-allocated-topology.rst doc/source/cli/command-objects/network-flavor-profile.rst doc/source/cli/command-objects/network-flavor.rst +doc/source/cli/command-objects/network-l3-conntrack-helper.rst doc/source/cli/command-objects/network-meter-rule.rst doc/source/cli/command-objects/network-meter.rst doc/source/cli/command-objects/network-qos-policy.rst @@ -133,9 +135,14 @@ doc/source/cli/command-objects/user-v2.rst doc/source/cli/command-objects/user-v3.rst doc/source/cli/command-objects/versions.rst +doc/source/cli/command-objects/volume-attachment.rst doc/source/cli/command-objects/volume-backend.rst doc/source/cli/command-objects/volume-backup.rst +doc/source/cli/command-objects/volume-group-snapshot.rst +doc/source/cli/command-objects/volume-group-type.rst +doc/source/cli/command-objects/volume-group.rst doc/source/cli/command-objects/volume-host.rst +doc/source/cli/command-objects/volume-message.rst doc/source/cli/command-objects/volume-qos.rst doc/source/cli/command-objects/volume-service.rst doc/source/cli/command-objects/volume-snapshot.rst @@ -289,6 +296,7 @@ openstackclient/network/v2/floating_ip_pool.py openstackclient/network/v2/floating_ip_port_forwarding.py openstackclient/network/v2/ip_availability.py +openstackclient/network/v2/l3_conntrack_helper.py openstackclient/network/v2/network.py openstackclient/network/v2/network_agent.py openstackclient/network/v2/network_auto_allocated_topology.py @@ -377,6 +385,7 @@ openstackclient/tests/functional/network/v2/test_address_scope.py openstackclient/tests/functional/network/v2/test_floating_ip.py openstackclient/tests/functional/network/v2/test_ip_availability.py +openstackclient/tests/functional/network/v2/test_l3_conntrack_helper.py openstackclient/tests/functional/network/v2/test_network.py openstackclient/tests/functional/network/v2/test_network_agent.py openstackclient/tests/functional/network/v2/test_network_flavor.py @@ -525,6 +534,7 @@ openstackclient/tests/unit/network/__init__.py openstackclient/tests/unit/network/test_common.py openstackclient/tests/unit/network/test_sdk_utils.py +openstackclient/tests/unit/network/test_utils.py openstackclient/tests/unit/network/v2/__init__.py openstackclient/tests/unit/network/v2/fakes.py openstackclient/tests/unit/network/v2/test_address_group.py @@ -535,6 +545,7 @@ 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_l3_conntrack_helper.py openstackclient/tests/unit/network/v2/test_network.py openstackclient/tests/unit/network/v2/test_network_agent.py openstackclient/tests/unit/network/v2/test_network_auto_allocated_topology.py @@ -582,13 +593,20 @@ 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_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/v2/test_volume_snapshot.py +openstackclient/tests/unit/volume/v2/test_volume_transfer_request.py openstackclient/tests/unit/volume/v3/__init__.py +openstackclient/tests/unit/volume/v3/fakes.py +openstackclient/tests/unit/volume/v3/test_volume_attachment.py +openstackclient/tests/unit/volume/v3/test_volume_group.py +openstackclient/tests/unit/volume/v3/test_volume_group_snapshot.py +openstackclient/tests/unit/volume/v3/test_volume_group_type.py +openstackclient/tests/unit/volume/v3/test_volume_message.py openstackclient/volume/__init__.py openstackclient/volume/client.py openstackclient/volume/v1/__init__.py @@ -613,6 +631,11 @@ openstackclient/volume/v2/volume_transfer_request.py openstackclient/volume/v2/volume_type.py openstackclient/volume/v3/__init__.py +openstackclient/volume/v3/volume_attachment.py +openstackclient/volume/v3/volume_group.py +openstackclient/volume/v3/volume_group_snapshot.py +openstackclient/volume/v3/volume_group_type.py +openstackclient/volume/v3/volume_message.py python_openstackclient.egg-info/PKG-INFO python_openstackclient.egg-info/SOURCES.txt python_openstackclient.egg-info/dependency_links.txt @@ -622,6 +645,7 @@ python_openstackclient.egg-info/requires.txt python_openstackclient.egg-info/top_level.txt releasenotes/notes/.placeholder +releasenotes/notes/L3-conntrack-helper-bd0d9da041747e84.yaml releasenotes/notes/add-auto-and-none-as-nic-parameter-ed23a6e7f99f250d.yaml releasenotes/notes/add-community-option-to-image-list-ac0651eb2e5d632f.yaml releasenotes/notes/add-description-field-in-port-forwarding-c536e077b243d517.yaml @@ -653,6 +677,7 @@ releasenotes/notes/add-missing-server-list-opts-c41e97e86ff1e1ca.yaml releasenotes/notes/add-missing-server-rebuild-opts-5c75e838d8f0487d.yaml releasenotes/notes/add-missing-server-set-opts-e1b4300f5f42e863.yaml +releasenotes/notes/add-missing-volume-backup-opts-b9246aded87427ce.yaml releasenotes/notes/add-network-auto-allocated-topology-481580f48840bfc4.yaml releasenotes/notes/add-network-flavor-profile-e7cc5b353c3ed9d9.yaml releasenotes/notes/add-network-list-option-to-ports-9d101344ddeb3e64.yaml @@ -695,7 +720,13 @@ releasenotes/notes/add-tag-support-server-add-port-7e30aa38202d0839.yaml releasenotes/notes/add-tag-support-server-add-volume-278e79a22dd482f4.yaml releasenotes/notes/add-virtio-forwarder-vnic-type-bad939c6a868b9e9.yaml +releasenotes/notes/add-volume-attachment-commands-db2974c6460fa3bc.yaml +releasenotes/notes/add-volume-group-commands-b121d6ec7da9779a.yaml +releasenotes/notes/add-volume-group-snapshot-commands-27fa8920d55f6bdb.yaml +releasenotes/notes/add-volume-group-type-commands-13eabc7664a5c2bc.yaml releasenotes/notes/add-volume-host-failover-8fc77b24533b7fed.yaml +releasenotes/notes/add-volume-message-commands-89a590a1549c333e.yaml +releasenotes/notes/add-volume-transfer-request-create-snapshots-opts-1361416d37021e89.yaml releasenotes/notes/add_id_and_enabled_to_list_identity_provider-e0981063a2dc5961.yaml releasenotes/notes/add_name_and_enabled_to_list_domain-6d23f02994b51c67.yaml releasenotes/notes/add_options_to_user_create_and_set-302401520f36d153.yaml @@ -1014,6 +1045,7 @@ releasenotes/notes/cliff-2.3.0-7ead18fae9ceea80.yaml releasenotes/notes/complete_image_switch-203e0b3105a54674.yaml releasenotes/notes/compute-agent-deff48988e81b30e.yaml +releasenotes/notes/compute-service-list-forced-down-2b16d1cb44f71a08.yaml releasenotes/notes/config-show-00512dc60882e5c0.yaml releasenotes/notes/credential_list_user_type-c809e5b8014d6275.yaml releasenotes/notes/deprecated-server-create-file-option-80246b13bd3c1b43.yaml @@ -1034,6 +1066,7 @@ releasenotes/notes/image-set-to-update-image-membership-68221f226ca3b6e0.yaml releasenotes/notes/image_set_visibility-babf4ff2f687d465.yaml releasenotes/notes/implement-system-scope-4c3c47996f98deac.yaml +releasenotes/notes/implements-hide-image-4c726a61c336ebaa.yaml releasenotes/notes/improved-server-output-6965b664f6abda8d.yaml releasenotes/notes/ip-availability-ca1cf440f6c70afc.yaml releasenotes/notes/ip-command-rework-8d3fe0858f51e6b8.yaml @@ -1049,6 +1082,7 @@ releasenotes/notes/name-lookup-one-by-one-e0f15f4eab329b19.yaml releasenotes/notes/network-add-qos-policy-a25e868e67142f90.yaml releasenotes/notes/network-flavor-command-support-afe3a9da962a09bf.yaml +releasenotes/notes/network-port-create-vnic-type-vdpa-fc02516cfb919941.yaml releasenotes/notes/network_dns_integration-5914b2c2be474a41.yaml releasenotes/notes/neutron-client-flavors-81387171f67a3c82.yaml releasenotes/notes/neutron_mtu-d87e53e2d76f8612.yaml @@ -1143,6 +1177,7 @@ releasenotes/source/unreleased.rst releasenotes/source/ussuri.rst releasenotes/source/victoria.rst +releasenotes/source/wallaby.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder tools/fast8.sh \ No newline at end of file diff -Nru python-openstackclient-5.5.0/README.rst python-openstackclient-5.6.0/README.rst --- python-openstackclient-5.5.0/README.rst 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/README.rst 2021-09-01 19:16:00.000000000 +0000 @@ -30,7 +30,7 @@ * `Developer`_ - getting started as a developer * `Contributing`_ - contributing code * `Testing`_ - testing code -* IRC: #openstack-sdks on Freenode (irc.freenode.net) +* IRC: #openstack-sdks on OFTC (irc.oftc.net) * License: Apache 2.0 .. _PyPi: https://pypi.org/project/python-openstackclient diff -Nru python-openstackclient-5.5.0/releasenotes/notes/add-missing-volume-backup-opts-b9246aded87427ce.yaml python-openstackclient-5.6.0/releasenotes/notes/add-missing-volume-backup-opts-b9246aded87427ce.yaml --- python-openstackclient-5.5.0/releasenotes/notes/add-missing-volume-backup-opts-b9246aded87427ce.yaml 1970-01-01 00:00:00.000000000 +0000 +++ python-openstackclient-5.6.0/releasenotes/notes/add-missing-volume-backup-opts-b9246aded87427ce.yaml 2021-09-01 19:16:00.000000000 +0000 @@ -0,0 +1,19 @@ +--- +features: + - | + Add ``--no-incremental``, ``--property`` and ``--availability-zone`` + options to ``volume backup create`` command, allowing users to request a + non-incremental backup, set a metadata property on the created backup, and + set an availability zone on the created backup, respectively. + - | + Add ``--property`` and ``--no-property`` options to the + ``volume backup set`` command to set a metadata property or remove all + metadata properties from an existing backup. + - | + Add new ``volume backup unset`` command to allow unsetting of properties + from an existing volume backup. +fixes: + - | + The ``--name`` and ``--description`` options of the ``volume backup set`` + command will now verify the version requested on the client side. + Previously this would fail on the server side. diff -Nru python-openstackclient-5.5.0/releasenotes/notes/add-volume-attachment-commands-db2974c6460fa3bc.yaml python-openstackclient-5.6.0/releasenotes/notes/add-volume-attachment-commands-db2974c6460fa3bc.yaml --- python-openstackclient-5.5.0/releasenotes/notes/add-volume-attachment-commands-db2974c6460fa3bc.yaml 1970-01-01 00:00:00.000000000 +0000 +++ python-openstackclient-5.6.0/releasenotes/notes/add-volume-attachment-commands-db2974c6460fa3bc.yaml 2021-09-01 19:16:00.000000000 +0000 @@ -0,0 +1,8 @@ +--- +features: + - | + Add ``volume attachment create``, ``volume attachment delete``, + ``volume attachment list``, ``volume attachment complete``, + ``volume attachment set`` and ``volume attachment show`` commands to + create, delete, list, complete, update and show volume attachments, + respectively. diff -Nru python-openstackclient-5.5.0/releasenotes/notes/add-volume-group-commands-b121d6ec7da9779a.yaml python-openstackclient-5.6.0/releasenotes/notes/add-volume-group-commands-b121d6ec7da9779a.yaml --- python-openstackclient-5.5.0/releasenotes/notes/add-volume-group-commands-b121d6ec7da9779a.yaml 1970-01-01 00:00:00.000000000 +0000 +++ python-openstackclient-5.6.0/releasenotes/notes/add-volume-group-commands-b121d6ec7da9779a.yaml 2021-09-01 19:16:00.000000000 +0000 @@ -0,0 +1,8 @@ +--- +features: + - | + Add ``volume group create``, ``volume group delete``, + ``volume group list``, ``volume group failover``, + ``volume group set`` and ``volume attachment show`` + commands to create, delete, list, failover, update and show volume groups, + respectively. diff -Nru python-openstackclient-5.5.0/releasenotes/notes/add-volume-group-snapshot-commands-27fa8920d55f6bdb.yaml python-openstackclient-5.6.0/releasenotes/notes/add-volume-group-snapshot-commands-27fa8920d55f6bdb.yaml --- python-openstackclient-5.5.0/releasenotes/notes/add-volume-group-snapshot-commands-27fa8920d55f6bdb.yaml 1970-01-01 00:00:00.000000000 +0000 +++ python-openstackclient-5.6.0/releasenotes/notes/add-volume-group-snapshot-commands-27fa8920d55f6bdb.yaml 2021-09-01 19:16:00.000000000 +0000 @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``volume group snapshot create``, ``volume group snapshot delete``, + ``volume group snapshot list`` and ``volume group snapshot show`` commands + to create, delete, list, and show volume group snapshots, respectively. diff -Nru python-openstackclient-5.5.0/releasenotes/notes/add-volume-group-type-commands-13eabc7664a5c2bc.yaml python-openstackclient-5.6.0/releasenotes/notes/add-volume-group-type-commands-13eabc7664a5c2bc.yaml --- python-openstackclient-5.5.0/releasenotes/notes/add-volume-group-type-commands-13eabc7664a5c2bc.yaml 1970-01-01 00:00:00.000000000 +0000 +++ python-openstackclient-5.6.0/releasenotes/notes/add-volume-group-type-commands-13eabc7664a5c2bc.yaml 2021-09-01 19:16:00.000000000 +0000 @@ -0,0 +1,7 @@ +--- +features: + - | + Add ``volume group type create``, ``volume group type delete``, + ``volume group type list``, ``volume group type set/unset`` and + ``volume group type show`` commands to create, delete, list, update, + and show volume group types, respectively. diff -Nru python-openstackclient-5.5.0/releasenotes/notes/add-volume-message-commands-89a590a1549c333e.yaml python-openstackclient-5.6.0/releasenotes/notes/add-volume-message-commands-89a590a1549c333e.yaml --- python-openstackclient-5.5.0/releasenotes/notes/add-volume-message-commands-89a590a1549c333e.yaml 1970-01-01 00:00:00.000000000 +0000 +++ python-openstackclient-5.6.0/releasenotes/notes/add-volume-message-commands-89a590a1549c333e.yaml 2021-09-01 19:16:00.000000000 +0000 @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``volume message list``, ``volume message get`` and + ``volume message delete`` commands, to list, get and delete volume + failure messages, respectively. diff -Nru python-openstackclient-5.5.0/releasenotes/notes/add-volume-transfer-request-create-snapshots-opts-1361416d37021e89.yaml python-openstackclient-5.6.0/releasenotes/notes/add-volume-transfer-request-create-snapshots-opts-1361416d37021e89.yaml --- python-openstackclient-5.5.0/releasenotes/notes/add-volume-transfer-request-create-snapshots-opts-1361416d37021e89.yaml 1970-01-01 00:00:00.000000000 +0000 +++ python-openstackclient-5.6.0/releasenotes/notes/add-volume-transfer-request-create-snapshots-opts-1361416d37021e89.yaml 2021-09-01 19:16:00.000000000 +0000 @@ -0,0 +1,6 @@ +--- +features: + - | + The ``volume transfer request create`` command now accepts the + ``--snapshots`` / ``--no-snapshots`` option to configure whether to + create a transfer request for a volume without snapshots or not. diff -Nru python-openstackclient-5.5.0/releasenotes/notes/compute-service-list-forced-down-2b16d1cb44f71a08.yaml python-openstackclient-5.6.0/releasenotes/notes/compute-service-list-forced-down-2b16d1cb44f71a08.yaml --- python-openstackclient-5.5.0/releasenotes/notes/compute-service-list-forced-down-2b16d1cb44f71a08.yaml 1970-01-01 00:00:00.000000000 +0000 +++ python-openstackclient-5.6.0/releasenotes/notes/compute-service-list-forced-down-2b16d1cb44f71a08.yaml 2021-09-01 19:16:00.000000000 +0000 @@ -0,0 +1,5 @@ +--- +features: + - | + Add column ``Forced Down`` to the output of ``compute service list + --long``. Only available starting with ``--os-compute-api-version 2.11``. diff -Nru python-openstackclient-5.5.0/releasenotes/notes/implements-hide-image-4c726a61c336ebaa.yaml python-openstackclient-5.6.0/releasenotes/notes/implements-hide-image-4c726a61c336ebaa.yaml --- python-openstackclient-5.5.0/releasenotes/notes/implements-hide-image-4c726a61c336ebaa.yaml 1970-01-01 00:00:00.000000000 +0000 +++ python-openstackclient-5.6.0/releasenotes/notes/implements-hide-image-4c726a61c336ebaa.yaml 2021-09-01 19:16:00.000000000 +0000 @@ -0,0 +1,8 @@ +--- +features: + - | + Add mutually exclusive options ``--hidden`` and ``--unhidden`` to + ``image set`` command to hide or unhide an image (``is_hidden`` + attribute). + - | + Add option ``--hidden`` to ``image list`` command to list hidden images. diff -Nru python-openstackclient-5.5.0/releasenotes/notes/L3-conntrack-helper-bd0d9da041747e84.yaml python-openstackclient-5.6.0/releasenotes/notes/L3-conntrack-helper-bd0d9da041747e84.yaml --- python-openstackclient-5.5.0/releasenotes/notes/L3-conntrack-helper-bd0d9da041747e84.yaml 1970-01-01 00:00:00.000000000 +0000 +++ python-openstackclient-5.6.0/releasenotes/notes/L3-conntrack-helper-bd0d9da041747e84.yaml 2021-09-01 19:16:00.000000000 +0000 @@ -0,0 +1,8 @@ +--- +features: + - | + Add new commands ``network l3 conntrack helper create``, + ``network l3 conntrack helper set``, ``network l3 conntrack helper show``, + ``network l3 conntrack helper set`` and + ``network l3 conntrack helper delete`` to support Neutron L3 conntrack + helper CRUD operations. diff -Nru python-openstackclient-5.5.0/releasenotes/notes/network-port-create-vnic-type-vdpa-fc02516cfb919941.yaml python-openstackclient-5.6.0/releasenotes/notes/network-port-create-vnic-type-vdpa-fc02516cfb919941.yaml --- python-openstackclient-5.5.0/releasenotes/notes/network-port-create-vnic-type-vdpa-fc02516cfb919941.yaml 1970-01-01 00:00:00.000000000 +0000 +++ python-openstackclient-5.6.0/releasenotes/notes/network-port-create-vnic-type-vdpa-fc02516cfb919941.yaml 2021-09-01 19:16:00.000000000 +0000 @@ -0,0 +1,4 @@ +--- +features: + - | + The ``port create --vnic-type`` option now accepts a ``vdpa`` value. diff -Nru python-openstackclient-5.5.0/releasenotes/source/index.rst python-openstackclient-5.6.0/releasenotes/source/index.rst --- python-openstackclient-5.5.0/releasenotes/source/index.rst 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/releasenotes/source/index.rst 2021-09-01 19:16:00.000000000 +0000 @@ -6,6 +6,7 @@ :maxdepth: 1 unreleased + wallaby victoria ussuri train diff -Nru python-openstackclient-5.5.0/releasenotes/source/wallaby.rst python-openstackclient-5.6.0/releasenotes/source/wallaby.rst --- python-openstackclient-5.5.0/releasenotes/source/wallaby.rst 1970-01-01 00:00:00.000000000 +0000 +++ python-openstackclient-5.6.0/releasenotes/source/wallaby.rst 2021-09-01 19:16:00.000000000 +0000 @@ -0,0 +1,6 @@ +============================ +Wallaby Series Release Notes +============================ + +.. release-notes:: + :branch: stable/wallaby diff -Nru python-openstackclient-5.5.0/requirements.txt python-openstackclient-5.6.0/requirements.txt --- python-openstackclient-5.5.0/requirements.txt 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/requirements.txt 2021-09-01 19:16:00.000000000 +0000 @@ -5,7 +5,7 @@ cliff>=3.5.0 # Apache-2.0 iso8601>=0.1.11 # MIT -openstacksdk>=0.53.0 # Apache-2.0 +openstacksdk>=0.56.0 # Apache-2.0 osc-lib>=2.3.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 diff -Nru python-openstackclient-5.5.0/setup.cfg python-openstackclient-5.6.0/setup.cfg --- python-openstackclient-5.5.0/setup.cfg 2021-03-20 09:18:12.292908000 +0000 +++ python-openstackclient-5.6.0/setup.cfg 2021-09-01 19:16:30.698420000 +0000 @@ -1,12 +1,12 @@ [metadata] name = python-openstackclient summary = OpenStack Command-line Client -description-file = +description_file = README.rst author = OpenStack -author-email = openstack-discuss@lists.openstack.org -home-page = https://docs.openstack.org/python-openstackclient/latest/ -python-requires = >=3.6 +author_email = openstack-discuss@lists.openstack.org +home_page = https://docs.openstack.org/python-openstackclient/latest/ +python_requires = >=3.6 classifier = Environment :: OpenStack Intended Audience :: Information Technology @@ -439,6 +439,12 @@ network_show = openstackclient.network.v2.network:ShowNetwork network_unset = openstackclient.network.v2.network:UnsetNetwork + network_l3_conntrack_helper_create = openstackclient.network.v2.l3_conntrack_helper:CreateConntrackHelper + network_l3_conntrack_helper_delete = openstackclient.network.v2.l3_conntrack_helper:DeleteConntrackHelper + network_l3_conntrack_helper_list = openstackclient.network.v2.l3_conntrack_helper:ListConntrackHelper + network_l3_conntrack_helper_set = openstackclient.network.v2.l3_conntrack_helper:SetConntrackHelper + network_l3_conntrack_helper_show = openstackclient.network.v2.l3_conntrack_helper:ShowConntrackHelper + network_meter_create = openstackclient.network.v2.network_meter:CreateMeter network_meter_delete = openstackclient.network.v2.network_meter:DeleteMeter network_meter_list = openstackclient.network.v2.network_meter:ListMeter @@ -684,18 +690,48 @@ volume_show = openstackclient.volume.v2.volume:ShowVolume volume_unset = openstackclient.volume.v2.volume:UnsetVolume + volume_attachment_create = openstackclient.volume.v3.volume_attachment:CreateVolumeAttachment + volume_attachment_delete = openstackclient.volume.v3.volume_attachment:DeleteVolumeAttachment + volume_attachment_list = openstackclient.volume.v3.volume_attachment:ListVolumeAttachment + volume_attachment_complete = openstackclient.volume.v3.volume_attachment:CompleteVolumeAttachment + volume_attachment_set = openstackclient.volume.v3.volume_attachment:SetVolumeAttachment + volume_attachment_show = openstackclient.volume.v3.volume_attachment:ShowVolumeAttachment + 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_unset = openstackclient.volume.v2.volume_backup:UnsetVolumeBackup 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 + volume_group_create = openstackclient.volume.v3.volume_group:CreateVolumeGroup + volume_group_delete = openstackclient.volume.v3.volume_group:DeleteVolumeGroup + volume_group_list = openstackclient.volume.v3.volume_group:ListVolumeGroup + volume_group_failover = openstackclient.volume.v3.volume_group:FailoverVolumeGroup + volume_group_set = openstackclient.volume.v3.volume_group:SetVolumeGroup + volume_group_show = openstackclient.volume.v3.volume_group:ShowVolumeGroup + + volume_group_snapshot_create = openstackclient.volume.v3.volume_group_snapshot:CreateVolumeGroupSnapshot + volume_group_snapshot_delete = openstackclient.volume.v3.volume_group_snapshot:DeleteVolumeGroupSnapshot + volume_group_snapshot_list = openstackclient.volume.v3.volume_group_snapshot:ListVolumeGroupSnapshot + volume_group_snapshot_show = openstackclient.volume.v3.volume_group_snapshot:ShowVolumeGroupSnapshot + + volume_group_type_create = openstackclient.volume.v3.volume_group_type:CreateVolumeGroupType + volume_group_type_delete = openstackclient.volume.v3.volume_group_type:DeleteVolumeGroupType + volume_group_type_list = openstackclient.volume.v3.volume_group_type:ListVolumeGroupType + volume_group_type_set = openstackclient.volume.v3.volume_group_type:SetVolumeGroupType + volume_group_type_show = openstackclient.volume.v3.volume_group_type:ShowVolumeGroupType + volume_host_set = openstackclient.volume.v2.volume_host:SetVolumeHost + volume_message_delete = openstackclient.volume.v3.volume_message:DeleteMessage + volume_message_list = openstackclient.volume.v3.volume_message:ListMessages + volume_message_show = openstackclient.volume.v3.volume_message:ShowMessage + volume_snapshot_create = openstackclient.volume.v2.volume_snapshot:CreateVolumeSnapshot volume_snapshot_delete = openstackclient.volume.v2.volume_snapshot:DeleteVolumeSnapshot volume_snapshot_list = openstackclient.volume.v2.volume_snapshot:ListVolumeSnapshot diff -Nru python-openstackclient-5.5.0/tox.ini python-openstackclient-5.6.0/tox.ini --- python-openstackclient-5.5.0/tox.ini 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/tox.ini 2021-09-01 19:16:00.000000000 +0000 @@ -1,7 +1,7 @@ [tox] minversion = 3.2.0 envlist = py38,pep8 -skipdist = True +skipsdist = True # Automatic envs (pyXX) will only use the python version appropriate to that # env and ignore basepython inherited from [testenv] if we set # ignore_basepython_conflict. diff -Nru python-openstackclient-5.5.0/.zuul.yaml python-openstackclient-5.6.0/.zuul.yaml --- python-openstackclient-5.5.0/.zuul.yaml 2021-03-20 09:17:40.000000000 +0000 +++ python-openstackclient-5.6.0/.zuul.yaml 2021-09-01 19:16:00.000000000 +0000 @@ -88,11 +88,29 @@ # NOTE(amotoki): Some neutron features are enabled by devstack plugin neutron: https://opendev.org/openstack/neutron devstack_services: + # Disable OVN services + br-ex-tcpdump: false + br-int-flows: false + ovn-controller: false + ovn-northd: false + ovs-vswitchd: false + ovsdb-server: false + q-ovn-metadata-agent: false + # Neutron services + q-agt: true + q-dhcp: true + q-l3: true + q-meta: true neutron-network-segment-range: true neutron-segments: true q-metering: true q-qos: true neutron-tag-ports-during-bulk-creation: true + neutron-conntrack-helper: true + devstack_localrc: + Q_AGENT: openvswitch + Q_ML2_TENANT_NETWORK_TYPE: vxlan + Q_ML2_PLUGIN_MECHANISM_DRIVERS: openvswitch tox_envlist: functional - job: @@ -222,7 +240,7 @@ - osc-tox-unit-tips - openstack-cover-jobs - openstack-lower-constraints-jobs - - openstack-python3-wallaby-jobs + - openstack-python3-xena-jobs - publish-openstack-docs-pti - check-requirements - release-notes-jobs-python3