summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames Page <james.page@ubuntu.com>2017-04-21 10:01:48 +0100
committergit-ubuntu importer <ubuntu-devel-discuss@lists.ubuntu.com>2017-04-24 09:03:11 +0000
commit5660bcc29dcc622e9eac58452127a7a988848963 (patch)
treecfd3c5338ff7cc9457f88d439cee1d0e78aee869
parentb03ff8d57f56f3a1923df213eff2e511c5dc151c (diff)
3.9.0-0ubuntu1 (patches unapplied)import/3.9.0-0ubuntu1
Imported using git-ubuntu import.
Notes
Notes: [ Chuck Short ] * New upstream release.
-rw-r--r--debian/changelog7
-rw-r--r--debian/control8
-rw-r--r--doc/source/command-objects/command.rst6
-rw-r--r--doc/source/command-objects/consistency-group.rst4
-rw-r--r--doc/source/command-objects/endpoint.rst4
-rw-r--r--doc/source/command-objects/flavor.rst6
-rw-r--r--doc/source/command-objects/image.rst3
-rw-r--r--doc/source/command-objects/network-auto-allocated-topology.rst69
-rw-r--r--doc/source/command-objects/network-flavor.rst139
-rw-r--r--doc/source/command-objects/network-qos-policy.rst10
-rw-r--r--doc/source/command-objects/network-qos-rule-type.rst2
-rw-r--r--doc/source/command-objects/network-qos-rule.rst10
-rw-r--r--doc/source/command-objects/network-service-provider.rst1
-rw-r--r--doc/source/command-objects/port.rst7
-rw-r--r--doc/source/command-objects/server-group.rst4
-rw-r--r--doc/source/command-objects/server.rst2
-rw-r--r--doc/source/command-objects/volume-host.rst26
-rw-r--r--doc/source/command-objects/volume-type.rst81
-rw-r--r--doc/source/command-objects/volume.rst9
-rw-r--r--doc/source/commands.rst3
-rw-r--r--doc/source/data/cinder.csv104
-rw-r--r--doc/source/data/glance.csv24
-rw-r--r--doc/source/data/keystone.csv37
-rw-r--r--doc/source/data/neutron.csv244
-rw-r--r--doc/source/data/nova.csv140
-rw-r--r--doc/source/data/swift.csv10
-rw-r--r--doc/source/decoder.rst56
-rw-r--r--doc/source/index.rst5
-rw-r--r--doc/source/plugin-commands.rst8
-rw-r--r--doc/source/plugins.rst13
-rw-r--r--openstackclient/common/client_config.py107
-rw-r--r--openstackclient/common/module.py48
-rw-r--r--openstackclient/common/quota.py71
-rw-r--r--openstackclient/compute/v2/aggregate.py4
-rw-r--r--openstackclient/compute/v2/console.py13
-rw-r--r--openstackclient/compute/v2/flavor.py24
-rw-r--r--openstackclient/compute/v2/keypair.py3
-rw-r--r--openstackclient/compute/v2/server.py36
-rw-r--r--openstackclient/compute/v2/server_group.py9
-rw-r--r--openstackclient/compute/v2/service.py3
-rw-r--r--openstackclient/identity/v2_0/ec2creds.py4
-rw-r--r--openstackclient/identity/v2_0/endpoint.py4
-rw-r--r--openstackclient/identity/v2_0/service.py4
-rw-r--r--openstackclient/identity/v3/consumer.py3
-rw-r--r--openstackclient/identity/v3/credential.py4
-rw-r--r--openstackclient/identity/v3/domain.py3
-rw-r--r--openstackclient/identity/v3/ec2creds.py4
-rw-r--r--openstackclient/identity/v3/endpoint.py5
-rw-r--r--openstackclient/identity/v3/federation_protocol.py4
-rw-r--r--openstackclient/identity/v3/group.py26
-rw-r--r--openstackclient/identity/v3/identity_provider.py4
-rw-r--r--openstackclient/identity/v3/mapping.py3
-rw-r--r--openstackclient/identity/v3/policy.py3
-rw-r--r--openstackclient/identity/v3/project.py13
-rw-r--r--openstackclient/identity/v3/region.py3
-rw-r--r--openstackclient/identity/v3/role.py29
-rw-r--r--openstackclient/identity/v3/service.py4
-rw-r--r--openstackclient/identity/v3/service_provider.py4
-rw-r--r--openstackclient/image/v1/image.py5
-rw-r--r--openstackclient/image/v2/image.py5
-rw-r--r--openstackclient/network/client.py5
-rw-r--r--openstackclient/network/v2/floating_ip.py4
-rw-r--r--openstackclient/network/v2/network.py8
-rw-r--r--openstackclient/network/v2/network_auto_allocated_topology.py136
-rw-r--r--openstackclient/network/v2/network_flavor.py247
-rw-r--r--openstackclient/network/v2/network_qos_rule.py3
-rw-r--r--openstackclient/network/v2/network_qos_rule_type.py5
-rw-r--r--openstackclient/network/v2/network_segment.py4
-rw-r--r--openstackclient/network/v2/port.py83
-rw-r--r--openstackclient/network/v2/router.py4
-rw-r--r--openstackclient/network/v2/security_group_rule.py10
-rw-r--r--openstackclient/network/v2/subnet.py4
-rw-r--r--openstackclient/network/v2/subnet_pool.py4
-rw-r--r--openstackclient/shell.py4
-rw-r--r--openstackclient/tests/functional/common/test_availability_zone.py19
-rw-r--r--openstackclient/tests/functional/common/test_configuration.py43
-rw-r--r--openstackclient/tests/functional/common/test_module.py79
-rw-r--r--openstackclient/tests/functional/compute/v2/test_agent.py233
-rw-r--r--openstackclient/tests/functional/compute/v2/test_aggregate.py209
-rw-r--r--openstackclient/tests/functional/compute/v2/test_server.py495
-rw-r--r--openstackclient/tests/functional/compute/v2/test_server_group.py112
-rw-r--r--openstackclient/tests/functional/identity/v3/test_group.py25
-rw-r--r--openstackclient/tests/functional/identity/v3/test_project.py13
-rw-r--r--openstackclient/tests/functional/image/v2/test_image.py2
-rw-r--r--openstackclient/tests/functional/network/v2/test_network.py68
-rw-r--r--openstackclient/tests/functional/network/v2/test_network_flavor.py176
-rw-r--r--openstackclient/tests/functional/network/v2/test_port.py199
-rw-r--r--openstackclient/tests/functional/network/v2/test_router.py235
-rw-r--r--openstackclient/tests/functional/network/v2/test_subnet.py268
-rw-r--r--openstackclient/tests/functional/volume/v1/test_qos.py134
-rw-r--r--openstackclient/tests/functional/volume/v1/test_volume.py247
-rw-r--r--openstackclient/tests/functional/volume/v1/test_volume_type.py71
-rw-r--r--openstackclient/tests/functional/volume/v2/test_qos.py141
-rw-r--r--openstackclient/tests/functional/volume/v2/test_volume.py22
-rw-r--r--openstackclient/tests/functional/volume/v2/test_volume_type.py87
-rw-r--r--openstackclient/tests/functional/volume/v3/__init__.py0
-rw-r--r--openstackclient/tests/functional/volume/v3/common.py23
-rw-r--r--openstackclient/tests/functional/volume/v3/test_qos.py23
-rw-r--r--openstackclient/tests/functional/volume/v3/test_snapshot.py23
-rw-r--r--openstackclient/tests/functional/volume/v3/test_transfer_request.py24
-rw-r--r--openstackclient/tests/functional/volume/v3/test_volume.py23
-rw-r--r--openstackclient/tests/functional/volume/v3/test_volume_type.py23
-rw-r--r--openstackclient/tests/unit/common/test_module.py78
-rw-r--r--openstackclient/tests/unit/common/test_quota.py72
-rw-r--r--openstackclient/tests/unit/compute/v2/test_console.py28
-rw-r--r--openstackclient/tests/unit/compute/v2/test_flavor.py17
-rw-r--r--openstackclient/tests/unit/compute/v2/test_server.py146
-rw-r--r--openstackclient/tests/unit/compute/v2/test_server_group.py37
-rw-r--r--openstackclient/tests/unit/fakes.py3
-rw-r--r--openstackclient/tests/unit/identity/v3/test_group.py29
-rw-r--r--openstackclient/tests/unit/identity/v3/test_project.py46
-rw-r--r--openstackclient/tests/unit/identity/v3/test_role.py43
-rw-r--r--openstackclient/tests/unit/network/v2/fakes.py101
-rw-r--r--openstackclient/tests/unit/network/v2/test_network_auto_allocated_topology.py267
-rw-r--r--openstackclient/tests/unit/network/v2/test_network_flavor.py407
-rw-r--r--openstackclient/tests/unit/network/v2/test_port.py131
-rw-r--r--openstackclient/tests/unit/network/v2/test_security_group_rule.py42
-rw-r--r--openstackclient/tests/unit/volume/v1/fakes.py31
-rw-r--r--openstackclient/tests/unit/volume/v1/test_qos_specs.py21
-rw-r--r--openstackclient/tests/unit/volume/v1/test_type.py237
-rw-r--r--openstackclient/tests/unit/volume/v1/test_volume.py6
-rw-r--r--openstackclient/tests/unit/volume/v2/fakes.py31
-rw-r--r--openstackclient/tests/unit/volume/v2/test_type.py276
-rw-r--r--openstackclient/tests/unit/volume/v2/test_volume.py18
-rw-r--r--openstackclient/tests/unit/volume/v2/test_volume_host.py31
-rw-r--r--openstackclient/tests/unit/volume/v3/__init__.py0
-rw-r--r--openstackclient/volume/client.py3
-rw-r--r--openstackclient/volume/v1/qos_specs.py11
-rw-r--r--openstackclient/volume/v1/volume.py18
-rw-r--r--openstackclient/volume/v1/volume_type.py221
-rw-r--r--openstackclient/volume/v2/volume.py19
-rw-r--r--openstackclient/volume/v2/volume_host.py29
-rw-r--r--openstackclient/volume/v2/volume_type.py210
-rw-r--r--openstackclient/volume/v3/__init__.py0
-rw-r--r--releasenotes/notes/add-network-auto-allocated-topology-481580f48840bfc4.yaml6
-rw-r--r--releasenotes/notes/add-no-property-f97e4b2f390cec06.yaml6
-rw-r--r--releasenotes/notes/add-volume-host-failover-8fc77b24533b7fed.yaml5
-rw-r--r--releasenotes/notes/bug-1499657-eeb260849febacf3.yaml5
-rw-r--r--releasenotes/notes/bug-1634799-1322227c9b0188ca.yaml5
-rw-r--r--releasenotes/notes/bug-1651117-a1df37e7ea939ba4.yaml9
-rw-r--r--releasenotes/notes/bug-1656788-2f5bda2205bc0329.yaml6
-rw-r--r--releasenotes/notes/bug-1658614-f84a8cece6f2ef8c.yaml7
-rw-r--r--releasenotes/notes/bug-1661814-1692e68a1d2d9770.yaml6
-rw-r--r--releasenotes/notes/bug-1663520-d880bfa51ca7b798.yaml8
-rw-r--r--releasenotes/notes/bug-1665231-3df6d090d137fe4f.yaml6
-rw-r--r--releasenotes/notes/bug-1666780-c10010e9061689d3.yaml12
-rw-r--r--releasenotes/notes/bug-1667699-6dad786b128ca8b5.yaml5
-rw-r--r--releasenotes/notes/change-098377fd53cce7a0.yaml10
-rw-r--r--releasenotes/notes/network-flavor-command-support-afe3a9da962a09bf.yaml7
-rw-r--r--releasenotes/notes/support-no-property-in-volume-811e67ff4a199eb6.yaml5
-rw-r--r--releasenotes/source/index.rst3
-rw-r--r--releasenotes/source/mitaka.rst6
-rw-r--r--releasenotes/source/ocata.rst6
-rw-r--r--requirements.txt6
-rw-r--r--setup.cfg73
-rw-r--r--setup.py2
-rw-r--r--test-requirements.txt9
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
diff --git a/setup.cfg b/setup.cfg
index dbe921a..e18aa5c 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -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
diff --git a/setup.py b/setup.py
index 782bb21..566d844 100644
--- a/setup.py
+++ b/setup.py
@@ -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