summaryrefslogtreecommitdiff
diff options
authorThomas Goirand <zigo@debian.org>2016-01-14 05:30:14 +0000
committergit-ubuntu importer <ubuntu-devel-discuss@lists.ubuntu.com>2016-01-14 10:10:52 +0000
commite0d4c1dc3577ed26f13556ccead5c4d478c7b45f (patch)
tree55f2c7a117edc5ab3e2ea0c3b4a60a2aee0e0722
parentbf2067db8c46b8842569fa37ba675f3e1d4246b8 (diff)
2.0.0-1 (patches unapplied)import/2.0.0-1
Imported using git-ubuntu import.
Notes
Notes: * New upstream release. * Fixed (build-)depends for this release.
-rw-r--r--.gitignore2
-rw-r--r--LICENSE26
-rw-r--r--README.rst8
-rw-r--r--debian/changelog7
-rw-r--r--debian/control41
-rw-r--r--debian/gbp.conf2
-rwxr-xr-xdebian/rules6
-rw-r--r--doc/source/authentication.rst4
-rw-r--r--doc/source/backwards-incompatible.rst24
-rw-r--r--doc/source/command-objects/compute-agent.rst100
-rw-r--r--doc/source/command-objects/compute-service.rst73
-rw-r--r--doc/source/command-objects/container.rst42
-rw-r--r--doc/source/command-objects/flavor.rst10
-rw-r--r--doc/source/command-objects/identity-provider.rst5
-rw-r--r--doc/source/command-objects/image.rst50
-rw-r--r--doc/source/command-objects/ip-fixed.rst45
-rw-r--r--doc/source/command-objects/ip-floating-pool.rst15
-rw-r--r--doc/source/command-objects/ip-floating.rst (renamed from doc/source/command-objects/floatingip.rst)36
-rw-r--r--doc/source/command-objects/object-store-account.rst45
-rw-r--r--doc/source/command-objects/object.rst52
-rw-r--r--doc/source/command-objects/region.rst10
-rw-r--r--doc/source/command-objects/security-group-rule.rst65
-rw-r--r--doc/source/command-objects/security-group.rst95
-rw-r--r--doc/source/command-objects/server.rst111
-rw-r--r--doc/source/command-objects/snapshot.rst5
-rw-r--r--doc/source/command-objects/volume.rst38
-rw-r--r--doc/source/command-options.rst135
-rw-r--r--doc/source/command-wrappers.rst52
-rw-r--r--doc/source/commands.rst70
-rw-r--r--doc/source/conf.py1
-rw-r--r--doc/source/developing.rst35
-rw-r--r--doc/source/history.rst1
-rw-r--r--doc/source/index.rst15
-rw-r--r--doc/source/plugin-commands.rst32
-rw-r--r--doc/source/plugins.rst12
-rw-r--r--doc/source/releases.rst89
-rwxr-xr-xexamples/common.py4
-rw-r--r--functional/common/test.py3
-rw-r--r--functional/tests/common/test_configuration.py12
-rw-r--r--functional/tests/compute/v2/test_server.py6
-rw-r--r--functional/tests/identity/v3/test_identity.py8
-rw-r--r--functional/tests/image/v1/test_image.py17
-rw-r--r--functional/tests/network/__init__.py0
-rw-r--r--functional/tests/network/v2/__init__.py0
-rw-r--r--functional/tests/network/v2/test_network.py50
-rw-r--r--functional/tests/volume/v1/test_volume.py2
-rw-r--r--openstackclient/api/api.py19
-rw-r--r--openstackclient/api/object_store_v1.py253
-rw-r--r--openstackclient/common/clientmanager.py4
-rw-r--r--openstackclient/common/logs.py (renamed from openstackclient/common/context.py)0
-rw-r--r--openstackclient/common/parseractions.py15
-rw-r--r--openstackclient/common/quota.py28
-rw-r--r--openstackclient/common/session.py4
-rw-r--r--openstackclient/common/utils.py52
-rw-r--r--openstackclient/compute/v2/console.py2
-rw-r--r--openstackclient/compute/v2/fixedip.py12
-rw-r--r--openstackclient/compute/v2/flavor.py13
-rw-r--r--openstackclient/compute/v2/floatingip.py18
-rw-r--r--openstackclient/compute/v2/floatingippool.py2
-rw-r--r--openstackclient/compute/v2/security_group.py262
-rw-r--r--openstackclient/compute/v2/server.py281
-rw-r--r--openstackclient/compute/v2/service.py23
-rw-r--r--openstackclient/compute/v2/usage.py33
-rw-r--r--openstackclient/identity/client.py2
-rw-r--r--openstackclient/identity/v2_0/project.py6
-rw-r--r--openstackclient/identity/v2_0/role.py4
-rw-r--r--openstackclient/identity/v2_0/user.py6
-rw-r--r--openstackclient/identity/v3/credential.py31
-rw-r--r--openstackclient/identity/v3/domain.py4
-rw-r--r--openstackclient/identity/v3/group.py4
-rw-r--r--openstackclient/identity/v3/identity_provider.py23
-rw-r--r--openstackclient/identity/v3/project.py4
-rw-r--r--openstackclient/identity/v3/region.py22
-rw-r--r--openstackclient/identity/v3/role.py9
-rw-r--r--openstackclient/identity/v3/user.py4
-rw-r--r--openstackclient/image/v1/image.py45
-rw-r--r--openstackclient/image/v2/image.py212
-rw-r--r--openstackclient/network/client.py4
-rw-r--r--openstackclient/network/common.py6
-rw-r--r--openstackclient/object/v1/account.py85
-rw-r--r--openstackclient/object/v1/container.py64
-rw-r--r--openstackclient/object/v1/object.py76
-rw-r--r--openstackclient/shell.py20
-rw-r--r--openstackclient/tests/api/fakes.py2
-rw-r--r--openstackclient/tests/api/test_image_v1.py2
-rw-r--r--openstackclient/tests/api/test_image_v2.py2
-rw-r--r--openstackclient/tests/api/test_network_v2.py2
-rw-r--r--openstackclient/tests/api/test_object_store_v1.py9
-rw-r--r--openstackclient/tests/common/test_logs.py (renamed from openstackclient/tests/common/test_context.py)64
-rw-r--r--openstackclient/tests/common/test_parseractions.py55
-rw-r--r--openstackclient/tests/common/test_quota.py173
-rw-r--r--openstackclient/tests/common/test_utils.py94
-rw-r--r--openstackclient/tests/compute/v2/fakes.py215
-rw-r--r--openstackclient/tests/compute/v2/test_flavor.py259
-rw-r--r--openstackclient/tests/compute/v2/test_server.py625
-rw-r--r--openstackclient/tests/compute/v2/test_service.py151
-rw-r--r--openstackclient/tests/fakes.py37
-rw-r--r--openstackclient/tests/identity/v2_0/test_project.py4
-rw-r--r--openstackclient/tests/identity/v2_0/test_role.py4
-rw-r--r--openstackclient/tests/identity/v2_0/test_user.py5
-rw-r--r--openstackclient/tests/identity/v3/fakes.py7
-rw-r--r--openstackclient/tests/identity/v3/test_credential.py112
-rw-r--r--openstackclient/tests/identity/v3/test_identity_provider.py62
-rw-r--r--openstackclient/tests/identity/v3/test_region.py79
-rw-r--r--openstackclient/tests/identity/v3/test_role.py38
-rw-r--r--openstackclient/tests/image/v1/test_image.py37
-rw-r--r--openstackclient/tests/image/v2/fakes.py15
-rw-r--r--openstackclient/tests/image/v2/test_image.py319
-rw-r--r--openstackclient/tests/object/v1/fakes.py2
-rw-r--r--openstackclient/tests/object/v1/test_container_all.py1
-rw-r--r--openstackclient/tests/volume/v1/test_volume.py182
-rw-r--r--openstackclient/tests/volume/v2/fakes.py88
-rw-r--r--openstackclient/tests/volume/v2/test_snapshot.py33
-rw-r--r--openstackclient/tests/volume/v2/test_type.py6
-rw-r--r--openstackclient/tests/volume/v2/test_volume.py164
-rw-r--r--openstackclient/volume/v1/snapshot.py13
-rw-r--r--openstackclient/volume/v1/volume.py12
-rw-r--r--openstackclient/volume/v2/snapshot.py13
-rw-r--r--openstackclient/volume/v2/volume.py62
-rw-r--r--openstackclient/volume/v2/volume_type.py4
-rwxr-xr-xpost_test_hook.sh2
-rw-r--r--python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po13
-rw-r--r--python-openstackclient/locale/python-openstackclient.pot23
-rw-r--r--python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po16
-rw-r--r--releasenotes/notes/.placeholder0
-rw-r--r--releasenotes/notes/add-ksa-7f0795157d93a898.yaml5
-rw-r--r--releasenotes/notes/bug-1475831-3caafd724d4ed644.yaml5
-rw-r--r--releasenotes/notes/bug-1517386-1c0aad8e3203b02b.yaml5
-rw-r--r--releasenotes/notes/bug-1519181-932c1ff07ef16666.yaml6
-rw-r--r--releasenotes/notes/bug-1519510-3cde4643b33ebb7a.yaml5
-rw-r--r--releasenotes/notes/bug-1520115-0367e1c8917dc912.yaml5
-rw-r--r--releasenotes/notes/bug-1520541-44d45e4693089c03.yaml5
-rw-r--r--releasenotes/notes/no-project-usage-list-e88eb49aa2e96cf7.yaml3
-rw-r--r--releasenotes/notes/server-changes-3962541b6ebdbbd8.yaml10
-rw-r--r--releasenotes/source/_static/.placeholder0
-rw-r--r--releasenotes/source/_templates/.placeholder0
-rw-r--r--releasenotes/source/conf.py282
-rw-r--r--releasenotes/source/index.rst8
-rw-r--r--releasenotes/source/unreleased.rst5
-rw-r--r--requirements.txt14
-rw-r--r--setup.cfg17
-rw-r--r--test-requirements.txt18
-rw-r--r--tox.ini5
143 files changed, 5318 insertions, 1199 deletions
diff --git a/.gitignore b/.gitignore
index 43bd5df..34cd3a2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,5 @@ doc/source/api/
.project
.pydevproject
cover
+# Files created by releasenotes build
+releasenotes/build
diff --git a/LICENSE b/LICENSE
index d645695..68c771a 100644
--- a/LICENSE
+++ b/LICENSE
@@ -174,29 +174,3 @@
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- 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.
diff --git a/README.rst b/README.rst
index 1bfef94..7a92b1c 100644
--- a/README.rst
+++ b/README.rst
@@ -2,6 +2,14 @@
OpenStackClient
===============
+.. image:: https://img.shields.io/pypi/v/python-openstackclient.svg
+ :target: https://pypi.python.org/pypi/python-openstackclient/
+ :alt: Latest Version
+
+.. image:: https://img.shields.io/pypi/dm/python-openstackclient.svg
+ :target: https://pypi.python.org/pypi/python-openstackclient/
+ :alt: Downloads
+
OpenStackClient (aka OSC) is a command-line client for OpenStack that brings
the command set for Compute, Identity, Image, Object Store and Volume APIs
together in a single shell with a uniform command structure.
diff --git a/debian/changelog b/debian/changelog
index 5119fa4..0947526 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+python-openstackclient (2.0.0-1) experimental; urgency=medium
+
+ * New upstream release.
+ * Fixed (build-)depends for this release.
+
+ -- Thomas Goirand <zigo@debian.org> Thu, 14 Jan 2016 05:30:14 +0000
+
python-openstackclient (1.7.1-1) unstable; urgency=medium
[ Corey Bryant ]
diff --git a/debian/control b/debian/control
index 7ae2058..15f665a 100644
--- a/debian/control
+++ b/debian/control
@@ -11,35 +11,44 @@ Build-Depends: debhelper (>= 9),
python-pbr (>= 1.8),
python-setuptools,
python-sphinx,
-Build-Depends-Indep: cliff-tablib,
- python-babel,
+Build-Depends-Indep: python-babel,
+ python-barbicanclient (>= 3.3.0),
python-cinderclient (>= 1:1.3.1),
- python-cliff (>= 1.14.0),
+ python-cliff (>= 1.15.0),
+ python-congressclient (>= 1.0.0),
python-coverage,
python-crypto,
+ python-designateclient (>= 1.5.0),
python-fixtures (>= 1.3.1),
python-glanceclient (>= 1:0.18.0),
python-hacking (>= 0.10.0),
+ python-heatclient (>= 0.6.0),
+ python-ironicclient (>= 0.8.0),
python-keyring,
+ python-keystoneauth1 (>= 1.0.0),
python-keystoneclient (>= 1:1.6.0),
+ python-mistralclient (>= 1.0.0),
python-mock (>= 1.3),
python-neutronclient (>= 1:2.6.0),
- python-novaclient (>= 2:2.28.1),
+ python-novaclient (>= 2:2.30.0),
python-os-client-config (>= 1.4.0),
- python-os-testr,
- python-oslo.config (>= 1:2.3.0),
+ python-os-testr (>= 0.4.2),
+ python-oslo.config (>= 1:3.2.0),
python-oslo.i18n (>= 1.5.0),
python-oslo.serialization (>= 1.2.0),
- python-oslo.utils (>= 2.0.0),
+ python-oslo.utils (>= 3.3.0),
python-oslosphinx (>= 2.5.0),
python-oslotest (>= 1.10.0),
- python-requests (>= 2.5.2),
+ python-requests (>= 2.8.1),
python-requests-mock (>= 0.6.0),
+ python-saharaclient (>= 0.10.0),
python-six (>= 1.9.0),
python-stevedore (>= 1.5.0),
- python-tempest-lib (>= 0.8.0),
+ python-tempest-lib (>= 0.11.0),
python-testtools (>= 1.4.0),
+ python-tuskarclient (>= 0.1.17),
python-webob,
+ python-zaqarclient (>= 0.3.0),
subunit,
testrepository,
Standards-Version: 3.9.6
@@ -49,23 +58,23 @@ Homepage: http://wiki.openstack.org/OpenStackClient
Package: python-openstackclient
Architecture: all
-Depends: cliff-tablib,
- python-babel,
+Depends: python-babel,
python-cinderclient (>= 1:1.3.1),
- python-cliff (>= 1.14.0),
+ python-cliff (>= 1.15.0),
python-crypto,
python-glanceclient (>= 1:0.18.0),
python-keyring,
+ python-keystoneauth1 (>= 1.0.0),
python-keystoneclient (>= 1:1.6.0),
python-neutronclient (>= 1:2.6.0),
- python-novaclient (>= 2:2.28.1),
+ python-novaclient (>= 2:2.30.0),
python-os-client-config (>= 1.4.0),
- python-oslo.config (>= 1:2.3.0),
+ python-oslo.config (>= 1:3.2.0),
python-oslo.i18n (>= 1.5.0),
python-oslo.serialization (>= 1.2.0),
- python-oslo.utils (>= 2.0.0),
+ python-oslo.utils (>= 3.3.0),
python-pbr (>= 1.8),
- python-requests (>= 2.5.2),
+ python-requests (>= 2.8.1),
python-six (>= 1.9.0),
python-stevedore (>= 1.5.0),
${misc:Depends},
diff --git a/debian/gbp.conf b/debian/gbp.conf
index 0ae05ad..7436424 100644
--- a/debian/gbp.conf
+++ b/debian/gbp.conf
@@ -1,6 +1,6 @@
[DEFAULT]
upstream-branch = master
-debian-branch = debian/liberty
+debian-branch = debian/mitaka
upstream-tag = %(version)s
compression = xz
diff --git a/debian/rules b/debian/rules
index 1f1e016..a67ddb9 100755
--- a/debian/rules
+++ b/debian/rules
@@ -37,3 +37,9 @@ ifeq (,$(findstring nodocs, $(DEB_BUILD_OPTIONS)))
PYTHONPATH=. sphinx-build -b html doc/source $(CURDIR)/debian/python-openstackclient/usr/share/doc/python-openstackclient/html
dh_sphinxdoc -O--buildsystem=python2
endif
+
+override_dh_installman:
+ifeq (,$(findstring nodocs, $(DEB_BUILD_OPTIONS)))
+ sphinx-build -b man doc/source doc/build/man
+ dh_installman
+endif
diff --git a/doc/source/authentication.rst b/doc/source/authentication.rst
index bf23b66..a3986ee 100644
--- a/doc/source/authentication.rst
+++ b/doc/source/authentication.rst
@@ -83,7 +83,7 @@ by the ``ClientManager`` object.
* Load the selected plugin class.
* When an operation that requires authentication is attempted ``ClientManager``
- makes the actual inital request to the Identity service.
+ makes the actual initial request to the Identity service.
* if ``--os-auth-url`` is not supplied for any of the types except
Token/Endpoint, exit with an error.
@@ -132,7 +132,7 @@ If using a domain as authorization scope, set either it's name or ID.
Note that if the user and project share the same domain, then simply setting
``os-default-domain`` or ``OS_DEFAULT_DOMAIN`` is sufficient.
-Thus, a minimal set of of environment variables would be:
+Thus, a minimal set of environment variables would be:
.. code-block:: bash
diff --git a/doc/source/backwards-incompatible.rst b/doc/source/backwards-incompatible.rst
index e89cc3a..94873c1 100644
--- a/doc/source/backwards-incompatible.rst
+++ b/doc/source/backwards-incompatible.rst
@@ -90,6 +90,30 @@ List of Backwards Incompatible Changes
* Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1453229
* Commit: https://review.openstack.org/#/c/181514/
+7. `image set` commands will no longer return the modified resource
+
+ Previously, modifying an image would result in the new image being displayed
+ to the user. To keep things consistent with other `set` commands, we will
+ no longer be showing the modified resource.
+
+ * In favor of: Use `set` then `show`
+ * As of: NA
+ * Removed in: NA
+ * Bug: NA
+ * Commit: NA
+
+8. `region` commands no longer support `url`
+
+ The Keystone team removed support for thr `url` attribute from the client
+ and server side. Changes to the `create`, `set` and `list` commands for
+ regions have been affected.
+
+ * In favor of: NA
+ * As of 1.9.0
+ * Removed in: NA
+ * Bug: https://launchpad.net/bugs/1506841
+ * Commit: https://review.openstack.org/#/c/236736/
+
For Developers
==============
diff --git a/doc/source/command-objects/compute-agent.rst b/doc/source/command-objects/compute-agent.rst
new file mode 100644
index 0000000..395ee4f
--- /dev/null
+++ b/doc/source/command-objects/compute-agent.rst
@@ -0,0 +1,100 @@
+=============
+compute agent
+=============
+
+Compute v2
+
+compute agent create
+--------------------
+
+Create compute agent
+
+.. program:: compute agent create
+.. code:: bash
+
+ os compute agent create
+ <os> <architecture> <version> <url> <md5hash>
+ <hypervisor>
+
+.. _compute_agent-create:
+.. describe:: <os>
+
+ Type of OS
+
+.. describe:: <architecture>
+
+ Type of architecture
+
+.. describe:: <version>
+
+ Version
+
+.. describe:: <url>
+
+ URL
+
+.. describe:: <md5hash>
+
+ MD5 hash
+
+.. describe:: <hypervisor>
+
+ Type of hypervisor
+
+compute agent delete
+--------------------
+
+Delete compute agent command
+
+.. program:: compute agent delete
+.. code:: bash
+
+ os compute agent delete <id>
+
+.. _compute_agent-delete:
+.. describe:: <id>
+
+ ID of agent to delete
+
+compute agent list
+------------------
+
+List compute agent command
+
+.. program:: compute agent list
+.. code:: bash
+
+ os compute agent list [--hypervisor <hypervisor>]
+
+.. _compute_agent-list:
+.. describe:: --hypervisor <hypervisor>
+
+ Optional type of hypervisor
+
+compute agent set
+-----------------
+
+Set compute agent command
+
+.. program:: agent set
+.. code:: bash
+
+ os compute agent set
+ <id> <version> <url> <md5hash>
+
+.. _compute_agent-set:
+.. describe:: <id>
+
+ ID of the agent
+
+.. describe:: <version>
+
+ Version of the agent
+
+.. describe:: <url>
+
+ URL
+
+.. describe:: <md5hash>
+
+ MD5 hash
diff --git a/doc/source/command-objects/compute-service.rst b/doc/source/command-objects/compute-service.rst
new file mode 100644
index 0000000..95a77bd
--- /dev/null
+++ b/doc/source/command-objects/compute-service.rst
@@ -0,0 +1,73 @@
+===============
+compute service
+===============
+
+Compute v2
+
+compute service delete
+----------------------
+
+Delete service command
+
+.. program:: compute service delete
+.. code:: bash
+
+ os compute service delete
+ <service>
+
+.. _compute-service-delete:
+.. describe:: <service>
+
+ Compute service to delete (ID only)
+
+compute service list
+--------------------
+
+List service command
+
+.. program:: compute service list
+.. code:: bash
+
+ os compute service list
+ [--host <host>]
+ [--service <service>]
+
+.. _compute-service-list:
+.. describe:: --host <host>
+
+ Name of host
+
+.. describe:: --service <service>
+
+ Name of service
+
+
+compute service set
+-------------------
+
+Set service command
+
+.. program:: compute service set
+.. code:: bash
+
+ os compute service list
+ [--enable | --disable]
+ <host> <service>
+
+.. _compute-service-set:
+.. describe:: --enable
+
+ Enable service
+
+.. describe:: --disable
+
+ Disable service
+
+.. describe:: <host>
+
+ Name of host
+
+.. describe:: <service>
+
+ Name of service
+
diff --git a/doc/source/command-objects/container.rst b/doc/source/command-objects/container.rst
index 3372f4d..e6517e9 100644
--- a/doc/source/command-objects/container.rst
+++ b/doc/source/command-objects/container.rst
@@ -2,7 +2,7 @@
container
=========
-Object Store v1
+Object Storage v1
container create
----------------
@@ -89,6 +89,26 @@ Save container contents locally
Container to save
+container set
+-------------
+
+Set container properties
+
+.. program:: container set
+.. code:: bash
+
+ os container set
+ [--property <key=value> [...] ]
+ [<container>]
+
+.. option:: --property <key=value>
+
+ Set a property on this container (repeat option to set multiple properties)
+
+.. describe:: <container>
+
+ Container to modify
+
container show
--------------
@@ -103,3 +123,23 @@ Display container details
.. describe:: <container>
Container to display
+
+container unset
+---------------
+
+Unset container properties
+
+.. program:: container unset
+.. code:: bash
+
+ os container unset
+ [--property <key>]
+ [<container>]
+
+.. option:: --property <key>
+
+ Property to remove from container (repeat option to remove multiple properties)
+
+.. describe:: <container>
+
+ Container to modify
diff --git a/doc/source/command-objects/flavor.rst b/doc/source/command-objects/flavor.rst
index 5254b12..2389f7d 100644
--- a/doc/source/command-objects/flavor.rst
+++ b/doc/source/command-objects/flavor.rst
@@ -91,6 +91,8 @@ List flavors
os flavor list
[--public | --private | --all]
[--long]
+ [--marker <marker>]
+ [--limit <limit>]
.. option:: --public
@@ -108,6 +110,14 @@ List flavors
List additional fields in output
+.. option:: --marker <marker>
+
+ The last flavor ID of the previous page
+
+.. option:: --limit <limit>
+
+ Maximum number of flavors to display
+
flavor show
-----------
diff --git a/doc/source/command-objects/identity-provider.rst b/doc/source/command-objects/identity-provider.rst
index 90f0b49..ac46273 100644
--- a/doc/source/command-objects/identity-provider.rst
+++ b/doc/source/command-objects/identity-provider.rst
@@ -81,6 +81,7 @@ Set identity provider properties
os identity provider set
[--remote-id <remote-id> [...] | --remote-id-file <file-name>]
+ [--description <description>]
[--enable | --disable]
<identity-provider>
@@ -94,6 +95,10 @@ Set identity provider properties
Name of a file that contains many remote IDs to associate with the identity
provider, one per line
+.. option:: --description
+
+ Set identity provider description
+
.. option:: --enable
Enable the identity provider
diff --git a/doc/source/command-objects/image.rst b/doc/source/command-objects/image.rst
index d2698b0..d4b9916 100644
--- a/doc/source/command-objects/image.rst
+++ b/doc/source/command-objects/image.rst
@@ -57,8 +57,6 @@ Create/upload an image
Image owner project name or ID
- *Image version 1 only.*
-
.. option:: --size <size>
Image size, in bytes (only used with --location and --copy-from)
@@ -238,6 +236,13 @@ Set image properties
[--checksum <checksum>]
[--stdin]
[--property <key=value> [...] ]
+ [--tag <tag> [...] ]
+ [--architecture <architecture>]
+ [--instance-id <instance-id>]
+ [--kernel-id <kernel-id>]
+ [--os-distro <os-distro>]
+ [--os-version <os-version>]
+ [--ramdisk-id <ramdisk-id>]
<image>
.. option:: --name <name>
@@ -258,14 +263,11 @@ Set image properties
.. option:: --container-format <container-format>
- Container format of image.
- Acceptable formats: ['ami', 'ari', 'aki', 'bare', 'ovf']
+ Image container format (default: bare)
.. option:: --disk-format <disk-format>
- Disk format of image.
- Acceptable formats: ['ami', 'ari', 'aki', 'vhd', 'vmdk', 'raw', 'qcow2',
- 'vdi', 'iso']
+ Image disk format (default: raw)
.. option:: --size <size>
@@ -339,45 +341,49 @@ Set image properties
.. option:: --property <key=value>
- Set a property on this image (repeat for multiple values)
+ Set a property on this image (repeat option to set multiple properties)
- *Image version 1 only.*
+ .. versionadded:: 2
+
+.. option:: --tag <tag>
+
+ Set a tag on this image (repeat for multiple values)
+
+ .. versionadded:: 2
.. option:: --architecture <architecture>
- Operating system Architecture
+ Operating system architecture
.. versionadded:: 2
-.. option:: --ramdisk-id <ramdisk-id>
+.. option:: --instance-id <instance-id>
- ID of image stored in Glance that should be used as
- the ramdisk when booting an AMI-style image
+ ID of server instance used to create this image
.. versionadded:: 2
-.. option:: --os-distro <os-distro>
+.. option:: --kernel-id <kernel-id>
- Common name of operating system distribution
+ ID of kernel image used to boot this disk image
.. versionadded:: 2
-.. option:: --os-version <os-version>
+.. option:: --os-distro <os-distro>
- Operating system version as specified by the distributor
+ Operating system distribution name
.. versionadded:: 2
-.. option:: --kernel-id <kernel-id>
+.. option:: --os-version <os-version>
- ID of image in Glance that should be used as the
- kernel when booting an AMI-style image
+ Operating system distribution version
.. versionadded:: 2
-.. option:: --instance-uuid <instance_uuid>
+.. option:: --ramdisk-id <ramdisk-id>
- ID of instance used to create this image
+ ID of ramdisk image used to boot this disk image
.. versionadded:: 2
diff --git a/doc/source/command-objects/ip-fixed.rst b/doc/source/command-objects/ip-fixed.rst
new file mode 100644
index 0000000..3a55b99
--- /dev/null
+++ b/doc/source/command-objects/ip-fixed.rst
@@ -0,0 +1,45 @@
+========
+ip fixed
+========
+
+Compute v2
+
+ip fixed add
+------------
+
+Add fixed IP address to server
+
+.. program:: ip fixed add
+.. code:: bash
+
+ os ip fixed add
+ <network>
+ <server>
+
+.. describe:: <network>
+
+ Network to fetch an IP address from (name or ID)
+
+.. describe:: <server>
+
+ Server to receive the IP address (name or ID)
+
+ip fixed remove
+---------------
+
+Remove fixed IP address from server
+
+.. program:: ip fixed remove
+.. code:: bash
+
+ os ip fixed remove
+ <ip-address>
+ <server>
+
+.. describe:: <ip-address>
+
+ IP address to remove from server (name only)
+
+.. describe:: <server>
+
+ Server to remove the IP address from (name or ID)
diff --git a/doc/source/command-objects/ip-floating-pool.rst b/doc/source/command-objects/ip-floating-pool.rst
new file mode 100644
index 0000000..63a450e
--- /dev/null
+++ b/doc/source/command-objects/ip-floating-pool.rst
@@ -0,0 +1,15 @@
+================
+ip floating pool
+================
+
+Compute v2
+
+ip floating pool list
+---------------------
+
+List pools of floating IP addresses
+
+.. program:: ip floating pool list
+.. code:: bash
+
+ os ip floating pool list
diff --git a/doc/source/command-objects/floatingip.rst b/doc/source/command-objects/ip-floating.rst
index 1ae3041..6bfd7f4 100644
--- a/doc/source/command-objects/floatingip.rst
+++ b/doc/source/command-objects/ip-floating.rst
@@ -1,24 +1,24 @@
-==========
-floatingip
-==========
+===========
+ip floating
+===========
Compute v2
ip floating add
---------------
-Add floating-ip to server
+Add floating IP address to server
.. program:: ip floating add
.. code:: bash
os ip floating add
- <ip_address>
+ <ip-address>
<server>
-.. describe:: <ip_address>
+.. describe:: <ip-address>
- IP address to add to server
+ IP address to add to server (name only)
.. describe:: <server>
@@ -27,7 +27,7 @@ Add floating-ip to server
ip floating create
------------------
-Create new floating-ip
+Create new floating IP address
.. program:: ip floating create
.. code:: bash
@@ -37,27 +37,27 @@ Create new floating-ip
.. describe:: <pool>
- Pool to fetch floating IP from
+ Pool to fetch IP address from (name or ID)
ip floating delete
------------------
-Delete a floating-ip
+Delete a floating IP address
.. program:: ip floating delete
.. code:: bash
os ip floating delete
- <ip_address>
+ <ip-address>
-.. describe:: <ip_address>
+.. describe:: <ip-address>
- IP address to delete
+ IP address to delete (ID only)
ip floating list
----------------
-List floating-ips
+List floating IP addresses
.. program:: ip floating list
.. code:: bash
@@ -67,18 +67,18 @@ List floating-ips
ip floating remove
------------------
-Remove floating-ip from server
+Remove floating IP address from server
.. program:: ip floating remove
.. code:: bash
os ip floating remove
- <ip_address>
+ <ip-address>
<server>
-.. describe:: <ip_address>
+.. describe:: <ip-address>
- IP address to remove from server
+ IP address to remove from server (name only)
.. describe:: <server>
diff --git a/doc/source/command-objects/object-store-account.rst b/doc/source/command-objects/object-store-account.rst
new file mode 100644
index 0000000..ba37078
--- /dev/null
+++ b/doc/source/command-objects/object-store-account.rst
@@ -0,0 +1,45 @@
+====================
+object store account
+====================
+
+Object Storage v1
+
+object store account set
+------------------------
+
+Set account properties
+
+.. program:: object store account set
+.. code:: bash
+
+ os object store account set
+ [--property <key=value> [...] ]
+
+.. option:: --property <key=value>
+
+ Set a property on this account (repeat option to set multiple properties)
+
+object store account show
+-------------------------
+
+Display account details
+
+.. program:: object store account show
+.. code:: bash
+
+ os object store account show
+
+object store account unset
+--------------------------
+
+Unset account properties
+
+.. program:: object store account unset
+.. code:: bash
+
+ os object store account unset
+ [--property <key>]
+
+.. option:: --property <key>
+
+ Property to remove from account (repeat option to remove multiple properties)
diff --git a/doc/source/command-objects/object.rst b/doc/source/command-objects/object.rst
index 90bcfa1..5aaad8a 100644
--- a/doc/source/command-objects/object.rst
+++ b/doc/source/command-objects/object.rst
@@ -2,7 +2,7 @@
object
======
-Object Store v1
+Object Storage v1
object create
-------------
@@ -119,6 +119,31 @@ Save object locally
Object to save
+object set
+----------
+
+Set object properties
+
+.. program:: object set
+.. code:: bash
+
+ os object set
+ [--property <key=value> [...] ]
+ <container>
+ [<object>]
+
+.. option:: --property <key=value>
+
+ Set a property on this object (repeat option to set multiple properties)
+
+.. describe:: <container>
+
+ Modify <object> from <container>
+
+.. describe:: <object>
+
+ Object to modify
+
object show
-----------
@@ -138,3 +163,28 @@ Display object details
.. describe:: <object>
Object to display
+
+object unset
+------------
+
+Unset object properties
+
+.. program:: object unset
+.. code:: bash
+
+ os object unset
+ [--property <key>]
+ <container>
+ [<object>]
+
+.. option:: --property <key>
+
+ Property to remove from object (repeat option to remove multiple properties)
+
+.. describe:: <container>
+
+ Modify <object> from <container>
+
+.. describe:: <object>
+
+ Object to modify
diff --git a/doc/source/command-objects/region.rst b/doc/source/command-objects/region.rst
index cb4a059..1892fc2 100644
--- a/doc/source/command-objects/region.rst
+++ b/doc/source/command-objects/region.rst
@@ -15,7 +15,6 @@ Create new region
os region create
[--parent-region <region-id>]
[--description <description>]
- [--url <url>]
<region-id>
.. option:: --parent-region <region-id>
@@ -26,10 +25,6 @@ Create new region
New region description
-.. option:: --url <url>
-
- New region URL
-
.. _region_create-region-id:
.. describe:: <region-id>
@@ -77,7 +72,6 @@ Set region properties
os region set
[--parent-region <region-id>]
[--description <description>]
- [--url <url>]
<region-id>
.. option:: --parent-region <region-id>
@@ -88,10 +82,6 @@ Set region properties
New region description
-.. option:: --url <url>
-
- New region URL
-
.. _region_set-region-id:
.. describe:: <region-id>
diff --git a/doc/source/command-objects/security-group-rule.rst b/doc/source/command-objects/security-group-rule.rst
new file mode 100644
index 0000000..8bd0d06
--- /dev/null
+++ b/doc/source/command-objects/security-group-rule.rst
@@ -0,0 +1,65 @@
+===================
+security group rule
+===================
+
+Compute v2
+
+security group rule create
+--------------------------
+
+Create a new security group rule
+
+.. program:: security group rule create
+.. code:: bash
+
+ os security group rule create
+ [--proto <proto>]
+ [--src-ip <ip-address>]
+ [--dst-port <port-range>]
+ <group>
+
+.. option:: --proto <proto>
+
+ IP protocol (icmp, tcp, udp; default: tcp)
+
+.. option:: --src-ip <ip-address>
+
+ Source IP (may use CIDR notation; default: 0.0.0.0/0)
+
+.. option:: --dst-port <port-range>
+
+ Destination port, may be a range: 137:139 (default: 0; only required for proto tcp and udp)
+
+.. describe:: <group>
+
+ Create rule in this security group (name or ID)
+
+security group rule delete
+--------------------------
+
+Delete a security group rule
+
+.. program:: security group rule delete
+.. code:: bash
+
+ os security group rule delete
+ <rule>
+
+.. describe:: <rule>
+
+ Security group rule to delete (ID only)
+
+security group rule list
+------------------------
+
+List security group rules
+
+.. program:: security group rule list
+.. code:: bash
+
+ os security group rule list
+ <group>
+
+.. describe:: <group>
+
+ List all rules in this security group (name or ID)
diff --git a/doc/source/command-objects/security-group.rst b/doc/source/command-objects/security-group.rst
new file mode 100644
index 0000000..60de41d
--- /dev/null
+++ b/doc/source/command-objects/security-group.rst
@@ -0,0 +1,95 @@
+==============
+security group
+==============
+
+Compute v2
+
+security group create
+---------------------
+
+Create a new security group
+
+.. program:: security group create
+.. code:: bash
+
+ os security group create
+ [--description <description>]
+ <name>
+
+.. option:: --description <description>
+
+ Security group description
+
+.. describe:: <name>
+
+ New security group name
+
+security group delete
+---------------------
+
+Delete a security group
+
+.. program:: security group delete
+.. code:: bash
+
+ os security group delete
+ <group>
+
+.. describe:: <group>
+
+ Security group to delete (name or ID)
+
+security group list
+-------------------
+
+List security groups
+
+.. program:: security group list
+.. code:: bash
+
+ os security group list
+ [--all-projects]
+
+.. option:: --all-projects
+
+ Display information from all projects (admin only)
+
+security group set
+------------------
+
+Set security group properties
+
+.. program:: security group set
+.. code:: bash
+
+ os security group set
+ [--name <new-name>]
+ [--description <description>]
+ <group>
+
+.. option:: --name <new-name>
+
+ New security group name
+
+.. option:: --description <description>
+
+ New security group description
+
+.. describe:: <group>
+
+ Security group to modify (name or ID)
+
+security group show
+-------------------
+
+Display security group details
+
+.. program:: security group show
+.. code:: bash
+
+ os security group show
+ <group>
+
+.. describe:: <group>
+
+ Security group to display (name or ID)
diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst
index 9ff4843..506a82d 100644
--- a/doc/source/command-objects/server.rst
+++ b/doc/source/command-objects/server.rst
@@ -165,7 +165,7 @@ Delete server(s)
.. describe:: <server>
- Server to delete (name or ID)
+ Server(s) to delete (name or ID)
server list
-----------
@@ -187,6 +187,8 @@ List servers
[--all-projects]
[--project <project> [--project-domain <project-domain>]]
[--long]
+ [--marker <server>]
+ [--limit <limit>]
.. option:: --reservation-id <reservation-id>
@@ -250,20 +252,31 @@ List servers
List additional fields in output
+.. option:: --marker <server>
+
+ The last server (name or ID) of the previous page. Display list of servers
+ after marker. Display all servers if not specified.
+
+.. option:: --limit <limit>
+
+ Maximum number of servers to display. If limit equals -1, all servers will
+ be displayed. If limit is greater than 'osapi_max_limit' option of Nova
+ API, 'osapi_max_limit' will be used instead.
+
server lock
-----------
-Lock a server. A non-admin user will not be able to execute actions
+Lock server(s). A non-admin user will not be able to execute actions
.. program:: server lock
.. code:: bash
os server lock
- <server>
+ <server> [<server> ...]
.. describe:: <server>
- Server (name or ID)
+ Server(s) to lock (name or ID)
server migrate
--------------
@@ -311,17 +324,17 @@ Migrate server to different host
server pause
------------
-Pause server
+Pause server(s)
.. program:: server pause
.. code:: bash
os server pause
- <server>
+ <server> [<server> ...]
.. describe:: <server>
- Server (name or ID)
+ Server(s) to pause (name or ID)
server reboot
-------------
@@ -483,17 +496,17 @@ a revert to release the new server and restart the old one.
server resume
-------------
-Resume server
+Resume server(s)
.. program:: server resume
.. code:: bash
os server resume
- <server>
+ <server> [<server> ...]
.. describe:: <server>
- Server (name or ID)
+ Server(s) to resume (name or ID)
server set
----------
@@ -527,6 +540,21 @@ Set server properties
Server (name or ID)
+server shelve
+-------------
+
+Shelve server(s)
+
+.. program:: server shelve
+.. code:: bash
+
+ os server shelve
+ <server> [<server> ...]
+
+.. describe:: <server>
+
+ Server(s) to shelve (name or ID)
+
server show
-----------
@@ -595,50 +623,80 @@ Ssh to server
Server (name or ID)
+server start
+------------
+
+Start server(s)
+
+.. program:: server start
+.. code:: bash
+
+ os server start
+ <server> [<server> ...]
+
+.. describe:: <server>
+
+ Server(s) to start (name or ID)
+
+server stop
+-----------
+
+Stop server(s)
+
+.. program:: server stop
+.. code:: bash
+
+ os server stop
+ <server> [<server> ...]
+
+.. describe:: <server>
+
+ Server(s) to stop (name or ID)
+
server suspend
--------------
-Suspend server
+Suspend server(s)
.. program:: server suspend
.. code:: bash
os server suspend
- <server>
+ <server> [<server> ...]
.. describe:: <server>
- Server (name or ID)
+ Server(s) to suspend (name or ID)
server unlock
-------------
-Unlock server
+Unlock server(s)
.. program:: server unlock
.. code:: bash
os server unlock
- <server>
+ <server> [<server> ...]
.. describe:: <server>
- Server (name or ID)
+ Server(s) to unlock (name or ID)
server unpause
--------------
-Unpause server
+Unpause server(s)
.. program:: server unpause
.. code:: bash
os server unpause
- <server>
+ <server> [<server> ...]
.. describe:: <server>
- Server (name or ID)
+ Server(s) to unpause (name or ID)
server unrescue
---------------
@@ -675,3 +733,18 @@ Unset server properties
.. describe:: <server>
Server (name or ID)
+
+server unshelve
+---------------
+
+Unshelve server(s)
+
+.. program:: server unshelve
+.. code:: bash
+
+ os server unshelve
+ <server> [<server> ...]
+
+.. describe:: <server>
+
+ Server(s) to unshelve (name or ID)
diff --git a/doc/source/command-objects/snapshot.rst b/doc/source/command-objects/snapshot.rst
index 7bfd1d9..e05673b 100644
--- a/doc/source/command-objects/snapshot.rst
+++ b/doc/source/command-objects/snapshot.rst
@@ -60,6 +60,11 @@ List snapshots
.. code:: bash
os snapshot list
+ [--all-projects]
+
+.. option:: --all-projects
+
+ Include all projects (admin only)
.. option:: --long
diff --git a/doc/source/command-objects/volume.rst b/doc/source/command-objects/volume.rst
index 556d164..cb52c56 100644
--- a/doc/source/command-objects/volume.rst
+++ b/doc/source/command-objects/volume.rst
@@ -102,18 +102,46 @@ List volumes
.. code:: bash
os volume list
- [--status <status>]
- [--name <name>]
[--all-projects]
+ [--project <project> [--project-domain <project-domain>]]
+ [--user <user> [--user-domain <user-domain>]]
+ [--name <name>]
+ [--status <status>]
[--long]
-.. option:: --status <status>
+.. option:: --project <project>
- Filter results by status
+ Filter results by project (name or ID) (admin only)
+
+ *Volume version 2 only*
+
+.. option:: --project-domain <project-domain>
+
+ Domain the project belongs to (name or ID).
+ This can be used in case collisions between project names exist.
+
+ *Volume version 2 only*
+
+.. option:: --user <user>
+
+ Filter results by user (name or ID) (admin only)
+
+ *Volume version 2 only*
+
+.. option:: --user-domain <user-domain>
+
+ Domain the user belongs to (name or ID).
+ This can be used in case collisions between user names exist.
+
+ *Volume version 2 only*
.. option:: --name <name>
- Filter results by name
+ Filter results by volume name
+
+.. option:: --status <status>
+
+ Filter results by status
.. option:: --all-projects
diff --git a/doc/source/command-options.rst b/doc/source/command-options.rst
new file mode 100644
index 0000000..edafb15
--- /dev/null
+++ b/doc/source/command-options.rst
@@ -0,0 +1,135 @@
+===============
+Command Options
+===============
+
+OpenStackClient commands all have a set of zero or more options unique to
+the command, however there are of course ways in which these options are
+common and consistent across all of the commands that include them.
+
+These are the set of guidelines for OSC developers that help keep the
+interface and commands consistent.
+
+In some cases (like the boolean variables below) we use the same pattern
+for defining and using options in all situations. The alternative of only
+using it when necessary leads to errors when copy-n-paste is used for a
+new command without understanding why or why not that instance is correct.
+
+Boolean Options
+===============
+
+Boolean options for any command that sets a resource state, such as 'enabled'
+or 'public', shall always have both positive and negative options defined.
+The names of those options shall either be a naturally occuring pair of
+words (in English) or a positive option and a negative option with `no-`
+prepended (such as in the traditional GNU option usage) like `--share` and
+`--no-share`.
+
+In order to handle those APIs that behave differently when a field is set to
+`None` and when the field is not present in a passed argument list or dict,
+each of the boolean options shall set its own variable to `True` as part of
+a mutiually exclusive group, rather than the more common configuration of setting a
+single destination variable `True` or `False` directly. This allows us to
+detect the situation when neither option is present (both variables will be
+`False`) and act accordingly for those APIs where this matters.
+
+This also requires that each of the boolean values be tested in the
+`take_action()` method to correctly set (or not) the underlying API field
+values.
+
+.. option:: --enable
+
+ Enable <resource> (default)
+
+.. option:: --disable
+
+ Disable <resource>
+
+Implementation
+~~~~~~~~~~~~~~
+
+The parser declaration should look like this::
+
+.. code-block: python
+
+ enable_group = parser.add_mutually_exclusive_group()
+ enable_group.add_argument(
+ '--enable',
+ action='store_true',
+ help=_('Enable <resource> (default)'),
+ )
+ enable_group.add_argument(
+ '--disable',
+ action='store_true',
+ help=_('Disable <resource>'),
+ )
+
+An example handler in `take_action()`::
+
+ # This leaves 'enabled' undefined if neither option is present
+ if parsed_args.enable:
+ kwargs['enabled'] = True
+ if parsed_args.disable:
+ kwargs['enabled'] = False
+
+List Command Options
+====================
+
+Additional Fields
+-----------------
+
+Most list commands only return a subset of the available fields by default.
+Additional fields are available with the `--long` option. All list
+commands should allow `--long` even if they return all fields by default.
+
+.. option:: --long
+
+ List additional fields in output
+
+Implementation
+~~~~~~~~~~~~~~
+
+The parser declaration should look like this::
+
+.. code-block: python
+
+ parser.add_argument(
+ '--long',
+ action='store_true',
+ default=False,
+ help='List additional fields in output',
+ )
+
+Pagination
+----------
+
+There are many ways to do pagination, some OpenStack APIs support it, some don't.
+OpenStackClient attempts to define a single common way to specify pagination on
+the command line.
+
+.. option:: --marker <marker>
+
+ Anchor for paging
+
+.. option:: --limit <limit>
+
+ Limit number of <resource> returned (*integer*)
+
+Implementation
+~~~~~~~~~~~~~~
+
+The parser declaration should look like this::
+
+.. code-block: python
+
+ parser.add_argument(
+ "--marker",
+ metavar="<marker>",
+ help="Anchor for paging",
+ )
+
+ parser.add_argument(
+ "--limit",
+ metavar="<limit>",
+ type=int,
+ help="Limit the number of <resource> returned",
+ )
diff --git a/doc/source/command-wrappers.rst b/doc/source/command-wrappers.rst
new file mode 100644
index 0000000..b14eccd
--- /dev/null
+++ b/doc/source/command-wrappers.rst
@@ -0,0 +1,52 @@
+======================
+Command Class Wrappers
+======================
+
+When we want to deprecate a command, policy says we need to alert the user.
+We do this with a message logged at WARNING level before any command output
+is emitted.
+
+OpenStackClient command classes are derived from the ``cliff`` classes.
+Cliff uses ``setuptools`` entry points for dispatching the parsed command
+to the respective handler classes. This lends itself to modifying the
+command execution at run-time.
+
+The obvious approach to adding the deprecation message would be to just add
+the message to the command class ``take_action()`` method directly. But then
+the various deprecations are scattered throughout the code base. If we
+instead wrap the deprecated command class with a new class we can put all of
+the wrappers into a separate, dedicated module. This also lets us leave the
+original class unmodified and puts all of the deprecation bits in one place.
+
+This is an example of a minimal wrapper around a command class that logs a
+deprecation message as a warning to the user then calls the original class.
+
+* Subclass the deprecated command.
+
+* Set class attribute ``deprecated`` to ``True`` to signal cliff to not
+ emit help text for this command.
+
+* Log the deprecation message at WARNING level and refer to the replacement
+ for the deprecated command in the log warning message.
+
+* Change the entry point class in ``setup.cfg`` to point to the new class.
+
+Example Deprecation Class
+-------------------------
+
+.. code-block: python
+
+ class ListFooOld(ListFoo):
+ """List resources"""
+
+ # This notifies cliff to not display the help for this command
+ deprecated = True
+
+ log = logging.getLogger('deprecated')
+
+ def take_action(self, parsed_args):
+ self.log.warning(
+ "%s is deprecated, use 'foobar list'",
+ getattr(self, 'cmd_name', 'this command'),
+ )
+ return super(ListFooOld, self).take_action(parsed_args)
diff --git a/doc/source/commands.rst b/doc/source/commands.rst
index b52d945..6069895 100644
--- a/doc/source/commands.rst
+++ b/doc/source/commands.rst
@@ -77,10 +77,10 @@ referring to both Compute and Volume quotas.
* ``console log``: (**Compute**) server console text dump
* ``console url``: (**Compute**) server remote console URL
* ``consumer``: (**Identity**) OAuth-based delegatee
-* ``container``: (**Object Store**) a grouping of objects
+* ``container``: (**Object Storage**) a grouping of objects
* ``credentials``: (**Identity**) specific to identity providers
* ``domain``: (**Identity**) a grouping of projects
-* ``ec2 cedentials``: (**Identity**) AWS EC2-compatible credentials
+* ``ec2 credentials``: (**Identity**) AWS EC2-compatible credentials
* ``endpoint``: (**Identity**) the base URL used to contact a specific service
* ``extension``: (**Compute**, **Identity**, **Volume**) OpenStack server API extensions
* ``federation protocol``: (**Identity**) the underlying protocol used while federating identities
@@ -91,14 +91,15 @@ referring to both Compute and Volume quotas.
* ``hypervisor stats``: (**Compute**) hypervisor statistics over all compute nodes
* ``identity provider``: (**Identity**) a source of users and authentication
* ``image``: (**Image**) a disk image
-* ``ip fixed``: Compute, Network - an internal IP address assigned to a server
-* ``ip floating``: Compute, Network - a public IP address that can be mapped to a server
+* ``ip fixed``: (**Compute**, **Network**) - an internal IP address assigned to a server
+* ``ip floating``: (**Compute**, **Network**) - a public IP address that can be mapped to a server
+* ``ip floating pool``: (**Compute**, **Network**) - a pool of public IP addresses
* ``keypair``: (**Compute**) an SSH public key
* ``limits``: (**Compute**, **Volume**) resource usage limits
* ``mapping``: (**Identity**) a definition to translate identity provider attributes to Identity concepts
* ``module``: internal - installed Python modules in the OSC process
-* ``network``: Network - a virtual network for connecting servers and other resources
-* ``object``: (**Object Store**) a single file in the Object Store
+* ``network``: (**Network**) - a virtual network for connecting servers and other resources
+* ``object``: (**Object Storage**) a single file in the Object Storage
* ``policy``: (**Identity**) determines authorization
* ``project``: (**Identity**) owns a group of resources
* ``quota``: (**Compute**, **Volume**) resource usage restrictions
@@ -106,8 +107,8 @@ referring to both Compute and Volume quotas.
* ``request token``: (**Identity**) temporary OAuth-based token
* ``role``: (**Identity**) a policy object used to determine authorization
* ``role assignment``: (**Identity**) a relationship between roles, users or groups, and domains or projects
-* ``security group``: Compute, Network - groups of network access rules
-* ``security group rule``: Compute, Network - the individual rules that define protocol/IP/port access
+* ``security group``: (**Compute**, **Network**) - groups of network access rules
+* ``security group rule``: (**Compute**, **Network**) - the individual rules that define protocol/IP/port access
* ``server``: (**Compute**) virtual machine instance
* ``server image``: (**Compute**) saved server disk image
* ``service``: (**Identity**) a cloud service
@@ -120,6 +121,41 @@ referring to both Compute and Volume quotas.
* ``volume``: (**Volume**) block volumes
* ``volume type``: (**Volume**) deployment-specific types of volumes available
+
+Plugin Objects
+--------------
+
+The following are known `Objects` used by OpenStack :doc:`plugins`. These are
+listed here to avoid name conflicts when creating new plugins. For a complete
+list check out :doc:`plugin-commands`.
+
+* ``baremetal``: (**Baremetal (Ironic)**)
+* ``congress datasource``: (**Policy (Congress)**)
+* ``congress driver``: (**Policy (Congress)**)
+* ``congress policy``: (**Policy (Congress)**)
+* ``congress policy rule``: (**Policy (Congress)**)
+* ``dataprocessing data source``: (**Data Processing (Sahara)**)
+* ``dataprocessing image``: (**Data Processing (Sahara)**)
+* ``dataprocessing image tags``: (**Data Processing (Sahara)**)
+* ``dataprocessing plugin``: (**Data Processing (Sahara)**)
+* ``management plan``: (**Management (Tuskar)**)
+* ``management role``: (**Management (Tuskar)**)
+* ``message-broker cluster``: (**Message Broker (Cue)**)
+* ``message flavor``: (**Messaging (Zaqar)**)
+* ``pool``: (**Messaging (Zaqar)**)
+* ``ptr record``: (**DNS (Designate)**)
+* ``queue``: (**Messaging (Zaqar)**)
+* ``recordset``: (**DNS (Designate)**)
+* ``secret``: (**Key Manager (Barbican)**)
+* ``secret container``: (**Key Manager (Barbican)**)
+* ``secret order``: (**Key Manager (Barbican)**)
+* ``stack``: (**Orchestration (Heat)**)
+* ``tld``: (**DNS (Designate)**)
+* ``zone``: (**DNS (Designate)**)
+* ``zone blacklist``: (**DNS (Designate)**)
+* ``zone transfer``: (**DNS (Designate)**)
+
+
Actions
-------
@@ -132,28 +168,32 @@ Those actions with an opposite action are noted in parens if applicable.
is built in the order of ``container add object <container> <object>``,
the positional arguments appear in the same order
* ``create`` (``delete``) - create a new occurrence of the specified object
-* ``delete`` (``create``) - delete a specific occurrence of the specified object
+* ``delete`` (``create``) - delete specific occurrences of the specified objects
* ``issue`` (``revoke``) - issue a token
* ``list`` - display summary information about multiple objects
-* ``lock`` (``unlock``)
+* ``lock`` (``unlock``) - lock one or more servers so that non-admin user won't be able to execute actions
* ``migrate`` - move a server to a different host; ``--live`` performs a
live migration if possible
-* ``pause`` (``unpause``) - stop a server and leave it in memory
+* ``pause`` (``unpause``) - stop one or more servers and leave them in memory
* ``reboot`` - forcibly reboot a server
* ``rebuild`` - rebuild a server using (most of) the same arguments as in the original create
* ``remove`` (``add``) - remove an object from a group of objects
* ``rescue`` (``unrescue``) - reboot a server in a special rescue mode allowing access to the original disks
* ``resize`` - change a server's flavor
-* ``resume`` (``suspend``) - return a suspended server to running state
+* ``resume`` (``suspend``) - return one or more suspended servers to running state
* ``revoke`` (``issue``) - revoke a token
* ``save`` - download an object locally
* ``set`` (``unset``) - set a property on the object, formerly called metadata
+* ``shelve`` (``unshelve``) - shelve one or more servers
* ``show`` - display detailed information about the specific object
-* ``suspend`` (``resume``) - stop a server and save to disk freeing memory
-* ``unlock`` (``lock``)
-* ``unpause`` (``pause``) - return a paused server to running state
+* ``start`` (``stop``) - start one or more servers
+* ``stop`` (``start``) - stop one or more servers
+* ``suspend`` (``resume``) - stop one or more servers and save to disk freeing memory
+* ``unlock`` (``lock``) - unlock one or more servers
+* ``unpause`` (``pause``) - return one or more paused servers to running state
* ``unrescue`` (``rescue``) - return a server to normal boot mode
* ``unset`` (``set``) - remove an attribute of the object
+* ``unshelve`` (``shelve``) - unshelve one or more servers
Implementation
diff --git a/doc/source/conf.py b/doc/source/conf.py
index f4434ec..792ba40 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -38,6 +38,7 @@ extensions = ['sphinx.ext.autodoc',
'sphinx.ext.todo',
'oslosphinx',
'ext.apidoc',
+ 'stevedore.sphinxext',
]
# Add any paths that contain templates here, relative to this directory.
diff --git a/doc/source/developing.rst b/doc/source/developing.rst
index c41fbf3..9f8e55c 100644
--- a/doc/source/developing.rst
+++ b/doc/source/developing.rst
@@ -98,6 +98,41 @@ create HTML docs, run the following:
The resultant HTML will be the ``doc/build/html`` directory.
+Release Notes
+-------------
+
+The release notes for a patch should be included in the patch. See the
+`Project Team Guide`_ for more information on using reno in OpenStack.
+
+.. _`Project Team Guide`: http://docs.openstack.org/project-team-guide/release-management.html#managing-release-notes
+
+If any of the following applies to the patch, a release note is required:
+
+* The deployer needs to take an action when upgrading
+* The plugin interface changes
+* A new feature is implemented
+* A command or option is removed
+* Current behavior is changed
+* A security bug is fixed
+
+Reno is used to generate release notes. Please read the docs for details. In summary, use
+
+.. code-block:: bash
+
+ $ tox -e venv -- reno new <bug-,bp-,whatever>
+
+Then edit the sample file that was created and push it with your change.
+
+To see the results:
+
+.. code-block:: bash
+
+ $ git commit # Commit the change because reno scans git log.
+
+ $ tox -e releasenotes
+
+Then look at the generated release notes files in releasenotes/build/html in your favorite browser.
+
Testing new code
----------------
diff --git a/doc/source/history.rst b/doc/source/history.rst
new file mode 100644
index 0000000..69ed4fe
--- /dev/null
+++ b/doc/source/history.rst
@@ -0,0 +1 @@
+.. include:: ../../ChangeLog
diff --git a/doc/source/index.rst b/doc/source/index.rst
index c90b8e5..4992997 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -3,7 +3,7 @@ OpenStackClient
===============
OpenStackClient (aka OSC) is a command-line client for OpenStack that
-brings the command set for Compute, Identity, Image, Object Store and Volume
+brings the command set for Compute, Identity, Image, Object Storage and Volume
APIs together in a single shell with a uniform command structure.
User Documentation
@@ -16,11 +16,11 @@ User Documentation
commands
configuration
plugins
+ plugin-commands
authentication
interactive
humaninterfaceguide
backwards-incompatible
- releases
man/openstack
Getting Started
@@ -30,6 +30,15 @@ Getting Started
* Read the source `on OpenStack's Git server`_
* Install OpenStackClient from `PyPi`_ or a `tarball`_
+Release Notes
+-------------
+
+.. toctree::
+ :maxdepth: 1
+
+ releases
+ history
+
Developer Documentation
-----------------------
@@ -37,6 +46,8 @@ Developer Documentation
:maxdepth: 1
developing
+ command-options
+ command-wrappers
Project Goals
-------------
diff --git a/doc/source/plugin-commands.rst b/doc/source/plugin-commands.rst
new file mode 100644
index 0000000..893c4c4
--- /dev/null
+++ b/doc/source/plugin-commands.rst
@@ -0,0 +1,32 @@
+=================
+ Plugin Commands
+=================
+
+.. list-plugins:: openstack.cli.extension
+
+.. list-plugins:: openstack.key_manager.v1
+ :detailed:
+
+.. list-plugins:: openstack.baremetal.v1
+ :detailed:
+
+.. list-plugins:: openstack.congressclient.v1
+ :detailed:
+
+.. list-plugins:: openstack.workflow_engine.v2
+ :detailed:
+
+.. list-plugins:: openstack.data_processing.v1
+ :detailed:
+
+.. list-plugins:: openstack.dns.v1
+ :detailed:
+
+.. list-plugins:: openstack.management.v1
+ :detailed:
+
+.. list-plugins:: openstack.messaging.v1
+ :detailed:
+
+.. list-plugins:: openstack.orchestration.v1
+ :detailed:
diff --git a/doc/source/plugins.rst b/doc/source/plugins.rst
index 469742d..5f82cc6 100644
--- a/doc/source/plugins.rst
+++ b/doc/source/plugins.rst
@@ -24,20 +24,20 @@ plugin.
============================= ======================================
project notes
============================= ======================================
-python-barbicanclient patch in progress (https://review.openstack.org/#/c/198732/)
+python-barbicanclient using OpenStackClient
python-ceilometerclient using argparse
python-congressclient using OpenStackClient
python-cueclient using OpenStackClient
-python-designateclient patch in progress (https://review.openstack.org/#/c/133676/)
+python-designateclient using OpenStackClient
python-heatclient patch in progress (https://review.openstack.org/#/c/195867/)
-python-ironicclient patch in progress (https://review.openstack.org/#/c/171672/)
+python-ironicclient Using OpenStackClient
python-magnumclient using argparse
python-manilaclient using argparse
-python-mistralclient using cliff, but not OpenStackClient
+python-mistralclient using cliff
python-muranoclient using argparse
-python-saharaclient using argparse
+python-saharaclient using OpenStackClient
python-troveclient using argparse
-python-tuskarclient using OpenStackClient and their own shell
+python-tuskarclient using OpenStackClient
python-zaqarclient using OpenStackClient
============================= ======================================
diff --git a/doc/source/releases.rst b/doc/source/releases.rst
index 582f584..853ef95 100644
--- a/doc/source/releases.rst
+++ b/doc/source/releases.rst
@@ -2,6 +2,95 @@
Release Notes
=============
+As of release 2.0 the release notes can be found on the OpenStack `Release Notes site`_.
+
+.. _`Release Notes site`: http://docs.openstack.org/releasenotes/python-openstackclient
+
+1.9.0 (17 Nov 2015)
+===================
+
+* Several updates to `openstack server`
+ Blueprint `servers <https://blueprints.launchpad.net/python-openstackclient/+spec/cmd-with-multi-servers>`_
+
+ * `openstack server start`
+ * `openstack server stop`
+ * `openstack server pause`
+ * `openstack server unpause`
+ * `openstack server lock`
+ * `openstack server unlock`
+
+* Obfuscate passwords when used in debug mode
+ Bug `1501598 <https://bugs.launchpad.net/bugs/1501598>`_
+
+* Clean up `identity provider show`
+ Bug `1478995 <https://bugs.launchpad.net/bugs/1478995>`_
+
+* Add `description` to `identity provider set`
+ Bug `1515815 <https://bugs.launchpad.net/bugs/1515815>`_
+
+* Add `compute service delete`
+ Bug `1503510 <https://bugs.launchpad.net/bugs/1503510>`_
+
+* Log a warning when setting non-ascii object store properties
+ Bug `1503898 <https://bugs.launchpad.net/bugs/1503898>`_
+
+* Add 'marker' and 'limit' to `openstack flavor list`
+ Bug `1505874 <https://bugs.launchpad.net/bugs/1505874>`_
+
+* Remove `url` from `region create` and `region set`
+ Bug `1506841 <https://bugs.launchpad.net/bugs/1506841>`_
+
+* `openstack image add project` fails with AttributeError for image v2
+ Bug `1509054 <https://bugs.launchpad.net/bugs/1509054>`_
+
+* Inconsistent output with `openstack usage show`
+ Bug `1512220 <https://bugs.launchpad.net/bugs/1512220>`_
+
+* Validate --lines with `openstack console log show`
+ Bug `1512263 <https://bugs.launchpad.net/bugs/1512263>`_
+
+* Validate --project does not exist with `openstack quota set`
+ Bug `1512638 <https://bugs.launchpad.net/bugs/1512638>`_
+
+* Cannot list servers while --user specified for `openstack server list`
+ Bug `1513701 <https://bugs.launchpad.net/bugs/1513701>`_
+
+* Cannot list volumes while --user specified for `openstack volume list`
+ Bug `1514145 <https://bugs.launchpad.net/bugs/1514145>`_
+
+* Cannot find volume with --block-device-mapping with `openstack server create`
+ Bug `1514394 <https://bugs.launchpad.net/bugs/1514394>`_
+
+* Fix documentation for `credential set`
+ Bug `1418837 <https://bugs.launchpad.net/bugs/1418837>`_
+
+1.8.0 (18 Oct 2015)
+===================
+
+* `image create --volume` command (v1) will attempt to create two images
+ Bug `1497221 <https://bugs.launchpad.net/bugs/1497221>`_
+
+* Add filtering by project/uesr for command `volume list`
+ Bug `1483976 <https://bugs.launchpad.net/bugs/1483976>`_
+
+* Password used by plugin shows up in debug mode
+ Bug `1501598 <https://bugs.launchpad.net/bugs/1501598>`_
+
+* Add support for `object store account`
+ Bug `1501943 <https://bugs.launchpad.net/bugs/1501943>`_
+
+* Add support for setting properties on objects and containers
+ Bug `1501945 <https://bugs.launchpad.net/bugs/1501945>`_
+
+* `role list` ignores `--inherited` option
+ Bug `1502822 <https://bugs.launchpad.net/bugs/1502822>`_
+
+* `compute service list` does not return service ID number
+ Bug `1503430 <https://bugs.launchpad.net/bugs/1503430>`_
+
+* Containers and objects with non-ascii characters fails
+ Bug `1503508 <https://bugs.launchpad.net/bugs/1503508>`_
+
1.7.1 (30 Sep 2015)
===================
diff --git a/examples/common.py b/examples/common.py
index 840e715..6d48a8c 100755
--- a/examples/common.py
+++ b/examples/common.py
@@ -37,7 +37,7 @@ import os
import sys
import traceback
-from keystoneclient import session as ksc_session
+from keystoneauth1 import session as ks_session
from openstackclient.api import auth
@@ -226,7 +226,7 @@ def make_session(opts, **kwargs):
)
auth_p = auth_plugin.load_from_options(**auth_params)
- session = ksc_session.Session(
+ session = ks_session.Session(
auth=auth_p,
**kwargs
)
diff --git a/functional/common/test.py b/functional/common/test.py
index 50d59fd..2fc355f 100644
--- a/functional/common/test.py
+++ b/functional/common/test.py
@@ -28,13 +28,14 @@ EXAMPLE_DIR = os.path.join(ROOT_DIR, 'examples')
def execute(cmd, fail_ok=False, merge_stderr=False):
"""Executes specified command for the given action."""
- cmdlist = shlex.split(cmd.encode('utf-8'))
+ cmdlist = shlex.split(cmd)
result = ''
result_err = ''
stdout = subprocess.PIPE
stderr = subprocess.STDOUT if merge_stderr else subprocess.PIPE
proc = subprocess.Popen(cmdlist, stdout=stdout, stderr=stderr)
result, result_err = proc.communicate()
+ result = result.decode('utf-8')
if not fail_ok and proc.returncode != 0:
raise exceptions.CommandFailed(proc.returncode, cmd, result,
result_err)
diff --git a/functional/tests/common/test_configuration.py b/functional/tests/common/test_configuration.py
index f4a0209..801ee10 100644
--- a/functional/tests/common/test_configuration.py
+++ b/functional/tests/common/test_configuration.py
@@ -30,9 +30,15 @@ class ConfigurationTests(test.TestCase):
def test_configuration_show_unmask(self):
raw_output = self.openstack('configuration show --unmask ' + self.opts)
- passwd = os.environ['OS_PASSWORD']
- self.assertOutput(passwd + '\n', raw_output)
+ # 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)
+ else:
+ self.assertNotEqual(configuration.REDACTED + '\n', raw_output)
def test_configuration_show_mask(self):
raw_output = self.openstack('configuration show --mask ' + self.opts)
- self.assertOutput(configuration.REDACTED + '\n', raw_output)
+ self.assertEqual(configuration.REDACTED + '\n', raw_output)
diff --git a/functional/tests/compute/v2/test_server.py b/functional/tests/compute/v2/test_server.py
index a6cc98e..bd1b2a8 100644
--- a/functional/tests/compute/v2/test_server.py
+++ b/functional/tests/compute/v2/test_server.py
@@ -32,14 +32,14 @@ class ServerTests(test.TestCase):
def get_flavor(cls):
raw_output = cls.openstack('flavor list -f value -c ID')
ray = raw_output.split('\n')
- idx = len(ray)/2
+ idx = int(len(ray)/2)
return ray[idx]
@classmethod
def get_image(cls):
raw_output = cls.openstack('image list -f value -c ID')
ray = raw_output.split('\n')
- idx = len(ray)/2
+ idx = int(len(ray)/2)
return ray[idx]
@classmethod
@@ -49,7 +49,7 @@ class ServerTests(test.TestCase):
except exceptions.CommandFailed:
return ''
ray = raw_output.split('\n')
- idx = len(ray)/2
+ idx = int(len(ray)/2)
return ' --nic net-id=' + ray[idx]
@classmethod
diff --git a/functional/tests/identity/v3/test_identity.py b/functional/tests/identity/v3/test_identity.py
index dd5f2f4..3164e8f 100644
--- a/functional/tests/identity/v3/test_identity.py
+++ b/functional/tests/identity/v3/test_identity.py
@@ -32,13 +32,12 @@ class IdentityTests(test.TestCase):
'enabled', 'name', 'parent_id', 'links']
ROLE_FIELDS = ['id', 'name', 'links']
SERVICE_FIELDS = ['id', 'enabled', 'name', 'type', 'description']
- REGION_FIELDS = ['description', 'enabled', 'parent_region',
- 'region', 'url']
+ REGION_FIELDS = ['description', 'enabled', 'parent_region', 'region']
ENDPOINT_FIELDS = ['id', 'region', 'region_id', 'service_id',
'service_name', 'service_type', 'enabled',
'interface', 'url']
- REGION_LIST_HEADERS = ['Region', 'Parent Region', 'Description', 'URL']
+ REGION_LIST_HEADERS = ['Region', 'Parent Region', 'Description']
ENDPOINT_LIST_HEADERS = ['ID', 'Region', 'Service Name', 'Service Type',
'Enabled', 'Interface', 'URL']
@@ -194,7 +193,6 @@ class IdentityTests(test.TestCase):
def _create_dummy_region(self, parent_region=None, add_clean_up=True):
region_id = data_utils.rand_name('TestRegion')
description = data_utils.rand_name('description')
- url = data_utils.rand_url()
parent_region_arg = ''
if parent_region is not None:
parent_region_arg = '--parent-region %s' % parent_region
@@ -202,10 +200,8 @@ class IdentityTests(test.TestCase):
'region create '
'%(parent_region_arg)s '
'--description %(description)s '
- '--url %(url)s '
'%(id)s' % {'parent_region_arg': parent_region_arg,
'description': description,
- 'url': url,
'id': region_id})
items = self.parse_show(raw_output)
self.assert_show_fields(items, self.REGION_FIELDS)
diff --git a/functional/tests/image/v1/test_image.py b/functional/tests/image/v1/test_image.py
index 17c0c3d..fe61f83 100644
--- a/functional/tests/image/v1/test_image.py
+++ b/functional/tests/image/v1/test_image.py
@@ -35,10 +35,9 @@ class ImageTests(test.TestCase):
@classmethod
def tearDownClass(cls):
# Rename test
- opts = cls.get_show_opts(cls.FIELDS)
- raw_output = cls.openstack(
- 'image set --name ' + cls.OTHER_NAME + ' ' + cls.NAME + opts)
- cls.assertOutput(cls.OTHER_NAME + "\n", raw_output)
+ raw_output = cls.openstack('image set --name ' + cls.OTHER_NAME + ' '
+ + cls.NAME)
+ cls.assertOutput('', raw_output)
# Delete test
raw_output = cls.openstack('image delete ' + cls.OTHER_NAME)
cls.assertOutput('', raw_output)
@@ -56,13 +55,13 @@ class ImageTests(test.TestCase):
def test_image_set(self):
opts = self.get_show_opts([
"disk_format", "is_public", "min_disk", "min_ram", "name"])
- raw_output = self.openstack('image set --min-disk 4 --min-ram 5 ' +
- '--disk-format qcow2 --public ' +
- self.NAME + opts)
+ self.openstack('image set --min-disk 4 --min-ram 5 ' +
+ '--disk-format qcow2 --public ' + self.NAME)
+ raw_output = self.openstack('image show ' + self.NAME + opts)
self.assertEqual("qcow2\nTrue\n4\n5\n" + self.NAME + '\n', raw_output)
def test_image_metadata(self):
opts = self.get_show_opts(["name", "properties"])
- raw_output = self.openstack(
- 'image set --property a=b --property c=d ' + self.NAME + opts)
+ self.openstack('image set --property a=b --property c=d ' + self.NAME)
+ raw_output = self.openstack('image show ' + self.NAME + opts)
self.assertEqual(self.NAME + "\na='b', c='d'\n", raw_output)
diff --git a/functional/tests/network/__init__.py b/functional/tests/network/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/functional/tests/network/__init__.py
diff --git a/functional/tests/network/v2/__init__.py b/functional/tests/network/v2/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/functional/tests/network/v2/__init__.py
diff --git a/functional/tests/network/v2/test_network.py b/functional/tests/network/v2/test_network.py
new file mode 100644
index 0000000..0b2f35e
--- /dev/null
+++ b/functional/tests/network/v2/test_network.py
@@ -0,0 +1,50 @@
+# 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 uuid
+
+from functional.common import test
+
+
+class NetworkTests(test.TestCase):
+ """Functional tests for network. """
+ NAME = uuid.uuid4().hex
+ HEADERS = ['Name']
+ FIELDS = ['name']
+
+ @classmethod
+ def setUpClass(cls):
+ opts = cls.get_show_opts(cls.FIELDS)
+ raw_output = cls.openstack('network create ' + cls.NAME + opts)
+ expected = cls.NAME + '\n'
+ cls.assertOutput(expected, raw_output)
+
+ @classmethod
+ def tearDownClass(cls):
+ raw_output = cls.openstack('network delete ' + cls.NAME)
+ cls.assertOutput('', raw_output)
+
+ def test_network_list(self):
+ opts = self.get_list_opts(self.HEADERS)
+ raw_output = self.openstack('network list' + opts)
+ self.assertIn(self.NAME, raw_output)
+
+ def test_network_set(self):
+ raw_output = self.openstack('network set --disable ' + self.NAME)
+ opts = self.get_show_opts(['name', 'state'])
+ raw_output = self.openstack('network show ' + self.NAME + opts)
+ self.assertEqual(self.NAME + "\nDOWN\n", raw_output)
+
+ def test_network_show(self):
+ opts = self.get_show_opts(self.FIELDS)
+ raw_output = self.openstack('network show ' + self.NAME + opts)
+ self.assertEqual(self.NAME + "\n", raw_output)
diff --git a/functional/tests/volume/v1/test_volume.py b/functional/tests/volume/v1/test_volume.py
index 596b821..874be6e 100644
--- a/functional/tests/volume/v1/test_volume.py
+++ b/functional/tests/volume/v1/test_volume.py
@@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import os
import uuid
from functional.tests.volume.v1 import common
@@ -25,6 +26,7 @@ class VolumeTests(common.BaseVolumeTests):
@classmethod
def setUpClass(cls):
+ os.environ['OS_VOLUME_API_VERSION'] = '1'
opts = cls.get_show_opts(cls.FIELDS)
raw_output = cls.openstack('volume create --size 1 ' + cls.NAME + opts)
expected = cls.NAME + '\n'
diff --git a/openstackclient/api/api.py b/openstackclient/api/api.py
index ba83ce4..97eb7e4 100644
--- a/openstackclient/api/api.py
+++ b/openstackclient/api/api.py
@@ -15,8 +15,9 @@
import simplejson as json
-from keystoneclient import exceptions as ksc_exceptions
-from keystoneclient import session as ksc_session
+from keystoneauth1 import exceptions as ks_exceptions
+from keystoneauth1 import session as ks_session
+
from openstackclient.common import exceptions
@@ -24,7 +25,7 @@ class KeystoneSession(object):
"""Wrapper for the Keystone Session
Restore some requests.session.Session compatibility;
- keystoneclient.session.Session.request() has the method and url
+ keystoneauth1.session.Session.request() has the method and url
arguments swapped from the rest of the requests-using world.
"""
@@ -70,7 +71,7 @@ class KeystoneSession(object):
if not session:
session = self.session
if not session:
- session = ksc_session.Session()
+ session = ks_session.Session()
if self.endpoint:
if url:
@@ -255,7 +256,7 @@ class BaseAPI(KeystoneSession):
return data[0]
if len(data) > 1:
msg = "Multiple %s exist with %s='%s'"
- raise ksc_exceptions.CommandError(
+ raise exceptions.CommandError(
msg % (resource, attr, value),
)
@@ -314,7 +315,7 @@ class BaseAPI(KeystoneSession):
num_bulk = len(bulk_list)
if num_bulk == 0:
msg = "none found"
- raise ksc_exceptions.NotFound(msg)
+ raise exceptions.NotFound(msg)
elif num_bulk > 1:
msg = "many found"
raise RuntimeError(msg)
@@ -338,12 +339,12 @@ class BaseAPI(KeystoneSession):
try:
ret = self._request('GET', "/%s/%s" % (path, value)).json()
- except ksc_exceptions.NotFound:
+ except ks_exceptions.NotFound:
kwargs = {attr: value}
try:
ret = self.find_one("/%s/detail" % (path), **kwargs)
- except ksc_exceptions.NotFound:
+ except ks_exceptions.NotFound:
msg = "%s not found" % value
- raise ksc_exceptions.NotFound(msg)
+ raise exceptions.NotFound(msg)
return ret
diff --git a/openstackclient/api/object_store_v1.py b/openstackclient/api/object_store_v1.py
index c52eeb3..ab75a78 100644
--- a/openstackclient/api/object_store_v1.py
+++ b/openstackclient/api/object_store_v1.py
@@ -14,8 +14,11 @@
"""Object Store v1 API Library"""
import io
+import logging
import os
+
import six
+from six.moves import urllib
try:
from urllib.parse import urlparse # noqa
@@ -23,6 +26,7 @@ except ImportError:
from urlparse import urlparse # noqa
from openstackclient.api import api
+from openstackclient.common import utils
class APIv1(api.BaseAPI):
@@ -42,11 +46,9 @@ class APIv1(api.BaseAPI):
:returns:
dict of returned headers
"""
-
- response = self.create(container, method='PUT')
- url_parts = urlparse(self.endpoint)
+ response = self.create(urllib.parse.quote(container), method='PUT')
data = {
- 'account': url_parts.path.split('/')[-1],
+ 'account': self._find_account_id(),
'container': container,
'x-trans-id': response.headers.get('x-trans-id', None),
}
@@ -64,7 +66,7 @@ class APIv1(api.BaseAPI):
"""
if container:
- self.delete(container)
+ self.delete(urllib.parse.quote(container))
def container_list(
self,
@@ -140,6 +142,23 @@ class APIv1(api.BaseAPI):
for object in objects:
self.object_save(container=container, object=object['name'])
+ def container_set(
+ self,
+ container,
+ properties,
+ ):
+ """Set container properties
+
+ :param string container:
+ name of container to modify
+ :param dict properties:
+ properties to add or update for the container
+ """
+
+ headers = self._set_properties(properties, 'X-Container-Meta-%s')
+ if headers:
+ self.create(urllib.parse.quote(container), headers=headers)
+
def container_show(
self,
container=None,
@@ -152,22 +171,52 @@ class APIv1(api.BaseAPI):
dict of returned headers
"""
- response = self._request('HEAD', container)
+ response = self._request('HEAD', urllib.parse.quote(container))
data = {
- 'account': response.headers.get('x-container-meta-owner', None),
+ 'account': self._find_account_id(),
'container': container,
'object_count': response.headers.get(
'x-container-object-count',
None,
),
- 'bytes_used': response.headers.get('x-container-bytes-used', None),
- 'read_acl': response.headers.get('x-container-read', None),
- 'write_acl': response.headers.get('x-container-write', None),
- 'sync_to': response.headers.get('x-container-sync-to', None),
- 'sync_key': response.headers.get('x-container-sync-key', None),
+ 'bytes_used': response.headers.get('x-container-bytes-used', None)
}
+
+ if 'x-container-read' in response.headers:
+ data['read_acl'] = response.headers.get('x-container-read', None)
+ if 'x-container-write' in response.headers:
+ data['write_acl'] = response.headers.get('x-container-write', None)
+ if 'x-container-sync-to' in response.headers:
+ data['sync_to'] = response.headers.get('x-container-sync-to', None)
+ if 'x-container-sync-key' in response.headers:
+ data['sync_key'] = response.headers.get('x-container-sync-key',
+ None)
+
+ properties = self._get_properties(response.headers,
+ 'x-container-meta-')
+ if properties:
+ data['properties'] = properties
+
return data
+ def container_unset(
+ self,
+ container,
+ properties,
+ ):
+ """Unset container properties
+
+ :param string container:
+ name of container to modify
+ :param dict properties:
+ properties to remove from the container
+ """
+
+ headers = self._unset_properties(properties,
+ 'X-Remove-Container-Meta-%s')
+ if headers:
+ self.create(urllib.parse.quote(container), headers=headers)
+
def object_create(
self,
container=None,
@@ -187,16 +236,16 @@ class APIv1(api.BaseAPI):
# TODO(dtroyer): What exception to raise here?
return {}
- full_url = "%s/%s" % (container, object)
+ full_url = "%s/%s" % (urllib.parse.quote(container),
+ urllib.parse.quote(object))
with io.open(object, 'rb') as f:
response = self.create(
full_url,
method='PUT',
data=f,
)
- url_parts = urlparse(self.endpoint)
data = {
- 'account': url_parts.path.split('/')[-1],
+ 'account': self._find_account_id(),
'container': container,
'object': object,
'x-trans-id': response.headers.get('X-Trans-Id', None),
@@ -221,7 +270,8 @@ class APIv1(api.BaseAPI):
if container is None or object is None:
return
- self.delete("%s/%s" % (container, object))
+ self.delete("%s/%s" % (urllib.parse.quote(container),
+ urllib.parse.quote(object)))
def object_list(
self,
@@ -298,7 +348,7 @@ class APIv1(api.BaseAPI):
if delimiter:
params['delimiter'] = delimiter
- return self.list(container, **params)
+ return self.list(urllib.parse.quote(container), **params)
def object_save(
self,
@@ -321,7 +371,8 @@ class APIv1(api.BaseAPI):
response = self._request(
'GET',
- "%s/%s" % (container, object),
+ "%s/%s" % (urllib.parse.quote(container),
+ urllib.parse.quote(object)),
stream=True,
)
if response.status_code == 200:
@@ -332,6 +383,50 @@ class APIv1(api.BaseAPI):
for chunk in response.iter_content():
f.write(chunk)
+ def object_set(
+ self,
+ container,
+ object,
+ properties,
+ ):
+ """Set object properties
+
+ :param string container:
+ container name for object to modify
+ :param string object:
+ name of object to modify
+ :param dict properties:
+ properties to add or update for the container
+ """
+
+ headers = self._set_properties(properties, 'X-Object-Meta-%s')
+ if headers:
+ self.create("%s/%s" % (urllib.parse.quote(container),
+ urllib.parse.quote(object)),
+ headers=headers)
+
+ def object_unset(
+ self,
+ container,
+ object,
+ properties,
+ ):
+ """Unset object properties
+
+ :param string container:
+ container name for object to modify
+ :param string object:
+ name of object to modify
+ :param dict properties:
+ properties to remove from the object
+ """
+
+ headers = self._unset_properties(properties, 'X-Remove-Object-Meta-%s')
+ if headers:
+ self.create("%s/%s" % (urllib.parse.quote(container),
+ urllib.parse.quote(object)),
+ headers=headers)
+
def object_show(
self,
container=None,
@@ -350,9 +445,12 @@ class APIv1(api.BaseAPI):
if container is None or object is None:
return {}
- response = self._request('HEAD', "%s/%s" % (container, object))
+ response = self._request('HEAD', "%s/%s" %
+ (urllib.parse.quote(container),
+ urllib.parse.quote(object)))
+
data = {
- 'account': response.headers.get('x-container-meta-owner', None),
+ 'account': self._find_account_id(),
'container': container,
'object': object,
'content-type': response.headers.get('content-type', None),
@@ -371,18 +469,107 @@ class APIv1(api.BaseAPI):
'x-object-manifest',
None,
)
- for key, value in six.iteritems(response.headers):
- if key.startswith('x-object-meta-'):
- data[key[len('x-object-meta-'):].lower()] = value
- elif key not in (
- 'content-type',
- 'content-length',
- 'last-modified',
- 'etag',
- 'date',
- 'x-object-manifest',
- 'x-container-meta-owner',
- ):
- data[key.lower()] = value
+ properties = self._get_properties(response.headers, 'x-object-meta-')
+ if properties:
+ data['properties'] = properties
+
+ return data
+
+ def account_set(
+ self,
+ properties,
+ ):
+ """Set account properties
+
+ :param dict properties:
+ properties to add or update for the account
+ """
+
+ headers = self._set_properties(properties, 'X-Account-Meta-%s')
+ if headers:
+ # NOTE(stevemar): The URL (first argument) in this case is already
+ # set to the swift account endpoint, because that's how it's
+ # registered in the catalog
+ self.create("", headers=headers)
+
+ def account_show(self):
+ """Show account details"""
+
+ # NOTE(stevemar): Just a HEAD request to the endpoint already in the
+ # catalog should be enough.
+ response = self._request("HEAD", "")
+ data = {}
+
+ properties = self._get_properties(response.headers, 'x-account-meta-')
+ if properties:
+ data['properties'] = properties
+
+ # Map containers, bytes and objects a bit nicer
+ data['Containers'] = response.headers.get('x-account-container-count',
+ None)
+ data['Objects'] = response.headers.get('x-account-object-count', None)
+ data['Bytes'] = response.headers.get('x-account-bytes-used', None)
+ # Add in Account info too
+ data['Account'] = self._find_account_id()
return data
+
+ def account_unset(
+ self,
+ properties,
+ ):
+ """Unset account properties
+
+ :param dict properties:
+ properties to remove from the account
+ """
+
+ headers = self._unset_properties(properties,
+ 'X-Remove-Account-Meta-%s')
+ if headers:
+ self.create("", headers=headers)
+
+ def _find_account_id(self):
+ url_parts = urlparse(self.endpoint)
+ return url_parts.path.split('/')[-1]
+
+ def _unset_properties(self, properties, header_tag):
+ # NOTE(stevemar): As per the API, the headers have to be in the form
+ # of "X-Remove-Account-Meta-Book: x". In the case where metadata is
+ # removed, we can set the value of the header to anything, so it's
+ # set to 'x'. In the case of a Container property we use:
+ # "X-Remove-Container-Meta-Book: x", and the same logic applies for
+ # Object properties
+
+ headers = {}
+ for k in properties:
+ header_name = header_tag % k
+ headers[header_name] = 'x'
+ return headers
+
+ def _set_properties(self, properties, header_tag):
+ # NOTE(stevemar): As per the API, the headers have to be in the form
+ # of "X-Account-Meta-Book: MobyDick". In the case of a Container
+ # property we use: "X-Add-Container-Meta-Book: MobyDick", and the same
+ # logic applies for Object properties
+
+ log = logging.getLogger(__name__ + '._set_properties')
+
+ headers = {}
+ for k, v in properties.iteritems():
+ if not utils.is_ascii(k) or not utils.is_ascii(v):
+ log.error('Cannot set property %s to non-ascii value', k)
+ continue
+
+ header_name = header_tag % k
+ headers[header_name] = v
+ return headers
+
+ def _get_properties(self, headers, header_tag):
+ # Add in properties as a top level key, this is consistent with other
+ # OSC commands
+ properties = {}
+ for k, v in six.iteritems(headers):
+ if k.startswith(header_tag):
+ properties[k[len(header_tag):]] = v
+ return properties
diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py
index 55c6fe5..edabf65 100644
--- a/openstackclient/common/clientmanager.py
+++ b/openstackclient/common/clientmanager.py
@@ -20,6 +20,7 @@ import logging
import pkg_resources
import sys
+from oslo_utils import strutils
import requests
from openstackclient.api import auth
@@ -167,7 +168,8 @@ class ClientManager(object):
self._project_name = self._auth_params['tenant_name']
LOG.info('Using auth plugin: %s' % self.auth_plugin_name)
- LOG.debug('Using parameters %s' % self._auth_params)
+ LOG.debug('Using parameters %s' %
+ strutils.mask_password(self._auth_params))
self.auth = auth_plugin.load_from_options(**self._auth_params)
# needed by SAML authentication
request_session = requests.session()
diff --git a/openstackclient/common/context.py b/openstackclient/common/logs.py
index 6d1aec1..6d1aec1 100644
--- a/openstackclient/common/context.py
+++ b/openstackclient/common/logs.py
diff --git a/openstackclient/common/parseractions.py b/openstackclient/common/parseractions.py
index 8f6008e..fd90369 100644
--- a/openstackclient/common/parseractions.py
+++ b/openstackclient/common/parseractions.py
@@ -65,3 +65,18 @@ class RangeAction(argparse.Action):
# Too many values
msg = "Invalid range, too many values"
raise argparse.ArgumentError(self, msg)
+
+
+class NonNegativeAction(argparse.Action):
+ """A custom action to check whether the value is non-negative or not
+
+ Ensures the value is >= 0.
+ """
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ try:
+ assert(int(values) >= 0)
+ setattr(namespace, self.dest, values)
+ except Exception:
+ msg = "%s expected a non-negative integer" % (str(option_string))
+ raise argparse.ArgumentTypeError(self, msg)
diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py
index e092fef..8a9b910 100644
--- a/openstackclient/common/quota.py
+++ b/openstackclient/common/quota.py
@@ -81,6 +81,7 @@ class SetQuota(command.Command):
parser.add_argument(
'--%s' % v,
metavar='<%s>' % v,
+ dest=k,
type=int,
help='New value for the %s quota' % v,
)
@@ -94,6 +95,7 @@ class SetQuota(command.Command):
@utils.log_method(log)
def take_action(self, parsed_args):
+ identity_client = self.app.client_manager.identity
compute_client = self.app.client_manager.compute
volume_client = self.app.client_manager.volume
@@ -115,23 +117,29 @@ class SetQuota(command.Command):
sys.stderr.write("No quotas updated")
return
+ if parsed_args.project:
+ project = utils.find_resource(
+ identity_client.projects,
+ parsed_args.project,
+ )
+
if parsed_args.quota_class:
if compute_kwargs:
compute_client.quota_classes.update(
- parsed_args.project,
+ project.id,
**compute_kwargs)
if volume_kwargs:
volume_client.quota_classes.update(
- parsed_args.project,
+ project.id,
**volume_kwargs)
else:
if compute_kwargs:
compute_client.quotas.update(
- parsed_args.project,
+ project.id,
**compute_kwargs)
if volume_kwargs:
volume_client.quotas.update(
- parsed_args.project,
+ project.id,
**volume_kwargs)
@@ -165,13 +173,19 @@ class ShowQuota(show.ShowOne):
return parser
def get_compute_volume_quota(self, client, parsed_args):
+ identity_client = self.app.client_manager.identity
+ project = utils.find_resource(
+ identity_client.projects,
+ parsed_args.project,
+ ).id
+
try:
if parsed_args.quota_class:
- quota = client.quota_classes.get(parsed_args.project)
+ quota = client.quota_classes.get(project)
elif parsed_args.default:
- quota = client.quotas.defaults(parsed_args.project)
+ quota = client.quotas.defaults(project)
else:
- quota = client.quotas.get(parsed_args.project)
+ quota = client.quotas.get(project)
except Exception as e:
if type(e).__name__ == 'EndpointNotFound':
return {}
diff --git a/openstackclient/common/session.py b/openstackclient/common/session.py
index dda1c41..9b19fd4 100644
--- a/openstackclient/common/session.py
+++ b/openstackclient/common/session.py
@@ -11,9 +11,9 @@
# under the License.
#
-"""Subclass of keystoneclient.session"""
+"""Subclass of keystoneauth1.session"""
-from keystoneclient import session
+from keystoneauth1 import session
class TimingSession(session.Session):
diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py
index b6726bf..42630d9 100644
--- a/openstackclient/common/utils.py
+++ b/openstackclient/common/utils.py
@@ -94,12 +94,15 @@ def find_resource(manager, name_or_id, **kwargs):
if len(kwargs) == 0:
kwargs = {}
- # Prepare the kwargs for calling find
- if 'NAME_ATTR' in manager.resource_class.__dict__:
- # novaclient does this for oddball resources
- kwargs[manager.resource_class.NAME_ATTR] = name_or_id
- else:
- kwargs['name'] = name_or_id
+ try:
+ # Prepare the kwargs for calling find
+ if 'NAME_ATTR' in manager.resource_class.__dict__:
+ # novaclient does this for oddball resources
+ kwargs[manager.resource_class.NAME_ATTR] = name_or_id
+ else:
+ kwargs['name'] = name_or_id
+ except Exception:
+ pass
# finally try to find entity by name
try:
@@ -118,14 +121,30 @@ def find_resource(manager, name_or_id, **kwargs):
(manager.resource_class.__name__.lower(), name_or_id)
raise exceptions.CommandError(msg)
else:
- raise
+ pass
+
+ try:
+ for resource in manager.list():
+ # short circuit and return the first match
+ if (resource.get('id') == name_or_id or
+ resource.get('name') == name_or_id):
+ return resource
+ else:
+ # we found no match, keep going to bomb out
+ pass
+ except Exception:
+ # in case the list fails for some reason
+ pass
+
+ # if we hit here, we've failed, report back this error:
+ msg = "Could not find resource %s" % name_or_id
+ raise exceptions.CommandError(msg)
def format_dict(data):
"""Return a formatted string of key value pairs
:param data: a dict
- :param format: optional formatting hints
:rtype: a string formatted to key='value'
"""
@@ -278,14 +297,16 @@ def wait_for_status(status_f,
res_id,
status_field='status',
success_status=['active'],
+ error_status=['error'],
sleep_time=5,
callback=None):
"""Wait for status change on a resource during a long-running operation
:param status_f: a status function that takes a single id argument
:param res_id: the resource id to watch
- :param success_status: a list of status strings for successful completion
:param status_field: the status attribute in the returned resource object
+ :param success_status: a list of status strings for successful completion
+ :param error_status: a list of status strings for error
:param sleep_time: wait this long (seconds)
:param callback: called per sleep cycle, useful to display progress
:rtype: True on success
@@ -296,7 +317,7 @@ def wait_for_status(status_f,
if status in success_status:
retval = True
break
- elif status == 'error':
+ elif status in error_status:
retval = False
break
if callback:
@@ -314,6 +335,7 @@ def wait_for_delete(manager,
callback=None):
"""Wait for resource deletion
+ :param manager: the manager from which we can get the resource
:param res_id: the resource id to watch
:param status_field: the status attribute in the returned resource object,
this is used to check for error states while the resource is being
@@ -355,7 +377,7 @@ def wait_for_delete(manager,
def get_effective_log_level():
"""Returns the lowest logging level considered by logging handlers
- Retrieve an return the smallest log level set among the root
+ Retrieve and return the smallest log level set among the root
logger's handlers (in case of multiple handlers).
"""
root_log = logging.getLogger()
@@ -399,3 +421,11 @@ def build_kwargs_dict(arg_name, value):
if value:
kwargs[arg_name] = value
return kwargs
+
+
+def is_ascii(string):
+ try:
+ string.decode('ascii')
+ return True
+ except UnicodeDecodeError:
+ return False
diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py
index bb0747b..aafa5d4 100644
--- a/openstackclient/compute/v2/console.py
+++ b/openstackclient/compute/v2/console.py
@@ -22,6 +22,7 @@ import sys
from cliff import command
from cliff import show
+from openstackclient.common import parseractions
from openstackclient.common import utils
@@ -42,6 +43,7 @@ class ShowConsoleLog(command.Command):
metavar='<num-lines>',
type=int,
default=None,
+ action=parseractions.NonNegativeAction,
help='Number of lines to display from the end of the log '
'(default=all)',
)
diff --git a/openstackclient/compute/v2/fixedip.py b/openstackclient/compute/v2/fixedip.py
index d105e39..da9d85c 100644
--- a/openstackclient/compute/v2/fixedip.py
+++ b/openstackclient/compute/v2/fixedip.py
@@ -23,7 +23,7 @@ from openstackclient.common import utils
class AddFixedIP(command.Command):
- """Add fixed-ip command"""
+ """Add fixed IP address to server"""
log = logging.getLogger(__name__ + ".AddFixedIP")
@@ -32,12 +32,12 @@ class AddFixedIP(command.Command):
parser.add_argument(
"network",
metavar="<network>",
- help="Name of the network to fetch an IP address from",
+ help="Network to fetch an IP address from (name or ID)",
)
parser.add_argument(
"server",
metavar="<server>",
- help="Name of the server to receive the IP address",
+ help="Server to receive the IP address (name or ID)",
)
return parser
@@ -56,7 +56,7 @@ class AddFixedIP(command.Command):
class RemoveFixedIP(command.Command):
- """Remove fixed-ip command"""
+ """Remove fixed IP address from server"""
log = logging.getLogger(__name__ + ".RemoveFixedIP")
@@ -65,12 +65,12 @@ class RemoveFixedIP(command.Command):
parser.add_argument(
"ip_address",
metavar="<ip-address>",
- help="IP address to remove from server",
+ help="IP address to remove from server (name only)",
)
parser.add_argument(
"server",
metavar="<server>",
- help="Name of the server to remove the IP address from",
+ help="Server to remove the IP address from (name or ID)",
)
return parser
diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py
index 3458cf7..7474580 100644
--- a/openstackclient/compute/v2/flavor.py
+++ b/openstackclient/compute/v2/flavor.py
@@ -181,6 +181,15 @@ class ListFlavor(lister.Lister):
action='store_true',
default=False,
help='List additional fields in output')
+ parser.add_argument(
+ '--marker',
+ metavar="<marker>",
+ help='The last flavor ID of the previous page')
+ parser.add_argument(
+ '--limit',
+ type=int,
+ metavar="<limit>",
+ help='Maximum number of flavors to display')
return parser
def take_action(self, parsed_args):
@@ -202,7 +211,9 @@ class ListFlavor(lister.Lister):
# and flavors from their own projects only.
is_public = None if parsed_args.all else parsed_args.public
- data = compute_client.flavors.list(is_public=is_public)
+ data = compute_client.flavors.list(is_public=is_public,
+ marker=parsed_args.marker,
+ limit=parsed_args.limit)
if parsed_args.long:
columns = columns + (
diff --git a/openstackclient/compute/v2/floatingip.py b/openstackclient/compute/v2/floatingip.py
index c557c24..65fe591 100644
--- a/openstackclient/compute/v2/floatingip.py
+++ b/openstackclient/compute/v2/floatingip.py
@@ -26,7 +26,7 @@ from openstackclient.common import utils
class AddFloatingIP(command.Command):
- """Add floating-ip to server"""
+ """Add floating IP address to server"""
log = logging.getLogger(__name__ + ".AddFloatingIP")
@@ -35,7 +35,7 @@ class AddFloatingIP(command.Command):
parser.add_argument(
"ip_address",
metavar="<ip-address>",
- help="IP address to add to server",
+ help="IP address to add to server (name only)",
)
parser.add_argument(
"server",
@@ -56,7 +56,7 @@ class AddFloatingIP(command.Command):
class CreateFloatingIP(show.ShowOne):
- """Create new floating-ip"""
+ """Create new floating IP address"""
log = logging.getLogger(__name__ + '.CreateFloatingIP')
@@ -65,7 +65,7 @@ class CreateFloatingIP(show.ShowOne):
parser.add_argument(
'pool',
metavar='<pool>',
- help='Pool to fetch floating IP from',
+ help='Pool to fetch IP address from (name or ID)',
)
return parser
@@ -80,7 +80,7 @@ class CreateFloatingIP(show.ShowOne):
class DeleteFloatingIP(command.Command):
- """Delete a floating-ip"""
+ """Delete a floating IP address"""
log = logging.getLogger(__name__ + '.DeleteFloatingIP')
@@ -89,7 +89,7 @@ class DeleteFloatingIP(command.Command):
parser.add_argument(
"ip_address",
metavar="<ip-address>",
- help="IP address to delete",
+ help="IP address to delete (ID only)",
)
return parser
@@ -107,7 +107,7 @@ class DeleteFloatingIP(command.Command):
class ListFloatingIP(lister.Lister):
- """List floating-ips"""
+ """List floating IP addresses"""
log = logging.getLogger(__name__ + '.ListFloatingIP')
@@ -127,7 +127,7 @@ class ListFloatingIP(lister.Lister):
class RemoveFloatingIP(command.Command):
- """Remove floating-ip from server"""
+ """Remove floating IP address from server"""
log = logging.getLogger(__name__ + ".RemoveFloatingIP")
@@ -136,7 +136,7 @@ class RemoveFloatingIP(command.Command):
parser.add_argument(
"ip_address",
metavar="<ip-address>",
- help="IP address to remove from server",
+ help="IP address to remove from server (name only)",
)
parser.add_argument(
"server",
diff --git a/openstackclient/compute/v2/floatingippool.py b/openstackclient/compute/v2/floatingippool.py
index cc48555..39a2d8f 100644
--- a/openstackclient/compute/v2/floatingippool.py
+++ b/openstackclient/compute/v2/floatingippool.py
@@ -23,7 +23,7 @@ from openstackclient.common import utils
class ListFloatingIPPool(lister.Lister):
- """List floating-ip-pools"""
+ """List pools of floating IP addresses"""
log = logging.getLogger(__name__ + '.ListFloatingIPPool')
diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py
index 3dd0c49..6d38195 100644
--- a/openstackclient/compute/v2/security_group.py
+++ b/openstackclient/compute/v2/security_group.py
@@ -23,7 +23,7 @@ from cliff import command
from cliff import lister
from cliff import show
-from keystoneclient import exceptions as ksc_exc
+from keystoneauth1 import exceptions as ks_exc
try:
from novaclient.v2 import security_group_rules
@@ -93,6 +93,64 @@ class CreateSecurityGroup(show.ShowOne):
return zip(*sorted(six.iteritems(info)))
+class CreateSecurityGroupRule(show.ShowOne):
+ """Create a new security group rule"""
+
+ log = logging.getLogger(__name__ + ".CreateSecurityGroupRule")
+
+ def get_parser(self, prog_name):
+ parser = super(CreateSecurityGroupRule, self).get_parser(prog_name)
+ parser.add_argument(
+ 'group',
+ metavar='<group>',
+ help='Create rule in this security group (name or ID)',
+ )
+ parser.add_argument(
+ "--proto",
+ metavar="<proto>",
+ default="tcp",
+ help="IP protocol (icmp, tcp, udp; default: tcp)",
+ )
+ parser.add_argument(
+ "--src-ip",
+ metavar="<ip-address>",
+ default="0.0.0.0/0",
+ help="Source IP (may use CIDR notation; default: 0.0.0.0/0)",
+ )
+ parser.add_argument(
+ "--dst-port",
+ metavar="<port-range>",
+ default=(0, 0),
+ action=parseractions.RangeAction,
+ help="Destination port, may be a range: 137:139 (default: 0; "
+ "only required for proto tcp and udp)",
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ self.log.debug("take_action(%s)", parsed_args)
+
+ compute_client = self.app.client_manager.compute
+ group = utils.find_resource(
+ compute_client.security_groups,
+ parsed_args.group,
+ )
+ if parsed_args.proto.lower() == 'icmp':
+ from_port, to_port = -1, -1
+ else:
+ from_port, to_port = parsed_args.dst_port
+ data = compute_client.security_group_rules.create(
+ group.id,
+ parsed_args.proto,
+ from_port,
+ to_port,
+ parsed_args.src_ip,
+ )
+
+ info = _xform_security_group_rule(data._info)
+ return zip(*sorted(six.iteritems(info)))
+
+
class DeleteSecurityGroup(command.Command):
"""Delete a security group"""
@@ -103,7 +161,7 @@ class DeleteSecurityGroup(command.Command):
parser.add_argument(
'group',
metavar='<group>',
- help='Name or ID of security group to delete',
+ help='Security group to delete (name or ID)',
)
return parser
@@ -119,8 +177,30 @@ class DeleteSecurityGroup(command.Command):
return
+class DeleteSecurityGroupRule(command.Command):
+ """Delete a security group rule"""
+
+ log = logging.getLogger(__name__ + '.DeleteSecurityGroupRule')
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteSecurityGroupRule, self).get_parser(prog_name)
+ parser.add_argument(
+ 'rule',
+ metavar='<rule>',
+ help='Security group rule to delete (ID only)',
+ )
+ return parser
+
+ @utils.log_method(log)
+ def take_action(self, parsed_args):
+
+ compute_client = self.app.client_manager.compute
+ compute_client.security_group_rules.delete(parsed_args.rule)
+ return
+
+
class ListSecurityGroup(lister.Lister):
- """List all security groups"""
+ """List security groups"""
log = logging.getLogger(__name__ + ".ListSecurityGroup")
@@ -161,7 +241,7 @@ class ListSecurityGroup(lister.Lister):
project_hash = {}
try:
projects = self.app.client_manager.identity.projects.list()
- except ksc_exc.ClientException:
+ except ks_exc.ClientException:
# This fails when the user is not an admin, just move along
pass
else:
@@ -175,6 +255,49 @@ class ListSecurityGroup(lister.Lister):
) for s in data))
+class ListSecurityGroupRule(lister.Lister):
+ """List security group rules"""
+
+ log = logging.getLogger(__name__ + ".ListSecurityGroupRule")
+
+ def get_parser(self, prog_name):
+ parser = super(ListSecurityGroupRule, self).get_parser(prog_name)
+ parser.add_argument(
+ 'group',
+ metavar='<group>',
+ help='List all rules in this security group (name or ID)',
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ self.log.debug("take_action(%s)", parsed_args)
+
+ compute_client = self.app.client_manager.compute
+ group = utils.find_resource(
+ compute_client.security_groups,
+ parsed_args.group,
+ )
+
+ # Argh, the rules are not Resources...
+ rules = []
+ for rule in group.rules:
+ rules.append(security_group_rules.SecurityGroupRule(
+ compute_client.security_group_rules,
+ _xform_security_group_rule(rule),
+ ))
+
+ columns = column_headers = (
+ "ID",
+ "IP Protocol",
+ "IP Range",
+ "Port Range",
+ )
+ return (column_headers,
+ (utils.get_item_properties(
+ s, columns,
+ ) for s in rules))
+
+
class SetSecurityGroup(show.ShowOne):
"""Set security group properties"""
@@ -185,7 +308,7 @@ class SetSecurityGroup(show.ShowOne):
parser.add_argument(
'group',
metavar='<group>',
- help='Name or ID of security group to change',
+ help='Security group to modify (name or ID)',
)
parser.add_argument(
'--name',
@@ -195,7 +318,7 @@ class SetSecurityGroup(show.ShowOne):
parser.add_argument(
"--description",
metavar="<description>",
- help="New security group name",
+ help="New security group description",
)
return parser
@@ -227,7 +350,7 @@ class SetSecurityGroup(show.ShowOne):
class ShowSecurityGroup(show.ShowOne):
- """Show a specific security group"""
+ """Display security group details"""
log = logging.getLogger(__name__ + '.ShowSecurityGroup')
@@ -236,7 +359,7 @@ class ShowSecurityGroup(show.ShowOne):
parser.add_argument(
'group',
metavar='<group>',
- help='Name or ID of security group to change',
+ help='Security group to display (name or ID)',
)
return parser
@@ -263,126 +386,3 @@ class ShowSecurityGroup(show.ShowOne):
)
return zip(*sorted(six.iteritems(info)))
-
-
-class CreateSecurityGroupRule(show.ShowOne):
- """Create a new security group rule"""
-
- log = logging.getLogger(__name__ + ".CreateSecurityGroupRule")
-
- def get_parser(self, prog_name):
- parser = super(CreateSecurityGroupRule, self).get_parser(prog_name)
- parser.add_argument(
- 'group',
- metavar='<group>',
- help='Create rule in this security group',
- )
- parser.add_argument(
- "--proto",
- metavar="<proto>",
- default="tcp",
- help="IP protocol (icmp, tcp, udp; default: tcp)",
- )
- parser.add_argument(
- "--src-ip",
- metavar="<ip-address>",
- default="0.0.0.0/0",
- help="Source IP (may use CIDR notation; default: 0.0.0.0/0)",
- )
- parser.add_argument(
- "--dst-port",
- metavar="<port-range>",
- default=(0, 0),
- action=parseractions.RangeAction,
- help="Destination port, may be a range: 137:139 (default: 0; "
- "only required for proto tcp and udp)",
- )
- return parser
-
- def take_action(self, parsed_args):
- self.log.debug("take_action(%s)", parsed_args)
-
- compute_client = self.app.client_manager.compute
- group = utils.find_resource(
- compute_client.security_groups,
- parsed_args.group,
- )
- if parsed_args.proto.lower() == 'icmp':
- from_port, to_port = -1, -1
- else:
- from_port, to_port = parsed_args.dst_port
- data = compute_client.security_group_rules.create(
- group.id,
- parsed_args.proto,
- from_port,
- to_port,
- parsed_args.src_ip,
- )
-
- info = _xform_security_group_rule(data._info)
- return zip(*sorted(six.iteritems(info)))
-
-
-class DeleteSecurityGroupRule(command.Command):
- """Delete a security group rule"""
-
- log = logging.getLogger(__name__ + '.DeleteSecurityGroupRule')
-
- def get_parser(self, prog_name):
- parser = super(DeleteSecurityGroupRule, self).get_parser(prog_name)
- parser.add_argument(
- 'rule',
- metavar='<rule>',
- help='Security group rule ID to delete',
- )
- return parser
-
- @utils.log_method(log)
- def take_action(self, parsed_args):
-
- compute_client = self.app.client_manager.compute
- compute_client.security_group_rules.delete(parsed_args.rule)
- return
-
-
-class ListSecurityGroupRule(lister.Lister):
- """List all security group rules"""
-
- log = logging.getLogger(__name__ + ".ListSecurityGroupRule")
-
- def get_parser(self, prog_name):
- parser = super(ListSecurityGroupRule, self).get_parser(prog_name)
- parser.add_argument(
- 'group',
- metavar='<group>',
- help='List all rules in this security group',
- )
- return parser
-
- def take_action(self, parsed_args):
- self.log.debug("take_action(%s)", parsed_args)
-
- compute_client = self.app.client_manager.compute
- group = utils.find_resource(
- compute_client.security_groups,
- parsed_args.group,
- )
-
- # Argh, the rules are not Resources...
- rules = []
- for rule in group.rules:
- rules.append(security_group_rules.SecurityGroupRule(
- compute_client.security_group_rules,
- _xform_security_group_rule(rule),
- ))
-
- columns = column_headers = (
- "ID",
- "IP Protocol",
- "IP Range",
- "Port Range",
- )
- return (column_headers,
- (utils.get_item_properties(
- s, columns,
- ) for s in rules))
diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py
index 661ce84..9dca784 100644
--- a/openstackclient/compute/v2/server.py
+++ b/openstackclient/compute/v2/server.py
@@ -43,7 +43,7 @@ from openstackclient.network import common
def _format_servers_list_networks(networks):
"""Return a formatted string of a server's networks
- :param server: a Server.networks field
+ :param networks: a Server.networks field
:rtype: a string of formatted network addresses
"""
output = []
@@ -56,6 +56,29 @@ def _format_servers_list_networks(networks):
return '; '.join(output)
+def _format_servers_list_power_state(state):
+ """Return a formatted string of a server's power state
+
+ :param state: the power state number of a server
+ :rtype: a string mapped to the power state number
+ """
+ power_states = [
+ 'NOSTATE', # 0x00
+ 'Running', # 0x01
+ '', # 0x02
+ 'Paused', # 0x03
+ 'Shutdown', # 0x04
+ '', # 0x05
+ 'Crashed', # 0x06
+ 'Suspended' # 0x07
+ ]
+
+ try:
+ return power_states[state]
+ except Exception:
+ return 'N/A'
+
+
def _get_ip_address(addresses, address_type, ip_address_family):
# Old style addresses
if address_type in addresses:
@@ -425,10 +448,17 @@ class CreateServer(show.ShowOne):
dev_key, dev_vol = dev_map.split('=', 1)
block_volume = None
if dev_vol:
- block_volume = utils.find_resource(
- volume_client.volumes,
- dev_vol,
- ).id
+ vol = dev_vol.split(':', 1)[0]
+ if vol:
+ vol_id = utils.find_resource(
+ volume_client.volumes,
+ vol,
+ ).id
+ block_volume = dev_vol.replace(vol, vol_id)
+ else:
+ msg = _("Volume name or ID must be specified if "
+ "--block-device-mapping is specified")
+ raise exceptions.CommandError(msg)
block_device_mapping.update({dev_key: block_volume})
nics = []
@@ -607,7 +637,7 @@ class DeleteServer(command.Command):
def get_parser(self, prog_name):
parser = super(DeleteServer, self).get_parser(prog_name)
parser.add_argument(
- 'servers',
+ 'server',
metavar='<server>',
nargs="+",
help=_('Server(s) to delete (name or ID)'),
@@ -622,7 +652,7 @@ class DeleteServer(command.Command):
@utils.log_method(log)
def take_action(self, parsed_args):
compute_client = self.app.client_manager.compute
- for server in parsed_args.servers:
+ for server in parsed_args.server:
server_obj = utils.find_resource(
compute_client.servers, server)
compute_client.servers.delete(server_obj.id)
@@ -717,6 +747,24 @@ class ListServer(lister.Lister):
default=False,
help=_('List additional fields in output'),
)
+ parser.add_argument(
+ '--marker',
+ metavar='<marker>',
+ default=None,
+ help=('The last server (name or ID) of the previous page. Display'
+ ' list of servers after marker. Display all servers if not'
+ ' specified.')
+ )
+ parser.add_argument(
+ '--limit',
+ metavar='<limit>',
+ type=int,
+ default=None,
+ help=("Maximum number of servers to display. If limit equals -1,"
+ " all servers will be displayed. If limit is greater than"
+ " 'osapi_max_limit' option of Nova API,"
+ " 'osapi_max_limit' will be used instead."),
+ )
return parser
@utils.log_method(log)
@@ -735,7 +783,7 @@ class ListServer(lister.Lister):
user_id = None
if parsed_args.user:
- user_id = identity_common.find_project(
+ user_id = identity_common.find_user(
identity_client,
parsed_args.user,
parsed_args.user_domain,
@@ -762,6 +810,8 @@ class ListServer(lister.Lister):
'ID',
'Name',
'Status',
+ 'OS-EXT-STS:task_state',
+ 'OS-EXT-STS:power_state',
'Networks',
'OS-EXT-AZ:availability_zone',
'OS-EXT-SRV-ATTR:host',
@@ -771,25 +821,49 @@ class ListServer(lister.Lister):
'ID',
'Name',
'Status',
+ 'Task State',
+ 'Power State',
'Networks',
'Availability Zone',
'Host',
'Properties',
)
mixed_case_fields = [
+ 'OS-EXT-STS:task_state',
+ 'OS-EXT-STS:power_state',
'OS-EXT-AZ:availability_zone',
'OS-EXT-SRV-ATTR:host',
]
else:
- columns = ('ID', 'Name', 'Status', 'Networks')
- column_headers = columns
+ columns = (
+ 'ID',
+ 'Name',
+ 'Status',
+ 'Networks',
+ )
+ column_headers = (
+ 'ID',
+ 'Name',
+ 'Status',
+ 'Networks',
+ )
mixed_case_fields = []
- data = compute_client.servers.list(search_opts=search_opts)
+
+ marker_id = None
+ if parsed_args.marker:
+ marker_id = utils.find_resource(compute_client.servers,
+ parsed_args.marker).id
+
+ data = compute_client.servers.list(search_opts=search_opts,
+ marker=marker_id,
+ limit=parsed_args.limit)
return (column_headers,
(utils.get_item_properties(
s, columns,
mixed_case_fields=mixed_case_fields,
formatters={
+ 'OS-EXT-STS:power_state':
+ _format_servers_list_power_state,
'Networks': _format_servers_list_networks,
'Metadata': utils.format_dict,
},
@@ -798,7 +872,7 @@ class ListServer(lister.Lister):
class LockServer(command.Command):
- """Lock a server. A non-admin user will not be able to execute actions"""
+ """Lock server(s). A non-admin user will not be able to execute actions"""
log = logging.getLogger(__name__ + '.LockServer')
@@ -807,7 +881,8 @@ class LockServer(command.Command):
parser.add_argument(
'server',
metavar='<server>',
- help=_('Server (name or ID)'),
+ nargs='+',
+ help=_('Server(s) to lock (name or ID)'),
)
return parser
@@ -815,10 +890,11 @@ class LockServer(command.Command):
def take_action(self, parsed_args):
compute_client = self.app.client_manager.compute
- utils.find_resource(
- compute_client.servers,
- parsed_args.server,
- ).lock()
+ for server in parsed_args.server:
+ utils.find_resource(
+ compute_client.servers,
+ server,
+ ).lock()
# FIXME(dtroyer): Here is what I want, how with argparse/cliff?
@@ -915,7 +991,7 @@ class MigrateServer(command.Command):
class PauseServer(command.Command):
- """Pause server"""
+ """Pause server(s)"""
log = logging.getLogger(__name__ + '.PauseServer')
@@ -924,18 +1000,19 @@ class PauseServer(command.Command):
parser.add_argument(
'server',
metavar='<server>',
- help=_('Server (name or ID)'),
+ nargs='+',
+ help=_('Server(s) to pause (name or ID)'),
)
return parser
@utils.log_method(log)
def take_action(self, parsed_args):
-
compute_client = self.app.client_manager.compute
- utils.find_resource(
- compute_client.servers,
- parsed_args.server,
- ).pause()
+ for server in parsed_args.server:
+ utils.find_resource(
+ compute_client.servers,
+ server
+ ).pause()
class RebootServer(command.Command):
@@ -1217,7 +1294,7 @@ class ResizeServer(command.Command):
class ResumeServer(command.Command):
- """Resume server"""
+ """Resume server(s)"""
log = logging.getLogger(__name__ + '.ResumeServer')
@@ -1226,7 +1303,8 @@ class ResumeServer(command.Command):
parser.add_argument(
'server',
metavar='<server>',
- help=_('Server (name or ID)'),
+ nargs='+',
+ help=_('Server(s) to resume (name or ID)'),
)
return parser
@@ -1234,10 +1312,11 @@ class ResumeServer(command.Command):
def take_action(self, parsed_args):
compute_client = self.app.client_manager.compute
- utils.find_resource(
- compute_client.servers,
- parsed_args.server,
- ) .resume()
+ for server in parsed_args.server:
+ utils.find_resource(
+ compute_client.servers,
+ server,
+ ).resume()
class SetServer(command.Command):
@@ -1299,6 +1378,31 @@ class SetServer(command.Command):
raise exceptions.CommandError(msg)
+class ShelveServer(command.Command):
+ """Shelve server(s)"""
+
+ log = logging.getLogger(__name__ + '.ShelveServer')
+
+ def get_parser(self, prog_name):
+ parser = super(ShelveServer, self).get_parser(prog_name)
+ parser.add_argument(
+ 'server',
+ metavar='<server>',
+ nargs='+',
+ help=_('Server(s) to shelve (name or ID)'),
+ )
+ return parser
+
+ @utils.log_method(log)
+ def take_action(self, parsed_args):
+ compute_client = self.app.client_manager.compute
+ for server in parsed_args.server:
+ utils.find_resource(
+ compute_client.servers,
+ server,
+ ).shelve()
+
+
class ShowServer(show.ShowOne):
"""Show server details"""
@@ -1483,8 +1587,58 @@ class SshServer(command.Command):
os.system(cmd % (login, ip_address))
+class StartServer(command.Command):
+ """Start server(s)."""
+
+ log = logging.getLogger(__name__ + '.StartServer')
+
+ def get_parser(self, prog_name):
+ parser = super(StartServer, self).get_parser(prog_name)
+ parser.add_argument(
+ 'server',
+ metavar='<server>',
+ nargs="+",
+ help=_('Server(s) to start (name or ID)'),
+ )
+ return parser
+
+ @utils.log_method(log)
+ def take_action(self, parsed_args):
+ compute_client = self.app.client_manager.compute
+ for server in parsed_args.server:
+ utils.find_resource(
+ compute_client.servers,
+ server,
+ ).start()
+
+
+class StopServer(command.Command):
+ """Stop server(s)."""
+
+ log = logging.getLogger(__name__ + '.StopServer')
+
+ def get_parser(self, prog_name):
+ parser = super(StopServer, self).get_parser(prog_name)
+ parser.add_argument(
+ 'server',
+ metavar='<server>',
+ nargs="+",
+ help=_('Server(s) to stop (name or ID)'),
+ )
+ return parser
+
+ @utils.log_method(log)
+ def take_action(self, parsed_args):
+ compute_client = self.app.client_manager.compute
+ for server in parsed_args.server:
+ utils.find_resource(
+ compute_client.servers,
+ server,
+ ).stop()
+
+
class SuspendServer(command.Command):
- """Suspend server"""
+ """Suspend server(s)"""
log = logging.getLogger(__name__ + '.SuspendServer')
@@ -1493,7 +1647,8 @@ class SuspendServer(command.Command):
parser.add_argument(
'server',
metavar='<server>',
- help=_('Server (name or ID)'),
+ nargs='+',
+ help=_('Server(s) to suspend (name or ID)'),
)
return parser
@@ -1501,14 +1656,15 @@ class SuspendServer(command.Command):
def take_action(self, parsed_args):
compute_client = self.app.client_manager.compute
- utils.find_resource(
- compute_client.servers,
- parsed_args.server,
- ).suspend()
+ for server in parsed_args.server:
+ utils.find_resource(
+ compute_client.servers,
+ server,
+ ).suspend()
class UnlockServer(command.Command):
- """Unlock server"""
+ """Unlock server(s)"""
log = logging.getLogger(__name__ + '.UnlockServer')
@@ -1517,7 +1673,8 @@ class UnlockServer(command.Command):
parser.add_argument(
'server',
metavar='<server>',
- help=_('Server (name or ID)'),
+ nargs='+',
+ help=_('Server(s) to unlock (name or ID)'),
)
return parser
@@ -1525,14 +1682,15 @@ class UnlockServer(command.Command):
def take_action(self, parsed_args):
compute_client = self.app.client_manager.compute
- utils.find_resource(
- compute_client.servers,
- parsed_args.server,
- ).unlock()
+ for server in parsed_args.server:
+ utils.find_resource(
+ compute_client.servers,
+ server,
+ ).unlock()
class UnpauseServer(command.Command):
- """Unpause server"""
+ """Unpause server(s)"""
log = logging.getLogger(__name__ + '.UnpauseServer')
@@ -1541,7 +1699,8 @@ class UnpauseServer(command.Command):
parser.add_argument(
'server',
metavar='<server>',
- help=_('Server (name or ID)'),
+ nargs='+',
+ help=_('Server(s) to unpause (name or ID)'),
)
return parser
@@ -1549,10 +1708,11 @@ class UnpauseServer(command.Command):
def take_action(self, parsed_args):
compute_client = self.app.client_manager.compute
- utils.find_resource(
- compute_client.servers,
- parsed_args.server,
- ).unpause()
+ for server in parsed_args.server:
+ utils.find_resource(
+ compute_client.servers,
+ server,
+ ).unpause()
class UnrescueServer(command.Command):
@@ -1614,3 +1774,28 @@ class UnsetServer(command.Command):
server,
parsed_args.property,
)
+
+
+class UnshelveServer(command.Command):
+ """Unshelve server(s)"""
+
+ log = logging.getLogger(__name__ + '.UnshelveServer')
+
+ def get_parser(self, prog_name):
+ parser = super(UnshelveServer, self).get_parser(prog_name)
+ parser.add_argument(
+ 'server',
+ metavar='<server>',
+ nargs='+',
+ help=_('Server(s) to unshelve (name or ID)'),
+ )
+ return parser
+
+ @utils.log_method(log)
+ def take_action(self, parsed_args):
+ compute_client = self.app.client_manager.compute
+ for server in parsed_args.server:
+ utils.find_resource(
+ compute_client.servers,
+ server,
+ ).unshelve()
diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py
index 4b2ebac..c2d51c2 100644
--- a/openstackclient/compute/v2/service.py
+++ b/openstackclient/compute/v2/service.py
@@ -17,11 +17,33 @@
import logging
+from cliff import command
from cliff import lister
from openstackclient.common import utils
+class DeleteService(command.Command):
+ """Delete service command"""
+
+ log = logging.getLogger(__name__ + ".DeleteService")
+
+ def get_parser(self, prog_name):
+ parser = super(DeleteService, self).get_parser(prog_name)
+ parser.add_argument(
+ "service",
+ metavar="<service>",
+ help="Compute service to delete (ID only)")
+ return parser
+
+ def take_action(self, parsed_args):
+ self.log.debug("take_action(%s)", parsed_args)
+ compute_client = self.app.client_manager.compute
+
+ compute_client.services.delete(parsed_args.service)
+ return
+
+
class ListService(lister.Lister):
"""List service command"""
@@ -43,6 +65,7 @@ class ListService(lister.Lister):
self.log.debug("take_action(%s)", parsed_args)
compute_client = self.app.client_manager.compute
columns = (
+ "Id",
"Binary",
"Host",
"Zone",
diff --git a/openstackclient/compute/v2/usage.py b/openstackclient/compute/v2/usage.py
index 31b90c2..4e7cf10 100644
--- a/openstackclient/compute/v2/usage.py
+++ b/openstackclient/compute/v2/usage.py
@@ -183,25 +183,16 @@ class ShowUsage(show.ShowOne):
))
info = {}
- info['Servers'] = len(usage.server_usages)
- info['RAM MB-Hours'] = float("%.2f" % usage.total_memory_mb_usage)
- info['CPU Hours'] = float("%.2f" % usage.total_vcpus_usage)
- info['Disk GB-Hours'] = float("%.2f" % usage.total_local_gb_usage)
+ info['Servers'] = (
+ len(usage.server_usages)
+ if hasattr(usage, "server_usages") else None)
+ info['RAM MB-Hours'] = (
+ float("%.2f" % usage.total_memory_mb_usage)
+ if hasattr(usage, "total_memory_mb_usage") else None)
+ info['CPU Hours'] = (
+ float("%.2f" % usage.total_vcpus_usage)
+ if hasattr(usage, "total_vcpus_usage") else None)
+ info['Disk GB-Hours'] = (
+ float("%.2f" % usage.total_local_gb_usage)
+ if hasattr(usage, "total_local_gb_usage") else None)
return zip(*sorted(six.iteritems(info)))
-
-
-# This is out of order due to the subclass, will eventually be removed
-
-class ListProjectUsage(ListUsage):
- """List resource usage per project"""
-
- deprecated = True
-
- log = logging.getLogger('DEPRECATED:')
-
- def take_action(self, parsed_args):
- self.log.warning(
- "%s is deprecated, use 'usage list'",
- getattr(self, 'cmd_name', 'this command'),
- )
- return super(ListProjectUsage, self).take_action(parsed_args)
diff --git a/openstackclient/identity/client.py b/openstackclient/identity/client.py
index b8bb33f..bd882ce 100644
--- a/openstackclient/identity/client.py
+++ b/openstackclient/identity/client.py
@@ -21,7 +21,7 @@ from openstackclient.common import utils
LOG = logging.getLogger(__name__)
-DEFAULT_API_VERSION = '2'
+DEFAULT_API_VERSION = '3'
API_VERSION_OPTION = 'os_identity_api_version'
API_NAME = 'identity'
API_VERSIONS = {
diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py
index 97a95f2..065f0ad 100644
--- a/openstackclient/identity/v2_0/project.py
+++ b/openstackclient/identity/v2_0/project.py
@@ -21,7 +21,7 @@ import six
from cliff import command
from cliff import lister
from cliff import show
-from keystoneclient import exceptions as ksc_exc
+from keystoneauth1 import exceptions as ks_exc
from openstackclient.common import parseractions
from openstackclient.common import utils
@@ -88,7 +88,7 @@ class CreateProject(show.ShowOne):
enabled=enabled,
**kwargs
)
- except ksc_exc.Conflict as e:
+ except ks_exc.Conflict as e:
if parsed_args.or_show:
project = utils.find_resource(
identity_client.tenants,
@@ -264,7 +264,7 @@ class ShowProject(show.ShowOne):
parsed_args.project,
)
info.update(project._info)
- except ksc_exc.Forbidden as e:
+ except ks_exc.Forbidden as e:
auth_ref = self.app.client_manager.auth_ref
if (
parsed_args.project == auth_ref.project_id or
diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py
index e98f8cb..cab6b4a 100644
--- a/openstackclient/identity/v2_0/role.py
+++ b/openstackclient/identity/v2_0/role.py
@@ -21,7 +21,7 @@ import six
from cliff import command
from cliff import lister
from cliff import show
-from keystoneclient import exceptions as ksc_exc
+from keystoneauth1 import exceptions as ks_exc
from openstackclient.common import exceptions
from openstackclient.common import utils
@@ -98,7 +98,7 @@ class CreateRole(show.ShowOne):
identity_client = self.app.client_manager.identity
try:
role = identity_client.roles.create(parsed_args.role_name)
- except ksc_exc.Conflict as e:
+ except ks_exc.Conflict as e:
if parsed_args.or_show:
role = utils.find_resource(
identity_client.roles,
diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py
index 76902e6..e2b285b 100644
--- a/openstackclient/identity/v2_0/user.py
+++ b/openstackclient/identity/v2_0/user.py
@@ -21,7 +21,7 @@ import six
from cliff import command
from cliff import lister
from cliff import show
-from keystoneclient import exceptions as ksc_exc
+from keystoneauth1 import exceptions as ks_exc
from openstackclient.common import utils
from openstackclient.i18n import _ # noqa
@@ -104,7 +104,7 @@ class CreateUser(show.ShowOne):
tenant_id=project_id,
enabled=enabled,
)
- except ksc_exc.Conflict as e:
+ except ks_exc.Conflict as e:
if parsed_args.or_show:
user = utils.find_resource(
identity_client.users,
@@ -373,7 +373,7 @@ class ShowUser(show.ShowOne):
parsed_args.user,
)
info.update(user._info)
- except ksc_exc.Forbidden as e:
+ except ks_exc.Forbidden as e:
auth_ref = self.app.client_manager.auth_ref
if (
parsed_args.user == auth_ref.user_id or
diff --git a/openstackclient/identity/v3/credential.py b/openstackclient/identity/v3/credential.py
index dbd73e2..f22092d 100644
--- a/openstackclient/identity/v3/credential.py
+++ b/openstackclient/identity/v3/credential.py
@@ -17,7 +17,6 @@
import logging
import six
-import sys
from cliff import command
from cliff import lister
@@ -130,17 +129,20 @@ class SetCredential(command.Command):
parser.add_argument(
'--user',
metavar='<user>',
+ required=True,
help='Name or ID of user that owns the credential',
)
parser.add_argument(
'--type',
metavar='<type>',
choices=['ec2', 'cert'],
+ required=True,
help='New credential type',
)
parser.add_argument(
'--data',
metavar='<data>',
+ required=True,
help='New credential data',
)
parser.add_argument(
@@ -153,25 +155,22 @@ class SetCredential(command.Command):
@utils.log_method(log)
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
- kwargs = {}
- if parsed_args.user:
- user_id = utils.find_resource(identity_client.users,
- parsed_args.user).id
- if user_id:
- kwargs['user'] = user_id
- if parsed_args.type:
- kwargs['type'] = parsed_args.type
- if parsed_args.data:
- kwargs['data'] = parsed_args.data
+
+ user_id = utils.find_resource(identity_client.users,
+ parsed_args.user).id
+
if parsed_args.project:
project = utils.find_resource(identity_client.projects,
parsed_args.project).id
- kwargs['project'] = project
+ else:
+ project = None
+
+ identity_client.credentials.update(parsed_args.credential,
+ user=user_id,
+ type=parsed_args.type,
+ blob=parsed_args.data,
+ project=project)
- if not kwargs:
- sys.stdout.write("Credential not updated, no arguments present")
- return
- identity_client.credentials.update(parsed_args.credential, **kwargs)
return
diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py
index aec530a..8278a30 100644
--- a/openstackclient/identity/v3/domain.py
+++ b/openstackclient/identity/v3/domain.py
@@ -22,7 +22,7 @@ import sys
from cliff import command
from cliff import lister
from cliff import show
-from keystoneclient import exceptions as ksc_exc
+from keystoneauth1 import exceptions as ks_exc
from openstackclient.common import utils
from openstackclient.i18n import _ # noqa
@@ -77,7 +77,7 @@ class CreateDomain(show.ShowOne):
description=parsed_args.description,
enabled=enabled,
)
- except ksc_exc.Conflict as e:
+ except ks_exc.Conflict as e:
if parsed_args.or_show:
domain = utils.find_resource(identity_client.domains,
parsed_args.name)
diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py
index 8297506..d503a6b 100644
--- a/openstackclient/identity/v3/group.py
+++ b/openstackclient/identity/v3/group.py
@@ -22,7 +22,7 @@ import sys
from cliff import command
from cliff import lister
from cliff import show
-from keystoneclient import exceptions as ksc_exc
+from keystoneauth1 import exceptions as ks_exc
from openstackclient.common import utils
from openstackclient.i18n import _ # noqa
@@ -156,7 +156,7 @@ class CreateGroup(show.ShowOne):
name=parsed_args.name,
domain=domain,
description=parsed_args.description)
- except ksc_exc.Conflict as e:
+ except ks_exc.Conflict as e:
if parsed_args.or_show:
group = utils.find_resource(identity_client.groups,
parsed_args.name,
diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py
index 27982a9..4e086b6 100644
--- a/openstackclient/identity/v3/identity_provider.py
+++ b/openstackclient/identity/v3/identity_provider.py
@@ -88,6 +88,8 @@ class CreateIdentityProvider(show.ShowOne):
enabled=parsed_args.enabled)
idp._info.pop('links', None)
+ remote_ids = utils.format_list(idp._info.pop('remote_ids', []))
+ idp._info['remote_ids'] = remote_ids
return zip(*sorted(six.iteritems(idp._info)))
@@ -142,6 +144,11 @@ class SetIdentityProvider(command.Command):
metavar='<identity-provider>',
help='Identity provider to modify',
)
+ parser.add_argument(
+ '--description',
+ metavar='<description>',
+ help='Set identity provider description',
+ )
identity_remote_id_provider = parser.add_mutually_exclusive_group()
identity_remote_id_provider.add_argument(
'--remote-id',
@@ -174,8 +181,10 @@ class SetIdentityProvider(command.Command):
federation_client = self.app.client_manager.identity.federation
# Basic argument checking
- if (not parsed_args.enable and not parsed_args.disable and not
- parsed_args.remote_id and not parsed_args.remote_id_file):
+ if (not parsed_args.enable and not parsed_args.disable and
+ not parsed_args.remote_id and
+ not parsed_args.remote_id_file and
+ not parsed_args.description):
self.log.error('No changes requested')
return (None, None)
@@ -190,6 +199,8 @@ class SetIdentityProvider(command.Command):
# Setup keyword args for the client
kwargs = {}
+ if parsed_args.description:
+ kwargs['description'] = parsed_args.description
if parsed_args.enable:
kwargs['enabled'] = True
if parsed_args.disable:
@@ -221,9 +232,11 @@ class ShowIdentityProvider(show.ShowOne):
@utils.log_method(log)
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
- identity_provider = utils.find_resource(
+ idp = utils.find_resource(
identity_client.federation.identity_providers,
parsed_args.identity_provider)
- identity_provider._info.pop('links', None)
- return zip(*sorted(six.iteritems(identity_provider._info)))
+ idp._info.pop('links', None)
+ remote_ids = utils.format_list(idp._info.pop('remote_ids', []))
+ idp._info['remote_ids'] = remote_ids
+ return zip(*sorted(six.iteritems(idp._info)))
diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py
index 96d7f97..f87105d 100644
--- a/openstackclient/identity/v3/project.py
+++ b/openstackclient/identity/v3/project.py
@@ -21,7 +21,7 @@ import six
from cliff import command
from cliff import lister
from cliff import show
-from keystoneclient import exceptions as ksc_exc
+from keystoneauth1 import exceptions as ks_exc
from openstackclient.common import parseractions
from openstackclient.common import utils
@@ -113,7 +113,7 @@ class CreateProject(show.ShowOne):
enabled=enabled,
**kwargs
)
- except ksc_exc.Conflict as e:
+ except ks_exc.Conflict as e:
if parsed_args.or_show:
project = utils.find_resource(identity_client.projects,
parsed_args.name,
diff --git a/openstackclient/identity/v3/region.py b/openstackclient/identity/v3/region.py
index eb4c084..1ff0b8c 100644
--- a/openstackclient/identity/v3/region.py
+++ b/openstackclient/identity/v3/region.py
@@ -48,12 +48,6 @@ class CreateRegion(show.ShowOne):
metavar='<description>',
help=_('New region description'),
)
- parser.add_argument(
- '--url',
- metavar='<url>',
- help=_('New region url'),
- )
-
return parser
@utils.log_method(log)
@@ -62,7 +56,6 @@ class CreateRegion(show.ShowOne):
region = identity_client.regions.create(
id=parsed_args.region,
- url=parsed_args.url,
parent_region=parsed_args.parent_region,
description=parsed_args.description,
)
@@ -117,8 +110,8 @@ class ListRegion(lister.Lister):
if parsed_args.parent_region:
kwargs['parent_region_id'] = parsed_args.parent_region
- columns_headers = ('Region', 'Parent Region', 'Description', 'URL')
- columns = ('ID', 'Parent Region Id', 'Description', 'URL')
+ columns_headers = ('Region', 'Parent Region', 'Description')
+ columns = ('ID', 'Parent Region Id', 'Description')
data = identity_client.regions.list(**kwargs)
return (columns_headers,
@@ -150,25 +143,16 @@ class SetRegion(command.Command):
metavar='<description>',
help=_('New region description'),
)
- parser.add_argument(
- '--url',
- metavar='<url>',
- help=_('New region url'),
- )
return parser
@utils.log_method(log)
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
- if (not parsed_args.url
- and not parsed_args.parent_region
- and not parsed_args.description):
+ if not parsed_args.parent_region and not parsed_args.description:
return
kwargs = {}
- if parsed_args.url:
- kwargs['url'] = parsed_args.url
if parsed_args.description:
kwargs['description'] = parsed_args.description
if parsed_args.parent_region:
diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py
index c72de47..66f1896 100644
--- a/openstackclient/identity/v3/role.py
+++ b/openstackclient/identity/v3/role.py
@@ -22,7 +22,7 @@ import sys
from cliff import command
from cliff import lister
from cliff import show
-from keystoneclient import exceptions as ksc_exc
+from keystoneauth1 import exceptions as ks_exc
from openstackclient.common import utils
from openstackclient.i18n import _ # noqa
@@ -172,7 +172,7 @@ class CreateRole(show.ShowOne):
try:
role = identity_client.roles.create(name=parsed_args.name)
- except ksc_exc.Conflict as e:
+ except ks_exc.Conflict as e:
if parsed_args.or_show:
role = utils.find_resource(identity_client.roles,
parsed_args.name)
@@ -260,6 +260,7 @@ class ListRole(lister.Lister):
data = identity_client.roles.list(
user=user,
domain=domain,
+ os_inherit_extension_inherited=parsed_args.inherited
)
for user_role in data:
user_role.user = user.name
@@ -269,6 +270,7 @@ class ListRole(lister.Lister):
data = identity_client.roles.list(
user=user,
project=project,
+ os_inherit_extension_inherited=parsed_args.inherited
)
for user_role in data:
user_role.user = user.name
@@ -278,12 +280,14 @@ class ListRole(lister.Lister):
data = identity_client.roles.list(
user=user,
domain='default',
+ os_inherit_extension_inherited=parsed_args.inherited
)
elif parsed_args.group and parsed_args.domain:
columns = ('ID', 'Name', 'Domain', 'Group')
data = identity_client.roles.list(
group=group,
domain=domain,
+ os_inherit_extension_inherited=parsed_args.inherited
)
for group_role in data:
group_role.group = group.name
@@ -293,6 +297,7 @@ class ListRole(lister.Lister):
data = identity_client.roles.list(
group=group,
project=project,
+ os_inherit_extension_inherited=parsed_args.inherited
)
for group_role in data:
group_role.group = group.name
diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py
index 0e89454..eaef8f0 100644
--- a/openstackclient/identity/v3/user.py
+++ b/openstackclient/identity/v3/user.py
@@ -22,7 +22,7 @@ import six
from cliff import command
from cliff import lister
from cliff import show
-from keystoneclient import exceptions as ksc_exc
+from keystoneauth1 import exceptions as ks_exc
from openstackclient.common import utils
from openstackclient.i18n import _ # noqa
@@ -122,7 +122,7 @@ class CreateUser(show.ShowOne):
description=parsed_args.description,
enabled=enabled
)
- except ksc_exc.Conflict as e:
+ except ks_exc.Conflict as e:
if parsed_args.or_show:
user = utils.find_resource(identity_client.users,
parsed_args.name,
diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py
index 68c81cd..4ebc8f9 100644
--- a/openstackclient/image/v1/image.py
+++ b/openstackclient/image/v1/image.py
@@ -213,6 +213,8 @@ class CreateImage(show.ShowOne):
if parsed_args.private:
kwargs['is_public'] = False
+ info = {}
+
if not parsed_args.location and not parsed_args.copy_from:
if parsed_args.volume:
volume_client = self.app.client_manager.volume
@@ -241,18 +243,18 @@ class CreateImage(show.ShowOne):
# do a chunked transfer
kwargs["data"] = sys.stdin
- # Wrap the call to catch exceptions in order to close files
- try:
- image = image_client.images.create(**kwargs)
- finally:
- # Clean up open files - make sure data isn't a string
- if ('data' in kwargs and hasattr(kwargs['data'], 'close') and
- kwargs['data'] != sys.stdin):
- kwargs['data'].close()
-
- info = {}
- info.update(image._info)
- info['properties'] = utils.format_dict(info.get('properties', {}))
+ if not parsed_args.volume:
+ # Wrap the call to catch exceptions in order to close files
+ try:
+ image = image_client.images.create(**kwargs)
+ finally:
+ # Clean up open files - make sure data isn't a string
+ if ('data' in kwargs and hasattr(kwargs['data'], 'close') and
+ kwargs['data'] != sys.stdin):
+ kwargs['data'].close()
+
+ info.update(image._info)
+ info['properties'] = utils.format_dict(info.get('properties', {}))
return zip(*sorted(six.iteritems(info)))
@@ -352,7 +354,12 @@ class ListImage(lister.Lister):
kwargs['public'] = True
if parsed_args.private:
kwargs['private'] = True
- kwargs['detailed'] = bool(parsed_args.property or parsed_args.long)
+ # Note: We specifically need to do that below to get the 'status'
+ # column.
+ #
+ # Always set kwargs['detailed'] to True, and then filter the columns
+ # according to whether the --long option is specified or not.
+ kwargs['detailed'] = True
if parsed_args.long:
columns = (
@@ -380,7 +387,7 @@ class ListImage(lister.Lister):
'Properties',
)
else:
- columns = ("ID", "Name")
+ columns = ("ID", "Name", "Status")
column_headers = columns
# List of image data received
@@ -452,7 +459,7 @@ class SaveImage(command.Command):
gc_utils.save_image(data, parsed_args.file)
-class SetImage(show.ShowOne):
+class SetImage(command.Command):
"""Set image properties"""
log = logging.getLogger(__name__ + ".SetImage")
@@ -629,7 +636,7 @@ class SetImage(show.ShowOne):
volume_client.volumes,
parsed_args.volume,
)
- response, body = volume_client.volumes.upload_to_image(
+ volume_client.volumes.upload_to_image(
source_volume.id,
parsed_args.force,
parsed_args.image,
@@ -640,7 +647,6 @@ class SetImage(show.ShowOne):
if parsed_args.disk_format
else image.disk_format),
)
- info = body['os-volume_upload_image']
elif parsed_args.file:
# Send an open file handle to glanceclient so it will
# do a chunked transfer
@@ -673,10 +679,7 @@ class SetImage(show.ShowOne):
kwargs['data'] != sys.stdin):
kwargs['data'].close()
- info = {}
- info.update(image._info)
- info['properties'] = utils.format_dict(info.get('properties', {}))
- return zip(*sorted(six.iteritems(info)))
+ return
class ShowImage(show.ShowOne):
diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py
index fff26c0..c0fb5b5 100644
--- a/openstackclient/image/v2/image.py
+++ b/openstackclient/image/v2/image.py
@@ -57,8 +57,7 @@ def _format_image(image):
properties[key] = image.get(key)
# format the tags if they are there
- if image.get('tags'):
- info['tags'] = utils.format_list(image.get('tags'))
+ info['tags'] = utils.format_list(image.get('tags'))
# add properties back into the dictionary as a top-level key
if properties:
@@ -106,14 +105,14 @@ class AddProjectToImage(show.ShowOne):
project_id,
)
- return zip(*sorted(six.iteritems(image_member._info)))
+ return zip(*sorted(six.iteritems(image_member)))
class CreateImage(show.ShowOne):
"""Create/upload an image"""
log = logging.getLogger(__name__ + ".CreateImage")
- deadopts = ('owner', 'size', 'location', 'copy-from', 'checksum', 'store')
+ deadopts = ('size', 'location', 'copy-from', 'checksum', 'store')
def get_parser(self, prog_name):
parser = super(CreateImage, self).get_parser(prog_name)
@@ -121,7 +120,6 @@ class CreateImage(show.ShowOne):
# TODO(bunting): There are additional arguments that v1 supported
# that v2 either doesn't support or supports weirdly.
# --checksum - could be faked clientside perhaps?
- # --owner - could be set as an update after the put?
# --location - maybe location add?
# --size - passing image size is actually broken in python-glanceclient
# --copy-from - does not exist in v2
@@ -151,6 +149,11 @@ class CreateImage(show.ShowOne):
"(default: %s)" % DEFAULT_DISK_FORMAT,
)
parser.add_argument(
+ "--owner",
+ metavar="<owner>",
+ help="Image owner project name or ID",
+ )
+ parser.add_argument(
"--min-disk",
metavar="<disk-gb>",
type=int,
@@ -210,7 +213,7 @@ class CreateImage(show.ShowOne):
"--%s" % deadopt,
metavar="<%s>" % deadopt,
dest=deadopt.replace('-', '_'),
- help=argparse.SUPPRESS
+ help=argparse.SUPPRESS,
)
return parser
@@ -230,7 +233,7 @@ class CreateImage(show.ShowOne):
copy_attrs = ('name', 'id',
'container_format', 'disk_format',
'min_disk', 'min_ram',
- 'tags')
+ 'tags', 'owner')
for attr in copy_attrs:
if attr in parsed_args:
val = getattr(parsed_args, attr, None)
@@ -414,7 +417,7 @@ class ListImage(lister.Lister):
'Tags',
)
else:
- columns = ("ID", "Name")
+ columns = ("ID", "Name", "Status")
column_headers = columns
# List of image data received
@@ -522,38 +525,11 @@ class SaveImage(command.Command):
gc_utils.save_image(data, parsed_args.file)
-class ShowImage(show.ShowOne):
- """Display image details"""
-
- log = logging.getLogger(__name__ + ".ShowImage")
-
- def get_parser(self, prog_name):
- parser = super(ShowImage, self).get_parser(prog_name)
- parser.add_argument(
- "image",
- metavar="<image>",
- help="Image to display (name or ID)",
- )
- return parser
-
- def take_action(self, parsed_args):
- self.log.debug("take_action(%s)", parsed_args)
-
- image_client = self.app.client_manager.image
- image = utils.find_resource(
- image_client.images,
- parsed_args.image,
- )
-
- info = _format_image(image)
- return zip(*sorted(six.iteritems(info)))
-
-
-class SetImage(show.ShowOne):
+class SetImage(command.Command):
"""Set image properties"""
log = logging.getLogger(__name__ + ".SetImage")
- deadopts = ('size', 'store', 'location', 'copy-from', 'checksum')
+ deadopts = ('visibility',)
def get_parser(self, prog_name):
parser = super(SetImage, self).get_parser(prog_name)
@@ -567,8 +543,6 @@ class SetImage(show.ShowOne):
# --force - needs adding
# --checksum - maybe could be done client side
# --stdin - could be implemented
- # --property - needs adding
- # --tags - needs adding
parser.add_argument(
"image",
metavar="<image>",
@@ -580,20 +554,44 @@ class SetImage(show.ShowOne):
help="New image name"
)
parser.add_argument(
- "--architecture",
- metavar="<architecture>",
- help="Operating system Architecture"
+ "--owner",
+ metavar="<project>",
+ help="New image owner project (name or ID)",
+ )
+ parser.add_argument(
+ "--min-disk",
+ type=int,
+ metavar="<disk-gb>",
+ help="Minimum disk size needed to boot image, in gigabytes"
+ )
+ parser.add_argument(
+ "--min-ram",
+ type=int,
+ metavar="<ram-mb>",
+ help="Minimum RAM size needed to boot image, in megabytes",
+ )
+ parser.add_argument(
+ "--container-format",
+ metavar="<container-format>",
+ help="Image container format "
+ "(default: %s)" % DEFAULT_CONTAINER_FORMAT,
+ )
+ parser.add_argument(
+ "--disk-format",
+ metavar="<disk-format>",
+ help="Image disk format "
+ "(default: %s)" % DEFAULT_DISK_FORMAT,
)
protected_group = parser.add_mutually_exclusive_group()
protected_group.add_argument(
"--protected",
action="store_true",
- help="Prevent image from being deleted"
+ help="Prevent image from being deleted",
)
protected_group.add_argument(
"--unprotected",
action="store_true",
- help="Allow image to be deleted (default)"
+ help="Allow image to be deleted (default)",
)
public_group = parser.add_mutually_exclusive_group()
public_group.add_argument(
@@ -607,82 +605,64 @@ class SetImage(show.ShowOne):
help="Image is inaccessible to the public (default)",
)
parser.add_argument(
- "--instance-uuid",
- metavar="<instance_uuid>",
- help="ID of instance used to create this image"
+ "--property",
+ dest="properties",
+ metavar="<key=value>",
+ action=parseractions.KeyValueAction,
+ help="Set a property on this image "
+ "(repeat option to set multiple properties)",
)
parser.add_argument(
- "--min-disk",
- type=int,
- metavar="<disk-gb>",
- help="Minimum disk size needed to boot image, in gigabytes"
+ "--tag",
+ dest="tags",
+ metavar="<tag>",
+ default=[],
+ action='append',
+ help="Set a tag on this image "
+ "(repeat option to set multiple tags)",
)
- visibility_choices = ["public", "private"]
- public_group.add_argument(
- "--visibility",
- metavar="<visibility>",
- choices=visibility_choices,
- help=argparse.SUPPRESS
+ parser.add_argument(
+ "--architecture",
+ metavar="<architecture>",
+ help="Operating system architecture",
)
- help_msg = ("ID of image in Glance that should be used as the kernel"
- " when booting an AMI-style image")
parser.add_argument(
- "--kernel-id",
- metavar="<kernel-id>",
- help=help_msg
+ "--instance-id",
+ metavar="<instance-id>",
+ help="ID of server instance used to create this image",
)
parser.add_argument(
- "--os-version",
- metavar="<os-version>",
- help="Operating system version as specified by the distributor"
+ "--instance-uuid",
+ metavar="<instance-id>",
+ dest="instance_id",
+ help=argparse.SUPPRESS,
)
- disk_choices = ["None", "ami", "ari", "aki", "vhd", "vmdk", "raw",
- "qcow2", "vdi", "iso"]
- help_msg = ("Format of the disk. Valid values: %s" % disk_choices)
parser.add_argument(
- "--disk-format",
- metavar="<disk-format>",
- choices=disk_choices,
- help=help_msg
+ "--kernel-id",
+ metavar="<kernel-id>",
+ help="ID of kernel image used to boot this disk image",
)
parser.add_argument(
"--os-distro",
metavar="<os-distro>",
- help="Common name of operating system distribution"
+ help="Operating system distribution name",
)
parser.add_argument(
- "--owner",
- metavar="<owner>",
- help="New Owner of the image"
+ "--os-version",
+ metavar="<os-version>",
+ help="Operating system distribution version",
)
- msg = ("ID of image stored in Glance that should be used as the "
- "ramdisk when booting an AMI-style image")
parser.add_argument(
"--ramdisk-id",
metavar="<ramdisk-id>",
- help=msg
- )
- parser.add_argument(
- "--min-ram",
- type=int,
- metavar="<ram-mb>",
- help="Amount of RAM (in MB) required to boot image"
- )
- container_choices = ["None", "ami", "ari", "aki", "bare", "ovf", "ova"]
- help_msg = ("Format of the container. Valid values: %s"
- % container_choices)
- parser.add_argument(
- "--container-format",
- metavar="<container-format>",
- choices=container_choices,
- help=help_msg
+ help="ID of ramdisk image used to boot this disk image",
)
for deadopt in self.deadopts:
parser.add_argument(
"--%s" % deadopt,
metavar="<%s>" % deadopt,
dest=deadopt.replace('-', '_'),
- help=argparse.SUPPRESS
+ help=argparse.SUPPRESS,
)
return parser
@@ -698,10 +678,9 @@ class SetImage(show.ShowOne):
kwargs = {}
copy_attrs = ('architecture', 'container_format', 'disk_format',
- 'file', 'kernel_id', 'locations', 'name',
+ 'file', 'instance_id', 'kernel_id', 'locations',
'min_disk', 'min_ram', 'name', 'os_distro', 'os_version',
- 'owner', 'prefix', 'progress', 'ramdisk_id',
- 'visibility')
+ 'owner', 'prefix', 'progress', 'ramdisk_id', 'tags')
for attr in copy_attrs:
if attr in parsed_args:
val = getattr(parsed_args, attr, None)
@@ -710,6 +689,11 @@ class SetImage(show.ShowOne):
# actually present on the command line
kwargs[attr] = val
+ # Properties should get flattened into the general kwargs
+ if getattr(parsed_args, 'properties', None):
+ for k, v in six.iteritems(parsed_args.properties):
+ kwargs[k] = str(v)
+
# Handle exclusive booleans with care
# Avoid including attributes in kwargs if an option is not
# present on the command line. These exclusive booleans are not
@@ -732,7 +716,35 @@ class SetImage(show.ShowOne):
image = utils.find_resource(
image_client.images, parsed_args.image)
+ if parsed_args.tags:
+ # Tags should be extended, but duplicates removed
+ kwargs['tags'] = list(set(image.tags).union(set(parsed_args.tags)))
+
image = image_client.images.update(image.id, **kwargs)
- info = {}
- info.update(image)
+
+
+class ShowImage(show.ShowOne):
+ """Display image details"""
+
+ log = logging.getLogger(__name__ + ".ShowImage")
+
+ def get_parser(self, prog_name):
+ parser = super(ShowImage, self).get_parser(prog_name)
+ parser.add_argument(
+ "image",
+ metavar="<image>",
+ help="Image to display (name or ID)",
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ self.log.debug("take_action(%s)", parsed_args)
+
+ image_client = self.app.client_manager.image
+ image = utils.find_resource(
+ image_client.images,
+ parsed_args.image,
+ )
+
+ info = _format_image(image)
return zip(*sorted(six.iteritems(info)))
diff --git a/openstackclient/network/client.py b/openstackclient/network/client.py
index 5f72782..69ed11f 100644
--- a/openstackclient/network/client.py
+++ b/openstackclient/network/client.py
@@ -18,10 +18,11 @@ from openstackclient.common import utils
LOG = logging.getLogger(__name__)
-DEFAULT_API_VERSION = '2'
+DEFAULT_API_VERSION = '2.0'
API_VERSION_OPTION = 'os_network_api_version'
API_NAME = "network"
API_VERSIONS = {
+ "2.0": "neutronclient.v2_0.client.Client",
"2": "neutronclient.v2_0.client.Client",
}
# Translate our API version to auth plugin version prefix
@@ -32,6 +33,7 @@ API_VERSION_MAP = {
NETWORK_API_TYPE = 'network'
NETWORK_API_VERSIONS = {
+ '2.0': 'openstackclient.api.network_v2.APIv2',
'2': 'openstackclient.api.network_v2.APIv2',
}
diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py
index bd6203b..31faef2 100644
--- a/openstackclient/network/common.py
+++ b/openstackclient/network/common.py
@@ -27,7 +27,8 @@ def find(client, resource, resources, name_or_id, name_attr='name'):
n = find(netclient, 'network', 'networks', 'matrix')
"""
list_method = getattr(client, "list_%s" % resources)
- # Search for by name
+
+ # Search by name
kwargs = {name_attr: name_or_id, 'fields': 'id'}
data = list_method(**kwargs)
info = data[resources]
@@ -36,7 +37,8 @@ def find(client, resource, resources, name_or_id, name_attr='name'):
if len(info) > 1:
msg = "More than one %s exists with the name '%s'."
raise exceptions.CommandError(msg % (resource, name_or_id))
- # Search for by id
+
+ # Search by id
data = list_method(id=name_or_id, fields='id')
info = data[resources]
if len(info) == 1:
diff --git a/openstackclient/object/v1/account.py b/openstackclient/object/v1/account.py
new file mode 100644
index 0000000..aa94ff5
--- /dev/null
+++ b/openstackclient/object/v1/account.py
@@ -0,0 +1,85 @@
+# 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.
+#
+
+"""Account v1 action implementations"""
+
+import logging
+
+from cliff import command
+from cliff import show
+import six
+
+from openstackclient.common import parseractions
+from openstackclient.common import utils
+
+
+class SetAccount(command.Command):
+ """Set account properties"""
+
+ log = logging.getLogger(__name__ + '.SetAccount')
+
+ def get_parser(self, prog_name):
+ parser = super(SetAccount, self).get_parser(prog_name)
+ parser.add_argument(
+ "--property",
+ metavar="<key=value>",
+ required=True,
+ action=parseractions.KeyValueAction,
+ help="Set a property on this account "
+ "(repeat option to set multiple properties)"
+ )
+ return parser
+
+ @utils.log_method(log)
+ def take_action(self, parsed_args):
+ self.app.client_manager.object_store.account_set(
+ properties=parsed_args.property,
+ )
+
+
+class ShowAccount(show.ShowOne):
+ """Display account details"""
+
+ log = logging.getLogger(__name__ + '.ShowAccount')
+
+ @utils.log_method(log)
+ def take_action(self, parsed_args):
+ data = self.app.client_manager.object_store.account_show()
+ if 'properties' in data:
+ data['properties'] = utils.format_dict(data.pop('properties'))
+ return zip(*sorted(six.iteritems(data)))
+
+
+class UnsetAccount(command.Command):
+ """Unset account properties"""
+
+ log = logging.getLogger(__name__ + '.UnsetAccount')
+
+ def get_parser(self, prog_name):
+ parser = super(UnsetAccount, self).get_parser(prog_name)
+ parser.add_argument(
+ '--property',
+ metavar='<key>',
+ required=True,
+ action='append',
+ default=[],
+ help='Property to remove from account '
+ '(repeat option to remove multiple properties)',
+ )
+ return parser
+
+ @utils.log_method(log)
+ def take_action(self, parsed_args):
+ self.app.client_manager.object_store.account_unset(
+ properties=parsed_args.property,
+ )
diff --git a/openstackclient/object/v1/container.py b/openstackclient/object/v1/container.py
index 49173de..8c8844e 100644
--- a/openstackclient/object/v1/container.py
+++ b/openstackclient/object/v1/container.py
@@ -23,6 +23,7 @@ from cliff import command
from cliff import lister
from cliff import show
+from openstackclient.common import parseractions
from openstackclient.common import utils
@@ -178,6 +179,36 @@ class SaveContainer(command.Command):
)
+class SetContainer(command.Command):
+ """Set container properties"""
+
+ log = logging.getLogger(__name__ + '.SetContainer')
+
+ def get_parser(self, prog_name):
+ parser = super(SetContainer, self).get_parser(prog_name)
+ parser.add_argument(
+ 'container',
+ metavar='<container>',
+ help='Container to modify',
+ )
+ parser.add_argument(
+ "--property",
+ metavar="<key=value>",
+ required=True,
+ action=parseractions.KeyValueAction,
+ help="Set a property on this container "
+ "(repeat option to set multiple properties)"
+ )
+ return parser
+
+ @utils.log_method(log)
+ def take_action(self, parsed_args):
+ self.app.client_manager.object_store.container_set(
+ parsed_args.container,
+ properties=parsed_args.property,
+ )
+
+
class ShowContainer(show.ShowOne):
"""Display container details"""
@@ -198,5 +229,38 @@ class ShowContainer(show.ShowOne):
data = self.app.client_manager.object_store.container_show(
container=parsed_args.container,
)
+ if 'properties' in data:
+ data['properties'] = utils.format_dict(data.pop('properties'))
return zip(*sorted(six.iteritems(data)))
+
+
+class UnsetContainer(command.Command):
+ """Unset container properties"""
+
+ log = logging.getLogger(__name__ + '.UnsetContainer')
+
+ def get_parser(self, prog_name):
+ parser = super(UnsetContainer, self).get_parser(prog_name)
+ parser.add_argument(
+ 'container',
+ metavar='<container>',
+ help='Container to modify',
+ )
+ parser.add_argument(
+ '--property',
+ metavar='<key>',
+ required=True,
+ action='append',
+ default=[],
+ help='Property to remove from container '
+ '(repeat option to remove multiple properties)',
+ )
+ return parser
+
+ @utils.log_method(log)
+ def take_action(self, parsed_args):
+ self.app.client_manager.object_store.container_unset(
+ parsed_args.container,
+ properties=parsed_args.property,
+ )
diff --git a/openstackclient/object/v1/object.py b/openstackclient/object/v1/object.py
index c90f031..4bd0612 100644
--- a/openstackclient/object/v1/object.py
+++ b/openstackclient/object/v1/object.py
@@ -23,6 +23,7 @@ from cliff import command
from cliff import lister
from cliff import show
+from openstackclient.common import parseractions
from openstackclient.common import utils
@@ -221,6 +222,42 @@ class SaveObject(command.Command):
)
+class SetObject(command.Command):
+ """Set object properties"""
+
+ log = logging.getLogger(__name__ + '.SetObject')
+
+ def get_parser(self, prog_name):
+ parser = super(SetObject, self).get_parser(prog_name)
+ parser.add_argument(
+ 'container',
+ metavar='<container>',
+ help='Modify <object> from <container>',
+ )
+ parser.add_argument(
+ 'object',
+ metavar='<object>',
+ help='Object to modify',
+ )
+ parser.add_argument(
+ "--property",
+ metavar="<key=value>",
+ required=True,
+ action=parseractions.KeyValueAction,
+ help="Set a property on this object "
+ "(repeat option to set multiple properties)"
+ )
+ return parser
+
+ @utils.log_method(log)
+ def take_action(self, parsed_args):
+ self.app.client_manager.object_store.object_set(
+ parsed_args.container,
+ parsed_args.object,
+ properties=parsed_args.property,
+ )
+
+
class ShowObject(show.ShowOne):
"""Display object details"""
@@ -247,5 +284,44 @@ class ShowObject(show.ShowOne):
container=parsed_args.container,
object=parsed_args.object,
)
+ if 'properties' in data:
+ data['properties'] = utils.format_dict(data.pop('properties'))
return zip(*sorted(six.iteritems(data)))
+
+
+class UnsetObject(command.Command):
+ """Unset object properties"""
+
+ log = logging.getLogger(__name__ + '.UnsetObject')
+
+ def get_parser(self, prog_name):
+ parser = super(UnsetObject, self).get_parser(prog_name)
+ parser.add_argument(
+ 'container',
+ metavar='<container>',
+ help='Modify <object> from <container>',
+ )
+ parser.add_argument(
+ 'object',
+ metavar='<object>',
+ help='Object to modify',
+ )
+ parser.add_argument(
+ '--property',
+ metavar='<key>',
+ required=True,
+ action='append',
+ default=[],
+ help='Property to remove from object '
+ '(repeat option to remove multiple properties)',
+ )
+ return parser
+
+ @utils.log_method(log)
+ def take_action(self, parsed_args):
+ self.app.client_manager.object_store.object_unset(
+ parsed_args.container,
+ parsed_args.object,
+ properties=parsed_args.property,
+ )
diff --git a/openstackclient/shell.py b/openstackclient/shell.py
index 5b36b8b..5b4939a 100644
--- a/openstackclient/shell.py
+++ b/openstackclient/shell.py
@@ -25,12 +25,13 @@ from cliff import app
from cliff import command
from cliff import complete
from cliff import help
+from oslo_utils import strutils
import openstackclient
from openstackclient.common import clientmanager
from openstackclient.common import commandmanager
-from openstackclient.common import context
from openstackclient.common import exceptions as exc
+from openstackclient.common import logs
from openstackclient.common import timing
from openstackclient.common import utils
@@ -102,7 +103,7 @@ class OpenStackShell(app.App):
def configure_logging(self):
"""Configure logging for the app."""
- self.log_configurator = context.LogConfigurator(self.options)
+ self.log_configurator = logs.LogConfigurator(self.options)
self.dump_stack_trace = self.log_configurator.dump_trace
def run(self, argv):
@@ -201,8 +202,10 @@ class OpenStackShell(app.App):
# Parent __init__ parses argv into self.options
super(OpenStackShell, self).initialize_app(argv)
- self.log.info("START with options: %s", self.command_options)
- self.log.debug("options: %s", self.options)
+ self.log.info("START with options: %s",
+ strutils.mask_password(self.command_options))
+ self.log.debug("options: %s",
+ strutils.mask_password(self.options))
# Set the default plugin to token_endpoint if url and token are given
if (self.options.url and self.options.token):
@@ -238,6 +241,10 @@ class OpenStackShell(app.App):
},
)
+ # TODO(thowe): Change cliff so the default value for debug
+ # can be set to None.
+ if not self.options.debug:
+ self.options.debug = None
self.cloud = cc.get_one_cloud(
cloud=self.options.cloud,
argparse=self.options,
@@ -246,7 +253,8 @@ class OpenStackShell(app.App):
self.log_configurator.configure(self.cloud)
self.dump_stack_trace = self.log_configurator.dump_trace
self.log.debug("defaults: %s", cc.defaults)
- self.log.debug("cloud cfg: %s", self.cloud.config)
+ self.log.debug("cloud cfg: %s",
+ strutils.mask_password(self.cloud.config))
# Set up client TLS
# NOTE(dtroyer): --insecure is the non-default condition that
@@ -272,7 +280,7 @@ class OpenStackShell(app.App):
for mod in clientmanager.PLUGIN_MODULES:
default_version = getattr(mod, 'DEFAULT_API_VERSION', None)
option = mod.API_VERSION_OPTION.replace('os_', '')
- version_opt = self.cloud.config.get(option, default_version)
+ version_opt = str(self.cloud.config.get(option, default_version))
if version_opt:
api = mod.API_NAME
self.api_version[api] = version_opt
diff --git a/openstackclient/tests/api/fakes.py b/openstackclient/tests/api/fakes.py
index 85617ab..e285a61 100644
--- a/openstackclient/tests/api/fakes.py
+++ b/openstackclient/tests/api/fakes.py
@@ -15,7 +15,7 @@
from requests_mock.contrib import fixture
-from keystoneclient import session
+from keystoneauth1 import session
from openstackclient.tests import utils
diff --git a/openstackclient/tests/api/test_image_v1.py b/openstackclient/tests/api/test_image_v1.py
index 34fcfca..f347975 100644
--- a/openstackclient/tests/api/test_image_v1.py
+++ b/openstackclient/tests/api/test_image_v1.py
@@ -15,7 +15,7 @@
from requests_mock.contrib import fixture
-from keystoneclient import session
+from keystoneauth1 import session
from openstackclient.api import image_v1
from openstackclient.tests import utils
diff --git a/openstackclient/tests/api/test_image_v2.py b/openstackclient/tests/api/test_image_v2.py
index ddb160e..7706399 100644
--- a/openstackclient/tests/api/test_image_v2.py
+++ b/openstackclient/tests/api/test_image_v2.py
@@ -15,7 +15,7 @@
from requests_mock.contrib import fixture
-from keystoneclient import session
+from keystoneauth1 import session
from openstackclient.api import image_v2
from openstackclient.tests import utils
diff --git a/openstackclient/tests/api/test_network_v2.py b/openstackclient/tests/api/test_network_v2.py
index 13c5d6e..80f1d9d 100644
--- a/openstackclient/tests/api/test_network_v2.py
+++ b/openstackclient/tests/api/test_network_v2.py
@@ -15,7 +15,7 @@
from requests_mock.contrib import fixture
-from keystoneclient import session
+from keystoneauth1 import session
from openstackclient.api import network_v2 as network
from openstackclient.tests import utils
diff --git a/openstackclient/tests/api/test_object_store_v1.py b/openstackclient/tests/api/test_object_store_v1.py
index b18a003..992bf26 100644
--- a/openstackclient/tests/api/test_object_store_v1.py
+++ b/openstackclient/tests/api/test_object_store_v1.py
@@ -17,7 +17,7 @@ import mock
from requests_mock.contrib import fixture
-from keystoneclient import session
+from keystoneauth1 import session
from openstackclient.api import object_store_v1 as object_store
from openstackclient.tests import utils
@@ -157,10 +157,6 @@ class TestContainer(TestObjectAPIv1):
'container': 'qaz',
'object_count': '1',
'bytes_used': '577',
- 'read_acl': None,
- 'write_acl': None,
- 'sync_to': None,
- 'sync_key': None,
}
self.requests_mock.register_uri(
'HEAD',
@@ -323,8 +319,7 @@ class TestObject(TestObjectAPIv1):
'content-length': '577',
'last-modified': '20130101',
'etag': 'qaz',
- 'wife': 'Wilma',
- 'x-tra-header': 'yabba-dabba-do',
+ 'properties': {'wife': 'Wilma'},
}
self.requests_mock.register_uri(
'HEAD',
diff --git a/openstackclient/tests/common/test_context.py b/openstackclient/tests/common/test_logs.py
index 55e4285..fe054a3 100644
--- a/openstackclient/tests/common/test_context.py
+++ b/openstackclient/tests/common/test_logs.py
@@ -14,7 +14,7 @@
import logging
import mock
-from openstackclient.common import context
+from openstackclient.common import logs
from openstackclient.tests import utils
@@ -23,51 +23,51 @@ class TestContext(utils.TestCase):
def test_log_level_from_options(self):
opts = mock.Mock()
opts.verbose_level = 0
- self.assertEqual(logging.ERROR, context.log_level_from_options(opts))
+ self.assertEqual(logging.ERROR, logs.log_level_from_options(opts))
opts.verbose_level = 1
- self.assertEqual(logging.WARNING, context.log_level_from_options(opts))
+ self.assertEqual(logging.WARNING, logs.log_level_from_options(opts))
opts.verbose_level = 2
- self.assertEqual(logging.INFO, context.log_level_from_options(opts))
+ self.assertEqual(logging.INFO, logs.log_level_from_options(opts))
opts.verbose_level = 3
- self.assertEqual(logging.DEBUG, context.log_level_from_options(opts))
+ self.assertEqual(logging.DEBUG, logs.log_level_from_options(opts))
def test_log_level_from_config(self):
cfg = {'verbose_level': 0}
- self.assertEqual(logging.ERROR, context.log_level_from_config(cfg))
+ self.assertEqual(logging.ERROR, logs.log_level_from_config(cfg))
cfg = {'verbose_level': 1}
- self.assertEqual(logging.WARNING, context.log_level_from_config(cfg))
+ self.assertEqual(logging.WARNING, logs.log_level_from_config(cfg))
cfg = {'verbose_level': 2}
- self.assertEqual(logging.INFO, context.log_level_from_config(cfg))
+ self.assertEqual(logging.INFO, logs.log_level_from_config(cfg))
cfg = {'verbose_level': 3}
- self.assertEqual(logging.DEBUG, context.log_level_from_config(cfg))
+ self.assertEqual(logging.DEBUG, logs.log_level_from_config(cfg))
cfg = {'verbose_level': 1, 'log_level': 'critical'}
- self.assertEqual(logging.CRITICAL, context.log_level_from_config(cfg))
+ self.assertEqual(logging.CRITICAL, logs.log_level_from_config(cfg))
cfg = {'verbose_level': 1, 'log_level': 'error'}
- self.assertEqual(logging.ERROR, context.log_level_from_config(cfg))
+ self.assertEqual(logging.ERROR, logs.log_level_from_config(cfg))
cfg = {'verbose_level': 1, 'log_level': 'warning'}
- self.assertEqual(logging.WARNING, context.log_level_from_config(cfg))
+ self.assertEqual(logging.WARNING, logs.log_level_from_config(cfg))
cfg = {'verbose_level': 1, 'log_level': 'info'}
- self.assertEqual(logging.INFO, context.log_level_from_config(cfg))
+ self.assertEqual(logging.INFO, logs.log_level_from_config(cfg))
cfg = {'verbose_level': 1, 'log_level': 'debug'}
- self.assertEqual(logging.DEBUG, context.log_level_from_config(cfg))
+ self.assertEqual(logging.DEBUG, logs.log_level_from_config(cfg))
cfg = {'verbose_level': 1, 'log_level': 'bogus'}
- self.assertEqual(logging.WARNING, context.log_level_from_config(cfg))
+ self.assertEqual(logging.WARNING, logs.log_level_from_config(cfg))
cfg = {'verbose_level': 1, 'log_level': 'info', 'debug': True}
- self.assertEqual(logging.DEBUG, context.log_level_from_config(cfg))
+ self.assertEqual(logging.DEBUG, logs.log_level_from_config(cfg))
@mock.patch('warnings.simplefilter')
def test_set_warning_filter(self, simplefilter):
- context.set_warning_filter(logging.ERROR)
+ logs.set_warning_filter(logging.ERROR)
simplefilter.assert_called_with("ignore")
- context.set_warning_filter(logging.WARNING)
+ logs.set_warning_filter(logging.WARNING)
simplefilter.assert_called_with("ignore")
- context.set_warning_filter(logging.INFO)
+ logs.set_warning_filter(logging.INFO)
simplefilter.assert_called_with("once")
class TestFileFormatter(utils.TestCase):
def test_nothing(self):
- formatter = context._FileFormatter()
+ formatter = logs._FileFormatter()
self.assertEqual(('%(asctime)s.%(msecs)03d %(process)d %(levelname)s '
'%(name)s %(message)s'), formatter.fmt)
@@ -77,7 +77,7 @@ class TestFileFormatter(utils.TestCase):
os_project_name = 'projecty'
username = 'usernamey'
options = Opts()
- formatter = context._FileFormatter(options=options)
+ formatter = logs._FileFormatter(options=options)
self.assertEqual(('%(asctime)s.%(msecs)03d %(process)d %(levelname)s '
'%(name)s [cloudy usernamey projecty] %(message)s'),
formatter.fmt)
@@ -86,7 +86,7 @@ class TestFileFormatter(utils.TestCase):
config = mock.Mock()
config.config = {'cloud': 'cloudy'}
config.auth = {'project_name': 'projecty', 'username': 'usernamey'}
- formatter = context._FileFormatter(config=config)
+ formatter = logs._FileFormatter(config=config)
self.assertEqual(('%(asctime)s.%(msecs)03d %(process)d %(levelname)s '
'%(name)s [cloudy usernamey projecty] %(message)s'),
formatter.fmt)
@@ -119,7 +119,7 @@ class TestLogConfigurator(utils.TestCase):
@mock.patch('logging.StreamHandler')
@mock.patch('logging.getLogger')
- @mock.patch('openstackclient.common.context.set_warning_filter')
+ @mock.patch('openstackclient.common.logs.set_warning_filter')
def test_init(self, warning_filter, getLogger, handle):
getLogger.side_effect = self.loggers
console_logger = mock.Mock()
@@ -127,7 +127,7 @@ class TestLogConfigurator(utils.TestCase):
console_logger.setLevel = mock.Mock()
handle.return_value = console_logger
- configurator = context.LogConfigurator(self.options)
+ configurator = logs.LogConfigurator(self.options)
getLogger.assert_called_with('iso8601') # last call
warning_filter.assert_called_with(logging.WARNING)
@@ -140,12 +140,12 @@ class TestLogConfigurator(utils.TestCase):
self.assertEqual(False, configurator.dump_trace)
@mock.patch('logging.getLogger')
- @mock.patch('openstackclient.common.context.set_warning_filter')
+ @mock.patch('openstackclient.common.logs.set_warning_filter')
def test_init_no_debug(self, warning_filter, getLogger):
getLogger.side_effect = self.loggers
self.options.debug = True
- configurator = context.LogConfigurator(self.options)
+ configurator = logs.LogConfigurator(self.options)
warning_filter.assert_called_with(logging.DEBUG)
self.requests_log.setLevel.assert_called_with(logging.DEBUG)
@@ -153,8 +153,8 @@ class TestLogConfigurator(utils.TestCase):
@mock.patch('logging.FileHandler')
@mock.patch('logging.getLogger')
- @mock.patch('openstackclient.common.context.set_warning_filter')
- @mock.patch('openstackclient.common.context._FileFormatter')
+ @mock.patch('openstackclient.common.logs.set_warning_filter')
+ @mock.patch('openstackclient.common.logs._FileFormatter')
def test_init_log_file(self, formatter, warning_filter, getLogger, handle):
getLogger.side_effect = self.loggers
self.options.log_file = '/tmp/log_file'
@@ -165,7 +165,7 @@ class TestLogConfigurator(utils.TestCase):
mock_formatter = mock.Mock()
formatter.return_value = mock_formatter
- context.LogConfigurator(self.options)
+ logs.LogConfigurator(self.options)
handle.assert_called_with(filename=self.options.log_file)
self.root_logger.addHandler.assert_called_with(file_logger)
@@ -174,11 +174,11 @@ class TestLogConfigurator(utils.TestCase):
@mock.patch('logging.FileHandler')
@mock.patch('logging.getLogger')
- @mock.patch('openstackclient.common.context.set_warning_filter')
- @mock.patch('openstackclient.common.context._FileFormatter')
+ @mock.patch('openstackclient.common.logs.set_warning_filter')
+ @mock.patch('openstackclient.common.logs._FileFormatter')
def test_configure(self, formatter, warning_filter, getLogger, handle):
getLogger.side_effect = self.loggers
- configurator = context.LogConfigurator(self.options)
+ configurator = logs.LogConfigurator(self.options)
cloud_config = mock.Mock()
config_log = '/tmp/config_log'
cloud_config.config = {
diff --git a/openstackclient/tests/common/test_parseractions.py b/openstackclient/tests/common/test_parseractions.py
index 8afcb63..b75c481 100644
--- a/openstackclient/tests/common/test_parseractions.py
+++ b/openstackclient/tests/common/test_parseractions.py
@@ -102,3 +102,58 @@ class TestKeyValueAction(utils.TestCase):
expect = {'green': '100%'}
self.assertDictEqual(expect, actual)
self.assertEqual(None, failhere)
+
+
+class TestNonNegativeAction(utils.TestCase):
+ def test_negative_values(self):
+ parser = argparse.ArgumentParser()
+
+ # Set up our typical usage
+ parser.add_argument(
+ '--foo',
+ metavar='<foo>',
+ type=int,
+ action=parseractions.NonNegativeAction,
+ )
+
+ self.assertRaises(
+ argparse.ArgumentTypeError,
+ parser.parse_args,
+ "--foo -1".split()
+ )
+
+ def test_zero_values(self):
+ parser = argparse.ArgumentParser()
+
+ # Set up our typical usage
+ parser.add_argument(
+ '--foo',
+ metavar='<foo>',
+ type=int,
+ action=parseractions.NonNegativeAction,
+ )
+
+ results = parser.parse_args(
+ '--foo 0'.split()
+ )
+
+ actual = getattr(results, 'foo', None)
+ self.assertEqual(actual, 0)
+
+ def test_positive_values(self):
+ parser = argparse.ArgumentParser()
+
+ # Set up our typical usage
+ parser.add_argument(
+ '--foo',
+ metavar='<foo>',
+ type=int,
+ action=parseractions.NonNegativeAction,
+ )
+
+ results = parser.parse_args(
+ '--foo 1'.split()
+ )
+
+ actual = getattr(results, 'foo', None)
+ self.assertEqual(actual, 1)
diff --git a/openstackclient/tests/common/test_quota.py b/openstackclient/tests/common/test_quota.py
index b6ad156..485b8a8 100644
--- a/openstackclient/tests/common/test_quota.py
+++ b/openstackclient/tests/common/test_quota.py
@@ -17,6 +17,8 @@ import mock
from openstackclient.common import quota
from openstackclient.tests.compute.v2 import fakes as compute_fakes
from openstackclient.tests import fakes
+from openstackclient.tests.identity.v2_0 import fakes as identity_fakes
+from openstackclient.tests.network.v2 import fakes as network_fakes
class FakeQuotaResource(fakes.FakeResource):
@@ -40,11 +42,23 @@ class TestQuota(compute_fakes.TestComputev2):
super(TestQuota, self).setUp()
self.quotas_mock = self.app.client_manager.compute.quotas
self.quotas_mock.reset_mock()
+ self.quotas_class_mock = self.app.client_manager.compute.quota_classes
+ self.quotas_class_mock.reset_mock()
volume_mock = mock.Mock()
volume_mock.quotas = mock.Mock()
self.app.client_manager.volume = volume_mock
self.volume_quotas_mock = volume_mock.quotas
self.volume_quotas_mock.reset_mock()
+ self.volume_quotas_class_mock = \
+ self.app.client_manager.volume.quota_classes
+ self.volume_quotas_class_mock.reset_mock()
+ self.projects_mock = self.app.client_manager.identity.projects
+ self.projects_mock.reset_mock()
+ self.app.client_manager.auth_ref = mock.Mock()
+ self.app.client_manager.auth_ref.service_catalog = mock.Mock()
+ self.service_catalog_mock = \
+ self.app.client_manager.auth_ref.service_catalog
+ self.service_catalog_mock.reset_mock()
class TestQuotaSet(TestQuota):
@@ -76,6 +90,12 @@ class TestQuotaSet(TestQuota):
loaded=True,
)
+ self.projects_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.PROJECT),
+ loaded=True,
+ )
+
self.cmd = quota.SetQuota(self.app, None)
def test_quota_set(self):
@@ -83,15 +103,32 @@ class TestQuotaSet(TestQuota):
'--floating-ips', str(compute_fakes.floating_ip_num),
'--fixed-ips', str(compute_fakes.fix_ip_num),
'--injected-files', str(compute_fakes.injected_file_num),
+ '--injected-file-size', str(compute_fakes.injected_file_size_num),
+ '--injected-path-size', str(compute_fakes.injected_path_size_num),
'--key-pairs', str(compute_fakes.key_pair_num),
- compute_fakes.project_name,
+ '--cores', str(compute_fakes.core_num),
+ '--ram', str(compute_fakes.ram_num),
+ '--instances', str(compute_fakes.instance_num),
+ '--properties', str(compute_fakes.property_num),
+ '--secgroup-rules', str(compute_fakes.secgroup_rule_num),
+ '--secgroups', str(compute_fakes.secgroup_num),
+ identity_fakes.project_name,
]
verifylist = [
('floating_ips', compute_fakes.floating_ip_num),
('fixed_ips', compute_fakes.fix_ip_num),
('injected_files', compute_fakes.injected_file_num),
+ ('injected_file_content_bytes',
+ compute_fakes.injected_file_size_num),
+ ('injected_file_path_bytes', compute_fakes.injected_path_size_num),
('key_pairs', compute_fakes.key_pair_num),
- ('project', compute_fakes.project_name),
+ ('cores', compute_fakes.core_num),
+ ('ram', compute_fakes.ram_num),
+ ('instances', compute_fakes.instance_num),
+ ('metadata_items', compute_fakes.property_num),
+ ('security_group_rules', compute_fakes.secgroup_rule_num),
+ ('security_groups', compute_fakes.secgroup_num),
+ ('project', identity_fakes.project_name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -102,17 +139,29 @@ class TestQuotaSet(TestQuota):
'floating_ips': compute_fakes.floating_ip_num,
'fixed_ips': compute_fakes.fix_ip_num,
'injected_files': compute_fakes.injected_file_num,
+ 'injected_file_content_bytes':
+ compute_fakes.injected_file_size_num,
+ 'injected_file_path_bytes': compute_fakes.injected_path_size_num,
'key_pairs': compute_fakes.key_pair_num,
+ 'cores': compute_fakes.core_num,
+ 'ram': compute_fakes.ram_num,
+ 'instances': compute_fakes.instance_num,
+ 'metadata_items': compute_fakes.property_num,
+ 'security_group_rules': compute_fakes.secgroup_rule_num,
+ 'security_groups': compute_fakes.secgroup_num,
}
- self.quotas_mock.update.assert_called_with('project_test', **kwargs)
+ self.quotas_mock.update.assert_called_with(
+ identity_fakes.project_id,
+ **kwargs
+ )
def test_quota_set_volume(self):
arglist = [
'--gigabytes', str(compute_fakes.floating_ip_num),
'--snapshots', str(compute_fakes.fix_ip_num),
'--volumes', str(compute_fakes.injected_file_num),
- compute_fakes.project_name,
+ identity_fakes.project_name,
]
verifylist = [
('gigabytes', compute_fakes.floating_ip_num),
@@ -130,5 +179,117 @@ class TestQuotaSet(TestQuota):
'volumes': compute_fakes.injected_file_num,
}
- self.volume_quotas_mock.update.assert_called_with('project_test',
- **kwargs)
+ self.volume_quotas_mock.update.assert_called_with(
+ identity_fakes.project_id,
+ **kwargs
+ )
+
+
+class TestQuotaShow(TestQuota):
+
+ def setUp(self):
+ super(TestQuotaShow, self).setUp()
+
+ self.quotas_mock.get.return_value = FakeQuotaResource(
+ None,
+ copy.deepcopy(compute_fakes.QUOTA),
+ loaded=True,
+ )
+
+ self.quotas_mock.defaults.return_value = FakeQuotaResource(
+ None,
+ copy.deepcopy(compute_fakes.QUOTA),
+ loaded=True,
+ )
+
+ self.volume_quotas_mock.get.return_value = FakeQuotaResource(
+ None,
+ copy.deepcopy(compute_fakes.QUOTA),
+ loaded=True,
+ )
+
+ self.volume_quotas_mock.defaults.return_value = FakeQuotaResource(
+ None,
+ copy.deepcopy(compute_fakes.QUOTA),
+ loaded=True,
+ )
+
+ self.service_catalog_mock.get_endpoints.return_value = [
+ fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.ENDPOINT),
+ loaded=True,
+ )
+ ]
+
+ self.quotas_class_mock.get.return_value = FakeQuotaResource(
+ None,
+ copy.deepcopy(compute_fakes.QUOTA),
+ loaded=True,
+ )
+
+ self.volume_quotas_class_mock.get.return_value = FakeQuotaResource(
+ None,
+ copy.deepcopy(compute_fakes.QUOTA),
+ loaded=True,
+ )
+
+ self.projects_mock.get.return_value = fakes.FakeResource(
+ None,
+ copy.deepcopy(identity_fakes.PROJECT),
+ loaded=True,
+ )
+
+ self.app.client_manager.network = network_fakes.FakeNetworkV2Client(
+ endpoint=fakes.AUTH_URL,
+ token=fakes.AUTH_TOKEN,
+ )
+
+ self.cmd = quota.ShowQuota(self.app, None)
+
+ def test_quota_show(self):
+ arglist = [
+ identity_fakes.project_name,
+ ]
+ verifylist = [
+ ('project', identity_fakes.project_name),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.cmd.take_action(parsed_args)
+
+ self.quotas_mock.get.assert_called_with(identity_fakes.project_id)
+
+ def test_quota_show_with_default(self):
+ arglist = [
+ '--default',
+ identity_fakes.project_name,
+ ]
+ verifylist = [
+ ('default', True),
+ ('project', identity_fakes.project_name),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.cmd.take_action(parsed_args)
+
+ self.quotas_mock.defaults.assert_called_with(identity_fakes.project_id)
+
+ def test_quota_show_with_class(self):
+ arglist = [
+ '--class',
+ identity_fakes.project_name,
+ ]
+ verifylist = [
+ ('quota_class', True),
+ ('project', identity_fakes.project_name),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.cmd.take_action(parsed_args)
+
+ self.quotas_class_mock.get.assert_called_with(
+ identity_fakes.project_id)
diff --git a/openstackclient/tests/common/test_utils.py b/openstackclient/tests/common/test_utils.py
index a25a5ba..b564ffa 100644
--- a/openstackclient/tests/common/test_utils.py
+++ b/openstackclient/tests/common/test_utils.py
@@ -20,6 +20,7 @@ import mock
from openstackclient.common import exceptions
from openstackclient.common import utils
+from openstackclient.tests import fakes
from openstackclient.tests import utils as test_utils
PASSWORD = "Pa$$w0rd"
@@ -27,6 +28,18 @@ WASSPORD = "Wa$$p0rd"
DROWSSAP = "dr0w$$aP"
+class FakeOddballResource(fakes.FakeResource):
+
+ def get(self, attr):
+ """get() is needed for utils.find_resource()"""
+ if attr == 'id':
+ return self.id
+ elif attr == 'name':
+ return self.name
+ else:
+ return None
+
+
class TestUtils(test_utils.TestCase):
def test_get_password_good(self):
@@ -124,6 +137,46 @@ class TestUtils(test_utils.TestCase):
items, sort_str)
@mock.patch.object(time, 'sleep')
+ def test_wait_for_status_ok(self, mock_sleep):
+ # Tests the normal flow that the resource is status=active
+ resource = mock.MagicMock(status='ACTIVE')
+ status_f = mock.Mock(return_value=resource)
+ res_id = str(uuid.uuid4())
+ self.assertTrue(utils.wait_for_status(status_f, res_id,))
+ self.assertFalse(mock_sleep.called)
+
+ @mock.patch.object(time, 'sleep')
+ def test_wait_for_status_ok__with_overrides(self, mock_sleep):
+ # Tests the normal flow that the resource is status=complete
+ resource = mock.MagicMock(my_status='COMPLETE')
+ status_f = mock.Mock(return_value=resource)
+ res_id = str(uuid.uuid4())
+ self.assertTrue(utils.wait_for_status(status_f, res_id,
+ status_field='my_status',
+ success_status=['complete']))
+ self.assertFalse(mock_sleep.called)
+
+ @mock.patch.object(time, 'sleep')
+ def test_wait_for_status_error(self, mock_sleep):
+ # Tests that we fail if the resource is status=error
+ resource = mock.MagicMock(status='ERROR')
+ status_f = mock.Mock(return_value=resource)
+ res_id = str(uuid.uuid4())
+ self.assertFalse(utils.wait_for_status(status_f, res_id))
+ self.assertFalse(mock_sleep.called)
+
+ @mock.patch.object(time, 'sleep')
+ def test_wait_for_status_error_with_overrides(self, mock_sleep):
+ # Tests that we fail if the resource is my_status=failed
+ resource = mock.MagicMock(my_status='FAILED')
+ status_f = mock.Mock(return_value=resource)
+ res_id = str(uuid.uuid4())
+ self.assertFalse(utils.wait_for_status(status_f, res_id,
+ status_field='my_status',
+ error_status=['failed']))
+ self.assertFalse(mock_sleep.called)
+
+ @mock.patch.object(time, 'sleep')
def test_wait_for_delete_ok(self, mock_sleep):
# Tests the normal flow that the resource is deleted with a 404 coming
# back on the 2nd iteration of the wait loop.
@@ -242,6 +295,47 @@ class TestFindResource(test_utils.TestCase):
self.manager.get.assert_called_with(self.name)
self.manager.find.assert_called_with(name=self.name)
+ def test_find_resource_silly_resource(self):
+ # We need a resource with no resource_class for this test, start fresh
+ self.manager = mock.Mock()
+ self.manager.get = mock.Mock(side_effect=Exception('Boom!'))
+ self.manager.find = mock.Mock(
+ side_effect=AttributeError(
+ "'Controller' object has no attribute 'find'",
+ )
+ )
+ silly_resource = FakeOddballResource(
+ None,
+ {'id': '12345', 'name': self.name},
+ loaded=True,
+ )
+ self.manager.list = mock.Mock(
+ return_value=[silly_resource, ],
+ )
+ result = utils.find_resource(self.manager, self.name)
+ self.assertEqual(silly_resource, result)
+ self.manager.get.assert_called_with(self.name)
+ self.manager.find.assert_called_with(name=self.name)
+
+ def test_find_resource_silly_resource_not_found(self):
+ # We need a resource with no resource_class for this test, start fresh
+ self.manager = mock.Mock()
+ self.manager.get = mock.Mock(side_effect=Exception('Boom!'))
+ self.manager.find = mock.Mock(
+ side_effect=AttributeError(
+ "'Controller' object has no attribute 'find'",
+ )
+ )
+ self.manager.list = mock.Mock(return_value=[])
+ result = self.assertRaises(exceptions.CommandError,
+ utils.find_resource,
+ self.manager,
+ self.name)
+ self.assertEqual("Could not find resource legos",
+ str(result))
+ self.manager.get.assert_called_with(self.name)
+ self.manager.find.assert_called_with(name=self.name)
+
def test_format_dict(self):
expected = "a='b', c='d', e='f'"
self.assertEqual(expected,
diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py
index e798bd4..91cc2bd 100644
--- a/openstackclient/tests/compute/v2/fakes.py
+++ b/openstackclient/tests/compute/v2/fakes.py
@@ -13,24 +13,18 @@
# under the License.
#
+import copy
import mock
+import uuid
from openstackclient.tests import fakes
from openstackclient.tests.identity.v2_0 import fakes as identity_fakes
from openstackclient.tests.image.v2 import fakes as image_fakes
from openstackclient.tests.network.v2 import fakes as network_fakes
from openstackclient.tests import utils
+from openstackclient.tests.volume.v2 import fakes as volume_fakes
-server_id = 'serv1'
-server_name = 'waiter'
-
-SERVER = {
- 'id': server_id,
- 'name': server_name,
- 'metadata': {},
-}
-
extension_name = 'Multinic'
extension_namespace = 'http://docs.openstack.org/compute/ext/'\
'multinic/api/v1.1'
@@ -50,34 +44,49 @@ EXTENSION = {
'links': extension_links,
}
-flavor_id = 'm1.large'
-flavor_name = 'Large'
-flavor_ram = 8192
-flavor_vcpus = 4
-
-FLAVOR = {
- 'id': flavor_id,
- 'name': flavor_name,
- 'ram': flavor_ram,
- 'vcpus': flavor_vcpus,
-}
-
floating_ip_num = 100
fix_ip_num = 100
injected_file_num = 100
+injected_file_size_num = 10240
+injected_path_size_num = 255
key_pair_num = 100
+core_num = 20
+ram_num = 51200
+instance_num = 10
+property_num = 128
+secgroup_rule_num = 20
+secgroup_num = 10
project_name = 'project_test'
QUOTA = {
'project': project_name,
'floating-ips': floating_ip_num,
'fix-ips': fix_ip_num,
'injected-files': injected_file_num,
+ 'injected-file-size': injected_file_size_num,
+ 'injected-path-size': injected_path_size_num,
'key-pairs': key_pair_num,
+ 'cores': core_num,
+ 'ram': ram_num,
+ 'instances': instance_num,
+ 'properties': property_num,
+ 'secgroup_rules': secgroup_rule_num,
+ 'secgroups': secgroup_num,
}
QUOTA_columns = tuple(sorted(QUOTA))
QUOTA_data = tuple(QUOTA[x] for x in sorted(QUOTA))
+block_device_mapping = 'vda=' + volume_fakes.volume_name + ':::0'
+
+service_host = 'host_test'
+service_binary = 'compute_test'
+service_status = 'enabled'
+SERVICE = {
+ 'host': service_host,
+ 'binary': service_binary,
+ 'status': service_status,
+}
+
class FakeComputev2Client(object):
def __init__(self, **kwargs):
@@ -85,12 +94,18 @@ class FakeComputev2Client(object):
self.images.resource_class = fakes.FakeResource(None, {})
self.servers = mock.Mock()
self.servers.resource_class = fakes.FakeResource(None, {})
+ self.services = mock.Mock()
+ self.services.resource_class = fakes.FakeResource(None, {})
self.extensions = mock.Mock()
self.extensions.resource_class = fakes.FakeResource(None, {})
self.flavors = mock.Mock()
self.flavors.resource_class = fakes.FakeResource(None, {})
self.quotas = mock.Mock()
self.quotas.resource_class = fakes.FakeResource(None, {})
+ self.quota_classes = mock.Mock()
+ self.quota_classes.resource_class = fakes.FakeResource(None, {})
+ self.volumes = mock.Mock()
+ self.volumes.resource_class = fakes.FakeResource(None, {})
self.auth_token = kwargs['token']
self.management_url = kwargs['endpoint']
@@ -118,3 +133,161 @@ class TestComputev2(utils.TestCommand):
endpoint=fakes.AUTH_URL,
token=fakes.AUTH_TOKEN,
)
+
+ self.app.client_manager.volume = volume_fakes.FakeVolumeClient(
+ endpoint=fakes.AUTH_URL,
+ token=fakes.AUTH_TOKEN,
+ )
+
+
+class FakeServer(object):
+ """Fake one or more compute servers."""
+
+ @staticmethod
+ def create_one_server(attrs={}, methods={}):
+ """Create a fake server.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param Dictionary methods:
+ A dictionary with all methods
+ :return:
+ A FakeResource object, with id, name, metadata
+ """
+ # Set default attributes.
+ server_info = {
+ 'id': 'server-id-' + uuid.uuid4().hex,
+ 'name': 'server-name-' + uuid.uuid4().hex,
+ 'metadata': {},
+ }
+
+ # Overwrite default attributes.
+ server_info.update(attrs)
+
+ server = fakes.FakeResource(info=copy.deepcopy(server_info),
+ methods=methods,
+ loaded=True)
+ return server
+
+ @staticmethod
+ def create_servers(attrs={}, methods={}, count=2):
+ """Create multiple fake servers.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param Dictionary methods:
+ A dictionary with all methods
+ :param int count:
+ The number of servers to fake
+ :return:
+ A list of FakeResource objects faking the servers
+ """
+ servers = []
+ for i in range(0, count):
+ servers.append(FakeServer.create_one_server(attrs, methods))
+
+ return servers
+
+ @staticmethod
+ def get_servers(servers=None, count=2):
+ """Get an iterable MagicMock object with a list of faked servers.
+
+ If servers list is provided, then initialize the Mock object with the
+ list. Otherwise create one.
+
+ :param List servers:
+ A list of FakeResource objects faking servers
+ :param int count:
+ The number of servers to fake
+ :return:
+ An iterable Mock object with side_effect set to a list of faked
+ servers
+ """
+ if servers is None:
+ servers = FakeServer.create_servers(count)
+ return mock.MagicMock(side_effect=servers)
+
+
+class FakeFlavorResource(fakes.FakeResource):
+ """Fake flavor object's methods to help test.
+
+ The flavor object has three methods to get, set, unset its properties.
+ Need to fake them, otherwise the functions to be tested won't run properly.
+ """
+
+ # Fake properties.
+ _keys = {'property': 'value'}
+
+ def set_keys(self, args):
+ self._keys.update(args)
+
+ def unset_keys(self, keys):
+ for key in keys:
+ self._keys.pop(key, None)
+
+ def get_keys(self):
+ return self._keys
+
+
+class FakeFlavor(object):
+ """Fake one or more flavors."""
+
+ @staticmethod
+ def create_one_flavor(attrs={}):
+ """Create a fake flavor.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeFlavorResource object, with id, name, ram, vcpus, properties
+ """
+ # Set default attributes.
+ flavor_info = {
+ 'id': 'flavor-id-' + uuid.uuid4().hex,
+ 'name': 'flavor-name-' + uuid.uuid4().hex,
+ 'ram': 8192,
+ 'vcpus': 4,
+ }
+
+ # Overwrite default attributes.
+ flavor_info.update(attrs)
+
+ flavor = FakeFlavorResource(info=copy.deepcopy(flavor_info),
+ loaded=True)
+ return flavor
+
+ @staticmethod
+ def create_flavors(attrs={}, count=2):
+ """Create multiple fake flavors.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param int count:
+ The number of flavors to fake
+ :return:
+ A list of FakeFlavorResource objects faking the flavors
+ """
+ flavors = []
+ for i in range(0, count):
+ flavors.append(FakeFlavor.create_one_flavor(attrs))
+
+ return flavors
+
+ @staticmethod
+ def get_flavors(flavors=None, count=2):
+ """Get an iterable MagicMock object with a list of faked flavors.
+
+ If flavors list is provided, then initialize the Mock object with the
+ list. Otherwise create one.
+
+ :param List flavors:
+ A list of FakeFlavorResource objects faking flavors
+ :param int count:
+ The number of flavors to fake
+ :return:
+ An iterable Mock object with side_effect set to a list of faked
+ flavors
+ """
+ if flavors is None:
+ flavors = FakeServer.create_flavors(count)
+ return mock.MagicMock(side_effect=flavors)
diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py
index 19be812..60356ef 100644
--- a/openstackclient/tests/compute/v2/test_flavor.py
+++ b/openstackclient/tests/compute/v2/test_flavor.py
@@ -13,26 +13,9 @@
# under the License.
#
-import copy
-
+from openstackclient.common import exceptions
from openstackclient.compute.v2 import flavor
from openstackclient.tests.compute.v2 import fakes as compute_fakes
-from openstackclient.tests import fakes
-
-
-class FakeFlavorResource(fakes.FakeResource):
-
- _keys = {'property': 'value'}
-
- def set_keys(self, args):
- self._keys.update(args)
-
- def unset_keys(self, keys):
- for key in keys:
- self._keys.pop(key, None)
-
- def get_keys(self):
- return self._keys
class TestFlavor(compute_fakes.TestComputev2):
@@ -45,18 +28,90 @@ class TestFlavor(compute_fakes.TestComputev2):
self.flavors_mock.reset_mock()
+class TestFlavorDelete(TestFlavor):
+
+ flavor = compute_fakes.FakeFlavor.create_one_flavor()
+
+ def setUp(self):
+ super(TestFlavorDelete, self).setUp()
+
+ self.flavors_mock.get.return_value = self.flavor
+ self.flavors_mock.delete.return_value = None
+
+ self.cmd = flavor.DeleteFlavor(self.app, None)
+
+ def test_flavor_delete(self):
+ arglist = [
+ self.flavor.id
+ ]
+ verifylist = [
+ ('flavor', self.flavor.id),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.cmd.take_action(parsed_args)
+
+ self.flavors_mock.delete.assert_called_with(self.flavor.id)
+
+ def test_flavor_delete_with_unexist_flavor(self):
+ self.flavors_mock.get.side_effect = exceptions.NotFound(None)
+ self.flavors_mock.find.side_effect = exceptions.NotFound(None)
+
+ arglist = [
+ 'unexist_flavor'
+ ]
+ verifylist = [
+ ('flavor', 'unexist_flavor'),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+
+
class TestFlavorList(TestFlavor):
+ # Return value of self.flavors_mock.list().
+ flavors = compute_fakes.FakeFlavor.create_flavors(count=1)
+
+ columns = (
+ 'ID',
+ 'Name',
+ 'RAM',
+ 'Disk',
+ 'Ephemeral',
+ 'VCPUs',
+ 'Is Public',
+ )
+ columns_long = columns + (
+ 'Swap',
+ 'RXTX Factor',
+ 'Properties'
+ )
+
+ data = ((
+ flavors[0].id,
+ flavors[0].name,
+ flavors[0].ram,
+ '',
+ '',
+ flavors[0].vcpus,
+ ''
+ ), )
+ data_long = (data[0] + (
+ '',
+ '',
+ 'property=\'value\''
+ ), )
+
def setUp(self):
super(TestFlavorList, self).setUp()
- self.flavors_mock.list.return_value = [
- FakeFlavorResource(
- None,
- copy.deepcopy(compute_fakes.FLAVOR),
- loaded=True,
- ),
- ]
+ self.flavors_mock.list.return_value = self.flavors
# Get the command object to test
self.cmd = flavor.ListFlavor(self.app, None)
@@ -76,33 +131,17 @@ class TestFlavorList(TestFlavor):
# Set expected values
kwargs = {
- 'is_public': True
+ 'is_public': True,
+ 'limit': None,
+ 'marker': None
}
self.flavors_mock.list.assert_called_with(
**kwargs
)
- collist = (
- 'ID',
- 'Name',
- 'RAM',
- 'Disk',
- 'Ephemeral',
- 'VCPUs',
- 'Is Public',
- )
- self.assertEqual(collist, columns)
- datalist = ((
- compute_fakes.flavor_id,
- compute_fakes.flavor_name,
- compute_fakes.flavor_ram,
- '',
- '',
- compute_fakes.flavor_vcpus,
- ''
- ), )
- self.assertEqual(datalist, tuple(data))
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(tuple(self.data), tuple(data))
def test_flavor_list_all_flavors(self):
arglist = [
@@ -119,33 +158,17 @@ class TestFlavorList(TestFlavor):
# Set expected values
kwargs = {
- 'is_public': None
+ 'is_public': None,
+ 'limit': None,
+ 'marker': None
}
self.flavors_mock.list.assert_called_with(
**kwargs
)
- collist = (
- 'ID',
- 'Name',
- 'RAM',
- 'Disk',
- 'Ephemeral',
- 'VCPUs',
- 'Is Public',
- )
- self.assertEqual(collist, columns)
- datalist = ((
- compute_fakes.flavor_id,
- compute_fakes.flavor_name,
- compute_fakes.flavor_ram,
- '',
- '',
- compute_fakes.flavor_vcpus,
- ''
- ), )
- self.assertEqual(datalist, tuple(data))
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(tuple(self.data), tuple(data))
def test_flavor_list_private_flavors(self):
arglist = [
@@ -162,33 +185,17 @@ class TestFlavorList(TestFlavor):
# Set expected values
kwargs = {
- 'is_public': False
+ 'is_public': False,
+ 'limit': None,
+ 'marker': None
}
self.flavors_mock.list.assert_called_with(
**kwargs
)
- collist = (
- 'ID',
- 'Name',
- 'RAM',
- 'Disk',
- 'Ephemeral',
- 'VCPUs',
- 'Is Public',
- )
- self.assertEqual(collist, columns)
- datalist = ((
- compute_fakes.flavor_id,
- compute_fakes.flavor_name,
- compute_fakes.flavor_ram,
- '',
- '',
- compute_fakes.flavor_vcpus,
- ''
- ), )
- self.assertEqual(datalist, tuple(data))
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(tuple(self.data), tuple(data))
def test_flavor_list_public_flavors(self):
arglist = [
@@ -205,33 +212,17 @@ class TestFlavorList(TestFlavor):
# Set expected values
kwargs = {
- 'is_public': True
+ 'is_public': True,
+ 'limit': None,
+ 'marker': None
}
self.flavors_mock.list.assert_called_with(
**kwargs
)
- collist = (
- 'ID',
- 'Name',
- 'RAM',
- 'Disk',
- 'Ephemeral',
- 'VCPUs',
- 'Is Public',
- )
- self.assertEqual(collist, columns)
- datalist = ((
- compute_fakes.flavor_id,
- compute_fakes.flavor_name,
- compute_fakes.flavor_ram,
- '',
- '',
- compute_fakes.flavor_vcpus,
- ''
- ), )
- self.assertEqual(datalist, tuple(data))
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(tuple(self.data), tuple(data))
def test_flavor_list_long(self):
arglist = [
@@ -248,51 +239,28 @@ class TestFlavorList(TestFlavor):
# Set expected values
kwargs = {
- 'is_public': True
+ 'is_public': True,
+ 'limit': None,
+ 'marker': None
}
self.flavors_mock.list.assert_called_with(
**kwargs
)
- collist = (
- 'ID',
- 'Name',
- 'RAM',
- 'Disk',
- 'Ephemeral',
- 'VCPUs',
- 'Is Public',
- 'Swap',
- 'RXTX Factor',
- 'Properties'
- )
- self.assertEqual(collist, columns)
- datalist = ((
- compute_fakes.flavor_id,
- compute_fakes.flavor_name,
- compute_fakes.flavor_ram,
- '',
- '',
- compute_fakes.flavor_vcpus,
- '',
- '',
- '',
- 'property=\'value\''
- ), )
- self.assertEqual(datalist, tuple(data))
+ self.assertEqual(self.columns_long, columns)
+ self.assertEqual(tuple(self.data_long), tuple(data))
class TestFlavorSet(TestFlavor):
+ # Return value of self.flavors_mock.find().
+ flavor = compute_fakes.FakeFlavor.create_one_flavor()
+
def setUp(self):
super(TestFlavorSet, self).setUp()
- self.flavors_mock.find.return_value = FakeFlavorResource(
- None,
- copy.deepcopy(compute_fakes.FLAVOR),
- loaded=True,
- )
+ self.flavors_mock.find.return_value = self.flavor
self.cmd = flavor.SetFlavor(self.app, None)
@@ -318,14 +286,13 @@ class TestFlavorSet(TestFlavor):
class TestFlavorUnset(TestFlavor):
+ # Return value of self.flavors_mock.find().
+ flavor = compute_fakes.FakeFlavor.create_one_flavor()
+
def setUp(self):
super(TestFlavorUnset, self).setUp()
- self.flavors_mock.find.return_value = FakeFlavorResource(
- None,
- copy.deepcopy(compute_fakes.FLAVOR),
- loaded=True,
- )
+ self.flavors_mock.find.return_value = self.flavor
self.cmd = flavor.UnsetFlavor(self.app, None)
diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py
index 4df18f0..359920f 100644
--- a/openstackclient/tests/compute/v2/test_server.py
+++ b/openstackclient/tests/compute/v2/test_server.py
@@ -17,6 +17,7 @@ import copy
import mock
import testtools
+from mock import call
from openstackclient.common import exceptions
from openstackclient.common import utils as common_utils
from openstackclient.compute.v2 import server
@@ -24,6 +25,7 @@ from openstackclient.tests.compute.v2 import fakes as compute_fakes
from openstackclient.tests import fakes
from openstackclient.tests.image.v2 import fakes as image_fakes
from openstackclient.tests import utils
+from openstackclient.tests.volume.v2 import fakes as volume_fakes
class TestServer(compute_fakes.TestComputev2):
@@ -47,24 +49,63 @@ class TestServer(compute_fakes.TestComputev2):
self.images_mock = self.app.client_manager.image.images
self.images_mock.reset_mock()
+ # Get a shortcut to the VolumeManager Mock
+ self.volumes_mock = self.app.client_manager.volume.volumes
+ self.volumes_mock.reset_mock()
+
+ # Set object attributes to be tested. Could be overwriten in subclass.
+ self.attrs = {}
+
+ # Set object methods to be tested. Could be overwriten in subclass.
+ self.methods = {}
+
+ def setup_servers_mock(self, count):
+ servers = compute_fakes.FakeServer.create_servers(attrs=self.attrs,
+ methods=self.methods,
+ count=count)
+
+ # This is the return value for utils.find_resource()
+ self.servers_mock.get = compute_fakes.FakeServer.get_servers(servers,
+ 0)
+ return servers
+
+ def run_method_with_servers(self, method_name, server_count):
+ servers = self.setup_servers_mock(server_count)
+
+ arglist = []
+ verifylist = []
+
+ for s in servers:
+ arglist.append(s.id)
+ verifylist = [
+ ('server', arglist),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ self.cmd.take_action(parsed_args)
+
+ for s in servers:
+ method = getattr(s, method_name)
+ method.assert_called_with()
+
class TestServerCreate(TestServer):
def setUp(self):
super(TestServerCreate, self).setUp()
- self.servers_mock.create.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(compute_fakes.SERVER),
- loaded=True,
- )
- new_server = fakes.FakeResource(
- None,
- copy.deepcopy(compute_fakes.SERVER),
- loaded=True,
- )
- new_server.__dict__['networks'] = {}
- self.servers_mock.get.return_value = new_server
+ attrs = {
+ 'networks': {},
+ }
+ self.new_server = compute_fakes.FakeServer.create_one_server(
+ attrs=attrs)
+
+ # This is the return value for utils.find_resource().
+ # This is for testing --wait option.
+ self.servers_mock.get.return_value = self.new_server
+
+ self.servers_mock.create.return_value = self.new_server
self.image = fakes.FakeResource(
None,
@@ -73,22 +114,25 @@ class TestServerCreate(TestServer):
)
self.cimages_mock.get.return_value = self.image
- self.flavor = fakes.FakeResource(
+ self.flavor = compute_fakes.FakeFlavor.create_one_flavor()
+ self.flavors_mock.get.return_value = self.flavor
+
+ self.volume = fakes.FakeResource(
None,
- copy.deepcopy(compute_fakes.FLAVOR),
+ copy.deepcopy(volume_fakes.VOLUME),
loaded=True,
)
- self.flavors_mock.get.return_value = self.flavor
+ self.volumes_mock.get.return_value = self.volume
# Get the command object to test
self.cmd = server.CreateServer(self.app, None)
def test_server_create_no_options(self):
arglist = [
- compute_fakes.server_id,
+ self.new_server.name,
]
verifylist = [
- ('server_name', compute_fakes.server_id),
+ ('server_name', self.new_server.name),
]
try:
# Missing required args should bail here
@@ -100,13 +144,13 @@ class TestServerCreate(TestServer):
arglist = [
'--image', 'image1',
'--flavor', 'flavor1',
- compute_fakes.server_id,
+ self.new_server.name,
]
verifylist = [
('image', 'image1'),
('flavor', 'flavor1'),
('config_drive', False),
- ('server_name', compute_fakes.server_id),
+ ('server_name', self.new_server.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -131,19 +175,27 @@ class TestServerCreate(TestServer):
)
# ServerManager.create(name, image, flavor, **kwargs)
self.servers_mock.create.assert_called_with(
- compute_fakes.server_id,
+ self.new_server.name,
self.image,
self.flavor,
**kwargs
)
- collist = ('addresses', 'flavor', 'id', 'name', 'properties')
+ collist = (
+ 'addresses',
+ 'flavor',
+ 'id',
+ 'name',
+ 'networks',
+ 'properties',
+ )
self.assertEqual(collist, columns)
datalist = (
'',
- 'Large ()',
- compute_fakes.server_id,
- compute_fakes.server_name,
+ self.flavor.name + ' ()',
+ self.new_server.id,
+ self.new_server.name,
+ self.new_server.networks,
'',
)
self.assertEqual(datalist, data)
@@ -154,14 +206,14 @@ class TestServerCreate(TestServer):
'--flavor', 'flavor1',
'--nic', 'net-id=net1',
'--nic', 'port-id=port1',
- compute_fakes.server_id,
+ self.new_server.name,
]
verifylist = [
('image', 'image1'),
('flavor', 'flavor1'),
('nic', ['net-id=net1', 'port-id=port1']),
('config_drive', False),
- ('server_name', compute_fakes.server_id),
+ ('server_name', self.new_server.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -207,19 +259,27 @@ class TestServerCreate(TestServer):
)
# ServerManager.create(name, image, flavor, **kwargs)
self.servers_mock.create.assert_called_with(
- compute_fakes.server_id,
+ self.new_server.name,
self.image,
self.flavor,
**kwargs
)
- collist = ('addresses', 'flavor', 'id', 'name', 'properties')
+ collist = (
+ 'addresses',
+ 'flavor',
+ 'id',
+ 'name',
+ 'networks',
+ 'properties',
+ )
self.assertEqual(collist, columns)
datalist = (
'',
- 'Large ()',
- compute_fakes.server_id,
- compute_fakes.server_name,
+ self.flavor.name + ' ()',
+ self.new_server.id,
+ self.new_server.name,
+ self.new_server.networks,
'',
)
self.assertEqual(datalist, data)
@@ -234,14 +294,14 @@ class TestServerCreate(TestServer):
'--image', 'image1',
'--flavor', 'flavor1',
'--user-data', 'userdata.sh',
- compute_fakes.server_id,
+ self.new_server.name,
]
verifylist = [
('image', 'image1'),
('flavor', 'flavor1'),
('user_data', 'userdata.sh'),
('config_drive', False),
- ('server_name', compute_fakes.server_id),
+ ('server_name', self.new_server.name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -272,19 +332,96 @@ class TestServerCreate(TestServer):
)
# ServerManager.create(name, image, flavor, **kwargs)
self.servers_mock.create.assert_called_with(
- compute_fakes.server_id,
+ self.new_server.name,
self.image,
self.flavor,
**kwargs
)
- collist = ('addresses', 'flavor', 'id', 'name', 'properties')
+ collist = (
+ 'addresses',
+ 'flavor',
+ 'id',
+ 'name',
+ 'networks',
+ 'properties',
+ )
self.assertEqual(collist, columns)
datalist = (
'',
- 'Large ()',
- compute_fakes.server_id,
- compute_fakes.server_name,
+ self.flavor.name + ' ()',
+ self.new_server.id,
+ self.new_server.name,
+ self.new_server.networks,
+ '',
+ )
+ self.assertEqual(datalist, data)
+
+ def test_server_create_with_block_device_mapping(self):
+ arglist = [
+ '--image', 'image1',
+ '--flavor', self.flavor.id,
+ '--block-device-mapping', compute_fakes.block_device_mapping,
+ self.new_server.name,
+ ]
+ verifylist = [
+ ('image', 'image1'),
+ ('flavor', self.flavor.id),
+ ('block_device_mapping', [compute_fakes.block_device_mapping]),
+ ('config_drive', False),
+ ('server_name', self.new_server.name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # CreateServer.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ real_volume_mapping = (
+ (compute_fakes.block_device_mapping.split('=', 1)[1]).replace(
+ volume_fakes.volume_name,
+ volume_fakes.volume_id))
+
+ # 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={
+ 'vda': real_volume_mapping
+ },
+ nics=[],
+ 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
+ )
+
+ collist = (
+ 'addresses',
+ 'flavor',
+ 'id',
+ 'name',
+ 'networks',
+ 'properties',
+ )
+ self.assertEqual(collist, columns)
+ datalist = (
+ '',
+ self.flavor.name + ' ()',
+ self.new_server.id,
+ self.new_server.name,
+ self.new_server.networks,
'',
)
self.assertEqual(datalist, data)
@@ -295,23 +432,19 @@ class TestServerDelete(TestServer):
def setUp(self):
super(TestServerDelete, self).setUp()
- # This is the return value for utils.find_resource()
- self.servers_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(compute_fakes.SERVER),
- loaded=True,
- )
self.servers_mock.delete.return_value = None
# Get the command object to test
self.cmd = server.DeleteServer(self.app, None)
def test_server_delete_no_options(self):
+ servers = self.setup_servers_mock(count=1)
+
arglist = [
- compute_fakes.server_id,
+ servers[0].id,
]
verifylist = [
- ('servers', [compute_fakes.server_id]),
+ ('server', [servers[0].id]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -319,16 +452,39 @@ class TestServerDelete(TestServer):
self.cmd.take_action(parsed_args)
self.servers_mock.delete.assert_called_with(
- compute_fakes.server_id,
+ servers[0].id,
)
+ def test_server_delete_multi_servers(self):
+ servers = self.setup_servers_mock(count=3)
+
+ arglist = []
+ verifylist = []
+
+ for s in servers:
+ arglist.append(s.id)
+ verifylist = [
+ ('server', arglist),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ self.cmd.take_action(parsed_args)
+
+ calls = []
+ for s in servers:
+ calls.append(call(s.id))
+ self.servers_mock.delete.assert_has_calls(calls)
+
@mock.patch.object(common_utils, 'wait_for_delete', return_value=True)
def test_server_delete_wait_ok(self, mock_wait_for_delete):
+ servers = self.setup_servers_mock(count=1)
+
arglist = [
- compute_fakes.server_id, '--wait'
+ servers[0].id, '--wait'
]
verifylist = [
- ('servers', [compute_fakes.server_id]),
+ ('server', [servers[0].id]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -336,22 +492,24 @@ class TestServerDelete(TestServer):
self.cmd.take_action(parsed_args)
self.servers_mock.delete.assert_called_with(
- compute_fakes.server_id,
+ servers[0].id,
)
mock_wait_for_delete.assert_called_once_with(
self.servers_mock,
- compute_fakes.server_id,
+ servers[0].id,
callback=server._show_progress
)
@mock.patch.object(common_utils, 'wait_for_delete', return_value=False)
def test_server_delete_wait_fails(self, mock_wait_for_delete):
+ servers = self.setup_servers_mock(count=1)
+
arglist = [
- compute_fakes.server_id, '--wait'
+ servers[0].id, '--wait'
]
verifylist = [
- ('servers', [compute_fakes.server_id]),
+ ('server', [servers[0].id]),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -359,12 +517,12 @@ class TestServerDelete(TestServer):
self.assertRaises(SystemExit, self.cmd.take_action, parsed_args)
self.servers_mock.delete.assert_called_with(
- compute_fakes.server_id,
+ servers[0].id,
)
mock_wait_for_delete.assert_called_once_with(
self.servers_mock,
- compute_fakes.server_id,
+ servers[0].id,
callback=server._show_progress
)
@@ -374,12 +532,10 @@ class TestServerImageCreate(TestServer):
def setUp(self):
super(TestServerImageCreate, self).setUp()
+ self.server = compute_fakes.FakeServer.create_one_server()
+
# This is the return value for utils.find_resource()
- self.servers_mock.get.return_value = fakes.FakeResource(
- None,
- copy.deepcopy(compute_fakes.SERVER),
- loaded=True,
- )
+ self.servers_mock.get.return_value = self.server
self.servers_mock.create_image.return_value = image_fakes.image_id
@@ -394,10 +550,10 @@ class TestServerImageCreate(TestServer):
def test_server_image_create_no_options(self):
arglist = [
- compute_fakes.server_id,
+ self.server.id,
]
verifylist = [
- ('server', compute_fakes.server_id),
+ ('server', self.server.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -407,16 +563,17 @@ class TestServerImageCreate(TestServer):
# ServerManager.create_image(server, image_name, metadata=)
self.servers_mock.create_image.assert_called_with(
self.servers_mock.get.return_value,
- compute_fakes.server_name,
+ self.server.name,
)
- collist = ('id', 'name', 'owner', 'protected', 'visibility')
+ collist = ('id', 'name', 'owner', 'protected', 'tags', 'visibility')
self.assertEqual(collist, columns)
datalist = (
image_fakes.image_id,
image_fakes.image_name,
image_fakes.image_owner,
image_fakes.image_protected,
+ image_fakes.image_tags,
image_fakes.image_visibility,
)
self.assertEqual(datalist, data)
@@ -424,11 +581,11 @@ class TestServerImageCreate(TestServer):
def test_server_image_create_name(self):
arglist = [
'--name', 'img-nam',
- compute_fakes.server_id,
+ self.server.id,
]
verifylist = [
('name', 'img-nam'),
- ('server', compute_fakes.server_id),
+ ('server', self.server.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -441,41 +598,157 @@ class TestServerImageCreate(TestServer):
'img-nam',
)
- collist = ('id', 'name', 'owner', 'protected', 'visibility')
+ collist = ('id', 'name', 'owner', 'protected', 'tags', 'visibility')
self.assertEqual(collist, columns)
datalist = (
image_fakes.image_id,
image_fakes.image_name,
image_fakes.image_owner,
image_fakes.image_protected,
+ image_fakes.image_tags,
image_fakes.image_visibility,
)
self.assertEqual(datalist, data)
+class TestServerList(TestServer):
+
+ # Columns to be listed up.
+ columns = (
+ 'ID',
+ 'Name',
+ 'Status',
+ 'Networks',
+ )
+
+ # Data returned by corresponding Nova API. The elements in this list are
+ # tuples filled with server attributes.
+ data = []
+
+ # Default search options, in the case of no commandline option specified.
+ search_opts = {
+ 'reservation_id': None,
+ 'ip': None,
+ 'ip6': None,
+ 'name': None,
+ 'instance_name': None,
+ 'status': None,
+ 'flavor': None,
+ 'image': None,
+ 'host': None,
+ 'tenant_id': None,
+ 'all_tenants': False,
+ 'user_id': None,
+ }
+
+ # Default params of the core function of the command in the case of no
+ # commandline option specified.
+ kwargs = {
+ 'search_opts': search_opts,
+ 'marker': None,
+ 'limit': None,
+ }
+
+ def setUp(self):
+ super(TestServerList, self).setUp()
+
+ # The fake servers' attributes.
+ self.attrs = {
+ 'status': 'ACTIVE',
+ 'networks': {
+ u'public': [u'10.20.30.40', u'2001:db8::5']
+ },
+ }
+
+ # The servers to be listed.
+ self.servers = self.setup_servers_mock(3)
+
+ self.servers_mock.list.return_value = self.servers
+
+ # Get the command object to test
+ self.cmd = server.ListServer(self.app, None)
+
+ # Prepare data returned by fake Nova API.
+ for s in self.servers:
+ self.data.append((
+ s.id,
+ s.name,
+ s.status,
+ u'public=10.20.30.40, 2001:db8::5',
+ ))
+
+ def test_server_list_no_option(self):
+ arglist = []
+ verifylist = [
+ ('all_projects', False),
+ ('long', False),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.servers_mock.list.assert_called_with(**self.kwargs)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(tuple(self.data), tuple(data))
+
+
+class TestServerLock(TestServer):
+
+ def setUp(self):
+ super(TestServerLock, self).setUp()
+
+ # Get the command object to test
+ self.cmd = server.LockServer(self.app, None)
+
+ # Set methods to be tested.
+ self.methods = {
+ 'lock': None,
+ }
+
+ def test_server_lock_one_server(self):
+ self.run_method_with_servers('lock', 1)
+
+ def test_server_lock_multi_servers(self):
+ self.run_method_with_servers('lock', 3)
+
+
+class TestServerPause(TestServer):
+
+ def setUp(self):
+ super(TestServerPause, self).setUp()
+
+ # Get the command object to test
+ self.cmd = server.PauseServer(self.app, None)
+
+ # Set methods to be tested.
+ self.methods = {
+ 'pause': None,
+ }
+
+ def test_server_pause_one_server(self):
+ self.run_method_with_servers('pause', 1)
+
+ def test_server_pause_multi_servers(self):
+ self.run_method_with_servers('pause', 3)
+
+
class TestServerResize(TestServer):
def setUp(self):
super(TestServerResize, self).setUp()
+ self.server = compute_fakes.FakeServer.create_one_server()
+
# This is the return value for utils.find_resource()
- self.servers_get_return_value = fakes.FakeResource(
- None,
- copy.deepcopy(compute_fakes.SERVER),
- loaded=True,
- )
- self.servers_mock.get.return_value = self.servers_get_return_value
+ self.servers_mock.get.return_value = self.server
self.servers_mock.resize.return_value = None
self.servers_mock.confirm_resize.return_value = None
self.servers_mock.revert_resize.return_value = None
# This is the return value for utils.find_resource()
- self.flavors_get_return_value = fakes.FakeResource(
- None,
- copy.deepcopy(compute_fakes.FLAVOR),
- loaded=True,
- )
+ self.flavors_get_return_value = \
+ compute_fakes.FakeFlavor.create_one_flavor()
self.flavors_mock.get.return_value = self.flavors_get_return_value
# Get the command object to test
@@ -483,12 +756,12 @@ class TestServerResize(TestServer):
def test_server_resize_no_options(self):
arglist = [
- compute_fakes.server_id,
+ self.server.id,
]
verifylist = [
('confirm', False),
('revert', False),
- ('server', compute_fakes.server_id),
+ ('server', self.server.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -496,7 +769,7 @@ class TestServerResize(TestServer):
self.cmd.take_action(parsed_args)
self.servers_mock.get.assert_called_with(
- compute_fakes.server_id,
+ self.server.id,
)
self.assertNotCalled(self.servers_mock.resize)
@@ -505,14 +778,14 @@ class TestServerResize(TestServer):
def test_server_resize(self):
arglist = [
- '--flavor', compute_fakes.flavor_id,
- compute_fakes.server_id,
+ '--flavor', self.flavors_get_return_value.id,
+ self.server.id,
]
verifylist = [
- ('flavor', compute_fakes.flavor_id),
+ ('flavor', self.flavors_get_return_value.id),
('confirm', False),
('revert', False),
- ('server', compute_fakes.server_id),
+ ('server', self.server.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -520,14 +793,14 @@ class TestServerResize(TestServer):
self.cmd.take_action(parsed_args)
self.servers_mock.get.assert_called_with(
- compute_fakes.server_id,
+ self.server.id,
)
self.flavors_mock.get.assert_called_with(
- compute_fakes.flavor_id,
+ self.flavors_get_return_value.id,
)
self.servers_mock.resize.assert_called_with(
- self.servers_get_return_value,
+ self.server,
self.flavors_get_return_value,
)
self.assertNotCalled(self.servers_mock.confirm_resize)
@@ -536,12 +809,12 @@ class TestServerResize(TestServer):
def test_server_resize_confirm(self):
arglist = [
'--confirm',
- compute_fakes.server_id,
+ self.server.id,
]
verifylist = [
('confirm', True),
('revert', False),
- ('server', compute_fakes.server_id),
+ ('server', self.server.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -549,24 +822,24 @@ class TestServerResize(TestServer):
self.cmd.take_action(parsed_args)
self.servers_mock.get.assert_called_with(
- compute_fakes.server_id,
+ self.server.id,
)
self.assertNotCalled(self.servers_mock.resize)
self.servers_mock.confirm_resize.assert_called_with(
- self.servers_get_return_value,
+ self.server,
)
self.assertNotCalled(self.servers_mock.revert_resize)
def test_server_resize_revert(self):
arglist = [
'--revert',
- compute_fakes.server_id,
+ self.server.id,
]
verifylist = [
('confirm', False),
('revert', True),
- ('server', compute_fakes.server_id),
+ ('server', self.server.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -574,16 +847,176 @@ class TestServerResize(TestServer):
self.cmd.take_action(parsed_args)
self.servers_mock.get.assert_called_with(
- compute_fakes.server_id,
+ self.server.id,
)
self.assertNotCalled(self.servers_mock.resize)
self.assertNotCalled(self.servers_mock.confirm_resize)
self.servers_mock.revert_resize.assert_called_with(
- self.servers_get_return_value,
+ self.server,
)
+class TestServerResume(TestServer):
+
+ def setUp(self):
+ super(TestServerResume, self).setUp()
+
+ # Get the command object to test
+ self.cmd = server.ResumeServer(self.app, None)
+
+ # Set methods to be tested.
+ self.methods = {
+ 'resume': None,
+ }
+
+ def test_server_resume_one_server(self):
+ self.run_method_with_servers('resume', 1)
+
+ def test_server_resume_multi_servers(self):
+ self.run_method_with_servers('resume', 3)
+
+
+class TestServerShelve(TestServer):
+
+ def setUp(self):
+ super(TestServerShelve, self).setUp()
+
+ # Get the command object to test
+ self.cmd = server.ShelveServer(self.app, None)
+
+ # Set shelve method to be tested.
+ self.methods = {
+ 'shelve': None,
+ }
+
+ def test_shelve_one_server(self):
+ self.run_method_with_servers('shelve', 1)
+
+ def test_shelve_multi_servers(self):
+ self.run_method_with_servers('shelve', 3)
+
+
+class TestServerStart(TestServer):
+
+ def setUp(self):
+ super(TestServerStart, self).setUp()
+
+ # Get the command object to test
+ self.cmd = server.StartServer(self.app, None)
+
+ # Set methods to be tested.
+ self.methods = {
+ 'start': None,
+ }
+
+ def test_server_start_one_server(self):
+ self.run_method_with_servers('start', 1)
+
+ def test_server_start_multi_servers(self):
+ self.run_method_with_servers('start', 3)
+
+
+class TestServerStop(TestServer):
+
+ def setUp(self):
+ super(TestServerStop, self).setUp()
+
+ # Get the command object to test
+ self.cmd = server.StopServer(self.app, None)
+
+ # Set methods to be tested.
+ self.methods = {
+ 'stop': None,
+ }
+
+ def test_server_stop_one_server(self):
+ self.run_method_with_servers('stop', 1)
+
+ def test_server_stop_multi_servers(self):
+ self.run_method_with_servers('stop', 3)
+
+
+class TestServerSuspend(TestServer):
+
+ def setUp(self):
+ super(TestServerSuspend, self).setUp()
+
+ # Get the command object to test
+ self.cmd = server.SuspendServer(self.app, None)
+
+ # Set methods to be tested.
+ self.methods = {
+ 'suspend': None,
+ }
+
+ def test_server_suspend_one_server(self):
+ self.run_method_with_servers('suspend', 1)
+
+ def test_server_suspend_multi_servers(self):
+ self.run_method_with_servers('suspend', 3)
+
+
+class TestServerUnlock(TestServer):
+
+ def setUp(self):
+ super(TestServerUnlock, self).setUp()
+
+ # Get the command object to test
+ self.cmd = server.UnlockServer(self.app, None)
+
+ # Set methods to be tested.
+ self.methods = {
+ 'unlock': None,
+ }
+
+ def test_server_unlock_one_server(self):
+ self.run_method_with_servers('unlock', 1)
+
+ def test_server_unlock_multi_servers(self):
+ self.run_method_with_servers('unlock', 3)
+
+
+class TestServerUnpause(TestServer):
+
+ def setUp(self):
+ super(TestServerUnpause, self).setUp()
+
+ # Get the command object to test
+ self.cmd = server.UnpauseServer(self.app, None)
+
+ # Set methods to be tested.
+ self.methods = {
+ 'unpause': None,
+ }
+
+ def test_server_unpause_one_server(self):
+ self.run_method_with_servers('unpause', 1)
+
+ def test_server_unpause_multi_servers(self):
+ self.run_method_with_servers('unpause', 3)
+
+
+class TestServerUnshelve(TestServer):
+
+ def setUp(self):
+ super(TestServerUnshelve, self).setUp()
+
+ # Get the command object to test
+ self.cmd = server.UnshelveServer(self.app, None)
+
+ # Set unshelve method to be tested.
+ self.methods = {
+ 'unshelve': None,
+ }
+
+ def test_unshelve_one_server(self):
+ self.run_method_with_servers('unshelve', 1)
+
+ def test_unshelve_multi_servers(self):
+ self.run_method_with_servers('unshelve', 3)
+
+
class TestServerGeneral(testtools.TestCase):
OLD = {
'private': [
diff --git a/openstackclient/tests/compute/v2/test_service.py b/openstackclient/tests/compute/v2/test_service.py
new file mode 100644
index 0000000..380fbc4
--- /dev/null
+++ b/openstackclient/tests/compute/v2/test_service.py
@@ -0,0 +1,151 @@
+# Copyright 2015 Mirantis, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+import copy
+
+from openstackclient.compute.v2 import service
+from openstackclient.tests.compute.v2 import fakes as compute_fakes
+from openstackclient.tests import fakes
+
+
+class TestService(compute_fakes.TestComputev2):
+
+ def setUp(self):
+ super(TestService, self).setUp()
+
+ # Get a shortcut to the ServiceManager Mock
+ self.service_mock = self.app.client_manager.compute.services
+ self.service_mock.reset_mock()
+
+
+class TestServiceDelete(TestService):
+
+ def setUp(self):
+ super(TestServiceDelete, self).setUp()
+
+ self.service_mock.delete.return_value = None
+
+ # Get the command object to test
+ self.cmd = service.DeleteService(self.app, None)
+
+ def test_service_delete_no_options(self):
+ arglist = [
+ compute_fakes.service_binary,
+ ]
+ verifylist = [
+ ('service', compute_fakes.service_binary),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ self.cmd.take_action(parsed_args)
+
+ self.service_mock.delete.assert_called_with(
+ compute_fakes.service_binary,
+ )
+
+
+class TestServiceList(TestService):
+
+ def setUp(self):
+ super(TestServiceList, self).setUp()
+
+ self.service_mock.list.return_value = [fakes.FakeResource(
+ None,
+ copy.deepcopy(compute_fakes.SERVICE),
+ loaded=True,
+ )]
+
+ # Get the command object to test
+ self.cmd = service.ListService(self.app, None)
+
+ def test_service_list(self):
+ arglist = [
+ '--host', compute_fakes.service_host,
+ '--service', compute_fakes.service_binary,
+ ]
+ verifylist = [
+ ('host', compute_fakes.service_host),
+ ('service', compute_fakes.service_binary),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ self.cmd.take_action(parsed_args)
+
+ self.service_mock.list.assert_called_with(
+ compute_fakes.service_host,
+ compute_fakes.service_binary,
+ )
+
+
+class TestServiceSet(TestService):
+
+ def setUp(self):
+ super(TestServiceSet, self).setUp()
+
+ self.service_mock.enable.return_value = [fakes.FakeResource(
+ None,
+ copy.deepcopy(compute_fakes.SERVICE),
+ loaded=True,
+ )]
+
+ self.service_mock.disable.return_value = [fakes.FakeResource(
+ None,
+ copy.deepcopy(compute_fakes.SERVICE),
+ loaded=True,
+ )]
+
+ self.cmd = service.SetService(self.app, None)
+
+ def test_service_set_enable(self):
+ arglist = [
+ compute_fakes.service_host,
+ compute_fakes.service_binary,
+ '--enable',
+ ]
+ verifylist = [
+ ('host', compute_fakes.service_host),
+ ('service', compute_fakes.service_binary),
+ ('enabled', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.cmd.take_action(parsed_args)
+
+ self.service_mock.enable.assert_called_with(
+ compute_fakes.service_host,
+ compute_fakes.service_binary,
+ )
+
+ def test_service_set_disable(self):
+ arglist = [
+ compute_fakes.service_host,
+ compute_fakes.service_binary,
+ '--disable',
+ ]
+ verifylist = [
+ ('host', compute_fakes.service_host),
+ ('service', compute_fakes.service_binary),
+ ('enabled', False),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.cmd.take_action(parsed_args)
+
+ self.service_mock.disable.assert_called_with(
+ compute_fakes.service_host,
+ compute_fakes.service_binary,
+ )
diff --git a/openstackclient/tests/fakes.py b/openstackclient/tests/fakes.py
index 979f948..9f4dcc5 100644
--- a/openstackclient/tests/fakes.py
+++ b/openstackclient/tests/fakes.py
@@ -14,10 +14,11 @@
#
import json
+import mock
import six
import sys
-from keystoneclient import fixture
+from keystoneauth1 import fixture
import requests
@@ -122,17 +123,41 @@ class FakeModule(object):
class FakeResource(object):
- def __init__(self, manager, info, loaded=False):
+ def __init__(self, manager=None, info={}, loaded=False, methods={}):
+ """Set attributes and methods for a resource.
+
+ :param manager:
+ The resource manager
+ :param Dictionary info:
+ A dictionary with all attributes
+ :param bool loaded:
+ True if the resource is loaded in memory
+ :param Dictionary methods:
+ A dictionary with all methods
+ """
self.__name__ = type(self).__name__
self.manager = manager
self._info = info
self._add_details(info)
+ self._add_methods(methods)
self._loaded = loaded
def _add_details(self, info):
for (k, v) in six.iteritems(info):
setattr(self, k, v)
+ def _add_methods(self, methods):
+ """Fake methods with MagicMock objects.
+
+ For each <@key, @value> pairs in methods, add an callable MagicMock
+ object named @key as an attribute, and set the mock's return_value to
+ @value. When users access the attribute with (), @value will be
+ returned, which looks like a function call.
+ """
+ for (name, ret) in six.iteritems(methods):
+ method = mock.MagicMock(return_value=ret)
+ setattr(self, name, method)
+
def __repr__(self):
reprkeys = sorted(k for k in self.__dict__.keys() if k[0] != '_' and
k != 'manager')
@@ -150,3 +175,11 @@ class FakeResponse(requests.Response):
self._content = json.dumps(data)
if not isinstance(self._content, six.binary_type):
self._content = self._content.encode()
+
+
+class FakeModel(dict):
+ def __getattr__(self, key):
+ try:
+ return self[key]
+ except KeyError:
+ raise AttributeError(key)
diff --git a/openstackclient/tests/identity/v2_0/test_project.py b/openstackclient/tests/identity/v2_0/test_project.py
index 18e862e..16ab195 100644
--- a/openstackclient/tests/identity/v2_0/test_project.py
+++ b/openstackclient/tests/identity/v2_0/test_project.py
@@ -15,7 +15,7 @@
import copy
-from keystoneclient import exceptions as ksc_exc
+from keystoneauth1 import exceptions as ks_exc
from openstackclient.identity.v2_0 import project
from openstackclient.tests import fakes
@@ -223,7 +223,7 @@ class TestProjectCreate(TestProject):
def test_project_create_or_show_exists(self):
def _raise_conflict(*args, **kwargs):
- raise ksc_exc.Conflict(None)
+ raise ks_exc.Conflict(None)
# need to make this throw an exception...
self.projects_mock.create.side_effect = _raise_conflict
diff --git a/openstackclient/tests/identity/v2_0/test_role.py b/openstackclient/tests/identity/v2_0/test_role.py
index 478a4ff..ec48433 100644
--- a/openstackclient/tests/identity/v2_0/test_role.py
+++ b/openstackclient/tests/identity/v2_0/test_role.py
@@ -16,7 +16,7 @@
import copy
import mock
-from keystoneclient import exceptions as ksc_exc
+from keystoneauth1 import exceptions as ks_exc
from openstackclient.common import exceptions
from openstackclient.identity.v2_0 import role
@@ -146,7 +146,7 @@ class TestRoleCreate(TestRole):
def test_role_create_or_show_exists(self):
def _raise_conflict(*args, **kwargs):
- raise ksc_exc.Conflict(None)
+ raise ks_exc.Conflict(None)
# need to make this throw an exception...
self.roles_mock.create.side_effect = _raise_conflict
diff --git a/openstackclient/tests/identity/v2_0/test_user.py b/openstackclient/tests/identity/v2_0/test_user.py
index 6fe82f9..bf25681 100644
--- a/openstackclient/tests/identity/v2_0/test_user.py
+++ b/openstackclient/tests/identity/v2_0/test_user.py
@@ -16,7 +16,8 @@
import copy
import mock
-from keystoneclient import exceptions as ksc_exc
+from keystoneauth1 import exceptions as ks_exc
+
from openstackclient.identity.v2_0 import user
from openstackclient.tests import fakes
from openstackclient.tests.identity.v2_0 import fakes as identity_fakes
@@ -345,7 +346,7 @@ class TestUserCreate(TestUser):
def test_user_create_or_show_exists(self):
def _raise_conflict(*args, **kwargs):
- raise ksc_exc.Conflict(None)
+ raise ks_exc.Conflict(None)
# need to make this throw an exception...
self.users_mock.create.side_effect = _raise_conflict
diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/identity/v3/fakes.py
index 9c4de9c..9fe341e 100644
--- a/openstackclient/tests/identity/v3/fakes.py
+++ b/openstackclient/tests/identity/v3/fakes.py
@@ -123,13 +123,11 @@ PROJECT_2 = {
}
region_id = 'region_one'
-region_url = 'http://localhost:1111'
region_parent_region_id = 'region_two'
region_description = 'region one'
REGION = {
'id': region_id,
- 'url': region_url,
'description': region_description,
'parent_region_id': region_parent_region_id,
'links': base_url + 'regions/' + region_id,
@@ -195,6 +193,8 @@ SERVICE_WITHOUT_NAME = {
'links': base_url + 'services/' + service_id,
}
+credential_id = 'c-123'
+
endpoint_id = 'e-123'
endpoint_url = 'http://127.0.0.1:35357'
endpoint_region = 'RegionOne'
@@ -261,6 +261,7 @@ TOKEN_WITH_DOMAIN_ID = {
idp_id = 'test_idp'
idp_description = 'super exciting IdP description'
idp_remote_ids = ['entity1', 'entity2']
+formatted_idp_remote_ids = 'entity1, entity2'
IDENTITY_PROVIDER = {
'id': idp_id,
@@ -400,6 +401,8 @@ class FakeIdentityv3Client(object):
def __init__(self, **kwargs):
self.domains = mock.Mock()
self.domains.resource_class = fakes.FakeResource(None, {})
+ self.credentials = mock.Mock()
+ self.credentials.resource_class = fakes.FakeResource(None, {})
self.endpoints = mock.Mock()
self.endpoints.resource_class = fakes.FakeResource(None, {})
self.groups = mock.Mock()
diff --git a/openstackclient/tests/identity/v3/test_credential.py b/openstackclient/tests/identity/v3/test_credential.py
new file mode 100644
index 0000000..e2e690c
--- /dev/null
+++ b/openstackclient/tests/identity/v3/test_credential.py
@@ -0,0 +1,112 @@
+# 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.identity.v3 import credential
+from openstackclient.tests.identity.v3 import fakes as identity_fakes
+from openstackclient.tests import utils
+
+
+class TestCredential(identity_fakes.TestIdentityv3):
+ data = {
+ "access": "abc123",
+ "secret": "hidden-message",
+ "trust_id": None
+ }
+
+ def __init__(self, *args):
+ super(TestCredential, self).__init__(*args)
+
+ self.json_data = json.dumps(self.data)
+
+ def setUp(self):
+ super(TestCredential, self).setUp()
+
+ # Get a shortcut to the CredentialManager Mock
+ self.credentials_mock = self.app.client_manager.identity.credentials
+ self.credentials_mock.reset_mock()
+
+ # Get a shortcut to the UserManager Mock
+ self.users_mock = self.app.client_manager.identity.users
+ self.users_mock.reset_mock()
+
+ # Get a shortcut to the ProjectManager Mock
+ self.projects_mock = self.app.client_manager.identity.projects
+ self.projects_mock.reset_mock()
+
+
+class TestCredentialSet(TestCredential):
+ def setUp(self):
+ super(TestCredentialSet, self).setUp()
+ self.cmd = credential.SetCredential(self.app, None)
+
+ def test_credential_set_no_options(self):
+ arglist = [
+ identity_fakes.credential_id,
+ ]
+
+ self.assertRaises(utils.ParserException,
+ self.check_parser, self.cmd, arglist, [])
+
+ def test_credential_set_missing_user(self):
+ arglist = [
+ '--type', 'ec2',
+ '--data', self.json_data,
+ identity_fakes.credential_id,
+ ]
+
+ self.assertRaises(utils.ParserException,
+ self.check_parser, self.cmd, arglist, [])
+
+ def test_credential_set_missing_type(self):
+ arglist = [
+ '--user', identity_fakes.user_name,
+ '--data', self.json_data,
+ identity_fakes.credential_id,
+ ]
+
+ self.assertRaises(utils.ParserException,
+ self.check_parser, self.cmd, arglist, [])
+
+ def test_credential_set_missing_data(self):
+ arglist = [
+ '--user', identity_fakes.user_name,
+ '--type', 'ec2',
+ identity_fakes.credential_id,
+ ]
+
+ self.assertRaises(utils.ParserException,
+ self.check_parser, self.cmd, arglist, [])
+
+ def test_credential_set_valid(self):
+ arglist = [
+ '--user', identity_fakes.user_name,
+ '--type', 'ec2',
+ '--data', self.json_data,
+ identity_fakes.credential_id,
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, [])
+ self.cmd.take_action(parsed_args)
+
+ def test_credential_set_valid_with_project(self):
+ arglist = [
+ '--user', identity_fakes.user_name,
+ '--type', 'ec2',
+ '--data', self.json_data,
+ '--project', identity_fakes.project_name,
+ identity_fakes.credential_id,
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, [])
+ self.cmd.take_action(parsed_args)
diff --git a/openstackclient/tests/identity/v3/test_identity_provider.py b/openstackclient/tests/identity/v3/test_identity_provider.py
index cd328c1..db097d5 100644
--- a/openstackclient/tests/identity/v3/test_identity_provider.py
+++ b/openstackclient/tests/identity/v3/test_identity_provider.py
@@ -69,7 +69,7 @@ class TestIdentityProviderCreate(TestIdentityProvider):
identity_fakes.idp_description,
True,
identity_fakes.idp_id,
- identity_fakes.idp_remote_ids
+ identity_fakes.formatted_idp_remote_ids
)
self.assertEqual(datalist, data)
@@ -103,7 +103,7 @@ class TestIdentityProviderCreate(TestIdentityProvider):
identity_fakes.idp_description,
True,
identity_fakes.idp_id,
- identity_fakes.idp_remote_ids
+ identity_fakes.formatted_idp_remote_ids
)
self.assertEqual(datalist, data)
@@ -137,7 +137,7 @@ class TestIdentityProviderCreate(TestIdentityProvider):
identity_fakes.idp_description,
True,
identity_fakes.idp_id,
- identity_fakes.idp_remote_ids
+ identity_fakes.formatted_idp_remote_ids
)
self.assertEqual(datalist, data)
@@ -172,7 +172,7 @@ class TestIdentityProviderCreate(TestIdentityProvider):
identity_fakes.idp_description,
True,
identity_fakes.idp_id,
- identity_fakes.idp_remote_ids
+ identity_fakes.formatted_idp_remote_ids
)
self.assertEqual(datalist, data)
@@ -211,7 +211,7 @@ class TestIdentityProviderCreate(TestIdentityProvider):
identity_fakes.idp_description,
True,
identity_fakes.idp_id,
- identity_fakes.idp_remote_ids
+ identity_fakes.formatted_idp_remote_ids
)
self.assertEqual(datalist, data)
@@ -253,7 +253,7 @@ class TestIdentityProviderCreate(TestIdentityProvider):
None,
False,
identity_fakes.idp_id,
- identity_fakes.idp_remote_ids
+ identity_fakes.formatted_idp_remote_ids
)
self.assertEqual(datalist, data)
@@ -363,7 +363,7 @@ class TestIdentityProviderShow(TestIdentityProvider):
identity_fakes.idp_description,
True,
identity_fakes.idp_id,
- identity_fakes.idp_remote_ids
+ identity_fakes.formatted_idp_remote_ids
)
self.assertEqual(datalist, data)
@@ -374,6 +374,50 @@ class TestIdentityProviderSet(TestIdentityProvider):
super(TestIdentityProviderSet, self).setUp()
self.cmd = identity_provider.SetIdentityProvider(self.app, None)
+ def test_identity_provider_set_description(self):
+ """Set Identity Provider's description. """
+ def prepare(self):
+ """Prepare fake return objects before the test is executed"""
+ updated_idp = copy.deepcopy(identity_fakes.IDENTITY_PROVIDER)
+ updated_idp['enabled'] = False
+ resources = fakes.FakeResource(
+ None,
+ updated_idp,
+ loaded=True
+ )
+ self.identity_providers_mock.update.return_value = resources
+
+ prepare(self)
+ new_description = 'new desc'
+ arglist = [
+ '--description', new_description,
+ identity_fakes.idp_id
+ ]
+ verifylist = [
+ ('identity_provider', identity_fakes.idp_id),
+ ('description', new_description),
+ ('enable', False),
+ ('disable', False),
+ ('remote_id', None)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ self.identity_providers_mock.update.assert_called_with(
+ identity_fakes.idp_id,
+ description=new_description
+ )
+
+ collist = ('description', 'enabled', 'id', 'remote_ids')
+ self.assertEqual(collist, columns)
+ datalist = (
+ identity_fakes.idp_description,
+ False,
+ identity_fakes.idp_id,
+ identity_fakes.idp_remote_ids
+ )
+ self.assertEqual(datalist, data)
+
def test_identity_provider_disable(self):
"""Disable Identity Provider
@@ -398,6 +442,7 @@ class TestIdentityProviderSet(TestIdentityProvider):
]
verifylist = [
('identity_provider', identity_fakes.idp_id),
+ ('description', None),
('enable', False),
('disable', True),
('remote_id', identity_fakes.idp_remote_ids)
@@ -443,6 +488,7 @@ class TestIdentityProviderSet(TestIdentityProvider):
]
verifylist = [
('identity_provider', identity_fakes.idp_id),
+ ('description', None),
('enable', True),
('disable', False),
('remote_id', identity_fakes.idp_remote_ids)
@@ -488,6 +534,7 @@ class TestIdentityProviderSet(TestIdentityProvider):
]
verifylist = [
('identity_provider', identity_fakes.idp_id),
+ ('description', None),
('enable', True),
('disable', False),
('remote_id', [self.new_remote_id])
@@ -533,6 +580,7 @@ class TestIdentityProviderSet(TestIdentityProvider):
]
verifylist = [
('identity_provider', identity_fakes.idp_id),
+ ('description', None),
('enable', True),
('disable', False),
('remote_id_file', self.new_remote_id),
diff --git a/openstackclient/tests/identity/v3/test_region.py b/openstackclient/tests/identity/v3/test_region.py
index 7f6ced9..0ebbbec 100644
--- a/openstackclient/tests/identity/v3/test_region.py
+++ b/openstackclient/tests/identity/v3/test_region.py
@@ -61,19 +61,17 @@ class TestRegionCreate(TestRegion):
'description': identity_fakes.region_description,
'id': identity_fakes.region_id,
'parent_region': None,
- 'url': None,
}
self.regions_mock.create.assert_called_with(
**kwargs
)
- collist = ('description', 'parent_region', 'region', 'url')
+ collist = ('description', 'parent_region', 'region')
self.assertEqual(collist, columns)
datalist = (
identity_fakes.region_description,
identity_fakes.region_parent_region_id,
identity_fakes.region_id,
- identity_fakes.region_url,
)
self.assertEqual(datalist, data)
@@ -94,19 +92,17 @@ class TestRegionCreate(TestRegion):
'description': None,
'id': identity_fakes.region_id,
'parent_region': None,
- 'url': None,
}
self.regions_mock.create.assert_called_with(
**kwargs
)
- collist = ('description', 'parent_region', 'region', 'url')
+ collist = ('description', 'parent_region', 'region')
self.assertEqual(collist, columns)
datalist = (
identity_fakes.region_description,
identity_fakes.region_parent_region_id,
identity_fakes.region_id,
- identity_fakes.region_url,
)
self.assertEqual(datalist, data)
@@ -129,54 +125,17 @@ class TestRegionCreate(TestRegion):
'description': None,
'id': identity_fakes.region_id,
'parent_region': identity_fakes.region_parent_region_id,
- 'url': None,
}
self.regions_mock.create.assert_called_with(
**kwargs
)
- collist = ('description', 'parent_region', 'region', 'url')
+ collist = ('description', 'parent_region', 'region')
self.assertEqual(collist, columns)
datalist = (
identity_fakes.region_description,
identity_fakes.region_parent_region_id,
identity_fakes.region_id,
- identity_fakes.region_url,
- )
- self.assertEqual(datalist, data)
-
- def test_region_create_url(self):
- arglist = [
- identity_fakes.region_id,
- '--url', identity_fakes.region_url,
- ]
- verifylist = [
- ('region', identity_fakes.region_id),
- ('url', identity_fakes.region_url),
- ]
- parsed_args = self.check_parser(self.cmd, arglist, verifylist)
-
- # DisplayCommandBase.take_action() returns two tuples
- columns, data = self.cmd.take_action(parsed_args)
-
- # Set expected values
- kwargs = {
- 'description': None,
- 'id': identity_fakes.region_id,
- 'parent_region': None,
- 'url': identity_fakes.region_url,
- }
- self.regions_mock.create.assert_called_with(
- **kwargs
- )
-
- collist = ('description', 'parent_region', 'region', 'url')
- self.assertEqual(collist, columns)
- datalist = (
- identity_fakes.region_description,
- identity_fakes.region_parent_region_id,
- identity_fakes.region_id,
- identity_fakes.region_url,
)
self.assertEqual(datalist, data)
@@ -233,13 +192,12 @@ class TestRegionList(TestRegion):
columns, data = self.cmd.take_action(parsed_args)
self.regions_mock.list.assert_called_with()
- collist = ('Region', 'Parent Region', 'Description', 'URL')
+ collist = ('Region', 'Parent Region', 'Description')
self.assertEqual(collist, columns)
datalist = ((
identity_fakes.region_id,
identity_fakes.region_parent_region_id,
identity_fakes.region_description,
- identity_fakes.region_url,
), )
self.assertEqual(datalist, tuple(data))
@@ -257,13 +215,12 @@ class TestRegionList(TestRegion):
self.regions_mock.list.assert_called_with(
parent_region_id=identity_fakes.region_parent_region_id)
- collist = ('Region', 'Parent Region', 'Description', 'URL')
+ collist = ('Region', 'Parent Region', 'Description')
self.assertEqual(collist, columns)
datalist = ((
identity_fakes.region_id,
identity_fakes.region_parent_region_id,
identity_fakes.region_description,
- identity_fakes.region_url,
), )
self.assertEqual(datalist, tuple(data))
@@ -319,29 +276,6 @@ class TestRegionSet(TestRegion):
**kwargs
)
- def test_region_set_url(self):
- arglist = [
- '--url', 'new url',
- identity_fakes.region_id,
- ]
- verifylist = [
- ('url', 'new url'),
- ('region', identity_fakes.region_id),
- ]
- parsed_args = self.check_parser(self.cmd, arglist, verifylist)
-
- result = self.cmd.run(parsed_args)
- self.assertEqual(0, result)
-
- # Set expected values
- kwargs = {
- 'url': 'new url',
- }
- self.regions_mock.update.assert_called_with(
- identity_fakes.region_id,
- **kwargs
- )
-
def test_region_set_parent_region_id(self):
arglist = [
'--parent-region', 'new_parent',
@@ -395,12 +329,11 @@ class TestRegionShow(TestRegion):
identity_fakes.region_id,
)
- collist = ('description', 'parent_region', 'region', 'url')
+ collist = ('description', 'parent_region', 'region')
self.assertEqual(collist, columns)
datalist = (
identity_fakes.region_description,
identity_fakes.region_parent_region_id,
identity_fakes.region_id,
- identity_fakes.region_url,
)
self.assertEqual(datalist, data)
diff --git a/openstackclient/tests/identity/v3/test_role.py b/openstackclient/tests/identity/v3/test_role.py
index 4a0ba06..8ad4b09 100644
--- a/openstackclient/tests/identity/v3/test_role.py
+++ b/openstackclient/tests/identity/v3/test_role.py
@@ -367,6 +367,39 @@ class TestRoleList(TestRole):
), )
self.assertEqual(datalist, tuple(data))
+ def test_user_list_inherited(self):
+ arglist = [
+ '--user', identity_fakes.user_id,
+ '--inherited',
+ ]
+ verifylist = [
+ ('user', identity_fakes.user_id),
+ ('inherited', True),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ # Set expected values
+ kwargs = {
+ 'domain': 'default',
+ 'user': self.users_mock.get(),
+ 'os_inherit_extension_inherited': True,
+ }
+ # RoleManager.list(user=, group=, domain=, project=, **kwargs)
+ self.roles_mock.list.assert_called_with(
+ **kwargs
+ )
+
+ collist = ('ID', 'Name')
+ self.assertEqual(collist, columns)
+ datalist = ((
+ identity_fakes.role_id,
+ identity_fakes.role_name,
+ ), )
+ self.assertEqual(datalist, tuple(data))
+
def test_user_list_user(self):
arglist = [
'--user', identity_fakes.user_id,
@@ -383,6 +416,7 @@ class TestRoleList(TestRole):
kwargs = {
'domain': 'default',
'user': self.users_mock.get(),
+ 'os_inherit_extension_inherited': False
}
# RoleManager.list(user=, group=, domain=, project=, **kwargs)
self.roles_mock.list.assert_called_with(
@@ -415,6 +449,7 @@ class TestRoleList(TestRole):
kwargs = {
'domain': self.domains_mock.get(),
'user': self.users_mock.get(),
+ 'os_inherit_extension_inherited': False
}
# RoleManager.list(user=, group=, domain=, project=, **kwargs)
self.roles_mock.list.assert_called_with(
@@ -449,6 +484,7 @@ class TestRoleList(TestRole):
kwargs = {
'domain': self.domains_mock.get(),
'group': self.groups_mock.get(),
+ 'os_inherit_extension_inherited': False
}
# RoleManager.list(user=, group=, domain=, project=, **kwargs)
self.roles_mock.list.assert_called_with(
@@ -483,6 +519,7 @@ class TestRoleList(TestRole):
kwargs = {
'project': self.projects_mock.get(),
'user': self.users_mock.get(),
+ 'os_inherit_extension_inherited': False
}
# RoleManager.list(user=, group=, domain=, project=, **kwargs)
self.roles_mock.list.assert_called_with(
@@ -517,6 +554,7 @@ class TestRoleList(TestRole):
kwargs = {
'project': self.projects_mock.get(),
'group': self.groups_mock.get(),
+ 'os_inherit_extension_inherited': False
}
# RoleManager.list(user=, group=, domain=, project=, **kwargs)
self.roles_mock.list.assert_called_with(
diff --git a/openstackclient/tests/image/v1/test_image.py b/openstackclient/tests/image/v1/test_image.py
index a79df8b..4d964bd 100644
--- a/openstackclient/tests/image/v1/test_image.py
+++ b/openstackclient/tests/image/v1/test_image.py
@@ -261,16 +261,17 @@ class TestImageList(TestImage):
# DisplayCommandBase.take_action() returns two tuples
columns, data = self.cmd.take_action(parsed_args)
self.api_mock.image_list.assert_called_with(
- detailed=False,
+ detailed=True,
marker=image_fakes.image_id,
)
- collist = ('ID', 'Name')
+ collist = ('ID', 'Name', 'Status')
self.assertEqual(collist, columns)
datalist = ((
image_fakes.image_id,
image_fakes.image_name,
+ '',
), )
self.assertEqual(datalist, tuple(data))
@@ -288,17 +289,18 @@ class TestImageList(TestImage):
# DisplayCommandBase.take_action() returns two tuples
columns, data = self.cmd.take_action(parsed_args)
self.api_mock.image_list.assert_called_with(
- detailed=False,
+ detailed=True,
public=True,
marker=image_fakes.image_id,
)
- collist = ('ID', 'Name')
+ collist = ('ID', 'Name', 'Status')
self.assertEqual(collist, columns)
datalist = ((
image_fakes.image_id,
image_fakes.image_name,
+ '',
), )
self.assertEqual(datalist, tuple(data))
@@ -316,17 +318,18 @@ class TestImageList(TestImage):
# DisplayCommandBase.take_action() returns two tuples
columns, data = self.cmd.take_action(parsed_args)
self.api_mock.image_list.assert_called_with(
- detailed=False,
+ detailed=True,
private=True,
marker=image_fakes.image_id,
)
- collist = ('ID', 'Name')
+ collist = ('ID', 'Name', 'Status')
self.assertEqual(collist, columns)
datalist = ((
image_fakes.image_id,
image_fakes.image_name,
+ '',
), )
self.assertEqual(datalist, tuple(data))
@@ -401,12 +404,13 @@ class TestImageList(TestImage):
property_field='properties',
)
- collist = ('ID', 'Name')
+ collist = ('ID', 'Name', 'Status')
self.assertEqual(columns, collist)
datalist = ((
image_fakes.image_id,
image_fakes.image_name,
+ '',
), )
self.assertEqual(datalist, tuple(data))
@@ -423,7 +427,7 @@ class TestImageList(TestImage):
# DisplayCommandBase.take_action() returns two tuples
columns, data = self.cmd.take_action(parsed_args)
self.api_mock.image_list.assert_called_with(
- detailed=False,
+ detailed=True,
marker=image_fakes.image_id,
)
si_mock.assert_called_with(
@@ -431,12 +435,13 @@ class TestImageList(TestImage):
'name:asc'
)
- collist = ('ID', 'Name')
+ collist = ('ID', 'Name', 'Status')
self.assertEqual(collist, columns)
datalist = ((
image_fakes.image_id,
- image_fakes.image_name
+ image_fakes.image_name,
+ '',
), )
self.assertEqual(datalist, tuple(data))
@@ -499,8 +504,7 @@ class TestImageSet(TestImage):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- # DisplayCommandBase.take_action() returns two tuples
- columns, data = self.cmd.take_action(parsed_args)
+ self.cmd.take_action(parsed_args)
kwargs = {
'name': 'new-name',
@@ -517,9 +521,6 @@ class TestImageSet(TestImage):
**kwargs
)
- self.assertEqual(image_fakes.IMAGE_columns, columns)
- self.assertEqual(image_fakes.IMAGE_data, data)
-
def test_image_set_bools1(self):
arglist = [
'--protected',
@@ -644,8 +645,7 @@ class TestImageSet(TestImage):
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
- # DisplayCommandBase.take_action() returns two tuples
- columns, data = self.cmd.take_action(parsed_args)
+ self.cmd.take_action(parsed_args)
# VolumeManager.upload_to_image(volume, force, image_name,
# container_format, disk_format)
@@ -664,9 +664,6 @@ class TestImageSet(TestImage):
volume='volly',
)
- self.assertEqual(image_fakes.IMAGE_columns, columns)
- self.assertEqual(image_fakes.IMAGE_data, data)
-
class TestImageShow(TestImage):
diff --git a/openstackclient/tests/image/v2/fakes.py b/openstackclient/tests/image/v2/fakes.py
index 1a9e301..11ad455 100644
--- a/openstackclient/tests/image/v2/fakes.py
+++ b/openstackclient/tests/image/v2/fakes.py
@@ -13,6 +13,7 @@
# under the License.
#
+import copy
import mock
from openstackclient.tests import fakes
@@ -25,6 +26,7 @@ image_name = 'graven'
image_owner = 'baal'
image_protected = False
image_visibility = 'public'
+image_tags = []
IMAGE = {
'id': image_id,
@@ -32,11 +34,16 @@ IMAGE = {
'owner': image_owner,
'protected': image_protected,
'visibility': image_visibility,
+ 'tags': image_tags
}
IMAGE_columns = tuple(sorted(IMAGE))
IMAGE_data = tuple((IMAGE[x] for x in sorted(IMAGE)))
+IMAGE_SHOW = copy.copy(IMAGE)
+IMAGE_SHOW['tags'] = ''
+IMAGE_SHOW_data = tuple((IMAGE_SHOW[x] for x in sorted(IMAGE_SHOW)))
+
member_status = 'pending'
MEMBER = {
'member_id': identity_fakes.project_id,
@@ -117,6 +124,14 @@ IMAGE_schema = {
"type": "string",
"description": "Status of the image (READ-ONLY)"
},
+ "tags": {
+ "items": {
+ "type": "string",
+ "maxLength": 255
+ },
+ "type": "array",
+ "description": "List of strings related to the image"
+ },
"visibility": {
"enum": [
"public",
diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py
index 65d5e55..be73c4c 100644
--- a/openstackclient/tests/image/v2/test_image.py
+++ b/openstackclient/tests/image/v2/test_image.py
@@ -96,7 +96,7 @@ class TestImageCreate(TestImage):
)
self.assertEqual(image_fakes.IMAGE_columns, columns)
- self.assertEqual(image_fakes.IMAGE_data, data)
+ self.assertEqual(image_fakes.IMAGE_SHOW_data, data)
@mock.patch('glanceclient.common.utils.get_data_file', name='Open')
def test_image_reserve_options(self, mock_open):
@@ -112,6 +112,7 @@ class TestImageCreate(TestImage):
'--disk-format', 'fs',
'--min-disk', '10',
'--min-ram', '4',
+ '--owner', '123456',
'--protected',
'--private',
image_fakes.image_name,
@@ -121,6 +122,7 @@ class TestImageCreate(TestImage):
('disk_format', 'fs'),
('min_disk', 10),
('min_ram', 4),
+ ('owner', '123456'),
('protected', True),
('unprotected', False),
('public', False),
@@ -139,6 +141,7 @@ class TestImageCreate(TestImage):
disk_format='fs',
min_disk=10,
min_ram=4,
+ owner='123456',
protected=True,
visibility='private',
)
@@ -151,7 +154,7 @@ class TestImageCreate(TestImage):
)
self.assertEqual(image_fakes.IMAGE_columns, columns)
- self.assertEqual(image_fakes.IMAGE_data, data)
+ self.assertEqual(image_fakes.IMAGE_SHOW_data, data)
@mock.patch('glanceclient.common.utils.get_data_file', name='Open')
def test_image_create_file(self, mock_open):
@@ -208,16 +211,15 @@ class TestImageCreate(TestImage):
)
self.assertEqual(image_fakes.IMAGE_columns, columns)
- self.assertEqual(image_fakes.IMAGE_data, data)
+ self.assertEqual(image_fakes.IMAGE_SHOW_data, data)
- def test_image_dead_options(self):
+ def test_image_create_dead_options(self):
arglist = [
- '--owner', 'nobody',
+ '--store', 'somewhere',
image_fakes.image_name,
]
verifylist = [
- ('owner', 'nobody'),
('name', image_fakes.image_name),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -238,10 +240,8 @@ class TestAddProjectToImage(TestImage):
copy.deepcopy(image_fakes.IMAGE),
loaded=True,
)
- self.image_members_mock.create.return_value = fakes.FakeResource(
- None,
+ self.image_members_mock.create.return_value = fakes.FakeModel(
copy.deepcopy(image_fakes.MEMBER),
- loaded=True,
)
self.project_mock.get.return_value = fakes.FakeResource(
None,
@@ -374,12 +374,13 @@ class TestImageList(TestImage):
marker=image_fakes.image_id,
)
- collist = ('ID', 'Name')
+ collist = ('ID', 'Name', 'Status')
self.assertEqual(collist, columns)
datalist = ((
image_fakes.image_id,
image_fakes.image_name,
+ '',
), )
self.assertEqual(datalist, tuple(data))
@@ -402,12 +403,13 @@ class TestImageList(TestImage):
marker=image_fakes.image_id,
)
- collist = ('ID', 'Name')
+ collist = ('ID', 'Name', 'Status')
self.assertEqual(collist, columns)
datalist = ((
image_fakes.image_id,
image_fakes.image_name,
+ '',
), )
self.assertEqual(datalist, tuple(data))
@@ -430,12 +432,13 @@ class TestImageList(TestImage):
marker=image_fakes.image_id,
)
- collist = ('ID', 'Name')
+ collist = ('ID', 'Name', 'Status')
self.assertEqual(collist, columns)
datalist = ((
image_fakes.image_id,
image_fakes.image_name,
+ '',
), )
self.assertEqual(datalist, tuple(data))
@@ -458,12 +461,13 @@ class TestImageList(TestImage):
marker=image_fakes.image_id,
)
- collist = ('ID', 'Name')
+ collist = ('ID', 'Name', 'Status')
self.assertEqual(columns, collist)
datalist = ((
image_fakes.image_id,
image_fakes.image_name,
+ '',
), )
self.assertEqual(datalist, tuple(data))
@@ -536,12 +540,13 @@ class TestImageList(TestImage):
property_field='properties',
)
- collist = ('ID', 'Name')
+ collist = ('ID', 'Name', 'Status')
self.assertEqual(columns, collist)
datalist = ((
image_fakes.image_id,
image_fakes.image_name,
+ '',
), )
self.assertEqual(datalist, tuple(data))
@@ -565,12 +570,13 @@ class TestImageList(TestImage):
'name:asc'
)
- collist = ('ID', 'Name')
+ collist = ('ID', 'Name', 'Status')
self.assertEqual(collist, columns)
datalist = ((
image_fakes.image_id,
- image_fakes.image_name
+ image_fakes.image_name,
+ '',
), )
self.assertEqual(datalist, tuple(data))
@@ -639,11 +645,10 @@ class TestRemoveProjectImage(TestImage):
)
-class TestImageShow(TestImage):
+class TestImageSet(TestImage):
def setUp(self):
- super(TestImageShow, self).setUp()
-
+ super(TestImageSet, self).setUp()
# Set up the schema
self.model = warlock.model_factory(
image_fakes.IMAGE_schema,
@@ -651,33 +656,262 @@ class TestImageShow(TestImage):
)
self.images_mock.get.return_value = self.model(**image_fakes.IMAGE)
-
+ self.images_mock.update.return_value = self.model(**image_fakes.IMAGE)
# Get the command object to test
- self.cmd = image.ShowImage(self.app, None)
+ self.cmd = image.SetImage(self.app, None)
- def test_image_show(self):
+ def test_image_set_options(self):
arglist = [
+ '--name', 'new-name',
+ '--owner', 'new-owner',
+ '--min-disk', '2',
+ '--min-ram', '4',
+ '--container-format', 'ovf',
+ '--disk-format', 'vmdk',
image_fakes.image_id,
]
verifylist = [
+ ('name', 'new-name'),
+ ('owner', 'new-owner'),
+ ('min_disk', 2),
+ ('min_ram', 4),
+ ('container_format', 'ovf'),
+ ('disk_format', 'vmdk'),
('image', image_fakes.image_id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
# DisplayCommandBase.take_action() returns two tuples
- columns, data = self.cmd.take_action(parsed_args)
- self.images_mock.get.assert_called_with(
+ self.cmd.take_action(parsed_args)
+
+ kwargs = {
+ 'name': 'new-name',
+ 'owner': 'new-owner',
+ 'min_disk': 2,
+ 'min_ram': 4,
+ 'container_format': 'ovf',
+ 'disk_format': 'vmdk',
+ }
+ # ImageManager.update(image, **kwargs)
+ self.images_mock.update.assert_called_with(
+ image_fakes.image_id, **kwargs)
+
+ def test_image_set_bools1(self):
+ arglist = [
+ '--protected',
+ '--private',
+ image_fakes.image_name,
+ ]
+ verifylist = [
+ ('protected', True),
+ ('unprotected', False),
+ ('public', False),
+ ('private', True),
+ ('image', image_fakes.image_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ self.cmd.take_action(parsed_args)
+
+ kwargs = {
+ 'protected': True,
+ 'visibility': 'private',
+ }
+ # ImageManager.update(image, **kwargs)
+ self.images_mock.update.assert_called_with(
image_fakes.image_id,
+ **kwargs
)
- self.assertEqual(image_fakes.IMAGE_columns, columns)
- self.assertEqual(image_fakes.IMAGE_data, data)
+ def test_image_set_bools2(self):
+ arglist = [
+ '--unprotected',
+ '--public',
+ image_fakes.image_name,
+ ]
+ verifylist = [
+ ('protected', False),
+ ('unprotected', True),
+ ('public', True),
+ ('private', False),
+ ('image', image_fakes.image_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ # DisplayCommandBase.take_action() returns two tuples
+ self.cmd.take_action(parsed_args)
-class TestImageSet(TestImage):
+ kwargs = {
+ 'protected': False,
+ 'visibility': 'public',
+ }
+ # ImageManager.update(image, **kwargs)
+ self.images_mock.update.assert_called_with(
+ image_fakes.image_id,
+ **kwargs
+ )
+
+ def test_image_set_properties(self):
+ arglist = [
+ '--property', 'Alpha=1',
+ '--property', 'Beta=2',
+ image_fakes.image_name,
+ ]
+ verifylist = [
+ ('properties', {'Alpha': '1', 'Beta': '2'}),
+ ('image', image_fakes.image_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ self.cmd.take_action(parsed_args)
+
+ kwargs = {
+ 'Alpha': '1',
+ 'Beta': '2',
+ }
+ # ImageManager.update(image, **kwargs)
+ self.images_mock.update.assert_called_with(
+ image_fakes.image_id,
+ **kwargs
+ )
+
+ def test_image_set_fake_properties(self):
+ arglist = [
+ '--architecture', 'z80',
+ '--instance-id', '12345',
+ '--kernel-id', '67890',
+ '--os-distro', 'cpm',
+ '--os-version', '2.2H',
+ '--ramdisk-id', 'xyzpdq',
+ image_fakes.image_name,
+ ]
+ verifylist = [
+ ('architecture', 'z80'),
+ ('instance_id', '12345'),
+ ('kernel_id', '67890'),
+ ('os_distro', 'cpm'),
+ ('os_version', '2.2H'),
+ ('ramdisk_id', 'xyzpdq'),
+ ('image', image_fakes.image_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ self.cmd.take_action(parsed_args)
+
+ kwargs = {
+ 'architecture': 'z80',
+ 'instance_id': '12345',
+ 'kernel_id': '67890',
+ 'os_distro': 'cpm',
+ 'os_version': '2.2H',
+ 'ramdisk_id': 'xyzpdq',
+ }
+ # ImageManager.update(image, **kwargs)
+ self.images_mock.update.assert_called_with(
+ image_fakes.image_id,
+ **kwargs
+ )
+
+ def test_image_set_tag(self):
+ arglist = [
+ '--tag', 'test-tag',
+ image_fakes.image_name,
+ ]
+ verifylist = [
+ ('tags', ['test-tag']),
+ ('image', image_fakes.image_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ self.cmd.take_action(parsed_args)
+
+ kwargs = {
+ 'tags': ['test-tag'],
+ }
+ # ImageManager.update(image, **kwargs)
+ self.images_mock.update.assert_called_with(
+ image_fakes.image_id,
+ **kwargs
+ )
+
+ def test_image_set_tag_merge(self):
+ old_image = copy.copy(image_fakes.IMAGE)
+ old_image['tags'] = ['old1', 'new2']
+ self.images_mock.get.return_value = self.model(**old_image)
+ arglist = [
+ '--tag', 'test-tag',
+ image_fakes.image_name,
+ ]
+ verifylist = [
+ ('tags', ['test-tag']),
+ ('image', image_fakes.image_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ self.cmd.take_action(parsed_args)
+
+ kwargs = {
+ 'tags': ['old1', 'new2', 'test-tag'],
+ }
+ # ImageManager.update(image, **kwargs)
+ a, k = self.images_mock.update.call_args
+ self.assertEqual(image_fakes.image_id, a[0])
+ self.assertTrue('tags' in k)
+ self.assertEqual(set(kwargs['tags']), set(k['tags']))
+
+ def test_image_set_tag_merge_dupe(self):
+ old_image = copy.copy(image_fakes.IMAGE)
+ old_image['tags'] = ['old1', 'new2']
+ self.images_mock.get.return_value = self.model(**old_image)
+ arglist = [
+ '--tag', 'old1',
+ image_fakes.image_name,
+ ]
+ verifylist = [
+ ('tags', ['old1']),
+ ('image', image_fakes.image_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ self.cmd.take_action(parsed_args)
+
+ kwargs = {
+ 'tags': ['new2', 'old1'],
+ }
+ # ImageManager.update(image, **kwargs)
+ a, k = self.images_mock.update.call_args
+ self.assertEqual(image_fakes.image_id, a[0])
+ self.assertTrue('tags' in k)
+ self.assertEqual(set(kwargs['tags']), set(k['tags']))
+
+ def test_image_set_dead_options(self):
+
+ arglist = [
+ '--visibility', '1-mile',
+ image_fakes.image_name,
+ ]
+ verifylist = [
+ ('visibility', '1-mile'),
+ ('image', image_fakes.image_name),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action, parsed_args)
+
+
+class TestImageShow(TestImage):
def setUp(self):
- super(TestImageSet, self).setUp()
+ super(TestImageShow, self).setUp()
+
# Set up the schema
self.model = warlock.model_factory(
image_fakes.IMAGE_schema,
@@ -685,39 +919,24 @@ class TestImageSet(TestImage):
)
self.images_mock.get.return_value = self.model(**image_fakes.IMAGE)
- self.images_mock.update.return_value = self.model(**image_fakes.IMAGE)
+
# Get the command object to test
- self.cmd = image.SetImage(self.app, None)
+ self.cmd = image.ShowImage(self.app, None)
- def test_image_set_options(self):
+ def test_image_show(self):
arglist = [
- '--name', 'new-name',
- '--owner', 'new-owner',
- '--min-disk', '2',
- '--min-ram', '4',
image_fakes.image_id,
]
verifylist = [
- ('name', 'new-name'),
- ('owner', 'new-owner'),
- ('min_disk', 2),
- ('min_ram', 4),
('image', image_fakes.image_id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
# DisplayCommandBase.take_action() returns two tuples
columns, data = self.cmd.take_action(parsed_args)
-
- kwargs = {
- 'name': 'new-name',
- 'owner': 'new-owner',
- 'min_disk': 2,
- 'min_ram': 4
- }
- # ImageManager.update(image, **kwargs)
- self.images_mock.update.assert_called_with(
- image_fakes.image_id, **kwargs)
+ self.images_mock.get.assert_called_with(
+ image_fakes.image_id,
+ )
self.assertEqual(image_fakes.IMAGE_columns, columns)
- self.assertEqual(image_fakes.IMAGE_data, data)
+ self.assertEqual(image_fakes.IMAGE_SHOW_data, data)
diff --git a/openstackclient/tests/object/v1/fakes.py b/openstackclient/tests/object/v1/fakes.py
index 6aef05b..986ab2f 100644
--- a/openstackclient/tests/object/v1/fakes.py
+++ b/openstackclient/tests/object/v1/fakes.py
@@ -13,7 +13,7 @@
# under the License.
#
-from keystoneclient import session
+from keystoneauth1 import session
from openstackclient.api import object_store_v1 as object_store
from openstackclient.tests import utils
diff --git a/openstackclient/tests/object/v1/test_container_all.py b/openstackclient/tests/object/v1/test_container_all.py
index 8b200e0..69fc0f3 100644
--- a/openstackclient/tests/object/v1/test_container_all.py
+++ b/openstackclient/tests/object/v1/test_container_all.py
@@ -286,7 +286,6 @@ class TestContainerShow(TestContainerAll):
def test_object_show_container(self):
headers = {
- 'x-container-meta-owner': object_fakes.ACCOUNT_ID,
'x-container-object-count': '42',
'x-container-bytes-used': '123',
'x-container-read': 'qaz',
diff --git a/openstackclient/tests/volume/v1/test_volume.py b/openstackclient/tests/volume/v1/test_volume.py
index f73260e..70ff50d 100644
--- a/openstackclient/tests/volume/v1/test_volume.py
+++ b/openstackclient/tests/volume/v1/test_volume.py
@@ -527,6 +527,188 @@ class TestVolumeCreate(TestVolume):
self.assertEqual(datalist, data)
+class TestVolumeList(TestVolume):
+
+ def setUp(self):
+ super(TestVolumeList, self).setUp()
+
+ self.volumes_mock.list.return_value = [
+ fakes.FakeResource(
+ None,
+ copy.deepcopy(volume_fakes.VOLUME),
+ loaded=True,
+ ),
+ ]
+
+ # Get the command object to test
+ self.cmd = volume.ListVolume(self.app, None)
+
+ def test_volume_list_no_options(self):
+ arglist = []
+ verifylist = [
+ ('long', False),
+ ('all_projects', False),
+ ('name', None),
+ ('status', None),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ collist = (
+ 'ID',
+ 'Display Name',
+ 'Status',
+ 'Size',
+ 'Attached to',
+ )
+ self.assertEqual(collist, columns)
+
+ datalist = ((
+ volume_fakes.volume_id,
+ volume_fakes.volume_name,
+ volume_fakes.volume_status,
+ volume_fakes.volume_size,
+ '',
+ ), )
+ self.assertEqual(datalist, tuple(data))
+
+ def test_volume_list_name(self):
+ arglist = [
+ '--name', volume_fakes.volume_name,
+ ]
+ verifylist = [
+ ('long', False),
+ ('all_projects', False),
+ ('name', volume_fakes.volume_name),
+ ('status', None),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ collist = (
+ 'ID',
+ 'Display Name',
+ 'Status',
+ 'Size',
+ 'Attached to',
+ )
+ self.assertEqual(collist, tuple(columns))
+
+ datalist = ((
+ volume_fakes.volume_id,
+ volume_fakes.volume_name,
+ volume_fakes.volume_status,
+ volume_fakes.volume_size,
+ '',
+ ), )
+ self.assertEqual(datalist, tuple(data))
+
+ def test_volume_list_status(self):
+ arglist = [
+ '--status', volume_fakes.volume_status,
+ ]
+ verifylist = [
+ ('long', False),
+ ('all_projects', False),
+ ('name', None),
+ ('status', volume_fakes.volume_status),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ collist = (
+ 'ID',
+ 'Display Name',
+ 'Status',
+ 'Size',
+ 'Attached to',
+ )
+ self.assertEqual(collist, tuple(columns))
+
+ datalist = ((
+ volume_fakes.volume_id,
+ volume_fakes.volume_name,
+ volume_fakes.volume_status,
+ volume_fakes.volume_size,
+ '',
+ ), )
+ self.assertEqual(datalist, tuple(data))
+
+ def test_volume_list_all_projects(self):
+ arglist = [
+ '--all-projects',
+ ]
+ verifylist = [
+ ('long', False),
+ ('all_projects', True),
+ ('name', None),
+ ('status', None),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ collist = (
+ 'ID',
+ 'Display Name',
+ 'Status',
+ 'Size',
+ 'Attached to',
+ )
+ self.assertEqual(collist, columns)
+
+ datalist = ((
+ volume_fakes.volume_id,
+ volume_fakes.volume_name,
+ volume_fakes.volume_status,
+ volume_fakes.volume_size,
+ '',
+ ), )
+ self.assertEqual(datalist, tuple(data))
+
+ def test_volume_list_long(self):
+ arglist = [
+ '--long',
+ ]
+ verifylist = [
+ ('long', True),
+ ('all_projects', False),
+ ('name', None),
+ ('status', None),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ collist = (
+ 'ID',
+ 'Display Name',
+ 'Status',
+ 'Size',
+ 'Type',
+ 'Bootable',
+ 'Attached to',
+ 'Properties',
+ )
+ self.assertEqual(collist, columns)
+
+ datalist = ((
+ volume_fakes.volume_id,
+ volume_fakes.volume_name,
+ volume_fakes.volume_status,
+ volume_fakes.volume_size,
+ volume_fakes.volume_type,
+ '',
+ '',
+ "Alpha='a', Beta='b', Gamma='g'",
+ ), )
+ self.assertEqual(datalist, tuple(data))
+
+
class TestVolumeSet(TestVolume):
def setUp(self):
diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py
index 7b7758a..60cec33 100644
--- a/openstackclient/tests/volume/v2/fakes.py
+++ b/openstackclient/tests/volume/v2/fakes.py
@@ -14,9 +14,11 @@
import copy
import mock
+import random
+import uuid
from openstackclient.tests import fakes
-from openstackclient.tests.identity.v2_0 import fakes as identity_fakes
+from openstackclient.tests.identity.v3 import fakes as identity_fakes
from openstackclient.tests.image.v2 import fakes as image_fakes
from openstackclient.tests import utils
@@ -212,7 +214,7 @@ class TestVolume(utils.TestCommand):
endpoint=fakes.AUTH_URL,
token=fakes.AUTH_TOKEN
)
- self.app.client_manager.identity = identity_fakes.FakeIdentityv2Client(
+ self.app.client_manager.identity = identity_fakes.FakeIdentityv3Client(
endpoint=fakes.AUTH_URL,
token=fakes.AUTH_TOKEN
)
@@ -220,3 +222,85 @@ class TestVolume(utils.TestCommand):
endpoint=fakes.AUTH_URL,
token=fakes.AUTH_TOKEN
)
+
+
+class FakeVolume(object):
+ """Fake one or more volumes.
+
+ TODO(xiexs): Currently, only volume API v2 is supported by this class.
+ """
+
+ @staticmethod
+ def create_one_volume(attrs={}):
+ """Create a fake volume.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes of volume
+ :retrun:
+ A FakeResource object with id, name, status, etc.
+ """
+ # Set default attribute
+ volume_info = {
+ 'id': 'volume-id' + uuid.uuid4().hex,
+ 'name': 'volume-name' + uuid.uuid4().hex,
+ 'description': 'description' + uuid.uuid4().hex,
+ 'status': random.choice(['available', 'in_use']),
+ 'size': random.randint(1, 20),
+ 'volume_type':
+ random.choice(['fake_lvmdriver-1', 'fake_lvmdriver-2']),
+ 'metadata': {
+ 'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex,
+ 'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex,
+ 'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex},
+ 'snapshot_id': random.randint(1, 5),
+ 'availability_zone': 'zone' + uuid.uuid4().hex,
+ 'attachments': {
+ 'device': '/dev/' + uuid.uuid4().hex,
+ 'server_id': uuid.uuid4().hex},
+ }
+
+ # Overwrite default attributes if there are some attributes set
+ volume_info.update(attrs)
+
+ volume = fakes.FakeResource(
+ None,
+ volume_info,
+ loaded=True)
+ return volume
+
+ @staticmethod
+ def create_volumes(attrs={}, count=2):
+ """Create multiple fake volumes.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes of volume
+ :param Integer count:
+ The number of volumes to be faked
+ :return:
+ A list of FakeResource objects
+ """
+ volumes = []
+ for n in range(0, count):
+ volumes.append(FakeVolume.create_one_volume(attrs))
+
+ return volumes
+
+ @staticmethod
+ def get_volumes(volumes=None, count=2):
+ """Get an iterable MagicMock object with a list of faked volumes.
+
+ If volumes list is provided, then initialize the Mock object with the
+ list. Otherwise create one.
+
+ :param List volumes:
+ A list of FakeResource objects faking volumes
+ :param Integer count:
+ The number of volumes to be faked
+ :return
+ An iterable Mock object with side_effect set to a list of faked
+ volumes
+ """
+ if volumes is None:
+ volumes = FakeVolume.create_volumes(count)
+
+ return mock.MagicMock(side_effect=volumes)
diff --git a/openstackclient/tests/volume/v2/test_snapshot.py b/openstackclient/tests/volume/v2/test_snapshot.py
index 3ceb57f..3b30d4e 100644
--- a/openstackclient/tests/volume/v2/test_snapshot.py
+++ b/openstackclient/tests/volume/v2/test_snapshot.py
@@ -225,6 +225,7 @@ class TestSnapshotList(TestSnapshot):
def test_snapshot_list_without_options(self):
arglist = []
verifylist = [
+ ('all_projects', False),
("long", False)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -243,7 +244,7 @@ class TestSnapshotList(TestSnapshot):
def test_snapshot_list_with_options(self):
arglist = ["--long"]
- verifylist = [("long", True)]
+ verifylist = [("long", True), ('all_projects', False)]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
@@ -263,3 +264,33 @@ class TestSnapshotList(TestSnapshot):
volume_fakes.EXPECTED_SNAPSHOT.get("properties")
),)
self.assertEqual(datalist, tuple(data))
+
+ def test_snapshot_list_all_projects(self):
+ arglist = [
+ '--all-projects',
+ ]
+ verifylist = [
+ ('long', False),
+ ('all_projects', True)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ collist = [
+ "ID",
+ "Name",
+ "Description",
+ "Status",
+ "Size"
+ ]
+ self.assertEqual(collist, columns)
+
+ datalist = ((
+ volume_fakes.snapshot_id,
+ volume_fakes.snapshot_name,
+ volume_fakes.snapshot_description,
+ "available",
+ volume_fakes.snapshot_size
+ ), )
+ self.assertEqual(datalist, tuple(data))
diff --git a/openstackclient/tests/volume/v2/test_type.py b/openstackclient/tests/volume/v2/test_type.py
index 9a07263..c63cd1f 100644
--- a/openstackclient/tests/volume/v2/test_type.py
+++ b/openstackclient/tests/volume/v2/test_type.py
@@ -73,7 +73,7 @@ class TestTypeCreate(TestType):
self.types_mock.create.assert_called_with(
volume_fakes.type_name,
description=volume_fakes.type_description,
- public=True,
+ is_public=True,
)
collist = (
@@ -93,7 +93,7 @@ class TestTypeCreate(TestType):
arglist = [
volume_fakes.type_name,
"--description", volume_fakes.type_description,
- "--private"
+ "--private",
]
verifylist = [
("name", volume_fakes.type_name),
@@ -107,7 +107,7 @@ class TestTypeCreate(TestType):
self.types_mock.create.assert_called_with(
volume_fakes.type_name,
description=volume_fakes.type_description,
- private=True,
+ is_public=False,
)
collist = (
diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/volume/v2/test_volume.py
index b15fd02..70324b6 100644
--- a/openstackclient/tests/volume/v2/test_volume.py
+++ b/openstackclient/tests/volume/v2/test_volume.py
@@ -15,7 +15,7 @@
import copy
from openstackclient.tests import fakes
-from openstackclient.tests.identity.v2_0 import fakes as identity_fakes
+from openstackclient.tests.identity.v3 import fakes as identity_fakes
from openstackclient.tests.volume.v2 import fakes as volume_fakes
from openstackclient.volume.v2 import volume
@@ -27,7 +27,7 @@ class TestVolume(volume_fakes.TestVolume):
self.volumes_mock = self.app.client_manager.volume.volumes
self.volumes_mock.reset_mock()
- self.projects_mock = self.app.client_manager.identity.tenants
+ self.projects_mock = self.app.client_manager.identity.projects
self.projects_mock.reset_mock()
self.users_mock = self.app.client_manager.identity.users
@@ -560,32 +560,145 @@ class TestVolumeList(TestVolume):
), )
self.assertEqual(datalist, tuple(data))
- def test_volume_list_all_projects_option(self):
+ def test_volume_list_project(self):
arglist = [
- '--all-projects',
+ '--project', identity_fakes.project_name,
]
verifylist = [
+ ('project', identity_fakes.project_name),
('long', False),
- ('all_projects', True),
- ('name', None),
+ ('all_projects', False),
('status', None),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
- collist = [
+ collist = (
'ID',
'Display Name',
'Status',
'Size',
'Attached to',
+ )
+ self.assertEqual(collist, tuple(columns))
+
+ server = volume_fakes.volume_attachment_server['server_id']
+ device = volume_fakes.volume_attachment_server['device']
+ msg = 'Attached to %s on %s ' % (server, device)
+
+ datalist = ((
+ volume_fakes.volume_id,
+ volume_fakes.volume_name,
+ volume_fakes.volume_status,
+ volume_fakes.volume_size,
+ msg,
+ ), )
+ self.assertEqual(datalist, tuple(data))
+
+ def test_volume_list_project_domain(self):
+ arglist = [
+ '--project', identity_fakes.project_name,
+ '--project-domain', identity_fakes.domain_name,
]
- self.assertEqual(collist, columns)
+ verifylist = [
+ ('project', identity_fakes.project_name),
+ ('project_domain', identity_fakes.domain_name),
+ ('long', False),
+ ('all_projects', False),
+ ('status', None),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ collist = (
+ 'ID',
+ 'Display Name',
+ 'Status',
+ 'Size',
+ 'Attached to',
+ )
+ self.assertEqual(collist, tuple(columns))
+
+ server = volume_fakes.volume_attachment_server['server_id']
+ device = volume_fakes.volume_attachment_server['device']
+ msg = 'Attached to %s on %s ' % (server, device)
+
+ datalist = ((
+ volume_fakes.volume_id,
+ volume_fakes.volume_name,
+ volume_fakes.volume_status,
+ volume_fakes.volume_size,
+ msg,
+ ), )
+ self.assertEqual(datalist, tuple(data))
+
+ def test_volume_list_user(self):
+ arglist = [
+ '--user', identity_fakes.user_name,
+ ]
+ verifylist = [
+ ('user', identity_fakes.user_name),
+ ('long', False),
+ ('all_projects', False),
+ ('status', None),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ collist = (
+ 'ID',
+ 'Display Name',
+ 'Status',
+ 'Size',
+ 'Attached to',
+ )
+ self.assertEqual(collist, tuple(columns))
server = volume_fakes.volume_attachment_server['server_id']
device = volume_fakes.volume_attachment_server['device']
msg = 'Attached to %s on %s ' % (server, device)
+
+ datalist = ((
+ volume_fakes.volume_id,
+ volume_fakes.volume_name,
+ volume_fakes.volume_status,
+ volume_fakes.volume_size,
+ msg,
+ ), )
+ self.assertEqual(datalist, tuple(data))
+
+ def test_volume_list_user_domain(self):
+ arglist = [
+ '--user', identity_fakes.user_name,
+ '--user-domain', identity_fakes.domain_name,
+ ]
+ verifylist = [
+ ('user', identity_fakes.user_name),
+ ('user_domain', identity_fakes.domain_name),
+ ('long', False),
+ ('all_projects', False),
+ ('status', None),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ collist = (
+ 'ID',
+ 'Display Name',
+ 'Status',
+ 'Size',
+ 'Attached to',
+ )
+ self.assertEqual(collist, tuple(columns))
+
+ server = volume_fakes.volume_attachment_server['server_id']
+ device = volume_fakes.volume_attachment_server['device']
+ msg = 'Attached to %s on %s ' % (server, device)
+
datalist = ((
volume_fakes.volume_id,
volume_fakes.volume_name,
@@ -666,6 +779,41 @@ class TestVolumeList(TestVolume):
), )
self.assertEqual(datalist, tuple(data))
+ def test_volume_list_all_projects(self):
+ arglist = [
+ '--all-projects',
+ ]
+ verifylist = [
+ ('long', False),
+ ('all_projects', True),
+ ('name', None),
+ ('status', None),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ collist = [
+ 'ID',
+ 'Display Name',
+ 'Status',
+ 'Size',
+ 'Attached to',
+ ]
+ self.assertEqual(collist, columns)
+
+ server = volume_fakes.volume_attachment_server['server_id']
+ device = volume_fakes.volume_attachment_server['device']
+ msg = 'Attached to %s on %s ' % (server, device)
+ datalist = ((
+ volume_fakes.volume_id,
+ volume_fakes.volume_name,
+ volume_fakes.volume_status,
+ volume_fakes.volume_size,
+ msg,
+ ), )
+ self.assertEqual(datalist, tuple(data))
+
def test_volume_list_long(self):
arglist = [
'--long',
diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py
index de7bb5b..93e17eb 100644
--- a/openstackclient/volume/v1/snapshot.py
+++ b/openstackclient/volume/v1/snapshot.py
@@ -111,6 +111,12 @@ class ListSnapshot(lister.Lister):
def get_parser(self, prog_name):
parser = super(ListSnapshot, self).get_parser(prog_name)
parser.add_argument(
+ '--all-projects',
+ action='store_true',
+ default=False,
+ help='Include all projects (admin only)',
+ )
+ parser.add_argument(
'--long',
action='store_true',
default=False,
@@ -157,7 +163,12 @@ class ListSnapshot(lister.Lister):
# Just forget it if there's any trouble
pass
- data = self.app.client_manager.volume.volume_snapshots.list()
+ search_opts = {
+ 'all_tenants': parsed_args.all_projects,
+ }
+
+ data = self.app.client_manager.volume.volume_snapshots.list(
+ search_opts=search_opts)
return (column_headers,
(utils.get_item_properties(
s, columns,
diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py
index 52b0eb2..0691d88 100644
--- a/openstackclient/volume/v1/volume.py
+++ b/openstackclient/volume/v1/volume.py
@@ -207,16 +207,16 @@ class ListVolume(lister.Lister):
def get_parser(self, prog_name):
parser = super(ListVolume, self).get_parser(prog_name)
parser.add_argument(
+ '--name',
+ metavar='<name>',
+ help='Filter results by volume name',
+ )
+ parser.add_argument(
'--status',
metavar='<status>',
help='Filter results by status',
)
parser.add_argument(
- '--name',
- metavar='<name>',
- help='Filter results by name',
- )
- parser.add_argument(
'--all-projects',
action='store_true',
default=False,
@@ -239,7 +239,7 @@ class ListVolume(lister.Lister):
def _format_attach(attachments):
"""Return a formatted string of a volume's attached instances
- :param volume: a volume.attachments field
+ :param attachments: a volume.attachments field
:rtype: a string of formatted instances
"""
diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py
index bbc92c4..aa7630a 100644
--- a/openstackclient/volume/v2/snapshot.py
+++ b/openstackclient/volume/v2/snapshot.py
@@ -108,6 +108,12 @@ class ListSnapshot(lister.Lister):
def get_parser(self, prog_name):
parser = super(ListSnapshot, self).get_parser(prog_name)
parser.add_argument(
+ '--all-projects',
+ action='store_true',
+ default=False,
+ help='Include all projects (admin only)',
+ )
+ parser.add_argument(
'--long',
action='store_true',
default=False,
@@ -149,7 +155,12 @@ class ListSnapshot(lister.Lister):
# Just forget it if there's any trouble
pass
- data = self.app.client_manager.volume.volume_snapshots.list()
+ search_opts = {
+ 'all_tenants': parsed_args.all_projects,
+ }
+
+ data = self.app.client_manager.volume.volume_snapshots.list(
+ search_opts=search_opts)
return (column_headers,
(utils.get_item_properties(
s, columns,
diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py
index ad6215e..925b01d 100644
--- a/openstackclient/volume/v2/volume.py
+++ b/openstackclient/volume/v2/volume.py
@@ -16,7 +16,6 @@
import copy
import logging
-import os
from cliff import command
from cliff import lister
@@ -25,6 +24,7 @@ import six
from openstackclient.common import parseractions
from openstackclient.common import utils
+from openstackclient.identity import common as identity_common
class CreateVolume(show.ShowOne):
@@ -175,7 +175,7 @@ class DeleteVolume(command.Command):
action="store_true",
default=False,
help="Attempt forced removal of volume(s), regardless of state "
- "(defaults to False"
+ "(defaults to False)"
)
return parser
@@ -185,10 +185,10 @@ class DeleteVolume(command.Command):
for volume in parsed_args.volumes:
volume_obj = utils.find_resource(
volume_client.volumes, volume)
- if parsed_args.force:
- volume_client.volumes.force_delete(volume_obj.id)
- else:
- volume_client.volumes.delete(volume_obj.id)
+ if parsed_args.force:
+ volume_client.volumes.force_delete(volume_obj.id)
+ else:
+ volume_client.volumes.delete(volume_obj.id)
return
@@ -200,27 +200,39 @@ class ListVolume(lister.Lister):
def get_parser(self, prog_name):
parser = super(ListVolume, self).get_parser(prog_name)
parser.add_argument(
- '--all-projects',
- action='store_true',
- default=bool(int(os.environ.get("ALL_PROJECTS", 0))),
- help='Include all projects (admin only)',
+ '--project',
+ metavar='<project-id>',
+ help='Filter results by project (name or ID) (admin only)'
)
+ identity_common.add_project_domain_option_to_parser(parser)
parser.add_argument(
- '--long',
- action='store_true',
- default=False,
- help='List additional fields in output',
+ '--user',
+ metavar='<user-id>',
+ help='Filter results by user (name or ID) (admin only)'
)
+ identity_common.add_user_domain_option_to_parser(parser)
parser.add_argument(
'--name',
metavar='<name>',
- help='Filter results by name',
+ help='Filter results by volume name',
)
parser.add_argument(
'--status',
metavar='<status>',
help='Filter results by status',
)
+ parser.add_argument(
+ '--all-projects',
+ action='store_true',
+ default=False,
+ help='Include all projects (admin only)',
+ )
+ parser.add_argument(
+ '--long',
+ action='store_true',
+ default=False,
+ help='List additional fields in output',
+ )
return parser
@utils.log_method(log)
@@ -228,11 +240,12 @@ class ListVolume(lister.Lister):
volume_client = self.app.client_manager.volume
compute_client = self.app.client_manager.compute
+ identity_client = self.app.client_manager.identity
def _format_attach(attachments):
"""Return a formatted string of a volume's attached instances
- :param volume: a volume.attachments field
+ :param attachments: a volume.attachments field
:rtype: a string of formatted instances
"""
@@ -282,8 +295,23 @@ class ListVolume(lister.Lister):
# Just forget it if there's any trouble
pass
+ project_id = None
+ if parsed_args.project:
+ project_id = identity_common.find_project(
+ identity_client,
+ parsed_args.project,
+ parsed_args.project_domain)
+
+ user_id = None
+ if parsed_args.user:
+ user_id = identity_common.find_user(identity_client,
+ parsed_args.user,
+ parsed_args.user_domain)
+
search_opts = {
- 'all_projects': parsed_args.all_projects,
+ 'all_tenants': parsed_args.all_projects,
+ 'project_id': project_id,
+ 'user_id': user_id,
'display_name': parsed_args.name,
'status': parsed_args.status,
}
diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py
index 8cca86f..583e6ed 100644
--- a/openstackclient/volume/v2/volume_type.py
+++ b/openstackclient/volume/v2/volume_type.py
@@ -73,9 +73,9 @@ class CreateVolumeType(show.ShowOne):
kwargs = {}
if parsed_args.public:
- kwargs['public'] = True
+ kwargs['is_public'] = True
if parsed_args.private:
- kwargs['private'] = True
+ kwargs['is_public'] = False
volume_type = volume_client.volume_types.create(
parsed_args.name,
diff --git a/post_test_hook.sh b/post_test_hook.sh
index 4c35b52..7bb036f 100755
--- a/post_test_hook.sh
+++ b/post_test_hook.sh
@@ -12,7 +12,7 @@ OPENSTACKCLIENT_DIR=$(cd $(dirname "$0") && pwd)
echo "Running openstackclient functional test suite"
sudo -H -u stack -i <<!
-source ~stack/devstack/accrc/admin/admin
+source ~stack/devstack/openrc admin admin
echo 'Running tests with:'
env | grep OS_
cd ${OPENSTACKCLIENT_DIR}
diff --git a/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po b/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po
index 6758943..983abdb 100644
--- a/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po
+++ b/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po
@@ -9,18 +9,18 @@
# OpenStack Infra <zanata@openstack.org>, 2015. #zanata
msgid ""
msgstr ""
-"Project-Id-Version: python-openstackclient 1.6.1.dev56\n"
+"Project-Id-Version: python-openstackclient 1.8.1.dev15\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2015-09-21 06:09+0000\n"
+"POT-Creation-Date: 2015-11-04 06:04+0000\n"
"PO-Revision-Date: 2015-09-19 07:10+0000\n"
"Last-Translator: Andreas Jaeger <jaegerandi@gmail.com>\n"
-"Language-Team: German (http://www.transifex.com/openstack/python-"
-"openstackclient/language/de/)\n"
+"Language: de\n"
+"Language-Team: German\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 2.0\n"
+"Generated-By: Babel 2.1.1\n"
msgid ""
"\n"
@@ -279,9 +279,6 @@ msgstr "Neue Regionskennung"
msgid "New region description"
msgstr "Beschreibung des neuen Bereichs"
-msgid "New region url"
-msgstr "URL des neuen Bereichs"
-
msgid "New role name"
msgstr "Name der neuen Rolle"
diff --git a/python-openstackclient/locale/python-openstackclient.pot b/python-openstackclient/locale/python-openstackclient.pot
index f613a5a..cc4f1a1 100644
--- a/python-openstackclient/locale/python-openstackclient.pot
+++ b/python-openstackclient/locale/python-openstackclient.pot
@@ -7,16 +7,16 @@
#, fuzzy
msgid ""
msgstr ""
-"Project-Id-Version: python-openstackclient 1.6.1.dev43\n"
+"Project-Id-Version: python-openstackclient 1.8.1.dev15\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2015-09-11 06:10+0000\n"
+"POT-Creation-Date: 2015-11-04 06:04+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 2.0\n"
+"Generated-By: Babel 2.1.1\n"
#: openstackclient/api/auth.py:144
msgid "Set a username with --os-username, OS_USERNAME, or auth.username\n"
@@ -756,32 +756,27 @@ msgid "Parent region ID"
msgstr ""
#: openstackclient/identity/v3/region.py:49
-#: openstackclient/identity/v3/region.py:151
+#: openstackclient/identity/v3/region.py:144
msgid "New region description"
msgstr ""
-#: openstackclient/identity/v3/region.py:54
-#: openstackclient/identity/v3/region.py:156
-msgid "New region url"
-msgstr ""
-
-#: openstackclient/identity/v3/region.py:86
+#: openstackclient/identity/v3/region.py:79
msgid "Region ID to delete"
msgstr ""
-#: openstackclient/identity/v3/region.py:108
+#: openstackclient/identity/v3/region.py:101
msgid "Filter by parent region ID"
msgstr ""
-#: openstackclient/identity/v3/region.py:141
+#: openstackclient/identity/v3/region.py:134
msgid "Region to modify"
msgstr ""
-#: openstackclient/identity/v3/region.py:146
+#: openstackclient/identity/v3/region.py:139
msgid "New parent region ID"
msgstr ""
-#: openstackclient/identity/v3/region.py:191
+#: openstackclient/identity/v3/region.py:175
msgid "Region to display"
msgstr ""
diff --git a/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po b/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po
index 0680a23..301912c 100644
--- a/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po
+++ b/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po
@@ -4,20 +4,21 @@
# python-openstackclient project.
#
# Translators:
+# OpenStack Infra <zanata@openstack.org>, 2015. #zanata
msgid ""
msgstr ""
-"Project-Id-Version: python-openstackclient\n"
+"Project-Id-Version: python-openstackclient 1.8.1.dev15\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2015-08-04 06:26+0000\n"
-"PO-Revision-Date: 2015-06-14 18:41+0000\n"
+"POT-Creation-Date: 2015-11-04 06:04+0000\n"
+"PO-Revision-Date: 2015-06-14 06:41+0000\n"
"Last-Translator: openstackjenkins <jenkins@openstack.org>\n"
-"Language-Team: Chinese (Taiwan) (http://www.transifex.com/openstack/python-"
-"openstackclient/language/zh_TW/)\n"
+"Language: zh_Hant_TW\n"
+"Language-Team: Chinese (Taiwan)\n"
"Plural-Forms: nplurals=1; plural=0\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 2.0\n"
+"Generated-By: Babel 2.1.1\n"
msgid ""
"\n"
@@ -227,9 +228,6 @@ msgstr "新地區識別號"
msgid "New region description"
msgstr "新地區描述"
-msgid "New region url"
-msgstr "新地區網址"
-
msgid "New role name"
msgstr "新角色名稱"
diff --git a/releasenotes/notes/.placeholder b/releasenotes/notes/.placeholder
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/releasenotes/notes/.placeholder
diff --git a/releasenotes/notes/add-ksa-7f0795157d93a898.yaml b/releasenotes/notes/add-ksa-7f0795157d93a898.yaml
new file mode 100644
index 0000000..c777469
--- /dev/null
+++ b/releasenotes/notes/add-ksa-7f0795157d93a898.yaml
@@ -0,0 +1,5 @@
+---
+other:
+ - |
+ Add dependency on keystoneauth1 module to perform authentication in place
+ of python-keystoneclient.
diff --git a/releasenotes/notes/bug-1475831-3caafd724d4ed644.yaml b/releasenotes/notes/bug-1475831-3caafd724d4ed644.yaml
new file mode 100644
index 0000000..dbe5285
--- /dev/null
+++ b/releasenotes/notes/bug-1475831-3caafd724d4ed644.yaml
@@ -0,0 +1,5 @@
+---
+fixes:
+ - |
+ Some compute quotas were not being set
+ [Bug `1475831 <https://bugs.launchpad.net/bugs/1475831>`_]
diff --git a/releasenotes/notes/bug-1517386-1c0aad8e3203b02b.yaml b/releasenotes/notes/bug-1517386-1c0aad8e3203b02b.yaml
new file mode 100644
index 0000000..5353a25
--- /dev/null
+++ b/releasenotes/notes/bug-1517386-1c0aad8e3203b02b.yaml
@@ -0,0 +1,5 @@
+---
+fixes:
+ - |
+ Add `--all` for `snapshot list`
+ [Bug `1517386 <https://bugs.launchpad.net/bugs/1517386>`_]
diff --git a/releasenotes/notes/bug-1519181-932c1ff07ef16666.yaml b/releasenotes/notes/bug-1519181-932c1ff07ef16666.yaml
new file mode 100644
index 0000000..919b0ec
--- /dev/null
+++ b/releasenotes/notes/bug-1519181-932c1ff07ef16666.yaml
@@ -0,0 +1,6 @@
+---
+fixes:
+ - |
+ Add Status column to default `image list` output (v1 and v2)
+ [Bug `1519181 <https://bugs.launchpad.net/bugs/1519181>`_]
+
diff --git a/releasenotes/notes/bug-1519510-3cde4643b33ebb7a.yaml b/releasenotes/notes/bug-1519510-3cde4643b33ebb7a.yaml
new file mode 100644
index 0000000..7476b4f
--- /dev/null
+++ b/releasenotes/notes/bug-1519510-3cde4643b33ebb7a.yaml
@@ -0,0 +1,5 @@
+---
+other:
+ - |
+ Drop Python 2.6 support
+ [Bug `1519510 <https://bugs.launchpad.net/bugs/1519510>`_]
diff --git a/releasenotes/notes/bug-1520115-0367e1c8917dc912.yaml b/releasenotes/notes/bug-1520115-0367e1c8917dc912.yaml
new file mode 100644
index 0000000..98829c7
--- /dev/null
+++ b/releasenotes/notes/bug-1520115-0367e1c8917dc912.yaml
@@ -0,0 +1,5 @@
+---
+fixes:
+ - |
+ Fix `volume type create`, passes incorrect privacy argument
+ [Bug `1520115 <https://bugs.launchpad.net/bugs/1520115>`_]
diff --git a/releasenotes/notes/bug-1520541-44d45e4693089c03.yaml b/releasenotes/notes/bug-1520541-44d45e4693089c03.yaml
new file mode 100644
index 0000000..d3e4455
--- /dev/null
+++ b/releasenotes/notes/bug-1520541-44d45e4693089c03.yaml
@@ -0,0 +1,5 @@
+---
+fixes:
+ - |
+ `volume delete` only deletes last volume specified
+ [Bug `1520541 <https://bugs.launchpad.net/bugs/1520541>`_]
diff --git a/releasenotes/notes/no-project-usage-list-e88eb49aa2e96cf7.yaml b/releasenotes/notes/no-project-usage-list-e88eb49aa2e96cf7.yaml
new file mode 100644
index 0000000..67c5870
--- /dev/null
+++ b/releasenotes/notes/no-project-usage-list-e88eb49aa2e96cf7.yaml
@@ -0,0 +1,3 @@
+---
+upgrade:
+ - Removed the deprecated command `project usage list` in favor of `usage list`
diff --git a/releasenotes/notes/server-changes-3962541b6ebdbbd8.yaml b/releasenotes/notes/server-changes-3962541b6ebdbbd8.yaml
new file mode 100644
index 0000000..ae3739b
--- /dev/null
+++ b/releasenotes/notes/server-changes-3962541b6ebdbbd8.yaml
@@ -0,0 +1,10 @@
+---
+other:
+ - |
+ `server` resource changes:
+
+ * Added `--limit` and `--marker` to `server list`
+ * Added `server shelve`
+ * Added `server unshelve`
+ * `server resume` now takes multiple servers
+ * `server suspend` now takes multiple servers
diff --git a/releasenotes/source/_static/.placeholder b/releasenotes/source/_static/.placeholder
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/releasenotes/source/_static/.placeholder
diff --git a/releasenotes/source/_templates/.placeholder b/releasenotes/source/_templates/.placeholder
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/releasenotes/source/_templates/.placeholder
diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py
new file mode 100644
index 0000000..1b6929e
--- /dev/null
+++ b/releasenotes/source/conf.py
@@ -0,0 +1,282 @@
+# -*- coding: utf-8 -*-
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# OpenStackClient Release Notes documentation build configuration file, created
+# by sphinx-quickstart on Tue Nov 3 17:40:50 2015.
+#
+# This file is execfile()d with the current directory set to its
+# containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+# sys.path.insert(0, os.path.abspath('.'))
+
+# -- General configuration ------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+# needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+ 'oslosphinx',
+ 'reno.sphinxext',
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+# source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'OpenStackClient Release Notes'
+copyright = u'2015, OpenStackClient Developers'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+from openstackclient import version_info as openstackclient_version
+# The full version, including alpha/beta/rc tags.
+release = openstackclient_version.version_string_with_vcs()
+# The short X.Y version.
+version = openstackclient_version.canonical_version_string()
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+# language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+# today = ''
+# Else, today_fmt is used as the format for a strftime call.
+# today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = []
+
+# The reST default role (used for this markup: `text`) to use for all
+# documents.
+# default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+# add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+# add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+# show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+# modindex_common_prefix = []
+
+# If true, keep warnings as "system message" paragraphs in the built documents.
+# keep_warnings = False
+
+
+# -- Options for HTML output ----------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+# html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+# html_theme_path = []
+
+# The name for this set of Sphinx documents. If None, it defaults to
+# "<project> v<release> documentation".
+# html_title = None
+
+# A shorter title for the navigation bar. Default is the same as html_title.
+# html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+# html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+# html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# Add any extra paths that contain custom files (such as robots.txt or
+# .htaccess) here, relative to this directory. These files are copied
+# directly to the root of the documentation.
+# html_extra_path = []
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+# html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+# html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+# html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+# html_additional_pages = {}
+
+# If false, no module index is generated.
+# html_domain_indices = True
+
+# If false, no index is generated.
+# html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+# html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+# html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+# html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+# html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it. The value of this option must be the
+# base URL from which the finished HTML is served.
+# html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+# html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'OpenStackClientReleaseNotesdoc'
+
+
+# -- Options for LaTeX output ---------------------------------------------
+
+latex_elements = {
+ # The paper size ('letterpaper' or 'a4paper').
+ # 'papersize': 'letterpaper',
+
+ # The font size ('10pt', '11pt' or '12pt').
+ # 'pointsize': '10pt',
+
+ # Additional stuff for the LaTeX preamble.
+ # 'preamble': '',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+# author, documentclass [howto, manual, or own class]).
+latex_documents = [(
+ 'index',
+ 'OpenStackClientReleaseNotes.tex',
+ u'OpenStackClient Release Notes Documentation',
+ u'OpenStackClient Developers',
+ 'manual',
+)]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+# latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+# latex_use_parts = False
+
+# If true, show page references after internal links.
+# latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+# latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+# latex_appendices = []
+
+# If false, no module index is generated.
+# latex_domain_indices = True
+
+
+# -- Options for manual page output ---------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [(
+ 'index',
+ 'openstackclientreleasenotes',
+ u'OpenStackClient Release Notes Documentation',
+ [u'OpenStackClient Developers'],
+ 1,
+)]
+
+# If true, show URL addresses after external links.
+# man_show_urls = False
+
+
+# -- Options for Texinfo output -------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [(
+ 'index',
+ 'OpenStackClientReleaseNotes',
+ u'OpenStackclient Release Notes Documentation',
+ u'OpenStackclient Developers',
+ 'OpenStackClientReleaseNotes',
+ 'One line description of project.',
+ 'Miscellaneous',
+)]
+
+# Documents to append as an appendix to all manuals.
+# texinfo_appendices = []
+
+# If false, no module index is generated.
+# texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+# texinfo_show_urls = 'footnote'
+
+# If true, do not generate a @detailmenu in the "Top" node's menu.
+# texinfo_no_detailmenu = False
diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst
new file mode 100644
index 0000000..c89ed74
--- /dev/null
+++ b/releasenotes/source/index.rst
@@ -0,0 +1,8 @@
+=============================
+OpenStackClient Release Notes
+=============================
+
+.. toctree::
+ :maxdepth: 1
+
+ unreleased
diff --git a/releasenotes/source/unreleased.rst b/releasenotes/source/unreleased.rst
new file mode 100644
index 0000000..cb3cccf
--- /dev/null
+++ b/releasenotes/source/unreleased.rst
@@ -0,0 +1,5 @@
+=====================
+Current Release Notes
+=====================
+
+.. release-notes::
diff --git a/requirements.txt b/requirements.txt
index c638ed1..fbe3b8d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,16 +5,16 @@ pbr>=1.6
six>=1.9.0
Babel>=1.3
-cliff>=1.14.0 # Apache-2.0
-cliff-tablib>=1.0
+cliff>=1.15.0 # Apache-2.0
+keystoneauth1>=1.0.0
os-client-config!=1.6.2,>=1.4.0
-oslo.config>=2.3.0 # Apache-2.0
+oslo.config>=2.7.0 # Apache-2.0
oslo.i18n>=1.5.0 # Apache-2.0
-oslo.utils>=2.0.0 # Apache-2.0
+oslo.utils>=2.8.0 # Apache-2.0
python-glanceclient>=0.18.0
-python-keystoneclient>=1.6.0
-python-novaclient>=2.28.1
+python-keystoneclient!=1.8.0,>=1.6.0
+python-novaclient!=2.33.0,>=2.29.0
python-cinderclient>=1.3.1
python-neutronclient>=2.6.0
-requests>=2.5.2
+requests>=2.8.1
stevedore>=1.5.0 # Apache-2.0
diff --git a/setup.cfg b/setup.cfg
index 0b93682..27b00d4 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -5,7 +5,7 @@ description-file =
README.rst
author = OpenStack
author-email = openstack-dev@lists.openstack.org
-home-page = http://wiki.openstack.org/OpenStackClient
+home-page = http://docs.openstack.org/developer/python-openstackclient
classifier =
Environment :: OpenStack
Intended Audience :: Information Technology
@@ -15,7 +15,6 @@ classifier =
Programming Language :: Python
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
- Programming Language :: Python :: 2.6
Programming Language :: Python :: 3
Programming Language :: Python :: 3.4
@@ -66,6 +65,7 @@ openstack.compute.v2 =
aggregate_set = openstackclient.compute.v2.aggregate:SetAggregate
aggregate_show = openstackclient.compute.v2.aggregate:ShowAggregate
+ compute_service_delete = openstackclient.compute.v2.service:DeleteService
compute_service_list = openstackclient.compute.v2.service:ListService
compute_service_set = openstackclient.compute.v2.service:SetService
@@ -103,8 +103,6 @@ openstack.compute.v2 =
keypair_list = openstackclient.compute.v2.keypair:ListKeypair
keypair_show = openstackclient.compute.v2.keypair:ShowKeypair
- project_usage_list = openstackclient.compute.v2.usage:ListProjectUsage
-
security_group_create = openstackclient.compute.v2.security_group:CreateSecurityGroup
security_group_delete = openstackclient.compute.v2.security_group:DeleteSecurityGroup
security_group_list = openstackclient.compute.v2.security_group:ListSecurityGroup
@@ -131,13 +129,17 @@ openstack.compute.v2 =
server_resize = openstackclient.compute.v2.server:ResizeServer
server_resume = openstackclient.compute.v2.server:ResumeServer
server_set = openstackclient.compute.v2.server:SetServer
+ server_shelve = openstackclient.compute.v2.server:ShelveServer
server_show = openstackclient.compute.v2.server:ShowServer
server_ssh = openstackclient.compute.v2.server:SshServer
+ server_start = openstackclient.compute.v2.server:StartServer
+ server_stop = openstackclient.compute.v2.server:StopServer
server_suspend = openstackclient.compute.v2.server:SuspendServer
server_unlock = openstackclient.compute.v2.server:UnlockServer
server_unpause = openstackclient.compute.v2.server:UnpauseServer
server_unrescue = openstackclient.compute.v2.server:UnrescueServer
server_unset = openstackclient.compute.v2.server:UnsetServer
+ server_unshelve = openstackclient.compute.v2.server:UnshelveServer
usage_list = openstackclient.compute.v2.usage:ListUsage
usage_show = openstackclient.compute.v2.usage:ShowUsage
@@ -332,16 +334,23 @@ openstack.network.v2 =
network_show = openstackclient.network.v2.network:ShowNetwork
openstack.object_store.v1 =
+ object_store_account_set = openstackclient.object.v1.account:SetAccount
+ object_store_account_show = openstackclient.object.v1.account:ShowAccount
+ object_store_account_unset = openstackclient.object.v1.account:UnsetAccount
container_create = openstackclient.object.v1.container:CreateContainer
container_delete = openstackclient.object.v1.container:DeleteContainer
container_list = openstackclient.object.v1.container:ListContainer
container_save = openstackclient.object.v1.container:SaveContainer
+ container_set = openstackclient.object.v1.container:SetContainer
container_show = openstackclient.object.v1.container:ShowContainer
+ container_unset = openstackclient.object.v1.container:UnsetContainer
object_create = openstackclient.object.v1.object:CreateObject
object_delete = openstackclient.object.v1.object:DeleteObject
object_list = openstackclient.object.v1.object:ListObject
object_save = openstackclient.object.v1.object:SaveObject
+ object_set = openstackclient.object.v1.object:SetObject
object_show = openstackclient.object.v1.object:ShowObject
+ object_unset = openstackclient.object.v1.object:UnsetObject
openstack.volume.v1 =
backup_create = openstackclient.volume.v1.backup:CreateBackup
diff --git a/test-requirements.txt b/test-requirements.txt
index 25d0628..7322c84 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -7,12 +7,24 @@ coverage>=3.6
discover
fixtures>=1.3.1
mock>=1.2
-oslosphinx>=2.5.0 # Apache-2.0
+oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
oslotest>=1.10.0 # Apache-2.0
+reno>=0.1.1 # Apache2
requests-mock>=0.6.0 # Apache-2.0
sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2
-os-testr>=0.1.0
+os-testr>=0.4.1
testrepository>=0.0.18
testtools>=1.4.0
WebOb>=1.2.3
-tempest-lib>=0.8.0
+tempest-lib>=0.10.0
+
+# Install these to generate sphinx autodocs
+python-barbicanclient>=3.3.0
+python-congressclient>=1.0.0
+python-designateclient>=1.5.0
+python-heatclient>=0.6.0
+python-ironicclient>=0.8.0
+python-mistralclient>=1.0.0
+python-saharaclient>=0.10.0
+python-tuskarclient>=0.1.17
+python-zaqarclient>=0.3.0
diff --git a/tox.ini b/tox.ini
index 23104b8..dd2b7b5 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,6 @@
[tox]
minversion = 1.6
-envlist = py34,py26,py27,pep8
+envlist = py34,py27,pep8
skipdist = True
[testenv]
@@ -33,6 +33,9 @@ downloadcache = ~/cache/pip
[testenv:docs]
commands = python setup.py build_sphinx
+[testenv:releasenotes]
+commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
+
[flake8]
show-source = True
exclude = .git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build,tools