diff options
| author | James Page <james.page@ubuntu.com> | 2017-04-21 10:01:48 +0100 |
|---|---|---|
| committer | git-ubuntu importer <ubuntu-devel-discuss@lists.ubuntu.com> | 2017-04-24 09:03:11 +0000 |
| commit | 5660bcc29dcc622e9eac58452127a7a988848963 (patch) | |
| tree | cfd3c5338ff7cc9457f88d439cee1d0e78aee869 | |
| parent | b03ff8d57f56f3a1923df213eff2e511c5dc151c (diff) | |
3.9.0-0ubuntu1 (patches unapplied)import/3.9.0-0ubuntu1
Imported using git-ubuntu import.
Notes
Notes:
[ Chuck Short ]
* New upstream release.
157 files changed, 6474 insertions, 1380 deletions
diff --git a/debian/changelog b/debian/changelog index a27d786..3f4685c 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,10 @@ +python-openstackclient (3.9.0-0ubuntu1) artful; urgency=medium + + [ Chuck Short ] + * New upstream release. + + -- James Page <james.page@ubuntu.com> Fri, 21 Apr 2017 10:01:48 +0100 + python-openstackclient (3.8.1-0ubuntu3) zesty; urgency=medium * debian/control: Dropped python3-hacking. diff --git a/debian/control b/debian/control index d115dc6..500f427 100644 --- a/debian/control +++ b/debian/control @@ -10,11 +10,11 @@ Build-Depends: debhelper (>= 9), dh-python, openstack-pkg-tools (>= 52~), python-all, - python-pbr (>= 1.8), + python-pbr (>= 2.0), python-setuptools, python-sphinx, python3-all, - python3-pbr (>= 1.8), + python3-pbr (>= 2.0), python3-setuptools, Build-Depends-Indep: python-aodhclient (>= 0.7.0), python-babel (>= 2.3.4), @@ -127,7 +127,7 @@ Depends: python-babel (>= 2.3.4), python-osc-lib (>= 1.2.0), python-oslo.i18n (>= 2.1.0), python-oslo.utils (>= 3.18.0), - python-pbr (>= 1.8), + python-pbr (>= 2.0), python-requests (>= 2.10.0), python-six (>= 1.9.0), python-stevedore (>= 1.17.1), @@ -164,7 +164,7 @@ Depends: python3-babel (>= 2.3.4), python3-osc-lib (>= 1.2.0), python3-oslo.i18n (>= 2.1.0), python3-oslo.utils (>= 3.18.0), - python3-pbr (>= 1.8), + python3-pbr (>= 2.0), python3-requests (>= 2.10.0), python3-six (>= 1.9.0), python3-stevedore (>= 1.17.1), diff --git a/doc/source/command-objects/command.rst b/doc/source/command-objects/command.rst index 34c07da..918fd95 100644 --- a/doc/source/command-objects/command.rst +++ b/doc/source/command-objects/command.rst @@ -15,3 +15,9 @@ List recognized commands by group .. code:: bash openstack command list + [--group <group-keyword>] + +.. option:: --group <group-keyword> + + Show commands filtered by a command group, for example: identity, volume, + compute, image, network and other keywords diff --git a/doc/source/command-objects/consistency-group.rst b/doc/source/command-objects/consistency-group.rst index e54ac65..232cf8e 100644 --- a/doc/source/command-objects/consistency-group.rst +++ b/doc/source/command-objects/consistency-group.rst @@ -12,7 +12,7 @@ Add volume(s) to consistency group. .. program:: consistency group add volume .. code:: bash - os consistency group add volume + openstack consistency group add volume <consistency-group> <volume> [<volume> ...] @@ -116,7 +116,7 @@ Remove volume(s) from consistency group. .. program:: consistency group remove volume .. code:: bash - os consistency group remove volume + openstack consistency group remove volume <consistency-group> <volume> [<volume> ...] diff --git a/doc/source/command-objects/endpoint.rst b/doc/source/command-objects/endpoint.rst index d6eb362..b98055a 100644 --- a/doc/source/command-objects/endpoint.rst +++ b/doc/source/command-objects/endpoint.rst @@ -103,14 +103,14 @@ List endpoints .. code:: bash openstack endpoint list - [--service <service] + [--service <service>] [--interface <interface>] [--region <region-id>] [--long] .. option:: --service <service> - Filter by service (name or ID) + Filter by service (type, name or ID) *Identity version 3 only* diff --git a/doc/source/command-objects/flavor.rst b/doc/source/command-objects/flavor.rst index 971628d..a9f8262 100644 --- a/doc/source/command-objects/flavor.rst +++ b/doc/source/command-objects/flavor.rst @@ -144,6 +144,7 @@ Set flavor properties .. code:: bash openstack flavor set + [--no-property] [--property <key=value> [...] ] [--project <project>] [--project-domain <project-domain>] @@ -162,6 +163,11 @@ Set flavor properties Domain the project belongs to (name or ID). This can be used in case collisions between project names exist. +.. option:: --no-property + + Remove all properties from this flavor (specify both --no-property and --property + to remove the current properties before setting new properties.) + .. describe:: <flavor> Flavor to modify (name or ID) diff --git a/doc/source/command-objects/image.rst b/doc/source/command-objects/image.rst index 7a9ead5..c39d9a2 100644 --- a/doc/source/command-objects/image.rst +++ b/doc/source/command-objects/image.rst @@ -53,8 +53,7 @@ Create/upload an image [--min-ram <ram-mb>] [--location <image-url>] [--copy-from <image-url>] - [--file <file>] - [--volume <volume>] + [--file <file> | --volume <volume>] [--force] [--checksum <checksum>] [--protected | --unprotected] diff --git a/doc/source/command-objects/network-auto-allocated-topology.rst b/doc/source/command-objects/network-auto-allocated-topology.rst new file mode 100644 index 0000000..4ed68cd --- /dev/null +++ b/doc/source/command-objects/network-auto-allocated-topology.rst @@ -0,0 +1,69 @@ +=============================== +network auto allocated topology +=============================== + +An **auto allocated topology** allows admins to quickly set up external +connectivity for end-users. Only one auto allocated topology is allowed per +project. For more information on how to set up the resources required +for auto allocated topology review the documentation at: +http://docs.openstack.org/newton/networking-guide/config-auto-allocation.html + +Network v2 + +network auto allocated topology create +-------------------------------------- + +Create the auto allocated topology for project + +.. program:: network auto allocated topology create +.. code:: bash + + openstack network auto allocated topology create + [--or-show] + [--check-resources] + [--project <project> [--project-domain <project-domain>]] + +.. option:: --or-show + + If topology exists returns the topologies information (Default). + +.. option:: --check-resources + + Validate the requirements for auto allocated topology. + Does not return a topology. + +.. option:: --project <project> + + Return the auto allocated topology for a given project. + Default is current project. + +.. option:: --project-domain <project-domain> + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + +.. _network_auto_allocated_topology_create: + + +network auto allocated topology delete +-------------------------------------- + +Delete auto allocated topology for project + +.. program:: network auto allocated topology delete +.. code:: bash + + openstack network auto allocated topology delete + [--project <project> [--project-domain <project-domain>]] + +.. option:: --project <project> + + Delete auto allocated topology for a given project. + Default is the current project. + +.. option:: --project-domain <project-domain> + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + +.. _network_auto_allocated_topology_delete: diff --git a/doc/source/command-objects/network-flavor.rst b/doc/source/command-objects/network-flavor.rst new file mode 100644 index 0000000..d5c011f --- /dev/null +++ b/doc/source/command-objects/network-flavor.rst @@ -0,0 +1,139 @@ +============== +network flavor +============== + +A **network flavor** extension allows the user selection of operator-curated +flavors during resource creations. It allows administrators to create network +service flavors. + +Network v2 + +network flavor create +--------------------- + +Create network flavor + +.. program:: network flavor create +.. code:: bash + + openstack network flavor create + --service-type <service-type> + [--description <description>] + [--enable | --disable] + [--project <project> [--project-domain <project-domain>]] + <name> + +.. option:: --service-type <service-type> + + Service type to which the flavor applies to: e.g. VPN. + (See openstack :ref:'network_service_provider_list` (required) + +.. option:: --description <description> + + Description for the flavor + +.. option:: --enable + + Enable the flavor (default) + +.. option:: --disable + + Disable the flavor + +.. option:: --project <project> + + Owner's project (name or ID) + +.. option:: --project-domain <project-domain> + + Domain the project belongs to (name or ID). This can + be used in case collisions between project names + exist. + +.. describe:: <name> + + Name for the flavor + +.. _network_flavor_create: + +network flavor delete +--------------------- + +Delete network flavor(s) + +.. program:: network flavor delete +.. code:: bash + + openstack network flavor delete + <flavor> [<flavor> ...] + +.. describe:: <flavor> + + Flavor(s) to delete (name or ID) + +.. _network_flavor_delete: + +network flavor list +------------------- + +List network flavors + +.. program:: network flavor list +.. code:: bash + + openstack network flavor list + +.. _network_flavor_list: + +network flavor set +------------------ + +Set network flavor properties + +.. program:: network flavor set +.. code:: bash + + openstack network flavor set + [--name <name>] + [--description <description>] + [--enable | --disable] + <flavor> + +.. option:: --name <name> + + Set flavor name + +.. option:: --description <description> + + Set network flavor description + +.. option:: --enable + + Enable network flavor + +.. option:: --disable + + Disable network flavor + +.. describe:: <flavor> + + Flavor to update (name or ID) + +.. _network_flavor_set: + +network flavor show +------------------- + +Show network flavor + +.. program:: network flavor show +.. code:: bash + + openstack network flavor show + <flavor> + +.. describe:: <flavor> + + Flavor to display (name or ID) + +.. _network_flavor_show: diff --git a/doc/source/command-objects/network-qos-policy.rst b/doc/source/command-objects/network-qos-policy.rst index cf59449..7ec6776 100644 --- a/doc/source/command-objects/network-qos-policy.rst +++ b/doc/source/command-objects/network-qos-policy.rst @@ -38,11 +38,12 @@ Create new Network QoS policy Owner's project (name or ID) -.. option:: --project-domain <project-domain>] +.. option:: --project-domain <project-domain> Domain the project belongs to (name or ID). This can be used in case collisions between project names exist. +.. _network_qos_policy_create-name: .. describe:: <name> New QoS policy specification name @@ -58,6 +59,7 @@ Delete Network QoS policy openstack network qos policy delete <qos-policy> [<qos-policy> ...] +.. _network_qos_policy_delete-qos-policy: .. describe:: <qos-policy> Network QoS policy(s) to delete (name or ID) @@ -102,9 +104,10 @@ Set Network QoS policy properties Make the QoS policy not accessible by other projects +.. _network_qos_policy_set-qos-policy: .. describe:: <qos-policy> - Network QoS policy(s) to delete (name or ID) + Network QoS policy to modify (name or ID) network qos policy show ----------------------- @@ -117,6 +120,7 @@ Display Network QoS policy details openstack network qos policy show <qos-policy> +.. _network_qos_policy_show-qos-policy: .. describe:: <qos-policy> - Network QoS policy(s) to show (name or ID) + Network QoS policy to display (name or ID) diff --git a/doc/source/command-objects/network-qos-rule-type.rst b/doc/source/command-objects/network-qos-rule-type.rst index 020a05c..ee53e30 100644 --- a/doc/source/command-objects/network-qos-rule-type.rst +++ b/doc/source/command-objects/network-qos-rule-type.rst @@ -15,4 +15,4 @@ List Network QoS rule types .. program:: network qos rule type list .. code:: bash - os network qos rule type list + openstack network qos rule type list diff --git a/doc/source/command-objects/network-qos-rule.rst b/doc/source/command-objects/network-qos-rule.rst index b98244b..1baf5db 100644 --- a/doc/source/command-objects/network-qos-rule.rst +++ b/doc/source/command-objects/network-qos-rule.rst @@ -17,7 +17,7 @@ Create new Network QoS rule .. program:: network qos rule create .. code:: bash - os network qos rule create + openstack network qos rule create --type <type> [--max-kbps <max-kbps>] [--max-burst-kbits <max-burst-kbits>] @@ -67,7 +67,7 @@ Delete Network QoS rule .. program:: network qos rule delete .. code:: bash - os network qos rule delete + openstack network qos rule delete <qos-policy> <rule-id> @@ -87,7 +87,7 @@ List Network QoS rules .. program:: network qos rule list .. code:: bash - os network qos rule list + openstack network qos rule list <qos-policy> .. describe:: <qos-policy> @@ -102,7 +102,7 @@ Set Network QoS rule properties .. program:: network qos rule set .. code:: bash - os network qos rule set + openstack network qos rule set [--max-kbps <max-kbps>] [--max-burst-kbits <max-burst-kbits>] [--dscp-marks <dscp-marks>] @@ -152,7 +152,7 @@ Display Network QoS rule details .. program:: network qos rule show .. code:: bash - os network qos rule show + openstack network qos rule show <qos-policy> <rule-id> diff --git a/doc/source/command-objects/network-service-provider.rst b/doc/source/command-objects/network-service-provider.rst index 8db2ab4..8ccec45 100644 --- a/doc/source/command-objects/network-service-provider.rst +++ b/doc/source/command-objects/network-service-provider.rst @@ -7,6 +7,7 @@ networking service Network v2 +.. _network_service_provider_list: network service provider list ----------------------------- diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst index 201a889..5080add 100644 --- a/doc/source/command-objects/port.rst +++ b/doc/source/command-objects/port.rst @@ -155,6 +155,7 @@ List ports [--router <router> | --server <server>] [--network <network>] [--mac-address <mac-address>] + [--fixed-ip subnet=<subnet>,ip-address=<ip-address>] [--long] [--project <project> [--project-domain <project-domain>]] @@ -179,6 +180,12 @@ List ports List only ports with this MAC address +.. option:: --fixed-ip subnet=<subnet>,ip-address=<ip-address> + + Desired IP and/or subnet (name or ID) for filtering ports: + subnet=<subnet>,ip-address=<ip-address> + (repeat option to set multiple fixed IP addresses) + .. option:: --long List additional fields in output diff --git a/doc/source/command-objects/server-group.rst b/doc/source/command-objects/server-group.rst index 77c6431..9769635 100644 --- a/doc/source/command-objects/server-group.rst +++ b/doc/source/command-objects/server-group.rst @@ -15,13 +15,13 @@ Create a new server group .. code-block:: bash openstack server group create - --policy <policy> [--policy <policy>] ... + --policy <policy> <name> .. option:: --policy <policy> Add a policy to :ref:`\<name\> <server_group_create-name>` - (repeat option to add multiple policies) + ('affinity' or 'anti-affinity', default to 'affinity') .. _server_group_create-name: .. describe:: <name> diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index a5d22f8..b2ae965 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -173,6 +173,8 @@ Create a new server v6-fixed-ip: IPv6 fixed address for NIC (optional). none: (v2.37+) no network is attached. auto: (v2.37+) the compute service will automatically allocate a network. + Specifying a --nic of auto or none cannot be used with any other + --nic value. .. option:: --hint <key=value> diff --git a/doc/source/command-objects/volume-host.rst b/doc/source/command-objects/volume-host.rst index a225e53..1e513cb 100644 --- a/doc/source/command-objects/volume-host.rst +++ b/doc/source/command-objects/volume-host.rst @@ -4,6 +4,28 @@ volume host Volume v2 +volume host failover +-------------------- + +Failover volume host to different backend + +.. program:: volume host failover +.. code:: bash + + openstack volume host failover + --volume-backend <backend-id> + <host-name> + +.. option:: --volume-backend <backend-id> + + The ID of the volume backend replication + target where the host will failover to (required) + +.. _volume_host_failover-host-name: +.. describe:: <host-name> + + Name of volume host + volume host set --------------- @@ -18,13 +40,13 @@ Set volume host properties .. option:: --enable - Thaw and enable the specified volume host + Thaw and enable the specified volume host. .. option:: --disable Freeze and disable the specified volume host -.. _volume-host-set: +.. _volume_host_set-host-name: .. describe:: <host-name> Name of volume host diff --git a/doc/source/command-objects/volume-type.rst b/doc/source/command-objects/volume-type.rst index 40e9825..afa293d 100644 --- a/doc/source/command-objects/volume-type.rst +++ b/doc/source/command-objects/volume-type.rst @@ -18,6 +18,10 @@ Create new volume type [--property <key=value> [...] ] [--project <project>] [--project-domain <project-domain>] + [--encryption-provider <provider>] + [--encryption-cipher <cipher>] + [--encryption-key-size <key-size>] + [--encryption-control-location <control-location>] <name> .. option:: --description <description> @@ -56,6 +60,34 @@ Create new volume type *Volume version 2 only* +.. option:: --encryption-provider <provider> + + Set the class that provides encryption support for this volume type + (e.g "LuksEncryptor") (admin only) + + This option is required when setting encryption type of a volume. + Consider using other encryption options such as: :option:`--encryption-cipher`, + :option:`--encryption-key-size` and :option:`--encryption-control-location` + +.. option:: --encryption-cipher <cipher> + + Set the encryption algorithm or mode for this volume type + (e.g "aes-xts-plain64") (admin only) + +.. option:: --encryption-key-size <key-size> + + Set the size of the encryption key of this volume type + (e.g "128" or "256") (admin only) + +.. option:: --encryption-control-location <control-location> + + Set the notional service where the encryption is performed + ("front-end" or "back-end") (admin only) + + The default value for this option is "front-end" when setting encryption type of + a volume. Consider using other encryption options such as: :option:`--encryption-cipher`, + :option:`--encryption-key-size` and :option:`--encryption-provider` + .. _volume_type_create-name: .. describe:: <name> @@ -88,6 +120,7 @@ List volume types openstack volume type list [--long] [--default | --public | --private] + [--encryption-type] .. option:: --long @@ -111,6 +144,10 @@ List volume types *Volume version 2 only* +.. option:: --encryption-type + + Display encryption information for each volume type (admin only) + volume type set --------------- @@ -125,6 +162,10 @@ Set volume type properties [--property <key=value> [...] ] [--project <project>] [--project-domain <project-domain>] + [--encryption-provider <provider>] + [--encryption-cipher <cipher>] + [--encryption-key-size <key-size>] + [--encryption-control-location <control-location>] <volume-type> .. option:: --name <name> @@ -154,6 +195,34 @@ Set volume type properties Set a property on this volume type (repeat option to set multiple properties) +.. option:: --encryption-provider <provider> + + Set the class that provides encryption support for this volume type + (e.g "LuksEncryptor") (admin only) + + This option is required when setting encryption type of a volume for the first time. + Consider using other encryption options such as: :option:`--encryption-cipher`, + :option:`--encryption-key-size` and :option:`--encryption-control-location` + +.. option:: --encryption-cipher <cipher> + + Set the encryption algorithm or mode for this volume type + (e.g "aes-xts-plain64") (admin only) + +.. option:: --encryption-key-size <key-size> + + Set the size of the encryption key of this volume type + (e.g "128" or "256") (admin only) + +.. option:: --encryption-control-location <control-location> + + Set the notional service where the encryption is performed + ("front-end" or "back-end") (admin only) + + The default value for this option is "front-end" when setting encryption type of + a volume for the first time. Consider using other encryption options such as: + :option:`--encryption-cipher`, :option:`--encryption-key-size` and :option:`--encryption-provider` + .. _volume_type_set-volume-type: .. describe:: <volume-type> @@ -168,8 +237,13 @@ Display volume type details .. code:: bash openstack volume type show + [--encryption-type] <volume-type> +.. option:: --encryption-type + + Display encryption information of this volume type (admin only) + .. _volume_type_show-volume-type: .. describe:: <volume-type> @@ -187,6 +261,7 @@ Unset volume type properties [--property <key> [...] ] [--project <project>] [--project-domain <project-domain>] + [--encryption-type] <volume-type> .. option:: --property <key> @@ -204,6 +279,12 @@ Unset volume type properties Domain the project belongs to (name or ID). This can be used in case collisions between project names exist. + *Volume version 2 only* + +.. option:: --encryption-type + + Remove the encryption type for this volume type (admin only) + .. _volume_type_unset-volume-type: .. describe:: <volume-type> diff --git a/doc/source/command-objects/volume.rst b/doc/source/command-objects/volume.rst index 56c5455..67598e6 100644 --- a/doc/source/command-objects/volume.rst +++ b/doc/source/command-objects/volume.rst @@ -269,6 +269,7 @@ Set volume properties [--name <name>] [--size <size>] [--description <description>] + [--no-property] [--property <key=value> [...] ] [--image-property <key=value> [...] ] [--state <state>] @@ -290,6 +291,12 @@ Set volume properties New volume description +.. option:: --no-property + + Remove all properties from :ref:`\<volume\> <volume_set-volume>` + (specify both :option:`--no-property` and :option:`--property` to + remove the current properties before setting new properties.) + .. option:: --property <key=value> Set a property on this volume (repeat option to set multiple properties) @@ -304,7 +311,7 @@ Set volume properties Migration policy while re-typing volume ("never" or "on-demand", default is "never" ) - (available only when "--type" option is specified) + (available only when :option:`--type` option is specified) *Volume version 2 only* diff --git a/doc/source/commands.rst b/doc/source/commands.rst index ece0b6e..a0c67cd 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -111,6 +111,8 @@ referring to both Compute and Volume quotas. * ``module``: (**Internal**) - installed Python modules in the OSC process * ``network``: (**Compute**, **Network**) - a virtual network for connecting servers and other resources * ``network agent``: (**Network**) - A network agent is an agent that handles various tasks used to implement virtual networks +* ``network auto allocated topology``: (**Network**) - an auto-allocated topology for a project +* ``network flavor``: (**Network**) - allows the user to choose the type of service by a set of advertised service capabilities (e.g., LOADBALANCER, FWAAS, L3, VPN, etc) rather than by a provider type or named vendor * ``network meter``: (**Network**) - allow traffic metering in a network * ``network meter rule``: (**Network**) - rules for network traffic metering * ``network rbac``: (**Network**) - an RBAC policy for network resources @@ -239,6 +241,7 @@ Those actions with an opposite action are noted in parens if applicable. * ``create`` (``delete``) - create a new occurrence of the specified object * ``delete`` (``create``) - delete specific occurrences of the specified objects * ``expand`` (``shrink``) - increase the capacity of a cluster +* ``failover`` - failover volume host to different backend * ``issue`` (``revoke``) - issue a token * ``list`` - display summary information about multiple objects * ``lock`` (``unlock``) - lock one or more servers so that non-admin user won't be able to execute actions diff --git a/doc/source/data/cinder.csv b/doc/source/data/cinder.csv new file mode 100644 index 0000000..385c738 --- /dev/null +++ b/doc/source/data/cinder.csv @@ -0,0 +1,104 @@ +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 export,Export backup metadata record.
+backup-import,volume backup 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 types 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,,Show backend volume stats and properties. Admin only.
+get-pools,,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 <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 <key=value>,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.
\ No newline at end of file diff --git a/doc/source/data/glance.csv b/doc/source/data/glance.csv new file mode 100644 index 0000000..2985e30 --- /dev/null +++ b/doc/source/data/glance.csv @@ -0,0 +1,24 @@ +explain,WONTFIX,Describe a specific model.
+image-create,image create,Create a new image.
+image-deactivate,image set --deactivate,Deactivate specified image.
+image-delete,image delete,Delete specified image.
+image-download,image save,Download a specific image.
+image-list,image list,List images you can access.
+image-reactivate,image set --activate,Reactivate specified image.
+image-show,image show,Describe a specific image.
+image-tag-delete,image set --tag <tag>,Delete the tag associated with the given image.
+image-tag-update,image unset --tag <tag>,Update an image with the given tag.
+image-update,image set,Update an existing image.
+image-upload,,Upload data for a specific image.
+location-add,,Add a location (and related metadata) to an image.
+location-delete,,Remove locations (and related metadata) from an image.
+location-update,,Update metadata of an image's location.
+member-create,image add project,Create member for a given image.
+member-delete,image remove project,Delete image member.
+member-list,,Describe sharing permissions by image.
+member-update,image set --accept --reject --status,Update the status of a member for a given image.
+task-create,,Create a new task.
+task-list,,List tasks you can access.
+task-show,,Describe a specific task.
+bash-completion,complete,Prints arguments for bash_completion.
+help,help,Display help about this program or one of its subcommands.
\ No newline at end of file diff --git a/doc/source/data/keystone.csv b/doc/source/data/keystone.csv new file mode 100644 index 0000000..b136401 --- /dev/null +++ b/doc/source/data/keystone.csv @@ -0,0 +1,37 @@ +catalog,catalog show,"List service catalog, possibly filtered by service."
+ec2-credentials-create,ec2 credentials create,Create EC2-compatible credentials for user per tenant.
+ec2-credentials-delete,ec2 credentials delete,Delete EC2-compatible credentials.
+ec2-credentials-get,ec2 credentials show,Display EC2-compatible credentials.
+ec2-credentials-list,ec2 credentials list,List EC2-compatible credentials for a user.
+endpoint-create,endpoint create,Create a new endpoint associated with a service.
+endpoint-delete,endpoint delete,Delete a service endpoint.
+endpoint-get,endpoint get,Find endpoint filtered by a specific attribute or service type.
+endpoint-list,endpoint list,List configured service endpoints.
+password-update,user password set,Update own password.
+role-create,role create,Create new role.
+role-delete,role delete,Delete role.
+role-get,role show,Display role details.
+role-list,role list,List all roles.
+service-create,service create,Add service to Service Catalog.
+service-delete,service delete,Delete service from Service Catalog.
+service-get,service show,Display service from Service Catalog.
+service-list,service list,List all services in Service Catalog.
+tenant-create,project create,Create new tenant.
+tenant-delete,project delete,Delete tenant.
+tenant-get,proejct show,Display tenant details.
+tenant-list,project list,List all tenants.
+tenant-update,project set,"Update tenant name, description, enabled status."
+token-get,issue token,Display the current user token.
+user-create,user create,Create new user.
+user-delete,user delete,Delete user.
+user-get,user show,Display user details.
+user-list,user list,List users.
+user-password-update,user set --password,Update user password.
+user-role-add,role add <role> --user --project,Add role to user.
+user-role-list,role assignment list --user --project,List roles granted to a user.
+user-role-remove,role remove <role> --user --project,Remove role from user.
+user-update,user set,"Update user's name, email, and enabled status."
+discover,WONTFIX,"Discover Keystone servers, supported API versions and extensions."
+bootstrap,WONTFIX,"Grants a new role to a new user on a new tenant, after creating each."
+bash-completion,complete,Prints all of the commands and options to stdout.
+help,help,Display help about this program or one of its subcommands.
\ No newline at end of file diff --git a/doc/source/data/neutron.csv b/doc/source/data/neutron.csv new file mode 100644 index 0000000..c2f0b41 --- /dev/null +++ b/doc/source/data/neutron.csv @@ -0,0 +1,244 @@ +address-scope-create,address scope create,Create an address scope for a given tenant. +address-scope-delete,address scope delete,Delete an address scope. +address-scope-list,address scope list,List address scopes that belong to a given tenant. +address-scope-show,address scope show,Show information about an address scope. +address-scope-update,address scope set,Update an address scope. +agent-delete,network agent delete,Delete a given agent. +agent-list,network agent list,List agents. +agent-show,network agent show,Show information of a given agent. +agent-update,network agent set,Updates the admin status and description for a specified agent. +auto-allocated-topology-delete,network auto allocated topology delete,Delete the auto-allocated topology of a given tenant. +auto-allocated-topology-show,network auto allocated topology create,Show the auto-allocated topology of a given tenant. +availability-zone-list,availability zone list,List availability zones. +bash-completion,complete,Prints all of the commands and options for bash-completion. +bgp-dragent-list-hosting-speaker,,List Dynamic Routing agents hosting a BGP speaker. +bgp-dragent-speaker-add,,Add a BGP speaker to a Dynamic Routing agent. +bgp-dragent-speaker-remove,,Removes a BGP speaker from a Dynamic Routing agent. +bgp-peer-create,,Create a BGP Peer. +bgp-peer-delete,,Delete a BGP peer. +bgp-peer-list,,List BGP peers. +bgp-peer-show,,Show information of a given BGP peer. +bgp-peer-update,,Update BGP Peer's information. +bgp-speaker-advertiseroute-list,,List routes advertised by a given BGP speaker. +bgp-speaker-create,,Create a BGP Speaker. +bgp-speaker-delete,,Delete a BGP speaker. +bgp-speaker-list,,List BGP speakers. +bgp-speaker-list-on-dragent,,List BGP speakers hosted by a Dynamic Routing agent. +bgp-speaker-network-add,,Add a network to the BGP speaker. +bgp-speaker-network-remove,,Remove a network from the BGP speaker. +bgp-speaker-peer-add,,Add a peer to the BGP speaker. +bgp-speaker-peer-remove,,Remove a peer from the BGP speaker. +bgp-speaker-show,,Show information of a given BGP speaker. +bgp-speaker-update,,Update BGP Speaker's information. +dhcp-agent-list-hosting-net,network agent list --network,List DHCP agents hosting a network. +dhcp-agent-network-add,network agent add network,Add a network to a DHCP agent. +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. +flavor-associate,network flavor associate,Associate 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. +flavor-disassociate,network flavor disassociate,Disassociate a Neutron service flavor from a flavor profile. +flavor-list,network flavor list,List Neutron service flavors. +flavor-profile-create,network flavor profile create,Create a Neutron service flavor profile. +flavor-profile-delete,network flavor profile delete,Delete a given Neutron service flavor profile. +flavor-profile-list,network flavor profile list,List Neutron service flavor profiles. +flavor-profile-show,network flavor profile show,Show information about a given Neutron service flavor profile. +flavor-profile-update,network flavor profile set,Update a given Neutron service flavor profile. +flavor-show,network flavor show,Show information about a given Neutron service flavor. +flavor-update,network flavor set,Update a Neutron service flavor. +floatingip-associate,floating ip set port --fixed-ip,Create a mapping between a floating IP and a fixed IP. +floatingip-create,floating ip create,Create a floating IP for a given tenant. +floatingip-delete,floating ip delete,Delete a given floating IP. +floatingip-disassociate,floating ip unset port,Remove a mapping from a floating IP to a fixed IP. +floatingip-list,floating ip list,List floating IPs that belong to a given tenant. +floatingip-show,floating ip show,Show information of a given floating IP. +help,help,print detailed help for another command +ipsec-site-connection-create,,Create an IPsec site connection. +ipsec-site-connection-delete,,Delete a given IPsec site connection. +ipsec-site-connection-list,,List IPsec site connections that belong to a given tenant. +ipsec-site-connection-show,,Show information of a given IPsec site connection. +ipsec-site-connection-update,,Update a given IPsec site connection. +l3-agent-list-hosting-router,network agent list --routers,List L3 agents hosting a router. +l3-agent-router-add,network agent add router --agent-type l3,Add a router to a L3 agent. +l3-agent-router-remove,network agent remove router --agent-type l3,Remove a router from a L3 agent. +lb-agent-hosting-pool,,Get loadbalancer agent hosting a pool. +lb-healthmonitor-associate,,Create a mapping between a health monitor and a pool. +lb-healthmonitor-create,,Create a health monitor. +lb-healthmonitor-delete,,Delete a given health monitor. +lb-healthmonitor-disassociate,,Remove a mapping from a health monitor to a pool. +lb-healthmonitor-list,,List health monitors that belong to a given tenant. +lb-healthmonitor-show,,Show information of a given health monitor. +lb-healthmonitor-update,,Update a given health monitor. +lb-member-create,,Create a member. +lb-member-delete,,Delete a given member. +lb-member-list,,List members that belong to a given tenant. +lb-member-show,,Show information of a given member. +lb-member-update,,Update a given member. +lb-pool-create,,Create a pool. +lb-pool-delete,,Delete a given pool. +lb-pool-list,,List pools that belong to a given tenant. +lb-pool-list-on-agent,,List the pools on a loadbalancer agent. +lb-pool-show,,Show information of a given pool. +lb-pool-stats,,Retrieve stats for a given pool. +lb-pool-update,,Update a given pool. +lb-vip-create,,Create a vip. +lb-vip-delete,,Delete a given vip. +lb-vip-list,,List vips that belong to a given tenant. +lb-vip-show,,Show information of a given vip. +lb-vip-update,,Update a given vip. +lbaas-agent-hosting-loadbalancer,,Get lbaas v2 agent hosting a loadbalancer. +lbaas-healthmonitor-create,,LBaaS v2 Create a healthmonitor. +lbaas-healthmonitor-delete,,LBaaS v2 Delete a given healthmonitor. +lbaas-healthmonitor-list,,LBaaS v2 List healthmonitors that belong to a given tenant. +lbaas-healthmonitor-show,,LBaaS v2 Show information of a given healthmonitor. +lbaas-healthmonitor-update,,LBaaS v2 Update a given healthmonitor. +lbaas-l7policy-create,,LBaaS v2 Create L7 policy. +lbaas-l7policy-delete,,LBaaS v2 Delete a given L7 policy. +lbaas-l7policy-list,,LBaaS v2 List L7 policies that belong to a given listener. +lbaas-l7policy-show,,LBaaS v2 Show information of a given L7 policy. +lbaas-l7policy-update,,LBaaS v2 Update a given L7 policy. +lbaas-l7rule-create,,LBaaS v2 Create L7 rule. +lbaas-l7rule-delete,,LBaaS v2 Delete a given L7 rule. +lbaas-l7rule-list,,LBaaS v2 List L7 rules that belong to a given L7 policy. +lbaas-l7rule-show,,LBaaS v2 Show information of a given rule. +lbaas-l7rule-update,,LBaaS v2 Update a given L7 rule. +lbaas-listener-create,,LBaaS v2 Create a listener. +lbaas-listener-delete,,LBaaS v2 Delete a given listener. +lbaas-listener-list,,LBaaS v2 List listeners that belong to a given tenant. +lbaas-listener-show,,LBaaS v2 Show information of a given listener. +lbaas-listener-update,,LBaaS v2 Update a given listener. +lbaas-loadbalancer-create,,LBaaS v2 Create a loadbalancer. +lbaas-loadbalancer-delete,,LBaaS v2 Delete a given loadbalancer. +lbaas-loadbalancer-list,,LBaaS v2 List loadbalancers that belong to a given tenant. +lbaas-loadbalancer-list-on-agent,,List the loadbalancers on a loadbalancer v2 agent. +lbaas-loadbalancer-show,,LBaaS v2 Show information of a given loadbalancer. +lbaas-loadbalancer-stats,,Retrieve stats for a given loadbalancer. +lbaas-loadbalancer-status,,Retrieve status for a given loadbalancer. +lbaas-loadbalancer-update,,LBaaS v2 Update a given loadbalancer. +lbaas-member-create,,LBaaS v2 Create a member. +lbaas-member-delete,,LBaaS v2 Delete a given member. +lbaas-member-list,,LBaaS v2 List members that belong to a given pool. +lbaas-member-show,,LBaaS v2 Show information of a given member. +lbaas-member-update,,LBaaS v2 Update a given member. +lbaas-pool-create,,LBaaS v2 Create a pool. +lbaas-pool-delete,,LBaaS v2 Delete a given pool. +lbaas-pool-list,,LBaaS v2 List pools that belong to a given tenant. +lbaas-pool-show,,LBaaS v2 Show information of a given pool. +lbaas-pool-update,,LBaaS v2 Update a given pool. +meter-label-create,network meter create,Create a metering label for a given tenant. +meter-label-delete,network meter delete,Delete a given metering label. +meter-label-list,network meter list,List metering labels that belong to a given tenant. +meter-label-rule-create,network meter rule create,Create a metering label rule for a given label. +meter-label-rule-delete,network meter rule delete,Delete a given metering label. +meter-label-rule-list,network meter rule list,List metering labels that belong to a given label. +meter-label-rule-show,network meter rule show,Show information of a given metering label rule. +meter-label-show,network meter show,Show information of a given metering label. +net-create,network create,Create a network for a given tenant. +net-delete,network delete,Delete a given network. +net-external-list,network list --external,List external networks that belong to a given tenant. +net-ip-availability-list,ip availability list,List IP usage of networks +net-ip-availability-show,ip availability show,Show IP usage of specific network +net-list,network list,List networks that belong to a given tenant. +net-list-on-dhcp-agent,network list --agent,List the networks on a DHCP agent. +net-show,network show,Show information of a given network. +net-update,network set,Update network's information. +port-create,port create,Create a port for a given tenant. +port-delete,port delete,Delete a given port. +port-list,port list,List ports that belong to a given tenant. +port-show,port show,Show information of a given port. +port-update,port set/port unset,Update port's information. +purge,,Delete all resources that belong to a given tenant. +qos-available-rule-types,network qos rule type list,List available qos rule types. +qos-bandwidth-limit-rule-create,network qos rule create --type bandwidth-limit,Create a qos bandwidth limit rule. +qos-bandwidth-limit-rule-delete,network qos rule delete --type bandwidth-limit,Delete a given qos bandwidth limit rule. +qos-bandwidth-limit-rule-list,network qos rule list --type bandwidth-limit,List all qos bandwidth limit rules belonging to the specified policy. +qos-bandwidth-limit-rule-show,network qos rule show --type bandwidth-limit,Show information about the given qos bandwidth limit rule. +qos-bandwidth-limit-rule-update,network qos rule update --type bandwidth-limit,Update the given qos bandwidth limit rule. +qos-dscp-marking-rule-create,network qos rule create --type dscp-marking,Create a QoS DSCP marking rule. +qos-dscp-marking-rule-delete,network qos rule delete --type dscp-marking,Delete a given qos dscp marking rule. +qos-dscp-marking-rule-list,network qos rule list --type dscp-marking,List all QoS DSCP marking rules belonging to the specified policy. +qos-dscp-marking-rule-show,network qos rule show --type dscp-marking,Show information about the given qos dscp marking rule. +qos-dscp-marking-rule-update,network qos rule update --type dscp-marking,Update the given QoS DSCP marking rule. +qos-minimum-bandwidth-rule-create,network qos rule create --type minimum-bandwidth,Create a qos minimum bandwidth rule. +qos-minimum-bandwidth-rule-delete,network qos rule delete --type minimum-bandwidth,Delete a given qos minimum bandwidth rule. +qos-minimum-bandwidth-rule-list,network qos rule list --type minimum-bandwidth,List all qos minimum bandwidth rules belonging to the specified policy. +qos-minimum-bandwidth-rule-show,network qos rule show --type minimum-bandwidth,Show information about the given qos minimum bandwidth rule. +qos-minimum-bandwidth-rule-update,network qos rule update --type minimum-bandwidth,Update the given qos minimum bandwidth rule. +qos-policy-create,network qos policy create,Create a qos policy. +qos-policy-delete,network qos policy delete,Delete a given qos policy. +qos-policy-list,network qos policy list,List QoS policies that belong to a given tenant connection. +qos-policy-show,network qos policy show,Show information of a given qos policy. +qos-policy-update,network qos policy set,Update a given qos policy. +quota-default-show,quota show --default,Show default quotas for a given tenant. +quota-delete,,Delete defined quotas of a given tenant. +quota-list,quota list,List quotas of all tenants who have non-default quota values. +quota-show,quota show,Show quotas for a given tenant. +quota-update,quota set,Define tenant's quotas not to use defaults. +rbac-create,network rbac create,Create a RBAC policy for a given tenant. +rbac-delete,network rbac delete,Delete a RBAC policy. +rbac-list,network rbac list,List RBAC policies that belong to a given tenant. +rbac-show,network rbac show,Show information of a given RBAC policy. +rbac-update,network rbac set,Update RBAC policy for given tenant. +router-create,router create,Create a router for a given tenant. +router-delete,router delete,Delete a given router. +router-gateway-clear,router unset,Remove an external network gateway from a router. +router-gateway-set,router set,Set the external network gateway for a router. +router-interface-add,router add subnet / router add port,Add an internal network interface to a router. +router-interface-delete,router remove subnet / router remove port,Remove an internal network interface from a router. +router-list,router list,List routers that belong to a given tenant. +router-list-on-l3-agent,router list --agents,List the routers on a L3 agent. +router-port-list,port list --router,"List ports that belong to a given tenant, with specified router." +router-show,router show,Show information of a given router. +router-update,router set,Update router's information. +security-group-create,security group create,Create a security group. +security-group-delete,security group delete,Delete a given security group. +security-group-list,security group list,List security groups that belong to a given tenant. +security-group-rule-create,security group rule create,Create a security group rule. +security-group-rule-delete,security group rule delete,Delete a given security group rule. +security-group-rule-list,security group rule list,List security group rules that belong to a given tenant. +security-group-rule-show,security group rule show,Show information of a given security group rule. +security-group-show,security group show,Show information of a given security group. +security-group-update,security group set,Update a given security group. +service-provider-list,network service provider list,List service providers. +subnet-create,subnet create,Create a subnet for a given tenant. +subnet-delete,subnet delete,Delete a given subnet. +subnet-list,subnet list,List subnets that belong to a given tenant. +subnet-show,subnet show,Show information of a given subnet. +subnet-update,subnet set / subnet unset,Update subnet's information. +subnetpool-create,subnet pool create,Create a subnetpool for a given tenant. +subnetpool-delete,subnet pool delete,Delete a given subnetpool. +subnetpool-list,subnet pool list,List subnetpools that belong to a given tenant. +subnetpool-show,subnet pool show,Show information of a given subnetpool. +subnetpool-update,subnet pool set / subnet pool unset,Update subnetpool's information. +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. +vpn-endpoint-group-show,,Show a specific VPN endpoint group. +vpn-endpoint-group-update,,Update a given VPN endpoint group. +vpn-ikepolicy-create,,Create an IKE policy. +vpn-ikepolicy-delete,,Delete a given IKE policy. +vpn-ikepolicy-list,,List IKE policies that belong to a tenant. +vpn-ikepolicy-show,,Show information of a given IKE policy. +vpn-ikepolicy-update,,Update a given IKE policy. +vpn-ipsecpolicy-create,,Create an IPsec policy. +vpn-ipsecpolicy-delete,,Delete a given IPsec policy. +vpn-ipsecpolicy-list,,List IPsec policies that belong to a given tenant connection. +vpn-ipsecpolicy-show,,Show information of a given IPsec policy. +vpn-ipsecpolicy-update,,Update a given IPsec policy. +vpn-service-create,,Create a VPN service. +vpn-service-delete,,Delete a given VPN service. +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.
\ No newline at end of file diff --git a/doc/source/data/nova.csv b/doc/source/data/nova.csv new file mode 100644 index 0000000..5598039 --- /dev/null +++ b/doc/source/data/nova.csv @@ -0,0 +1,140 @@ +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-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. +aggregate-remove-host,aggregate remove host,Remove the specified host from the specified aggregate. +aggregate-set-metadata,aggregate set / unset,Update the metadata associated with the aggregate. +aggregate-show,aggregate show,Show details of the specified aggregate. +aggregate-update,aggregate set / unset,Update the aggregate's name and optionally availability zone. +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. +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. +evacuate,,Evacuate server from failed host. +flavor-access-add,,Add flavor access for the given tenant. +flavor-access-list,,Print access information about the given flavor. +flavor-access-remove,,Remove flavor access for the given tenant. +flavor-create,flavor create,Create a new flavor. +flavor-delete,flavor delete,Delete a specific flavor +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. +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-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. +host-evacuate,,Evacuate all instances from failed host. +host-evacuate-live,,Live migrate all instances of the specified host to other available hosts. +host-list,host list,List all hosts by service. +host-meta,host set / unset,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-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. +hypervisor-uptime,,Display the uptime of the specified hypervisor. +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. +interface-attach,,Attach a network interface to a server. +interface-detach,,Detach a network interface from a server. +interface-list,,List interfaces attached to a server. +keypair-add,keypair create,Create a new key pair for use with servers. +keypair-delete,keypair delete,Delete keypair given by its name. +keypair-list,keypair list,Print a list of keypairs for a user +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. +live-migration-force-comp,,Force on-going live migration to complete. +lock,server lock,Lock a server. +meta,server set --property / unset,Set or delete metadata on a server. +migrate,server migrate,Migrate a server. The new host will be selected by the scheduler. +migration-list,,Print a list of migrations. +pause,server pause,Pause a server. +quota-class-show,,List the quotas for a quota class. +quota-class-update,quota set --class,Update the quotas for a quota class. +quota-defaults,quota list,List the default quotas for a tenant. +quota-delete,quota set,Delete quota for a tenant/user so their quota will Revert back to default. +quota-show,quota show,List the quotas for a tenant/user. +quota-update,quota set,Update the quotas for a tenant/user. +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. +reset-state,server set --state,Reset the state of a server. +resize,server resize,Resize a server. +resize-confirm,server resize --confirm,Confirm a previous resize. +resize-revert,server resize --revert,Revert a previous resize. +restore,server restore,Restore a soft-deleted server. +resume,server resume,Resume a server. +server-group-create,server group create,Create a new server group with the specified details. +server-group-delete,server group delete,Delete specific server group(s). +server-group-get,server group show,Get a specific server group. +server-group-list,server group list,Print a list of all server groups. +server-migration-list,,Get the migrations list of specified server. +server-migration-show,,Get the migration of specified server. +server-tag-add,,Add one or more tags to a server. +server-tag-delete,,Delete one or more tags from a server. +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. +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. +service-force-down,compute service set --force,Force service to down. +service-list,compute service list,Show a list of all running services. +set-password,server set --root-password,Change the admin password for a server. +shelve,server shelve,Shelve a server. +shelve-offload,,Remove a shelved server from the compute node. +show,server show,Show details about the given server. +ssh,server ssh,SSH into a server. +start,server start,Start the server(s). +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. +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,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 +help,help,Display help about this program or one of its subcommands.
\ No newline at end of file diff --git a/doc/source/data/swift.csv b/doc/source/data/swift.csv new file mode 100644 index 0000000..6814741 --- /dev/null +++ b/doc/source/data/swift.csv @@ -0,0 +1,10 @@ +delete,object delete / container delete,Delete a container or objects within a container.
+download,object save / container save,Download objects from containers.
+list,object list / container list,Lists the containers for the account or the objects for a container.
+post,container create / object set / container set / object store account set,"Updates meta information for the account, container, or object."
+copy,,"Copies object, optionally adds meta."
+stat,object show / container show / object store account show,"Displays information for the account, container, or object."
+upload,object create,Uploads files or directories to the given container.
+capabilities,,List cluster capabilities.
+tempurl,,Create a temporary URL.
+auth,WONTFIX,Display auth related environment variables.
diff --git a/doc/source/decoder.rst b/doc/source/decoder.rst new file mode 100644 index 0000000..16810a4 --- /dev/null +++ b/doc/source/decoder.rst @@ -0,0 +1,56 @@ +============= +Mapping Guide +============= + +The following is an incomplete mapping between legacy OpenStack CLIs and +OpenStackClient. Think of it as a magic decoder ring if you were using the +legacy CLIs and want to transition to OpenStack CLI. Command options are only +shown when necessary. + +``cinder CLI`` +-------------- + +.. csv-table:: + :header: "Cinder CLI", "OSC Equivalent", "Description" + :widths: 25, 25, 50 + :file: data/cinder.csv + +``glance CLI`` +-------------- + +.. csv-table:: + :header: "Glance CLI", "OSC Equivalent", "Description" + :widths: 25, 25, 50 + :file: data/glance.csv + +``keystone CLI`` +---------------- + +.. csv-table:: + :header: "Keystone CLI", "OSC Equivalent", "Description" + :widths: 25, 25, 50 + :file: data/keystone.csv + +``neutron CLI`` +--------------- + +.. csv-table:: + :header: "Neutron CLI", "OSC Equivalent", "Description" + :widths: 25, 25, 50 + :file: data/neutron.csv + +``nova CLI`` +------------ + +.. csv-table:: + :header: "Nova CLI", "OSC Equivalent", "Description" + :widths: 25, 25, 50 + :file: data/nova.csv + +``swift CLI`` +------------- + +.. csv-table:: + :header: "Swift CLI", "OSC Equivalent", "Description" + :widths: 25, 25, 50 + :file: data/swift.csv diff --git a/doc/source/index.rst b/doc/source/index.rst index c8fc6f2..e19b466 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -24,6 +24,11 @@ User Documentation humaninterfaceguide backwards-incompatible +.. toctree:: + :maxdepth: 2 + + decoder + Getting Started --------------- diff --git a/doc/source/plugin-commands.rst b/doc/source/plugin-commands.rst index 844c48a..182f539 100644 --- a/doc/source/plugin-commands.rst +++ b/doc/source/plugin-commands.rst @@ -33,10 +33,10 @@ designate .. list-plugins:: openstack.dns.v1 :detailed: -.. gnocchi -.. # gnocchiclient is not in global-requirements -.. # list-plugins:: openstack.metric.v1 -.. # :detailed: +gnocchi +------- +.. list-plugins:: openstack.metric.v1 + :detailed: heat ---- diff --git a/doc/source/plugins.rst b/doc/source/plugins.rst index eafe5e8..7dda52a 100644 --- a/doc/source/plugins.rst +++ b/doc/source/plugins.rst @@ -22,7 +22,7 @@ OpenStackClient. The following is a list of projects that are an OpenStackClient plugin. - aodhclient -- gnocchiclient\*\* +- gnocchiclient - python-barbicanclient - python-congressclient - python-designateclient @@ -190,12 +190,15 @@ unit tests for the plugin commands: Requirements ------------ -OSC must be included in ``requirements.txt`` or ``test-requirements.txt`` -for the plugin project. Update ``requirements.txt`` if the plugin project -considers the CLI a required feature. Update ``test-requirements.txt`` if -the plugin project can be installed as a library with the CLI being an +OSC should be included in the plugin's ``test-requirements.txt`` if +the plugin can be installed as a library with the CLI being an optional feature (available when OSC is also installed). +OSC should not appear in ``requirements.txt`` unless the plugin project +wants OSC and all of its dependencies installed with it. This is +specifically not a good idea for plugins that are also libraries +installed with OpenStack services. + .. code-block:: ini python-openstackclient>=X.Y.Z # Apache-2.0 diff --git a/openstackclient/common/client_config.py b/openstackclient/common/client_config.py index ad86e9f..a22dd0c 100644 --- a/openstackclient/common/client_config.py +++ b/openstackclient/common/client_config.py @@ -13,53 +13,13 @@ """OpenStackConfig subclass for argument compatibility""" -import logging - -from os_client_config import exceptions as occ_exceptions from osc_lib.cli import client_config -LOG = logging.getLogger(__name__) - - # Sublcass OpenStackConfig in order to munge config values # before auth plugins are loaded class OSC_Config(client_config.OSC_Config): - # TODO(dtroyer): Once os-client-config with pw_func argument is in - # global-requirements we can remove __init()__ - def __init__( - self, - config_files=None, - vendor_files=None, - override_defaults=None, - force_ipv4=None, - envvar_prefix=None, - secure_files=None, - pw_func=None, - ): - ret = super(OSC_Config, self).__init__( - config_files=config_files, - vendor_files=vendor_files, - override_defaults=override_defaults, - force_ipv4=force_ipv4, - envvar_prefix=envvar_prefix, - secure_files=secure_files, - ) - - # NOTE(dtroyer): This will be pushed down into os-client-config - # The default is there is no callback, the calling - # application must specify what to use, typically - # it will be osc_lib.shell.prompt_for_password() - if '_pw_callback' not in vars(self): - # Set the default if it doesn't already exist - self._pw_callback = None - if pw_func is not None: - # Set the passed in value - self._pw_callback = pw_func - - return ret - # TODO(dtroyer): Remove _auth_default_domain when the v3otp fix is # backported to osc-lib, should be in release 1.3.0 def _auth_default_domain(self, config): @@ -109,70 +69,3 @@ class OSC_Config(client_config.OSC_Config): config = self._validate_auth(config, loader) auth_plugin = loader.load_from_options(**config['auth']) return auth_plugin - - # TODO(dtroyer): Remove _validate_auth_ksc when it is in osc-lib 1.3.0 - def _validate_auth_ksc(self, config, cloud, fixed_argparse=None): - """Old compatibility hack for OSC, no longer needed/wanted""" - return config - - # TODO(dtroyer): Remove _validate_auth when it is in osc-lib 1.3.0 - def _validate_auth(self, config, loader, fixed_argparse=None): - """Validate auth plugin arguments""" - # May throw a keystoneauth1.exceptions.NoMatchingPlugin - - plugin_options = loader.get_options() - - msgs = [] - prompt_options = [] - for p_opt in plugin_options: - # if it's in config, win, move it and kill it from config dict - # if it's in config.auth but not in config we're good - # deprecated loses to current - # provided beats default, deprecated or not - winning_value = self._find_winning_auth_value(p_opt, config) - if not winning_value: - winning_value = self._find_winning_auth_value( - p_opt, config['auth']) - - # if the plugin tells us that this value is required - # then error if it's doesn't exist now - if not winning_value and p_opt.required: - msgs.append( - 'Missing value {auth_key}' - ' required for auth plugin {plugin}'.format( - auth_key=p_opt.name, plugin=config.get('auth_type'), - ) - ) - - # Clean up after ourselves - for opt in [p_opt.name] + [o.name for o in p_opt.deprecated]: - opt = opt.replace('-', '_') - config.pop(opt, None) - config['auth'].pop(opt, None) - - if winning_value: - # Prefer the plugin configuration dest value if the value's key - # is marked as depreciated. - if p_opt.dest is None: - config['auth'][p_opt.name.replace('-', '_')] = ( - winning_value) - else: - config['auth'][p_opt.dest] = winning_value - - # See if this needs a prompting - if ( - 'prompt' in vars(p_opt) and - p_opt.prompt is not None and - p_opt.dest not in config['auth'] and - self._pw_callback is not None - ): - # Defer these until we know all required opts are present - prompt_options.append(p_opt) - - if msgs: - raise occ_exceptions.OpenStackConfigException('\n'.join(msgs)) - else: - for p_opt in prompt_options: - config['auth'][p_opt.dest] = self._pw_callback(p_opt.prompt) - - return config diff --git a/openstackclient/common/module.py b/openstackclient/common/module.py index 15719a3..20497f2 100644 --- a/openstackclient/common/module.py +++ b/openstackclient/common/module.py @@ -29,12 +29,26 @@ class ListCommand(command.Lister): auth_required = False + def get_parser(self, prog_name): + parser = super(ListCommand, self).get_parser(prog_name) + parser.add_argument( + '--group', + metavar='<group-keyword>', + help=_('Show commands filtered by a command group, for example: ' + 'identity, volume, compute, image, network and ' + 'other keywords'), + ) + return parser + def take_action(self, parsed_args): cm = self.app.command_manager groups = cm.get_command_groups() groups = sorted(groups) columns = ('Command Group', 'Commands') + if parsed_args.group: + groups = (group for group in groups if parsed_args.group in group) + commands = [] for group in groups: command_names = cm.get_command_names(group) @@ -74,15 +88,29 @@ class ListModule(command.ShowOne): mods = sys.modules for k in mods.keys(): k = k.split('.')[0] - # TODO(dtroyer): Need a better way to decide which modules to - # show for the default (not --all) invocation. - # It should be just the things we actually care - # about like client and plugin modules... - if (parsed_args.all or 'client' in k): - try: - data[k] = mods[k].__version__ - except AttributeError: - # aw, just skip it - pass + # Skip private modules and the modules that had been added, + # like: keystoneclient, keystoneclient.exceptions and + # keystoneclient.auth + if not k.startswith('_') and k not in data: + # TODO(dtroyer): Need a better way to decide which modules to + # show for the default (not --all) invocation. + # It should be just the things we actually care + # about like client and plugin modules... + if (parsed_args.all or + # Handle xxxclient and openstacksdk + (k.endswith('client') or k == 'openstack')): + try: + # NOTE(RuiChen): openstacksdk bug/1588823 exist, + # no good way to add __version__ for + # openstack module properly, hard code + # looks bad, but openstacksdk module + # information is important. + if k == 'openstack': + data[k] = mods[k].version.__version__ + else: + data[k] = mods[k].__version__ + except Exception: + # Catch all exceptions, just skip it + pass return zip(*sorted(six.iteritems(data))) diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index d86aec5..82fbf2b 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -18,8 +18,6 @@ import itertools import sys -from openstack import exceptions as sdk_exceptions -from openstack.network.v2 import quota as _quota from osc_lib.command import command from osc_lib import utils import six @@ -167,7 +165,7 @@ class SetQuota(command.Command): **volume_kwargs) if network_kwargs: sys.stderr.write("Network quotas are ignored since quota class" - "is not supported.") + " is not supported.") else: project = utils.find_resource( identity_client.projects, @@ -182,36 +180,9 @@ class SetQuota(command.Command): project, **volume_kwargs) if network_kwargs: - if hasattr(_quota.Quota, 'allow_get'): - # TODO(huanxuan): Remove this block once the fixed - # SDK Quota class is the minimum required version. - # This is expected to be SDK release 0.9.13 - res = network_client._get_resource( - _quota.Quota, project, **network_kwargs) - if any([res._body.dirty, res._header.dirty]): - request = res._prepare_request(prepend_key=True) - # remove the id in the body - if 'id' in request.body[res.resource_key]: - del request.body[res.resource_key]['id'] - if res.patch_update: - response = network_client.session.patch( - request.uri, - endpoint_filter=res.service, - json=request.body, - headers=request.headers - ) - else: - response = network_client.session.put( - request.uri, - endpoint_filter=res.service, - json=request.body, - headers=request.headers - ) - res._translate_response(response, has_body=True) - else: - network_client.update_quota( - project, - **network_kwargs) + network_client.update_quota( + project, + **network_kwargs) class ShowQuota(command.ShowOne): @@ -280,39 +251,7 @@ class ShowQuota(command.ShowOne): project = self._get_project(parsed_args) client = self.app.client_manager.network if parsed_args.default: - # TODO(dtroyer): Remove the top of this if block once the - # fixed SDK QuotaDefault class is the minimum - # required version. This is expected to be - # SDK release 0.9.13 - if hasattr(_quota.QuotaDefault, 'project'): - # hack 0.9.11+ - quotadef_obj = client._get_resource( - _quota.QuotaDefault, - project, - ) - quotadef_obj.base_path = quotadef_obj.base_path % { - 'project': project, - } - try: - network_quota = quotadef_obj.get( - client.session, - requires_id=False, - ) - except sdk_exceptions.NotFoundException as e: - raise sdk_exceptions.ResourceNotFound( - message="No %s found for %s" % - (_quota.QuotaDefault.__name__, project), - details=e.details, - response=e.response, - request_id=e.request_id, - url=e.url, - method=e.method, - http_status=e.http_status, - cause=e.cause, - ) - # end hack-around - else: - network_quota = client.get_quota_default(project) + network_quota = client.get_quota_default(project) else: network_quota = client.get_quota(project) return network_quota diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py index 76ba5cc..7f9161a 100644 --- a/openstackclient/compute/v2/aggregate.py +++ b/openstackclient/compute/v2/aggregate.py @@ -128,8 +128,8 @@ class DeleteAggregate(command.Command): except Exception as e: result += 1 LOG.error(_("Failed to delete aggregate with name or " - "ID '%(aggregate)s': %(e)s") - % {'aggregate': a, 'e': e}) + "ID '%(aggregate)s': %(e)s"), + {'aggregate': a, 'e': e}) if result > 0: total = len(parsed_args.aggregate) diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py index 358df50..25f9210 100644 --- a/openstackclient/compute/v2/console.py +++ b/openstackclient/compute/v2/console.py @@ -126,18 +126,7 @@ class ShowConsoleURL(command.ShowOne): parsed_args.server, ) - data = None - if parsed_args.url_type in ['novnc', 'xvpvnc']: - data = server.get_vnc_console(parsed_args.url_type) - if parsed_args.url_type in ['spice-html5']: - data = server.get_spice_console(parsed_args.url_type) - if parsed_args.url_type in ['rdp-html5']: - data = server.get_rdp_console(parsed_args.url_type) - if parsed_args.url_type in ['serial']: - data = server.get_serial_console(parsed_args.url_type) - if parsed_args.url_type in ['webmks']: - data = server.get_mks_console() - + data = server.get_console_url(parsed_args.url_type) if not data: return ({}, {}) diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 7cd22ed..009c9bd 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -171,7 +171,7 @@ class CreateFlavor(command.ShowOne): except Exception as e: msg = _("Failed to add project %(project)s access to " "flavor: %(e)s") - LOG.error(msg % {'project': parsed_args.project, 'e': e}) + LOG.error(msg, {'project': parsed_args.project, 'e': e}) if parsed_args.property: try: flavor.set_keys(parsed_args.property) @@ -208,8 +208,7 @@ class DeleteFlavor(command.Command): except Exception as e: result += 1 LOG.error(_("Failed to delete flavor with name or " - "ID '%(flavor)s': %(e)s") - % {'flavor': f, 'e': e}) + "ID '%(flavor)s': %(e)s"), {'flavor': f, 'e': e}) if result > 0: total = len(parsed_args.flavor) @@ -313,6 +312,14 @@ class SetFlavor(command.Command): help=_("Flavor to modify (name or ID)") ) parser.add_argument( + "--no-property", + action="store_true", + help=_("Remove all properties from this flavor " + "(specify both --no-property and --property" + " to remove the current properties before setting" + " new properties.)"), + ) + parser.add_argument( "--property", metavar="<key=value>", action=parseractions.KeyValueAction, @@ -336,6 +343,15 @@ class SetFlavor(command.Command): flavor = _find_flavor(compute_client, parsed_args.flavor) result = 0 + key_list = [] + if parsed_args.no_property: + try: + for key in flavor.get_keys().keys(): + key_list.append(key) + flavor.unset_keys(key_list) + except Exception as e: + LOG.error(_("Failed to clear flavor property: %s"), e) + result += 1 if parsed_args.property: try: flavor.set_keys(parsed_args.property) @@ -395,7 +411,7 @@ class ShowFlavor(command.ShowOne): except Exception as e: msg = _("Failed to get access projects list " "for flavor '%(flavor)s': %(e)s") - LOG.error(msg % {'flavor': parsed_args.flavor, 'e': e}) + LOG.error(msg, {'flavor': parsed_args.flavor, 'e': e}) flavor = resource_flavor._info.copy() flavor.update({ diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index a63cbfe..2a8524d 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -106,8 +106,7 @@ class DeleteKeypair(command.Command): except Exception as e: result += 1 LOG.error(_("Failed to delete key with name " - "'%(name)s': %(e)s") - % {'name': n, 'e': e}) + "'%(name)s': %(e)s"), {'name': n, 'e': e}) if result > 0: total = len(parsed_args.name) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index f9f0df4..d33c631 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -402,7 +402,7 @@ class CreateServer(command.ShowOne): parser.add_argument( '--nic', metavar="<net-id=net-uuid,v4-fixed-ip=ip-addr,v6-fixed-ip=ip-addr," - "port-id=port-uuid>", + "port-id=port-uuid,auto,none>", action='append', default=[], help=_("Create a NIC on the server. " @@ -414,7 +414,8 @@ class CreateServer(command.ShowOne): "v6-fixed-ip: IPv6 fixed address for NIC (optional), " "none: (v2.37+) no network is attached, " "auto: (v2.37+) the compute service will automatically " - "allocate a network."), + "allocate a network. Specifying a --nic of auto or none " + "cannot be used with any other --nic value."), ) parser.add_argument( '--hint', @@ -547,14 +548,21 @@ class CreateServer(command.ShowOne): block_device_mapping_v2.append(mapping) nics = [] - if parsed_args.nic in ('auto', 'none'): - nics = [parsed_args.nic] - else: - for nic_str in parsed_args.nic: + auto_or_none = False + for nic_str in parsed_args.nic: + # Handle the special auto/none cases + if nic_str in ('auto', 'none'): + auto_or_none = True + nics.append(nic_str) + else: nic_info = {"net-id": "", "v4-fixed-ip": "", "v6-fixed-ip": "", "port-id": ""} - nic_info.update(dict(kv_str.split("=", 1) - for kv_str in nic_str.split(","))) + try: + nic_info.update(dict(kv_str.split("=", 1) + for kv_str in nic_str.split(","))) + except ValueError: + msg = _('Invalid --nic argument %s.') % nic_str + raise exceptions.CommandError(msg) if bool(nic_info["net-id"]) == bool(nic_info["port-id"]): msg = _("either net-id or port-id should be specified " "but not both") @@ -581,6 +589,18 @@ class CreateServer(command.ShowOne): raise exceptions.CommandError(msg) nics.append(nic_info) + if nics: + if auto_or_none: + if len(nics) > 1: + msg = _('Specifying a --nic of auto or none cannot ' + 'be used with any other --nic value.') + raise exceptions.CommandError(msg) + nics = nics[0] + else: + # Default to empty list if nothing was specified, let nova side to + # decide the default behavior. + nics = [] + hints = {} for hint in parsed_args.hint: key, _sep, value = hint.partition('=') diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py index 6bcfc6a..c6e2161 100644 --- a/openstackclient/compute/v2/server_group.py +++ b/openstackclient/compute/v2/server_group.py @@ -55,10 +55,11 @@ class CreateServerGroup(command.ShowOne): parser.add_argument( '--policy', metavar='<policy>', - action='append', - required=True, + choices=['affinity', 'anti-affinity'], + default='affinity', help=_("Add a policy to <name> " - "(repeat option to add multiple policies)") + "('affinity' or 'anti-affinity', " + "default to 'affinity')") ) return parser @@ -67,7 +68,7 @@ class CreateServerGroup(command.ShowOne): info = {} server_group = compute_client.server_groups.create( name=parsed_args.name, - policies=parsed_args.policy) + policies=[parsed_args.policy]) info.update(server_group._info) columns = _get_columns(info) diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 9c384f0..7b18c2e 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -50,8 +50,7 @@ class DeleteService(command.Command): except Exception as e: result += 1 LOG.error(_("Failed to delete compute service with " - "ID '%(service)s': %(e)s") - % {'service': s, 'e': e}) + "ID '%(service)s': %(e)s"), {'service': s, 'e': e}) if result > 0: total = len(parsed_args.service) diff --git a/openstackclient/identity/v2_0/ec2creds.py b/openstackclient/identity/v2_0/ec2creds.py index 2572c4f..0bc4832 100644 --- a/openstackclient/identity/v2_0/ec2creds.py +++ b/openstackclient/identity/v2_0/ec2creds.py @@ -122,8 +122,8 @@ class DeleteEC2Creds(command.Command): except Exception as e: result += 1 LOG.error(_("Failed to delete EC2 credentials with " - "access key '%(access_key)s': %(e)s") - % {'access_key': access_key, 'e': e}) + "access key '%(access_key)s': %(e)s"), + {'access_key': access_key, 'e': e}) if result > 0: total = len(parsed_args.access_keys) diff --git a/openstackclient/identity/v2_0/endpoint.py b/openstackclient/identity/v2_0/endpoint.py index 7e0751a..1628e48 100644 --- a/openstackclient/identity/v2_0/endpoint.py +++ b/openstackclient/identity/v2_0/endpoint.py @@ -102,8 +102,8 @@ class DeleteEndpoint(command.Command): except Exception as e: result += 1 LOG.error(_("Failed to delete endpoint with " - "ID '%(endpoint)s': %(e)s") - % {'endpoint': endpoint, 'e': e}) + "ID '%(endpoint)s': %(e)s"), + {'endpoint': endpoint, 'e': e}) if result > 0: total = len(parsed_args.endpoints) diff --git a/openstackclient/identity/v2_0/service.py b/openstackclient/identity/v2_0/service.py index f70f0fa..80f2d72 100644 --- a/openstackclient/identity/v2_0/service.py +++ b/openstackclient/identity/v2_0/service.py @@ -114,8 +114,8 @@ class DeleteService(command.Command): except Exception as e: result += 1 LOG.error(_("Failed to delete service with " - "name or ID '%(service)s': %(e)s") - % {'service': service, 'e': e}) + "name or ID '%(service)s': %(e)s"), + {'service': service, 'e': e}) if result > 0: total = len(parsed_args.services) diff --git a/openstackclient/identity/v3/consumer.py b/openstackclient/identity/v3/consumer.py index bcb29db..6dd24dc 100644 --- a/openstackclient/identity/v3/consumer.py +++ b/openstackclient/identity/v3/consumer.py @@ -73,8 +73,7 @@ class DeleteConsumer(command.Command): except Exception as e: result += 1 LOG.error(_("Failed to delete consumer with name or " - "ID '%(consumer)s': %(e)s") - % {'consumer': i, 'e': e}) + "ID '%(consumer)s': %(e)s"), {'consumer': i, 'e': e}) if result > 0: total = len(parsed_args.consumer) diff --git a/openstackclient/identity/v3/credential.py b/openstackclient/identity/v3/credential.py index 68287f2..2fd0062 100644 --- a/openstackclient/identity/v3/credential.py +++ b/openstackclient/identity/v3/credential.py @@ -99,8 +99,8 @@ class DeleteCredential(command.Command): except Exception as e: result += 1 LOG.error(_("Failed to delete credentials with " - "ID '%(credential)s': %(e)s") - % {'credential': i, 'e': e}) + "ID '%(credential)s': %(e)s"), + {'credential': i, 'e': e}) if result > 0: total = len(parsed_args.credential) diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py index 59ab0f0..064624a 100644 --- a/openstackclient/identity/v3/domain.py +++ b/openstackclient/identity/v3/domain.py @@ -111,8 +111,7 @@ class DeleteDomain(command.Command): except Exception as e: result += 1 LOG.error(_("Failed to delete domain with name or " - "ID '%(domain)s': %(e)s") - % {'domain': i, 'e': e}) + "ID '%(domain)s': %(e)s"), {'domain': i, 'e': e}) if result > 0: total = len(parsed_args.domain) diff --git a/openstackclient/identity/v3/ec2creds.py b/openstackclient/identity/v3/ec2creds.py index 9854efd..44e9a2c 100644 --- a/openstackclient/identity/v3/ec2creds.py +++ b/openstackclient/identity/v3/ec2creds.py @@ -140,8 +140,8 @@ class DeleteEC2Creds(command.Command): except Exception as e: result += 1 LOG.error(_("Failed to delete EC2 credentials with " - "access key '%(access_key)s': %(e)s") - % {'access_key': i, 'e': e}) + "access key '%(access_key)s': %(e)s"), + {'access_key': i, 'e': e}) if result > 0: total = len(parsed_args.access_key) diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index 39fd49e..15760a1 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -122,8 +122,7 @@ class DeleteEndpoint(command.Command): except Exception as e: result += 1 LOG.error(_("Failed to delete endpoint with " - "ID '%(endpoint)s': %(e)s") - % {'endpoint': i, 'e': e}) + "ID '%(endpoint)s': %(e)s"), {'endpoint': i, 'e': e}) if result > 0: total = len(parsed_args.endpoint) @@ -140,7 +139,7 @@ class ListEndpoint(command.Lister): parser.add_argument( '--service', metavar='<service>', - help=_('Filter by service (name or ID)'), + help=_('Filter by service (type, name or ID)'), ) parser.add_argument( '--interface', diff --git a/openstackclient/identity/v3/federation_protocol.py b/openstackclient/identity/v3/federation_protocol.py index 0752e8f..6429d93 100644 --- a/openstackclient/identity/v3/federation_protocol.py +++ b/openstackclient/identity/v3/federation_protocol.py @@ -102,8 +102,8 @@ class DeleteProtocol(command.Command): except Exception as e: result += 1 LOG.error(_("Failed to delete federation protocol " - "with name or ID '%(protocol)s': %(e)s") - % {'protocol': i, 'e': e}) + "with name or ID '%(protocol)s': %(e)s"), + {'protocol': i, 'e': e}) if result > 0: total = len(parsed_args.federation_protocol) diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index a03a86e..2afdabc 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -62,18 +62,13 @@ class AddUserToGroup(command.Command): try: identity_client.users.add_to_group(user_id, group_id) - except Exception: - msg = _("%(user)s not added to group %(group)s\n") % { - 'user': parsed_args.user, - 'group': parsed_args.group, - } - sys.stderr.write(msg) - else: - msg = _("%(user)s added to group %(group)s\n") % { + except Exception as e: + msg = _("%(user)s not added to group %(group)s: %(e)s") % { 'user': parsed_args.user, 'group': parsed_args.group, + 'e': e, } - sys.stdout.write(msg) + raise exceptions.CommandError(msg) class CheckUserInGroup(command.Command): @@ -306,18 +301,13 @@ class RemoveUserFromGroup(command.Command): try: identity_client.users.remove_from_group(user_id, group_id) - except Exception: - msg = _("%(user)s not removed from group %(group)s\n") % { - 'user': parsed_args.user, - 'group': parsed_args.group, - } - sys.stderr.write(msg) - else: - msg = _("%(user)s removed from group %(group)s\n") % { + except Exception as e: + msg = _("%(user)s not removed from group %(group)s: %(e)s") % { 'user': parsed_args.user, 'group': parsed_args.group, + 'e': e, } - sys.stdout.write(msg) + raise exceptions.CommandError(msg) class SetGroup(command.Command): diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py index 163dcb5..e8b3a2f 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -115,8 +115,8 @@ class DeleteIdentityProvider(command.Command): except Exception as e: result += 1 LOG.error(_("Failed to delete identity providers with " - "name or ID '%(provider)s': %(e)s") - % {'provider': i, 'e': e}) + "name or ID '%(provider)s': %(e)s"), + {'provider': i, 'e': e}) if result > 0: total = len(parsed_args.identity_provider) diff --git a/openstackclient/identity/v3/mapping.py b/openstackclient/identity/v3/mapping.py index 28080f8..e729c41 100644 --- a/openstackclient/identity/v3/mapping.py +++ b/openstackclient/identity/v3/mapping.py @@ -132,8 +132,7 @@ class DeleteMapping(command.Command): except Exception as e: result += 1 LOG.error(_("Failed to delete mapping with name or " - "ID '%(mapping)s': %(e)s") - % {'mapping': i, 'e': e}) + "ID '%(mapping)s': %(e)s"), {'mapping': i, 'e': e}) if result > 0: total = len(parsed_args.mapping) diff --git a/openstackclient/identity/v3/policy.py b/openstackclient/identity/v3/policy.py index c511652..3b64419 100644 --- a/openstackclient/identity/v3/policy.py +++ b/openstackclient/identity/v3/policy.py @@ -82,8 +82,7 @@ class DeletePolicy(command.Command): except Exception as e: result += 1 LOG.error(_("Failed to delete policy with name or " - "ID '%(policy)s': %(e)s") - % {'policy': i, 'e': e}) + "ID '%(policy)s': %(e)s"), {'policy': i, 'e': e}) if result > 0: total = len(parsed_args.policy) diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 12197cd..43eca2b 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -336,13 +336,18 @@ class ShowProject(command.ShowOne): project = utils.find_resource( identity_client.projects, project_str, - domain_id=domain.id, - parents_as_list=parsed_args.parents, - subtree_as_list=parsed_args.children) + domain_id=domain.id) else: project = utils.find_resource( identity_client.projects, - project_str, + project_str) + + if parsed_args.parents or parsed_args.children: + # NOTE(RuiChen): utils.find_resource() can't pass kwargs, + # if id query hit the result at first, so call + # identity manager.get() with kwargs directly. + project = identity_client.projects.get( + project.id, parents_as_list=parsed_args.parents, subtree_as_list=parsed_args.children) diff --git a/openstackclient/identity/v3/region.py b/openstackclient/identity/v3/region.py index d3e712e..69c8b50 100644 --- a/openstackclient/identity/v3/region.py +++ b/openstackclient/identity/v3/region.py @@ -87,8 +87,7 @@ class DeleteRegion(command.Command): except Exception as e: result += 1 LOG.error(_("Failed to delete region with " - "ID '%(region)s': %(e)s") - % {'region': i, 'e': e}) + "ID '%(region)s': %(e)s"), {'region': i, 'e': e}) if result > 0: total = len(parsed_args.region) diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 994ecc9..1bbf5f0 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -16,7 +16,6 @@ """Identity v3 Role action implementations""" import logging -import sys from keystoneauth1 import exceptions as ks_exc from osc_lib.command import command @@ -129,7 +128,9 @@ class AddRole(command.Command): if (not parsed_args.user and not parsed_args.domain and not parsed_args.group and not parsed_args.project): - return + msg = _("Role not added, incorrect set of arguments " + "provided. See openstack --help for more details") + raise exceptions.CommandError(msg) domain_id = None if parsed_args.role_domain: @@ -143,11 +144,6 @@ class AddRole(command.Command): kwargs = _process_identity_and_resource_options( parsed_args, self.app.client_manager.identity) - if not kwargs: - sys.stderr.write(_("Role not added, incorrect set of arguments " - "provided. See openstack --help for more " - "details\n")) - return identity_client.roles.grant(role.id, **kwargs) @@ -372,10 +368,10 @@ class ListRole(command.Lister): '<group-name> --project <project-name> --names ' 'instead.')) else: - sys.stderr.write(_("Error: If a user or group is specified, " - "either --domain or --project must also be " - "specified to list role grants.\n")) - return ([], []) + msg = _("Error: If a user or group is specified, " + "either --domain or --project must also be " + "specified to list role grants.") + raise exceptions.CommandError(msg) return (columns, (utils.get_item_properties( @@ -405,9 +401,9 @@ class RemoveRole(command.Command): if (not parsed_args.user and not parsed_args.domain and not parsed_args.group and not parsed_args.project): - sys.stderr.write(_("Incorrect set of arguments provided. " - "See openstack --help for more details\n")) - return + msg = _("Incorrect set of arguments provided. " + "See openstack --help for more details") + raise exceptions.CommandError(msg) domain_id = None if parsed_args.role_domain: @@ -421,11 +417,6 @@ class RemoveRole(command.Command): kwargs = _process_identity_and_resource_options( parsed_args, self.app.client_manager.identity) - if not kwargs: - sys.stderr.write(_("Role not removed, incorrect set of arguments " - "provided. See openstack --help for more " - "details\n")) - return identity_client.roles.revoke(role.id, **kwargs) diff --git a/openstackclient/identity/v3/service.py b/openstackclient/identity/v3/service.py index 7daf891..ac8d8d9 100644 --- a/openstackclient/identity/v3/service.py +++ b/openstackclient/identity/v3/service.py @@ -103,8 +103,8 @@ class DeleteService(command.Command): except Exception as e: result += 1 LOG.error(_("Failed to delete consumer with type, " - "name or ID '%(service)s': %(e)s") - % {'service': i, 'e': e}) + "name or ID '%(service)s': %(e)s"), + {'service': i, 'e': e}) if result > 0: total = len(parsed_args.service) diff --git a/openstackclient/identity/v3/service_provider.py b/openstackclient/identity/v3/service_provider.py index 459dc00..bb2d991 100644 --- a/openstackclient/identity/v3/service_provider.py +++ b/openstackclient/identity/v3/service_provider.py @@ -108,8 +108,8 @@ class DeleteServiceProvider(command.Command): except Exception as e: result += 1 LOG.error(_("Failed to delete service provider with " - "name or ID '%(provider)s': %(e)s") - % {'provider': i, 'e': e}) + "name or ID '%(provider)s': %(e)s"), + {'provider': i, 'e': e}) if result > 0: total = len(parsed_args.service_provider) diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index f4944af..60d6170 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -128,12 +128,13 @@ class CreateImage(command.ShowOne): metavar="<image-url>", help=_("Copy image from the data store (similar to --location)"), ) - parser.add_argument( + source_group = parser.add_mutually_exclusive_group() + source_group.add_argument( "--file", metavar="<file>", help=_("Upload image from local file"), ) - parser.add_argument( + source_group.add_argument( "--volume", metavar="<volume>", help=_("Create image from a volume"), diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 29499ec..c4be69f 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -164,12 +164,13 @@ class CreateImage(command.ShowOne): type=int, help=_("Minimum RAM size needed to boot image, in megabytes"), ) - parser.add_argument( + source_group = parser.add_mutually_exclusive_group() + source_group.add_argument( "--file", metavar="<file>", help=_("Upload image from local file"), ) - parser.add_argument( + source_group.add_argument( "--volume", metavar="<volume>", help=_("Create image from a volume"), diff --git a/openstackclient/network/client.py b/openstackclient/network/client.py index c562058..9525b94 100644 --- a/openstackclient/network/client.py +++ b/openstackclient/network/client.py @@ -44,6 +44,11 @@ def make_client(instance): LOG.debug('Connection: %s', conn) LOG.debug('Network client initialized using OpenStack SDK: %s', conn.network) + + # NOTE(dtroyer): Horrible ugly hack since we don't actually save + # the connection anywhere yet, so stash it in the + # instance directly from here for other uses + instance.sdk_connection = conn return conn.network diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index 980c41c..41b208a 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -241,7 +241,7 @@ class DeleteFloatingIP(common.NetworkAndComputeDelete): def take_action_network(self, client, parsed_args): (obj, self.ip_cache) = _find_floating_ip( - client.session, + self.app.client_manager.sdk_connection.session, self.ip_cache, self.r, ignore_missing=False, @@ -472,7 +472,7 @@ class ShowFloatingIP(common.NetworkAndComputeShowOne): def take_action_network(self, client, parsed_args): (obj, self.ip_cache) = _find_floating_ip( - client.session, + self.app.client_manager.sdk_connection.session, [], parsed_args.floating_ip, ignore_missing=False, diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 655147d..3aa8ec8 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -111,10 +111,10 @@ def _get_attrs(client_manager, parsed_args): attrs['router:external'] = False if parsed_args.external: attrs['router:external'] = True - if parsed_args.no_default: - attrs['is_default'] = False - if parsed_args.default: - attrs['is_default'] = True + if parsed_args.no_default: + attrs['is_default'] = False + if parsed_args.default: + attrs['is_default'] = True # Update Provider network options if parsed_args.provider_network_type: attrs['provider:network_type'] = parsed_args.provider_network_type diff --git a/openstackclient/network/v2/network_auto_allocated_topology.py b/openstackclient/network/v2/network_auto_allocated_topology.py new file mode 100644 index 0000000..36f3920 --- /dev/null +++ b/openstackclient/network/v2/network_auto_allocated_topology.py @@ -0,0 +1,136 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +"""Auto-allocated Topology Implementations""" + +import logging + +from osc_lib.command import command +from osc_lib import utils + +from openstackclient.i18n import _ +from openstackclient.identity import common as identity_common +from openstackclient.network import sdk_utils + +LOG = logging.getLogger(__name__) + + +def _get_columns(item): + column_map = { + 'tenant_id': 'project_id', + } + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + + +def _format_check_resource_columns(): + return ('dry_run',) + + +def _format_check_resource(item): + item_id = getattr(item, 'id', False) + if item_id == 'dry-run=pass': + item.check_resource = 'pass' + return item + + +def _get_attrs(client_manager, parsed_args): + attrs = {} + if parsed_args.project: + identity_client = client_manager.identity + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + attrs['tenant_id'] = project_id + if parsed_args.check_resources: + attrs['check_resources'] = True + + return attrs + + +# TODO(ankur-gupta-f): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. +class CreateAutoAllocatedTopology(command.ShowOne): + _description = _("Create the auto allocated topology for project") + + def get_parser(self, prog_name): + parser = super(CreateAutoAllocatedTopology, self).get_parser(prog_name) + parser.add_argument( + '--project', + metavar='<project>', + help=_("Return the auto allocated topology for a given project. " + "Default is current project") + ) + identity_common.add_project_domain_option_to_parser(parser) + parser.add_argument( + '--check-resources', + action='store_true', + help=_("Validate the requirements for auto allocated topology. " + "Does not return a topology.") + ) + parser.add_argument( + '--or-show', + action='store_true', + default=True, + help=_("If topology exists returns the topology's " + "information (Default)") + ) + + return parser + + def check_resource_topology(self, client, parsed_args): + obj = client.validate_auto_allocated_topology(parsed_args.project) + + columns = _format_check_resource_columns() + data = utils.get_item_properties(_format_check_resource(obj), + columns, + formatters={}) + + return (columns, data) + + def get_topology(self, client, parsed_args): + obj = client.get_auto_allocated_topology(parsed_args.project) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters={}) + return (display_columns, data) + + def take_action(self, parsed_args): + client = self.app.client_manager.network + if parsed_args.check_resources: + columns, data = self.check_resource_topology(client, parsed_args) + else: + columns, data = self.get_topology(client, parsed_args) + return (columns, data) + + +# TODO(ankur-gupta-f): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. +class DeleteAutoAllocatedTopology(command.Command): + _description = _("Delete auto allocated topology for project") + + def get_parser(self, prog_name): + parser = super(DeleteAutoAllocatedTopology, self).get_parser(prog_name) + parser.add_argument( + '--project', + metavar='<project>', + help=_('Delete auto allocated topology for a given project. ' + 'Default is the current project') + ) + identity_common.add_project_domain_option_to_parser(parser) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + client.delete_auto_allocated_topology(parsed_args.project) diff --git a/openstackclient/network/v2/network_flavor.py b/openstackclient/network/v2/network_flavor.py new file mode 100644 index 0000000..3a3324c --- /dev/null +++ b/openstackclient/network/v2/network_flavor.py @@ -0,0 +1,247 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +"""Flavor 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.identity import common as identity_common +from openstackclient.network import sdk_utils + + +LOG = logging.getLogger(__name__) + + +def _get_columns(item): + column_map = { + 'is_enabled': 'enabled', + 'tenant_id': 'project_id', + } + + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + + +def _get_attrs(client_manager, parsed_args): + attrs = {} + attrs['name'] = parsed_args.name + attrs['service_type'] = parsed_args.service_type + if parsed_args.description is not None: + attrs['description'] = parsed_args.description + if parsed_args.enable: + attrs['enabled'] = True + if parsed_args.disable: + attrs['enabled'] = False + if 'project' in parsed_args and parsed_args.project is not None: + identity_client = client_manager.identity + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + attrs['tenant_id'] = project_id + + return attrs + + +# TODO(dasanind): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. +class CreateNetworkFlavor(command.ShowOne): + _description = _("Create new network flavor") + + def get_parser(self, prog_name): + parser = super(CreateNetworkFlavor, self).get_parser(prog_name) + parser.add_argument( + 'name', + metavar="<name>", + help=_("Name for the flavor") + ) + parser.add_argument( + '--service-type', + metavar="<service-type>", + required=True, + help=_('Service type to which the flavor applies to: e.g. VPN ' + '(See openstack network service provider list for loaded ' + 'examples.)') + ) + parser.add_argument( + '--description', + help=_('Description for the flavor') + ) + parser.add_argument( + '--project', + metavar="<project>", + help=_("Owner's project (name or ID)") + ) + identity_common.add_project_domain_option_to_parser(parser) + + enable_group = parser.add_mutually_exclusive_group() + enable_group.add_argument( + '--enable', + action='store_true', + help=_("Enable the flavor (default)") + ) + enable_group.add_argument( + '--disable', + action='store_true', + help=_("Disable the flavor") + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + attrs = _get_attrs(self.app.client_manager, parsed_args) + obj = client.create_flavor(**attrs) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters={}) + + return (display_columns, data) + + +class DeleteNetworkFlavor(command.Command): + _description = _("Delete network flavors") + + def get_parser(self, prog_name): + parser = super(DeleteNetworkFlavor, self).get_parser(prog_name) + + parser.add_argument( + 'flavor', + metavar='<flavor>', + nargs='+', + help=_('Flavor(s) to delete (name or ID)') + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + result = 0 + + for flavor in parsed_args.flavor: + try: + obj = client.find_flavor(flavor, ignore_missing=False) + client.delete_flavor(obj) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete flavor with " + "name or ID '%(flavor)s': %(e)s"), + {"flavor": flavor, "e": e}) + if result > 0: + total = len(parsed_args.flavor) + msg = (_("%(result)s of %(total)s flavors failed " + "to delete.") % {"result": result, "total": total}) + raise exceptions.CommandError(msg) + + +class ListNetworkFlavor(command.Lister): + _description = _("List network flavors") + + def take_action(self, parsed_args): + client = self.app.client_manager.network + + columns = ( + 'id', + 'name', + 'is_enabled', + 'service_type', + 'description' + ) + column_headers = ( + 'ID', + 'Name', + 'Enabled', + 'Service Type', + 'Description' + ) + + data = client.flavors() + return (column_headers, + (utils.get_item_properties( + s, columns, + ) for s in data)) + + +# TODO(dasanind): Use only the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. +class SetNetworkFlavor(command.Command): + _description = _("Set network flavor properties") + + def get_parser(self, prog_name): + parser = super(SetNetworkFlavor, self).get_parser(prog_name) + parser.add_argument( + 'flavor', + metavar="<flavor>", + help=_("Flavor to update (name or ID)") + ) + parser.add_argument( + '--description', + help=_('Set network flavor description') + ) + enable_group = parser.add_mutually_exclusive_group() + enable_group.add_argument( + '--disable', + action='store_true', + help=_("Disable network flavor") + ) + enable_group.add_argument( + '--enable', + action='store_true', + help=_("Enable network flavor") + ) + parser.add_argument( + '--name', + metavar="<name>", + help=_('Set flavor name') + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_flavor( + parsed_args.flavor, + ignore_missing=False) + attrs = {} + if parsed_args.name is not None: + attrs['name'] = parsed_args.name + if parsed_args.description is not None: + attrs['description'] = parsed_args.description + if parsed_args.enable: + attrs['enabled'] = True + if parsed_args.disable: + attrs['enabled'] = False + client.update_flavor(obj, **attrs) + + +class ShowNetworkFlavor(command.ShowOne): + _description = _("Display network flavor details") + + def get_parser(self, prog_name): + parser = super(ShowNetworkFlavor, self).get_parser(prog_name) + parser.add_argument( + 'flavor', + metavar='<flavor>', + help=_('Flavor to display (name or ID)') + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_flavor(parsed_args.flavor, ignore_missing=False) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns) + return display_columns, data diff --git a/openstackclient/network/v2/network_qos_rule.py b/openstackclient/network/v2/network_qos_rule.py index a662ca1..baed042 100644 --- a/openstackclient/network/v2/network_qos_rule.py +++ b/openstackclient/network/v2/network_qos_rule.py @@ -14,7 +14,6 @@ # under the License. import itertools -import logging import six from osc_lib.command import command @@ -25,8 +24,6 @@ from openstackclient.i18n import _ from openstackclient.network import sdk_utils -LOG = logging.getLogger(__name__) - RULE_TYPE_BANDWIDTH_LIMIT = 'bandwidth-limit' RULE_TYPE_DSCP_MARKING = 'dscp-marking' RULE_TYPE_MINIMUM_BANDWIDTH = 'minimum-bandwidth' diff --git a/openstackclient/network/v2/network_qos_rule_type.py b/openstackclient/network/v2/network_qos_rule_type.py index 657eb54..52f8e23 100644 --- a/openstackclient/network/v2/network_qos_rule_type.py +++ b/openstackclient/network/v2/network_qos_rule_type.py @@ -13,17 +13,12 @@ # License for the specific language governing permissions and limitations # under the License. -import logging - from osc_lib.command import command from osc_lib import utils from openstackclient.i18n import _ -LOG = logging.getLogger(__name__) - - class ListNetworkQosRuleType(command.Lister): _description = _("List QoS rule types") diff --git a/openstackclient/network/v2/network_segment.py b/openstackclient/network/v2/network_segment.py index 709dc29..c1a672e 100644 --- a/openstackclient/network/v2/network_segment.py +++ b/openstackclient/network/v2/network_segment.py @@ -119,8 +119,8 @@ class DeleteNetworkSegment(command.Command): except Exception as e: result += 1 LOG.error(_("Failed to delete network segment with " - "ID '%(network_segment)s': %(e)s") - % {'network_segment': network_segment, 'e': e}) + "ID '%(network_segment)s': %(e)s"), + {'network_segment': network_segment, 'e': e}) if result > 0: total = len(parsed_args.network_segment) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index c9de47e..6117175 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -51,7 +51,6 @@ _formatters = { 'extra_dhcp_opts': utils.format_list_of_dicts, 'fixed_ips': utils.format_list_of_dicts, 'security_group_ids': utils.format_list, - 'security_groups': utils.format_list, } @@ -64,7 +63,6 @@ def _get_columns(item): 'binding:vnic_type': 'binding_vnic_type', 'is_admin_state_up': 'admin_state_up', 'is_port_security_enabled': 'port_security_enabled', - 'security_group_ids': 'security_groups', 'tenant_id': 'project_id', } return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) @@ -194,6 +192,29 @@ def _prepare_fixed_ips(client_manager, parsed_args): parsed_args.fixed_ip = ips +def _prepare_filter_fixed_ips(client_manager, parsed_args): + """Fix and properly format fixed_ip option for filtering. + + Appropriately convert any subnet names to their respective ids. + Convert fixed_ips in parsed args to be in valid list format for filter: + ['subnet_id=foo']. + """ + client = client_manager.network + ips = [] + + for ip_spec in parsed_args.fixed_ip: + if 'subnet' in ip_spec: + subnet_name_id = ip_spec['subnet'] + if subnet_name_id: + _subnet = client.find_subnet(subnet_name_id, + ignore_missing=False) + ips.append('subnet_id=%s' % _subnet.id) + + if 'ip-address' in ip_spec: + ips.append('ip_address=%s' % ip_spec['ip-address']) + return ips + + def _add_updatable_args(parser): parser.add_argument( '--description', @@ -326,7 +347,7 @@ class CreatePort(command.ShowOne): '--security-group', metavar='<security-group>', action='append', - dest='security_groups', + dest='security_group', help=_("Security group to associate with this port (name or ID) " "(repeat option to set multiple security groups)") ) @@ -368,12 +389,13 @@ class CreatePort(command.ShowOne): _prepare_fixed_ips(self.app.client_manager, parsed_args) attrs = _get_attrs(self.app.client_manager, parsed_args) - if parsed_args.security_groups: - attrs['security_groups'] = [client.find_security_group( - sg, ignore_missing=False).id - for sg in parsed_args.security_groups] - if parsed_args.no_security_group: - attrs['security_groups'] = [] + if parsed_args.security_group: + attrs['security_group_ids'] = [client.find_security_group( + sg, ignore_missing=False).id + for sg in + parsed_args.security_group] + elif parsed_args.no_security_group: + attrs['security_group_ids'] = [] if parsed_args.allowed_address_pairs: attrs['allowed_address_pairs'] = ( _convert_address_pairs(parsed_args)) @@ -466,6 +488,15 @@ class ListPort(command.Lister): help=_("List ports according to their project (name or ID)") ) identity_common.add_project_domain_option_to_parser(parser) + parser.add_argument( + '--fixed-ip', + metavar='subnet=<subnet>,ip-address=<ip-address>', + action=parseractions.MultiKeyValueAction, + optional_keys=['subnet', 'ip-address'], + help=_("Desired IP and/or subnet (name or ID) for filtering " + "ports: subnet=<subnet>,ip-address=<ip-address> " + "(repeat option to set multiple fixed IP addresses)") + ) return parser def take_action(self, parsed_args): @@ -516,6 +547,9 @@ class ListPort(command.Lister): ).id filters['tenant_id'] = project_id filters['project_id'] = project_id + if parsed_args.fixed_ip: + filters['fixed_ips'] = _prepare_filter_fixed_ips( + self.app.client_manager, parsed_args) data = network_client.ports(**filters) @@ -591,7 +625,7 @@ class SetPort(command.Command): '--security-group', metavar='<security-group>', action='append', - dest='security_groups', + dest='security_group', help=_("Security group to associate with this port (name or ID) " "(repeat option to set multiple security groups)") ) @@ -659,17 +693,16 @@ class SetPort(command.Command): attrs['fixed_ips'] += [ip for ip in obj.fixed_ips if ip] elif parsed_args.no_fixed_ip: attrs['fixed_ips'] = [] - if parsed_args.security_groups and parsed_args.no_security_group: - attrs['security_groups'] = [client.find_security_group(sg, - ignore_missing=False).id - for sg in parsed_args.security_groups] - elif parsed_args.security_groups: - attrs['security_groups'] = obj.security_groups - for sg in parsed_args.security_groups: - sg_id = client.find_security_group(sg, ignore_missing=False).id - attrs['security_groups'].append(sg_id) + + if parsed_args.security_group: + attrs['security_group_ids'] = [ + client.find_security_group(sg, ignore_missing=False).id for + sg in parsed_args.security_group] + if not parsed_args.no_security_group: + attrs['security_group_ids'] += obj.security_group_ids + elif parsed_args.no_security_group: - attrs['security_groups'] = [] + attrs['security_group_ids'] = [] if (parsed_args.allowed_address_pairs and parsed_args.no_allowed_address_pair): @@ -734,7 +767,7 @@ class UnsetPort(command.Command): '--security-group', metavar='<security-group>', action='append', - dest='security_groups', + dest='security_group_ids', help=_("Security group which should be removed this port (name " "or ID) (repeat option to unset multiple security groups)") ) @@ -767,7 +800,7 @@ class UnsetPort(command.Command): # Unset* classes tmp_fixed_ips = copy.deepcopy(obj.fixed_ips) tmp_binding_profile = copy.deepcopy(obj.binding_profile) - tmp_secgroups = copy.deepcopy(obj.security_groups) + tmp_secgroups = copy.deepcopy(obj.security_group_ids) tmp_addr_pairs = copy.deepcopy(obj.allowed_address_pairs) _prepare_fixed_ips(self.app.client_manager, parsed_args) attrs = {} @@ -787,16 +820,16 @@ class UnsetPort(command.Command): msg = _("Port does not contain binding-profile %s") % key raise exceptions.CommandError(msg) attrs['binding:profile'] = tmp_binding_profile - if parsed_args.security_groups: + if parsed_args.security_group_ids: try: - for sg in parsed_args.security_groups: + for sg in parsed_args.security_group_ids: sg_id = client.find_security_group( sg, ignore_missing=False).id tmp_secgroups.remove(sg_id) except ValueError: msg = _("Port does not contain security group %s") % sg raise exceptions.CommandError(msg) - attrs['security_groups'] = tmp_secgroups + attrs['security_group_ids'] = tmp_secgroups if parsed_args.allowed_address_pairs: try: for addr in _convert_address_pairs(parsed_args): diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index aad35a9..f46c869 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -249,8 +249,8 @@ class DeleteRouter(command.Command): except Exception as e: result += 1 LOG.error(_("Failed to delete router with " - "name or ID '%(router)s': %(e)s") - % {'router': router, 'e': e}) + "name or ID '%(router)s': %(e)s"), + {'router': router, 'e': e}) if result > 0: total = len(parsed_args.router) diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index 4fb62c7..8f07c5a 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -315,7 +315,7 @@ class CreateSecurityGroupRule(common.NetworkAndComputeShowOne): if parsed_args.src_group: LOG.warning( _("The %(old)s option is deprecated, " - "please use %(new)s instead.") % + "please use %(new)s instead."), {'old': '--src-group', 'new': '--remote-group'}, ) elif not (parsed_args.remote_ip is None and @@ -326,7 +326,7 @@ class CreateSecurityGroupRule(common.NetworkAndComputeShowOne): if parsed_args.src_ip: LOG.warning( _("The %(old)s option is deprecated, " - "please use %(new)s instead.") % + "please use %(new)s instead."), {'old': '--src-ip', 'new': '--remote-ip'}, ) elif attrs['ethertype'] == 'IPv4': @@ -370,7 +370,7 @@ class CreateSecurityGroupRule(common.NetworkAndComputeShowOne): if parsed_args.src_group: LOG.warning( _("The %(old)s option is deprecated, " - "please use %(new)s instead.") % + "please use %(new)s instead."), {'old': '--src-group', 'new': '--remote-group'}, ) if not (parsed_args.remote_ip is None and @@ -379,7 +379,7 @@ class CreateSecurityGroupRule(common.NetworkAndComputeShowOne): if parsed_args.src_ip: LOG.warning( _("The %(old)s option is deprecated, " - "please use %(new)s instead.") % + "please use %(new)s instead."), {'old': '--src-ip', 'new': '--remote-ip'}, ) else: @@ -521,7 +521,7 @@ class ListSecurityGroupRule(common.NetworkAndComputeLister): 'port_range', ) if parsed_args.long: - columns = columns + ('direction', 'ethertype',) + columns = columns + ('direction', 'ether_type',) columns = columns + ('remote_group_id',) # Get the security group rules using the requested query. diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 292b7c0..2771858 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -365,8 +365,8 @@ class DeleteSubnet(command.Command): except Exception as e: result += 1 LOG.error(_("Failed to delete subnet with " - "name or ID '%(subnet)s': %(e)s") - % {'subnet': subnet, 'e': e}) + "name or ID '%(subnet)s': %(e)s"), + {'subnet': subnet, 'e': e}) if result > 0: total = len(parsed_args.subnet) diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index a5a2442..0473111 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -220,8 +220,8 @@ class DeleteSubnetPool(command.Command): except Exception as e: result += 1 LOG.error(_("Failed to delete subnet pool with " - "name or ID '%(pool)s': %(e)s") - % {'pool': pool, 'e': e}) + "name or ID '%(pool)s': %(e)s"), + {'pool': pool, 'e': e}) if result > 0: total = len(parsed_args.subnet_pool) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index e08eee6..d7fe6ac 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -76,7 +76,9 @@ class OpenStackShell(shell.OpenStackShell): # Loop through extensions to get API versions for mod in clientmanager.PLUGIN_MODULES: default_version = getattr(mod, 'DEFAULT_API_VERSION', None) - option = mod.API_VERSION_OPTION.replace('os_', '') + # Only replace the first instance of "os", some service names will + # have "os" in their name, like: "antiddos" + option = mod.API_VERSION_OPTION.replace('os_', '', 1) version_opt = str(self.cloud.config.get(option, default_version)) if version_opt: api = mod.API_NAME diff --git a/openstackclient/tests/functional/common/test_availability_zone.py b/openstackclient/tests/functional/common/test_availability_zone.py index f73e1ed..025da95 100644 --- a/openstackclient/tests/functional/common/test_availability_zone.py +++ b/openstackclient/tests/functional/common/test_availability_zone.py @@ -10,16 +10,23 @@ # License for the specific language governing permissions and limitations # under the License. +import json + from openstackclient.tests.functional import base class AvailabilityZoneTests(base.TestCase): """Functional tests for availability zone. """ - HEADERS = ["'Zone Name'"] - # So far, all components have the same default availability zone name. - DEFAULT_AZ_NAME = 'nova' def test_availability_zone_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('availability zone list' + opts) - self.assertIn(self.DEFAULT_AZ_NAME, raw_output) + cmd_output = json.loads(self.openstack( + 'availability zone list -f json')) + zones = [x['Zone Name'] for x in cmd_output] + self.assertIn( + 'internal', + zones + ) + self.assertIn( + 'nova', + zones + ) diff --git a/openstackclient/tests/functional/common/test_configuration.py b/openstackclient/tests/functional/common/test_configuration.py index f47d3b0..63a17d0 100644 --- a/openstackclient/tests/functional/common/test_configuration.py +++ b/openstackclient/tests/functional/common/test_configuration.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import json import os from openstackclient.common import configuration @@ -20,25 +21,47 @@ BASIC_CONFIG_HEADERS = ['Field', 'Value'] class ConfigurationTests(base.TestCase): - - opts = "-f value -c auth.password" + """Functional test for configuration.""" def test_configuration_show(self): + + # Test show without option raw_output = self.openstack('configuration show') items = self.parse_listing(raw_output) self.assert_table_structure(items, BASIC_CONFIG_HEADERS) - def test_configuration_show_unmask(self): - raw_output = self.openstack('configuration show --unmask ' + self.opts) + cmd_output = json.loads(self.openstack( + 'configuration show -f json' + )) + self.assertEqual( + configuration.REDACTED, + cmd_output['auth.password'] + ) + + # Test show --mask + cmd_output = json.loads(self.openstack( + 'configuration show --mask -f json' + )) + self.assertEqual( + configuration.REDACTED, + cmd_output['auth.password'] + ) + + # Test show --unmask + cmd_output = json.loads(self.openstack( + 'configuration show --unmask -f json' + )) # If we are using os-client-config, this will not be set. Rather than # parse clouds.yaml to get the right value, just make sure # we are not getting redacted. passwd = os.environ.get('OS_PASSWORD') if passwd: - self.assertEqual(passwd + '\n', raw_output) + self.assertEqual( + passwd, + cmd_output['auth.password'] + ) else: - self.assertNotEqual(configuration.REDACTED + '\n', raw_output) - - def test_configuration_show_mask(self): - raw_output = self.openstack('configuration show --mask ' + self.opts) - self.assertEqual(configuration.REDACTED + '\n', raw_output) + self.assertNotEqual( + configuration.REDACTED, + cmd_output['auth.password'] + ) diff --git a/openstackclient/tests/functional/common/test_module.py b/openstackclient/tests/functional/common/test_module.py new file mode 100644 index 0000000..e9e4ee3 --- /dev/null +++ b/openstackclient/tests/functional/common/test_module.py @@ -0,0 +1,79 @@ +# Copyright 2017 Huawei, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import json + +from openstackclient.tests.functional import base + + +class ModuleTest(base.TestCase): + """Functional tests for openstackclient module list output.""" + + CLIENTS = ['openstackclient', + 'keystoneclient', + 'novaclient', + 'openstack'] + + LIBS = ['osc_lib', + 'os_client_config', + 'keystoneauth1'] + + def test_module_list(self): + # Test module list + cmd_output = json.loads(self.openstack('module list -f json')) + for one_module in self.CLIENTS: + self.assertIn(one_module, cmd_output.keys()) + for one_module in self.LIBS: + self.assertNotIn(one_module, cmd_output.keys()) + + # Test module list --all + cmd_output = json.loads(self.openstack('module list --all -f json')) + for one_module in self.CLIENTS + self.LIBS: + self.assertIn(one_module, cmd_output.keys()) + + +class CommandTest(base.TestCase): + """Functional tests for openstackclient command list.""" + GROUPS = [ + 'openstack.volume.v2', + 'openstack.network.v2', + 'openstack.image.v2', + 'openstack.identity.v3', + 'openstack.compute.v2', + 'openstack.common', + 'openstack.cli', + ] + + def test_command_list_no_option(self): + cmd_output = json.loads(self.openstack('command list -f json')) + group_names = [each.get('Command Group') for each in cmd_output] + for one_group in self.GROUPS: + self.assertIn(one_group, group_names) + + def test_command_list_with_group(self): + input_groups = [ + 'volume', + 'network', + 'image', + 'identity', + 'compute.v2' + ] + for each_input in input_groups: + cmd_output = json.loads(self.openstack( + 'command list --group %s -f json' % each_input + )) + group_names = [each.get('Command Group') for each in cmd_output] + for each_name in group_names: + self.assertIn(each_input, each_name) diff --git a/openstackclient/tests/functional/compute/v2/test_agent.py b/openstackclient/tests/functional/compute/v2/test_agent.py index 7115db1..1a112e8 100644 --- a/openstackclient/tests/functional/compute/v2/test_agent.py +++ b/openstackclient/tests/functional/compute/v2/test_agent.py @@ -11,6 +11,7 @@ # under the License. import hashlib +import json from openstackclient.tests.functional import base @@ -18,60 +19,178 @@ from openstackclient.tests.functional import base class ComputeAgentTests(base.TestCase): """Functional tests for compute agent.""" - ID = None - MD5HASH = hashlib.md5().hexdigest() - URL = "http://localhost" - VER = "v1" - OS = "TEST_OS" - ARCH = "x86_64" - HYPER = "kvm" - - HEADERS = ['agent_id', 'md5hash'] - FIELDS = ['agent_id', 'md5hash'] - - @classmethod - def setUpClass(cls): - opts = cls.get_opts(cls.HEADERS) - raw_output = cls.openstack('compute agent create ' + - cls.OS + ' ' + cls.ARCH + ' ' + - cls.VER + ' ' + cls.URL + ' ' + - cls.MD5HASH + ' ' + cls.HYPER + ' ' + - opts) - - # Get agent id because agent can only be deleted by ID - output_list = raw_output.split('\n', 1) - cls.ID = output_list[0] - - cls.assertOutput(cls.MD5HASH + '\n', output_list[1]) - - @classmethod - def tearDownClass(cls): - raw_output = cls.openstack('compute agent delete ' + cls.ID) - cls.assertOutput('', raw_output) - - def test_agent_list(self): - raw_output = self.openstack('compute agent list') - self.assertIn(self.ID, raw_output) - self.assertIn(self.OS, raw_output) - self.assertIn(self.ARCH, raw_output) - self.assertIn(self.VER, raw_output) - self.assertIn(self.URL, raw_output) - self.assertIn(self.MD5HASH, raw_output) - self.assertIn(self.HYPER, raw_output) - - def test_agent_set(self): - ver = 'v2' - url = "http://openstack" - md5hash = hashlib.md5().hexdigest() - - self.openstack('compute agent set ' - + self.ID - + ' --agent-version ' + ver - + ' --url ' + url - + ' --md5hash ' + md5hash) - - raw_output = self.openstack('compute agent list') - self.assertIn(self.ID, raw_output) - self.assertIn(ver, raw_output) - self.assertIn(url, raw_output) - self.assertIn(md5hash, raw_output) + # Generate two different md5hash + MD5HASH1 = hashlib.md5() + MD5HASH1.update('agent_1') + MD5HASH1 = MD5HASH1.hexdigest() + MD5HASH2 = hashlib.md5() + MD5HASH2.update('agent_2') + MD5HASH2 = MD5HASH2.hexdigest() + + def test_compute_agent_delete(self): + """Test compute agent create, delete multiple""" + os1 = "os_1" + arch1 = "x86_64" + ver1 = "v1" + url1 = "http://localhost" + md5hash1 = self.MD5HASH1 + hyper1 = "kvm" + cmd1 = ' '.join((os1, arch1, ver1, url1, md5hash1, hyper1)) + + cmd_output = json.loads(self.openstack( + 'compute agent create -f json ' + + cmd1 + )) + agent_id1 = str(cmd_output["agent_id"]) + + os2 = "os_2" + arch2 = "x86" + ver2 = "v2" + url2 = "http://openstack" + md5hash2 = self.MD5HASH2 + hyper2 = "xen" + cmd2 = ' '.join((os2, arch2, ver2, url2, md5hash2, hyper2)) + + cmd_output = json.loads(self.openstack( + 'compute agent create -f json ' + + cmd2 + )) + agent_id2 = str(cmd_output["agent_id"]) + + # Test compute agent delete + del_output = self.openstack( + 'compute agent delete ' + + agent_id1 + ' ' + agent_id2 + ) + self.assertOutput('', del_output) + + def test_compute_agent_list(self): + """Test compute agent create and list""" + os1 = "os_1" + arch1 = "x86_64" + ver1 = "v1" + url1 = "http://localhost" + md5hash1 = self.MD5HASH1 + hyper1 = "kvm" + cmd1 = ' '.join((os1, arch1, ver1, url1, md5hash1, hyper1)) + + cmd_output = json.loads(self.openstack( + 'compute agent create -f json ' + + cmd1 + )) + agent_id1 = str(cmd_output["agent_id"]) + self.addCleanup(self.openstack, 'compute agent delete ' + agent_id1) + + os2 = "os_2" + arch2 = "x86" + ver2 = "v2" + url2 = "http://openstack" + md5hash2 = self.MD5HASH2 + hyper2 = "xen" + cmd2 = ' '.join((os2, arch2, ver2, url2, md5hash2, hyper2)) + + cmd_output = json.loads(self.openstack( + 'compute agent create -f json ' + + cmd2 + )) + agent_id2 = str(cmd_output["agent_id"]) + self.addCleanup(self.openstack, 'compute agent delete ' + agent_id2) + + # Test compute agent list + cmd_output = json.loads(self.openstack( + 'compute agent list -f json' + )) + + hypervisors = [x["Hypervisor"] for x in cmd_output] + self.assertIn(hyper1, hypervisors) + self.assertIn(hyper2, hypervisors) + + os = [x['OS'] for x in cmd_output] + self.assertIn(os1, os) + self.assertIn(os2, os) + + archs = [x['Architecture'] for x in cmd_output] + self.assertIn(arch1, archs) + self.assertIn(arch2, archs) + + versions = [x['Version'] for x in cmd_output] + self.assertIn(ver1, versions) + self.assertIn(ver2, versions) + + md5hashes = [x['Md5Hash'] for x in cmd_output] + self.assertIn(md5hash1, md5hashes) + self.assertIn(md5hash2, md5hashes) + + urls = [x['URL'] for x in cmd_output] + self.assertIn(url1, urls) + self.assertIn(url2, urls) + + # Test compute agent list --hypervisor + cmd_output = json.loads(self.openstack( + 'compute agent list -f json ' + + '--hypervisor kvm' + )) + + hypervisors = [x["Hypervisor"] for x in cmd_output] + self.assertIn(hyper1, hypervisors) + self.assertNotIn(hyper2, hypervisors) + + os = [x['OS'] for x in cmd_output] + self.assertIn(os1, os) + self.assertNotIn(os2, os) + + archs = [x['Architecture'] for x in cmd_output] + self.assertIn(arch1, archs) + self.assertNotIn(arch2, archs) + + versions = [x['Version'] for x in cmd_output] + self.assertIn(ver1, versions) + self.assertNotIn(ver2, versions) + + md5hashes = [x['Md5Hash'] for x in cmd_output] + self.assertIn(md5hash1, md5hashes) + self.assertNotIn(md5hash2, md5hashes) + + urls = [x['URL'] for x in cmd_output] + self.assertIn(url1, urls) + self.assertNotIn(url2, urls) + + def test_compute_agent_set(self): + """Test compute agent set""" + os1 = "os_1" + arch1 = "x86_64" + ver1 = "v1" + ver2 = "v2" + url1 = "http://localhost" + url2 = "http://openstack" + md5hash1 = self.MD5HASH1 + md5hash2 = self.MD5HASH2 + hyper1 = "kvm" + cmd = ' '.join((os1, arch1, ver1, url1, md5hash1, hyper1)) + + cmd_output = json.loads(self.openstack( + 'compute agent create -f json ' + + cmd + )) + agent_id = str(cmd_output["agent_id"]) + self.assertEqual(ver1, cmd_output["version"]) + self.assertEqual(url1, cmd_output["url"]) + self.assertEqual(md5hash1, cmd_output["md5hash"]) + + self.addCleanup(self.openstack, 'compute agent delete ' + agent_id) + + raw_output = self.openstack( + 'compute agent set ' + + agent_id + ' ' + + '--agent-version ' + ver2 + ' ' + + '--url ' + url2 + ' ' + + '--md5hash ' + md5hash2 + ) + self.assertOutput('', raw_output) + + cmd_output = json.loads(self.openstack( + 'compute agent list -f json' + )) + self.assertEqual(ver2, cmd_output[0]["Version"]) + self.assertEqual(url2, cmd_output[0]["URL"]) + self.assertEqual(md5hash2, cmd_output[0]["Md5Hash"]) diff --git a/openstackclient/tests/functional/compute/v2/test_aggregate.py b/openstackclient/tests/functional/compute/v2/test_aggregate.py index 3836810..95068fc 100644 --- a/openstackclient/tests/functional/compute/v2/test_aggregate.py +++ b/openstackclient/tests/functional/compute/v2/test_aggregate.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import json import uuid from openstackclient.tests.functional import base @@ -18,90 +19,164 @@ from openstackclient.tests.functional import base class AggregateTests(base.TestCase): """Functional tests for aggregate.""" - NAME = uuid.uuid4().hex - HEADERS = ['Name'] - FIELDS = ['name'] + def test_aggregate_create_and_delete(self): + """Test create, delete multiple""" + name1 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'aggregate create -f json ' + + '--zone nova ' + + name1)) + self.assertEqual( + name1, + cmd_output['name'] + ) + self.assertEqual( + 'nova', + cmd_output['availability_zone'] + ) - @classmethod - def setUpClass(cls): - opts = cls.get_opts(cls.FIELDS) - # Use the default 'nova' availability zone for the aggregate. - raw_output = cls.openstack( - 'aggregate create --zone nova ' + cls.NAME + opts + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'aggregate create -f json ' + + '--zone nova ' + + name2)) + self.assertEqual( + name2, + cmd_output['name'] + ) + self.assertEqual( + 'nova', + cmd_output['availability_zone'] ) - expected = cls.NAME + '\n' - cls.assertOutput(expected, raw_output) - @classmethod - def tearDownClass(cls): - raw_output = cls.openstack('aggregate delete ' + cls.NAME) - cls.assertOutput('', raw_output) + del_output = self.openstack( + 'aggregate delete ' + name1 + ' ' + name2) + self.assertOutput('', del_output) def test_aggregate_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('aggregate list' + opts) - self.assertIn(self.NAME, raw_output) - - def test_aggregate_show(self): - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack('aggregate show ' + self.NAME + opts) - self.assertEqual(self.NAME + "\n", raw_output) + """Test aggregate list""" + name1 = uuid.uuid4().hex + self.openstack( + 'aggregate create ' + + '--zone nova ' + + '--property a=b ' + + name1) + self.addCleanup(self.openstack, 'aggregate delete ' + name1) - def test_aggregate_properties(self): - opts = self.get_opts(['name', 'properties']) + name2 = uuid.uuid4().hex + self.openstack( + 'aggregate create ' + + '--zone internal ' + + '--property c=d ' + + name2) + self.addCleanup(self.openstack, 'aggregate delete ' + name2) + + cmd_output = json.loads(self.openstack( + 'aggregate list -f json')) + names = [x['Name'] for x in cmd_output] + self.assertIn(name1, names) + self.assertIn(name2, names) + zones = [x['Availability Zone'] for x in cmd_output] + self.assertIn('nova', zones) + self.assertIn('internal', zones) + + # Test aggregate list --long + cmd_output = json.loads(self.openstack( + 'aggregate list --long -f json')) + names = [x['Name'] for x in cmd_output] + self.assertIn(name1, names) + self.assertIn(name2, names) + zones = [x['Availability Zone'] for x in cmd_output] + self.assertIn('nova', zones) + self.assertIn('internal', zones) + properties = [x['Properties'] for x in cmd_output] + self.assertIn({'a': 'b'}, properties) + self.assertIn({'c': 'd'}, properties) + + def test_aggregate_set_and_unset(self): + """Test aggregate set, show and unset""" + name1 = uuid.uuid4().hex + name2 = uuid.uuid4().hex + self.openstack( + 'aggregate create ' + + '--zone nova ' + + '--property a=b ' + + name1) + self.addCleanup(self.openstack, 'aggregate delete ' + name2) raw_output = self.openstack( - 'aggregate set --property a=b --property c=d ' + self.NAME + 'aggregate set --name ' + + name2 + + ' --zone internal ' + + '--no-property ' + + '--property c=d ' + + name1 ) - self.assertEqual('', raw_output) - - raw_output = self.openstack('aggregate show ' + self.NAME + opts) - self.assertIn(self.NAME + "\na='b', c='d'\n", raw_output) + self.assertOutput('', raw_output) - raw_output = self.openstack( - 'aggregate unset --property a ' + self.NAME + cmd_output = json.loads(self.openstack( + 'aggregate show -f json ' + name2)) + self.assertEqual( + name2, + cmd_output['name'] ) - self.assertEqual('', raw_output) - - raw_output = self.openstack('aggregate show ' + self.NAME + opts) - self.assertIn(self.NAME + "\nc='d'\n", raw_output) - - raw_output = self.openstack( - 'aggregate set --property a=b --property c=d ' + self.NAME + self.assertEqual( + 'internal', + cmd_output['availability_zone'] ) - self.assertEqual('', raw_output) - - raw_output = self.openstack( - 'aggregate set --no-property ' + self.NAME + self.assertIn( + "c='d'", + cmd_output['properties'] + ) + self.assertNotIn( + "a='b'", + cmd_output['properties'] ) - self.assertEqual('', raw_output) - - raw_output = self.openstack('aggregate show ' + self.NAME + opts) - self.assertNotIn("a='b', c='d'", raw_output) - - def test_aggregate_set(self): - opts = self.get_opts(["name", "availability_zone"]) + # Test unset raw_output = self.openstack( - 'aggregate set --zone Zone_1 ' + self.NAME) - self.assertEqual("", raw_output) + 'aggregate unset --property c ' + + name2 + ) + self.assertOutput('', raw_output) - raw_output = self.openstack('aggregate show ' + self.NAME + opts) - self.assertEqual("Zone_1\n" + self.NAME + "\n", raw_output) + cmd_output = json.loads(self.openstack( + 'aggregate show -f json ' + name2)) + self.assertNotIn( + "c='d'", + cmd_output['properties'] + ) def test_aggregate_add_and_remove_host(self): - opts = self.get_opts(["hosts", "name"]) - - raw_output = self.openstack('host list -f value -c "Host Name"') - host_name = raw_output.split()[0] - + """Test aggregate add and remove host""" + name = uuid.uuid4().hex self.openstack( - 'aggregate add host ' + self.NAME + ' ' + host_name) - raw_output = self.openstack('aggregate show ' + self.NAME + opts) - self.assertEqual("[u'" + host_name + "']" + "\n" + self.NAME + "\n", - raw_output) + 'aggregate create ' + name) + self.addCleanup(self.openstack, 'aggregate delete ' + name) + + # Get a host + cmd_output = json.loads(self.openstack( + 'host list -f json')) + host_name = cmd_output[0]['Host Name'] + + # Test add host + cmd_output = json.loads(self.openstack( + 'aggregate add host -f json ' + + name + ' ' + + host_name + )) + self.assertIn( + host_name, + cmd_output['hosts'] + ) - self.openstack( - 'aggregate remove host ' + self.NAME + ' ' + host_name) - raw_output = self.openstack('aggregate show ' + self.NAME + opts) - self.assertEqual("[]\n" + self.NAME + "\n", raw_output) + # Test remove host + cmd_output = json.loads(self.openstack( + 'aggregate remove host -f json ' + + name + ' ' + + host_name + )) + self.assertNotIn( + host_name, + cmd_output['hosts'] + ) diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index 119ef05..4d8019b 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -12,12 +12,12 @@ import json import time +import uuid -from tempest.lib.common.utils import data_utils +from tempest.lib import exceptions from openstackclient.tests.functional import base from openstackclient.tests.functional.volume.v2 import test_volume -from tempest.lib import exceptions class ServerTests(base.TestCase): @@ -27,23 +27,30 @@ class ServerTests(base.TestCase): def get_flavor(cls): # NOTE(rtheis): Get cirros256 or m1.tiny flavors since functional # tests may create other flavors. - flavors = cls.openstack('flavor list -c Name -f value').split('\n') + flavors = json.loads(cls.openstack( + "flavor list -f json " + )) server_flavor = None for flavor in flavors: - if flavor in ['m1.tiny', 'cirros256']: - server_flavor = flavor + if flavor['Name'] in ['m1.tiny', 'cirros256']: + server_flavor = flavor['Name'] break return server_flavor @classmethod def get_image(cls): - # NOTE(rtheis): Get cirros image since functional tests may - # create other images. - images = cls.openstack('image list -c Name -f value').split('\n') + # NOTE(rtheis): Get first Cirros image since functional tests may + # create other images. Image may be named '-uec' or + # '-disk'. + images = json.loads(cls.openstack( + "image list -f json " + )) server_image = None for image in images: - if image.startswith('cirros-') and image.endswith('-uec'): - server_image = image + if (image['Name'].startswith('cirros-') and + (image['Name'].endswith('-uec') or + image['Name'].endswith('-disk'))): + server_image = image['Name'] break return server_image @@ -52,92 +59,84 @@ class ServerTests(base.TestCase): try: # NOTE(rtheis): Get private network since functional tests may # create other networks. - raw_output = cls.openstack('network show private -c id -f value') + cmd_output = json.loads(cls.openstack( + 'network show private -f json' + )) except exceptions.CommandFailed: return '' - return ' --nic net-id=' + raw_output.strip('\n') + return '--nic net-id=' + cmd_output['id'] def server_create(self, name=None): - """Create server. Add cleanup.""" - name = name or data_utils.rand_uuid() - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack('--debug server create --flavor ' + - self.flavor_name + - ' --image ' + self.image_name + - self.network_arg + ' ' + - name + opts) - if not raw_output: + """Create server, with cleanup""" + name = name or uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'server create -f json ' + + '--flavor ' + self.flavor_name + ' ' + + '--image ' + self.image_name + ' ' + + self.network_arg + ' ' + + '--wait ' + + name + )) + if not cmd_output: self.fail('Server has not been created!') self.addCleanup(self.server_delete, name) - - def server_list(self, params=[]): - """List servers.""" - opts = self.get_opts(params) - return self.openstack('server list' + opts) + self.assertEqual( + name, + cmd_output["name"], + ) + return cmd_output def server_delete(self, name): - """Delete server by name.""" + """Delete server by name""" self.openstack('server delete ' + name) def setUp(self): - """Set necessary variables and create server.""" + """Select common resources""" super(ServerTests, self).setUp() self.flavor_name = self.get_flavor() self.image_name = self.get_image() self.network_arg = self.get_network() - self.NAME = data_utils.rand_name('TestServer') - self.OTHER_NAME = data_utils.rand_name('TestServer') - self.HEADERS = ['"Name"'] - self.FIELDS = ['name'] - self.IP_POOL = 'public' - self.server_create(self.NAME) - - def test_server_rename(self): - """Test server rename command. - - Test steps: - 1) Boot server in setUp - 2) Rename server - 3) Check output - 4) Rename server back to original name - """ - raw_output = self.openstack('server set --name ' + self.OTHER_NAME + - ' ' + self.NAME) - self.assertOutput("", raw_output) - self.assertNotIn(self.NAME, self.server_list(['Name'])) - self.assertIn(self.OTHER_NAME, self.server_list(['Name'])) - self.openstack('server set --name ' + self.NAME + ' ' + - self.OTHER_NAME) - def test_server_list(self): - """Test server list command. + """Test server list, set""" + cmd_output = self.server_create() + name1 = cmd_output['name'] + cmd_output = self.server_create() + name2 = cmd_output['name'] + self.wait_for_status(name1, "ACTIVE") + self.wait_for_status(name2, "ACTIVE") - Test steps: - 1) Boot server in setUp - 2) List servers - 3) Check output - """ - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('server list' + opts) - self.assertIn(self.NAME, raw_output) + cmd_output = json.loads(self.openstack( + 'server list -f json' + )) + col_name = [x["Name"] for x in cmd_output] + self.assertIn(name1, col_name) + self.assertIn(name2, col_name) - def test_server_show(self): - """Test server create, server delete commands""" - name1 = data_utils.rand_name('TestServer') + # Test list --status PAUSED + raw_output = self.openstack('server pause ' + name2) + self.assertEqual("", raw_output) + self.wait_for_status(name2, "PAUSED") cmd_output = json.loads(self.openstack( - 'server create -f json ' + - '--flavor ' + self.flavor_name + ' ' + - '--image ' + self.image_name + ' ' + - self.network_arg + ' ' + - name1 + 'server list -f json ' + + '--status ACTIVE' )) - self.assertIsNotNone(cmd_output["id"]) - self.addCleanup(self.openstack, 'server delete ' + name1) - self.assertEqual( - name1, - cmd_output["name"], - ) + col_name = [x["Name"] for x in cmd_output] + self.assertIn(name1, col_name) + self.assertNotIn(name2, col_name) + cmd_output = json.loads(self.openstack( + 'server list -f json ' + + '--status PAUSED' + )) + col_name = [x["Name"] for x in cmd_output] + self.assertNotIn(name1, col_name) + self.assertIn(name2, col_name) + + def test_server_set(self): + """Test server create, delete, set, show""" + cmd_output = self.server_create() + name = cmd_output['name'] + # self.wait_for_status(name, "ACTIVE") # Have a look at some other fields flavor = json.loads(self.openstack( @@ -165,171 +164,176 @@ class ServerTests(base.TestCase): cmd_output["image"], ) - def test_server_metadata(self): - """Test command to set server metadata. + # Test properties set + raw_output = self.openstack( + 'server set ' + + '--property a=b --property c=d ' + + name + ) + self.assertOutput('', raw_output) + + cmd_output = json.loads(self.openstack( + 'server show -f json ' + + name + )) + # Really, shouldn't this be a list? + self.assertEqual( + "a='b', c='d'", + cmd_output['properties'], + ) - Test steps: - 1) Boot server in setUp - 2) Set properties for server - 3) Check server properties in server show output - 4) Unset properties for server - 5) Check server properties in server show output - """ - self.wait_for_status("ACTIVE") - # metadata raw_output = self.openstack( - 'server set --property a=b --property c=d ' + self.NAME) - opts = self.get_opts(["name", "properties"]) - raw_output = self.openstack('server show ' + self.NAME + opts) - self.assertEqual(self.NAME + "\na='b', c='d'\n", raw_output) + 'server unset ' + + '--property a ' + + name + ) + cmd_output = json.loads(self.openstack( + 'server show -f json ' + + name + )) + self.assertEqual( + "c='d'", + cmd_output['properties'], + ) + # Test set --name + new_name = uuid.uuid4().hex raw_output = self.openstack( - 'server unset --property a ' + self.NAME) - opts = self.get_opts(["name", "properties"]) - raw_output = self.openstack('server show ' + self.NAME + opts) - self.assertEqual(self.NAME + "\nc='d'\n", raw_output) - - def test_server_suspend_resume(self): - """Test server suspend and resume commands. - - Test steps: - 1) Boot server in setUp - 2) Suspend server - 3) Check for SUSPENDED server status - 4) Resume server - 5) Check for ACTIVE server status - """ - self.wait_for_status("ACTIVE") - # suspend - raw_output = self.openstack('server suspend ' + self.NAME) - self.assertEqual("", raw_output) - self.wait_for_status("SUSPENDED") - # resume - raw_output = self.openstack('server resume ' + self.NAME) - self.assertEqual("", raw_output) - self.wait_for_status("ACTIVE") + 'server set ' + + '--name ' + new_name + ' ' + + name + ) + self.assertOutput("", raw_output) + cmd_output = json.loads(self.openstack( + 'server show -f json ' + + new_name + )) + self.assertEqual( + new_name, + cmd_output["name"], + ) + # Put it back so we clean up properly + raw_output = self.openstack( + 'server set ' + + '--name ' + name + ' ' + + new_name + ) + self.assertOutput("", raw_output) - def test_server_lock_unlock(self): - """Test server lock and unlock commands. + def test_server_actions(self): + """Test server action pairs - Test steps: - 1) Boot server in setUp - 2) Lock server - 3) Check output - 4) Unlock server - 5) Check output + suspend/resume + pause/unpause + rescue/unrescue + lock/unlock """ - self.wait_for_status("ACTIVE") - # lock - raw_output = self.openstack('server lock ' + self.NAME) - self.assertEqual("", raw_output) - # unlock - raw_output = self.openstack('server unlock ' + self.NAME) + cmd_output = self.server_create() + name = cmd_output['name'] + + # suspend + raw_output = self.openstack('server suspend ' + name) self.assertEqual("", raw_output) + self.wait_for_status(name, "SUSPENDED") - def test_server_pause_unpause(self): - """Test server pause and unpause commands. + # resume + raw_output = self.openstack('server resume ' + name) + self.assertEqual("", raw_output) + self.wait_for_status(name, "ACTIVE") - Test steps: - 1) Boot server in setUp - 2) Pause server - 3) Check for PAUSED server status - 4) Unpause server - 5) Check for ACTIVE server status - """ - self.wait_for_status("ACTIVE") # pause - raw_output = self.openstack('server pause ' + self.NAME) + raw_output = self.openstack('server pause ' + name) self.assertEqual("", raw_output) - self.wait_for_status("PAUSED") + self.wait_for_status(name, "PAUSED") + # unpause - raw_output = self.openstack('server unpause ' + self.NAME) + raw_output = self.openstack('server unpause ' + name) self.assertEqual("", raw_output) - self.wait_for_status("ACTIVE") - - def test_server_rescue_unrescue(self): - """Test server rescue and unrescue commands. + self.wait_for_status(name, "ACTIVE") - Test steps: - 1) Boot server in setUp - 2) Rescue server - 3) Check for RESCUE server status - 4) Unrescue server - 5) Check for ACTIVE server status - """ - self.wait_for_status("ACTIVE") # rescue - opts = self.get_opts(["adminPass"]) - raw_output = self.openstack('server rescue ' + self.NAME + opts) + raw_output = self.openstack('server rescue ' + name) self.assertNotEqual("", raw_output) - self.wait_for_status("RESCUE") + self.wait_for_status(name, "RESCUE") + # unrescue - raw_output = self.openstack('server unrescue ' + self.NAME) + raw_output = self.openstack('server unrescue ' + name) + self.assertEqual("", raw_output) + self.wait_for_status(name, "ACTIVE") + + # lock + raw_output = self.openstack('server lock ' + name) + self.assertEqual("", raw_output) + # NOTE(dtroyer): No way to verify this status??? + + # unlock + raw_output = self.openstack('server unlock ' + name) self.assertEqual("", raw_output) - self.wait_for_status("ACTIVE") + # NOTE(dtroyer): No way to verify this status??? def test_server_attach_detach_floating_ip(self): - """Test commands to attach and detach floating IP for server. - - Test steps: - 1) Boot server in setUp - 2) Create floating IP - 3) Add floating IP to server - 4) Check for floating IP in server show output - 5) Remove floating IP from server - 6) Check that floating IP is not in server show output - 7) Delete floating IP - 8) Check output - """ - self.wait_for_status("ACTIVE") + """Test floating ip create/delete; server add/remove floating ip""" + cmd_output = self.server_create() + name = cmd_output['name'] + self.wait_for_status(name, "ACTIVE") + # attach ip - opts = self.get_opts(["id", "floating_ip_address"]) - raw_output = self.openstack('floating ip create ' + - self.IP_POOL + - opts) - ip, ipid, rol = tuple(raw_output.split('\n')) - self.assertNotEqual("", ipid) - self.assertNotEqual("", ip) - raw_output = self.openstack('server add floating ip ' + self.NAME + - ' ' + ip) + cmd_output = json.loads(self.openstack( + 'floating ip create -f json ' + + 'public' + )) + floating_ip = cmd_output['floating_ip_address'] + self.assertNotEqual('', cmd_output['id']) + self.assertNotEqual('', floating_ip) + self.addCleanup( + self.openstack, + 'floating ip delete ' + cmd_output['id'] + ) + + raw_output = self.openstack( + 'server add floating ip ' + + name + ' ' + + floating_ip + ) self.assertEqual("", raw_output) - raw_output = self.openstack('server show ' + self.NAME) - self.assertIn(ip, raw_output) + cmd_output = json.loads(self.openstack( + 'server show -f json ' + + name + )) + self.assertIn( + floating_ip, + cmd_output['addresses'], + ) # detach ip - raw_output = self.openstack('server remove floating ip ' + self.NAME + - ' ' + ip) - self.assertEqual("", raw_output) - raw_output = self.openstack('server show ' + self.NAME) - self.assertNotIn(ip, raw_output) - raw_output = self.openstack('floating ip delete ' + ipid) + raw_output = self.openstack( + 'server remove floating ip ' + + name + ' ' + + floating_ip + ) self.assertEqual("", raw_output) + cmd_output = json.loads(self.openstack( + 'server show -f json ' + + name + )) + self.assertNotIn( + floating_ip, + cmd_output['addresses'], + ) + def test_server_reboot(self): - """Test server reboot command. + """Test server reboot""" + cmd_output = self.server_create() + name = cmd_output['name'] - Test steps: - 1) Boot server in setUp - 2) Reboot server - 3) Check for ACTIVE server status - """ - self.wait_for_status("ACTIVE") # reboot - raw_output = self.openstack('server reboot ' + self.NAME) + raw_output = self.openstack('server reboot ' + name) self.assertEqual("", raw_output) - self.wait_for_status("ACTIVE") + self.wait_for_status(name, "ACTIVE") def test_server_boot_from_volume(self): - """Test server create from volume, server delete - - Test steps: - 1) Create volume from image - 2) Create empty volume - 3) Create server from new volumes - 4) Check for ACTIVE new server status - 5) Check volumes attached to server - """ - # server_image = self.get_image() + """Test server create from volume, server delete""" # get volume status wait function volume_wait_for = test_volume.VolumeTests( methodName='wait_for', @@ -348,7 +352,7 @@ class ServerTests(base.TestCase): image_size = 1 # create volume from image - volume_name = data_utils.rand_name('volume', self.image_name) + volume_name = uuid.uuid4().hex cmd_output = json.loads(self.openstack( 'volume create -f json ' + '--image ' + self.image_name + ' ' + @@ -364,7 +368,7 @@ class ServerTests(base.TestCase): volume_wait_for("volume", volume_name, "available") # create empty volume - empty_volume_name = data_utils.rand_name('TestVolume') + empty_volume_name = uuid.uuid4().hex cmd_output = json.loads(self.openstack( 'volume create -f json ' + '--size ' + str(image_size) + ' ' + @@ -379,13 +383,14 @@ class ServerTests(base.TestCase): volume_wait_for("volume", empty_volume_name, "available") # create server - server_name = data_utils.rand_name('TestServer') + server_name = uuid.uuid4().hex server = json.loads(self.openstack( 'server create -f json ' + '--flavor ' + self.flavor_name + ' ' + '--volume ' + volume_name + ' ' + '--block-device-mapping vdb=' + empty_volume_name + ' ' + self.network_arg + ' ' + + '--wait ' + server_name )) self.assertIsNotNone(server["id"]) @@ -394,7 +399,6 @@ class ServerTests(base.TestCase): server_name, server['name'], ) - volume_wait_for("server", server_name, "ACTIVE") # check volumes cmd_output = json.loads(self.openstack( @@ -437,25 +441,78 @@ class ServerTests(base.TestCase): cmd_output['status'], ) - def wait_for_status(self, expected_status='ACTIVE', wait=900, interval=30): + def test_server_create_with_none_network(self): + """Test server create with none network option.""" + server_name = uuid.uuid4().hex + server = json.loads(self.openstack( + # auto/none enable in nova micro version (v2.37+) + '--os-compute-api-version 2.latest ' + + 'server create -f json ' + + '--flavor ' + self.flavor_name + ' ' + + '--image ' + self.image_name + ' ' + + '--nic none ' + + server_name + )) + self.assertIsNotNone(server["id"]) + self.addCleanup(self.openstack, 'server delete --wait ' + server_name) + self.assertEqual(server_name, server['name']) + self.wait_for_status(server_name, "ACTIVE") + server = json.loads(self.openstack( + 'server show -f json ' + server_name + )) + self.assertIsNotNone(server['addresses']) + self.assertEqual('', server['addresses']) + + def test_server_create_with_empty_network_option_latest(self): + """Test server create with empty network option in nova 2.latest.""" + server_name = uuid.uuid4().hex + try: + self.openstack( + # auto/none enable in nova micro version (v2.37+) + '--os-compute-api-version 2.latest ' + + 'server create -f json ' + + '--flavor ' + self.flavor_name + ' ' + + '--image ' + self.image_name + ' ' + + server_name + ) + except exceptions.CommandFailed as e: + self.assertIn('nics are required after microversion 2.36', + e.stderr) + else: + self.fail('CommandFailed should be raised.') + + def wait_for_status( + self, + name, + expected_status='ACTIVE', + wait=900, + interval=10, + ): """Wait until server reaches expected status.""" # TODO(thowe): Add a server wait command to osc failures = ['ERROR'] total_sleep = 0 - opts = self.get_opts(['status']) while total_sleep < wait: - status = self.openstack('server show ' + self.NAME + opts) - status = status.rstrip() - print('Waiting for {} current status: {}'.format(expected_status, - status)) + cmd_output = json.loads(self.openstack( + 'server show -f json ' + + name + )) + status = cmd_output['status'] + print('Waiting for {}, current status: {}'.format( + expected_status, + status, + )) if status == expected_status: break self.assertNotIn(status, failures) time.sleep(interval) total_sleep += interval - status = self.openstack('server show ' + self.NAME + opts) - status = status.rstrip() + cmd_output = json.loads(self.openstack( + 'server show -f json ' + + name + )) + status = cmd_output['status'] self.assertEqual(status, expected_status) # give it a little bit more time time.sleep(5) diff --git a/openstackclient/tests/functional/compute/v2/test_server_group.py b/openstackclient/tests/functional/compute/v2/test_server_group.py index 3f0a24e..44ecda1 100644 --- a/openstackclient/tests/functional/compute/v2/test_server_group.py +++ b/openstackclient/tests/functional/compute/v2/test_server_group.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import json import uuid from openstackclient.tests.functional import base @@ -18,29 +19,88 @@ from openstackclient.tests.functional import base class ServerGroupTests(base.TestCase): """Functional tests for servergroup.""" - NAME = uuid.uuid4().hex - HEADERS = ['Name'] - FIELDS = ['name'] - - @classmethod - def setUpClass(cls): - opts = cls.get_opts(cls.FIELDS) - raw_output = cls.openstack('server group create --policy affinity ' + - cls.NAME + opts) - expected = cls.NAME + '\n' - cls.assertOutput(expected, raw_output) - - @classmethod - def tearDownClass(cls): - raw_output = cls.openstack('server group delete ' + cls.NAME) - cls.assertOutput('', raw_output) - - def test_server_group_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('server group list' + opts) - self.assertIn(self.NAME, raw_output) - - def test_server_group_show(self): - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack('server group show ' + self.NAME + opts) - self.assertEqual(self.NAME + "\n", raw_output) + def test_server_group_delete(self): + """Test create, delete multiple""" + name1 = uuid.uuid4().hex + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'server group create -f json ' + + '--policy affinity ' + + name1 + )) + self.assertEqual( + name1, + cmd_output['name'] + ) + self.assertEqual( + 'affinity', + cmd_output['policies'] + ) + + cmd_output = json.loads(self.openstack( + 'server group create -f json ' + + '--policy anti-affinity ' + + name2 + )) + self.assertEqual( + name2, + cmd_output['name'] + ) + self.assertEqual( + 'anti-affinity', + cmd_output['policies'] + ) + + del_output = self.openstack( + 'server group delete ' + name1 + ' ' + name2) + self.assertOutput('', del_output) + + def test_server_group_show_and_list(self): + """Test server group create, show, and list""" + name1 = uuid.uuid4().hex + name2 = uuid.uuid4().hex + + # test server gorup show + cmd_output = json.loads(self.openstack( + 'server group create -f json ' + + '--policy affinity ' + + name1 + )) + self.addCleanup(self.openstack, 'server group delete ' + name1) + cmd_output = json.loads(self.openstack( + 'server group show -f json ' + name1)) + self.assertEqual( + name1, + cmd_output['name'] + ) + self.assertEqual( + 'affinity', + cmd_output['policies'] + ) + + cmd_output = json.loads(self.openstack( + 'server group create -f json ' + + '--policy anti-affinity ' + + name2 + )) + self.addCleanup(self.openstack, 'server group delete ' + name2) + cmd_output = json.loads(self.openstack( + 'server group show -f json ' + name2)) + self.assertEqual( + name2, + cmd_output['name'] + ) + self.assertEqual( + 'anti-affinity', + cmd_output['policies'] + ) + + # test server group list + cmd_output = json.loads(self.openstack( + 'server group list -f json')) + names = [x["Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertIn(name2, names) + policies = [x["Policies"] for x in cmd_output] + self.assertIn('affinity', policies) + self.assertIn('anti-affinity', policies) diff --git a/openstackclient/tests/functional/identity/v3/test_group.py b/openstackclient/tests/functional/identity/v3/test_group.py index 7049118..917d5df 100644 --- a/openstackclient/tests/functional/identity/v3/test_group.py +++ b/openstackclient/tests/functional/identity/v3/test_group.py @@ -102,11 +102,7 @@ class GroupTests(common.IdentityTests): 'user_domain': self.domain_name, 'group': group_name, 'user': username}) - self.assertEqual( - '%(user)s added to group %(group)s\n' % {'user': username, - 'group': group_name}, - raw_output - ) + self.assertOutput('', raw_output) def test_group_contains_user(self): group_name = self._create_dummy_group() @@ -128,11 +124,7 @@ class GroupTests(common.IdentityTests): 'user_domain': self.domain_name, 'group': group_name, 'user': username}) - self.assertEqual( - '%(user)s added to group %(group)s\n' % {'user': username, - 'group': group_name}, - raw_output - ) + self.assertOutput('', raw_output) raw_output = self.openstack( 'group contains user ' '--group-domain %(group_domain)s ' @@ -165,14 +157,5 @@ class GroupTests(common.IdentityTests): 'user_domain': self.domain_name, 'group': group_name, 'user': username}) - self.assertEqual( - '%(user)s added to group %(group)s\n' % {'user': username, - 'group': group_name}, - add_raw_output - ) - self.assertEqual( - '%(user)s removed from ' - 'group %(group)s\n' % {'user': username, - 'group': group_name}, - remove_raw_output - ) + self.assertOutput('', add_raw_output) + self.assertOutput('', remove_raw_output) diff --git a/openstackclient/tests/functional/identity/v3/test_project.py b/openstackclient/tests/functional/identity/v3/test_project.py index 5639dc1..96d41c3 100644 --- a/openstackclient/tests/functional/identity/v3/test_project.py +++ b/openstackclient/tests/functional/identity/v3/test_project.py @@ -10,6 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. +import json + from tempest.lib.common.utils import data_utils from openstackclient.tests.functional.identity.v3 import common @@ -111,3 +113,14 @@ class ProjectTests(common.IdentityTests): 'name': self.project_name}) items = self.parse_show(raw_output) self.assert_show_fields(items, self.PROJECT_FIELDS) + + def test_project_show_with_parents_children(self): + json_output = json.loads(self.openstack( + 'project show ' + '--parents --children -f json ' + '--domain %(domain)s ' + '%(name)s' % {'domain': self.domain_name, + 'name': self.project_name})) + for attr_name in (self.PROJECT_FIELDS + ['parents', 'subtree']): + self.assertIn(attr_name, json_output) + self.assertEqual(self.project_name, json_output.get('name')) diff --git a/openstackclient/tests/functional/image/v2/test_image.py b/openstackclient/tests/functional/image/v2/test_image.py index 6faff94..b6baf57 100644 --- a/openstackclient/tests/functional/image/v2/test_image.py +++ b/openstackclient/tests/functional/image/v2/test_image.py @@ -84,7 +84,7 @@ class ImageTests(base.TestCase): self.openstack( 'image set --accept ' + self.NAME) shared_img_list = self.parse_listing( - self.openstack('image list --shared', self.get_opts(['name'])) + self.openstack('image list --shared') ) self.assertIn(self.NAME, [img['Name'] for img in shared_img_list]) diff --git a/openstackclient/tests/functional/network/v2/test_network.py b/openstackclient/tests/functional/network/v2/test_network.py index 1f7b7c9..1263655 100644 --- a/openstackclient/tests/functional/network/v2/test_network.py +++ b/openstackclient/tests/functional/network/v2/test_network.py @@ -126,6 +126,7 @@ class NetworkTests(base.TestCase): cmd_output = json.loads(self.openstack( 'network create -f json ' + '--description aaaa ' + + '--no-default ' + name1 )) self.addCleanup(self.openstack, 'network delete ' + name1) @@ -147,17 +148,11 @@ class NetworkTests(base.TestCase): 'Internal', cmd_output["router:external"], ) - # NOTE(dtroyer): is_default is not present in the create output - # so make sure it stays that way. - # NOTE(stevemar): is_default *is* present in SDK 0.9.11 and newer, - # but the value seems to always be None, regardless - # of the --default or --no-default value. - # self.assertEqual('x', cmd_output) - if ('is_default' in cmd_output): - self.assertEqual( - None, - cmd_output["is_default"], - ) + + self.assertEqual( + False, + cmd_output["is_default"], + ) self.assertEqual( True, cmd_output["port_security_enabled"], @@ -185,11 +180,10 @@ class NetworkTests(base.TestCase): True, cmd_output["shared"], ) - if ('is_default' in cmd_output): - self.assertEqual( - None, - cmd_output["is_default"], - ) + self.assertEqual( + None, + cmd_output["is_default"], + ) self.assertEqual( True, cmd_output["port_security_enabled"], @@ -275,16 +269,11 @@ class NetworkTests(base.TestCase): 'Internal', cmd_output["router:external"], ) - # NOTE(dtroyer): is_default is not present in the create output - # so make sure it stays that way. - # NOTE(stevemar): is_default *is* present in SDK 0.9.11 and newer, - # but the value seems to always be None, regardless - # of the --default or --no-default value. - if ('is_default' in cmd_output): - self.assertEqual( - None, - cmd_output["is_default"], - ) + + self.assertEqual( + False, + cmd_output["is_default"], + ) self.assertEqual( True, cmd_output["port_security_enabled"], @@ -321,7 +310,6 @@ class NetworkTests(base.TestCase): 'External', cmd_output["router:external"], ) - # why not 'None' like above?? self.assertEqual( False, cmd_output["is_default"], @@ -330,29 +318,3 @@ class NetworkTests(base.TestCase): False, cmd_output["port_security_enabled"], ) - - # NOTE(dtroyer): There is ambiguity around is_default in that - # it is not in the API docs and apparently can - # not be set when the network is --external, - # although the option handling code only looks at - # the value of is_default when external is True. - raw_output = self.openstack( - 'network set ' + - '--default ' + - name - ) - self.assertOutput('', raw_output) - - cmd_output = json.loads(self.openstack( - 'network show -f json ' + name - )) - - self.assertEqual( - 'cccc', - cmd_output["description"], - ) - # NOTE(dtroyer): This should be 'True' - self.assertEqual( - False, - cmd_output["port_security_enabled"], - ) diff --git a/openstackclient/tests/functional/network/v2/test_network_flavor.py b/openstackclient/tests/functional/network/v2/test_network_flavor.py new file mode 100644 index 0000000..b2fc2ea --- /dev/null +++ b/openstackclient/tests/functional/network/v2/test_network_flavor.py @@ -0,0 +1,176 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# 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 import base + + +class NetworkFlavorTests(base.TestCase): + """Functional tests for network flavor.""" + + def test_network_flavor_delete(self): + """Test create, delete multiple""" + name1 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'network flavor create -f json --description testdescription ' + '--enable --service-type L3_ROUTER_NAT ' + name1, + )) + self.assertEqual( + name1, + cmd_output['name'], + ) + self.assertEqual( + True, + cmd_output['enabled'], + ) + self.assertEqual( + 'testdescription', + cmd_output['description'], + ) + + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'network flavor create -f json --description testdescription1 ' + '--disable --service-type L3_ROUTER_NAT ' + name2, + )) + self.assertEqual( + name2, + cmd_output['name'], + ) + self.assertEqual( + False, + cmd_output['enabled'], + ) + self.assertEqual( + 'testdescription1', + cmd_output['description'], + ) + raw_output = self.openstack( + 'network flavor delete ' + name1 + " " + name2) + self.assertOutput('', raw_output) + + def test_network_flavor_list(self): + """Test create defaults, list filters, delete""" + name1 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'network flavor create -f json --description testdescription ' + '--enable --service-type L3_ROUTER_NAT ' + name1, + )) + self.addCleanup(self.openstack, "network flavor delete " + name1) + self.assertEqual( + name1, + cmd_output['name'], + ) + self.assertEqual( + True, + cmd_output['enabled'], + ) + self.assertEqual( + 'testdescription', + cmd_output['description'], + ) + + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'network flavor create -f json --description testdescription1 ' + '--disable --service-type L3_ROUTER_NAT ' + name2, + )) + self.assertEqual( + name2, + cmd_output['name'], + ) + self.assertEqual( + False, + cmd_output['enabled'], + ) + self.assertEqual( + 'testdescription1', + cmd_output['description'], + ) + self.addCleanup(self.openstack, "network flavor delete " + name2) + + # Test list + cmd_output = json.loads(self.openstack( + 'network flavor list -f json ',)) + self.assertIsNotNone(cmd_output) + + name_list = [item.get('Name') for item in cmd_output] + self.assertIn(name1, name_list) + self.assertIn(name2, name_list) + + def test_network_flavor_set(self): + """Tests create options, set, show, delete""" + name = uuid.uuid4().hex + newname = name + "_" + cmd_output = json.loads(self.openstack( + 'network flavor create -f json --description testdescription ' + '--disable --service-type L3_ROUTER_NAT ' + name, + )) + self.addCleanup(self.openstack, "network flavor delete " + newname) + self.assertEqual( + name, + cmd_output['name'], + ) + self.assertEqual( + False, + cmd_output['enabled'], + ) + self.assertEqual( + 'testdescription', + cmd_output['description'], + ) + + raw_output = self.openstack( + 'network flavor set --name ' + newname + ' --disable ' + name + ) + self.assertOutput('', raw_output) + + cmd_output = json.loads(self.openstack( + 'network flavor show -f json ' + newname,)) + self.assertEqual( + newname, + cmd_output['name'], + ) + self.assertEqual( + False, + cmd_output['enabled'], + ) + self.assertEqual( + 'testdescription', + cmd_output['description'], + ) + + def test_network_flavor_show(self): + """Test show network flavor""" + name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'network flavor create -f json --description testdescription ' + '--disable --service-type L3_ROUTER_NAT ' + name, + )) + self.addCleanup(self.openstack, "network flavor delete " + name) + cmd_output = json.loads(self.openstack( + 'network flavor show -f json ' + name,)) + self.assertEqual( + name, + cmd_output['name'], + ) + self.assertEqual( + False, + cmd_output['enabled'], + ) + self.assertEqual( + 'testdescription', + cmd_output['description'], + ) diff --git a/openstackclient/tests/functional/network/v2/test_port.py b/openstackclient/tests/functional/network/v2/test_port.py index e100bd8..78c5727 100644 --- a/openstackclient/tests/functional/network/v2/test_port.py +++ b/openstackclient/tests/functional/network/v2/test_port.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -import re +import json import uuid from openstackclient.tests.functional import base @@ -20,21 +20,12 @@ class PortTests(base.TestCase): """Functional tests for port. """ NAME = uuid.uuid4().hex NETWORK_NAME = uuid.uuid4().hex - HEADERS = ['Name'] - FIELDS = ['name'] + SG_NAME = uuid.uuid4().hex @classmethod def setUpClass(cls): - # Set up some regex for matching below - cls.re_id = re.compile("\s+id\s+\|\s+(\S+)") - cls.re_name = re.compile("\s+name\s+\|\s+([^|]+?)\s+\|") - cls.re_description = re.compile("\s+description\s+\|\s+([^|]+?)\s+\|") - cls.re_mac_address = re.compile("\s+mac_address\s+\|\s+([^|]+?)\s+\|") - cls.re_state = re.compile("\s+admin_state_up\s+\|\s+([^|]+?)\s+\|") - # Create a network for the port - raw_output = cls.openstack('network create ' + cls.NETWORK_NAME) - cls.network_id = re.search(cls.re_id, raw_output).group(1) + cls.openstack('network create ' + cls.NETWORK_NAME) @classmethod def tearDownClass(cls): @@ -43,33 +34,23 @@ class PortTests(base.TestCase): def test_port_delete(self): """Test create, delete multiple""" - raw_output = self.openstack( - 'port create --network ' + self.NETWORK_NAME + ' ' + self.NAME - ) - re_id1 = re.search(self.re_id, raw_output) - self.assertIsNotNone(re_id1) - id1 = re_id1.group(1) - self.assertIsNotNone( - re.search(self.re_mac_address, raw_output).group(1), - ) - self.assertEqual( - self.NAME, - re.search(self.re_name, raw_output).group(1), - ) - - raw_output = self.openstack( - 'port create ' + - '--network ' + self.NETWORK_NAME + ' ' + + json_output = json.loads(self.openstack( + 'port create -f json --network ' + + self.NETWORK_NAME + ' ' + self.NAME + )) + id1 = json_output.get('id') + self.assertIsNotNone(id1) + self.assertIsNotNone(json_output.get('mac_address')) + self.assertEqual(self.NAME, json_output.get('name')) + + json_output = json.loads(self.openstack( + 'port create -f json --network ' + self.NETWORK_NAME + ' ' + self.NAME + 'x' - ) - id2 = re.search(self.re_id, raw_output).group(1) - self.assertIsNotNone( - re.search(self.re_mac_address, raw_output).group(1), - ) - self.assertEqual( - self.NAME + 'x', - re.search(self.re_name, raw_output).group(1), - ) + )) + id2 = json_output.get('id') + self.assertIsNotNone(id2) + self.assertIsNotNone(json_output.get('mac_address')) + self.assertEqual(self.NAME + 'x', json_output.get('name')) # Clean up after ourselves raw_output = self.openstack('port delete ' + id1 + ' ' + id2) @@ -77,100 +58,92 @@ class PortTests(base.TestCase): def test_port_list(self): """Test create defaults, list, delete""" - raw_output = self.openstack( - 'port create --network ' + self.NETWORK_NAME + ' ' + self.NAME - ) - re_id1 = re.search(self.re_id, raw_output) - self.assertIsNotNone(re_id1) - id1 = re_id1.group(1) - mac1 = re.search(self.re_mac_address, raw_output).group(1) + json_output = json.loads(self.openstack( + 'port create -f json --network ' + self.NETWORK_NAME + ' ' + + self.NAME + )) + id1 = json_output.get('id') + self.assertIsNotNone(id1) + mac1 = json_output.get('mac_address') + self.assertIsNotNone(mac1) self.addCleanup(self.openstack, 'port delete ' + id1) - self.assertEqual( - self.NAME, - re.search(self.re_name, raw_output).group(1), - ) + self.assertEqual(self.NAME, json_output.get('name')) - raw_output = self.openstack( - 'port create ' + - '--network ' + self.NETWORK_NAME + ' ' + + json_output = json.loads(self.openstack( + 'port create -f json --network ' + self.NETWORK_NAME + ' ' + self.NAME + 'x' - ) - id2 = re.search(self.re_id, raw_output).group(1) - mac2 = re.search(self.re_mac_address, raw_output).group(1) + )) + id2 = json_output.get('id') + self.assertIsNotNone(id2) + mac2 = json_output.get('mac_address') + self.assertIsNotNone(mac2) self.addCleanup(self.openstack, 'port delete ' + id2) - self.assertEqual( - self.NAME + 'x', - re.search(self.re_name, raw_output).group(1), - ) + self.assertEqual(self.NAME + 'x', json_output.get('name')) # Test list - raw_output = self.openstack('port list') - self.assertIsNotNone(re.search("\|\s+" + id1 + "\s+\|", raw_output)) - self.assertIsNotNone(re.search("\|\s+" + id2 + "\s+\|", raw_output)) - self.assertIsNotNone(re.search("\|\s+" + mac1 + "\s+\|", raw_output)) - self.assertIsNotNone(re.search("\|\s+" + mac2 + "\s+\|", raw_output)) + json_output = json.loads(self.openstack( + 'port list -f json' + )) + item_map = {item.get('ID'): item.get('MAC Address') for item in + json_output} + self.assertIn(id1, item_map.keys()) + self.assertIn(id2, item_map.keys()) + self.assertIn(mac1, item_map.values()) + self.assertIn(mac2, item_map.values()) # Test list --long - raw_output = self.openstack('port list --long') - self.assertIsNotNone(re.search("\|\s+" + id1 + "\s+\|", raw_output)) - self.assertIsNotNone(re.search("\|\s+" + id2 + "\s+\|", raw_output)) + json_output = json.loads(self.openstack( + 'port list --long -f json' + )) + id_list = [item.get('ID') for item in json_output] + self.assertIn(id1, id_list) + self.assertIn(id2, id_list) # Test list --mac-address - raw_output = self.openstack('port list --mac-address ' + mac2) - self.assertIsNone(re.search("\|\s+" + id1 + "\s+\|", raw_output)) - self.assertIsNotNone(re.search("\|\s+" + id2 + "\s+\|", raw_output)) - self.assertIsNone(re.search("\|\s+" + mac1 + "\s+\|", raw_output)) - self.assertIsNotNone(re.search("\|\s+" + mac2 + "\s+\|", raw_output)) + json_output = json.loads(self.openstack( + 'port list -f json --mac-address ' + mac2 + )) + item_map = {item.get('ID'): item.get('MAC Address') for item in + json_output} + self.assertNotIn(id1, item_map.keys()) + self.assertIn(id2, item_map.keys()) + self.assertNotIn(mac1, item_map.values()) + self.assertIn(mac2, item_map.values()) def test_port_set(self): """Test create, set, show, delete""" - raw_output = self.openstack( - 'port create ' + + json_output = json.loads(self.openstack( + 'port create -f json ' + '--network ' + self.NETWORK_NAME + ' ' + '--description xyzpdq ' '--disable ' + self.NAME - ) - re_id = re.search(self.re_id, raw_output) - self.assertIsNotNone(re_id) - id = re_id.group(1) - self.addCleanup(self.openstack, 'port delete ' + id) - self.assertEqual( - self.NAME, - re.search(self.re_name, raw_output).group(1), - ) - self.assertEqual( - 'xyzpdq', - re.search(self.re_description, raw_output).group(1), - ) - self.assertEqual( - 'DOWN', - re.search(self.re_state, raw_output).group(1), - ) + )) + id1 = json_output.get('id') + self.addCleanup(self.openstack, 'port delete ' + id1) + self.assertEqual(self.NAME, json_output.get('name')) + self.assertEqual('xyzpdq', json_output.get('description')) + self.assertEqual('DOWN', json_output.get('admin_state_up')) raw_output = self.openstack( - 'port set ' + - '--enable ' + - self.NAME - ) + 'port set ' + '--enable ' + self.NAME) self.assertOutput('', raw_output) + json_output = json.loads(self.openstack( + 'port show -f json ' + self.NAME + )) + sg_id = json_output.get('security_group_ids') + + self.assertEqual(self.NAME, json_output.get('name')) + self.assertEqual('xyzpdq', json_output.get('description')) + self.assertEqual('UP', json_output.get('admin_state_up')) + self.assertIsNotNone(json_output.get('mac_address')) + raw_output = self.openstack( - 'port show ' + - self.NAME - ) - self.assertEqual( - self.NAME, - re.search(self.re_name, raw_output).group(1), - ) - self.assertEqual( - 'xyzpdq', - re.search(self.re_description, raw_output).group(1), - ) - self.assertEqual( - 'UP', - re.search(self.re_state, raw_output).group(1), - ) - self.assertIsNotNone( - re.search(self.re_mac_address, raw_output).group(1), - ) + 'port unset --security-group ' + sg_id + ' ' + id1) + self.assertOutput('', raw_output) + + json_output = json.loads(self.openstack( + 'port show -f json ' + self.NAME + )) + self.assertEqual('', json_output.get('security_group_ids')) diff --git a/openstackclient/tests/functional/network/v2/test_router.py b/openstackclient/tests/functional/network/v2/test_router.py index 789c382..443f68b 100644 --- a/openstackclient/tests/functional/network/v2/test_router.py +++ b/openstackclient/tests/functional/network/v2/test_router.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import json import uuid from openstackclient.tests.functional import base @@ -17,34 +18,210 @@ from openstackclient.tests.functional import base class RouterTests(base.TestCase): """Functional tests for router. """ - NAME = uuid.uuid4().hex - HEADERS = ['Name'] - FIELDS = ['name'] - - @classmethod - def setUpClass(cls): - opts = cls.get_opts(cls.FIELDS) - raw_output = cls.openstack('router create ' + cls.NAME + opts) - expected = cls.NAME + '\n' - cls.assertOutput(expected, raw_output) - - @classmethod - def tearDownClass(cls): - raw_output = cls.openstack('router delete ' + cls.NAME) - cls.assertOutput('', raw_output) + + def test_router_create_and_delete(self): + """Test create options, delete""" + name1 = uuid.uuid4().hex + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'router create -f json ' + + name1 + )) + self.assertEqual( + name1, + cmd_output["name"], + ) + cmd_output = json.loads(self.openstack( + 'router create -f json ' + + name2 + )) + self.assertEqual( + name2, + cmd_output["name"], + ) + + del_output = self.openstack( + 'router delete ' + name1 + ' ' + name2) + self.assertOutput('', del_output) def test_router_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('router list' + opts) - self.assertIn(self.NAME, raw_output) - - def test_router_set(self): - self.openstack('router set --disable ' + self.NAME) - opts = self.get_opts(['name', 'admin_state_up']) - raw_output = self.openstack('router show ' + self.NAME + opts) - self.assertEqual("DOWN\n" + self.NAME + "\n", raw_output) - - def test_router_show(self): - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack('router show ' + self.NAME + opts) - self.assertEqual(self.NAME + "\n", raw_output) + """Test create, list filter""" + # Get project IDs + cmd_output = json.loads(self.openstack('token issue -f json ')) + auth_project_id = cmd_output['project_id'] + + cmd_output = json.loads(self.openstack('project list -f json ')) + admin_project_id = None + demo_project_id = None + for p in cmd_output: + if p['Name'] == 'admin': + admin_project_id = p['ID'] + if p['Name'] == 'demo': + demo_project_id = p['ID'] + + # Verify assumptions: + # * admin and demo projects are present + # * demo and admin are distinct projects + # * tests run as admin + self.assertIsNotNone(admin_project_id) + self.assertIsNotNone(demo_project_id) + self.assertNotEqual(admin_project_id, demo_project_id) + self.assertEqual(admin_project_id, auth_project_id) + + name1 = uuid.uuid4().hex + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'router create -f json ' + + '--disable ' + + name1 + )) + self.assertEqual( + name1, + cmd_output["name"], + ) + self.assertEqual( + "DOWN", + cmd_output["admin_state_up"], + ) + self.assertEqual( + admin_project_id, + cmd_output["project_id"], + ) + cmd_output = json.loads(self.openstack( + 'router create -f json ' + + '--project ' + demo_project_id + + ' ' + name2 + )) + self.assertEqual( + name2, + cmd_output["name"], + ) + self.assertEqual( + "UP", + cmd_output["admin_state_up"], + ) + self.assertEqual( + demo_project_id, + cmd_output["project_id"], + ) + + # Test list --project + cmd_output = json.loads(self.openstack( + 'router list -f json ' + + '--project ' + demo_project_id + )) + names = [x["Name"] for x in cmd_output] + self.assertNotIn(name1, names) + self.assertIn(name2, names) + + # Test list --disable + cmd_output = json.loads(self.openstack( + 'router list -f json ' + + '--disable ' + )) + names = [x["Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertNotIn(name2, names) + + # Test list --name + cmd_output = json.loads(self.openstack( + 'router list -f json ' + + '--name ' + name1 + )) + names = [x["Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertNotIn(name2, names) + + # Test list --long + cmd_output = json.loads(self.openstack( + 'router list -f json ' + + '--long ' + )) + names = [x["Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertIn(name2, names) + + del_output = self.openstack( + 'router delete ' + name1 + ' ' + name2) + self.assertOutput('', del_output) + + def test_router_set_show_unset(self): + """Tests create router, set, unset, show, delete""" + + name = uuid.uuid4().hex + new_name = name + "_" + cmd_output = json.loads(self.openstack( + 'router create -f json ' + + '--description aaaa ' + + name + )) + self.assertEqual( + name, + cmd_output["name"], + ) + self.assertEqual( + 'aaaa', + cmd_output["description"], + ) + + # Test set --disable + cmd_output = self.openstack( + 'router set ' + + '--name ' + new_name + + ' --description bbbb ' + + '--disable ' + + name + ) + self.assertOutput('', cmd_output) + + cmd_output = json.loads(self.openstack( + 'router show -f json ' + + new_name + )) + self.assertEqual( + new_name, + cmd_output["name"], + ) + self.assertEqual( + 'bbbb', + cmd_output["description"], + ) + self.assertEqual( + 'DOWN', + cmd_output["admin_state_up"], + ) + + # Test set --ha --distributed + cmd_output = self.openstack( + 'router set ' + + '--distributed ' + + '--external-gateway public ' + + new_name + ) + self.assertOutput('', cmd_output) + + cmd_output = json.loads(self.openstack( + 'router show -f json ' + + new_name + )) + self.assertEqual( + True, + cmd_output["distributed"], + ) + self.assertIsNotNone(cmd_output["external_gateway_info"]) + + # Test unset + cmd_output = self.openstack( + 'router unset ' + + '--external-gateway ' + + new_name + ) + cmd_output = json.loads(self.openstack( + 'router show -f json ' + + new_name + )) + self.assertIsNone(cmd_output["external_gateway_info"]) + + del_output = self.openstack( + 'router delete ' + new_name) + self.assertOutput('', del_output) diff --git a/openstackclient/tests/functional/network/v2/test_subnet.py b/openstackclient/tests/functional/network/v2/test_subnet.py index 231671f..995a497 100644 --- a/openstackclient/tests/functional/network/v2/test_subnet.py +++ b/openstackclient/tests/functional/network/v2/test_subnet.py @@ -10,6 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. +import json +import random import uuid from openstackclient.tests.functional import base @@ -17,50 +19,242 @@ from openstackclient.tests.functional import base class SubnetTests(base.TestCase): """Functional tests for subnet. """ - NAME = uuid.uuid4().hex - NETWORK_NAME = uuid.uuid4().hex - HEADERS = ['Name'] - FIELDS = ['name'] @classmethod def setUpClass(cls): - # Create a network for the subnet. - cls.openstack('network create ' + cls.NETWORK_NAME) - opts = cls.get_opts(cls.FIELDS) - raw_output = cls.openstack( - 'subnet create --network ' + cls.NETWORK_NAME + - ' --subnet-range 10.10.10.0/24 ' + - cls.NAME + opts - ) - expected = cls.NAME + '\n' - cls.assertOutput(expected, raw_output) + # Create a network for the all subnet tests. + cls.NETWORK_NAME = uuid.uuid4().hex + cmd_output = json.loads(cls.openstack( + 'network create -f json ' + + cls.NETWORK_NAME + )) + # Get network_id for assertEqual + cls.NETWORK_ID = cmd_output["id"] @classmethod def tearDownClass(cls): - raw_output = cls.openstack('subnet delete ' + cls.NAME) - cls.assertOutput('', raw_output) raw_output = cls.openstack('network delete ' + cls.NETWORK_NAME) cls.assertOutput('', raw_output) + def test_subnet_create_and_delete(self): + """Test create, delete""" + name1 = uuid.uuid4().hex + cmd = ('subnet create -f json --network ' + + self.NETWORK_NAME + + ' --subnet-range') + cmd_output = self._subnet_create(cmd, name1) + self.assertEqual( + name1, + cmd_output["name"], + ) + self.assertEqual( + self.NETWORK_ID, + cmd_output["network_id"], + ) + + del_output = self.openstack( + 'subnet delete ' + name1) + self.assertOutput('', del_output) + def test_subnet_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('subnet list' + opts) - self.assertIn(self.NAME, raw_output) - - def test_subnet_set(self): - self.openstack('subnet set --no-dhcp ' + self.NAME) - opts = self.get_opts(['name', 'enable_dhcp']) - raw_output = self.openstack('subnet show ' + self.NAME + opts) - self.assertEqual("False\n" + self.NAME + "\n", raw_output) - - def test_subnet_set_service_type(self): - TYPE = 'network:floatingip_agent_gateway' - self.openstack('subnet set --service-type ' + TYPE + ' ' + self.NAME) - opts = self.get_opts(['name', 'service_types']) - raw_output = self.openstack('subnet show ' + self.NAME + opts) - self.assertEqual(self.NAME + "\n" + TYPE + "\n", raw_output) - - def test_subnet_show(self): - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack('subnet show ' + self.NAME + opts) - self.assertEqual(self.NAME + "\n", raw_output) + """Test create, list filter""" + name1 = uuid.uuid4().hex + name2 = uuid.uuid4().hex + cmd = ('subnet create -f json ' + + '--network ' + self.NETWORK_NAME + + ' --dhcp --subnet-range') + cmd_output = self._subnet_create(cmd, name1) + self.assertEqual( + name1, + cmd_output["name"], + ) + self.assertEqual( + True, + cmd_output["enable_dhcp"], + ) + self.assertEqual( + self.NETWORK_ID, + cmd_output["network_id"], + ) + self.assertEqual( + 4, + cmd_output["ip_version"], + ) + + cmd = ('subnet create -f json ' + + '--network ' + self.NETWORK_NAME + + ' --ip-version 6 --no-dhcp ' + + '--subnet-range') + cmd_output = self._subnet_create(cmd, name2, is_type_ipv4=False) + self.assertEqual( + name2, + cmd_output["name"], + ) + self.assertEqual( + False, + cmd_output["enable_dhcp"], + ) + self.assertEqual( + self.NETWORK_ID, + cmd_output["network_id"], + ) + self.assertEqual( + 6, + cmd_output["ip_version"], + ) + + # Test list --long + cmd_output = json.loads(self.openstack( + 'subnet list -f json ' + + '--long ' + )) + names = [x["Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertIn(name2, names) + + # Test list --name + cmd_output = json.loads(self.openstack( + 'subnet list -f json ' + + '--name ' + name1 + )) + names = [x["Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertNotIn(name2, names) + + # Test list --ip-version + cmd_output = json.loads(self.openstack( + 'subnet list -f json ' + + '--ip-version 6' + )) + names = [x["Name"] for x in cmd_output] + self.assertNotIn(name1, names) + self.assertIn(name2, names) + + # Test list --network + cmd_output = json.loads(self.openstack( + 'subnet list -f json ' + + '--network ' + self.NETWORK_ID + )) + names = [x["Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertIn(name2, names) + + # Test list --no-dhcp + cmd_output = json.loads(self.openstack( + 'subnet list -f json ' + + '--no-dhcp ' + )) + names = [x["Name"] for x in cmd_output] + self.assertNotIn(name1, names) + self.assertIn(name2, names) + + del_output = self.openstack( + 'subnet delete ' + name1 + ' ' + name2) + self.assertOutput('', del_output) + + def test_subnet_set_show_unset(self): + """Test create subnet, set, unset, show, delete""" + + name = uuid.uuid4().hex + new_name = name + "_" + cmd = ('subnet create -f json ' + + '--network ' + self.NETWORK_NAME + + ' --description aaaa --subnet-range') + cmd_output = self._subnet_create(cmd, name) + self.assertEqual( + name, + cmd_output["name"], + ) + self.assertEqual( + 'aaaa', + cmd_output["description"], + ) + + # Test set --no-dhcp --name --gateway --description + cmd_output = self.openstack( + 'subnet set ' + + '--name ' + new_name + + ' --description bbbb ' + + '--no-dhcp ' + + '--gateway 10.10.11.1 ' + + '--service-type network:floatingip_agent_gateway ' + + name + ) + self.assertOutput('', cmd_output) + + cmd_output = json.loads(self.openstack( + 'subnet show -f json ' + + new_name + )) + self.assertEqual( + new_name, + cmd_output["name"], + ) + self.assertEqual( + 'bbbb', + cmd_output["description"], + ) + self.assertEqual( + False, + cmd_output["enable_dhcp"], + ) + self.assertEqual( + '10.10.11.1', + cmd_output["gateway_ip"], + ) + self.assertEqual( + 'network:floatingip_agent_gateway', + cmd_output["service_types"], + ) + + # Test unset + cmd_output = self.openstack( + 'subnet unset ' + + '--service-type network:floatingip_agent_gateway ' + + new_name + ) + self.assertOutput('', cmd_output) + + cmd_output = json.loads(self.openstack( + 'subnet show -f json ' + + new_name + )) + self.assertEqual( + '', + cmd_output["service_types"], + ) + + del_output = self.openstack( + 'subnet delete ' + new_name) + self.assertOutput('', del_output) + + def _subnet_create(self, cmd, name, is_type_ipv4=True): + # Try random subnet range for subnet creating + # Because we can not determine ahead of time what subnets are already + # in use, possibly by another test running in parallel, try 4 times + for i in range(4): + # Make a random subnet + if is_type_ipv4: + subnet = ".".join(map( + str, + (random.randint(0, 223) for _ in range(3)) + )) + ".0/26" + else: + subnet = ":".join(map( + str, + (hex(random.randint(0, 65535))[2:] for _ in range(7)) + )) + ":0/112" + try: + cmd_output = json.loads(self.openstack( + cmd + ' ' + subnet + ' ' + + name + )) + except Exception: + if (i == 3): + # raise the exception at the last time + raise + pass + else: + # break and no longer retry if create sucessfully + break + return cmd_output diff --git a/openstackclient/tests/functional/volume/v1/test_qos.py b/openstackclient/tests/functional/volume/v1/test_qos.py index 770d5ac..434840f 100644 --- a/openstackclient/tests/functional/volume/v1/test_qos.py +++ b/openstackclient/tests/functional/volume/v1/test_qos.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import json import uuid from openstackclient.tests.functional.volume.v1 import common @@ -18,38 +19,103 @@ from openstackclient.tests.functional.volume.v1 import common class QosTests(common.BaseVolumeTests): """Functional tests for volume qos. """ - NAME = uuid.uuid4().hex - HEADERS = ['Name'] - FIELDS = ['id', 'name'] - ID = None - - @classmethod - def setUpClass(cls): - super(QosTests, cls).setUpClass() - opts = cls.get_opts(cls.FIELDS) - raw_output = cls.openstack('volume qos create ' + cls.NAME + opts) - cls.ID, name, rol = raw_output.split('\n') - cls.assertOutput(cls.NAME, name) - - @classmethod - def tearDownClass(cls): - raw_output = cls.openstack('volume qos delete ' + cls.ID) - cls.assertOutput('', raw_output) - - def test_volume_qos_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('volume qos list' + opts) - self.assertIn(self.NAME, raw_output) - - def test_volume_qos_show(self): - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack('volume qos show ' + self.ID + opts) - self.assertEqual(self.ID + "\n" + self.NAME + "\n", raw_output) - - def test_volume_qos_metadata(self): + def test_volume_qos_create_list(self): + """Test create, list, delete multiple""" + name1 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume qos create -f json ' + + name1 + )) + self.assertEqual( + name1, + cmd_output['name'] + ) + + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume qos create -f json ' + + name2 + )) + self.assertEqual( + name2, + cmd_output['name'] + ) + + # Test list + cmd_output = json.loads(self.openstack( + 'volume qos list -f json' + )) + names = [x["Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertIn(name2, names) + + # Test delete multiple + del_output = self.openstack('volume qos delete ' + name1 + ' ' + name2) + self.assertOutput('', del_output) + + def test_volume_qos_set_show_unset(self): + """Tests create volume qos, set, unset, show, delete""" + + name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume qos create -f json ' + + '--consumer front-end ' + '--property Alpha=a ' + + name + )) + self.addCleanup(self.openstack, 'volume qos delete ' + name) + self.assertEqual( + name, + cmd_output['name'] + ) + + self.assertEqual( + "front-end", + cmd_output['consumer'] + ) + + # Test volume qos set + raw_output = self.openstack( + 'volume qos set ' + + '--property Alpha=c ' + + '--property Beta=b ' + + name, + ) + self.assertOutput('', raw_output) + + # Test volume qos show + cmd_output = json.loads(self.openstack( + 'volume qos show -f json ' + + name + )) + self.assertEqual( + name, + cmd_output['name'] + ) + self.assertEqual( + "Alpha='c', Beta='b'", + cmd_output['properties'] + ) + + # Test volume qos unset raw_output = self.openstack( - 'volume qos set --property a=b --property c=d ' + self.ID) - self.assertEqual("", raw_output) - opts = self.get_opts(['name', 'specs']) - raw_output = self.openstack('volume qos show ' + self.ID + opts) - self.assertEqual(self.NAME + "\na='b', c='d'\n", raw_output) + 'volume qos unset ' + + '--property Alpha ' + + name, + ) + self.assertOutput('', raw_output) + + cmd_output = json.loads(self.openstack( + 'volume qos show -f json ' + + name + )) + self.assertEqual( + name, + cmd_output['name'] + ) + self.assertEqual( + "Beta='b'", + cmd_output['properties'] + ) + + # TODO(qiangjiahui): Add tests for associate and disassociate volume type diff --git a/openstackclient/tests/functional/volume/v1/test_volume.py b/openstackclient/tests/functional/volume/v1/test_volume.py index 5e4bcbe..3f04e07 100644 --- a/openstackclient/tests/functional/volume/v1/test_volume.py +++ b/openstackclient/tests/functional/volume/v1/test_volume.py @@ -10,6 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. +import json +import time import uuid from openstackclient.tests.functional.volume.v1 import common @@ -18,71 +20,190 @@ from openstackclient.tests.functional.volume.v1 import common class VolumeTests(common.BaseVolumeTests): """Functional tests for volume. """ - NAME = uuid.uuid4().hex - OTHER_NAME = uuid.uuid4().hex - HEADERS = ['"Display Name"'] - FIELDS = ['display_name'] - - @classmethod - def setUpClass(cls): - super(VolumeTests, cls).setUpClass() - opts = cls.get_opts(cls.FIELDS) - raw_output = cls.openstack('volume create --size 1 ' + cls.NAME + opts) - expected = cls.NAME + '\n' - cls.assertOutput(expected, raw_output) - - @classmethod - def tearDownClass(cls): - # Rename test - raw_output = cls.openstack( - 'volume set --name ' + cls.OTHER_NAME + ' ' + cls.NAME) - cls.assertOutput('', raw_output) - # Delete test - raw_output = cls.openstack('volume delete ' + cls.OTHER_NAME) - cls.assertOutput('', raw_output) + def test_volume_create_and_delete(self): + """Test create, delete multiple""" + name1 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume create -f json ' + + '--size 1 ' + + name1 + )) + self.assertEqual( + 1, + cmd_output["size"], + ) + + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume create -f json ' + + '--size 2 ' + + name2 + )) + self.assertEqual( + 2, + cmd_output["size"], + ) + + self.wait_for("volume", name1, "available") + self.wait_for("volume", name2, "available") + del_output = self.openstack('volume delete ' + name1 + ' ' + name2) + self.assertOutput('', del_output) def test_volume_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('volume list' + opts) - self.assertIn(self.NAME, raw_output) + """Test create, list filter""" + name1 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume create -f json ' + + '--size 1 ' + + name1 + )) + self.addCleanup(self.openstack, 'volume delete ' + name1) + self.assertEqual( + 1, + cmd_output["size"], + ) + self.wait_for("volume", name1, "available") + + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume create -f json ' + + '--size 2 ' + + name2 + )) + self.addCleanup(self.openstack, 'volume delete ' + name2) + self.assertEqual( + 2, + cmd_output["size"], + ) + self.wait_for("volume", name2, "available") + + # Test list + cmd_output = json.loads(self.openstack( + 'volume list -f json ' + )) + names = [x["Display Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertIn(name2, names) + + # Test list --long + cmd_output = json.loads(self.openstack( + 'volume list -f json --long' + )) + bootable = [x["Bootable"] for x in cmd_output] + self.assertIn('false', bootable) + + # Test list --name + cmd_output = json.loads(self.openstack( + 'volume list -f json ' + + '--name ' + name1 + )) + names = [x["Display Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertNotIn(name2, names) - def test_volume_show(self): - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack('volume show ' + self.NAME + opts) - self.assertEqual(self.NAME + "\n", raw_output) + def test_volume_set_and_unset(self): + """Tests create volume, set, unset, show, delete""" + name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume create -f json ' + + '--size 1 ' + + '--description aaaa ' + + '--property Alpha=a ' + + name + )) + self.assertEqual( + name, + cmd_output["display_name"], + ) + self.assertEqual( + 1, + cmd_output["size"], + ) + self.assertEqual( + 'aaaa', + cmd_output["display_description"], + ) + self.assertEqual( + "Alpha='a'", + cmd_output["properties"], + ) + self.assertEqual( + 'false', + cmd_output["bootable"], + ) + self.wait_for("volume", name, "available") - def test_volume_properties(self): + # Test volume set + new_name = uuid.uuid4().hex + self.addCleanup(self.openstack, 'volume delete ' + new_name) raw_output = self.openstack( - 'volume set --property a=b --property c=d ' + self.NAME) - self.assertEqual("", raw_output) - opts = self.get_opts(["properties"]) - raw_output = self.openstack('volume show ' + self.NAME + opts) - self.assertEqual("a='b', c='d'\n", raw_output) - - raw_output = self.openstack('volume unset --property a ' + self.NAME) - self.assertEqual("", raw_output) - raw_output = self.openstack('volume show ' + self.NAME + opts) - self.assertEqual("c='d'\n", raw_output) - - def test_volume_set(self): - self.openstack('volume set --description RAMAC ' + self.NAME) - opts = self.get_opts(["display_description", "display_name"]) - raw_output = self.openstack('volume show ' + self.NAME + opts) - self.assertEqual("RAMAC\n" + self.NAME + "\n", raw_output) - - def test_volume_set_size(self): - self.openstack('volume set --size 2 ' + self.NAME) - opts = self.get_opts(["display_name", "size"]) - raw_output = self.openstack('volume show ' + self.NAME + opts) - self.assertEqual(self.NAME + "\n2\n", raw_output) - - def test_volume_set_bootable(self): - self.openstack('volume set --bootable ' + self.NAME) - opts = self.get_opts(["bootable"]) - raw_output = self.openstack('volume show ' + self.NAME + opts) - self.assertEqual("true\n", raw_output) - - self.openstack('volume set --non-bootable ' + self.NAME) - opts = self.get_opts(["bootable"]) - raw_output = self.openstack('volume show ' + self.NAME + opts) - self.assertEqual("false\n", raw_output) + 'volume set ' + + '--name ' + new_name + + ' --size 2 ' + + '--description bbbb ' + + '--no-property ' + + '--property Beta=b ' + + '--property Gamma=c ' + + '--bootable ' + + name, + ) + self.assertOutput('', raw_output) + + cmd_output = json.loads(self.openstack( + 'volume show -f json ' + + new_name + )) + self.assertEqual( + new_name, + cmd_output["display_name"], + ) + self.assertEqual( + 2, + cmd_output["size"], + ) + self.assertEqual( + 'bbbb', + cmd_output["display_description"], + ) + self.assertEqual( + "Beta='b', Gamma='c'", + cmd_output["properties"], + ) + self.assertEqual( + 'true', + cmd_output["bootable"], + ) + + # Test volume unset + raw_output = self.openstack( + 'volume unset ' + + '--property Beta ' + + new_name, + ) + self.assertOutput('', raw_output) + + cmd_output = json.loads(self.openstack( + 'volume show -f json ' + + new_name + )) + self.assertEqual( + "Gamma='c'", + cmd_output["properties"], + ) + + def wait_for(self, check_type, check_name, desired_status, wait=120, + interval=5, failures=['ERROR']): + status = "notset" + total_sleep = 0 + opts = self.get_opts(['status']) + while total_sleep < wait: + status = self.openstack(check_type + ' show ' + check_name + opts) + status = status.rstrip() + print('Checking {} {} Waiting for {} current status: {}' + .format(check_type, check_name, desired_status, status)) + if status == desired_status: + break + self.assertNotIn(status, failures) + time.sleep(interval) + total_sleep += interval + self.assertEqual(desired_status, status) diff --git a/openstackclient/tests/functional/volume/v1/test_volume_type.py b/openstackclient/tests/functional/volume/v1/test_volume_type.py index 955759b..d184279 100644 --- a/openstackclient/tests/functional/volume/v1/test_volume_type.py +++ b/openstackclient/tests/functional/volume/v1/test_volume_type.py @@ -87,3 +87,74 @@ class VolumeTypeTests(common.BaseVolumeTests): time.sleep(5) raw_output = self.openstack(cmd) self.assertOutput('', raw_output) + + # NOTE: Add some basic funtional tests with the old format to + # make sure the command works properly, need to change + # these to new test format when beef up all tests for + # volume tye commands. + def test_encryption_type(self): + encryption_type = uuid.uuid4().hex + # test create new encryption type + opts = self.get_opts(['encryption']) + raw_output = self.openstack( + 'volume type create ' + '--encryption-provider LuksEncryptor ' + '--encryption-cipher aes-xts-plain64 ' + '--encryption-key-size 128 ' + '--encryption-control-location front-end ' + + encryption_type + opts) + expected = ["provider='LuksEncryptor'", + "cipher='aes-xts-plain64'", + "key_size='128'", + "control_location='front-end'"] + for attr in expected: + self.assertIn(attr, raw_output) + # test show encryption type + opts = self.get_opts(['encryption']) + raw_output = self.openstack( + 'volume type show --encryption-type ' + encryption_type + opts) + expected = ["provider='LuksEncryptor'", + "cipher='aes-xts-plain64'", + "key_size='128'", + "control_location='front-end'"] + for attr in expected: + self.assertIn(attr, raw_output) + # test list encryption type + opts = self.get_opts(['Encryption']) + raw_output = self.openstack( + 'volume type list --encryption-type ' + opts) + expected = ["provider='LuksEncryptor'", + "cipher='aes-xts-plain64'", + "key_size='128'", + "control_location='front-end'"] + for attr in expected: + self.assertIn(attr, raw_output) + # test set new encryption type + raw_output = self.openstack( + 'volume type set ' + '--encryption-provider LuksEncryptor ' + '--encryption-cipher aes-xts-plain64 ' + '--encryption-key-size 128 ' + '--encryption-control-location front-end ' + + self.NAME) + self.assertEqual('', raw_output) + opts = self.get_opts(['encryption']) + raw_output = self.openstack( + 'volume type show --encryption-type ' + self.NAME + opts) + expected = ["provider='LuksEncryptor'", + "cipher='aes-xts-plain64'", + "key_size='128'", + "control_location='front-end'"] + for attr in expected: + self.assertIn(attr, raw_output) + # test unset encryption type + raw_output = self.openstack( + 'volume type unset --encryption-type ' + self.NAME) + self.assertEqual('', raw_output) + opts = self.get_opts(['encryption']) + raw_output = self.openstack( + 'volume type show --encryption-type ' + self.NAME + opts) + self.assertEqual('\n', raw_output) + # test delete encryption type + raw_output = self.openstack('volume type delete ' + encryption_type) + self.assertEqual('', raw_output) diff --git a/openstackclient/tests/functional/volume/v2/test_qos.py b/openstackclient/tests/functional/volume/v2/test_qos.py index 1558c21..aee10dc 100644 --- a/openstackclient/tests/functional/volume/v2/test_qos.py +++ b/openstackclient/tests/functional/volume/v2/test_qos.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import json import uuid from openstackclient.tests.functional.volume.v2 import common @@ -18,45 +19,107 @@ from openstackclient.tests.functional.volume.v2 import common class QosTests(common.BaseVolumeTests): """Functional tests for volume qos. """ - NAME = uuid.uuid4().hex - HEADERS = ['Name'] - FIELDS = ['id', 'name'] - ID = None - - @classmethod - def setUpClass(cls): - super(QosTests, cls).setUpClass() - opts = cls.get_opts(cls.FIELDS) - raw_output = cls.openstack('volume qos create ' + cls.NAME + opts) - cls.ID, name, rol = raw_output.split('\n') - cls.assertOutput(cls.NAME, name) - - @classmethod - def tearDownClass(cls): - raw_output = cls.openstack('volume qos delete ' + cls.ID) - cls.assertOutput('', raw_output) - - def test_volume_qos_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('volume qos list' + opts) - self.assertIn(self.NAME, raw_output) - - def test_volume_qos_show(self): - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack('volume qos show ' + self.ID + opts) - self.assertEqual(self.ID + "\n" + self.NAME + "\n", raw_output) - - def test_volume_qos_metadata(self): + def test_volume_qos_create_delete_list(self): + """Test create, list, delete multiple""" + name1 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume qos create -f json ' + + name1 + )) + self.assertEqual( + name1, + cmd_output['name'] + ) + + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume qos create -f json ' + + name2 + )) + self.assertEqual( + name2, + cmd_output['name'] + ) + + # Test list + cmd_output = json.loads(self.openstack( + 'volume qos list -f json' + )) + names = [x["Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertIn(name2, names) + + # Test delete multiple + del_output = self.openstack('volume qos delete ' + name1 + ' ' + name2) + self.assertOutput('', del_output) + + def test_volume_qos_set_show_unset(self): + """Tests create volume qos, set, unset, show, delete""" + + name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume qos create -f json ' + + '--consumer front-end ' + '--property Alpha=a ' + + name + )) + self.addCleanup(self.openstack, 'volume qos delete ' + name) + self.assertEqual( + name, + cmd_output['name'] + ) + + self.assertEqual( + "front-end", + cmd_output['consumer'] + ) + self.assertEqual( + "Alpha='a'", + cmd_output['properties'] + ) + + # Test volume qos set raw_output = self.openstack( - 'volume qos set --property a=b --property c=d ' + self.ID) - self.assertEqual("", raw_output) - opts = self.get_opts(['name', 'properties']) - raw_output = self.openstack('volume qos show ' + self.ID + opts) - self.assertEqual(self.NAME + "\na='b', c='d'\n", raw_output) + 'volume qos set ' + + '--property Alpha=c ' + + '--property Beta=b ' + + name, + ) + self.assertOutput('', raw_output) + + # Test volume qos show + cmd_output = json.loads(self.openstack( + 'volume qos show -f json ' + + name + )) + self.assertEqual( + name, + cmd_output['name'] + ) + self.assertEqual( + "Alpha='c', Beta='b'", + cmd_output['properties'] + ) + # Test volume qos unset raw_output = self.openstack( - 'volume qos unset --property a ' + self.ID) - self.assertEqual("", raw_output) - opts = self.get_opts(['name', 'properties']) - raw_output = self.openstack('volume qos show ' + self.ID + opts) - self.assertEqual(self.NAME + "\nc='d'\n", raw_output) + 'volume qos unset ' + + '--property Alpha ' + + name, + ) + self.assertOutput('', raw_output) + + cmd_output = json.loads(self.openstack( + 'volume qos show -f json ' + + name + )) + self.assertEqual( + name, + cmd_output['name'] + ) + self.assertEqual( + "Beta='b'", + cmd_output['properties'] + ) + + # TODO(qiangjiahui): Add tests for associate and disassociate volume type diff --git a/openstackclient/tests/functional/volume/v2/test_volume.py b/openstackclient/tests/functional/volume/v2/test_volume.py index 203ca81..ce98236 100644 --- a/openstackclient/tests/functional/volume/v2/test_volume.py +++ b/openstackclient/tests/functional/volume/v2/test_volume.py @@ -104,7 +104,7 @@ class VolumeTests(common.BaseVolumeTests): # TODO(qiangjiahui): Add project option to filter tests when we can # specify volume with project - def test_volume_set(self): + def test_volume_set_and_unset(self): """Tests create volume, set, unset, show, delete""" name = uuid.uuid4().hex new_name = name + "_" @@ -144,8 +144,11 @@ class VolumeTests(common.BaseVolumeTests): '--name ' + new_name + ' --size 2 ' + '--description bbbb ' + - '--property Alpha=c ' + + '--no-property ' + '--property Beta=b ' + + '--property Gamma=c ' + + '--image-property a=b ' + + '--image-property c=d ' + '--bootable ' + name, ) @@ -168,10 +171,14 @@ class VolumeTests(common.BaseVolumeTests): cmd_output["description"], ) self.assertEqual( - "Alpha='c', Beta='b'", + "Beta='b', Gamma='c'", cmd_output["properties"], ) self.assertEqual( + {'a': 'b', 'c': 'd'}, + cmd_output["volume_image_metadata"], + ) + self.assertEqual( 'true', cmd_output["bootable"], ) @@ -179,7 +186,8 @@ class VolumeTests(common.BaseVolumeTests): # Test volume unset raw_output = self.openstack( 'volume unset ' + - '--property Alpha ' + + '--property Beta ' + + '--image-property a ' + new_name, ) self.assertOutput('', raw_output) @@ -189,9 +197,13 @@ class VolumeTests(common.BaseVolumeTests): new_name )) self.assertEqual( - "Beta='b'", + "Gamma='c'", cmd_output["properties"], ) + self.assertEqual( + {'c': 'd'}, + cmd_output["volume_image_metadata"], + ) def test_volume_snapshot(self): """Tests volume create from snapshot""" diff --git a/openstackclient/tests/functional/volume/v2/test_volume_type.py b/openstackclient/tests/functional/volume/v2/test_volume_type.py index b4df5b2..a5d0a76 100644 --- a/openstackclient/tests/functional/volume/v2/test_volume_type.py +++ b/openstackclient/tests/functional/volume/v2/test_volume_type.py @@ -102,3 +102,90 @@ class VolumeTypeTests(common.BaseVolumeTests): time.sleep(5) raw_output = self.openstack(cmd) self.assertOutput('', raw_output) + + # NOTE: Add some basic funtional tests with the old format to + # make sure the command works properly, need to change + # these to new test format when beef up all tests for + # volume tye commands. + def test_encryption_type(self): + encryption_type = uuid.uuid4().hex + # test create new encryption type + opts = self.get_opts(['encryption']) + raw_output = self.openstack( + 'volume type create ' + '--encryption-provider LuksEncryptor ' + '--encryption-cipher aes-xts-plain64 ' + '--encryption-key-size 128 ' + '--encryption-control-location front-end ' + + encryption_type + opts) + expected = ["provider='LuksEncryptor'", + "cipher='aes-xts-plain64'", + "key_size='128'", + "control_location='front-end'"] + for attr in expected: + self.assertIn(attr, raw_output) + # test show encryption type + opts = self.get_opts(['encryption']) + raw_output = self.openstack( + 'volume type show --encryption-type ' + encryption_type + opts) + expected = ["provider='LuksEncryptor'", + "cipher='aes-xts-plain64'", + "key_size='128'", + "control_location='front-end'"] + for attr in expected: + self.assertIn(attr, raw_output) + # test list encryption type + opts = self.get_opts(['Encryption']) + raw_output = self.openstack( + 'volume type list --encryption-type ' + opts) + expected = ["provider='LuksEncryptor'", + "cipher='aes-xts-plain64'", + "key_size='128'", + "control_location='front-end'"] + for attr in expected: + self.assertIn(attr, raw_output) + # test set existing encryption type + raw_output = self.openstack( + 'volume type set ' + '--encryption-key-size 256 ' + '--encryption-control-location back-end ' + + encryption_type) + self.assertEqual('', raw_output) + opts = self.get_opts(['encryption']) + raw_output = self.openstack( + 'volume type show --encryption-type ' + encryption_type + opts) + expected = ["provider='LuksEncryptor'", + "cipher='aes-xts-plain64'", + "key_size='256'", + "control_location='back-end'"] + for attr in expected: + self.assertIn(attr, raw_output) + # test set new encryption type + raw_output = self.openstack( + 'volume type set ' + '--encryption-provider LuksEncryptor ' + '--encryption-cipher aes-xts-plain64 ' + '--encryption-key-size 128 ' + '--encryption-control-location front-end ' + + self.NAME) + self.assertEqual('', raw_output) + opts = self.get_opts(['encryption']) + raw_output = self.openstack( + 'volume type show --encryption-type ' + self.NAME + opts) + expected = ["provider='LuksEncryptor'", + "cipher='aes-xts-plain64'", + "key_size='128'", + "control_location='front-end'"] + for attr in expected: + self.assertIn(attr, raw_output) + # test unset encryption type + raw_output = self.openstack( + 'volume type unset --encryption-type ' + self.NAME) + self.assertEqual('', raw_output) + opts = self.get_opts(['encryption']) + raw_output = self.openstack( + 'volume type show --encryption-type ' + self.NAME + opts) + self.assertEqual('\n', raw_output) + # test delete encryption type + raw_output = self.openstack('volume type delete ' + encryption_type) + self.assertEqual('', raw_output) diff --git a/openstackclient/tests/functional/volume/v3/__init__.py b/openstackclient/tests/functional/volume/v3/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/openstackclient/tests/functional/volume/v3/__init__.py diff --git a/openstackclient/tests/functional/volume/v3/common.py b/openstackclient/tests/functional/volume/v3/common.py new file mode 100644 index 0000000..57a62df --- /dev/null +++ b/openstackclient/tests/functional/volume/v3/common.py @@ -0,0 +1,23 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import os + +from openstackclient.tests.functional import base + + +class BaseVolumeTests(base.TestCase): + """Base class for Volume functional tests. """ + + @classmethod + def setUpClass(cls): + os.environ['OS_VOLUME_API_VERSION'] = '3' diff --git a/openstackclient/tests/functional/volume/v3/test_qos.py b/openstackclient/tests/functional/volume/v3/test_qos.py new file mode 100644 index 0000000..46965ce --- /dev/null +++ b/openstackclient/tests/functional/volume/v3/test_qos.py @@ -0,0 +1,23 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# 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 openstackclient.tests.functional.volume.v2 import test_qos as v2 +import os + + +class QosTests(v2.QosTests): + """Functional tests for volume qos. """ + + @classmethod + def setUpClass(cls): + super(QosTests, cls).setUpClass() + os.environ['OS_VOLUME_API_VERSION'] = '3' diff --git a/openstackclient/tests/functional/volume/v3/test_snapshot.py b/openstackclient/tests/functional/volume/v3/test_snapshot.py new file mode 100644 index 0000000..bf05b9d --- /dev/null +++ b/openstackclient/tests/functional/volume/v3/test_snapshot.py @@ -0,0 +1,23 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# 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 openstackclient.tests.functional.volume.v2 import test_snapshot as v2 +import os + + +class VolumeSnapshotTests(v2.VolumeSnapshotTests): + """Functional tests for volume snapshot. """ + + @classmethod + def setUpClass(cls): + super(VolumeSnapshotTests, cls).setUpClass() + os.environ['OS_VOLUME_API_VERSION'] = '3' diff --git a/openstackclient/tests/functional/volume/v3/test_transfer_request.py b/openstackclient/tests/functional/volume/v3/test_transfer_request.py new file mode 100644 index 0000000..7b54dd2 --- /dev/null +++ b/openstackclient/tests/functional/volume/v3/test_transfer_request.py @@ -0,0 +1,24 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# 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 openstackclient.tests.functional.volume.v2 import test_transfer_request \ + as v2 +import os + + +class TransferRequestTests(v2.TransferRequestTests): + """Functional tests for transfer request. """ + + @classmethod + def setUpClass(cls): + super(TransferRequestTests, cls).setUpClass() + os.environ['OS_VOLUME_API_VERSION'] = '3' diff --git a/openstackclient/tests/functional/volume/v3/test_volume.py b/openstackclient/tests/functional/volume/v3/test_volume.py new file mode 100644 index 0000000..333826d --- /dev/null +++ b/openstackclient/tests/functional/volume/v3/test_volume.py @@ -0,0 +1,23 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# 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 openstackclient.tests.functional.volume.v2 import test_volume as v2 +import os + + +class VolumeTests(v2.VolumeTests): + """Functional tests for volume. """ + + @classmethod + def setUpClass(cls): + super(VolumeTests, cls).setUpClass() + os.environ['OS_VOLUME_API_VERSION'] = '3' diff --git a/openstackclient/tests/functional/volume/v3/test_volume_type.py b/openstackclient/tests/functional/volume/v3/test_volume_type.py new file mode 100644 index 0000000..f10e64b --- /dev/null +++ b/openstackclient/tests/functional/volume/v3/test_volume_type.py @@ -0,0 +1,23 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# 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 openstackclient.tests.functional.volume.v2 import test_volume_type as v2 +import os + + +class VolumeTypeTests(v2.VolumeTypeTests): + """Functional tests for volume type. """ + + @classmethod + def setUpClass(cls): + super(VolumeTypeTests, cls).setUpClass() + os.environ['OS_VOLUME_API_VERSION'] = '3' diff --git a/openstackclient/tests/unit/common/test_module.py b/openstackclient/tests/unit/common/test_module.py index eb54dbe..2491d63 100644 --- a/openstackclient/tests/unit/common/test_module.py +++ b/openstackclient/tests/unit/common/test_module.py @@ -26,19 +26,28 @@ from openstackclient.tests.unit import utils # currently == '*client*' module_name_1 = 'fakeclient' module_version_1 = '0.1.2' -MODULE_1 = { - '__version__': module_version_1, -} module_name_2 = 'zlib' module_version_2 = '1.1' -MODULE_2 = { - '__version__': module_version_2, -} + +# module_3 match openstacksdk +module_name_3 = 'openstack' +module_version_3 = '0.9.13' + +# module_4 match sub module of fakeclient +module_name_4 = 'fakeclient.submodule' +module_version_4 = '0.2.2' + +# module_5 match private module +module_name_5 = '_private_module.lib' +module_version_5 = '0.0.1' MODULES = { module_name_1: fakes.FakeModule(module_name_1, module_version_1), module_name_2: fakes.FakeModule(module_name_2, module_version_2), + module_name_3: fakes.FakeModule(module_name_3, module_version_3), + module_name_4: fakes.FakeModule(module_name_4, module_version_4), + module_name_5: fakes.FakeModule(module_name_5, module_version_5), } @@ -79,6 +88,41 @@ class TestCommandList(utils.TestCommand): self.assertEqual(datalist, tuple(data)) + def test_command_list_with_group_not_found(self): + arglist = [ + '--group', 'not_exist', + ] + verifylist = [ + ('group', 'not_exist'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = ('Command Group', 'Commands') + self.assertEqual(collist, columns) + self.assertEqual([], data) + + def test_command_list_with_group(self): + arglist = [ + '--group', 'common', + ] + verifylist = [ + ('group', 'common'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = ('Command Group', 'Commands') + self.assertEqual(collist, columns) + datalist = (( + 'openstack.common', + 'limits show\nextension list' + ),) + + self.assertEqual(datalist, tuple(data)) + @mock.patch.dict( 'openstackclient.common.module.sys.modules', @@ -105,9 +149,18 @@ class TestModuleList(utils.TestCommand): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - # Additional modules may be present, just check our additions + # Output xxxclient and openstacksdk, but not regular module, like: zlib self.assertIn(module_name_1, columns) self.assertIn(module_version_1, data) + self.assertNotIn(module_name_2, columns) + self.assertNotIn(module_version_2, data) + self.assertIn(module_name_3, columns) + self.assertIn(module_version_3, data) + # Filter sub and private modules + self.assertNotIn(module_name_4, columns) + self.assertNotIn(module_version_4, data) + self.assertNotIn(module_name_5, columns) + self.assertNotIn(module_version_5, data) def test_module_list_all(self): arglist = [ @@ -123,8 +176,15 @@ class TestModuleList(utils.TestCommand): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - # Additional modules may be present, just check our additions + # Output xxxclient, openstacksdk and regular module, like: zlib self.assertIn(module_name_1, columns) - self.assertIn(module_name_2, columns) self.assertIn(module_version_1, data) + self.assertIn(module_name_2, columns) self.assertIn(module_version_2, data) + self.assertIn(module_name_3, columns) + self.assertIn(module_version_3, data) + # Filter sub and private modules + self.assertNotIn(module_name_4, columns) + self.assertNotIn(module_version_4, data) + self.assertNotIn(module_name_5, columns) + self.assertNotIn(module_version_5, data) diff --git a/openstackclient/tests/unit/common/test_quota.py b/openstackclient/tests/unit/common/test_quota.py index 244d74d..7dd2337 100644 --- a/openstackclient/tests/unit/common/test_quota.py +++ b/openstackclient/tests/unit/common/test_quota.py @@ -13,8 +13,6 @@ import copy import mock -from openstack.network.v2 import quota as _quota - from openstackclient.common import quota from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes from openstackclient.tests.unit import fakes @@ -284,32 +282,27 @@ class TestQuotaSet(TestQuota): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # TODO(huanxuan): Remove this if condition once the fixed - # SDK Quota class is the minimum required version. - # This is expected to be SDK release 0.9.13 - if not hasattr(_quota.Quota, 'allow_get'): - # Just run this when sdk <= 0.9.10 - result = self.cmd.take_action(parsed_args) - kwargs = { - 'subnet': network_fakes.QUOTA['subnet'], - 'network': network_fakes.QUOTA['network'], - 'floatingip': network_fakes.QUOTA['floatingip'], - 'subnetpool': network_fakes.QUOTA['subnetpool'], - 'security_group_rule': - network_fakes.QUOTA['security_group_rule'], - 'security_group': network_fakes.QUOTA['security_group'], - 'router': network_fakes.QUOTA['router'], - 'rbac_policy': network_fakes.QUOTA['rbac_policy'], - 'port': network_fakes.QUOTA['port'], - 'vip': network_fakes.QUOTA['vip'], - 'healthmonitor': network_fakes.QUOTA['healthmonitor'], - 'l7policy': network_fakes.QUOTA['l7policy'], - } - self.network_mock.update_quota.assert_called_once_with( - identity_fakes.project_id, - **kwargs - ) - self.assertIsNone(result) + result = self.cmd.take_action(parsed_args) + kwargs = { + 'subnet': network_fakes.QUOTA['subnet'], + 'network': network_fakes.QUOTA['network'], + 'floatingip': network_fakes.QUOTA['floatingip'], + 'subnetpool': network_fakes.QUOTA['subnetpool'], + 'security_group_rule': + network_fakes.QUOTA['security_group_rule'], + 'security_group': network_fakes.QUOTA['security_group'], + 'router': network_fakes.QUOTA['router'], + 'rbac_policy': network_fakes.QUOTA['rbac_policy'], + 'port': network_fakes.QUOTA['port'], + 'vip': network_fakes.QUOTA['vip'], + 'healthmonitor': network_fakes.QUOTA['healthmonitor'], + 'l7policy': network_fakes.QUOTA['l7policy'], + } + self.network_mock.update_quota.assert_called_once_with( + identity_fakes.project_id, + **kwargs + ) + self.assertIsNone(result) def test_quota_set_with_class(self): arglist = [ @@ -483,20 +476,15 @@ class TestQuotaShow(TestQuota): parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # TODO(huanxuan): Remove this if condition once the fixed - # SDK QuotaDefault class is the minimum required version. - # This is expected to be SDK release 0.9.13 - if not hasattr(_quota.QuotaDefault, 'project'): - # Just run this when sdk <= 0.9.10 - self.cmd.take_action(parsed_args) - - self.quotas_mock.defaults.assert_called_once_with( - identity_fakes.project_id) - self.volume_quotas_mock.defaults.assert_called_once_with( - identity_fakes.project_id) - self.network.get_quota_default.assert_called_once_with( - identity_fakes.project_id) - self.assertNotCalled(self.network.get_quota) + self.cmd.take_action(parsed_args) + + self.quotas_mock.defaults.assert_called_once_with( + identity_fakes.project_id) + self.volume_quotas_mock.defaults.assert_called_once_with( + identity_fakes.project_id) + self.network.get_quota_default.assert_called_once_with( + identity_fakes.project_id) + self.assertNotCalled(self.network.get_quota) def test_quota_show_with_class(self): arglist = [ diff --git a/openstackclient/tests/unit/compute/v2/test_console.py b/openstackclient/tests/unit/compute/v2/test_console.py index d53d241..3c708aa 100644 --- a/openstackclient/tests/unit/compute/v2/test_console.py +++ b/openstackclient/tests/unit/compute/v2/test_console.py @@ -35,11 +35,7 @@ class TestConsoleUrlShow(TestConsole): 'protocol': 'fake_protocol', 'type': 'fake_type'}} methods = { - 'get_vnc_console': fake_console_data, - 'get_spice_console': fake_console_data, - 'get_serial_console': fake_console_data, - 'get_rdp_console': fake_console_data, - 'get_mks_console': fake_console_data, + 'get_console_url': fake_console_data } self.fake_server = compute_fakes.FakeServer.create_one_server( methods=methods) @@ -68,7 +64,7 @@ class TestConsoleUrlShow(TestConsole): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.fake_server.get_vnc_console.assert_called_once_with('novnc') + self.fake_server.get_console_url.assert_called_once_with('novnc') self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -83,7 +79,7 @@ class TestConsoleUrlShow(TestConsole): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.fake_server.get_vnc_console.assert_called_once_with('novnc') + self.fake_server.get_console_url.assert_called_once_with('novnc') self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -98,7 +94,7 @@ class TestConsoleUrlShow(TestConsole): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.fake_server.get_vnc_console.assert_called_once_with('xvpvnc') + self.fake_server.get_console_url.assert_called_once_with('xvpvnc') self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -113,14 +109,14 @@ class TestConsoleUrlShow(TestConsole): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.fake_server.get_spice_console.assert_called_once_with( + self.fake_server.get_console_url.assert_called_once_with( 'spice-html5') self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) def test_console_url_show_compatible(self): methods = { - 'get_vnc_console': {'console': {'url': 'http://localhost', + 'get_console_url': {'console': {'url': 'http://localhost', 'type': 'fake_type'}}, } old_fake_server = compute_fakes.FakeServer.create_one_server( @@ -130,8 +126,8 @@ class TestConsoleUrlShow(TestConsole): 'url', ) old_data = ( - methods['get_vnc_console']['console']['type'], - methods['get_vnc_console']['console']['url'] + methods['get_console_url']['console']['type'], + methods['get_console_url']['console']['url'] ) arglist = [ 'foo_vm', @@ -144,7 +140,7 @@ class TestConsoleUrlShow(TestConsole): with mock.patch.object(self.servers_mock, 'get', return_value=old_fake_server): columns, data = self.cmd.take_action(parsed_args) - old_fake_server.get_vnc_console.assert_called_once_with('novnc') + old_fake_server.get_console_url.assert_called_once_with('novnc') self.assertEqual(old_columns, columns) self.assertEqual(old_data, data) @@ -159,7 +155,7 @@ class TestConsoleUrlShow(TestConsole): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.fake_server.get_rdp_console.assert_called_once_with( + self.fake_server.get_console_url.assert_called_once_with( 'rdp-html5') self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -175,7 +171,7 @@ class TestConsoleUrlShow(TestConsole): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.fake_server.get_serial_console.assert_called_once_with( + self.fake_server.get_console_url.assert_called_once_with( 'serial') self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -191,6 +187,6 @@ class TestConsoleUrlShow(TestConsole): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.fake_server.get_mks_console.assert_called_once_with() + self.fake_server.get_console_url.assert_called_once_with('webmks') self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) diff --git a/openstackclient/tests/unit/compute/v2/test_flavor.py b/openstackclient/tests/unit/compute/v2/test_flavor.py index 632fcda..4cdbb25 100644 --- a/openstackclient/tests/unit/compute/v2/test_flavor.py +++ b/openstackclient/tests/unit/compute/v2/test_flavor.py @@ -528,6 +528,23 @@ class TestFlavorSet(TestFlavor): self.flavor.set_keys.assert_called_with({'FOO': '"B A R"'}) self.assertIsNone(result) + def test_flavor_set_no_property(self): + arglist = [ + '--no-property', + 'baremetal' + ] + verifylist = [ + ('no_property', True), + ('flavor', 'baremetal') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.flavors_mock.find.assert_called_with(name=parsed_args.flavor, + is_public=None) + self.flavor.unset_keys.assert_called_with(['property']) + self.assertIsNone(result) + def test_flavor_set_project(self): arglist = [ '--project', self.project.id, diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 4cac990..249902b 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -467,6 +467,152 @@ class TestServerCreate(TestServer): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist(), data) + def test_server_create_with_auto_network(self): + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--nic', 'auto', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('nic', ['auto']), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = dict( + meta=None, + files={}, + reservation_id=None, + min_count=1, + max_count=1, + security_groups=[], + userdata=None, + key_name=None, + availability_zone=None, + block_device_mapping_v2=[], + nics='auto', + scheduler_hints={}, + config_drive=None, + ) + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + self.image, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + + def test_server_create_with_none_network(self): + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--nic', 'none', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('nic', ['none']), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = dict( + meta=None, + files={}, + reservation_id=None, + min_count=1, + max_count=1, + security_groups=[], + userdata=None, + key_name=None, + availability_zone=None, + block_device_mapping_v2=[], + nics='none', + scheduler_hints={}, + config_drive=None, + ) + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + self.image, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + + def test_server_create_with_conflict_network_options(self): + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--nic', 'none', + '--nic', 'auto', + '--nic', 'port-id=port1', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('nic', ['none', 'auto', 'port-id=port1']), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + get_endpoints = mock.Mock() + get_endpoints.return_value = {'network': []} + self.app.client_manager.auth_ref = mock.Mock() + self.app.client_manager.auth_ref.service_catalog = mock.Mock() + self.app.client_manager.auth_ref.service_catalog.get_endpoints = ( + get_endpoints) + + find_port = mock.Mock() + network_client = self.app.client_manager.network + network_client.find_port = find_port + port_resource = mock.Mock() + port_resource.id = 'port1_uuid' + find_port.return_value = port_resource + + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) + self.assertNotCalled(self.servers_mock.create) + + def test_server_create_with_invalid_network_options(self): + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--nic', 'abcdefgh', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('nic', ['abcdefgh']), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) + self.assertNotCalled(self.servers_mock.create) + @mock.patch.object(common_utils, 'wait_for_status', return_value=True) def test_server_create_with_wait_ok(self, mock_wait_for_status): arglist = [ diff --git a/openstackclient/tests/unit/compute/v2/test_server_group.py b/openstackclient/tests/unit/compute/v2/test_server_group.py index d474f41..088497d 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_group.py +++ b/openstackclient/tests/unit/compute/v2/test_server_group.py @@ -63,54 +63,23 @@ class TestServerGroupCreate(TestServerGroup): def test_server_group_create(self): arglist = [ - '--policy', 'affinity', + '--policy', 'anti-affinity', 'affinity_group', ] verifylist = [ - ('policy', ['affinity']), + ('policy', 'anti-affinity'), ('name', 'affinity_group'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.server_groups_mock.create.assert_called_once_with( name=parsed_args.name, - policies=parsed_args.policy, + policies=[parsed_args.policy], ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) - def test_server_group_create_with_multiple_policies(self): - arglist = [ - '--policy', 'affinity', - '--policy', 'soft-affinity', - 'affinity_group', - ] - verifylist = [ - ('policy', ['affinity', 'soft-affinity']), - ('name', 'affinity_group'), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) - self.server_groups_mock.create.assert_called_once_with( - name=parsed_args.name, - policies=parsed_args.policy, - ) - - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) - - def test_server_group_create_no_policy(self): - arglist = [ - 'affinity_group', - ] - verifylist = None - self.assertRaises(tests_utils.ParserException, - self.check_parser, - self.cmd, - arglist, - verifylist) - class TestServerGroupDelete(TestServerGroup): diff --git a/openstackclient/tests/unit/fakes.py b/openstackclient/tests/unit/fakes.py index 626b466..f28f910 100644 --- a/openstackclient/tests/unit/fakes.py +++ b/openstackclient/tests/unit/fakes.py @@ -161,6 +161,9 @@ class FakeModule(object): def __init__(self, name, version): self.name = name self.__version__ = version + # Workaround for openstacksdk case + self.version = mock.Mock() + self.version.__version__ = version class FakeResource(object): diff --git a/openstackclient/tests/unit/identity/v3/test_group.py b/openstackclient/tests/unit/identity/v3/test_group.py index 8558de9..00bd217 100644 --- a/openstackclient/tests/unit/identity/v3/test_group.py +++ b/openstackclient/tests/unit/identity/v3/test_group.py @@ -70,6 +70,20 @@ class TestGroupAddUser(TestGroup): self.user.id, self.group.id) self.assertIsNone(result) + def test_group_add_user_with_error(self): + self.users_mock.add_to_group.side_effect = exceptions.CommandError() + arglist = [ + self.group.name, + self.user.name, + ] + verifylist = [ + ('group', self.group.name), + ('user', self.user.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) + class TestGroupCheckUser(TestGroup): @@ -460,6 +474,21 @@ class TestGroupRemoveUser(TestGroup): self.user.id, self.group.id) self.assertIsNone(result) + def test_group_remove_user_with_error(self): + self.users_mock.remove_from_group.side_effect = ( + exceptions.CommandError()) + arglist = [ + self.group.id, + self.user.id, + ] + verifylist = [ + ('group', self.group.id), + ('user', self.user.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) + class TestGroupSet(TestGroup): diff --git a/openstackclient/tests/unit/identity/v3/test_project.py b/openstackclient/tests/unit/identity/v3/test_project.py index 2b89809..b99eaf8 100644 --- a/openstackclient/tests/unit/identity/v3/test_project.py +++ b/openstackclient/tests/unit/identity/v3/test_project.py @@ -14,6 +14,7 @@ # import mock +from mock import call from osc_lib import exceptions from osc_lib import utils @@ -763,8 +764,6 @@ class TestProjectShow(TestProject): def test_project_show(self): - self.projects_mock.get.side_effect = [Exception("Not found"), - self.project] self.projects_mock.get.return_value = self.project arglist = [ @@ -790,11 +789,7 @@ class TestProjectShow(TestProject): # data to be shown. columns, data = self.cmd.take_action(parsed_args) - self.projects_mock.get.assert_called_with( - self.project.id, - parents_as_list=False, - subtree_as_list=False, - ) + self.projects_mock.get.assert_called_once_with(self.project.id) collist = ( 'description', @@ -824,8 +819,6 @@ class TestProjectShow(TestProject): 'parents': [{'project': {'id': self.project.parent_id}}] } ) - self.projects_mock.get.side_effect = [Exception("Not found"), - self.project] self.projects_mock.get.return_value = self.project arglist = [ @@ -849,11 +842,12 @@ class TestProjectShow(TestProject): } columns, data = self.cmd.take_action(parsed_args) - self.projects_mock.get.assert_called_with( - self.project.id, - parents_as_list=True, - subtree_as_list=False, - ) + + self.projects_mock.get.assert_has_calls([call(self.project.id), + call(self.project.id, + parents_as_list=True, + subtree_as_list=False, + )]) collist = ( 'description', @@ -885,8 +879,6 @@ class TestProjectShow(TestProject): 'subtree': [{'project': {'id': 'children-id'}}] } ) - self.projects_mock.get.side_effect = [Exception("Not found"), - self.project] self.projects_mock.get.return_value = self.project arglist = [ @@ -910,11 +902,11 @@ class TestProjectShow(TestProject): } columns, data = self.cmd.take_action(parsed_args) - self.projects_mock.get.assert_called_with( - self.project.id, - parents_as_list=False, - subtree_as_list=True, - ) + self.projects_mock.get.assert_has_calls([call(self.project.id), + call(self.project.id, + parents_as_list=False, + subtree_as_list=True, + )]) collist = ( 'description', @@ -947,8 +939,6 @@ class TestProjectShow(TestProject): 'subtree': [{'project': {'id': 'children-id'}}] } ) - self.projects_mock.get.side_effect = [Exception("Not found"), - self.project] self.projects_mock.get.return_value = self.project arglist = [ @@ -973,11 +963,11 @@ class TestProjectShow(TestProject): } columns, data = self.cmd.take_action(parsed_args) - self.projects_mock.get.assert_called_with( - self.project.id, - parents_as_list=True, - subtree_as_list=True, - ) + self.projects_mock.get.assert_has_calls([call(self.project.id), + call(self.project.id, + parents_as_list=True, + subtree_as_list=True, + )]) collist = ( 'description', diff --git a/openstackclient/tests/unit/identity/v3/test_role.py b/openstackclient/tests/unit/identity/v3/test_role.py index c0b68bd..39dbd24 100644 --- a/openstackclient/tests/unit/identity/v3/test_role.py +++ b/openstackclient/tests/unit/identity/v3/test_role.py @@ -273,6 +273,22 @@ class TestRoleAdd(TestRole): ) self.assertIsNone(result) + def test_role_add_with_error(self): + arglist = [ + identity_fakes.role_name, + ] + verifylist = [ + ('user', None), + ('group', None), + ('domain', None), + ('project', None), + ('role', identity_fakes.role_name), + ('inherited', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) + class TestRoleAddInherited(TestRoleAdd, TestRoleInherited): pass @@ -771,6 +787,17 @@ class TestRoleList(TestRole): ), ) self.assertEqual(datalist, tuple(data)) + def test_role_list_group_with_error(self): + arglist = [ + '--group', identity_fakes.group_id, + ] + verifylist = [ + ('group', identity_fakes.group_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) + class TestRoleRemove(TestRole): @@ -982,6 +1009,22 @@ class TestRoleRemove(TestRole): ) self.assertIsNone(result) + def test_role_remove_with_error(self): + arglist = [ + identity_fakes.role_name, + ] + verifylist = [ + ('user', None), + ('group', None), + ('domain', None), + ('project', None), + ('role', identity_fakes.role_name), + ('inherited', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) + class TestRoleSet(TestRole): diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index eb96533..7afe332 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -71,6 +71,10 @@ class TestNetworkV2(utils.TestCommand): token=fakes.AUTH_TOKEN, ) + self.app.client_manager.sdk_connection = mock.Mock() + self.app.client_manager.sdk_connection.network = \ + self.app.client_manager.network + self.app.client_manager.identity = ( identity_fakes_v3.FakeIdentityv3Client( endpoint=fakes.AUTH_URL, @@ -153,6 +157,31 @@ class FakeAddressScope(object): return mock.Mock(side_effect=address_scopes) +class FakeAutoAllocatedTopology(object): + """Fake Auto Allocated Topology""" + + @staticmethod + def create_one_topology(attrs=None): + attrs = attrs or {} + + auto_allocated_topology_attrs = { + 'id': 'network-id-' + uuid.uuid4().hex, + 'tenant_id': 'project-id-' + uuid.uuid4().hex, + } + + auto_allocated_topology_attrs.update(attrs) + + auto_allocated_topology = fakes.FakeResource( + info=copy.deepcopy(auto_allocated_topology_attrs), + loaded=True) + + auto_allocated_topology.project_id = auto_allocated_topology_attrs[ + 'tenant_id' + ] + + return auto_allocated_topology + + class FakeAvailabilityZone(object): """Fake one or more network availability zones (AZs).""" @@ -386,6 +415,69 @@ class FakeNetwork(object): return mock.Mock(side_effect=networks) +class FakeNetworkFlavor(object): + """Fake Network Flavor.""" + + @staticmethod + def create_one_network_flavor(attrs=None): + """Create a fake network flavor. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object faking the network flavor + """ + attrs = attrs or {} + + fake_uuid = uuid.uuid4().hex + network_flavor_attrs = { + 'description': 'network-flavor-description-' + fake_uuid, + 'enabled': True, + 'id': 'network-flavor-id-' + fake_uuid, + 'name': 'network-flavor-name-' + fake_uuid, + 'service_type': 'vpn', + 'tenant_id': 'project-id-' + uuid.uuid4().hex, + } + + # Overwrite default attributes. + network_flavor_attrs.update(attrs) + + network_flavor = fakes.FakeResource( + info=copy.deepcopy(network_flavor_attrs), + loaded=True + ) + + network_flavor.project_id = network_flavor_attrs['tenant_id'] + network_flavor.is_enabled = network_flavor_attrs['enabled'] + + return network_flavor + + @staticmethod + def create_flavor(attrs=None, count=2): + """Create multiple fake network flavors. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of network flavors to fake + :return: + A list of FakeResource objects faking the network falvors + """ + network_flavors = [] + for i in range(0, count): + network_flavors.append( + FakeNetworkFlavor.create_one_network_flavor(attrs) + ) + return network_flavors + + @staticmethod + def get_flavor(network_flavors=None, count=2): + """Get a list of flavors.""" + if network_flavors is None: + network_flavors = (FakeNetworkFlavor.create_flavor(count)) + return mock.Mock(side_effect=network_flavors) + + class FakeNetworkSegment(object): """Fake one or more network segments.""" @@ -470,13 +562,14 @@ class FakePort(object): 'dns_assignment': [{}], 'dns_name': 'dns-name-' + uuid.uuid4().hex, 'extra_dhcp_opts': [{}], - 'fixed_ips': [{}], + 'fixed_ips': [{'ip_address': '10.0.0.3', + 'subnet_id': 'subnet-id-' + uuid.uuid4().hex}], 'id': 'port-id-' + uuid.uuid4().hex, 'mac_address': 'fa:16:3e:a9:4e:72', 'name': 'port-name-' + uuid.uuid4().hex, 'network_id': 'network-id-' + uuid.uuid4().hex, 'port_security_enabled': True, - 'security_groups': [], + 'security_group_ids': [], 'status': 'ACTIVE', 'tenant_id': 'project-id-' + uuid.uuid4().hex, } @@ -496,7 +589,7 @@ class FakePort(object): port.is_admin_state_up = port_attrs['admin_state_up'] port.is_port_security_enabled = port_attrs['port_security_enabled'] port.project_id = port_attrs['tenant_id'] - port.security_group_ids = port_attrs['security_groups'] + port.security_group_ids = port_attrs['security_group_ids'] return port @@ -1051,7 +1144,7 @@ class FakeSecurityGroupRule(object): 'description': 'security-group-rule-description-' + uuid.uuid4().hex, 'direction': 'ingress', - 'ethertype': 'IPv4', + 'ether_type': 'IPv4', 'id': 'security-group-rule-id-' + uuid.uuid4().hex, 'port_range_max': None, 'port_range_min': None, diff --git a/openstackclient/tests/unit/network/v2/test_network_auto_allocated_topology.py b/openstackclient/tests/unit/network/v2/test_network_auto_allocated_topology.py new file mode 100644 index 0000000..1a23116 --- /dev/null +++ b/openstackclient/tests/unit/network/v2/test_network_auto_allocated_topology.py @@ -0,0 +1,267 @@ +# Copyright (c) 2016, Intel Corporation. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock + +from openstackclient.network.v2 import network_auto_allocated_topology +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit.network.v2 import fakes as network_fakes + + +class TestAutoAllocatedTopology(network_fakes.TestNetworkV2): + def setUp(self): + super(TestAutoAllocatedTopology, self).setUp() + self.network = self.app.client_manager.network + self.projects_mock = self.app.client_manager.identity.projects + + +class TestCreateAutoAllocatedTopology(TestAutoAllocatedTopology): + project = identity_fakes.FakeProject.create_one_project() + network_object = network_fakes.FakeNetwork.create_one_network() + + topology = network_fakes.FakeAutoAllocatedTopology.create_one_topology( + attrs={'id': network_object.id, + 'tenant_id': project.id} + ) + + columns = ( + 'id', + 'project_id', + ) + + data = ( + network_object.id, + project.id, + ) + + def setUp(self): + super(TestCreateAutoAllocatedTopology, self).setUp() + + self.cmd = network_auto_allocated_topology.CreateAutoAllocatedTopology( + self.app, + self.namespace) + self.network.get_auto_allocated_topology = mock.Mock( + return_value=self.topology) + + def test_create_no_options(self): + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.network.get_auto_allocated_topology.assert_called_with(None) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_project_option(self): + arglist = [ + '--project', self.project.id, + ] + + verifylist = [ + ('project', self.project.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.network.get_auto_allocated_topology.assert_called_with( + self.project.id + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_project_domain_option(self): + arglist = [ + '--project', self.project.id, + '--project-domain', self.project.domain_id, + ] + + verifylist = [ + ('project', self.project.id), + ('project_domain', self.project.domain_id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.network.get_auto_allocated_topology.assert_called_with( + self.project.id + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_or_show_option(self): + arglist = [ + '--or-show', + ] + + verifylist = [ + ('or_show', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.network.get_auto_allocated_topology.assert_called_with(None) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestValidateAutoAllocatedTopology(TestAutoAllocatedTopology): + project = identity_fakes.FakeProject.create_one_project() + network_object = network_fakes.FakeNetwork.create_one_network() + + topology = network_fakes.FakeAutoAllocatedTopology.create_one_topology( + attrs={'id': network_object.id, + 'tenant_id': project.id} + ) + + columns = ( + 'id', + 'project_id', + ) + + data = ( + network_object.id, + project.id, + ) + + def setUp(self): + super(TestValidateAutoAllocatedTopology, self).setUp() + + self.cmd = network_auto_allocated_topology.CreateAutoAllocatedTopology( + self.app, + self.namespace) + self.network.validate_auto_allocated_topology = mock.Mock( + return_value=self.topology) + + def test_show_dry_run_no_project(self): + arglist = [ + '--check-resources', + ] + verifylist = [ + ('check_resources', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.validate_auto_allocated_topology.assert_called_with( + None) + + def test_show_dry_run_project_option(self): + arglist = [ + '--check-resources', + '--project', self.project.id, + ] + verifylist = [ + ('check_resources', True), + ('project', self.project.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.validate_auto_allocated_topology.assert_called_with( + self.project.id) + + def test_show_dry_run_project_domain_option(self): + arglist = [ + '--check-resources', + '--project', self.project.id, + '--project-domain', self.project.domain_id, + ] + verifylist = [ + ('check_resources', True), + ('project', self.project.id), + ('project_domain', self.project.domain_id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.validate_auto_allocated_topology.assert_called_with( + self.project.id) + + +class TestDeleteAutoAllocatedTopology(TestAutoAllocatedTopology): + project = identity_fakes.FakeProject.create_one_project() + network_object = network_fakes.FakeNetwork.create_one_network() + + topology = network_fakes.FakeAutoAllocatedTopology.create_one_topology( + attrs={'id': network_object.id, + 'tenant_id': project.id} + ) + + def setUp(self): + super(TestDeleteAutoAllocatedTopology, self).setUp() + + self.cmd = network_auto_allocated_topology.DeleteAutoAllocatedTopology( + self.app, + self.namespace) + self.network.delete_auto_allocated_topology = mock.Mock( + return_value=None) + + def test_delete_no_project(self): + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.network.delete_auto_allocated_topology.assert_called_once_with( + None) + + self.assertIsNone(result) + + def test_delete_project_arg(self): + arglist = [ + '--project', self.project.id, + ] + verifylist = [ + ('project', self.project.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.network.delete_auto_allocated_topology.assert_called_once_with( + self.project.id) + + self.assertIsNone(result) + + def test_delete_project_domain_arg(self): + arglist = [ + '--project', self.project.id, + '--project-domain', self.project.domain_id, + ] + verifylist = [ + ('project', self.project.id), + ('project_domain', self.project.domain_id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.network.delete_auto_allocated_topology.assert_called_once_with( + self.project.id) + + self.assertIsNone(result) diff --git a/openstackclient/tests/unit/network/v2/test_network_flavor.py b/openstackclient/tests/unit/network/v2/test_network_flavor.py new file mode 100644 index 0000000..11e2784 --- /dev/null +++ b/openstackclient/tests/unit/network/v2/test_network_flavor.py @@ -0,0 +1,407 @@ +# Copyright (c) 2016, Intel Corporation. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import mock + +from osc_lib import exceptions + +from openstackclient.network.v2 import network_flavor +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 +from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit import utils as tests_utils + + +class TestNetworkFlavor(network_fakes.TestNetworkV2): + + def setUp(self): + super(TestNetworkFlavor, self).setUp() + + # Get a shortcut to the network client + self.network = self.app.client_manager.network + # Get a shortcut to the ProjectManager Mock + self.projects_mock = self.app.client_manager.identity.projects + # Get a shortcut to the DomainManager Mock + self.domains_mock = self.app.client_manager.identity.domains + + +class TestCreateNetworkFlavor(TestNetworkFlavor): + + project = identity_fakes_v3.FakeProject.create_one_project() + domain = identity_fakes_v3.FakeDomain.create_one_domain() + # The new network flavor created. + new_network_flavor = ( + network_fakes.FakeNetworkFlavor.create_one_network_flavor()) + columns = ( + 'description', + 'enabled', + 'id', + 'name', + 'project_id', + 'service_type' + ) + data = ( + new_network_flavor.description, + new_network_flavor.enabled, + new_network_flavor.id, + new_network_flavor.name, + new_network_flavor.project_id, + new_network_flavor.service_type, + ) + + def setUp(self): + super(TestCreateNetworkFlavor, self).setUp() + self.network.create_flavor = mock.Mock( + return_value=self.new_network_flavor) + + # Get the command object to test + self.cmd = network_flavor.CreateNetworkFlavor(self.app, self.namespace) + + self.projects_mock.get.return_value = self.project + self.domains_mock.get.return_value = self.domain + + 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 = [ + '--service-type', self.new_network_flavor.service_type, + self.new_network_flavor.name, + ] + verifylist = [ + ('service_type', self.new_network_flavor.service_type), + ('name', self.new_network_flavor.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_flavor.assert_called_once_with(**{ + 'service_type': self.new_network_flavor.service_type, + 'name': self.new_network_flavor.name, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_all_options(self): + arglist = [ + '--description', self.new_network_flavor.description, + '--enable', + '--project', self.new_network_flavor.project_id, + '--project-domain', self.domain.name, + '--service-type', self.new_network_flavor.service_type, + self.new_network_flavor.name, + ] + verifylist = [ + ('description', self.new_network_flavor.description), + ('enable', True), + ('project', self.new_network_flavor.project_id), + ('project_domain', self.domain.name), + ('service_type', self.new_network_flavor.service_type), + ('name', self.new_network_flavor.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_flavor.assert_called_once_with(**{ + 'description': self.new_network_flavor.description, + 'enabled': True, + 'tenant_id': self.project.id, + 'service_type': self.new_network_flavor.service_type, + 'name': self.new_network_flavor.name, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_disable(self): + arglist = [ + '--disable', + '--service-type', self.new_network_flavor.service_type, + self.new_network_flavor.name, + ] + verifylist = [ + ('disable', True), + ('service_type', self.new_network_flavor.service_type), + ('name', self.new_network_flavor.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_flavor.assert_called_once_with(**{ + 'enabled': False, + 'service_type': self.new_network_flavor.service_type, + 'name': self.new_network_flavor.name, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestDeleteNetworkFlavor(TestNetworkFlavor): + + # The network flavor to delete. + _network_flavors = ( + network_fakes.FakeNetworkFlavor.create_flavor(count=2)) + + def setUp(self): + super(TestDeleteNetworkFlavor, self).setUp() + self.network.delete_flavor = mock.Mock(return_value=None) + self.network.find_flavor = ( + network_fakes.FakeNetworkFlavor.get_flavor( + network_flavors=self._network_flavors) + ) + + # Get the command object to test + self.cmd = network_flavor.DeleteNetworkFlavor(self.app, self.namespace) + + def test_network_flavor_delete(self): + arglist = [ + self._network_flavors[0].name, + ] + verifylist = [ + ('flavor', [self._network_flavors[0].name]), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.network.find_flavor.assert_called_once_with( + self._network_flavors[0].name, ignore_missing=False) + self.network.delete_flavor.assert_called_once_with( + self._network_flavors[0]) + self.assertIsNone(result) + + def test_multi_network_flavors_delete(self): + arglist = [] + verifylist = [] + + for a in self._network_flavors: + arglist.append(a.name) + verifylist = [ + ('flavor', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for a in self._network_flavors: + calls.append(mock.call(a)) + self.network.delete_flavor.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multi_network_flavors_delete_with_exception(self): + arglist = [ + self._network_flavors[0].name, + 'unexist_network_flavor', + ] + verifylist = [ + ('flavor', + [self._network_flavors[0].name, 'unexist_network_flavor']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self._network_flavors[0], exceptions.CommandError] + self.network.find_flavor = ( + mock.Mock(side_effect=find_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 flavors failed to delete.', str(e)) + + self.network.find_flavor.assert_any_call( + self._network_flavors[0].name, ignore_missing=False) + self.network.find_flavor.assert_any_call( + 'unexist_network_flavor', ignore_missing=False) + self.network.delete_flavor.assert_called_once_with( + self._network_flavors[0] + ) + + +class TestListNetworkFlavor(TestNetworkFlavor): + + # The network flavors to list up. + _network_flavors = ( + network_fakes.FakeNetworkFlavor.create_flavor(count=2)) + columns = ( + 'ID', + 'Name', + 'Enabled', + 'Service Type', + 'Description', + ) + data = [] + for flavor in _network_flavors: + data.append(( + flavor.id, + flavor.name, + flavor.enabled, + flavor.service_type, + flavor.description, + )) + + def setUp(self): + super(TestListNetworkFlavor, self).setUp() + self.network.flavors = mock.Mock( + return_value=self._network_flavors) + + # Get the command object to test + self.cmd = network_flavor.ListNetworkFlavor(self.app, self.namespace) + + def test_network_flavor_list(self): + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.flavors.assert_called_once_with(**{}) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + +class TestShowNetworkFlavor(TestNetworkFlavor): + + # The network flavor to show. + new_network_flavor = ( + network_fakes.FakeNetworkFlavor.create_one_network_flavor()) + columns = ( + 'description', + 'enabled', + 'id', + 'name', + 'project_id', + 'service_type' + ) + data = ( + new_network_flavor.description, + new_network_flavor.enabled, + new_network_flavor.id, + new_network_flavor.name, + new_network_flavor.project_id, + new_network_flavor.service_type, + ) + + def setUp(self): + super(TestShowNetworkFlavor, self).setUp() + self.network.find_flavor = mock.Mock( + return_value=self.new_network_flavor) + + # Get the command object to test + self.cmd = network_flavor.ShowNetworkFlavor(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_all_options(self): + arglist = [ + self.new_network_flavor.name, + ] + verifylist = [ + ('flavor', self.new_network_flavor.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.find_flavor.assert_called_once_with( + self.new_network_flavor.name, ignore_missing=False) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestSetNetworkFlavor(TestNetworkFlavor): + + # The network flavor to set. + new_network_flavor = ( + network_fakes.FakeNetworkFlavor.create_one_network_flavor()) + + def setUp(self): + super(TestSetNetworkFlavor, self).setUp() + self.network.update_flavor = mock.Mock(return_value=None) + self.network.find_flavor = mock.Mock( + return_value=self.new_network_flavor) + + # Get the command object to test + self.cmd = network_flavor.SetNetworkFlavor(self.app, self.namespace) + + def test_set_nothing(self): + arglist = [self.new_network_flavor.name, ] + verifylist = [ + ('flavor', self.new_network_flavor.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = {} + self.network.update_flavor.assert_called_with( + self.new_network_flavor, **attrs) + self.assertIsNone(result) + + def test_set_name_and_enable(self): + arglist = [ + '--name', 'new_network_flavor', + '--enable', + self.new_network_flavor.name, + ] + verifylist = [ + ('name', 'new_network_flavor'), + ('enable', True), + ('flavor', self.new_network_flavor.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = { + 'name': "new_network_flavor", + 'enabled': True, + } + self.network.update_flavor.assert_called_with( + self.new_network_flavor, **attrs) + self.assertIsNone(result) + + def test_set_disable(self): + arglist = [ + '--disable', + self.new_network_flavor.name, + ] + verifylist = [ + ('disable', True), + ('flavor', self.new_network_flavor.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = { + 'enabled': False, + } + self.network.update_flavor.assert_called_with( + self.new_network_flavor, **attrs) + self.assertIsNone(result) diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index fc62668..80eba3a 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -57,7 +57,7 @@ class TestPort(network_fakes.TestNetworkV2): 'network_id', 'port_security_enabled', 'project_id', - 'security_groups', + 'security_group_ids', 'status', ) @@ -82,7 +82,7 @@ class TestPort(network_fakes.TestNetworkV2): fake_port.network_id, fake_port.port_security_enabled, fake_port.project_id, - utils.format_list(fake_port.security_groups), + utils.format_list(fake_port.security_group_ids), fake_port.status, ) @@ -251,7 +251,7 @@ class TestCreatePort(TestPort): verifylist = [ ('network', self._port.network_id,), ('enable', True), - ('security_groups', [secgroup.id]), + ('security_group', [secgroup.id]), ('name', 'test-port'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -261,7 +261,7 @@ class TestCreatePort(TestPort): self.network.create_port.assert_called_once_with(**{ 'admin_state_up': True, 'network_id': self._port.network_id, - 'security_groups': [secgroup.id], + 'security_group_ids': [secgroup.id], 'name': 'test-port', }) @@ -309,7 +309,7 @@ class TestCreatePort(TestPort): verifylist = [ ('network', self._port.network_id,), ('enable', True), - ('security_groups', [sg_1.id, sg_2.id]), + ('security_group', [sg_1.id, sg_2.id]), ('name', 'test-port'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -319,7 +319,7 @@ class TestCreatePort(TestPort): self.network.create_port.assert_called_once_with(**{ 'admin_state_up': True, 'network_id': self._port.network_id, - 'security_groups': [sg_1.id, sg_2.id], + 'security_group_ids': [sg_1.id, sg_2.id], 'name': 'test-port', }) @@ -346,7 +346,7 @@ class TestCreatePort(TestPort): self.network.create_port.assert_called_once_with(**{ 'admin_state_up': True, 'network_id': self._port.network_id, - 'security_groups': [], + 'security_group_ids': [], 'name': 'test-port', }) @@ -590,7 +590,7 @@ class TestListPort(TestPort): prt.mac_address, utils.format_list_of_dicts(prt.fixed_ips), prt.status, - utils.format_list(prt.security_groups), + utils.format_list(prt.security_group_ids), prt.device_owner, )) @@ -727,6 +727,92 @@ class TestListPort(TestPort): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_port_list_fixed_ip_opt_ip_address(self): + ip_address = self._ports[0].fixed_ips[0]['ip_address'] + arglist = [ + '--fixed-ip', "ip-address=%s" % ip_address, + ] + verifylist = [ + ('fixed_ip', [{'ip-address': ip_address}]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.ports.assert_called_once_with(**{ + 'fixed_ips': ['ip_address=%s' % ip_address]}) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_port_list_fixed_ip_opt_subnet_id(self): + subnet_id = self._ports[0].fixed_ips[0]['subnet_id'] + arglist = [ + '--fixed-ip', "subnet=%s" % subnet_id, + ] + verifylist = [ + ('fixed_ip', [{'subnet': subnet_id}]) + ] + + self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet( + {'id': subnet_id}) + self.network.find_subnet = mock.Mock(return_value=self.fake_subnet) + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.ports.assert_called_once_with(**{ + 'fixed_ips': ['subnet_id=%s' % subnet_id]}) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_port_list_fixed_ip_opts(self): + subnet_id = self._ports[0].fixed_ips[0]['subnet_id'] + ip_address = self._ports[0].fixed_ips[0]['ip_address'] + arglist = [ + '--fixed-ip', "subnet=%s,ip-address=%s" % (subnet_id, + ip_address) + ] + verifylist = [ + ('fixed_ip', [{'subnet': subnet_id, + 'ip-address': ip_address}]) + ] + + self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet( + {'id': subnet_id}) + self.network.find_subnet = mock.Mock(return_value=self.fake_subnet) + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.ports.assert_called_once_with(**{ + 'fixed_ips': ['subnet_id=%s' % subnet_id, + 'ip_address=%s' % ip_address]}) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_port_list_fixed_ips(self): + subnet_id = self._ports[0].fixed_ips[0]['subnet_id'] + ip_address = self._ports[0].fixed_ips[0]['ip_address'] + arglist = [ + '--fixed-ip', "subnet=%s" % subnet_id, + '--fixed-ip', "ip-address=%s" % ip_address, + ] + verifylist = [ + ('fixed_ip', [{'subnet': subnet_id}, + {'ip-address': ip_address}]) + ] + + self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet( + {'id': subnet_id}) + self.network.find_subnet = mock.Mock(return_value=self.fake_subnet) + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.ports.assert_called_once_with(**{ + 'fixed_ips': ['subnet_id=%s' % subnet_id, + 'ip_address=%s' % ip_address]}) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + def test_list_port_with_long(self): arglist = [ '--long', @@ -801,6 +887,7 @@ class TestSetPort(TestPort): arglist = [ '--fixed-ip', 'ip-address=10.0.0.11', self._port.name, + '--no-fixed-ip', ] verifylist = [ ('fixed_ip', [{'ip-address': '10.0.0.11'}]), @@ -1024,7 +1111,7 @@ class TestSetPort(TestPort): self._port.name, ] verifylist = [ - ('security_groups', [sg.id]), + ('security_group', [sg.id]), ('port', self._port.name), ] @@ -1032,7 +1119,7 @@ class TestSetPort(TestPort): result = self.cmd.take_action(parsed_args) attrs = { - 'security_groups': [sg.id], + 'security_group_ids': [sg.id], } self.network.update_port.assert_called_once_with(self._port, **attrs) self.assertIsNone(result) @@ -1043,7 +1130,7 @@ class TestSetPort(TestPort): sg_3 = network_fakes.FakeSecurityGroup.create_one_security_group() self.network.find_security_group = mock.Mock(side_effect=[sg_2, sg_3]) _testport = network_fakes.FakePort.create_one_port( - {'security_groups': [sg_1.id]}) + {'security_group_ids': [sg_1.id]}) self.network.find_port = mock.Mock(return_value=_testport) arglist = [ '--security-group', sg_2.id, @@ -1051,13 +1138,13 @@ class TestSetPort(TestPort): _testport.name, ] verifylist = [ - ('security_groups', [sg_2.id, sg_3.id]), + ('security_group', [sg_2.id, sg_3.id]), ('port', _testport.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) attrs = { - 'security_groups': [sg_1.id, sg_2.id, sg_3.id], + 'security_group_ids': [sg_2.id, sg_3.id, sg_1.id], } self.network.update_port.assert_called_once_with(_testport, **attrs) self.assertIsNone(result) @@ -1076,7 +1163,7 @@ class TestSetPort(TestPort): result = self.cmd.take_action(parsed_args) attrs = { - 'security_groups': [], + 'security_group_ids': [], } self.network.update_port.assert_called_once_with(self._port, **attrs) self.assertIsNone(result) @@ -1085,7 +1172,7 @@ class TestSetPort(TestPort): sg1 = network_fakes.FakeSecurityGroup.create_one_security_group() sg2 = network_fakes.FakeSecurityGroup.create_one_security_group() _testport = network_fakes.FakePort.create_one_port( - {'security_groups': [sg1.id]}) + {'security_group_ids': [sg1.id]}) self.network.find_port = mock.Mock(return_value=_testport) self.network.find_security_group = mock.Mock(return_value=sg2) arglist = [ @@ -1094,13 +1181,13 @@ class TestSetPort(TestPort): _testport.name, ] verifylist = [ - ('security_groups', [sg2.id]), + ('security_group', [sg2.id]), ('no_security_group', True) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) attrs = { - 'security_groups': [sg2.id], + 'security_group_ids': [sg2.id], } self.network.update_port.assert_called_once_with(_testport, **attrs) self.assertIsNone(result) @@ -1347,7 +1434,7 @@ class TestUnsetPort(TestPort): _fake_sg1 = network_fakes.FakeSecurityGroup.create_one_security_group() _fake_sg2 = network_fakes.FakeSecurityGroup.create_one_security_group() _fake_port = network_fakes.FakePort.create_one_port( - {'security_groups': [_fake_sg1.id, _fake_sg2.id]}) + {'security_group_ids': [_fake_sg1.id, _fake_sg2.id]}) self.network.find_port = mock.Mock(return_value=_fake_port) self.network.find_security_group = mock.Mock(return_value=_fake_sg2) arglist = [ @@ -1355,14 +1442,14 @@ class TestUnsetPort(TestPort): _fake_port.name, ] verifylist = [ - ('security_groups', [_fake_sg2.id]), + ('security_group_ids', [_fake_sg2.id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) attrs = { - 'security_groups': [_fake_sg1.id] + 'security_group_ids': [_fake_sg1.id] } self.network.update_port.assert_called_once_with( _fake_port, **attrs) @@ -1372,14 +1459,14 @@ class TestUnsetPort(TestPort): _fake_sg1 = network_fakes.FakeSecurityGroup.create_one_security_group() _fake_sg2 = network_fakes.FakeSecurityGroup.create_one_security_group() _fake_port = network_fakes.FakePort.create_one_port( - {'security_groups': [_fake_sg1.id]}) + {'security_group_ids': [_fake_sg1.id]}) self.network.find_security_group = mock.Mock(return_value=_fake_sg2) arglist = [ '--security-group', _fake_sg2.id, _fake_port.name, ] verifylist = [ - ('security_groups', [_fake_sg2.id]), + ('security_group_ids', [_fake_sg2.id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/unit/network/v2/test_security_group_rule.py b/openstackclient/tests/unit/network/v2/test_security_group_rule.py index 5fe9013..e3538d5 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_rule.py @@ -62,7 +62,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): expected_columns = ( 'description', 'direction', - 'ethertype', + 'ether_type', 'id', 'port_range_max', 'port_range_min', @@ -84,7 +84,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): self.expected_data = ( self._security_group_rule.description, self._security_group_rule.direction, - self._security_group_rule.ethertype, + self._security_group_rule.ether_type, self._security_group_rule.id, self._security_group_rule.port_range_max, self._security_group_rule.port_range_min, @@ -184,7 +184,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): self.network.create_security_group_rule.assert_called_once_with(**{ 'direction': self._security_group_rule.direction, - 'ethertype': self._security_group_rule.ethertype, + 'ethertype': self._security_group_rule.ether_type, 'port_range_max': self._security_group_rule.port_range_max, 'port_range_min': self._security_group_rule.port_range_min, 'protocol': self._security_group_rule.protocol, @@ -216,7 +216,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): self.network.create_security_group_rule.assert_called_once_with(**{ 'direction': self._security_group_rule.direction, - 'ethertype': self._security_group_rule.ethertype, + 'ethertype': self._security_group_rule.ether_type, 'protocol': self._security_group_rule.protocol, 'remote_ip_prefix': self._security_group_rule.remote_ip_prefix, 'security_group_id': self._security_group.id, @@ -249,7 +249,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): self.network.create_security_group_rule.assert_called_once_with(**{ 'direction': self._security_group_rule.direction, - 'ethertype': self._security_group_rule.ethertype, + 'ethertype': self._security_group_rule.ether_type, 'port_range_max': self._security_group_rule.port_range_max, 'port_range_min': self._security_group_rule.port_range_min, 'protocol': self._security_group_rule.protocol, @@ -279,7 +279,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): self.network.create_security_group_rule.assert_called_once_with(**{ 'direction': self._security_group_rule.direction, - 'ethertype': self._security_group_rule.ethertype, + 'ethertype': self._security_group_rule.ether_type, 'protocol': self._security_group_rule.protocol, 'remote_group_id': self._security_group_rule.remote_group_id, 'security_group_id': self._security_group.id, @@ -308,7 +308,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): self.network.create_security_group_rule.assert_called_once_with(**{ 'direction': self._security_group_rule.direction, - 'ethertype': self._security_group_rule.ethertype, + 'ethertype': self._security_group_rule.ether_type, 'protocol': self._security_group_rule.protocol, 'remote_ip_prefix': self._security_group_rule.remote_ip_prefix, 'security_group_id': self._security_group.id, @@ -337,7 +337,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): self.network.create_security_group_rule.assert_called_once_with(**{ 'direction': self._security_group_rule.direction, - 'ethertype': self._security_group_rule.ethertype, + 'ethertype': self._security_group_rule.ether_type, 'protocol': self._security_group_rule.protocol, 'remote_ip_prefix': self._security_group_rule.remote_ip_prefix, 'security_group_id': self._security_group.id, @@ -348,7 +348,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): def test_create_network_options(self): self._setup_security_group_rule({ 'direction': 'egress', - 'ethertype': 'IPv6', + 'ether_type': 'IPv6', 'port_range_max': 443, 'port_range_min': 443, 'protocol': '6', @@ -358,7 +358,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): arglist = [ '--dst-port', str(self._security_group_rule.port_range_min), '--egress', - '--ethertype', self._security_group_rule.ethertype, + '--ethertype', self._security_group_rule.ether_type, '--project', self.project.name, '--project-domain', self.domain.name, '--protocol', self._security_group_rule.protocol, @@ -368,7 +368,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): ('dst_port', (self._security_group_rule.port_range_min, self._security_group_rule.port_range_max)), ('egress', True), - ('ethertype', self._security_group_rule.ethertype), + ('ethertype', self._security_group_rule.ether_type), ('project', self.project.name), ('project_domain', self.domain.name), ('protocol', self._security_group_rule.protocol), @@ -380,7 +380,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): self.network.create_security_group_rule.assert_called_once_with(**{ 'direction': self._security_group_rule.direction, - 'ethertype': self._security_group_rule.ethertype, + 'ethertype': self._security_group_rule.ether_type, 'port_range_max': self._security_group_rule.port_range_max, 'port_range_min': self._security_group_rule.port_range_min, 'protocol': self._security_group_rule.protocol, @@ -444,7 +444,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): self.network.create_security_group_rule.assert_called_once_with(**{ 'direction': self._security_group_rule.direction, - 'ethertype': self._security_group_rule.ethertype, + 'ethertype': self._security_group_rule.ether_type, 'port_range_min': self._security_group_rule.port_range_min, 'protocol': self._security_group_rule.protocol, 'remote_ip_prefix': self._security_group_rule.remote_ip_prefix, @@ -455,7 +455,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): def test_create_ipv6_icmp_type_code(self): self._setup_security_group_rule({ - 'ethertype': 'IPv6', + 'ether_type': 'IPv6', 'port_range_min': 139, 'port_range_max': 2, 'protocol': 'ipv6-icmp', @@ -479,7 +479,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): self.network.create_security_group_rule.assert_called_once_with(**{ 'direction': self._security_group_rule.direction, - 'ethertype': self._security_group_rule.ethertype, + 'ethertype': self._security_group_rule.ether_type, 'port_range_min': self._security_group_rule.port_range_min, 'port_range_max': self._security_group_rule.port_range_max, 'protocol': self._security_group_rule.protocol, @@ -490,7 +490,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): def test_create_icmpv6_type(self): self._setup_security_group_rule({ - 'ethertype': 'IPv6', + 'ether_type': 'IPv6', 'port_range_min': 139, 'protocol': 'icmpv6', }) @@ -512,7 +512,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): self.network.create_security_group_rule.assert_called_once_with(**{ 'direction': self._security_group_rule.direction, - 'ethertype': self._security_group_rule.ethertype, + 'ethertype': self._security_group_rule.ether_type, 'port_range_min': self._security_group_rule.port_range_min, 'protocol': self._security_group_rule.protocol, 'security_group_id': self._security_group.id, @@ -539,7 +539,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): self.network.create_security_group_rule.assert_called_once_with(**{ 'description': self._security_group_rule.description, 'direction': self._security_group_rule.direction, - 'ethertype': self._security_group_rule.ethertype, + 'ethertype': self._security_group_rule.ether_type, 'protocol': self._security_group_rule.protocol, 'remote_ip_prefix': self._security_group_rule.remote_ip_prefix, 'security_group_id': self._security_group.id, @@ -1039,7 +1039,7 @@ class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): security_group_rule._format_network_port_range( _security_group_rule), _security_group_rule.direction, - _security_group_rule.ethertype, + _security_group_rule.ether_type, _security_group_rule.remote_group_id, )) expected_data_no_group.append(( @@ -1299,7 +1299,7 @@ class TestShowSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): columns = ( 'description', 'direction', - 'ethertype', + 'ether_type', 'id', 'port_range_max', 'port_range_min', @@ -1313,7 +1313,7 @@ class TestShowSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): data = ( _security_group_rule.description, _security_group_rule.direction, - _security_group_rule.ethertype, + _security_group_rule.ether_type, _security_group_rule.id, _security_group_rule.port_range_max, _security_group_rule.port_range_min, diff --git a/openstackclient/tests/unit/volume/v1/fakes.py b/openstackclient/tests/unit/volume/v1/fakes.py index 78a8227..fff5181 100644 --- a/openstackclient/tests/unit/volume/v1/fakes.py +++ b/openstackclient/tests/unit/volume/v1/fakes.py @@ -364,6 +364,9 @@ class FakeVolumev1Client(object): self.qos_specs.resource_class = fakes.FakeResource(None, {}) self.volume_types = mock.Mock() self.volume_types.resource_class = fakes.FakeResource(None, {}) + self.volume_encryption_types = mock.Mock() + self.volume_encryption_types.resource_class = ( + fakes.FakeResource(None, {})) self.transfers = mock.Mock() self.transfers.resource_class = fakes.FakeResource(None, {}) self.volume_snapshots = mock.Mock() @@ -470,6 +473,34 @@ class FakeType(object): return mock.Mock(side_effect=types) + @staticmethod + def create_one_encryption_type(attrs=None): + """Create a fake encryption type. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with volume_type_id etc. + """ + attrs = attrs or {} + + # Set default attributes. + encryption_info = { + "volume_type_id": 'type-id-' + uuid.uuid4().hex, + 'provider': 'LuksEncryptor', + 'cipher': None, + 'key_size': None, + 'control_location': 'front-end', + } + + # Overwrite default attributes. + encryption_info.update(attrs) + + encryption_type = fakes.FakeResource( + info=copy.deepcopy(encryption_info), + loaded=True) + return encryption_type + class FakeSnapshot(object): """Fake one or more snapshot.""" diff --git a/openstackclient/tests/unit/volume/v1/test_qos_specs.py b/openstackclient/tests/unit/volume/v1/test_qos_specs.py index 464038e..e3dc1e7 100644 --- a/openstackclient/tests/unit/volume/v1/test_qos_specs.py +++ b/openstackclient/tests/unit/volume/v1/test_qos_specs.py @@ -70,23 +70,22 @@ class TestQosAssociate(TestQos): class TestQosCreate(TestQos): - new_qos_spec = volume_fakes.FakeQos.create_one_qos() columns = ( 'consumer', 'id', 'name', - 'specs' - ) - datalist = ( - new_qos_spec.consumer, - new_qos_spec.id, - new_qos_spec.name, - new_qos_spec.specs + 'properties' ) def setUp(self): super(TestQosCreate, self).setUp() - + self.new_qos_spec = volume_fakes.FakeQos.create_one_qos() + self.datalist = ( + self.new_qos_spec.consumer, + self.new_qos_spec.id, + self.new_qos_spec.name, + utils.format_dict(self.new_qos_spec.specs) + ) self.qos_mock.create.return_value = self.new_qos_spec # Get the command object to test self.cmd = qos_specs.CreateQos(self.app, None) @@ -336,7 +335,7 @@ class TestQosList(TestQos): 'Name', 'Consumer', 'Associations', - 'Specs', + 'Properties', ) self.assertEqual(collist, columns) datalist = (( @@ -413,7 +412,7 @@ class TestQosShow(TestQos): 'consumer', 'id', 'name', - 'specs' + 'properties' ) self.assertEqual(collist, columns) datalist = ( diff --git a/openstackclient/tests/unit/volume/v1/test_type.py b/openstackclient/tests/unit/volume/v1/test_type.py index 81ad830..dcdd3d5 100644 --- a/openstackclient/tests/unit/volume/v1/test_type.py +++ b/openstackclient/tests/unit/volume/v1/test_type.py @@ -31,6 +31,10 @@ class TestType(volume_fakes.TestVolumev1): self.types_mock = self.app.client_manager.volume.volume_types self.types_mock.reset_mock() + self.encryption_types_mock = ( + self.app.client_manager.volume.volume_encryption_types) + self.encryption_types_mock.reset_mock() + class TestTypeCreate(TestType): @@ -75,6 +79,67 @@ class TestTypeCreate(TestType): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_type_create_with_encryption(self): + encryption_info = { + 'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + '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}) + self.types_mock.create.return_value = self.new_volume_type + self.encryption_types_mock.create.return_value = encryption_type + encryption_columns = ( + 'description', + 'encryption', + 'id', + 'is_public', + 'name', + ) + encryption_data = ( + self.new_volume_type.description, + utils.format_dict(encryption_info), + self.new_volume_type.id, + True, + self.new_volume_type.name, + ) + arglist = [ + '--encryption-provider', 'LuksEncryptor', + '--encryption-cipher', 'aes-xts-plain64', + '--encryption-key-size', '128', + '--encryption-control-location', 'front-end', + self.new_volume_type.name, + ] + verifylist = [ + ('encryption_provider', 'LuksEncryptor'), + ('encryption_cipher', 'aes-xts-plain64'), + ('encryption_key_size', 128), + ('encryption_control_location', 'front-end'), + ('name', self.new_volume_type.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.types_mock.create.assert_called_with( + self.new_volume_type.name, + ) + body = { + 'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': 128, + 'control_location': 'front-end', + } + self.encryption_types_mock.create.assert_called_with( + self.new_volume_type, + body, + ) + self.assertEqual(encryption_columns, columns) + self.assertEqual(encryption_data, data) + class TestTypeDelete(TestType): @@ -156,17 +221,17 @@ class TestTypeList(TestType): volume_types = volume_fakes.FakeType.create_types() - columns = ( + columns = [ "ID", "Name", "Is Public", - ) - columns_long = ( + ] + columns_long = [ "ID", "Name", "Is Public", "Properties" - ) + ] data = [] for t in volume_types: @@ -188,6 +253,8 @@ class TestTypeList(TestType): super(TestTypeList, self).setUp() self.types_mock.list.return_value = self.volume_types + self.encryption_types_mock.create.return_value = None + self.encryption_types_mock.update.return_value = None # get the command to test self.cmd = volume_type.ListVolumeType(self.app, None) @@ -195,6 +262,7 @@ class TestTypeList(TestType): arglist = [] verifylist = [ ("long", False), + ("encryption_type", False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -217,6 +285,47 @@ class TestTypeList(TestType): self.assertEqual(self.columns_long, columns) self.assertEqual(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_info = { + 'provider': 'LuksEncryptor', + 'cipher': None, + 'key_size': None, + 'control_location': 'front-end', + } + encryption_columns = self.columns + [ + "Encryption", + ] + encryption_data = [] + encryption_data.append(( + self.volume_types[0].id, + self.volume_types[0].name, + self.volume_types[0].is_public, + utils.format_dict(encryption_info), + )) + encryption_data.append(( + self.volume_types[1].id, + self.volume_types[1].name, + self.volume_types[1].is_public, + '-', + )) + + self.encryption_types_mock.list.return_value = [encryption_type] + arglist = [ + "--encryption-type", + ] + verifylist = [ + ("encryption_type", True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.encryption_types_mock.list.assert_called_once_with() + self.types_mock.list.assert_called_once_with() + self.assertEqual(encryption_columns, columns) + self.assertEqual(encryption_data, list(data)) + class TestTypeSet(TestType): @@ -260,6 +369,60 @@ class TestTypeSet(TestType): {'myprop': 'myvalue'}) self.assertIsNone(result) + def test_type_set_new_encryption(self): + arglist = [ + '--encryption-provider', 'LuksEncryptor', + '--encryption-cipher', 'aes-xts-plain64', + '--encryption-key-size', '128', + '--encryption-control-location', 'front-end', + self.volume_type.id, + ] + verifylist = [ + ('encryption_provider', 'LuksEncryptor'), + ('encryption_cipher', 'aes-xts-plain64'), + ('encryption_key_size', 128), + ('encryption_control_location', 'front-end'), + ('volume_type', self.volume_type.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + body = { + 'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': 128, + 'control_location': 'front-end', + } + self.encryption_types_mock.create.assert_called_with( + self.volume_type, + body, + ) + self.assertIsNone(result) + + def test_type_set_new_encryption_without_provider(self): + arglist = [ + '--encryption-cipher', 'aes-xts-plain64', + '--encryption-key-size', '128', + '--encryption-control-location', 'front-end', + self.volume_type.id, + ] + verifylist = [ + ('encryption_cipher', 'aes-xts-plain64'), + ('encryption_key_size', 128), + ('encryption_control_location', 'front-end'), + ('volume_type', self.volume_type.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("Command Failed: One or more of" + " the operations failed", + str(e)) + self.encryption_types_mock.create.assert_not_called() + self.encryption_types_mock.update.assert_not_called() + class TestTypeShow(TestType): @@ -293,7 +456,8 @@ class TestTypeShow(TestType): self.volume_type.id ] verifylist = [ - ("volume_type", self.volume_type.id) + ("volume_type", self.volume_type.id), + ("encryption_type", False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -303,6 +467,50 @@ class TestTypeShow(TestType): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_type_show_with_encryption(self): + encryption_type = volume_fakes.FakeType.create_one_encryption_type() + encryption_info = { + 'provider': 'LuksEncryptor', + 'cipher': None, + 'key_size': None, + 'control_location': 'front-end', + } + self.volume_type = volume_fakes.FakeType.create_one_type( + attrs={'encryption': encryption_info}) + self.types_mock.get.return_value = self.volume_type + self.encryption_types_mock.get.return_value = encryption_type + encryption_columns = ( + 'description', + 'encryption', + 'id', + 'is_public', + 'name', + 'properties', + ) + encryption_data = ( + self.volume_type.description, + utils.format_dict(encryption_info), + self.volume_type.id, + True, + self.volume_type.name, + utils.format_dict(self.volume_type.extra_specs) + ) + arglist = [ + '--encryption-type', + self.volume_type.id + ] + verifylist = [ + ('encryption_type', True), + ("volume_type", self.volume_type.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.types_mock.get.assert_called_with(self.volume_type.id) + self.encryption_types_mock.get.assert_called_with(self.volume_type.id) + self.assertEqual(encryption_columns, columns) + self.assertEqual(encryption_data, data) + class TestTypeUnset(TestType): @@ -317,13 +525,14 @@ class TestTypeUnset(TestType): # Get the command object to test self.cmd = volume_type.UnsetVolumeType(self.app, None) - def test_type_unset(self): + def test_type_unset_property(self): arglist = [ '--property', 'property', '--property', 'multi_property', self.volume_type.id, ] verifylist = [ + ('encryption_type', False), ('property', ['property', 'multi_property']), ('volume_type', self.volume_type.id), ] @@ -333,6 +542,7 @@ class TestTypeUnset(TestType): result = self.cmd.take_action(parsed_args) self.volume_type.unset_keys.assert_called_once_with( ['property', 'multi_property']) + self.encryption_types_mock.delete.assert_not_called() self.assertIsNone(result) def test_type_unset_failed_with_missing_volume_type_argument(self): @@ -362,3 +572,18 @@ class TestTypeUnset(TestType): result = self.cmd.take_action(parsed_args) self.assertIsNone(result) + + def test_type_unset_encryption_type(self): + arglist = [ + '--encryption-type', + self.volume_type.id, + ] + verifylist = [ + ('encryption_type', True), + ('volume_type', self.volume_type.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.encryption_types_mock.delete.assert_called_with(self.volume_type) + self.assertIsNone(result) diff --git a/openstackclient/tests/unit/volume/v1/test_volume.py b/openstackclient/tests/unit/volume/v1/test_volume.py index 6c6d9a1..d46a7ba 100644 --- a/openstackclient/tests/unit/volume/v1/test_volume.py +++ b/openstackclient/tests/unit/volume/v1/test_volume.py @@ -1071,6 +1071,7 @@ class TestVolumeSet(TestVolume): def test_volume_set_property(self): arglist = [ + '--no-property', '--property', 'myprop=myvalue', self._volume.display_name, ] @@ -1080,6 +1081,7 @@ class TestVolumeSet(TestVolume): ('name', None), ('description', None), ('size', None), + ('no_property', True), ('property', {'myprop': 'myvalue'}), ('volume', self._volume.display_name), ('bootable', False), @@ -1097,6 +1099,10 @@ class TestVolumeSet(TestVolume): self._volume.id, metadata ) + self.volumes_mock.delete_metadata.assert_called_with( + self._volume.id, + self._volume.metadata.keys() + ) self.volumes_mock.update_readonly_flag.assert_not_called() self.assertIsNone(result) diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index a667640..d54faec 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -208,6 +208,9 @@ class FakeVolumeClient(object): self.volume_types.resource_class = fakes.FakeResource(None, {}) self.volume_type_access = mock.Mock() self.volume_type_access.resource_class = fakes.FakeResource(None, {}) + self.volume_encryption_types = mock.Mock() + self.volume_encryption_types.resource_class = ( + fakes.FakeResource(None, {})) self.restores = mock.Mock() self.restores.resource_class = fakes.FakeResource(None, {}) self.qos_specs = mock.Mock() @@ -923,3 +926,31 @@ class FakeType(object): types = FakeType.create_types(count) return mock.Mock(side_effect=types) + + @staticmethod + def create_one_encryption_type(attrs=None): + """Create a fake encryption type. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with volume_type_id etc. + """ + attrs = attrs or {} + + # Set default attributes. + encryption_info = { + "volume_type_id": 'type-id-' + uuid.uuid4().hex, + 'provider': 'LuksEncryptor', + 'cipher': None, + 'key_size': None, + 'control_location': 'front-end', + } + + # Overwrite default attributes. + encryption_info.update(attrs) + + encryption_type = fakes.FakeResource( + info=copy.deepcopy(encryption_info), + loaded=True) + return encryption_type diff --git a/openstackclient/tests/unit/volume/v2/test_type.py b/openstackclient/tests/unit/volume/v2/test_type.py index cec01bd..4023d55 100644 --- a/openstackclient/tests/unit/volume/v2/test_type.py +++ b/openstackclient/tests/unit/volume/v2/test_type.py @@ -36,6 +36,10 @@ class TestType(volume_fakes.TestVolume): self.app.client_manager.volume.volume_type_access) self.types_access_mock.reset_mock() + self.encryption_types_mock = ( + self.app.client_manager.volume.volume_encryption_types) + self.encryption_types_mock.reset_mock() + self.projects_mock = self.app.client_manager.identity.projects self.projects_mock.reset_mock() @@ -131,6 +135,68 @@ class TestTypeCreate(TestType): self.cmd.take_action, parsed_args) + def test_type_create_with_encryption(self): + encryption_info = { + 'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + '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}) + self.types_mock.create.return_value = self.new_volume_type + self.encryption_types_mock.create.return_value = encryption_type + encryption_columns = ( + 'description', + 'encryption', + 'id', + 'is_public', + 'name', + ) + encryption_data = ( + self.new_volume_type.description, + utils.format_dict(encryption_info), + self.new_volume_type.id, + True, + self.new_volume_type.name, + ) + arglist = [ + '--encryption-provider', 'LuksEncryptor', + '--encryption-cipher', 'aes-xts-plain64', + '--encryption-key-size', '128', + '--encryption-control-location', 'front-end', + self.new_volume_type.name, + ] + verifylist = [ + ('encryption_provider', 'LuksEncryptor'), + ('encryption_cipher', 'aes-xts-plain64'), + ('encryption_key_size', 128), + ('encryption_control_location', 'front-end'), + ('name', self.new_volume_type.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.types_mock.create.assert_called_with( + self.new_volume_type.name, + description=None, + ) + body = { + 'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': 128, + 'control_location': 'front-end', + } + self.encryption_types_mock.create.assert_called_with( + self.new_volume_type, + body, + ) + self.assertEqual(encryption_columns, columns) + self.assertEqual(encryption_data, data) + class TestTypeDelete(TestType): @@ -305,6 +371,7 @@ class TestTypeList(TestType): "--default", ] verifylist = [ + ("encryption_type", False), ("long", False), ("private", False), ("public", False), @@ -317,6 +384,47 @@ class TestTypeList(TestType): self.assertEqual(self.columns, columns) self.assertEqual(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_info = { + 'provider': 'LuksEncryptor', + 'cipher': None, + 'key_size': None, + 'control_location': 'front-end', + } + encryption_columns = self.columns + [ + "Encryption", + ] + encryption_data = [] + encryption_data.append(( + self.volume_types[0].id, + self.volume_types[0].name, + self.volume_types[0].is_public, + utils.format_dict(encryption_info), + )) + encryption_data.append(( + self.volume_types[1].id, + self.volume_types[1].name, + self.volume_types[1].is_public, + '-', + )) + + self.encryption_types_mock.list.return_value = [encryption_type] + arglist = [ + "--encryption-type", + ] + verifylist = [ + ("encryption_type", True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.encryption_types_mock.list.assert_called_once_with() + self.types_mock.list.assert_called_once_with(is_public=None) + self.assertEqual(encryption_columns, columns) + self.assertEqual(encryption_data, list(data)) + class TestTypeSet(TestType): @@ -331,6 +439,8 @@ class TestTypeSet(TestType): # Return a project self.projects_mock.get.return_value = self.project + self.encryption_types_mock.create.return_value = None + self.encryption_types_mock.update.return_value = None # Get the command object to test self.cmd = volume_type.SetVolumeType(self.app, None) @@ -454,6 +564,107 @@ class TestTypeSet(TestType): self.project.id, ) + def test_type_set_new_encryption(self): + self.encryption_types_mock.update.side_effect = ( + exceptions.NotFound('NotFound')) + arglist = [ + '--encryption-provider', 'LuksEncryptor', + '--encryption-cipher', 'aes-xts-plain64', + '--encryption-key-size', '128', + '--encryption-control-location', 'front-end', + self.volume_type.id, + ] + verifylist = [ + ('encryption_provider', 'LuksEncryptor'), + ('encryption_cipher', 'aes-xts-plain64'), + ('encryption_key_size', 128), + ('encryption_control_location', 'front-end'), + ('volume_type', self.volume_type.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + body = { + 'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': 128, + 'control_location': 'front-end', + } + self.encryption_types_mock.update.assert_called_with( + self.volume_type, + body, + ) + self.encryption_types_mock.create.assert_called_with( + self.volume_type, + body, + ) + self.assertIsNone(result) + + @mock.patch.object(utils, 'find_resource') + def test_type_set_existing_encryption(self, mock_find): + mock_find.side_effect = [self.volume_type, + "existing_encryption_type"] + arglist = [ + '--encryption-provider', 'LuksEncryptor', + '--encryption-cipher', 'aes-xts-plain64', + '--encryption-control-location', 'front-end', + self.volume_type.id, + ] + verifylist = [ + ('encryption_provider', 'LuksEncryptor'), + ('encryption_cipher', 'aes-xts-plain64'), + ('encryption_control_location', 'front-end'), + ('volume_type', self.volume_type.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + body = { + 'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'control_location': 'front-end', + } + self.encryption_types_mock.update.assert_called_with( + self.volume_type, + body, + ) + self.encryption_types_mock.create.assert_not_called() + self.assertIsNone(result) + + def test_type_set_new_encryption_without_provider(self): + self.encryption_types_mock.update.side_effect = ( + exceptions.NotFound('NotFound')) + arglist = [ + '--encryption-cipher', 'aes-xts-plain64', + '--encryption-key-size', '128', + '--encryption-control-location', 'front-end', + self.volume_type.id, + ] + verifylist = [ + ('encryption_cipher', 'aes-xts-plain64'), + ('encryption_key_size', 128), + ('encryption_control_location', 'front-end'), + ('volume_type', self.volume_type.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("Command Failed: One or more of" + " the operations failed", + str(e)) + body = { + 'cipher': 'aes-xts-plain64', + 'key_size': 128, + 'control_location': 'front-end', + } + self.encryption_types_mock.update.assert_called_with( + self.volume_type, + body, + ) + self.encryption_types_mock.create.assert_not_called() + class TestTypeShow(TestType): @@ -489,6 +700,7 @@ class TestTypeShow(TestType): self.volume_type.id ] verifylist = [ + ("encryption_type", False), ("volume_type", self.volume_type.id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -564,6 +776,52 @@ class TestTypeShow(TestType): ) self.assertEqual(private_type_data, data) + def test_type_show_with_encryption(self): + encryption_type = volume_fakes.FakeType.create_one_encryption_type() + encryption_info = { + 'provider': 'LuksEncryptor', + 'cipher': None, + 'key_size': None, + 'control_location': 'front-end', + } + self.volume_type = volume_fakes.FakeType.create_one_type( + attrs={'encryption': encryption_info}) + self.types_mock.get.return_value = self.volume_type + self.encryption_types_mock.get.return_value = encryption_type + encryption_columns = ( + 'access_project_ids', + 'description', + 'encryption', + 'id', + 'is_public', + 'name', + 'properties', + ) + encryption_data = ( + None, + self.volume_type.description, + utils.format_dict(encryption_info), + self.volume_type.id, + True, + self.volume_type.name, + utils.format_dict(self.volume_type.extra_specs) + ) + arglist = [ + '--encryption-type', + self.volume_type.id + ] + verifylist = [ + ('encryption_type', True), + ("volume_type", self.volume_type.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.types_mock.get.assert_called_with(self.volume_type.id) + self.encryption_types_mock.get.assert_called_with(self.volume_type.id) + self.assertEqual(encryption_columns, columns) + self.assertEqual(encryption_data, data) + class TestTypeUnset(TestType): @@ -625,6 +883,7 @@ class TestTypeUnset(TestType): self.volume_type.id, ] verifylist = [ + ('encryption_type', False), ('project', ''), ('volume_type', self.volume_type.id), ] @@ -633,7 +892,7 @@ class TestTypeUnset(TestType): result = self.cmd.take_action(parsed_args) self.assertIsNone(result) - + self.encryption_types_mock.delete.assert_not_called() self.assertFalse(self.types_access_mock.remove_project_access.called) def test_type_unset_failed_with_missing_volume_type_argument(self): @@ -649,3 +908,18 @@ class TestTypeUnset(TestType): self.cmd, arglist, verifylist) + + def test_type_unset_encryption_type(self): + arglist = [ + '--encryption-type', + self.volume_type.id, + ] + verifylist = [ + ('encryption_type', True), + ('volume_type', self.volume_type.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.encryption_types_mock.delete.assert_called_with(self.volume_type) + self.assertIsNone(result) diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index 4fef9dd..fbe719f 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -1368,6 +1368,24 @@ class TestVolumeSet(TestVolume): # Get the command object to test self.cmd = volume.SetVolume(self.app, None) + def test_volume_set_property(self): + arglist = [ + '--property', 'a=b', + '--property', 'c=d', + self.new_volume.id, + ] + verifylist = [ + ('property', {'a': 'b', 'c': 'd'}), + ('volume', self.new_volume.id), + ('bootable', False), + ('non_bootable', False) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + self.volumes_mock.set_metadata.assert_called_with( + self.new_volume.id, parsed_args.property) + def test_volume_set_image_property(self): arglist = [ '--image-property', 'Alpha=a', diff --git a/openstackclient/tests/unit/volume/v2/test_volume_host.py b/openstackclient/tests/unit/volume/v2/test_volume_host.py index aad7bb0..b024329 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume_host.py +++ b/openstackclient/tests/unit/volume/v2/test_volume_host.py @@ -35,6 +35,7 @@ class TestVolumeHostSet(TestVolumeHost): self.host_mock.freeze_host.return_value = None self.host_mock.thaw_host.return_value = None + # Get the command object to mock self.cmd = volume_host.SetVolumeHost(self.app, None) def test_volume_host_set_nothing(self): @@ -84,3 +85,33 @@ class TestVolumeHostSet(TestVolumeHost): self.host_mock.freeze_host.assert_called_with(self.service.host) self.host_mock.thaw_host.assert_not_called() self.assertIsNone(result) + + +class TestVolumeHostFailover(TestVolumeHost): + + service = host_fakes.FakeService.create_one_service() + + def setUp(self): + super(TestVolumeHostFailover, self).setUp() + + self.host_mock.failover_host.return_value = None + + # Get the command object to mock + self.cmd = volume_host.FailoverVolumeHost(self.app, None) + + def test_volume_host_failover(self): + arglist = [ + '--volume-backend', 'backend_test', + self.service.host, + ] + verifylist = [ + ('volume_backend', 'backend_test'), + ('host', self.service.host), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.host_mock.failover_host.assert_called_with( + self.service.host, 'backend_test') + self.assertIsNone(result) diff --git a/openstackclient/tests/unit/volume/v3/__init__.py b/openstackclient/tests/unit/volume/v3/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/openstackclient/tests/unit/volume/v3/__init__.py diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py index ade5a95..c4b0dfc 100644 --- a/openstackclient/volume/client.py +++ b/openstackclient/volume/client.py @@ -27,7 +27,8 @@ API_VERSION_OPTION = 'os_volume_api_version' API_NAME = "volume" API_VERSIONS = { "1": "cinderclient.v1.client.Client", - "2": "cinderclient.v2.client.Client" + "2": "cinderclient.v2.client.Client", + "3": "cinderclient.v3.client.Client", } diff --git a/openstackclient/volume/v1/qos_specs.py b/openstackclient/volume/v1/qos_specs.py index b824b35..bae8c1a 100644 --- a/openstackclient/volume/v1/qos_specs.py +++ b/openstackclient/volume/v1/qos_specs.py @@ -94,7 +94,9 @@ class CreateQos(command.ShowOne): specs.update(parsed_args.property) qos_spec = volume_client.qos_specs.create(parsed_args.name, specs) - + qos_spec._info.update( + {'properties': utils.format_dict(qos_spec._info.pop('specs'))} + ) return zip(*sorted(six.iteritems(qos_spec._info))) @@ -190,8 +192,10 @@ class ListQos(command.Lister): for association in qos_associations] qos._info.update({'associations': associations}) + display_columns = ( + 'ID', 'Name', 'Consumer', 'Associations', 'Properties') columns = ('ID', 'Name', 'Consumer', 'Associations', 'Specs') - return (columns, + return (display_columns, (utils.get_dict_properties( s._info, columns, formatters={ @@ -254,7 +258,8 @@ class ShowQos(command.ShowOne): qos_spec._info.update({ 'associations': utils.format_list(associations) }) - qos_spec._info.update({'specs': utils.format_dict(qos_spec.specs)}) + qos_spec._info.update( + {'properties': utils.format_dict(qos_spec._info.pop('specs'))}) return zip(*sorted(six.iteritems(qos_spec._info))) diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index 739484d..8e1097f 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -440,6 +440,15 @@ class SetVolume(command.Command): help=_('Extend volume size in GB'), ) parser.add_argument( + "--no-property", + dest="no_property", + action="store_true", + help=_("Remove all properties from <volume> " + "(specify both --no-property and --property to " + "remove the current properties before setting " + "new properties.)"), + ) + parser.add_argument( '--property', metavar='<key=value>', action=parseractions.KeyValueAction, @@ -489,6 +498,15 @@ class SetVolume(command.Command): except Exception as e: LOG.error(_("Failed to set volume size: %s"), e) result += 1 + + if parsed_args.no_property: + try: + volume_client.volumes.delete_metadata( + volume.id, volume.metadata.keys()) + except Exception as e: + LOG.error(_("Failed to clean volume properties: %s"), e) + result += 1 + if parsed_args.property: try: volume_client.volumes.set_metadata( diff --git a/openstackclient/volume/v1/volume_type.py b/openstackclient/volume/v1/volume_type.py index 8adce32..f9baa5b 100644 --- a/openstackclient/volume/v1/volume_type.py +++ b/openstackclient/volume/v1/volume_type.py @@ -29,6 +29,26 @@ from openstackclient.i18n import _ LOG = logging.getLogger(__name__) +def _create_encryption_type(volume_client, volume_type, parsed_args): + if not parsed_args.encryption_provider: + msg = _("'--encryption-provider' should be specified while " + "creating a new encryption type") + raise exceptions.CommandError(msg) + # set the default of control location while creating + control_location = 'front-end' + if parsed_args.encryption_control_location: + control_location = parsed_args.encryption_control_location + body = { + 'provider': parsed_args.encryption_provider, + 'cipher': parsed_args.encryption_cipher, + 'key_size': parsed_args.encryption_key_size, + 'control_location': control_location + } + encryption = volume_client.volume_encryption_types.create( + volume_type, body) + return encryption + + class CreateVolumeType(command.ShowOne): _description = _("Create new volume type") @@ -46,6 +66,42 @@ class CreateVolumeType(command.ShowOne): help=_('Set a property on this volume type ' '(repeat option to set multiple properties)'), ) + # TODO(Huanxuan Ao): Add choices for each "--encryption-*" option. + parser.add_argument( + '--encryption-provider', + metavar='<provider>', + help=_('Set the class that provides encryption support for ' + 'this volume type (e.g "LuksEncryptor") (admin only) ' + '(This option is required when setting encryption type ' + 'of a volume. Consider using other encryption options ' + 'such as: "--encryption-cipher", "--encryption-key-size" ' + 'and "--encryption-control-location")'), + ) + parser.add_argument( + '--encryption-cipher', + metavar='<cipher>', + help=_('Set the encryption algorithm or mode for this ' + 'volume type (e.g "aes-xts-plain64") (admin only)'), + ) + parser.add_argument( + '--encryption-key-size', + metavar='<key-size>', + type=int, + help=_('Set the size of the encryption key of this ' + 'volume type (e.g "128" or "256") (admin only)'), + ) + parser.add_argument( + '--encryption-control-location', + metavar='<control-location>', + choices=['front-end', 'back-end'], + help=_('Set the notional service where the encryption is ' + 'performed ("front-end" or "back-end") (admin only) ' + '(The default value for this option is "front-end" ' + 'when setting encryption type of a volume. Consider ' + 'using other encryption options such as: ' + '"--encryption-cipher", "--encryption-key-size" and ' + '"--encryption-provider")'), + ) return parser def take_action(self, parsed_args): @@ -55,6 +111,21 @@ class CreateVolumeType(command.ShowOne): if parsed_args.property: result = volume_type.set_keys(parsed_args.property) volume_type._info.update({'properties': utils.format_dict(result)}) + if (parsed_args.encryption_provider or + parsed_args.encryption_cipher or + parsed_args.encryption_key_size or + parsed_args.encryption_control_location): + try: + # create new encryption + encryption = _create_encryption_type( + volume_client, volume_type, parsed_args) + except Exception as e: + LOG.error(_("Failed to set encryption information for this " + "volume type: %s"), e) + # add encryption info in result + encryption._info.pop("volume_type_id", None) + volume_type._info.update( + {'encryption': utils.format_dict(encryption._info)}) volume_type._info.pop("os-volume-type-access:is_public", None) return zip(*sorted(six.iteritems(volume_type._info))) @@ -107,20 +178,58 @@ class ListVolumeType(command.Lister): default=False, help=_('List additional fields in output') ) + parser.add_argument( + "--encryption-type", + action="store_true", + help=_("Display encryption information for each volume type " + "(admin only)"), + ) return parser def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume if parsed_args.long: - columns = ('ID', 'Name', 'Is Public', 'Extra Specs') - column_headers = ('ID', 'Name', 'Is Public', 'Properties') + columns = ['ID', 'Name', 'Is Public', 'Extra Specs'] + column_headers = ['ID', 'Name', 'Is Public', 'Properties'] else: - columns = ('ID', 'Name', 'Is Public') - column_headers = columns - data = self.app.client_manager.volume.volume_types.list() + columns = ['ID', 'Name', 'Is Public'] + column_headers = ['ID', 'Name', 'Is Public'] + data = volume_client.volume_types.list() + + def _format_encryption_info(type_id, encryption_data=None): + encryption_data = encryption + encryption_info = '-' + if type_id in encryption_data.keys(): + encryption_info = encryption_data[type_id] + return encryption_info + + if parsed_args.encryption_type: + encryption = {} + for d in volume_client.volume_encryption_types.list(): + volume_type_id = d._info['volume_type_id'] + # remove some redundant information + del_key = [ + 'deleted', + 'created_at', + 'updated_at', + 'deleted_at', + 'volume_type_id' + ] + for key in del_key: + d._info.pop(key, None) + # save the encryption information with their volume type ID + encryption[volume_type_id] = utils.format_dict(d._info) + # We need to get volume type ID, then show encryption + # information according to the ID, so use "id" to keep + # difference to the real "ID" column. + columns += ['id'] + column_headers += ['Encryption'] + return (column_headers, (utils.get_item_properties( s, columns, - formatters={'Extra Specs': utils.format_dict}, + formatters={'Extra Specs': utils.format_dict, + 'id': _format_encryption_info}, ) for s in data)) @@ -141,6 +250,42 @@ class SetVolumeType(command.Command): help=_('Set a property on this volume type ' '(repeat option to set multiple properties)'), ) + # TODO(Huanxuan Ao): Add choices for each "--encryption-*" option. + parser.add_argument( + '--encryption-provider', + metavar='<provider>', + help=_('Set the class that provides encryption support for ' + 'this volume type (e.g "LuksEncryptor") (admin only) ' + '(This option is required when setting encryption type ' + 'of a volume. Consider using other encryption options ' + 'such as: "--encryption-cipher", "--encryption-key-size" ' + 'and "--encryption-control-location")'), + ) + parser.add_argument( + '--encryption-cipher', + metavar='<cipher>', + help=_('Set the encryption algorithm or mode for this ' + 'volume type (e.g "aes-xts-plain64") (admin only)'), + ) + parser.add_argument( + '--encryption-key-size', + metavar='<key-size>', + type=int, + help=_('Set the size of the encryption key of this ' + 'volume type (e.g "128" or "256") (admin only)'), + ) + parser.add_argument( + '--encryption-control-location', + metavar='<control-location>', + choices=['front-end', 'back-end'], + help=_('Set the notional service where the encryption is ' + 'performed ("front-end" or "back-end") (admin only) ' + '(The default value for this option is "front-end" ' + 'when setting encryption type of a volume. Consider ' + 'using other encryption options such as: ' + '"--encryption-cipher", "--encryption-key-size" and ' + '"--encryption-provider")'), + ) return parser def take_action(self, parsed_args): @@ -148,8 +293,29 @@ class SetVolumeType(command.Command): volume_type = utils.find_resource( volume_client.volume_types, parsed_args.volume_type) + result = 0 if parsed_args.property: - volume_type.set_keys(parsed_args.property) + try: + volume_type.set_keys(parsed_args.property) + except Exception as e: + LOG.error(_("Failed to set volume type property: %s"), e) + result += 1 + + if (parsed_args.encryption_provider or + parsed_args.encryption_cipher or + parsed_args.encryption_key_size or + parsed_args.encryption_control_location): + try: + _create_encryption_type( + volume_client, volume_type, parsed_args) + except Exception as e: + LOG.error(_("Failed to set encryption information for this " + "volume type: %s"), e) + result += 1 + + if result > 0: + raise exceptions.CommandError(_("Command Failed: One or more of" + " the operations failed")) class ShowVolumeType(command.ShowOne): @@ -162,6 +328,12 @@ class ShowVolumeType(command.ShowOne): metavar="<volume-type>", help=_("Volume type to display (name or ID)") ) + parser.add_argument( + "--encryption-type", + action="store_true", + help=_("Display encryption information of this volume type " + "(admin only)"), + ) return parser def take_action(self, parsed_args): @@ -170,6 +342,17 @@ class ShowVolumeType(command.ShowOne): volume_client.volume_types, parsed_args.volume_type) properties = utils.format_dict(volume_type._info.pop('extra_specs')) volume_type._info.update({'properties': properties}) + if parsed_args.encryption_type: + # show encryption type information for this volume type + try: + encryption = volume_client.volume_encryption_types.get( + volume_type.id) + encryption._info.pop("volume_type_id", None) + volume_type._info.update( + {'encryption': utils.format_dict(encryption._info)}) + except Exception as e: + LOG.error(_("Failed to display the encryption information " + "of this volume type: %s"), e) volume_type._info.pop("os-volume-type-access:is_public", None) return zip(*sorted(six.iteritems(volume_type._info))) @@ -191,6 +374,12 @@ class UnsetVolumeType(command.Command): help=_('Remove a property from this volume type ' '(repeat option to remove multiple properties)'), ) + parser.add_argument( + "--encryption-type", + action="store_true", + help=_("Remove the encryption type for this volume type " + "(admin oly)"), + ) return parser def take_action(self, parsed_args): @@ -200,5 +389,21 @@ class UnsetVolumeType(command.Command): parsed_args.volume_type, ) + result = 0 if parsed_args.property: - volume_type.unset_keys(parsed_args.property) + try: + volume_type.unset_keys(parsed_args.property) + except Exception as e: + LOG.error(_("Failed to unset volume type property: %s"), e) + result += 1 + if parsed_args.encryption_type: + try: + volume_client.volume_encryption_types.delete(volume_type) + except Exception as e: + LOG.error(_("Failed to remove the encryption type for this " + "volume type: %s"), e) + result += 1 + + if result > 0: + raise exceptions.CommandError(_("Command Failed: One or more of" + " the operations failed")) diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 78db261..c361d70 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -524,6 +524,15 @@ class SetVolume(command.Command): help=_('New volume description'), ) parser.add_argument( + "--no-property", + dest="no_property", + action="store_true", + help=_("Remove all properties from <volume> " + "(specify both --no-property and --property to " + "remove the current properties before setting " + "new properties.)"), + ) + parser.add_argument( '--property', metavar='<key=value>', action=parseractions.KeyValueAction, @@ -561,7 +570,7 @@ class SetVolume(command.Command): choices=['never', 'on-demand'], help=_('Migration policy while re-typing volume ' '("never" or "on-demand", default is "never" ) ' - '(available only when "--type" option is specified)'), + '(available only when --type option is specified)'), ) bootable_group = parser.add_mutually_exclusive_group() bootable_group.add_argument( @@ -607,6 +616,14 @@ class SetVolume(command.Command): LOG.error(_("Failed to set volume size: %s"), e) result += 1 + if parsed_args.no_property: + try: + volume_client.volumes.delete_metadata( + volume.id, volume.metadata.keys()) + except Exception as e: + LOG.error(_("Failed to clean volume properties: %s"), e) + result += 1 + if parsed_args.property: try: volume_client.volumes.set_metadata( diff --git a/openstackclient/volume/v2/volume_host.py b/openstackclient/volume/v2/volume_host.py index 376e502..2fdeb96 100644 --- a/openstackclient/volume/v2/volume_host.py +++ b/openstackclient/volume/v2/volume_host.py @@ -19,6 +19,31 @@ from osc_lib.command import command from openstackclient.i18n import _ +class FailoverVolumeHost(command.Command): + _description = _("Failover volume host to different backend") + + def get_parser(self, prog_name): + parser = super(FailoverVolumeHost, self).get_parser(prog_name) + parser.add_argument( + "host", + metavar="<host-name>", + help=_("Name of volume host") + ) + parser.add_argument( + "--volume-backend", + metavar="<backend-id>", + required=True, + help=_("The ID of the volume backend replication " + "target where the host will failover to (required)") + ) + return parser + + def take_action(self, parsed_args): + service_client = self.app.client_manager.volume + service_client.services.failover_host(parsed_args.host, + parsed_args.volume_backend) + + class SetVolumeHost(command.Command): _description = _("Set volume host properties") @@ -33,12 +58,12 @@ class SetVolumeHost(command.Command): enabled_group.add_argument( "--disable", action="store_true", - help=_("Freeze and disable the specified volume host.") + help=_("Freeze and disable the specified volume host") ) enabled_group.add_argument( "--enable", action="store_true", - help=_("Thaw and enable the specified volume host.") + help=_("Thaw and enable the specified volume host") ) return parser diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 4646678..8d2901f 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -29,6 +29,44 @@ from openstackclient.identity import common as identity_common LOG = logging.getLogger(__name__) +def _create_encryption_type(volume_client, volume_type, parsed_args): + if not parsed_args.encryption_provider: + msg = _("'--encryption-provider' should be specified while " + "creating a new encryption type") + raise exceptions.CommandError(msg) + # set the default of control location while creating + control_location = 'front-end' + if parsed_args.encryption_control_location: + control_location = parsed_args.encryption_control_location + body = { + 'provider': parsed_args.encryption_provider, + 'cipher': parsed_args.encryption_cipher, + 'key_size': parsed_args.encryption_key_size, + 'control_location': control_location + } + encryption = volume_client.volume_encryption_types.create( + volume_type, body) + return encryption + + +def _set_encryption_type(volume_client, volume_type, parsed_args): + # update the existing encryption type + body = {} + for attr in ['provider', 'cipher', 'key_size', 'control_location']: + info = getattr(parsed_args, 'encryption_' + attr, None) + if info is not None: + body[attr] = info + try: + volume_client.volume_encryption_types.update(volume_type, body) + except Exception as e: + if type(e).__name__ == 'NotFound': + # create new encryption type + LOG.warning(_("No existing encryption type found, creating " + "new encryption type for this volume type ...")) + _create_encryption_type( + volume_client, volume_type, parsed_args) + + class CreateVolumeType(command.ShowOne): _description = _("Create new volume type") @@ -70,6 +108,42 @@ class CreateVolumeType(command.ShowOne): help=_("Allow <project> to access private type (name or ID) " "(Must be used with --private option)"), ) + # TODO(Huanxuan Ao): Add choices for each "--encryption-*" option. + parser.add_argument( + '--encryption-provider', + metavar='<provider>', + help=_('Set the class that provides encryption support for ' + 'this volume type (e.g "LuksEncryptor") (admin only) ' + '(This option is required when setting encryption type ' + 'of a volume. Consider using other encryption options ' + 'such as: "--encryption-cipher", "--encryption-key-size" ' + 'and "--encryption-control-location")'), + ) + parser.add_argument( + '--encryption-cipher', + metavar='<cipher>', + help=_('Set the encryption algorithm or mode for this ' + 'volume type (e.g "aes-xts-plain64") (admin only)'), + ) + parser.add_argument( + '--encryption-key-size', + metavar='<key-size>', + type=int, + help=_('Set the size of the encryption key of this ' + 'volume type (e.g "128" or "256") (admin only)'), + ) + parser.add_argument( + '--encryption-control-location', + metavar='<control-location>', + choices=['front-end', 'back-end'], + help=_('Set the notional service where the encryption is ' + 'performed ("front-end" or "back-end") (admin only) ' + '(The default value for this option is "front-end" ' + 'when setting encryption type of a volume. Consider ' + 'using other encryption options such as: ' + '"--encryption-cipher", "--encryption-key-size" and ' + '"--encryption-provider")'), + ) identity_common.add_project_domain_option_to_parser(parser) return parser @@ -110,6 +184,21 @@ class CreateVolumeType(command.ShowOne): if parsed_args.property: result = volume_type.set_keys(parsed_args.property) volume_type._info.update({'properties': utils.format_dict(result)}) + if (parsed_args.encryption_provider or + parsed_args.encryption_cipher or + parsed_args.encryption_key_size or + parsed_args.encryption_control_location): + try: + # create new encryption + encryption = _create_encryption_type( + volume_client, volume_type, parsed_args) + except Exception as e: + LOG.error(_("Failed to set encryption information for this " + "volume type: %s"), e) + # add encryption info in result + encryption._info.pop("volume_type_id", None) + volume_type._info.update( + {'encryption': utils.format_dict(encryption._info)}) volume_type._info.pop("os-volume-type-access:is_public", None) return zip(*sorted(six.iteritems(volume_type._info))) @@ -179,6 +268,12 @@ class ListVolumeType(command.Lister): action="store_true", help=_("List only private types (admin only)") ) + parser.add_argument( + "--encryption-type", + action="store_true", + help=_("Display encryption information for each volume type " + "(admin only)"), + ) return parser def take_action(self, parsed_args): @@ -189,7 +284,7 @@ class ListVolumeType(command.Lister): 'ID', 'Name', 'Is Public', 'Description', 'Properties'] else: columns = ['ID', 'Name', 'Is Public'] - column_headers = columns + column_headers = ['ID', 'Name', 'Is Public'] if parsed_args.default: data = [volume_client.volume_types.default()] else: @@ -200,10 +295,41 @@ class ListVolumeType(command.Lister): is_public = False data = volume_client.volume_types.list( is_public=is_public) + + def _format_encryption_info(type_id, encryption_data=None): + encryption_data = encryption + encryption_info = '-' + if type_id in encryption_data.keys(): + encryption_info = encryption_data[type_id] + return encryption_info + + if parsed_args.encryption_type: + encryption = {} + for d in volume_client.volume_encryption_types.list(): + volume_type_id = d._info['volume_type_id'] + # remove some redundant information + del_key = [ + 'deleted', + 'created_at', + 'updated_at', + 'deleted_at', + 'volume_type_id' + ] + for key in del_key: + d._info.pop(key, None) + # save the encryption information with their volume type ID + encryption[volume_type_id] = utils.format_dict(d._info) + # We need to get volume type ID, then show encryption + # information according to the ID, so use "id" to keep + # difference to the real "ID" column. + columns += ['id'] + column_headers += ['Encryption'] + return (column_headers, (utils.get_item_properties( s, columns, - formatters={'Extra Specs': utils.format_dict}, + formatters={'Extra Specs': utils.format_dict, + 'id': _format_encryption_info}, ) for s in data)) @@ -241,7 +367,43 @@ class SetVolumeType(command.Command): '(admin only)'), ) identity_common.add_project_domain_option_to_parser(parser) - + # TODO(Huanxuan Ao): Add choices for each "--encryption-*" option. + parser.add_argument( + '--encryption-provider', + metavar='<provider>', + help=_('Set the class that provides encryption support for ' + 'this volume type (e.g "LuksEncryptor") (admin only) ' + '(This option is required when setting encryption type ' + 'of a volume for the first time. Consider using other ' + 'encryption options such as: "--encryption-cipher", ' + '"--encryption-key-size" and ' + '"--encryption-control-location")'), + ) + parser.add_argument( + '--encryption-cipher', + metavar='<cipher>', + help=_('Set the encryption algorithm or mode for this ' + 'volume type (e.g "aes-xts-plain64") (admin only)'), + ) + parser.add_argument( + '--encryption-key-size', + metavar='<key-size>', + type=int, + help=_('Set the size of the encryption key of this ' + 'volume type (e.g "128" or "256") (admin only)'), + ) + parser.add_argument( + '--encryption-control-location', + metavar='<control-location>', + choices=['front-end', 'back-end'], + help=_('Set the notional service where the encryption is ' + 'performed ("front-end" or "back-end") (admin only) ' + '(The default value for this option is "front-end" ' + 'when setting encryption type of a volume for the ' + 'first time. Consider using other encryption options ' + 'such as: "--encryption-cipher", "--encryption-key-size" ' + 'and "--encryption-provider")'), + ) return parser def take_action(self, parsed_args): @@ -290,6 +452,17 @@ class SetVolumeType(command.Command): "project: %s"), e) result += 1 + if (parsed_args.encryption_provider or + parsed_args.encryption_cipher or + parsed_args.encryption_key_size or + parsed_args.encryption_control_location): + try: + _set_encryption_type(volume_client, volume_type, parsed_args) + except Exception as e: + LOG.error(_("Failed to set encryption information for this " + "volume type: %s"), e) + result += 1 + if result > 0: raise exceptions.CommandError(_("Command Failed: One or more of" " the operations failed")) @@ -305,6 +478,12 @@ class ShowVolumeType(command.ShowOne): metavar="<volume-type>", help=_("Volume type to display (name or ID)") ) + parser.add_argument( + "--encryption-type", + action="store_true", + help=_("Display encryption information of this volume type " + "(admin only)"), + ) return parser def take_action(self, parsed_args): @@ -329,6 +508,17 @@ class ShowVolumeType(command.ShowOne): '%(type)s: %(e)s') LOG.error(msg % {'type': volume_type.id, 'e': e}) volume_type._info.update({'access_project_ids': access_project_ids}) + if parsed_args.encryption_type: + # show encryption type information for this volume type + try: + encryption = volume_client.volume_encryption_types.get( + volume_type.id) + encryption._info.pop("volume_type_id", None) + volume_type._info.update( + {'encryption': utils.format_dict(encryption._info)}) + except Exception as e: + LOG.error(_("Failed to display the encryption information " + "of this volume type: %s"), e) volume_type._info.pop("os-volume-type-access:is_public", None) return zip(*sorted(six.iteritems(volume_type._info))) @@ -357,7 +547,12 @@ class UnsetVolumeType(command.Command): ' (admin only)'), ) identity_common.add_project_domain_option_to_parser(parser) - + parser.add_argument( + "--encryption-type", + action="store_true", + help=_("Remove the encryption type for this volume type " + "(admin only)"), + ) return parser def take_action(self, parsed_args): @@ -391,6 +586,13 @@ class UnsetVolumeType(command.Command): LOG.error(_("Failed to remove volume type access from " "project: %s"), e) result += 1 + if parsed_args.encryption_type: + try: + volume_client.volume_encryption_types.delete(volume_type) + except Exception as e: + LOG.error(_("Failed to remove the encryption type for this " + "volume type: %s"), e) + result += 1 if result > 0: raise exceptions.CommandError(_("Command Failed: One or more of" diff --git a/openstackclient/volume/v3/__init__.py b/openstackclient/volume/v3/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/openstackclient/volume/v3/__init__.py diff --git a/releasenotes/notes/add-network-auto-allocated-topology-481580f48840bfc4.yaml b/releasenotes/notes/add-network-auto-allocated-topology-481580f48840bfc4.yaml new file mode 100644 index 0000000..df0831a --- /dev/null +++ b/releasenotes/notes/add-network-auto-allocated-topology-481580f48840bfc4.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add support for the ``network auto allocated topology`` command for + creating and deleting auto allocated topologies. + [Blueprint :oscbp:`network-auto-allocated-topology`] diff --git a/releasenotes/notes/add-no-property-f97e4b2f390cec06.yaml b/releasenotes/notes/add-no-property-f97e4b2f390cec06.yaml new file mode 100644 index 0000000..ab50254 --- /dev/null +++ b/releasenotes/notes/add-no-property-f97e4b2f390cec06.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add support to clear/overwrite all flavor properties using + ``--no-property`` option with ``flavor set`` command. + [ Blueprint `allow-overwrite-set-options <https://blueprints.launchpad.net/python-openstackclient/+spec/allow-overwrite-set-options>` _] diff --git a/releasenotes/notes/add-volume-host-failover-8fc77b24533b7fed.yaml b/releasenotes/notes/add-volume-host-failover-8fc77b24533b7fed.yaml new file mode 100644 index 0000000..8630f74 --- /dev/null +++ b/releasenotes/notes/add-volume-host-failover-8fc77b24533b7fed.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``volume host failover`` command. + [Blueprint `cinder-command-support <https://blueprints.launchpad.net/python-openstackclient/+spec/cinder-command-support>`_] diff --git a/releasenotes/notes/bug-1499657-eeb260849febacf3.yaml b/releasenotes/notes/bug-1499657-eeb260849febacf3.yaml new file mode 100644 index 0000000..186b3a8 --- /dev/null +++ b/releasenotes/notes/bug-1499657-eeb260849febacf3.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Fix ``--parents`` and ``--children`` options in ``project show`` command. + [Bug `1499657 <https://bugs.launchpad.net/python-openstackclient/+bug/1499657>`_] diff --git a/releasenotes/notes/bug-1634799-1322227c9b0188ca.yaml b/releasenotes/notes/bug-1634799-1322227c9b0188ca.yaml new file mode 100644 index 0000000..414710d --- /dev/null +++ b/releasenotes/notes/bug-1634799-1322227c9b0188ca.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``--fixed-ip`` option to the ``port list`` command. + [Bug `1634799 <https://bugs.launchpad.net/bugs/1634799>`_] diff --git a/releasenotes/notes/bug-1651117-a1df37e7ea939ba4.yaml b/releasenotes/notes/bug-1651117-a1df37e7ea939ba4.yaml new file mode 100644 index 0000000..d175e4f --- /dev/null +++ b/releasenotes/notes/bug-1651117-a1df37e7ea939ba4.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Add ``--encryption-provider``, ``--encryption-cipher``, ``--encryption-key-size`` + and ``--encryption-control-location`` options to ``volume type set`` and + ``volume type create`` commands. + Add ``--encryption-type`` option to ``volume type unset``, ``volume type list`` + and ``volume type show`` commands. + [Bug `1651117 <https://bugs.launchpad.net/bugs/1651117>`_] diff --git a/releasenotes/notes/bug-1656788-2f5bda2205bc0329.yaml b/releasenotes/notes/bug-1656788-2f5bda2205bc0329.yaml new file mode 100644 index 0000000..1a5fd2c --- /dev/null +++ b/releasenotes/notes/bug-1656788-2f5bda2205bc0329.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fixed the ``port set`` and ``port unset`` command failures + (AttributeError) when ``--security-group`` option is included. + [Bug `1656788 <https://bugs.launchpad.net/python-openstackclient/+bug/1656788>`_] diff --git a/releasenotes/notes/bug-1658614-f84a8cece6f2ef8c.yaml b/releasenotes/notes/bug-1658614-f84a8cece6f2ef8c.yaml new file mode 100644 index 0000000..b69e384 --- /dev/null +++ b/releasenotes/notes/bug-1658614-f84a8cece6f2ef8c.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Fix wrong behavior of parsing plugin service name when the service name end + with keyword ``os``, like: antiddos. That cause the service api version + specified by users don't work. + [Bug `1658614 <https://bugs.launchpad.net/python-openstackclient/+bug/1658614>`_] diff --git a/releasenotes/notes/bug-1661814-1692e68a1d2d9770.yaml b/releasenotes/notes/bug-1661814-1692e68a1d2d9770.yaml new file mode 100644 index 0000000..b321138 --- /dev/null +++ b/releasenotes/notes/bug-1661814-1692e68a1d2d9770.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fix ``module list --all`` command failed, and enhance the related unit + tests and funcational tests. + [Bug `1661814 <https://bugs.launchpad.net/python-openstackclient/+bug/1661814>`_] diff --git a/releasenotes/notes/bug-1663520-d880bfa51ca7b798.yaml b/releasenotes/notes/bug-1663520-d880bfa51ca7b798.yaml new file mode 100644 index 0000000..4c4d7e3 --- /dev/null +++ b/releasenotes/notes/bug-1663520-d880bfa51ca7b798.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - | + Fix ``server create`` command failed when ``--nic`` auto or none. + ``auto`` and ``none`` options was added into --nic argument of server + create command, but that don't work and raise internal error when execute + command. The patch fix that issue. + [Bug `1663520 <https://bugs.launchpad.net/python-openstackclient/+bug/1663520>`_] diff --git a/releasenotes/notes/bug-1665231-3df6d090d137fe4f.yaml b/releasenotes/notes/bug-1665231-3df6d090d137fe4f.yaml new file mode 100644 index 0000000..104ea71 --- /dev/null +++ b/releasenotes/notes/bug-1665231-3df6d090d137fe4f.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Allow ``--default`` and ``--no-default`` options in ``network create`` command to + be recognized when ``--external`` is not present. + [Bug `1665231 <https://bugs.launchpad.net/python-openstackclient/+bug/1665231>`_] diff --git a/releasenotes/notes/bug-1666780-c10010e9061689d3.yaml b/releasenotes/notes/bug-1666780-c10010e9061689d3.yaml new file mode 100644 index 0000000..ed2ee80 --- /dev/null +++ b/releasenotes/notes/bug-1666780-c10010e9061689d3.yaml @@ -0,0 +1,12 @@ +--- +features: + - | + Add ``--group`` option to the ``command list`` command to filter the + commands by group name: ``openstack command list --group volume`` will + list all Volume commands for the selected API version. Use + ``--os-XXXX-api-version`` to select a specific API version for the desired APIs. + + This provides an alternative to searching help output to list the comamnds + available for specific APIs. Note that the ``--group`` argument is used as + a simple substring search in the Command Group column. + [Bug `1666780 <https://bugs.launchpad.net/python-openstackclient/+bug/1666780>`_] diff --git a/releasenotes/notes/bug-1667699-6dad786b128ca8b5.yaml b/releasenotes/notes/bug-1667699-6dad786b128ca8b5.yaml new file mode 100644 index 0000000..329f140 --- /dev/null +++ b/releasenotes/notes/bug-1667699-6dad786b128ca8b5.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Fix the ``Ethertype`` column output of ``security group rule list`` command. + [Bug `1667699 <https://bugs.launchpad.net/python-openstackclient/+bug/1667699>`_] diff --git a/releasenotes/notes/change-098377fd53cce7a0.yaml b/releasenotes/notes/change-098377fd53cce7a0.yaml new file mode 100644 index 0000000..dc41a7b --- /dev/null +++ b/releasenotes/notes/change-098377fd53cce7a0.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + Added support for Volume API v3 for the following block storage command + resources: ``consistency group``, ``consistency group snapshot``, + ``volume``, ``volume backup``, ``volume host``, ``volume snapshot``, + ``volume type``, ``volume qos``, ``volume service``, + ``volume transfer request``. Note that microversion support for Volume API + v3 is not yet implemented, each command will assume the API version is + ``3.0``. diff --git a/releasenotes/notes/network-flavor-command-support-afe3a9da962a09bf.yaml b/releasenotes/notes/network-flavor-command-support-afe3a9da962a09bf.yaml new file mode 100644 index 0000000..eed7525 --- /dev/null +++ b/releasenotes/notes/network-flavor-command-support-afe3a9da962a09bf.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Add ``network flavor create``, ``network flavor delete``, + ``network flavor list``, Add ``network flavor show`` and + ``network flavor set`` command + [Blueprint :oscbp:`neutron-client-flavors`] diff --git a/releasenotes/notes/support-no-property-in-volume-811e67ff4a199eb6.yaml b/releasenotes/notes/support-no-property-in-volume-811e67ff4a199eb6.yaml new file mode 100644 index 0000000..fda425e --- /dev/null +++ b/releasenotes/notes/support-no-property-in-volume-811e67ff4a199eb6.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``--no-property`` option in ``volume set``, this removes all properties from a volume. + [Blueprint `allow-overwrite-set-options <https://blueprints.launchpad.net/python-openstackclient/+spec/allow-overwrite-set-options>`_] diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index 0941837..1dbd112 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,7 +6,9 @@ OpenStackClient Release Notes :maxdepth: 1 unreleased + ocata newton + mitaka 20_releases pre_20_releases @@ -21,6 +23,7 @@ OpenStack release was made is shown below: ================= ======================= OpenStack Release OpenStackClient Release ================= ======================= +Ocata 3.8.1 Newton 3.2.0 Mitaka 2.3.0 Liberty 1.7.3 diff --git a/releasenotes/source/mitaka.rst b/releasenotes/source/mitaka.rst new file mode 100644 index 0000000..0dc585c --- /dev/null +++ b/releasenotes/source/mitaka.rst @@ -0,0 +1,6 @@ +============================= + Mitaka Series Release Notes +============================= + +.. release-notes:: + :branch: origin/stable/mitaka diff --git a/releasenotes/source/ocata.rst b/releasenotes/source/ocata.rst new file mode 100644 index 0000000..ebe62f4 --- /dev/null +++ b/releasenotes/source/ocata.rst @@ -0,0 +1,6 @@ +=================================== + Ocata Series Release Notes +=================================== + +.. release-notes:: + :branch: origin/stable/ocata diff --git a/requirements.txt b/requirements.txt index 422f782..8688e2a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -pbr>=1.8 # Apache-2.0 +pbr>=2.0.0 # Apache-2.0 six>=1.9.0 # MIT Babel>=2.3.4 # BSD @@ -10,8 +10,8 @@ keystoneauth1>=2.18.0 # Apache-2.0 openstacksdk>=0.9.13 # Apache-2.0 osc-lib>=1.2.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 -oslo.utils>=3.18.0 # Apache-2.0 +oslo.utils>=3.20.0 # Apache-2.0 python-glanceclient>=2.5.0 # Apache-2.0 python-keystoneclient>=3.8.0 # Apache-2.0 -python-novaclient!=7.0.0,>=6.0.0 # Apache-2.0 +python-novaclient>=7.1.0 # Apache-2.0 python-cinderclient!=1.7.0,!=1.7.1,>=1.6.0 # Apache-2.0 @@ -360,6 +360,15 @@ openstack.network.v2 = network_agent_set = openstackclient.network.v2.network_agent:SetNetworkAgent network_agent_show = openstackclient.network.v2.network_agent:ShowNetworkAgent + network_auto_allocated_topology_create = openstackclient.network.v2.network_auto_allocated_topology:CreateAutoAllocatedTopology + network_auto_allocated_topology_delete = openstackclient.network.v2.network_auto_allocated_topology:DeleteAutoAllocatedTopology + + network_flavor_create = openstackclient.network.v2.network_flavor:CreateNetworkFlavor + network_flavor_delete = openstackclient.network.v2.network_flavor:DeleteNetworkFlavor + network_flavor_list = openstackclient.network.v2.network_flavor:ListNetworkFlavor + network_flavor_set = openstackclient.network.v2.network_flavor:SetNetworkFlavor + network_flavor_show = openstackclient.network.v2.network_flavor:ShowNetworkFlavor + network_create = openstackclient.network.v2.network:CreateNetwork network_delete = openstackclient.network.v2.network:DeleteNetwork network_list = openstackclient.network.v2.network:ListNetwork @@ -568,6 +577,70 @@ openstack.volume.v2 = volume_backup_set = openstackclient.volume.v2.backup:SetVolumeBackup volume_backup_show = openstackclient.volume.v2.backup:ShowVolumeBackup + volume_host_failover = openstackclient.volume.v2.volume_host:FailoverVolumeHost + volume_host_set = openstackclient.volume.v2.volume_host:SetVolumeHost + + 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 + volume_snapshot_set = openstackclient.volume.v2.volume_snapshot:SetVolumeSnapshot + volume_snapshot_show = openstackclient.volume.v2.volume_snapshot:ShowVolumeSnapshot + volume_snapshot_unset = openstackclient.volume.v2.volume_snapshot:UnsetVolumeSnapshot + + volume_type_create = openstackclient.volume.v2.volume_type:CreateVolumeType + volume_type_delete = openstackclient.volume.v2.volume_type:DeleteVolumeType + volume_type_list = openstackclient.volume.v2.volume_type:ListVolumeType + volume_type_set = openstackclient.volume.v2.volume_type:SetVolumeType + volume_type_show = openstackclient.volume.v2.volume_type:ShowVolumeType + volume_type_unset = openstackclient.volume.v2.volume_type:UnsetVolumeType + + volume_qos_associate = openstackclient.volume.v2.qos_specs:AssociateQos + volume_qos_create = openstackclient.volume.v2.qos_specs:CreateQos + volume_qos_delete = openstackclient.volume.v2.qos_specs:DeleteQos + volume_qos_disassociate = openstackclient.volume.v2.qos_specs:DisassociateQos + volume_qos_list = openstackclient.volume.v2.qos_specs:ListQos + volume_qos_set = openstackclient.volume.v2.qos_specs:SetQos + volume_qos_show = openstackclient.volume.v2.qos_specs:ShowQos + volume_qos_unset = openstackclient.volume.v2.qos_specs:UnsetQos + + volume_service_list = openstackclient.volume.v2.service:ListService + volume_service_set = openstackclient.volume.v2.service:SetService + + volume_transfer_request_accept = openstackclient.volume.v2.volume_transfer_request:AcceptTransferRequest + volume_transfer_request_create = openstackclient.volume.v2.volume_transfer_request:CreateTransferRequest + volume_transfer_request_delete = openstackclient.volume.v2.volume_transfer_request:DeleteTransferRequest + volume_transfer_request_list = openstackclient.volume.v2.volume_transfer_request:ListTransferRequest + volume_transfer_request_show = openstackclient.volume.v2.volume_transfer_request:ShowTransferRequest + +openstack.volume.v3 = + consistency_group_add_volume = openstackclient.volume.v2.consistency_group:AddVolumeToConsistencyGroup + consistency_group_create = openstackclient.volume.v2.consistency_group:CreateConsistencyGroup + consistency_group_delete = openstackclient.volume.v2.consistency_group:DeleteConsistencyGroup + consistency_group_list = openstackclient.volume.v2.consistency_group:ListConsistencyGroup + consistency_group_remove_volume = openstackclient.volume.v2.consistency_group:RemoveVolumeFromConsistencyGroup + consistency_group_set = openstackclient.volume.v2.consistency_group:SetConsistencyGroup + consistency_group_show = openstackclient.volume.v2.consistency_group:ShowConsistencyGroup + + consistency_group_snapshot_create = openstackclient.volume.v2.consistency_group_snapshot:CreateConsistencyGroupSnapshot + 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_create = openstackclient.volume.v2.volume:CreateVolume + volume_delete = openstackclient.volume.v2.volume:DeleteVolume + volume_list = openstackclient.volume.v2.volume:ListVolume + volume_migrate = openstackclient.volume.v2.volume:MigrateVolume + volume_set = openstackclient.volume.v2.volume:SetVolume + volume_show = openstackclient.volume.v2.volume:ShowVolume + volume_unset = openstackclient.volume.v2.volume:UnsetVolume + + volume_backup_create = openstackclient.volume.v2.backup:CreateVolumeBackup + volume_backup_delete = openstackclient.volume.v2.backup:DeleteVolumeBackup + volume_backup_list = openstackclient.volume.v2.backup:ListVolumeBackup + volume_backup_restore = openstackclient.volume.v2.backup:RestoreVolumeBackup + volume_backup_set = openstackclient.volume.v2.backup:SetVolumeBackup + volume_backup_show = openstackclient.volume.v2.backup:ShowVolumeBackup + volume_host_set = openstackclient.volume.v2.volume_host:SetVolumeHost volume_snapshot_create = openstackclient.volume.v2.volume_snapshot:CreateVolumeSnapshot @@ -25,5 +25,5 @@ except ImportError: pass setuptools.setup( - setup_requires=['pbr>=1.8'], + setup_requires=['pbr>=2.0.0'], pbr=True) diff --git a/test-requirements.txt b/test-requirements.txt index 707df3c..f6917e3 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -9,21 +9,22 @@ mock>=2.0 # BSD oslosphinx>=4.7.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 reno>=1.8.0 # Apache-2.0 -requests!=2.12.2,>=2.10.0 # Apache-2.0 +requests!=2.12.2,!=2.13.0,>=2.10.0 # Apache-2.0 requests-mock>=1.1 # Apache-2.0 -sphinx!=1.3b1,<1.4,>=1.2.1 # BSD -stevedore>=1.17.1 # Apache-2.0 +sphinx>=1.5.1 # BSD +stevedore>=1.20.0 # Apache-2.0 os-client-config>=1.22.0 # Apache-2.0 os-testr>=0.8.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testtools>=1.4.0 # MIT -tempest>=12.1.0 # Apache-2.0 +tempest>=14.0.0 # Apache-2.0 osprofiler>=1.4.0 # Apache-2.0 bandit>=1.1.0 # Apache-2.0 wrapt>=1.7.0 # BSD License # Install these to generate sphinx autodocs aodhclient>=0.7.0 # Apache-2.0 +gnocchiclient>=2.7.0 # Apache-2.0 python-barbicanclient>=4.0.0 # Apache-2.0 python-congressclient<2000,>=1.3.0 # Apache-2.0 python-designateclient>=1.5.0 # Apache-2.0 |
