Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b914e8877 | ||
|
|
918d2f0bbd | ||
|
|
91d1d8b0e1 | ||
|
|
3ffe4869f3 | ||
|
|
bde259e38c | ||
|
|
75dd8dc561 | ||
|
|
a6aed1772d | ||
|
|
63a1039fb2 | ||
|
|
9d3a6c51b1 | ||
|
|
8bcdf13d7f | ||
|
|
f9fb0875e0 | ||
|
|
d36d835d08 | ||
|
|
20931e2ce7 | ||
|
|
fb84ae9b25 | ||
|
|
e64e597aa6 | ||
|
|
f9aac4fc9f | ||
|
|
0f276e483b | ||
|
|
888a58fd2a | ||
|
|
89cfc55870 | ||
|
|
4c19084cb1 | ||
|
|
bc572e70dc | ||
|
|
544db1951f | ||
|
|
1b14be868e | ||
|
|
e8236aaf65 | ||
|
|
0260775f30 | ||
|
|
3b79b58f68 | ||
|
|
d9c558107e | ||
|
|
0be6832e6b | ||
|
|
5586bbdff3 | ||
|
|
cbc578998a | ||
|
|
94af770a6d | ||
|
|
d2c22f0353 | ||
|
|
c5a5b7dad7 | ||
|
|
e6ca4e54a7 | ||
|
|
e0d1e5facf | ||
|
|
35c4e93a09 | ||
|
|
567b74ad0a | ||
|
|
f306a61ece | ||
|
|
0aaaf13278 | ||
|
|
e17dbab7c6 | ||
|
|
3640fc4a0b | ||
|
|
15511203d8 | ||
|
|
f390f3526f | ||
|
|
0ce454f3b3 | ||
|
|
5b4729dcbe | ||
|
|
a36b42afef | ||
|
|
9699fc5f70 | ||
|
|
51a3b3fe3f | ||
|
|
b30fad9965 | ||
|
|
59c0dc667e | ||
|
|
dee7be63d9 | ||
|
|
3ef4a0cae0 | ||
|
|
670a70ff5a | ||
|
|
926af3273a | ||
|
|
a0f97ee02b | ||
|
|
740dcf7bc2 | ||
|
|
4b2f948e7a | ||
|
|
eb0c70d332 | ||
|
|
03d12fd030 | ||
|
|
1852227505 | ||
|
|
dbcc77bc6c |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -42,6 +42,7 @@ output/*/index.html
|
||||
|
||||
# Sphinx
|
||||
doc/build
|
||||
doc/source/api
|
||||
|
||||
# pbr generates these
|
||||
AUTHORS
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[DEFAULT]
|
||||
test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
|
||||
OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
|
||||
OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-10} \
|
||||
${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION
|
||||
OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-160} \
|
||||
${PYTHON:-python} -m subunit.run discover -t ./ ${OS_TEST_PATH:-./watcherclient/tests} $LISTOPT $IDOPTION
|
||||
test_id_option=--load-list $IDFILE
|
||||
test_list_option=--list
|
||||
test_list_option=--list
|
||||
|
||||
176
LICENSE
Normal file
176
LICENSE
Normal file
@@ -0,0 +1,176 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
66
README.rst
66
README.rst
@@ -2,16 +2,22 @@
|
||||
python-watcherclient
|
||||
====================
|
||||
|
||||
Python client library for Watcher API.
|
||||
Client for resource optimization service for OpenStack.
|
||||
|
||||
Watcher takes advantage of CEP and ML algorithms/metaheuristics to improve physical resources usage through better VM placement. Watcher can improve your cloud optimization by reducing energy footprint and increasing profits.
|
||||
OpenStack Watcher provides a flexible and scalable resource optimization
|
||||
service for multi-tenant OpenStack-based clouds.
|
||||
Watcher provides a complete optimization loop-including everything from a
|
||||
metrics receiver, complex event processor and profiler, optimization processor
|
||||
and an action plan applier. This provides a robust framework to realize a wide
|
||||
range of cloud optimization goals, including the reduction of data center
|
||||
operating costs, increased system performance via intelligent virtual machine
|
||||
migration, increased energy efficiency-and more!
|
||||
|
||||
* Free software: Apache license
|
||||
* Wiki: http://wiki.openstack.org/wiki/Watcher
|
||||
* Source: http://git.openstack.org/cgit/openstack/python-watcher
|
||||
* Bugs: http://bugs.launchpad.net/watcher
|
||||
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
@@ -23,13 +29,13 @@ On Ubuntu (tested on 14.04-64)
|
||||
.. code::
|
||||
|
||||
sudo apt-get install python-dev libssl-dev python-pip git-core libmysqlclient-dev libffi-dev
|
||||
|
||||
|
||||
On Fedora-based distributions e.g., Fedora/RHEL/CentOS/Scientific Linux (tested on CentOS 6.5)
|
||||
|
||||
.. code::
|
||||
|
||||
sudo yum install python-virtualenv openssl-devel python-pip git gcc libffi-devel mysql-devel postgresql-devel
|
||||
|
||||
|
||||
On openSUSE-based distributions (SLES 12, openSUSE 13.1, Factory or Tumbleweed)
|
||||
|
||||
.. code::
|
||||
@@ -43,13 +49,21 @@ You can install the Watcher CLI with the following command:
|
||||
|
||||
.. code::
|
||||
|
||||
pip install python-watcherclient
|
||||
sudo pip install python-watcherclient
|
||||
|
||||
|
||||
You can also use the `OpenStack client <http://docs.openstack.org/cli-reference/overview.html>`_
|
||||
with Watcher (our watcher plugin for OpenStack client is included in the
|
||||
python-watcherclient package). To install it, you have just to run this command:
|
||||
|
||||
.. code::
|
||||
|
||||
sudo pip install python-openstackclient
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
Create a **creds** file containing your Openstack credentials:
|
||||
Create a **creds** file containing your OpenStack credentials:
|
||||
|
||||
.. code::
|
||||
|
||||
@@ -67,31 +81,48 @@ Source these credentials into your current shell session:
|
||||
|
||||
# source creds
|
||||
|
||||
You should be able to launch the following command which gets the list of previously created Audit Templates:
|
||||
You should be able to launch the following command which gets the list of
|
||||
previously created Audit Templates:
|
||||
|
||||
.. code::
|
||||
|
||||
# watcher audit-template-list
|
||||
+------+------+
|
||||
| UUID | Name |
|
||||
+------+------+
|
||||
+------+------+
|
||||
# watcher audittemplate list
|
||||
|
||||
You can view the entire list of available Watcher commands and options using this command:
|
||||
or::
|
||||
|
||||
# openstack optimize audittemplate list
|
||||
+--------------------------------+------+----------------------+----------+
|
||||
| UUID | Name | Goal | Strategy |
|
||||
+--------------------------------+------+----------------------+----------+
|
||||
+--------------------------------+------+----------------------+----------+
|
||||
|
||||
|
||||
You can view the entire list of available Watcher commands and options using
|
||||
this command:
|
||||
|
||||
.. code::
|
||||
|
||||
# watcher help
|
||||
|
||||
or::
|
||||
|
||||
# openstack help optimize
|
||||
|
||||
|
||||
Troubleshootings
|
||||
================
|
||||
|
||||
If any watcher command fails, you can obtain more details with the **--debug** option :
|
||||
If any watcher command fails, you can obtain more details with the **--debug**
|
||||
option :
|
||||
|
||||
.. code::
|
||||
|
||||
# watcher --debug audit-template-list
|
||||
# watcher --debug audittemplate list
|
||||
|
||||
or::
|
||||
|
||||
# openstack --debug optimize audittemplate list
|
||||
|
||||
|
||||
Install the openstack CLI :
|
||||
|
||||
@@ -99,7 +130,8 @@ Install the openstack CLI :
|
||||
|
||||
# pip install python-openstackclient
|
||||
|
||||
Make sure that your Openstack credentials are correct. If so, you should be able to verify that the watcher user has been declared in your Openstack keystone :
|
||||
Make sure that your Openstack credentials are correct. If so, you should be able
|
||||
to verify that the watcher user has been declared in your Openstack keystone :
|
||||
|
||||
.. code::
|
||||
|
||||
|
||||
51
doc/source/api/autoindex.rst
Normal file
51
doc/source/api/autoindex.rst
Normal file
@@ -0,0 +1,51 @@
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
watcherclient._i18n.rst
|
||||
watcherclient.client.rst
|
||||
watcherclient.common.apiclient.base.rst
|
||||
watcherclient.common.apiclient.exceptions.rst
|
||||
watcherclient.common.base.rst
|
||||
watcherclient.common.cliutils.rst
|
||||
watcherclient.common.command.rst
|
||||
watcherclient.common.http.rst
|
||||
watcherclient.common.utils.rst
|
||||
watcherclient.exceptions.rst
|
||||
watcherclient.plugin.rst
|
||||
watcherclient.shell.rst
|
||||
watcherclient.tests.keystone_client_fixtures.rst
|
||||
watcherclient.tests.test_client.rst
|
||||
watcherclient.tests.test_http.rst
|
||||
watcherclient.tests.test_import.rst
|
||||
watcherclient.tests.test_utils.rst
|
||||
watcherclient.tests.utils.rst
|
||||
watcherclient.tests.v1.base.rst
|
||||
watcherclient.tests.v1.test_action.rst
|
||||
watcherclient.tests.v1.test_action_plan.rst
|
||||
watcherclient.tests.v1.test_action_plan_shell.rst
|
||||
watcherclient.tests.v1.test_action_shell.rst
|
||||
watcherclient.tests.v1.test_audit.rst
|
||||
watcherclient.tests.v1.test_audit_shell.rst
|
||||
watcherclient.tests.v1.test_audit_template.rst
|
||||
watcherclient.tests.v1.test_audit_template_shell.rst
|
||||
watcherclient.tests.v1.test_goal.rst
|
||||
watcherclient.tests.v1.test_goal_shell.rst
|
||||
watcherclient.tests.v1.test_metric_collector.rst
|
||||
watcherclient.tests.v1.test_strategy.rst
|
||||
watcherclient.tests.v1.test_strategy_shell.rst
|
||||
watcherclient.v1.action.rst
|
||||
watcherclient.v1.action_plan.rst
|
||||
watcherclient.v1.action_plan_shell.rst
|
||||
watcherclient.v1.action_shell.rst
|
||||
watcherclient.v1.audit.rst
|
||||
watcherclient.v1.audit_shell.rst
|
||||
watcherclient.v1.audit_template.rst
|
||||
watcherclient.v1.audit_template_shell.rst
|
||||
watcherclient.v1.client.rst
|
||||
watcherclient.v1.goal.rst
|
||||
watcherclient.v1.goal_shell.rst
|
||||
watcherclient.v1.metric_collector.rst
|
||||
watcherclient.v1.resource_fields.rst
|
||||
watcherclient.v1.strategy.rst
|
||||
watcherclient.v1.strategy_shell.rst
|
||||
watcherclient.version.rst
|
||||
7
doc/source/api/watcherclient.client.rst
Normal file
7
doc/source/api/watcherclient.client.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.client` Module
|
||||
======================================
|
||||
|
||||
.. automodule:: watcherclient.client
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/api/watcherclient.common.apiclient.auth.rst
Normal file
7
doc/source/api/watcherclient.common.apiclient.auth.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.common.apiclient.auth` Module
|
||||
=====================================================
|
||||
|
||||
.. automodule:: watcherclient.common.apiclient.auth
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/api/watcherclient.common.apiclient.base.rst
Normal file
7
doc/source/api/watcherclient.common.apiclient.base.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.common.apiclient.base` Module
|
||||
=====================================================
|
||||
|
||||
.. automodule:: watcherclient.common.apiclient.base
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/api/watcherclient.common.apiclient.client.rst
Normal file
7
doc/source/api/watcherclient.common.apiclient.client.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.common.apiclient.client` Module
|
||||
=======================================================
|
||||
|
||||
.. automodule:: watcherclient.common.apiclient.client
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.common.apiclient.exceptions` Module
|
||||
===========================================================
|
||||
|
||||
.. automodule:: watcherclient.common.apiclient.exceptions
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/api/watcherclient.common.apiclient.utils.rst
Normal file
7
doc/source/api/watcherclient.common.apiclient.utils.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.common.apiclient.utils` Module
|
||||
======================================================
|
||||
|
||||
.. automodule:: watcherclient.common.apiclient.utils
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/api/watcherclient.common.base.rst
Normal file
7
doc/source/api/watcherclient.common.base.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.common.base` Module
|
||||
===========================================
|
||||
|
||||
.. automodule:: watcherclient.common.base
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/api/watcherclient.common.cliutils.rst
Normal file
7
doc/source/api/watcherclient.common.cliutils.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.common.cliutils` Module
|
||||
===============================================
|
||||
|
||||
.. automodule:: watcherclient.common.cliutils
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/api/watcherclient.common.http.rst
Normal file
7
doc/source/api/watcherclient.common.http.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.common.http` Module
|
||||
===========================================
|
||||
|
||||
.. automodule:: watcherclient.common.http
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/api/watcherclient.common.i18n.rst
Normal file
7
doc/source/api/watcherclient.common.i18n.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.common.i18n` Module
|
||||
===========================================
|
||||
|
||||
.. automodule:: watcherclient.common.i18n
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/api/watcherclient.common.utils.rst
Normal file
7
doc/source/api/watcherclient.common.utils.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.common.utils` Module
|
||||
============================================
|
||||
|
||||
.. automodule:: watcherclient.common.utils
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/api/watcherclient.exceptions.rst
Normal file
7
doc/source/api/watcherclient.exceptions.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.exceptions` Module
|
||||
==========================================
|
||||
|
||||
.. automodule:: watcherclient.exceptions
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/api/watcherclient.shell.rst
Normal file
7
doc/source/api/watcherclient.shell.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.shell` Module
|
||||
=====================================
|
||||
|
||||
.. automodule:: watcherclient.shell
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.tests.keystone_client_fixtures` Module
|
||||
==============================================================
|
||||
|
||||
.. automodule:: watcherclient.tests.keystone_client_fixtures
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/api/watcherclient.tests.test_client.rst
Normal file
7
doc/source/api/watcherclient.tests.test_client.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.tests.test_client` Module
|
||||
=================================================
|
||||
|
||||
.. automodule:: watcherclient.tests.test_client
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/api/watcherclient.tests.test_http.rst
Normal file
7
doc/source/api/watcherclient.tests.test_http.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.tests.test_http` Module
|
||||
===============================================
|
||||
|
||||
.. automodule:: watcherclient.tests.test_http
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/api/watcherclient.tests.test_import.rst
Normal file
7
doc/source/api/watcherclient.tests.test_import.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.tests.test_import` Module
|
||||
=================================================
|
||||
|
||||
.. automodule:: watcherclient.tests.test_import
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/api/watcherclient.tests.test_shell.rst
Normal file
7
doc/source/api/watcherclient.tests.test_shell.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.tests.test_shell` Module
|
||||
================================================
|
||||
|
||||
.. automodule:: watcherclient.tests.test_shell
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/api/watcherclient.tests.test_utils.rst
Normal file
7
doc/source/api/watcherclient.tests.test_utils.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.tests.test_utils` Module
|
||||
================================================
|
||||
|
||||
.. automodule:: watcherclient.tests.test_utils
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/api/watcherclient.tests.utils.rst
Normal file
7
doc/source/api/watcherclient.tests.utils.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.tests.utils` Module
|
||||
===========================================
|
||||
|
||||
.. automodule:: watcherclient.tests.utils
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/api/watcherclient.tests.v1.test_action.rst
Normal file
7
doc/source/api/watcherclient.tests.v1.test_action.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.tests.v1.test_action` Module
|
||||
====================================================
|
||||
|
||||
.. automodule:: watcherclient.tests.v1.test_action
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.tests.v1.test_action_plan` Module
|
||||
=========================================================
|
||||
|
||||
.. automodule:: watcherclient.tests.v1.test_action_plan
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.tests.v1.test_action_plan_shell` Module
|
||||
===============================================================
|
||||
|
||||
.. automodule:: watcherclient.tests.v1.test_action_plan_shell
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.tests.v1.test_action_shell` Module
|
||||
==========================================================
|
||||
|
||||
.. automodule:: watcherclient.tests.v1.test_action_shell
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/api/watcherclient.tests.v1.test_audit.rst
Normal file
7
doc/source/api/watcherclient.tests.v1.test_audit.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.tests.v1.test_audit` Module
|
||||
===================================================
|
||||
|
||||
.. automodule:: watcherclient.tests.v1.test_audit
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.tests.v1.test_audit_shell` Module
|
||||
=========================================================
|
||||
|
||||
.. automodule:: watcherclient.tests.v1.test_audit_shell
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.tests.v1.test_audit_template` Module
|
||||
============================================================
|
||||
|
||||
.. automodule:: watcherclient.tests.v1.test_audit_template
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.tests.v1.test_audit_template_shell` Module
|
||||
==================================================================
|
||||
|
||||
.. automodule:: watcherclient.tests.v1.test_audit_template_shell
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/api/watcherclient.tests.v1.test_goal.rst
Normal file
7
doc/source/api/watcherclient.tests.v1.test_goal.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.tests.v1.test_goal` Module
|
||||
==================================================
|
||||
|
||||
.. automodule:: watcherclient.tests.v1.test_goal
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.tests.v1.test_goal_shell` Module
|
||||
========================================================
|
||||
|
||||
.. automodule:: watcherclient.tests.v1.test_goal_shell
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.tests.v1.test_metric_collector` Module
|
||||
==============================================================
|
||||
|
||||
.. automodule:: watcherclient.tests.v1.test_metric_collector
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.tests.v1.test_metric_collector_shell` Module
|
||||
====================================================================
|
||||
|
||||
.. automodule:: watcherclient.tests.v1.test_metric_collector_shell
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/api/watcherclient.v1.action.rst
Normal file
7
doc/source/api/watcherclient.v1.action.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.v1.action` Module
|
||||
=========================================
|
||||
|
||||
.. automodule:: watcherclient.v1.action
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/api/watcherclient.v1.action_plan.rst
Normal file
7
doc/source/api/watcherclient.v1.action_plan.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.v1.action_plan` Module
|
||||
==============================================
|
||||
|
||||
.. automodule:: watcherclient.v1.action_plan
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/api/watcherclient.v1.action_plan_shell.rst
Normal file
7
doc/source/api/watcherclient.v1.action_plan_shell.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.v1.action_plan_shell` Module
|
||||
====================================================
|
||||
|
||||
.. automodule:: watcherclient.v1.action_plan_shell
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/api/watcherclient.v1.action_shell.rst
Normal file
7
doc/source/api/watcherclient.v1.action_shell.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.v1.action_shell` Module
|
||||
===============================================
|
||||
|
||||
.. automodule:: watcherclient.v1.action_shell
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/api/watcherclient.v1.audit.rst
Normal file
7
doc/source/api/watcherclient.v1.audit.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.v1.audit` Module
|
||||
========================================
|
||||
|
||||
.. automodule:: watcherclient.v1.audit
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/api/watcherclient.v1.audit_shell.rst
Normal file
7
doc/source/api/watcherclient.v1.audit_shell.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.v1.audit_shell` Module
|
||||
==============================================
|
||||
|
||||
.. automodule:: watcherclient.v1.audit_shell
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/api/watcherclient.v1.audit_template.rst
Normal file
7
doc/source/api/watcherclient.v1.audit_template.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.v1.audit_template` Module
|
||||
=================================================
|
||||
|
||||
.. automodule:: watcherclient.v1.audit_template
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/api/watcherclient.v1.audit_template_shell.rst
Normal file
7
doc/source/api/watcherclient.v1.audit_template_shell.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.v1.audit_template_shell` Module
|
||||
=======================================================
|
||||
|
||||
.. automodule:: watcherclient.v1.audit_template_shell
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/api/watcherclient.v1.client.rst
Normal file
7
doc/source/api/watcherclient.v1.client.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.v1.client` Module
|
||||
=========================================
|
||||
|
||||
.. automodule:: watcherclient.v1.client
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/api/watcherclient.v1.goal.rst
Normal file
7
doc/source/api/watcherclient.v1.goal.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.v1.goal` Module
|
||||
=======================================
|
||||
|
||||
.. automodule:: watcherclient.v1.goal
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/api/watcherclient.v1.goal_shell.rst
Normal file
7
doc/source/api/watcherclient.v1.goal_shell.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.v1.goal_shell` Module
|
||||
=============================================
|
||||
|
||||
.. automodule:: watcherclient.v1.goal_shell
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/api/watcherclient.v1.metric_collector.rst
Normal file
7
doc/source/api/watcherclient.v1.metric_collector.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.v1.metric_collector` Module
|
||||
===================================================
|
||||
|
||||
.. automodule:: watcherclient.v1.metric_collector
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.v1.metric_collector_shell` Module
|
||||
=========================================================
|
||||
|
||||
.. automodule:: watcherclient.v1.metric_collector_shell
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/api/watcherclient.v1.resource_fields.rst
Normal file
7
doc/source/api/watcherclient.v1.resource_fields.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.v1.resource_fields` Module
|
||||
==================================================
|
||||
|
||||
.. automodule:: watcherclient.v1.resource_fields
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/api/watcherclient.v1.shell.rst
Normal file
7
doc/source/api/watcherclient.v1.shell.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.v1.shell` Module
|
||||
========================================
|
||||
|
||||
.. automodule:: watcherclient.v1.shell
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/api/watcherclient.version.rst
Normal file
7
doc/source/api/watcherclient.version.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
The :mod:`watcherclient.version` Module
|
||||
=======================================
|
||||
|
||||
.. automodule:: watcherclient.version
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -19,7 +19,7 @@ DESCRIPTION
|
||||
===========
|
||||
|
||||
The :program:`watcher` command-line interface (CLI) interacts with the
|
||||
OpenStack TODEFINE Service (Watcher).
|
||||
OpenStack infra-optim Service (Watcher).
|
||||
|
||||
In order to use the CLI, you must provide your OpenStack username, password,
|
||||
project (historically called tenant), and auth endpoint. You can use
|
||||
@@ -28,26 +28,26 @@ configuration options :option:`--os-username`, :option:`--os-password`,
|
||||
and :option:`--os-auth-url`, or set the corresponding
|
||||
environment variables::
|
||||
|
||||
export OS_USERNAME=user
|
||||
export OS_PASSWORD=password
|
||||
export OS_TENANT_ID=b363706f891f48019483f8bd6503c54b # or OS_TENANT_NAME
|
||||
export OS_TENANT_NAME=project # or OS_TENANT_ID
|
||||
export OS_AUTH_URL=http://auth.example.com:5000/v2.0
|
||||
$ export OS_USERNAME=user
|
||||
$ export OS_PASSWORD=password
|
||||
$ export OS_TENANT_ID=b363706f891f48019483f8bd6503c54b # or OS_TENANT_NAME
|
||||
$ export OS_TENANT_NAME=project # or OS_TENANT_ID
|
||||
$ export OS_AUTH_URL=http://auth.example.com:5000/v2.0
|
||||
|
||||
The command-line tool will attempt to reauthenticate using the provided
|
||||
credentials for every request. You can override this behavior by manually
|
||||
supplying an auth token using :option:`--watcher-url` and
|
||||
:option:`--os-auth-token`, or by setting the corresponding environment variables::
|
||||
|
||||
export WATCHER_URL=http://watcher.example.org:9322/
|
||||
export OS_AUTH_TOKEN=3bcc3d3a03f44e3d8377f9247b0ad155
|
||||
$ export WATCHER_URL=http://watcher.example.org:9322/
|
||||
$ export OS_AUTH_TOKEN=3bcc3d3a03f44e3d8377f9247b0ad155
|
||||
|
||||
Since Keystone can return multiple regions in the Service Catalog, you can
|
||||
specify the one you want with :option:`--os-region-name` or set the following
|
||||
environment variable. (It defaults to the first in the list returned.)
|
||||
::
|
||||
|
||||
export OS_REGION_NAME=region
|
||||
$ export OS_REGION_NAME=region
|
||||
|
||||
Watcher CLI supports bash completion. The command-line tool can automatically
|
||||
fill partially typed commands. To use this feature, source the below file
|
||||
@@ -55,7 +55,7 @@ fill partially typed commands. To use this feature, source the below file
|
||||
https://git.openstack.org/cgit/openstack/python-watcherclient/tree/tools/watcher.bash_completion)
|
||||
to your terminal and then bash completion should work::
|
||||
|
||||
source watcher.bash_completion
|
||||
$ source watcher.bash_completion
|
||||
|
||||
To avoid doing this every time, add this to your ``.bashrc`` or copy the
|
||||
watcher.bash_completion file to the default bash completion scripts directory
|
||||
@@ -66,11 +66,11 @@ OPTIONS
|
||||
|
||||
To get a list of available (sub)commands and options, run::
|
||||
|
||||
watcher help
|
||||
$ watcher help
|
||||
|
||||
To get usage and options of a command, run::
|
||||
|
||||
watcher help <command>
|
||||
$ watcher help <command>
|
||||
|
||||
|
||||
EXAMPLES
|
||||
@@ -78,12 +78,12 @@ EXAMPLES
|
||||
|
||||
Get information about the audit-create command::
|
||||
|
||||
watcher help audit-create
|
||||
$ watcher help audit create
|
||||
|
||||
Get a list of available goal::
|
||||
|
||||
watcher goal-list
|
||||
$ watcher goal list
|
||||
|
||||
Get a list of audits::
|
||||
|
||||
watcher audit-list
|
||||
$ watcher audit list
|
||||
|
||||
@@ -18,6 +18,7 @@ Contents:
|
||||
installation
|
||||
api_v1
|
||||
cli
|
||||
openstack_cli
|
||||
contributing
|
||||
|
||||
Contributing
|
||||
@@ -39,12 +40,4 @@ The preferred way to run the unit tests is using ``tox``.
|
||||
See `Consistent Testing Interface`_ for more details.
|
||||
|
||||
.. _Consistent Testing Interface: http://git.openstack.org/cgit/openstack/governance/tree/reference/project-testing-interface.rst
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
||||
.. _Watcher: https://wiki.openstack.org/wiki/Watcher
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Installation
|
||||
============
|
||||
|
||||
Or, if you have `virtualenvwrapper <https://virtualenvwrapper.readthedocs.org/en/latest/install.html>`_ installed::
|
||||
If you have `virtualenvwrapper <https://virtualenvwrapper.readthedocs.org/en/latest/install.html>`_ installed::
|
||||
|
||||
$ mkvirtualenv python-watcherclient
|
||||
$ git clone https://git.openstack.org/openstack/python-watcherclient
|
||||
|
||||
81
doc/source/openstack_cli.rst
Normal file
81
doc/source/openstack_cli.rst
Normal file
@@ -0,0 +1,81 @@
|
||||
=====================================================================
|
||||
:program:`openstack` Command-Line Interface (CLI) with Watcher plugin
|
||||
=====================================================================
|
||||
|
||||
.. program:: openstack
|
||||
.. highlight:: bash
|
||||
|
||||
SYNOPSIS
|
||||
========
|
||||
|
||||
:program:`openstack` [options] :program:`optimize` <command> [command-options]
|
||||
|
||||
:program:`openstack help optimize`
|
||||
|
||||
:program:`openstack help optimize` <command>
|
||||
|
||||
|
||||
DESCRIPTION
|
||||
===========
|
||||
|
||||
The :program:`openstack` command-line interface (CLI) can interact with the
|
||||
OpenStack infra-optim Service (Watcher), by using our additional plugin
|
||||
(included into the python-watcherclient package).
|
||||
|
||||
In order to use the CLI, you must provide your OpenStack username, password,
|
||||
project (historically called tenant), and auth endpoint. You can use
|
||||
configuration options :option:`--os-username`, :option:`--os-password`,
|
||||
:option:`--os-tenant-id` (or :option:`--os-tenant-name`),
|
||||
and :option:`--os-auth-url`, or set the corresponding
|
||||
environment variables::
|
||||
|
||||
$ export OS_USERNAME=user
|
||||
$ export OS_PASSWORD=password
|
||||
$ export OS_TENANT_ID=b363706f891f48019483f8bd6503c54b # or OS_TENANT_NAME
|
||||
$ export OS_TENANT_NAME=project # or OS_TENANT_ID
|
||||
$ export OS_AUTH_URL=http://auth.example.com:5000/v2.0
|
||||
|
||||
The command-line tool will attempt to reauthenticate using the provided
|
||||
credentials for every request. You can override this behavior by manually
|
||||
supplying an auth token using :option:`--watcher-url` and
|
||||
:option:`--os-auth-token`, or by setting the corresponding environment variables::
|
||||
|
||||
export WATCHER_URL=http://watcher.example.org:9322/
|
||||
export OS_AUTH_TOKEN=3bcc3d3a03f44e3d8377f9247b0ad155
|
||||
|
||||
Since Keystone can return multiple regions in the Service Catalog, you can
|
||||
specify the one you want with :option:`--os-region-name` or set the following
|
||||
environment variable. (It defaults to the first in the list returned.)
|
||||
::
|
||||
|
||||
$ export OS_REGION_NAME=region
|
||||
|
||||
OPTIONS
|
||||
=======
|
||||
|
||||
To get a list of available (sub)commands and options, run::
|
||||
|
||||
$ openstack help optimize
|
||||
|
||||
To get usage and options of a command, run::
|
||||
|
||||
$ openstack help optimize <command>
|
||||
|
||||
|
||||
EXAMPLES
|
||||
========
|
||||
|
||||
Get information about the audit-create command::
|
||||
|
||||
$ openstack help optimize audit create
|
||||
|
||||
|
||||
Get a list of available goal::
|
||||
|
||||
$ openstack optimize goal list
|
||||
|
||||
|
||||
Get a list of audits::
|
||||
|
||||
$ openstack optimize audit list
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
argparse
|
||||
Babel>=1.3
|
||||
oslo.i18n>=1.5.0 # Apache-2.0
|
||||
pbr>=1.6
|
||||
python-keystoneclient>=1.6.0,!=1.8.0
|
||||
six>=1.9.0
|
||||
Babel>=2.3.4 # BSD
|
||||
cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0
|
||||
oslo.i18n>=2.1.0 # Apache-2.0
|
||||
oslo.utils>=3.14.0 # Apache-2.0
|
||||
pbr>=1.6 # Apache-2.0
|
||||
PrettyTable<0.8,>=0.7 # BSD
|
||||
python-keystoneclient!=1.8.0,!=2.1.0,>=1.7.0 # Apache-2.0
|
||||
python-openstackclient>=2.1.0 # Apache-2.0
|
||||
six>=1.9.0 # MIT
|
||||
|
||||
77
setup.cfg
77
setup.cfg
@@ -16,7 +16,6 @@ classifier =
|
||||
Programming Language :: Python :: 2
|
||||
Programming Language :: Python :: 2.7
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3.3
|
||||
Programming Language :: Python :: 3.4
|
||||
|
||||
[files]
|
||||
@@ -27,6 +26,68 @@ packages =
|
||||
console_scripts =
|
||||
watcher = watcherclient.shell:main
|
||||
|
||||
openstack.cli.extension =
|
||||
infra_optim = watcherclient.plugin
|
||||
|
||||
# Entry points for the 'openstack' command
|
||||
openstack.infra_optim.v1 =
|
||||
optimize_goal_show = watcherclient.v1.goal_shell:ShowGoal
|
||||
optimize_goal_list = watcherclient.v1.goal_shell:ListGoal
|
||||
|
||||
optimize_strategy_show = watcherclient.v1.strategy_shell:ShowStrategy
|
||||
optimize_strategy_list = watcherclient.v1.strategy_shell:ListStrategy
|
||||
|
||||
optimize_audittemplate_show = watcherclient.v1.audit_template_shell:ShowAuditTemplate
|
||||
optimize_audittemplate_list = watcherclient.v1.audit_template_shell:ListAuditTemplate
|
||||
optimize_audittemplate_create = watcherclient.v1.audit_template_shell:CreateAuditTemplate
|
||||
optimize_audittemplate_update = watcherclient.v1.audit_template_shell:UpdateAuditTemplate
|
||||
optimize_audittemplate_delete = watcherclient.v1.audit_template_shell:DeleteAuditTemplate
|
||||
|
||||
optimize_audit_show = watcherclient.v1.audit_shell:ShowAudit
|
||||
optimize_audit_list = watcherclient.v1.audit_shell:ListAudit
|
||||
optimize_audit_create = watcherclient.v1.audit_shell:CreateAudit
|
||||
optimize_audit_update = watcherclient.v1.audit_shell:UpdateAudit
|
||||
optimize_audit_delete = watcherclient.v1.audit_shell:DeleteAudit
|
||||
|
||||
optimize_actionplan_show = watcherclient.v1.action_plan_shell:ShowActionPlan
|
||||
optimize_actionplan_list = watcherclient.v1.action_plan_shell:ListActionPlan
|
||||
optimize_actionplan_create = watcherclient.v1.action_plan_shell:CreateActionPlan
|
||||
optimize_actionplan_update = watcherclient.v1.action_plan_shell:UpdateActionPlan
|
||||
optimize_actionplan_start = watcherclient.v1.action_plan_shell:StartActionPlan
|
||||
|
||||
optimize_action_show = watcherclient.v1.action_shell:ShowAction
|
||||
optimize_action_list = watcherclient.v1.action_shell:ListAction
|
||||
|
||||
# The same as above but used by the 'watcher' command
|
||||
watcherclient.v1 =
|
||||
goal_show = watcherclient.v1.goal_shell:ShowGoal
|
||||
goal_list = watcherclient.v1.goal_shell:ListGoal
|
||||
|
||||
strategy_show = watcherclient.v1.strategy_shell:ShowStrategy
|
||||
strategy_list = watcherclient.v1.strategy_shell:ListStrategy
|
||||
|
||||
audittemplate_show = watcherclient.v1.audit_template_shell:ShowAuditTemplate
|
||||
audittemplate_list = watcherclient.v1.audit_template_shell:ListAuditTemplate
|
||||
audittemplate_create = watcherclient.v1.audit_template_shell:CreateAuditTemplate
|
||||
audittemplate_update = watcherclient.v1.audit_template_shell:UpdateAuditTemplate
|
||||
audittemplate_delete = watcherclient.v1.audit_template_shell:DeleteAuditTemplate
|
||||
|
||||
audit_show = watcherclient.v1.audit_shell:ShowAudit
|
||||
audit_list = watcherclient.v1.audit_shell:ListAudit
|
||||
audit_create = watcherclient.v1.audit_shell:CreateAudit
|
||||
audit_update = watcherclient.v1.audit_shell:UpdateAudit
|
||||
audit_delete = watcherclient.v1.audit_shell:DeleteAudit
|
||||
|
||||
actionplan_show = watcherclient.v1.action_plan_shell:ShowActionPlan
|
||||
actionplan_list = watcherclient.v1.action_plan_shell:ListActionPlan
|
||||
actionplan_create = watcherclient.v1.action_plan_shell:CreateActionPlan
|
||||
actionplan_update = watcherclient.v1.action_plan_shell:UpdateActionPlan
|
||||
actionplan_start = watcherclient.v1.action_plan_shell:StartActionPlan
|
||||
actionplan_delete = watcherclient.v1.action_plan_shell:DeleteActionPlan
|
||||
|
||||
action_show = watcherclient.v1.action_shell:ShowAction
|
||||
action_list = watcherclient.v1.action_shell:ListAction
|
||||
|
||||
[pbr]
|
||||
autodoc_index_modules = True
|
||||
|
||||
@@ -37,3 +98,17 @@ all_files = 1
|
||||
|
||||
[bdist_wheel]
|
||||
universal = 1
|
||||
|
||||
[compile_catalog]
|
||||
directory = watcherclient/locale
|
||||
domain = watcherclient
|
||||
|
||||
[update_catalog]
|
||||
domain = watcherclient
|
||||
output_dir = watcherclient/locale
|
||||
input_file = watcherclient/locale/watcherclient.pot
|
||||
|
||||
[extract_messages]
|
||||
keywords = _ gettext ngettext l_ lazy_gettext _LI _LW _LE _LC
|
||||
mapping_file = babel.cfg
|
||||
output_file = watcherclient/locale/watcherclient.pot
|
||||
|
||||
3
setup.py
Executable file → Normal file
3
setup.py
Executable file → Normal file
@@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -26,5 +25,5 @@ except ImportError:
|
||||
pass
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['pbr'],
|
||||
setup_requires=['pbr>=1.8'],
|
||||
pbr=True)
|
||||
|
||||
@@ -2,19 +2,17 @@
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
coverage>=3.6
|
||||
discover
|
||||
hacking<0.11,>=0.10
|
||||
httpretty>=0.8.4,<0.8.7
|
||||
mock>=1.2
|
||||
oslosphinx>=2.5.0 # Apache-2.0
|
||||
oslotest>=1.10.0 # Apache-2.0
|
||||
python-subunit>=0.0.18
|
||||
sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3
|
||||
testrepository>=0.0.18
|
||||
testscenarios>=0.4
|
||||
testtools>=1.4.0
|
||||
coverage>=3.6 # Apache-2.0
|
||||
discover # BSD
|
||||
hacking<0.11,>=0.10.2
|
||||
mock>=2.0 # BSD
|
||||
oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
|
||||
oslotest>=1.10.0 # Apache-2.0
|
||||
python-subunit>=0.0.18 # Apache-2.0/BSD
|
||||
sphinx!=1.3b1,<1.3,>=1.2.1 # BSD
|
||||
testrepository>=0.0.18 # Apache-2.0/BSD
|
||||
testscenarios>=0.4 # Apache-2.0/BSD
|
||||
testtools>=1.4.0 # MIT
|
||||
|
||||
# Needed for pypi packaging
|
||||
wheel
|
||||
twine
|
||||
wheel # MIT
|
||||
|
||||
12
tox.ini
12
tox.ini
@@ -1,6 +1,6 @@
|
||||
[tox]
|
||||
minversion = 1.6
|
||||
envlist = py33,py34,py27,pep8
|
||||
envlist = py34,py27,pep8
|
||||
skipsdist = True
|
||||
|
||||
[testenv]
|
||||
@@ -32,12 +32,10 @@ commands = oslo_debug_helper {posargs}
|
||||
show-source = True
|
||||
ignore = E123,E125
|
||||
builtins = _
|
||||
exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build
|
||||
|
||||
[testenv:pypi]
|
||||
commands =
|
||||
python setup.py sdist bdist_wheel
|
||||
twine upload --config-file .pypirc {posargs} dist/*
|
||||
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build
|
||||
|
||||
[testenv:wheel]
|
||||
commands = python setup.py bdist_wheel
|
||||
|
||||
[hacking]
|
||||
import_exceptions = watcherclient._i18n
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
# under the License.
|
||||
|
||||
import pbr.version
|
||||
|
||||
from watcherclient import client
|
||||
from watcherclient import exceptions
|
||||
|
||||
|
||||
45
watcherclient/_i18n.py
Normal file
45
watcherclient/_i18n.py
Normal file
@@ -0,0 +1,45 @@
|
||||
# Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import oslo_i18n
|
||||
|
||||
DOMAIN = "watcherclient"
|
||||
|
||||
_translators = oslo_i18n.TranslatorFactory(domain=DOMAIN)
|
||||
|
||||
# The primary translation function using the well-known name "_"
|
||||
_ = _translators.primary
|
||||
|
||||
# The contextual translation function using the name "_C"
|
||||
# requires oslo.i18n >=2.1.0
|
||||
_C = _translators.contextual_form
|
||||
|
||||
# The plural translation function using the name "_P"
|
||||
# requires oslo.i18n >=2.1.0
|
||||
_P = _translators.plural_form
|
||||
|
||||
# Translators for log levels.
|
||||
#
|
||||
# The abbreviated names are meant to reflect the usual use of a short
|
||||
# name like '_'. The "L" is for "log" and the other letter comes from
|
||||
# the level.
|
||||
_LI = _translators.log_info
|
||||
_LW = _translators.log_warning
|
||||
_LE = _translators.log_error
|
||||
_LC = _translators.log_critical
|
||||
|
||||
|
||||
def get_available_languages():
|
||||
return oslo_i18n.get_available_languages(DOMAIN)
|
||||
@@ -13,13 +13,13 @@
|
||||
# under the License.
|
||||
|
||||
from keystoneclient.v2_0 import client as ksclient
|
||||
import oslo_i18n
|
||||
|
||||
from watcherclient._i18n import _
|
||||
from watcherclient.common import utils
|
||||
from watcherclient import exceptions as exc
|
||||
from watcherclient.openstack.common._i18n import _
|
||||
from watcherclient.openstack.common import gettextutils
|
||||
|
||||
gettextutils.install('watcherclient')
|
||||
oslo_i18n.install('watcherclient')
|
||||
|
||||
|
||||
def _get_ksclient(**kwargs):
|
||||
|
||||
@@ -44,8 +44,8 @@ from oslo_utils import strutils
|
||||
import six
|
||||
from six.moves.urllib import parse
|
||||
|
||||
from watcherclient.openstack.common._i18n import _
|
||||
from watcherclient.openstack.common.apiclient import exceptions
|
||||
from watcherclient._i18n import _
|
||||
from watcherclient.common.apiclient import exceptions
|
||||
|
||||
|
||||
def getid(obj):
|
||||
@@ -467,8 +467,7 @@ class Resource(object):
|
||||
|
||||
@property
|
||||
def human_id(self):
|
||||
"""Human-readable ID which can be used for bash completion.
|
||||
"""
|
||||
"""Human-readable ID which can be used for bash completion."""
|
||||
if self.HUMAN_ID:
|
||||
name = getattr(self, self.NAME_ATTR, None)
|
||||
if name is not None:
|
||||
@@ -38,12 +38,11 @@ import sys
|
||||
|
||||
import six
|
||||
|
||||
from watcherclient.openstack.common._i18n import _
|
||||
from watcherclient._i18n import _
|
||||
|
||||
|
||||
class ClientException(Exception):
|
||||
"""The base exception class for all exceptions this library raises.
|
||||
"""
|
||||
"""The base exception class for all exceptions this library raises."""
|
||||
pass
|
||||
|
||||
|
||||
@@ -118,8 +117,7 @@ class AmbiguousEndpoints(EndpointException):
|
||||
|
||||
|
||||
class HttpError(ClientException):
|
||||
"""The base exception class for all HTTP exceptions.
|
||||
"""
|
||||
"""The base exception class for all HTTP exceptions."""
|
||||
http_status = 0
|
||||
message = _("HTTP Error")
|
||||
|
||||
@@ -23,7 +23,7 @@ import copy
|
||||
|
||||
import six.moves.urllib.parse as urlparse
|
||||
|
||||
from watcherclient.openstack.common.apiclient import base
|
||||
from watcherclient.common.apiclient import base
|
||||
|
||||
|
||||
def getid(obj):
|
||||
|
||||
@@ -30,7 +30,7 @@ import prettytable
|
||||
import six
|
||||
from six import moves
|
||||
|
||||
from watcherclient.openstack.common._i18n import _
|
||||
from watcherclient._i18n import _
|
||||
|
||||
|
||||
class MissingArgs(Exception):
|
||||
@@ -169,15 +169,18 @@ def print_list(objs, fields, formatters=None, sortby_index=0,
|
||||
for o in objs:
|
||||
row = []
|
||||
for field in fields:
|
||||
data = '-'
|
||||
if field in formatters:
|
||||
row.append(formatters[field](o))
|
||||
data = formatters[field](o)
|
||||
else:
|
||||
if field in mixed_case_fields:
|
||||
field_name = field.replace(' ', '_')
|
||||
else:
|
||||
field_name = field.lower().replace(' ', '_')
|
||||
data = getattr(o, field_name, '')
|
||||
row.append(data)
|
||||
if data is None:
|
||||
data = '-'
|
||||
row.append(data)
|
||||
pt.add_row(row)
|
||||
|
||||
if six.PY3:
|
||||
@@ -210,6 +213,8 @@ def print_dict(dct, dict_property="Property", wrap=0):
|
||||
pt.add_row([col1, line])
|
||||
col1 = ''
|
||||
else:
|
||||
if v is None:
|
||||
v = '-'
|
||||
pt.add_row([k, v])
|
||||
|
||||
if six.PY3:
|
||||
@@ -267,5 +272,5 @@ def pretty_choice_list(l):
|
||||
|
||||
def exit(msg=''):
|
||||
if msg:
|
||||
print (msg, file=sys.stderr)
|
||||
print(msg, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
46
watcherclient/common/command.py
Normal file
46
watcherclient/common/command.py
Normal file
@@ -0,0 +1,46 @@
|
||||
# Copyright 2016 NEC Corporation
|
||||
#
|
||||
# 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 abc
|
||||
import logging
|
||||
|
||||
from cliff import command
|
||||
from cliff import lister
|
||||
from cliff import show
|
||||
import six
|
||||
|
||||
|
||||
class CommandMeta(abc.ABCMeta):
|
||||
|
||||
def __new__(mcs, name, bases, cls_dict):
|
||||
if 'log' not in cls_dict:
|
||||
cls_dict['log'] = logging.getLogger(
|
||||
cls_dict['__module__'] + '.' + name)
|
||||
return super(CommandMeta, mcs).__new__(mcs, name, bases, cls_dict)
|
||||
|
||||
|
||||
@six.add_metaclass(CommandMeta)
|
||||
class Command(command.Command):
|
||||
|
||||
def run(self, parsed_args):
|
||||
self.log.debug('run(%s)', parsed_args)
|
||||
return super(Command, self).run(parsed_args)
|
||||
|
||||
|
||||
class Lister(Command, lister.Lister):
|
||||
pass
|
||||
|
||||
|
||||
class ShowOne(Command, show.ShowOne):
|
||||
pass
|
||||
@@ -26,6 +26,7 @@ from keystoneclient import adapter
|
||||
import six
|
||||
import six.moves.urllib.parse as urlparse
|
||||
|
||||
from watcherclient._i18n import _, _LW, _LE
|
||||
from watcherclient import exceptions as exc
|
||||
|
||||
|
||||
@@ -44,6 +45,8 @@ def _trim_endpoint_api_version(url):
|
||||
def _extract_error_json(body):
|
||||
"""Return error_message from the HTTP response body."""
|
||||
error_json = {}
|
||||
if six.PY3 and not isinstance(body, six.string_types):
|
||||
body = body.decode("utf-8")
|
||||
try:
|
||||
body_json = json.loads(body)
|
||||
if 'error_message' in body_json:
|
||||
@@ -83,8 +86,8 @@ class HTTPClient(object):
|
||||
elif parts.scheme == 'http':
|
||||
_class = six.moves.http_client.HTTPConnection
|
||||
else:
|
||||
msg = 'Unsupported scheme: %s' % parts.scheme
|
||||
raise exc.EndpointException(msg)
|
||||
raise exc.EndpointException(
|
||||
_('Unsupported scheme: %s'), parts.scheme)
|
||||
|
||||
return (_class, _args, _kwargs)
|
||||
|
||||
@@ -157,15 +160,14 @@ class HTTPClient(object):
|
||||
conn.request(method, conn_url, **kwargs)
|
||||
resp = conn.getresponse()
|
||||
except socket.gaierror as e:
|
||||
message = ("Error finding address for %(url)s: %(e)s"
|
||||
% dict(url=url, e=e))
|
||||
raise exc.EndpointNotFound(message)
|
||||
raise exc.EndpointNotFound(
|
||||
_("Error finding address for %(url)s: %(e)s"),
|
||||
url=url, e=e)
|
||||
except (socket.error, socket.timeout) as e:
|
||||
endpoint = self.endpoint
|
||||
message = ("Error communicating with %(endpoint)s %(e)s"
|
||||
% dict(endpoint=endpoint, e=e))
|
||||
raise exc.ConnectionRefused(message)
|
||||
|
||||
raise exc.CommunicationError(
|
||||
_("Error communicating with %(endpoint)s %(e)s"),
|
||||
endpoint=endpoint, e=e)
|
||||
body_iter = ResponseBodyIterator(resp)
|
||||
|
||||
# Read body into string if it isn't obviously image data
|
||||
@@ -178,7 +180,7 @@ class HTTPClient(object):
|
||||
self.log_http_response(resp)
|
||||
|
||||
if 400 <= resp.status < 600:
|
||||
LOG.warn("Request returned failure status.")
|
||||
LOG.warning(_LW("Request returned failure status."))
|
||||
error_json = _extract_error_json(body_str)
|
||||
raise exc.from_response(
|
||||
resp, error_json.get('faultstring'),
|
||||
@@ -210,7 +212,7 @@ class HTTPClient(object):
|
||||
try:
|
||||
body = json.loads(body)
|
||||
except ValueError:
|
||||
LOG.error('Could not decode response body as JSON')
|
||||
LOG.error(_LE('Could not decode response body as JSON'))
|
||||
else:
|
||||
body = None
|
||||
|
||||
@@ -334,7 +336,7 @@ class SessionClient(adapter.LegacyJsonAdapter):
|
||||
try:
|
||||
body = resp.json()
|
||||
except ValueError:
|
||||
LOG.error('Could not decode response body as JSON')
|
||||
LOG.error(_LE('Could not decode response body as JSON'))
|
||||
else:
|
||||
body = None
|
||||
|
||||
@@ -360,6 +362,8 @@ class ResponseBodyIterator(object):
|
||||
def next(self):
|
||||
chunk = self.resp.read(CHUNKSIZE)
|
||||
if chunk:
|
||||
if six.PY3 and not isinstance(chunk, six.string_types):
|
||||
chunk = chunk.decode("utf-8")
|
||||
return chunk
|
||||
else:
|
||||
raise StopIteration()
|
||||
|
||||
@@ -19,10 +19,12 @@ from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import uuid
|
||||
|
||||
from oslo_utils import importutils
|
||||
|
||||
from watcherclient._i18n import _
|
||||
from watcherclient import exceptions as exc
|
||||
from watcherclient.openstack.common._i18n import _
|
||||
from watcherclient.openstack.common import importutils
|
||||
|
||||
|
||||
class HelpFormatter(argparse.HelpFormatter):
|
||||
@@ -177,3 +179,16 @@ def common_filters(limit=None, sort_key=None, sort_dir=None):
|
||||
if sort_dir is not None:
|
||||
filters.append('sort_dir=%s' % sort_dir)
|
||||
return filters
|
||||
|
||||
|
||||
def is_uuid_like(val):
|
||||
"""Returns validation of a value as a UUID.
|
||||
|
||||
For our purposes, a UUID is a canonical form string:
|
||||
aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
|
||||
|
||||
"""
|
||||
try:
|
||||
return str(uuid.UUID(val)) == val
|
||||
except (TypeError, ValueError, AttributeError):
|
||||
return False
|
||||
|
||||
@@ -12,21 +12,49 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from watcherclient.openstack.common.apiclient import exceptions
|
||||
from watcherclient.openstack.common.apiclient.exceptions import * # noqa
|
||||
from watcherclient.common.apiclient import exceptions
|
||||
|
||||
|
||||
# NOTE(akurilin): This alias is left here since v.0.1.3 to support backwards
|
||||
# compatibility.
|
||||
InvalidEndpoint = EndpointException
|
||||
CommunicationError = ConnectionRefused
|
||||
HTTPBadRequest = BadRequest
|
||||
HTTPInternalServerError = InternalServerError
|
||||
HTTPNotFound = NotFound
|
||||
HTTPServiceUnavailable = ServiceUnavailable
|
||||
InvalidEndpoint = exceptions.EndpointException
|
||||
CommunicationError = exceptions.ConnectionRefused
|
||||
HTTPBadRequest = exceptions.BadRequest
|
||||
HTTPInternalServerError = exceptions.InternalServerError
|
||||
HTTPNotFound = exceptions.NotFound
|
||||
HTTPServiceUnavailable = exceptions.ServiceUnavailable
|
||||
|
||||
|
||||
class AmbiguousAuthSystem(ClientException):
|
||||
CommandError = exceptions.CommandError
|
||||
"""Error in CLI tool.
|
||||
|
||||
An alias of :py:exc:`watcherclient.common.apiclient.CommandError`
|
||||
"""
|
||||
|
||||
Unauthorized = exceptions.Unauthorized
|
||||
"""HTTP 401 - Unauthorized.
|
||||
|
||||
Similar to 403 Forbidden, but specifically for use when authentication
|
||||
is required and has failed or has not yet been provided.
|
||||
An alias of :py:exc:`watcherclient.common.apiclient.Unauthorized`
|
||||
"""
|
||||
|
||||
InternalServerError = exceptions.InternalServerError
|
||||
"""HTTP 500 - Internal Server Error.
|
||||
|
||||
A generic error message, given when no more specific message is suitable.
|
||||
An alias of :py:exc:`watcherclient.common.apiclient.InternalServerError`
|
||||
"""
|
||||
|
||||
ValidationError = exceptions.ValidationError
|
||||
"""Error in validation on API client side.
|
||||
|
||||
A generic error message, given when no more specific message is suitable.
|
||||
An alias of :py:exc:`watcherclient.common.apiclient.ValidationError`
|
||||
"""
|
||||
|
||||
|
||||
class AmbiguousAuthSystem(exceptions.ClientException):
|
||||
"""Could not obtain token and endpoint using provided credentials."""
|
||||
pass
|
||||
|
||||
@@ -34,7 +62,7 @@ class AmbiguousAuthSystem(ClientException):
|
||||
AmbigiousAuthSystem = AmbiguousAuthSystem
|
||||
|
||||
|
||||
class InvalidAttribute(ClientException):
|
||||
class InvalidAttribute(exceptions.ClientException):
|
||||
pass
|
||||
|
||||
|
||||
@@ -65,7 +93,7 @@ def from_response(response, message=None, traceback=None, method=None,
|
||||
if (response.headers['Content-Type'].startswith('text/') and
|
||||
not hasattr(response, 'text')):
|
||||
# NOTE(clif_h): There seems to be a case in the
|
||||
# openstack.common.apiclient.exceptions module where if the
|
||||
# common.apiclient.exceptions module where if the
|
||||
# content-type of the response is text/* then it expects
|
||||
# the response to have a 'text' attribute, but that
|
||||
# doesn't always seem to necessarily be the case.
|
||||
|
||||
279
watcherclient/locale/fr/LC_MESSAGES/watcherclient.po
Normal file
279
watcherclient/locale/fr/LC_MESSAGES/watcherclient.po
Normal file
@@ -0,0 +1,279 @@
|
||||
# French translations for python-watcherclient.
|
||||
# Copyright (C) 2016 ORGANIZATION
|
||||
# This file is distributed under the same license as the
|
||||
# python-watcherclient project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2016.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: python-watcherclient 0.20.1.dev4\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2016-01-14 14:57+0100\n"
|
||||
"PO-Revision-Date: 2016-01-12 02:05+0100\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: fr\n"
|
||||
"Language-Team: fr <LL@li.org>\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.1.1\n"
|
||||
|
||||
#: watcherclient/client.py:103
|
||||
msgid "Must provide Keystone credentials or user-defined endpoint and token"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/shell.py:279
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Unable to determine the Keystone version to authenticate with using the "
|
||||
"given auth_url. Identity service may not support API version discovery. "
|
||||
"Please provide a versioned auth_url instead. %s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/shell.py:344
|
||||
msgid ""
|
||||
"Unable to determine the Keystone version to authenticate with using the "
|
||||
"given auth_url."
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/shell.py:380
|
||||
msgid ""
|
||||
"You must provide a username via either --os-username or via "
|
||||
"env[OS_USERNAME]"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/shell.py:396
|
||||
msgid ""
|
||||
"You must provide a password via either --os-password, env[OS_PASSWORD], "
|
||||
"or prompted response"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/shell.py:403
|
||||
msgid ""
|
||||
"You must provide a project name or project id via --os-project-name, "
|
||||
"--os-project-id, env[OS_PROJECT_ID] or env[OS_PROJECT_NAME]. You may use"
|
||||
" os-project and os-tenant interchangeably."
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/shell.py:410
|
||||
msgid ""
|
||||
"You must provide an auth url via either --os-auth-url or via "
|
||||
"env[OS_AUTH_URL]"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/shell.py:473
|
||||
msgid "Invalid OpenStack Identity credentials"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/shell.py:483
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid subcommand"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/cliutils.py:40
|
||||
#, python-format
|
||||
msgid "Missing arguments: %s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/cliutils.py:158
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Field labels list %(labels)s has different number of elements than fields"
|
||||
" list %(fields)s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/http.py:88
|
||||
#, python-format
|
||||
msgid "Unsupported scheme: %s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/http.py:162
|
||||
#, python-format
|
||||
msgid "Error finding address for %(url)s: %(e)s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/http.py:167
|
||||
#, python-format
|
||||
msgid "Error communicating with %(endpoint)s %(e)s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/http.py:181
|
||||
msgid "Request returned failure status."
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/http.py:213 watcherclient/common/http.py:337
|
||||
msgid "Could not decode response body as JSON"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/utils.py:87
|
||||
#, python-format
|
||||
msgid "Attributes must be a list of PATH=VALUE not \"%s\""
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/utils.py:120
|
||||
#, python-format
|
||||
msgid "Unknown PATCH operation: %s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/utils.py:136
|
||||
#, python-format
|
||||
msgid "Expected non-negative --limit, got %s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/utils.py:147
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%(sort_key)s is an invalid field for sorting, valid values for --sort-key"
|
||||
" are: %(valid)s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/utils.py:155
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%s is an invalid value for sort direction, valid values for --sort-dir "
|
||||
"are: 'asc', 'desc'"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/base.py:244
|
||||
#: watcherclient/common/apiclient/base.py:401
|
||||
#, python-format
|
||||
msgid "No %(name)s matching %(args)s."
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:83
|
||||
#, python-format
|
||||
msgid "Authentication failed. Missing options: %s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:92
|
||||
#, python-format
|
||||
msgid "AuthSystemNotFound: %r"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:115
|
||||
#, python-format
|
||||
msgid "AmbiguousEndpoints: %r"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:122
|
||||
msgid "HTTP Error"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:142
|
||||
msgid "HTTP Redirection"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:150
|
||||
msgid "HTTP Client Error"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:159
|
||||
msgid "HTTP Server Error"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:169
|
||||
msgid "Multiple Choices"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:178
|
||||
msgid "Bad Request"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:188
|
||||
msgid "Unauthorized"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:197
|
||||
msgid "Payment Required"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:207
|
||||
msgid "Forbidden"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:217
|
||||
msgid "Not Found"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:227
|
||||
msgid "Method Not Allowed"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:237
|
||||
msgid "Not Acceptable"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:246
|
||||
msgid "Proxy Authentication Required"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:255
|
||||
msgid "Request Timeout"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:265
|
||||
msgid "Conflict"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:275
|
||||
msgid "Gone"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:285
|
||||
msgid "Length Required"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:295
|
||||
msgid "Precondition Failed"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:304
|
||||
msgid "Request Entity Too Large"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:321
|
||||
msgid "Request-URI Too Long"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:331
|
||||
msgid "Unsupported Media Type"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:341
|
||||
msgid "Requested Range Not Satisfiable"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:350
|
||||
msgid "Expectation Failed"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:360
|
||||
msgid "Unprocessable Entity"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:369
|
||||
msgid "Internal Server Error"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:380
|
||||
msgid "Not Implemented"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:390
|
||||
msgid "Bad Gateway"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:399
|
||||
msgid "Service Unavailable"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:409
|
||||
msgid "Gateway Timeout"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:418
|
||||
msgid "HTTP Version Not Supported"
|
||||
msgstr ""
|
||||
|
||||
277
watcherclient/locale/watcherclient.pot
Normal file
277
watcherclient/locale/watcherclient.pot
Normal file
@@ -0,0 +1,277 @@
|
||||
# Translations template for python-watcherclient.
|
||||
# Copyright (C) 2016 ORGANIZATION
|
||||
# This file is distributed under the same license as the
|
||||
# python-watcherclient project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2016.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: python-watcherclient 0.20.1.dev5\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2016-01-14 14:57+0100\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.1.1\n"
|
||||
|
||||
#: watcherclient/client.py:103
|
||||
msgid "Must provide Keystone credentials or user-defined endpoint and token"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/shell.py:279
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Unable to determine the Keystone version to authenticate with using the "
|
||||
"given auth_url. Identity service may not support API version discovery. "
|
||||
"Please provide a versioned auth_url instead. %s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/shell.py:344
|
||||
msgid ""
|
||||
"Unable to determine the Keystone version to authenticate with using the "
|
||||
"given auth_url."
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/shell.py:380
|
||||
msgid ""
|
||||
"You must provide a username via either --os-username or via "
|
||||
"env[OS_USERNAME]"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/shell.py:396
|
||||
msgid ""
|
||||
"You must provide a password via either --os-password, env[OS_PASSWORD], "
|
||||
"or prompted response"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/shell.py:403
|
||||
msgid ""
|
||||
"You must provide a project name or project id via --os-project-name, "
|
||||
"--os-project-id, env[OS_PROJECT_ID] or env[OS_PROJECT_NAME]. You may use"
|
||||
" os-project and os-tenant interchangeably."
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/shell.py:410
|
||||
msgid ""
|
||||
"You must provide an auth url via either --os-auth-url or via "
|
||||
"env[OS_AUTH_URL]"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/shell.py:473
|
||||
msgid "Invalid OpenStack Identity credentials"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/shell.py:483
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid subcommand"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/cliutils.py:40
|
||||
#, python-format
|
||||
msgid "Missing arguments: %s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/cliutils.py:158
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Field labels list %(labels)s has different number of elements than fields"
|
||||
" list %(fields)s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/http.py:88
|
||||
#, python-format
|
||||
msgid "Unsupported scheme: %s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/http.py:162
|
||||
#, python-format
|
||||
msgid "Error finding address for %(url)s: %(e)s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/http.py:167
|
||||
#, python-format
|
||||
msgid "Error communicating with %(endpoint)s %(e)s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/http.py:181
|
||||
msgid "Request returned failure status."
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/http.py:213 watcherclient/common/http.py:337
|
||||
msgid "Could not decode response body as JSON"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/utils.py:87
|
||||
#, python-format
|
||||
msgid "Attributes must be a list of PATH=VALUE not \"%s\""
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/utils.py:120
|
||||
#, python-format
|
||||
msgid "Unknown PATCH operation: %s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/utils.py:136
|
||||
#, python-format
|
||||
msgid "Expected non-negative --limit, got %s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/utils.py:147
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%(sort_key)s is an invalid field for sorting, valid values for --sort-key"
|
||||
" are: %(valid)s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/utils.py:155
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%s is an invalid value for sort direction, valid values for --sort-dir "
|
||||
"are: 'asc', 'desc'"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/base.py:244
|
||||
#: watcherclient/common/apiclient/base.py:401
|
||||
#, python-format
|
||||
msgid "No %(name)s matching %(args)s."
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:83
|
||||
#, python-format
|
||||
msgid "Authentication failed. Missing options: %s"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:92
|
||||
#, python-format
|
||||
msgid "AuthSystemNotFound: %r"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:115
|
||||
#, python-format
|
||||
msgid "AmbiguousEndpoints: %r"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:122
|
||||
msgid "HTTP Error"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:142
|
||||
msgid "HTTP Redirection"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:150
|
||||
msgid "HTTP Client Error"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:159
|
||||
msgid "HTTP Server Error"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:169
|
||||
msgid "Multiple Choices"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:178
|
||||
msgid "Bad Request"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:188
|
||||
msgid "Unauthorized"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:197
|
||||
msgid "Payment Required"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:207
|
||||
msgid "Forbidden"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:217
|
||||
msgid "Not Found"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:227
|
||||
msgid "Method Not Allowed"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:237
|
||||
msgid "Not Acceptable"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:246
|
||||
msgid "Proxy Authentication Required"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:255
|
||||
msgid "Request Timeout"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:265
|
||||
msgid "Conflict"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:275
|
||||
msgid "Gone"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:285
|
||||
msgid "Length Required"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:295
|
||||
msgid "Precondition Failed"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:304
|
||||
msgid "Request Entity Too Large"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:321
|
||||
msgid "Request-URI Too Long"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:331
|
||||
msgid "Unsupported Media Type"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:341
|
||||
msgid "Requested Range Not Satisfiable"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:350
|
||||
msgid "Expectation Failed"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:360
|
||||
msgid "Unprocessable Entity"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:369
|
||||
msgid "Internal Server Error"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:380
|
||||
msgid "Not Implemented"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:390
|
||||
msgid "Bad Gateway"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:399
|
||||
msgid "Service Unavailable"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:409
|
||||
msgid "Gateway Timeout"
|
||||
msgstr ""
|
||||
|
||||
#: watcherclient/common/apiclient/exceptions.py:418
|
||||
msgid "HTTP Version Not Supported"
|
||||
msgstr ""
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""oslo.i18n integration module.
|
||||
|
||||
See http://docs.openstack.org/developer/oslo.i18n/usage.html
|
||||
|
||||
"""
|
||||
|
||||
try:
|
||||
import oslo_i18n
|
||||
|
||||
# NOTE(dhellmann): This reference to o-s-l-o will be replaced by the
|
||||
# application name when this module is synced into the separate
|
||||
# repository. It is OK to have more than one translation function
|
||||
# using the same domain, since there will still only be one message
|
||||
# catalog.
|
||||
_translators = oslo_i18n.TranslatorFactory(domain='watcherclient')
|
||||
|
||||
# The primary translation function using the well-known name "_"
|
||||
_ = _translators.primary
|
||||
|
||||
# Translators for log levels.
|
||||
#
|
||||
# The abbreviated names are meant to reflect the usual use of a short
|
||||
# name like '_'. The "L" is for "log" and the other letter comes from
|
||||
# the level.
|
||||
_LI = _translators.log_info
|
||||
_LW = _translators.log_warning
|
||||
_LE = _translators.log_error
|
||||
_LC = _translators.log_critical
|
||||
except ImportError:
|
||||
# NOTE(dims): Support for cases where a project wants to use
|
||||
# code from oslo-incubator, but is not ready to be internationalized
|
||||
# (like tempest)
|
||||
_ = _LI = _LW = _LE = _LC = lambda x: x
|
||||
@@ -1,234 +0,0 @@
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# Copyright 2013 Spanish National Research Council.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
# E0202: An attribute inherited from %s hide this method
|
||||
# pylint: disable=E0202
|
||||
|
||||
########################################################################
|
||||
#
|
||||
# THIS MODULE IS DEPRECATED
|
||||
#
|
||||
# Please refer to
|
||||
# https://etherpad.openstack.org/p/kilo-watcherclient-library-proposals for
|
||||
# the discussion leading to this deprecation.
|
||||
#
|
||||
# We recommend checking out the python-openstacksdk project
|
||||
# (https://launchpad.net/python-openstacksdk) instead.
|
||||
#
|
||||
########################################################################
|
||||
|
||||
import abc
|
||||
import argparse
|
||||
import os
|
||||
|
||||
import six
|
||||
from stevedore import extension
|
||||
|
||||
from watcherclient.openstack.common.apiclient import exceptions
|
||||
|
||||
|
||||
_discovered_plugins = {}
|
||||
|
||||
|
||||
def discover_auth_systems():
|
||||
"""Discover the available auth-systems.
|
||||
|
||||
This won't take into account the old style auth-systems.
|
||||
"""
|
||||
global _discovered_plugins
|
||||
_discovered_plugins = {}
|
||||
|
||||
def add_plugin(ext):
|
||||
_discovered_plugins[ext.name] = ext.plugin
|
||||
|
||||
ep_namespace = "watcherclient.openstack.common.apiclient.auth"
|
||||
mgr = extension.ExtensionManager(ep_namespace)
|
||||
mgr.map(add_plugin)
|
||||
|
||||
|
||||
def load_auth_system_opts(parser):
|
||||
"""Load options needed by the available auth-systems into a parser.
|
||||
|
||||
This function will try to populate the parser with options from the
|
||||
available plugins.
|
||||
"""
|
||||
group = parser.add_argument_group("Common auth options")
|
||||
BaseAuthPlugin.add_common_opts(group)
|
||||
for name, auth_plugin in six.iteritems(_discovered_plugins):
|
||||
group = parser.add_argument_group(
|
||||
"Auth-system '%s' options" % name,
|
||||
conflict_handler="resolve")
|
||||
auth_plugin.add_opts(group)
|
||||
|
||||
|
||||
def load_plugin(auth_system):
|
||||
try:
|
||||
plugin_class = _discovered_plugins[auth_system]
|
||||
except KeyError:
|
||||
raise exceptions.AuthSystemNotFound(auth_system)
|
||||
return plugin_class(auth_system=auth_system)
|
||||
|
||||
|
||||
def load_plugin_from_args(args):
|
||||
"""Load required plugin and populate it with options.
|
||||
|
||||
Try to guess auth system if it is not specified. Systems are tried in
|
||||
alphabetical order.
|
||||
|
||||
:type args: argparse.Namespace
|
||||
:raises: AuthPluginOptionsMissing
|
||||
"""
|
||||
auth_system = args.os_auth_system
|
||||
if auth_system:
|
||||
plugin = load_plugin(auth_system)
|
||||
plugin.parse_opts(args)
|
||||
plugin.sufficient_options()
|
||||
return plugin
|
||||
|
||||
for plugin_auth_system in sorted(six.iterkeys(_discovered_plugins)):
|
||||
plugin_class = _discovered_plugins[plugin_auth_system]
|
||||
plugin = plugin_class()
|
||||
plugin.parse_opts(args)
|
||||
try:
|
||||
plugin.sufficient_options()
|
||||
except exceptions.AuthPluginOptionsMissing:
|
||||
continue
|
||||
return plugin
|
||||
raise exceptions.AuthPluginOptionsMissing(["auth_system"])
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BaseAuthPlugin(object):
|
||||
"""Base class for authentication plugins.
|
||||
|
||||
An authentication plugin needs to override at least the authenticate
|
||||
method to be a valid plugin.
|
||||
"""
|
||||
|
||||
auth_system = None
|
||||
opt_names = []
|
||||
common_opt_names = [
|
||||
"auth_system",
|
||||
"username",
|
||||
"password",
|
||||
"tenant_name",
|
||||
"token",
|
||||
"auth_url",
|
||||
]
|
||||
|
||||
def __init__(self, auth_system=None, **kwargs):
|
||||
self.auth_system = auth_system or self.auth_system
|
||||
self.opts = dict((name, kwargs.get(name))
|
||||
for name in self.opt_names)
|
||||
|
||||
@staticmethod
|
||||
def _parser_add_opt(parser, opt):
|
||||
"""Add an option to parser in two variants.
|
||||
|
||||
:param opt: option name (with underscores)
|
||||
"""
|
||||
dashed_opt = opt.replace("_", "-")
|
||||
env_var = "OS_%s" % opt.upper()
|
||||
arg_default = os.environ.get(env_var, "")
|
||||
arg_help = "Defaults to env[%s]." % env_var
|
||||
parser.add_argument(
|
||||
"--os-%s" % dashed_opt,
|
||||
metavar="<%s>" % dashed_opt,
|
||||
default=arg_default,
|
||||
help=arg_help)
|
||||
parser.add_argument(
|
||||
"--os_%s" % opt,
|
||||
metavar="<%s>" % dashed_opt,
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
@classmethod
|
||||
def add_opts(cls, parser):
|
||||
"""Populate the parser with the options for this plugin.
|
||||
"""
|
||||
for opt in cls.opt_names:
|
||||
# use `BaseAuthPlugin.common_opt_names` since it is never
|
||||
# changed in child classes
|
||||
if opt not in BaseAuthPlugin.common_opt_names:
|
||||
cls._parser_add_opt(parser, opt)
|
||||
|
||||
@classmethod
|
||||
def add_common_opts(cls, parser):
|
||||
"""Add options that are common for several plugins.
|
||||
"""
|
||||
for opt in cls.common_opt_names:
|
||||
cls._parser_add_opt(parser, opt)
|
||||
|
||||
@staticmethod
|
||||
def get_opt(opt_name, args):
|
||||
"""Return option name and value.
|
||||
|
||||
:param opt_name: name of the option, e.g., "username"
|
||||
:param args: parsed arguments
|
||||
"""
|
||||
return (opt_name, getattr(args, "os_%s" % opt_name, None))
|
||||
|
||||
def parse_opts(self, args):
|
||||
"""Parse the actual auth-system options if any.
|
||||
|
||||
This method is expected to populate the attribute `self.opts` with a
|
||||
dict containing the options and values needed to make authentication.
|
||||
"""
|
||||
self.opts.update(dict(self.get_opt(opt_name, args)
|
||||
for opt_name in self.opt_names))
|
||||
|
||||
def authenticate(self, http_client):
|
||||
"""Authenticate using plugin defined method.
|
||||
|
||||
The method usually analyses `self.opts` and performs
|
||||
a request to authentication server.
|
||||
|
||||
:param http_client: client object that needs authentication
|
||||
:type http_client: HTTPClient
|
||||
:raises: AuthorizationFailure
|
||||
"""
|
||||
self.sufficient_options()
|
||||
self._do_authenticate(http_client)
|
||||
|
||||
@abc.abstractmethod
|
||||
def _do_authenticate(self, http_client):
|
||||
"""Protected method for authentication.
|
||||
"""
|
||||
|
||||
def sufficient_options(self):
|
||||
"""Check if all required options are present.
|
||||
|
||||
:raises: AuthPluginOptionsMissing
|
||||
"""
|
||||
missing = [opt
|
||||
for opt in self.opt_names
|
||||
if not self.opts.get(opt)]
|
||||
if missing:
|
||||
raise exceptions.AuthPluginOptionsMissing(missing)
|
||||
|
||||
@abc.abstractmethod
|
||||
def token_and_endpoint(self, endpoint_type, service_type):
|
||||
"""Return token and endpoint.
|
||||
|
||||
:param service_type: Service type of the endpoint
|
||||
:type service_type: string
|
||||
:param endpoint_type: Type of endpoint.
|
||||
Possible values: public or publicURL,
|
||||
internal or internalURL,
|
||||
admin or adminURL
|
||||
:type endpoint_type: string
|
||||
:returns: tuple of token and endpoint strings
|
||||
:raises: EndpointException
|
||||
"""
|
||||
@@ -1,388 +0,0 @@
|
||||
# Copyright 2010 Jacob Kaplan-Moss
|
||||
# Copyright 2011 OpenStack Foundation
|
||||
# Copyright 2011 Piston Cloud Computing, Inc.
|
||||
# Copyright 2013 Alessio Ababilov
|
||||
# Copyright 2013 Grid Dynamics
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
OpenStack Client interface. Handles the REST calls and responses.
|
||||
"""
|
||||
|
||||
# E0202: An attribute inherited from %s hide this method
|
||||
# pylint: disable=E0202
|
||||
|
||||
import hashlib
|
||||
import logging
|
||||
import time
|
||||
|
||||
try:
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
import json
|
||||
|
||||
from oslo_utils import encodeutils
|
||||
from oslo_utils import importutils
|
||||
import requests
|
||||
|
||||
from watcherclient.openstack.common._i18n import _
|
||||
from watcherclient.openstack.common.apiclient import exceptions
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
SENSITIVE_HEADERS = ('X-Auth-Token', 'X-Subject-Token',)
|
||||
|
||||
|
||||
class HTTPClient(object):
|
||||
"""This client handles sending HTTP requests to OpenStack servers.
|
||||
|
||||
Features:
|
||||
|
||||
- share authentication information between several clients to different
|
||||
services (e.g., for compute and image clients);
|
||||
- reissue authentication request for expired tokens;
|
||||
- encode/decode JSON bodies;
|
||||
- raise exceptions on HTTP errors;
|
||||
- pluggable authentication;
|
||||
- store authentication information in a keyring;
|
||||
- store time spent for requests;
|
||||
- register clients for particular services, so one can use
|
||||
`http_client.identity` or `http_client.compute`;
|
||||
- log requests and responses in a format that is easy to copy-and-paste
|
||||
into terminal and send the same request with curl.
|
||||
"""
|
||||
|
||||
user_agent = "watcherclient.openstack.common.apiclient"
|
||||
|
||||
def __init__(self,
|
||||
auth_plugin,
|
||||
region_name=None,
|
||||
endpoint_type="publicURL",
|
||||
original_ip=None,
|
||||
verify=True,
|
||||
cert=None,
|
||||
timeout=None,
|
||||
timings=False,
|
||||
keyring_saver=None,
|
||||
debug=False,
|
||||
user_agent=None,
|
||||
http=None):
|
||||
self.auth_plugin = auth_plugin
|
||||
|
||||
self.endpoint_type = endpoint_type
|
||||
self.region_name = region_name
|
||||
|
||||
self.original_ip = original_ip
|
||||
self.timeout = timeout
|
||||
self.verify = verify
|
||||
self.cert = cert
|
||||
|
||||
self.keyring_saver = keyring_saver
|
||||
self.debug = debug
|
||||
self.user_agent = user_agent or self.user_agent
|
||||
|
||||
self.times = [] # [("item", starttime, endtime), ...]
|
||||
self.timings = timings
|
||||
|
||||
# requests within the same session can reuse TCP connections from pool
|
||||
self.http = http or requests.Session()
|
||||
|
||||
self.cached_token = None
|
||||
self.last_request_id = None
|
||||
|
||||
def _safe_header(self, name, value):
|
||||
if name in SENSITIVE_HEADERS:
|
||||
# because in python3 byte string handling is ... ug
|
||||
v = value.encode('utf-8')
|
||||
h = hashlib.sha1(v)
|
||||
d = h.hexdigest()
|
||||
return encodeutils.safe_decode(name), "{SHA1}%s" % d
|
||||
else:
|
||||
return (encodeutils.safe_decode(name),
|
||||
encodeutils.safe_decode(value))
|
||||
|
||||
def _http_log_req(self, method, url, kwargs):
|
||||
if not self.debug:
|
||||
return
|
||||
|
||||
string_parts = [
|
||||
"curl -g -i",
|
||||
"-X '%s'" % method,
|
||||
"'%s'" % url,
|
||||
]
|
||||
|
||||
for element in kwargs['headers']:
|
||||
header = ("-H '%s: %s'" %
|
||||
self._safe_header(element, kwargs['headers'][element]))
|
||||
string_parts.append(header)
|
||||
|
||||
_logger.debug("REQ: %s" % " ".join(string_parts))
|
||||
if 'data' in kwargs:
|
||||
_logger.debug("REQ BODY: %s\n" % (kwargs['data']))
|
||||
|
||||
def _http_log_resp(self, resp):
|
||||
if not self.debug:
|
||||
return
|
||||
_logger.debug(
|
||||
"RESP: [%s] %s\n",
|
||||
resp.status_code,
|
||||
resp.headers)
|
||||
if resp._content_consumed:
|
||||
_logger.debug(
|
||||
"RESP BODY: %s\n",
|
||||
resp.text)
|
||||
|
||||
def serialize(self, kwargs):
|
||||
if kwargs.get('json') is not None:
|
||||
kwargs['headers']['Content-Type'] = 'application/json'
|
||||
kwargs['data'] = json.dumps(kwargs['json'])
|
||||
try:
|
||||
del kwargs['json']
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def get_timings(self):
|
||||
return self.times
|
||||
|
||||
def reset_timings(self):
|
||||
self.times = []
|
||||
|
||||
def request(self, method, url, **kwargs):
|
||||
"""Send an http request with the specified characteristics.
|
||||
|
||||
Wrapper around `requests.Session.request` to handle tasks such as
|
||||
setting headers, JSON encoding/decoding, and error handling.
|
||||
|
||||
:param method: method of HTTP request
|
||||
:param url: URL of HTTP request
|
||||
:param kwargs: any other parameter that can be passed to
|
||||
requests.Session.request (such as `headers`) or `json`
|
||||
that will be encoded as JSON and used as `data` argument
|
||||
"""
|
||||
kwargs.setdefault("headers", {})
|
||||
kwargs["headers"]["User-Agent"] = self.user_agent
|
||||
if self.original_ip:
|
||||
kwargs["headers"]["Forwarded"] = "for=%s;by=%s" % (
|
||||
self.original_ip, self.user_agent)
|
||||
if self.timeout is not None:
|
||||
kwargs.setdefault("timeout", self.timeout)
|
||||
kwargs.setdefault("verify", self.verify)
|
||||
if self.cert is not None:
|
||||
kwargs.setdefault("cert", self.cert)
|
||||
self.serialize(kwargs)
|
||||
|
||||
self._http_log_req(method, url, kwargs)
|
||||
if self.timings:
|
||||
start_time = time.time()
|
||||
resp = self.http.request(method, url, **kwargs)
|
||||
if self.timings:
|
||||
self.times.append(("%s %s" % (method, url),
|
||||
start_time, time.time()))
|
||||
self._http_log_resp(resp)
|
||||
|
||||
self.last_request_id = resp.headers.get('x-openstack-request-id')
|
||||
|
||||
if resp.status_code >= 400:
|
||||
_logger.debug(
|
||||
"Request returned failure status: %s",
|
||||
resp.status_code)
|
||||
raise exceptions.from_response(resp, method, url)
|
||||
|
||||
return resp
|
||||
|
||||
@staticmethod
|
||||
def concat_url(endpoint, url):
|
||||
"""Concatenate endpoint and final URL.
|
||||
|
||||
E.g., "http://keystone/v2.0/" and "/tokens" are concatenated to
|
||||
"http://keystone/v2.0/tokens".
|
||||
|
||||
:param endpoint: the base URL
|
||||
:param url: the final URL
|
||||
"""
|
||||
return "%s/%s" % (endpoint.rstrip("/"), url.strip("/"))
|
||||
|
||||
def client_request(self, client, method, url, **kwargs):
|
||||
"""Send an http request using `client`'s endpoint and specified `url`.
|
||||
|
||||
If request was rejected as unauthorized (possibly because the token is
|
||||
expired), issue one authorization attempt and send the request once
|
||||
again.
|
||||
|
||||
:param client: instance of BaseClient descendant
|
||||
:param method: method of HTTP request
|
||||
:param url: URL of HTTP request
|
||||
:param kwargs: any other parameter that can be passed to
|
||||
`HTTPClient.request`
|
||||
"""
|
||||
|
||||
filter_args = {
|
||||
"endpoint_type": client.endpoint_type or self.endpoint_type,
|
||||
"service_type": client.service_type,
|
||||
}
|
||||
token, endpoint = (self.cached_token, client.cached_endpoint)
|
||||
just_authenticated = False
|
||||
if not (token and endpoint):
|
||||
try:
|
||||
token, endpoint = self.auth_plugin.token_and_endpoint(
|
||||
**filter_args)
|
||||
except exceptions.EndpointException:
|
||||
pass
|
||||
if not (token and endpoint):
|
||||
self.authenticate()
|
||||
just_authenticated = True
|
||||
token, endpoint = self.auth_plugin.token_and_endpoint(
|
||||
**filter_args)
|
||||
if not (token and endpoint):
|
||||
raise exceptions.AuthorizationFailure(
|
||||
_("Cannot find endpoint or token for request"))
|
||||
|
||||
old_token_endpoint = (token, endpoint)
|
||||
kwargs.setdefault("headers", {})["X-Auth-Token"] = token
|
||||
self.cached_token = token
|
||||
client.cached_endpoint = endpoint
|
||||
# Perform the request once. If we get Unauthorized, then it
|
||||
# might be because the auth token expired, so try to
|
||||
# re-authenticate and try again. If it still fails, bail.
|
||||
try:
|
||||
return self.request(
|
||||
method, self.concat_url(endpoint, url), **kwargs)
|
||||
except exceptions.Unauthorized as unauth_ex:
|
||||
if just_authenticated:
|
||||
raise
|
||||
self.cached_token = None
|
||||
client.cached_endpoint = None
|
||||
if self.auth_plugin.opts.get('token'):
|
||||
self.auth_plugin.opts['token'] = None
|
||||
if self.auth_plugin.opts.get('endpoint'):
|
||||
self.auth_plugin.opts['endpoint'] = None
|
||||
self.authenticate()
|
||||
try:
|
||||
token, endpoint = self.auth_plugin.token_and_endpoint(
|
||||
**filter_args)
|
||||
except exceptions.EndpointException:
|
||||
raise unauth_ex
|
||||
if (not (token and endpoint) or
|
||||
old_token_endpoint == (token, endpoint)):
|
||||
raise unauth_ex
|
||||
self.cached_token = token
|
||||
client.cached_endpoint = endpoint
|
||||
kwargs["headers"]["X-Auth-Token"] = token
|
||||
return self.request(
|
||||
method, self.concat_url(endpoint, url), **kwargs)
|
||||
|
||||
def add_client(self, base_client_instance):
|
||||
"""Add a new instance of :class:`BaseClient` descendant.
|
||||
|
||||
`self` will store a reference to `base_client_instance`.
|
||||
|
||||
Example:
|
||||
|
||||
>>> def test_clients():
|
||||
... from keystoneclient.auth import keystone
|
||||
... from openstack.common.apiclient import client
|
||||
... auth = keystone.KeystoneAuthPlugin(
|
||||
... username="user", password="pass", tenant_name="tenant",
|
||||
... auth_url="http://auth:5000/v2.0")
|
||||
... openstack_client = client.HTTPClient(auth)
|
||||
... # create nova client
|
||||
... from novaclient.v1_1 import client
|
||||
... client.Client(openstack_client)
|
||||
... # create keystone client
|
||||
... from keystoneclient.v2_0 import client
|
||||
... client.Client(openstack_client)
|
||||
... # use them
|
||||
... openstack_client.identity.tenants.list()
|
||||
... openstack_client.compute.servers.list()
|
||||
"""
|
||||
service_type = base_client_instance.service_type
|
||||
if service_type and not hasattr(self, service_type):
|
||||
setattr(self, service_type, base_client_instance)
|
||||
|
||||
def authenticate(self):
|
||||
self.auth_plugin.authenticate(self)
|
||||
# Store the authentication results in the keyring for later requests
|
||||
if self.keyring_saver:
|
||||
self.keyring_saver.save(self)
|
||||
|
||||
|
||||
class BaseClient(object):
|
||||
"""Top-level object to access the OpenStack API.
|
||||
|
||||
This client uses :class:`HTTPClient` to send requests. :class:`HTTPClient`
|
||||
will handle a bunch of issues such as authentication.
|
||||
"""
|
||||
|
||||
service_type = None
|
||||
endpoint_type = None # "publicURL" will be used
|
||||
cached_endpoint = None
|
||||
|
||||
def __init__(self, http_client, extensions=None):
|
||||
self.http_client = http_client
|
||||
http_client.add_client(self)
|
||||
|
||||
# Add in any extensions...
|
||||
if extensions:
|
||||
for extension in extensions:
|
||||
if extension.manager_class:
|
||||
setattr(self, extension.name,
|
||||
extension.manager_class(self))
|
||||
|
||||
def client_request(self, method, url, **kwargs):
|
||||
return self.http_client.client_request(
|
||||
self, method, url, **kwargs)
|
||||
|
||||
@property
|
||||
def last_request_id(self):
|
||||
return self.http_client.last_request_id
|
||||
|
||||
def head(self, url, **kwargs):
|
||||
return self.client_request("HEAD", url, **kwargs)
|
||||
|
||||
def get(self, url, **kwargs):
|
||||
return self.client_request("GET", url, **kwargs)
|
||||
|
||||
def post(self, url, **kwargs):
|
||||
return self.client_request("POST", url, **kwargs)
|
||||
|
||||
def put(self, url, **kwargs):
|
||||
return self.client_request("PUT", url, **kwargs)
|
||||
|
||||
def delete(self, url, **kwargs):
|
||||
return self.client_request("DELETE", url, **kwargs)
|
||||
|
||||
def patch(self, url, **kwargs):
|
||||
return self.client_request("PATCH", url, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def get_class(api_name, version, version_map):
|
||||
"""Returns the client class for the requested API version
|
||||
|
||||
:param api_name: the name of the API, e.g. 'compute', 'image', etc
|
||||
:param version: the requested API version
|
||||
:param version_map: a dict of client classes keyed by version
|
||||
:rtype: a client class for the requested API version
|
||||
"""
|
||||
try:
|
||||
client_path = version_map[str(version)]
|
||||
except (KeyError, ValueError):
|
||||
msg = _("Invalid %(api_name)s client version '%(version)s'. "
|
||||
"Must be one of: %(version_map)s") % {
|
||||
'api_name': api_name,
|
||||
'version': version,
|
||||
'version_map': ', '.join(version_map.keys())}
|
||||
raise exceptions.UnsupportedVersion(msg)
|
||||
|
||||
return importutils.import_class(client_path)
|
||||
@@ -1,190 +0,0 @@
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
A fake server that "responds" to API methods with pre-canned responses.
|
||||
|
||||
All of these responses come from the spec, so if for some reason the spec's
|
||||
wrong the tests might raise AssertionError. I've indicated in comments the
|
||||
places where actual behavior differs from the spec.
|
||||
"""
|
||||
|
||||
########################################################################
|
||||
#
|
||||
# THIS MODULE IS DEPRECATED
|
||||
#
|
||||
# Please refer to
|
||||
# https://etherpad.openstack.org/p/kilo-watcherclient-library-proposals for
|
||||
# the discussion leading to this deprecation.
|
||||
#
|
||||
# We recommend checking out the python-openstacksdk project
|
||||
# (https://launchpad.net/python-openstacksdk) instead.
|
||||
#
|
||||
########################################################################
|
||||
|
||||
# W0102: Dangerous default value %s as argument
|
||||
# pylint: disable=W0102
|
||||
|
||||
import json
|
||||
|
||||
import requests
|
||||
import six
|
||||
from six.moves.urllib import parse
|
||||
|
||||
from watcherclient.openstack.common.apiclient import client
|
||||
|
||||
|
||||
def assert_has_keys(dct, required=None, optional=None):
|
||||
required = required or []
|
||||
optional = optional or []
|
||||
for k in required:
|
||||
try:
|
||||
assert k in dct
|
||||
except AssertionError:
|
||||
extra_keys = set(dct.keys()).difference(set(required + optional))
|
||||
raise AssertionError("found unexpected keys: %s" %
|
||||
list(extra_keys))
|
||||
|
||||
|
||||
class TestResponse(requests.Response):
|
||||
"""Wrap requests.Response and provide a convenient initialization.
|
||||
"""
|
||||
|
||||
def __init__(self, data):
|
||||
super(TestResponse, self).__init__()
|
||||
self._content_consumed = True
|
||||
if isinstance(data, dict):
|
||||
self.status_code = data.get('status_code', 200)
|
||||
# Fake the text attribute to streamline Response creation
|
||||
text = data.get('text', "")
|
||||
if isinstance(text, (dict, list)):
|
||||
self._content = json.dumps(text)
|
||||
default_headers = {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
else:
|
||||
self._content = text
|
||||
default_headers = {}
|
||||
if six.PY3 and isinstance(self._content, six.string_types):
|
||||
self._content = self._content.encode('utf-8', 'strict')
|
||||
self.headers = data.get('headers') or default_headers
|
||||
else:
|
||||
self.status_code = data
|
||||
|
||||
def __eq__(self, other):
|
||||
return (self.status_code == other.status_code and
|
||||
self.headers == other.headers and
|
||||
self._content == other._content)
|
||||
|
||||
|
||||
class FakeHTTPClient(client.HTTPClient):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.callstack = []
|
||||
self.fixtures = kwargs.pop("fixtures", None) or {}
|
||||
if not args and "auth_plugin" not in kwargs:
|
||||
args = (None, )
|
||||
super(FakeHTTPClient, self).__init__(*args, **kwargs)
|
||||
|
||||
def assert_called(self, method, url, body=None, pos=-1):
|
||||
"""Assert than an API method was just called.
|
||||
"""
|
||||
expected = (method, url)
|
||||
called = self.callstack[pos][0:2]
|
||||
assert self.callstack, \
|
||||
"Expected %s %s but no calls were made." % expected
|
||||
|
||||
assert expected == called, 'Expected %s %s; got %s %s' % \
|
||||
(expected + called)
|
||||
|
||||
if body is not None:
|
||||
if self.callstack[pos][3] != body:
|
||||
raise AssertionError('%r != %r' %
|
||||
(self.callstack[pos][3], body))
|
||||
|
||||
def assert_called_anytime(self, method, url, body=None):
|
||||
"""Assert than an API method was called anytime in the test.
|
||||
"""
|
||||
expected = (method, url)
|
||||
|
||||
assert self.callstack, \
|
||||
"Expected %s %s but no calls were made." % expected
|
||||
|
||||
found = False
|
||||
entry = None
|
||||
for entry in self.callstack:
|
||||
if expected == entry[0:2]:
|
||||
found = True
|
||||
break
|
||||
|
||||
assert found, 'Expected %s %s; got %s' % \
|
||||
(method, url, self.callstack)
|
||||
if body is not None:
|
||||
assert entry[3] == body, "%s != %s" % (entry[3], body)
|
||||
|
||||
self.callstack = []
|
||||
|
||||
def clear_callstack(self):
|
||||
self.callstack = []
|
||||
|
||||
def authenticate(self):
|
||||
pass
|
||||
|
||||
def client_request(self, client, method, url, **kwargs):
|
||||
# Check that certain things are called correctly
|
||||
if method in ["GET", "DELETE"]:
|
||||
assert "json" not in kwargs
|
||||
|
||||
# Note the call
|
||||
self.callstack.append(
|
||||
(method,
|
||||
url,
|
||||
kwargs.get("headers") or {},
|
||||
kwargs.get("json") or kwargs.get("data")))
|
||||
try:
|
||||
fixture = self.fixtures[url][method]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
return TestResponse({"headers": fixture[0],
|
||||
"text": fixture[1]})
|
||||
|
||||
# Call the method
|
||||
args = parse.parse_qsl(parse.urlparse(url)[4])
|
||||
kwargs.update(args)
|
||||
munged_url = url.rsplit('?', 1)[0]
|
||||
munged_url = munged_url.strip('/').replace('/', '_').replace('.', '_')
|
||||
munged_url = munged_url.replace('-', '_')
|
||||
|
||||
callback = "%s_%s" % (method.lower(), munged_url)
|
||||
|
||||
if not hasattr(self, callback):
|
||||
raise AssertionError('Called unknown API method: %s %s, '
|
||||
'expected fakes method name: %s' %
|
||||
(method, url, callback))
|
||||
|
||||
resp = getattr(self, callback)(**kwargs)
|
||||
if len(resp) == 3:
|
||||
status, headers, body = resp
|
||||
else:
|
||||
status, body = resp
|
||||
headers = {}
|
||||
self.last_request_id = headers.get('x-openstack-request-id',
|
||||
'req-test')
|
||||
return TestResponse({
|
||||
"status_code": status,
|
||||
"text": body,
|
||||
"headers": headers,
|
||||
})
|
||||
@@ -1,100 +0,0 @@
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
########################################################################
|
||||
#
|
||||
# THIS MODULE IS DEPRECATED
|
||||
#
|
||||
# Please refer to
|
||||
# https://etherpad.openstack.org/p/kilo-watcherclient-library-proposals for
|
||||
# the discussion leading to this deprecation.
|
||||
#
|
||||
# We recommend checking out the python-openstacksdk project
|
||||
# (https://launchpad.net/python-openstacksdk) instead.
|
||||
#
|
||||
########################################################################
|
||||
|
||||
from oslo_utils import encodeutils
|
||||
from oslo_utils import uuidutils
|
||||
import six
|
||||
|
||||
from watcherclient.openstack.common._i18n import _
|
||||
from watcherclient.openstack.common.apiclient import exceptions
|
||||
|
||||
|
||||
def find_resource(manager, name_or_id, **find_args):
|
||||
"""Look for resource in a given manager.
|
||||
|
||||
Used as a helper for the _find_* methods.
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def _find_hypervisor(cs, hypervisor):
|
||||
#Get a hypervisor by name or ID.
|
||||
return cliutils.find_resource(cs.hypervisors, hypervisor)
|
||||
"""
|
||||
# first try to get entity as integer id
|
||||
try:
|
||||
return manager.get(int(name_or_id))
|
||||
except (TypeError, ValueError, exceptions.NotFound):
|
||||
pass
|
||||
|
||||
# now try to get entity as uuid
|
||||
try:
|
||||
if six.PY2:
|
||||
tmp_id = encodeutils.safe_encode(name_or_id)
|
||||
else:
|
||||
tmp_id = encodeutils.safe_decode(name_or_id)
|
||||
|
||||
if uuidutils.is_uuid_like(tmp_id):
|
||||
return manager.get(tmp_id)
|
||||
except (TypeError, ValueError, exceptions.NotFound):
|
||||
pass
|
||||
|
||||
# for str id which is not uuid
|
||||
if getattr(manager, 'is_alphanum_id_allowed', False):
|
||||
try:
|
||||
return manager.get(name_or_id)
|
||||
except exceptions.NotFound:
|
||||
pass
|
||||
|
||||
try:
|
||||
try:
|
||||
return manager.find(human_id=name_or_id, **find_args)
|
||||
except exceptions.NotFound:
|
||||
pass
|
||||
|
||||
# finally try to find entity by name
|
||||
try:
|
||||
resource = getattr(manager, 'resource_class', None)
|
||||
name_attr = resource.NAME_ATTR if resource else 'name'
|
||||
kwargs = {name_attr: name_or_id}
|
||||
kwargs.update(find_args)
|
||||
return manager.find(**kwargs)
|
||||
except exceptions.NotFound:
|
||||
msg = _("No %(name)s with a name or "
|
||||
"ID of '%(name_or_id)s' exists.") % \
|
||||
{
|
||||
"name": manager.resource_class.__name__.lower(),
|
||||
"name_or_id": name_or_id
|
||||
}
|
||||
raise exceptions.CommandError(msg)
|
||||
except exceptions.NoUniqueMatch:
|
||||
msg = _("Multiple %(name)s matches found for "
|
||||
"'%(name_or_id)s', use an ID to be more specific.") % \
|
||||
{
|
||||
"name": manager.resource_class.__name__.lower(),
|
||||
"name_or_id": name_or_id
|
||||
}
|
||||
raise exceptions.CommandError(msg)
|
||||
@@ -1,479 +0,0 @@
|
||||
# Copyright 2012 Red Hat, Inc.
|
||||
# Copyright 2013 IBM Corp.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
gettext for openstack-common modules.
|
||||
|
||||
Usual usage in an openstack.common module:
|
||||
|
||||
from watcherclient.openstack.common.gettextutils import _
|
||||
"""
|
||||
|
||||
import copy
|
||||
import gettext
|
||||
import locale
|
||||
from logging import handlers
|
||||
import os
|
||||
|
||||
from babel import localedata
|
||||
import six
|
||||
|
||||
_AVAILABLE_LANGUAGES = {}
|
||||
|
||||
# FIXME(dhellmann): Remove this when moving to oslo.i18n.
|
||||
USE_LAZY = False
|
||||
|
||||
|
||||
class TranslatorFactory(object):
|
||||
"""Create translator functions
|
||||
"""
|
||||
|
||||
def __init__(self, domain, localedir=None):
|
||||
"""Establish a set of translation functions for the domain.
|
||||
|
||||
:param domain: Name of translation domain,
|
||||
specifying a message catalog.
|
||||
:type domain: str
|
||||
:param lazy: Delays translation until a message is emitted.
|
||||
Defaults to False.
|
||||
:type lazy: Boolean
|
||||
:param localedir: Directory with translation catalogs.
|
||||
:type localedir: str
|
||||
"""
|
||||
self.domain = domain
|
||||
if localedir is None:
|
||||
localedir = os.environ.get(domain.upper() + '_LOCALEDIR')
|
||||
self.localedir = localedir
|
||||
|
||||
def _make_translation_func(self, domain=None):
|
||||
"""Return a new translation function ready for use.
|
||||
|
||||
Takes into account whether or not lazy translation is being
|
||||
done.
|
||||
|
||||
The domain can be specified to override the default from the
|
||||
factory, but the localedir from the factory is always used
|
||||
because we assume the log-level translation catalogs are
|
||||
installed in the same directory as the main application
|
||||
catalog.
|
||||
|
||||
"""
|
||||
if domain is None:
|
||||
domain = self.domain
|
||||
t = gettext.translation(domain,
|
||||
localedir=self.localedir,
|
||||
fallback=True)
|
||||
# Use the appropriate method of the translation object based
|
||||
# on the python version.
|
||||
m = t.gettext if six.PY3 else t.ugettext
|
||||
|
||||
def f(msg):
|
||||
"""oslo.i18n.gettextutils translation function."""
|
||||
if USE_LAZY:
|
||||
return Message(msg, domain=domain)
|
||||
return m(msg)
|
||||
return f
|
||||
|
||||
@property
|
||||
def primary(self):
|
||||
"The default translation function."
|
||||
return self._make_translation_func()
|
||||
|
||||
def _make_log_translation_func(self, level):
|
||||
return self._make_translation_func(self.domain + '-log-' + level)
|
||||
|
||||
@property
|
||||
def log_info(self):
|
||||
"Translate info-level log messages."
|
||||
return self._make_log_translation_func('info')
|
||||
|
||||
@property
|
||||
def log_warning(self):
|
||||
"Translate warning-level log messages."
|
||||
return self._make_log_translation_func('warning')
|
||||
|
||||
@property
|
||||
def log_error(self):
|
||||
"Translate error-level log messages."
|
||||
return self._make_log_translation_func('error')
|
||||
|
||||
@property
|
||||
def log_critical(self):
|
||||
"Translate critical-level log messages."
|
||||
return self._make_log_translation_func('critical')
|
||||
|
||||
|
||||
# NOTE(dhellmann): When this module moves out of the incubator into
|
||||
# oslo.i18n, these global variables can be moved to an integration
|
||||
# module within each application.
|
||||
|
||||
# Create the global translation functions.
|
||||
_translators = TranslatorFactory('watcherclient')
|
||||
|
||||
# The primary translation function using the well-known name "_"
|
||||
_ = _translators.primary
|
||||
|
||||
# Translators for log levels.
|
||||
#
|
||||
# The abbreviated names are meant to reflect the usual use of a short
|
||||
# name like '_'. The "L" is for "log" and the other letter comes from
|
||||
# the level.
|
||||
_LI = _translators.log_info
|
||||
_LW = _translators.log_warning
|
||||
_LE = _translators.log_error
|
||||
_LC = _translators.log_critical
|
||||
|
||||
# NOTE(dhellmann): End of globals that will move to the application's
|
||||
# integration module.
|
||||
|
||||
|
||||
def enable_lazy():
|
||||
"""Convenience function for configuring _() to use lazy gettext
|
||||
|
||||
Call this at the start of execution to enable the gettextutils._
|
||||
function to use lazy gettext functionality. This is useful if
|
||||
your project is importing _ directly instead of using the
|
||||
gettextutils.install() way of importing the _ function.
|
||||
"""
|
||||
global USE_LAZY
|
||||
USE_LAZY = True
|
||||
|
||||
|
||||
def install(domain):
|
||||
"""Install a _() function using the given translation domain.
|
||||
|
||||
Given a translation domain, install a _() function using gettext's
|
||||
install() function.
|
||||
|
||||
The main difference from gettext.install() is that we allow
|
||||
overriding the default localedir (e.g. /usr/share/locale) using
|
||||
a translation-domain-specific environment variable (e.g.
|
||||
NOVA_LOCALEDIR).
|
||||
|
||||
Note that to enable lazy translation, enable_lazy must be
|
||||
called.
|
||||
|
||||
:param domain: the translation domain
|
||||
"""
|
||||
from six import moves
|
||||
tf = TranslatorFactory(domain)
|
||||
moves.builtins.__dict__['_'] = tf.primary
|
||||
|
||||
|
||||
class Message(six.text_type):
|
||||
"""A Message object is a unicode object that can be translated.
|
||||
|
||||
Translation of Message is done explicitly using the translate() method.
|
||||
For all non-translation intents and purposes, a Message is simply unicode,
|
||||
and can be treated as such.
|
||||
"""
|
||||
|
||||
def __new__(cls, msgid, msgtext=None, params=None,
|
||||
domain='watcherclient', *args):
|
||||
"""Create a new Message object.
|
||||
|
||||
In order for translation to work gettext requires a message ID, this
|
||||
msgid will be used as the base unicode text. It is also possible
|
||||
for the msgid and the base unicode text to be different by passing
|
||||
the msgtext parameter.
|
||||
"""
|
||||
# If the base msgtext is not given, we use the default translation
|
||||
# of the msgid (which is in English) just in case the system locale is
|
||||
# not English, so that the base text will be in that locale by default.
|
||||
if not msgtext:
|
||||
msgtext = Message._translate_msgid(msgid, domain)
|
||||
# We want to initialize the parent unicode with the actual object that
|
||||
# would have been plain unicode if 'Message' was not enabled.
|
||||
msg = super(Message, cls).__new__(cls, msgtext)
|
||||
msg.msgid = msgid
|
||||
msg.domain = domain
|
||||
msg.params = params
|
||||
return msg
|
||||
|
||||
def translate(self, desired_locale=None):
|
||||
"""Translate this message to the desired locale.
|
||||
|
||||
:param desired_locale: The desired locale to translate the message to,
|
||||
if no locale is provided the message will be
|
||||
translated to the system's default locale.
|
||||
|
||||
:returns: the translated message in unicode
|
||||
"""
|
||||
|
||||
translated_message = Message._translate_msgid(self.msgid,
|
||||
self.domain,
|
||||
desired_locale)
|
||||
if self.params is None:
|
||||
# No need for more translation
|
||||
return translated_message
|
||||
|
||||
# This Message object may have been formatted with one or more
|
||||
# Message objects as substitution arguments, given either as a single
|
||||
# argument, part of a tuple, or as one or more values in a dictionary.
|
||||
# When translating this Message we need to translate those Messages too
|
||||
translated_params = _translate_args(self.params, desired_locale)
|
||||
|
||||
translated_message = translated_message % translated_params
|
||||
|
||||
return translated_message
|
||||
|
||||
@staticmethod
|
||||
def _translate_msgid(msgid, domain, desired_locale=None):
|
||||
if not desired_locale:
|
||||
system_locale = locale.getdefaultlocale()
|
||||
# If the system locale is not available to the runtime use English
|
||||
if not system_locale[0]:
|
||||
desired_locale = 'en_US'
|
||||
else:
|
||||
desired_locale = system_locale[0]
|
||||
|
||||
locale_dir = os.environ.get(domain.upper() + '_LOCALEDIR')
|
||||
lang = gettext.translation(domain,
|
||||
localedir=locale_dir,
|
||||
languages=[desired_locale],
|
||||
fallback=True)
|
||||
if six.PY3:
|
||||
translator = lang.gettext
|
||||
else:
|
||||
translator = lang.ugettext
|
||||
|
||||
translated_message = translator(msgid)
|
||||
return translated_message
|
||||
|
||||
def __mod__(self, other):
|
||||
# When we mod a Message we want the actual operation to be performed
|
||||
# by the parent class (i.e. unicode()), the only thing we do here is
|
||||
# save the original msgid and the parameters in case of a translation
|
||||
params = self._sanitize_mod_params(other)
|
||||
unicode_mod = super(Message, self).__mod__(params)
|
||||
modded = Message(self.msgid,
|
||||
msgtext=unicode_mod,
|
||||
params=params,
|
||||
domain=self.domain)
|
||||
return modded
|
||||
|
||||
def _sanitize_mod_params(self, other):
|
||||
"""Sanitize the object being modded with this Message.
|
||||
|
||||
- Add support for modding 'None' so translation supports it
|
||||
- Trim the modded object, which can be a large dictionary, to only
|
||||
those keys that would actually be used in a translation
|
||||
- Snapshot the object being modded, in case the message is
|
||||
translated, it will be used as it was when the Message was created
|
||||
"""
|
||||
if other is None:
|
||||
params = (other,)
|
||||
elif isinstance(other, dict):
|
||||
# Merge the dictionaries
|
||||
# Copy each item in case one does not support deep copy.
|
||||
params = {}
|
||||
if isinstance(self.params, dict):
|
||||
for key, val in self.params.items():
|
||||
params[key] = self._copy_param(val)
|
||||
for key, val in other.items():
|
||||
params[key] = self._copy_param(val)
|
||||
else:
|
||||
params = self._copy_param(other)
|
||||
return params
|
||||
|
||||
def _copy_param(self, param):
|
||||
try:
|
||||
return copy.deepcopy(param)
|
||||
except Exception:
|
||||
# Fallback to casting to unicode this will handle the
|
||||
# python code-like objects that can't be deep-copied
|
||||
return six.text_type(param)
|
||||
|
||||
def __add__(self, other):
|
||||
msg = _('Message objects do not support addition.')
|
||||
raise TypeError(msg)
|
||||
|
||||
def __radd__(self, other):
|
||||
return self.__add__(other)
|
||||
|
||||
if six.PY2:
|
||||
def __str__(self):
|
||||
# NOTE(luisg): Logging in python 2.6 tries to str() log records,
|
||||
# and it expects specifically a UnicodeError in order to proceed.
|
||||
msg = _('Message objects do not support str() because they may '
|
||||
'contain non-ascii characters. '
|
||||
'Please use unicode() or translate() instead.')
|
||||
raise UnicodeError(msg)
|
||||
|
||||
|
||||
def get_available_languages(domain):
|
||||
"""Lists the available languages for the given translation domain.
|
||||
|
||||
:param domain: the domain to get languages for
|
||||
"""
|
||||
if domain in _AVAILABLE_LANGUAGES:
|
||||
return copy.copy(_AVAILABLE_LANGUAGES[domain])
|
||||
|
||||
localedir = '%s_LOCALEDIR' % domain.upper()
|
||||
find = lambda x: gettext.find(domain,
|
||||
localedir=os.environ.get(localedir),
|
||||
languages=[x])
|
||||
|
||||
# NOTE(mrodden): en_US should always be available (and first in case
|
||||
# order matters) since our in-line message strings are en_US
|
||||
language_list = ['en_US']
|
||||
# NOTE(luisg): Babel <1.0 used a function called list(), which was
|
||||
# renamed to locale_identifiers() in >=1.0, the requirements master list
|
||||
# requires >=0.9.6, uncapped, so defensively work with both. We can remove
|
||||
# this check when the master list updates to >=1.0, and update all projects
|
||||
list_identifiers = (getattr(localedata, 'list', None) or
|
||||
getattr(localedata, 'locale_identifiers'))
|
||||
locale_identifiers = list_identifiers()
|
||||
|
||||
for i in locale_identifiers:
|
||||
if find(i) is not None:
|
||||
language_list.append(i)
|
||||
|
||||
# NOTE(luisg): Babel>=1.0,<1.3 has a bug where some OpenStack supported
|
||||
# locales (e.g. 'zh_CN', and 'zh_TW') aren't supported even though they
|
||||
# are perfectly legitimate locales:
|
||||
# https://github.com/mitsuhiko/babel/issues/37
|
||||
# In Babel 1.3 they fixed the bug and they support these locales, but
|
||||
# they are still not explicitly "listed" by locale_identifiers().
|
||||
# That is why we add the locales here explicitly if necessary so that
|
||||
# they are listed as supported.
|
||||
aliases = {'zh': 'zh_CN',
|
||||
'zh_Hant_HK': 'zh_HK',
|
||||
'zh_Hant': 'zh_TW',
|
||||
'fil': 'tl_PH'}
|
||||
for (locale_, alias) in six.iteritems(aliases):
|
||||
if locale_ in language_list and alias not in language_list:
|
||||
language_list.append(alias)
|
||||
|
||||
_AVAILABLE_LANGUAGES[domain] = language_list
|
||||
return copy.copy(language_list)
|
||||
|
||||
|
||||
def translate(obj, desired_locale=None):
|
||||
"""Gets the translated unicode representation of the given object.
|
||||
|
||||
If the object is not translatable it is returned as-is.
|
||||
If the locale is None the object is translated to the system locale.
|
||||
|
||||
:param obj: the object to translate
|
||||
:param desired_locale: the locale to translate the message to, if None the
|
||||
default system locale will be used
|
||||
:returns: the translated object in unicode, or the original object if
|
||||
it could not be translated
|
||||
"""
|
||||
message = obj
|
||||
if not isinstance(message, Message):
|
||||
# If the object to translate is not already translatable,
|
||||
# let's first get its unicode representation
|
||||
message = six.text_type(obj)
|
||||
if isinstance(message, Message):
|
||||
# Even after unicoding() we still need to check if we are
|
||||
# running with translatable unicode before translating
|
||||
return message.translate(desired_locale)
|
||||
return obj
|
||||
|
||||
|
||||
def _translate_args(args, desired_locale=None):
|
||||
"""Translates all the translatable elements of the given arguments object.
|
||||
|
||||
This method is used for translating the translatable values in method
|
||||
arguments which include values of tuples or dictionaries.
|
||||
If the object is not a tuple or a dictionary the object itself is
|
||||
translated if it is translatable.
|
||||
|
||||
If the locale is None the object is translated to the system locale.
|
||||
|
||||
:param args: the args to translate
|
||||
:param desired_locale: the locale to translate the args to, if None the
|
||||
default system locale will be used
|
||||
:returns: a new args object with the translated contents of the original
|
||||
"""
|
||||
if isinstance(args, tuple):
|
||||
return tuple(translate(v, desired_locale) for v in args)
|
||||
if isinstance(args, dict):
|
||||
translated_dict = {}
|
||||
for (k, v) in six.iteritems(args):
|
||||
translated_v = translate(v, desired_locale)
|
||||
translated_dict[k] = translated_v
|
||||
return translated_dict
|
||||
return translate(args, desired_locale)
|
||||
|
||||
|
||||
class TranslationHandler(handlers.MemoryHandler):
|
||||
"""Handler that translates records before logging them.
|
||||
|
||||
The TranslationHandler takes a locale and a target logging.Handler object
|
||||
to forward LogRecord objects to after translating them. This handler
|
||||
depends on Message objects being logged, instead of regular strings.
|
||||
|
||||
The handler can be configured declaratively in the logging.conf as follows:
|
||||
|
||||
[handlers]
|
||||
keys = translatedlog, translator
|
||||
|
||||
[handler_translatedlog]
|
||||
class = handlers.WatchedFileHandler
|
||||
args = ('/var/log/api-localized.log',)
|
||||
formatter = context
|
||||
|
||||
[handler_translator]
|
||||
class = openstack.common.log.TranslationHandler
|
||||
target = translatedlog
|
||||
args = ('zh_CN',)
|
||||
|
||||
If the specified locale is not available in the system, the handler will
|
||||
log in the default locale.
|
||||
"""
|
||||
|
||||
def __init__(self, locale=None, target=None):
|
||||
"""Initialize a TranslationHandler
|
||||
|
||||
:param locale: locale to use for translating messages
|
||||
:param target: logging.Handler object to forward
|
||||
LogRecord objects to after translation
|
||||
"""
|
||||
# NOTE(luisg): In order to allow this handler to be a wrapper for
|
||||
# other handlers, such as a FileHandler, and still be able to
|
||||
# configure it using logging.conf, this handler has to extend
|
||||
# MemoryHandler because only the MemoryHandlers' logging.conf
|
||||
# parsing is implemented such that it accepts a target handler.
|
||||
handlers.MemoryHandler.__init__(self, capacity=0, target=target)
|
||||
self.locale = locale
|
||||
|
||||
def setFormatter(self, fmt):
|
||||
self.target.setFormatter(fmt)
|
||||
|
||||
def emit(self, record):
|
||||
# We save the message from the original record to restore it
|
||||
# after translation, so other handlers are not affected by this
|
||||
original_msg = record.msg
|
||||
original_args = record.args
|
||||
|
||||
try:
|
||||
self._translate_and_log_record(record)
|
||||
finally:
|
||||
record.msg = original_msg
|
||||
record.args = original_args
|
||||
|
||||
def _translate_and_log_record(self, record):
|
||||
record.msg = translate(record.msg, self.locale)
|
||||
|
||||
# In addition to translating the message, we also need to translate
|
||||
# arguments that were passed to the log method that were not part
|
||||
# of the main message e.g., log.info(_('Some message %s'), this_one))
|
||||
record.args = _translate_args(record.args, self.locale)
|
||||
|
||||
self.target.emit(record)
|
||||
@@ -1,73 +0,0 @@
|
||||
# Copyright 2011 OpenStack Foundation.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Import related utilities and helper functions.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
|
||||
def import_class(import_str):
|
||||
"""Returns a class from a string including module and class."""
|
||||
mod_str, _sep, class_str = import_str.rpartition('.')
|
||||
__import__(mod_str)
|
||||
try:
|
||||
return getattr(sys.modules[mod_str], class_str)
|
||||
except AttributeError:
|
||||
raise ImportError('Class %s cannot be found (%s)' %
|
||||
(class_str,
|
||||
traceback.format_exception(*sys.exc_info())))
|
||||
|
||||
|
||||
def import_object(import_str, *args, **kwargs):
|
||||
"""Import a class and return an instance of it."""
|
||||
return import_class(import_str)(*args, **kwargs)
|
||||
|
||||
|
||||
def import_object_ns(name_space, import_str, *args, **kwargs):
|
||||
"""Tries to import object from default namespace.
|
||||
|
||||
Imports a class and return an instance of it, first by trying
|
||||
to find the class in a default namespace, then failing back to
|
||||
a full path if not found in the default namespace.
|
||||
"""
|
||||
import_value = "%s.%s" % (name_space, import_str)
|
||||
try:
|
||||
return import_class(import_value)(*args, **kwargs)
|
||||
except ImportError:
|
||||
return import_class(import_str)(*args, **kwargs)
|
||||
|
||||
|
||||
def import_module(import_str):
|
||||
"""Import a module."""
|
||||
__import__(import_str)
|
||||
return sys.modules[import_str]
|
||||
|
||||
|
||||
def import_versioned_module(version, submodule=None):
|
||||
module = 'watcherclient.v%s' % version
|
||||
if submodule:
|
||||
module = '.'.join((module, submodule))
|
||||
return import_module(module)
|
||||
|
||||
|
||||
def try_import(import_str, default=None):
|
||||
"""Try to import a module and if it fails return default."""
|
||||
try:
|
||||
return import_module(import_str)
|
||||
except ImportError:
|
||||
return default
|
||||
@@ -1,316 +0,0 @@
|
||||
# Copyright 2011 OpenStack Foundation.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
System-level utilities and helper functions.
|
||||
"""
|
||||
|
||||
import math
|
||||
import re
|
||||
import sys
|
||||
import unicodedata
|
||||
|
||||
import six
|
||||
|
||||
from watcherclient.openstack.common.gettextutils import _
|
||||
|
||||
|
||||
UNIT_PREFIX_EXPONENT = {
|
||||
'k': 1,
|
||||
'K': 1,
|
||||
'Ki': 1,
|
||||
'M': 2,
|
||||
'Mi': 2,
|
||||
'G': 3,
|
||||
'Gi': 3,
|
||||
'T': 4,
|
||||
'Ti': 4,
|
||||
}
|
||||
UNIT_SYSTEM_INFO = {
|
||||
'IEC': (1024, re.compile(r'(^[-+]?\d*\.?\d+)([KMGT]i?)?(b|bit|B)$')),
|
||||
'SI': (1000, re.compile(r'(^[-+]?\d*\.?\d+)([kMGT])?(b|bit|B)$')),
|
||||
}
|
||||
|
||||
TRUE_STRINGS = ('1', 't', 'true', 'on', 'y', 'yes')
|
||||
FALSE_STRINGS = ('0', 'f', 'false', 'off', 'n', 'no')
|
||||
|
||||
SLUGIFY_STRIP_RE = re.compile(r"[^\w\s-]")
|
||||
SLUGIFY_HYPHENATE_RE = re.compile(r"[-\s]+")
|
||||
|
||||
|
||||
# NOTE(flaper87): The following globals are used by `mask_password`
|
||||
_SANITIZE_KEYS = ['adminPass', 'admin_pass', 'password', 'admin_password']
|
||||
|
||||
# NOTE(ldbragst): Let's build a list of regex objects using the list of
|
||||
# _SANITIZE_KEYS we already have. This way, we only have to add the new key
|
||||
# to the list of _SANITIZE_KEYS and we can generate regular expressions
|
||||
# for XML and JSON automatically.
|
||||
_SANITIZE_PATTERNS_2 = []
|
||||
_SANITIZE_PATTERNS_1 = []
|
||||
|
||||
# NOTE(amrith): Some regular expressions have only one parameter, some
|
||||
# have two parameters. Use different lists of patterns here.
|
||||
_FORMAT_PATTERNS_1 = [r'(%(key)s\s*[=]\s*)[^\s^\'^\"]+']
|
||||
_FORMAT_PATTERNS_2 = [r'(%(key)s\s*[=]\s*[\"\']).*?([\"\'])',
|
||||
r'(%(key)s\s+[\"\']).*?([\"\'])',
|
||||
r'([-]{2}%(key)s\s+)[^\'^\"^=^\s]+([\s]*)',
|
||||
r'(<%(key)s>).*?(</%(key)s>)',
|
||||
r'([\"\']%(key)s[\"\']\s*:\s*[\"\']).*?([\"\'])',
|
||||
r'([\'"].*?%(key)s[\'"]\s*:\s*u?[\'"]).*?([\'"])',
|
||||
r'([\'"].*?%(key)s[\'"]\s*,\s*\'--?[A-z]+\'\s*,\s*u?'
|
||||
'[\'"]).*?([\'"])',
|
||||
r'(%(key)s\s*--?[A-z]+\s*)\S+(\s*)']
|
||||
|
||||
for key in _SANITIZE_KEYS:
|
||||
for pattern in _FORMAT_PATTERNS_2:
|
||||
reg_ex = re.compile(pattern % {'key': key}, re.DOTALL)
|
||||
_SANITIZE_PATTERNS_2.append(reg_ex)
|
||||
|
||||
for pattern in _FORMAT_PATTERNS_1:
|
||||
reg_ex = re.compile(pattern % {'key': key}, re.DOTALL)
|
||||
_SANITIZE_PATTERNS_1.append(reg_ex)
|
||||
|
||||
|
||||
def int_from_bool_as_string(subject):
|
||||
"""Interpret a string as a boolean and return either 1 or 0.
|
||||
|
||||
Any string value in:
|
||||
|
||||
('True', 'true', 'On', 'on', '1')
|
||||
|
||||
is interpreted as a boolean True.
|
||||
|
||||
Useful for JSON-decoded stuff and config file parsing
|
||||
"""
|
||||
return bool_from_string(subject) and 1 or 0
|
||||
|
||||
|
||||
def bool_from_string(subject, strict=False, default=False):
|
||||
"""Interpret a string as a boolean.
|
||||
|
||||
A case-insensitive match is performed such that strings matching 't',
|
||||
'true', 'on', 'y', 'yes', or '1' are considered True and, when
|
||||
`strict=False`, anything else returns the value specified by 'default'.
|
||||
|
||||
Useful for JSON-decoded stuff and config file parsing.
|
||||
|
||||
If `strict=True`, unrecognized values, including None, will raise a
|
||||
ValueError which is useful when parsing values passed in from an API call.
|
||||
Strings yielding False are 'f', 'false', 'off', 'n', 'no', or '0'.
|
||||
"""
|
||||
if not isinstance(subject, six.string_types):
|
||||
subject = six.text_type(subject)
|
||||
|
||||
lowered = subject.strip().lower()
|
||||
|
||||
if lowered in TRUE_STRINGS:
|
||||
return True
|
||||
elif lowered in FALSE_STRINGS:
|
||||
return False
|
||||
elif strict:
|
||||
acceptable = ', '.join(
|
||||
"'%s'" % s for s in sorted(TRUE_STRINGS + FALSE_STRINGS))
|
||||
msg = _("Unrecognized value '%(val)s', acceptable values are:"
|
||||
" %(acceptable)s") % {'val': subject,
|
||||
'acceptable': acceptable}
|
||||
raise ValueError(msg)
|
||||
else:
|
||||
return default
|
||||
|
||||
|
||||
def safe_decode(text, incoming=None, errors='strict'):
|
||||
"""Decodes incoming text/bytes string using `incoming` if they're not
|
||||
already unicode.
|
||||
|
||||
:param incoming: Text's current encoding
|
||||
:param errors: Errors handling policy. See here for valid
|
||||
values http://docs.python.org/2/library/codecs.html
|
||||
:returns: text or a unicode `incoming` encoded
|
||||
representation of it.
|
||||
:raises TypeError: If text is not an instance of str
|
||||
"""
|
||||
if not isinstance(text, (six.string_types, six.binary_type)):
|
||||
raise TypeError("%s can't be decoded" % type(text))
|
||||
|
||||
if isinstance(text, six.text_type):
|
||||
return text
|
||||
|
||||
if not incoming:
|
||||
incoming = (sys.stdin.encoding or
|
||||
sys.getdefaultencoding())
|
||||
|
||||
try:
|
||||
return text.decode(incoming, errors)
|
||||
except UnicodeDecodeError:
|
||||
# Note(flaper87) If we get here, it means that
|
||||
# sys.stdin.encoding / sys.getdefaultencoding
|
||||
# didn't return a suitable encoding to decode
|
||||
# text. This happens mostly when global LANG
|
||||
# var is not set correctly and there's no
|
||||
# default encoding. In this case, most likely
|
||||
# python will use ASCII or ANSI encoders as
|
||||
# default encodings but they won't be capable
|
||||
# of decoding non-ASCII characters.
|
||||
#
|
||||
# Also, UTF-8 is being used since it's an ASCII
|
||||
# extension.
|
||||
return text.decode('utf-8', errors)
|
||||
|
||||
|
||||
def safe_encode(text, incoming=None,
|
||||
encoding='utf-8', errors='strict'):
|
||||
"""Encodes incoming text/bytes string using `encoding`.
|
||||
|
||||
If incoming is not specified, text is expected to be encoded with
|
||||
current python's default encoding. (`sys.getdefaultencoding`)
|
||||
|
||||
:param incoming: Text's current encoding
|
||||
:param encoding: Expected encoding for text (Default UTF-8)
|
||||
:param errors: Errors handling policy. See here for valid
|
||||
values http://docs.python.org/2/library/codecs.html
|
||||
:returns: text or a bytestring `encoding` encoded
|
||||
representation of it.
|
||||
:raises TypeError: If text is not an instance of str
|
||||
"""
|
||||
if not isinstance(text, (six.string_types, six.binary_type)):
|
||||
raise TypeError("%s can't be encoded" % type(text))
|
||||
|
||||
if not incoming:
|
||||
incoming = (sys.stdin.encoding or
|
||||
sys.getdefaultencoding())
|
||||
|
||||
if isinstance(text, six.text_type):
|
||||
return text.encode(encoding, errors)
|
||||
elif text and encoding != incoming:
|
||||
# Decode text before encoding it with `encoding`
|
||||
text = safe_decode(text, incoming, errors)
|
||||
return text.encode(encoding, errors)
|
||||
else:
|
||||
return text
|
||||
|
||||
|
||||
def string_to_bytes(text, unit_system='IEC', return_int=False):
|
||||
"""Converts a string into an float representation of bytes.
|
||||
|
||||
The units supported for IEC ::
|
||||
|
||||
Kb(it), Kib(it), Mb(it), Mib(it), Gb(it), Gib(it), Tb(it), Tib(it)
|
||||
KB, KiB, MB, MiB, GB, GiB, TB, TiB
|
||||
|
||||
The units supported for SI ::
|
||||
|
||||
kb(it), Mb(it), Gb(it), Tb(it)
|
||||
kB, MB, GB, TB
|
||||
|
||||
Note that the SI unit system does not support capital letter 'K'
|
||||
|
||||
:param text: String input for bytes size conversion.
|
||||
:param unit_system: Unit system for byte size conversion.
|
||||
:param return_int: If True, returns integer representation of text
|
||||
in bytes. (default: decimal)
|
||||
:returns: Numerical representation of text in bytes.
|
||||
:raises ValueError: If text has an invalid value.
|
||||
|
||||
"""
|
||||
try:
|
||||
base, reg_ex = UNIT_SYSTEM_INFO[unit_system]
|
||||
except KeyError:
|
||||
msg = _('Invalid unit system: "%s"') % unit_system
|
||||
raise ValueError(msg)
|
||||
match = reg_ex.match(text)
|
||||
if match:
|
||||
magnitude = float(match.group(1))
|
||||
unit_prefix = match.group(2)
|
||||
if match.group(3) in ['b', 'bit']:
|
||||
magnitude /= 8
|
||||
else:
|
||||
msg = _('Invalid string format: %s') % text
|
||||
raise ValueError(msg)
|
||||
if not unit_prefix:
|
||||
res = magnitude
|
||||
else:
|
||||
res = magnitude * pow(base, UNIT_PREFIX_EXPONENT[unit_prefix])
|
||||
if return_int:
|
||||
return int(math.ceil(res))
|
||||
return res
|
||||
|
||||
|
||||
def to_slug(value, incoming=None, errors="strict"):
|
||||
"""Normalize string.
|
||||
|
||||
Convert to lowercase, remove non-word characters, and convert spaces
|
||||
to hyphens.
|
||||
|
||||
Inspired by Django's `slugify` filter.
|
||||
|
||||
:param value: Text to slugify
|
||||
:param incoming: Text's current encoding
|
||||
:param errors: Errors handling policy. See here for valid
|
||||
values http://docs.python.org/2/library/codecs.html
|
||||
:returns: slugified unicode representation of `value`
|
||||
:raises TypeError: If text is not an instance of str
|
||||
"""
|
||||
value = safe_decode(value, incoming, errors)
|
||||
# NOTE(aababilov): no need to use safe_(encode|decode) here:
|
||||
# encodings are always "ascii", error handling is always "ignore"
|
||||
# and types are always known (first: unicode; second: str)
|
||||
value = unicodedata.normalize("NFKD", value).encode(
|
||||
"ascii", "ignore").decode("ascii")
|
||||
value = SLUGIFY_STRIP_RE.sub("", value).strip().lower()
|
||||
return SLUGIFY_HYPHENATE_RE.sub("-", value)
|
||||
|
||||
|
||||
def mask_password(message, secret="***"):
|
||||
"""Replace password with 'secret' in message.
|
||||
|
||||
:param message: The string which includes security information.
|
||||
:param secret: value with which to replace passwords.
|
||||
:returns: The unicode value of message with the password fields masked.
|
||||
|
||||
For example:
|
||||
|
||||
>>> mask_password("'adminPass' : 'aaaaa'")
|
||||
"'adminPass' : '***'"
|
||||
>>> mask_password("'admin_pass' : 'aaaaa'")
|
||||
"'admin_pass' : '***'"
|
||||
>>> mask_password('"password" : "aaaaa"')
|
||||
'"password" : "***"'
|
||||
>>> mask_password("'original_password' : 'aaaaa'")
|
||||
"'original_password' : '***'"
|
||||
>>> mask_password("u'original_password' : u'aaaaa'")
|
||||
"u'original_password' : u'***'"
|
||||
"""
|
||||
try:
|
||||
message = six.text_type(message)
|
||||
except UnicodeDecodeError:
|
||||
# NOTE(jecarey): Temporary fix to handle cases where message is a
|
||||
# byte string. A better solution will be provided in Kilo.
|
||||
pass
|
||||
|
||||
# NOTE(ldbragst): Check to see if anything in message contains any key
|
||||
# specified in _SANITIZE_KEYS, if not then just return the message since
|
||||
# we don't have to mask any passwords.
|
||||
if not any(key in message for key in _SANITIZE_KEYS):
|
||||
return message
|
||||
|
||||
substitute = r'\g<1>' + secret + r'\g<2>'
|
||||
for pattern in _SANITIZE_PATTERNS_2:
|
||||
message = re.sub(pattern, substitute, message)
|
||||
|
||||
substitute = r'\g<1>' + secret
|
||||
for pattern in _SANITIZE_PATTERNS_1:
|
||||
message = re.sub(pattern, substitute, message)
|
||||
|
||||
return message
|
||||
64
watcherclient/plugin.py
Normal file
64
watcherclient/plugin.py
Normal file
@@ -0,0 +1,64 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
from openstackclient.common import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_API_VERSION = '1'
|
||||
API_VERSION_OPTION = 'os_infra_optim_api_version'
|
||||
API_NAME = 'infra-optim'
|
||||
API_VERSIONS = {
|
||||
'1': 'watcherclient.v1.client.Client',
|
||||
}
|
||||
|
||||
|
||||
def make_client(instance):
|
||||
"""Returns an infra-optim service client"""
|
||||
watcher_client = utils.get_client_class(
|
||||
API_NAME,
|
||||
instance._api_version[API_NAME],
|
||||
API_VERSIONS)
|
||||
LOG.debug('Instantiating infra-optim client: %s', watcher_client)
|
||||
|
||||
endpoint = instance.get_endpoint_for_service_type(
|
||||
API_NAME,
|
||||
region_name=instance._region_name,
|
||||
interface=instance._interface,
|
||||
)
|
||||
|
||||
client = watcher_client(
|
||||
endpoint=endpoint,
|
||||
session=instance.session,
|
||||
auth_url=instance._auth_url,
|
||||
username=instance._username,
|
||||
password=instance._password,
|
||||
region_name=instance._region_name,
|
||||
)
|
||||
|
||||
return client
|
||||
|
||||
|
||||
def build_option_parser(parser):
|
||||
"""Hook to add global options."""
|
||||
parser.add_argument('--os-infra-optim-api-version',
|
||||
metavar='<infra-optim-api-version>',
|
||||
default=utils.env(
|
||||
'OS_INFRA_OPTIM_API_VERSION',
|
||||
default=DEFAULT_API_VERSION),
|
||||
help=('Watcher API version, default=' +
|
||||
DEFAULT_API_VERSION +
|
||||
' (Env: OS_INFRA_OPTIM_API_VERSION)'))
|
||||
return parser
|
||||
@@ -1,263 +1,126 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2013 Rackspace, 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
|
||||
# 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
|
||||
# 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.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Command-line interface to the Watcher API.
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import getpass
|
||||
from collections import namedtuple
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from keystoneclient.auth.identity import v2 as v2_auth
|
||||
from keystoneclient.auth.identity import v3 as v3_auth
|
||||
from cliff import app
|
||||
from cliff import command
|
||||
from cliff import commandmanager
|
||||
from cliff import complete
|
||||
from cliff import help as cli_help
|
||||
from keystoneclient.auth.identity import v2
|
||||
from keystoneclient.auth.identity import v3
|
||||
from keystoneclient import discover
|
||||
from keystoneclient.openstack.common.apiclient import exceptions as ks_exc
|
||||
from keystoneclient import session as kssession
|
||||
from keystoneclient import exceptions as ks_exc
|
||||
from keystoneclient import session
|
||||
from openstackclient.common import logs
|
||||
from openstackclient.common import utils
|
||||
import six.moves.urllib.parse as urlparse
|
||||
|
||||
|
||||
import watcherclient
|
||||
from watcherclient import client as watcher_client
|
||||
from watcherclient.common import utils
|
||||
from watcherclient._i18n import _
|
||||
from watcherclient import exceptions as exc
|
||||
from watcherclient.openstack.common._i18n import _
|
||||
from watcherclient.openstack.common import cliutils
|
||||
from watcherclient.openstack.common import gettextutils
|
||||
from watcherclient import version
|
||||
|
||||
gettextutils.install('watcherclient')
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WatcherShell(object):
|
||||
API_NAME = 'infra-optim'
|
||||
API_VERSIONS = {
|
||||
'1': 'watcherclient.v1.client.Client',
|
||||
}
|
||||
_DEFAULT_IDENTITY_API_VERSION = '3'
|
||||
_IDENTITY_API_VERSION_2 = ['2', '2.0']
|
||||
_IDENTITY_API_VERSION_3 = ['3']
|
||||
|
||||
def _append_global_identity_args(self, parser):
|
||||
# FIXME(dhu): these are global identity (Keystone) arguments which
|
||||
# should be consistent and shared by all service clients. Therefore,
|
||||
# they should be provided by python-keystoneclient. We will need to
|
||||
# refactor this code once this functionality is avaible in
|
||||
# python-keystoneclient.
|
||||
|
||||
# Register arguments needed for a Session
|
||||
kssession.Session.register_cli_options(parser)
|
||||
class WatcherShell(app.App):
|
||||
"""Watcher command line interface."""
|
||||
|
||||
parser.add_argument('--os-user-domain-id',
|
||||
default=cliutils.env('OS_USER_DOMAIN_ID'),
|
||||
help='Defaults to env[OS_USER_DOMAIN_ID].')
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
parser.add_argument('--os-user-domain-name',
|
||||
default=cliutils.env('OS_USER_DOMAIN_NAME'),
|
||||
help='Defaults to env[OS_USER_DOMAIN_NAME].')
|
||||
def __init__(self, **kwargs):
|
||||
self.client = None
|
||||
|
||||
parser.add_argument('--os-project-id',
|
||||
default=cliutils.env('OS_PROJECT_ID'),
|
||||
help='Another way to specify tenant ID. '
|
||||
'This option is mutually exclusive with '
|
||||
' --os-tenant-id. '
|
||||
'Defaults to env[OS_PROJECT_ID].')
|
||||
# Patch command.Command to add a default auth_required = True
|
||||
command.Command.auth_required = True
|
||||
|
||||
parser.add_argument('--os-project-name',
|
||||
default=cliutils.env('OS_PROJECT_NAME'),
|
||||
help='Another way to specify tenant name. '
|
||||
'This option is mutually exclusive with '
|
||||
' --os-tenant-name. '
|
||||
'Defaults to env[OS_PROJECT_NAME].')
|
||||
# Some commands do not need authentication
|
||||
cli_help.HelpCommand.auth_required = False
|
||||
complete.CompleteCommand.auth_required = False
|
||||
|
||||
parser.add_argument('--os-project-domain-id',
|
||||
default=cliutils.env('OS_PROJECT_DOMAIN_ID'),
|
||||
help='Defaults to env[OS_PROJECT_DOMAIN_ID].')
|
||||
|
||||
parser.add_argument('--os-project-domain-name',
|
||||
default=cliutils.env('OS_PROJECT_DOMAIN_NAME'),
|
||||
help='Defaults to env[OS_PROJECT_DOMAIN_NAME].')
|
||||
|
||||
def get_base_parser(self):
|
||||
parser = argparse.ArgumentParser(
|
||||
prog='watcher',
|
||||
super(WatcherShell, self).__init__(
|
||||
description=__doc__.strip(),
|
||||
epilog='See "watcher help COMMAND" '
|
||||
'for help on a specific command.',
|
||||
add_help=False,
|
||||
formatter_class=HelpFormatter,
|
||||
version=version.__version__,
|
||||
command_manager=commandmanager.CommandManager(
|
||||
'watcherclient.v1'),
|
||||
deferred_help=True,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
# Global arguments
|
||||
parser.add_argument('-h', '--help',
|
||||
action='store_true',
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
def create_client(self, args):
|
||||
service_type = 'infra-optim'
|
||||
project_id = args.os_project_id or args.os_tenant_id
|
||||
project_name = args.os_project_name or args.os_tenant_name
|
||||
|
||||
parser.add_argument('--version',
|
||||
action='version',
|
||||
version=watcherclient.__version__)
|
||||
keystone_session = session.Session.load_from_cli_options(args)
|
||||
|
||||
parser.add_argument('--debug',
|
||||
default=bool(cliutils.env('WATCHERCLIENT_DEBUG')),
|
||||
action='store_true',
|
||||
help='Defaults to env[WATCHERCLIENT_DEBUG]')
|
||||
kwargs = {
|
||||
'username': args.os_username,
|
||||
'user_domain_id': args.os_user_domain_id,
|
||||
'user_domain_name': args.os_user_domain_name,
|
||||
'password': args.os_password,
|
||||
'auth_token': args.os_auth_token,
|
||||
'project_id': project_id,
|
||||
'project_name': project_name,
|
||||
'project_domain_id': args.os_project_domain_id,
|
||||
'project_domain_name': args.os_project_domain_name,
|
||||
}
|
||||
keystone_auth = self._get_keystone_auth(keystone_session,
|
||||
args.os_auth_url,
|
||||
**kwargs)
|
||||
region_name = args.os_region_name
|
||||
endpoint = keystone_auth.get_endpoint(keystone_session,
|
||||
service_type=service_type,
|
||||
region_name=region_name)
|
||||
|
||||
parser.add_argument('-v', '--verbose',
|
||||
default=False, action="store_true",
|
||||
help="Print more verbose output")
|
||||
endpoint_type = args.os_endpoint_type or 'publicURL'
|
||||
kwargs = {
|
||||
'auth_url': args.os_auth_url,
|
||||
'session': keystone_session,
|
||||
'auth': keystone_auth,
|
||||
'service_type': service_type,
|
||||
'endpoint_type': endpoint_type,
|
||||
'region_name': args.os_region_name,
|
||||
'username': args.os_username,
|
||||
'password': args.os_password,
|
||||
}
|
||||
|
||||
# for backward compatibility only
|
||||
parser.add_argument('--cert-file',
|
||||
dest='os_cert',
|
||||
help='DEPRECATED! Use --os-cert.')
|
||||
watcher_client = utils.get_client_class(
|
||||
API_NAME,
|
||||
args.watcher_api_version or 1,
|
||||
API_VERSIONS)
|
||||
LOG.debug('Instantiating infra-optim client: %s', watcher_client)
|
||||
|
||||
# for backward compatibility only
|
||||
parser.add_argument('--key-file',
|
||||
dest='os_key',
|
||||
help='DEPRECATED! Use --os-key.')
|
||||
client = watcher_client(args.watcher_api_version, endpoint, **kwargs)
|
||||
|
||||
# for backward compatibility only
|
||||
parser.add_argument('--ca-file',
|
||||
dest='os_cacert',
|
||||
help='DEPRECATED! Use --os-cacert.')
|
||||
|
||||
parser.add_argument('--os-username',
|
||||
default=cliutils.env('OS_USERNAME'),
|
||||
help='Defaults to env[OS_USERNAME]')
|
||||
|
||||
parser.add_argument('--os_username',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-password',
|
||||
default=cliutils.env('OS_PASSWORD'),
|
||||
help='Defaults to env[OS_PASSWORD]')
|
||||
|
||||
parser.add_argument('--os_password',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-tenant-id',
|
||||
default=cliutils.env('OS_TENANT_ID'),
|
||||
help='Defaults to env[OS_TENANT_ID]')
|
||||
|
||||
parser.add_argument('--os_tenant_id',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-tenant-name',
|
||||
default=cliutils.env('OS_TENANT_NAME'),
|
||||
help='Defaults to env[OS_TENANT_NAME]')
|
||||
|
||||
parser.add_argument('--os_tenant_name',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-auth-url',
|
||||
default=cliutils.env('OS_AUTH_URL'),
|
||||
help='Defaults to env[OS_AUTH_URL]')
|
||||
|
||||
parser.add_argument('--os_auth_url',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-region-name',
|
||||
default=cliutils.env('OS_REGION_NAME'),
|
||||
help='Defaults to env[OS_REGION_NAME]')
|
||||
|
||||
parser.add_argument('--os_region_name',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-auth-token',
|
||||
default=cliutils.env('OS_AUTH_TOKEN'),
|
||||
help='Defaults to env[OS_AUTH_TOKEN]')
|
||||
|
||||
parser.add_argument('--os_auth_token',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--watcher-url',
|
||||
default=cliutils.env('WATCHER_URL'),
|
||||
help='Defaults to env[WATCHER_URL]')
|
||||
|
||||
parser.add_argument('--watcher_url',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--watcher-api-version',
|
||||
default=cliutils.env(
|
||||
'WATCHER_API_VERSION', default='1'),
|
||||
help='Defaults to env[WATCHER_API_VERSION] '
|
||||
'or 1')
|
||||
|
||||
parser.add_argument('--watcher_api_version',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-service-type',
|
||||
default=cliutils.env('OS_SERVICE_TYPE'),
|
||||
help='Defaults to env[OS_SERVICE_TYPE] or '
|
||||
'"watcher"')
|
||||
|
||||
parser.add_argument('--os_service_type',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-endpoint',
|
||||
default=cliutils.env('OS_SERVICE_ENDPOINT'),
|
||||
help='Specify an endpoint to use instead of '
|
||||
'retrieving one from the service catalog '
|
||||
'(via authentication). '
|
||||
'Defaults to env[OS_SERVICE_ENDPOINT].')
|
||||
|
||||
parser.add_argument('--os_endpoint',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-endpoint-type',
|
||||
default=cliutils.env('OS_ENDPOINT_TYPE'),
|
||||
help='Defaults to env[OS_ENDPOINT_TYPE] or '
|
||||
'"publicURL"')
|
||||
|
||||
parser.add_argument('--os_endpoint_type',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
# FIXME(gyee): this method should come from python-keystoneclient.
|
||||
# Will refactor this code once it is available.
|
||||
# https://bugs.launchpad.net/python-keystoneclient/+bug/1332337
|
||||
|
||||
self._append_global_identity_args(parser)
|
||||
|
||||
return parser
|
||||
|
||||
def get_subcommand_parser(self, version):
|
||||
parser = self.get_base_parser()
|
||||
|
||||
self.subcommands = {}
|
||||
subparsers = parser.add_subparsers(metavar='<subcommand>')
|
||||
submodule = utils.import_versioned_module(version, 'shell')
|
||||
submodule.enhance_parser(parser, subparsers, self.subcommands)
|
||||
utils.define_commands_from_module(subparsers, self, self.subcommands)
|
||||
return parser
|
||||
|
||||
def _setup_debugging(self, debug):
|
||||
if debug:
|
||||
logging.basicConfig(
|
||||
format="%(levelname)s (%(module)s:%(lineno)d) %(message)s",
|
||||
level=logging.DEBUG)
|
||||
else:
|
||||
logging.basicConfig(
|
||||
format="%(levelname)s %(message)s",
|
||||
level=logging.CRITICAL)
|
||||
|
||||
def do_bash_completion(self):
|
||||
"""Prints all of the commands and options for bash-completion."""
|
||||
commands = set()
|
||||
options = set()
|
||||
for sc_str, sc in self.subcommands.items():
|
||||
commands.add(sc_str)
|
||||
for option in sc._optionals._option_string_actions.keys():
|
||||
options.add(option)
|
||||
|
||||
commands.remove('bash-completion')
|
||||
print(' '.join(commands | options))
|
||||
return client
|
||||
|
||||
def _discover_auth_versions(self, session, auth_url):
|
||||
# discover the API versions the server is supporting base on the
|
||||
@@ -292,20 +155,20 @@ class WatcherShell(object):
|
||||
def _get_keystone_v3_auth(self, v3_auth_url, **kwargs):
|
||||
auth_token = kwargs.pop('auth_token', None)
|
||||
if auth_token:
|
||||
return v3_auth.Token(v3_auth_url, auth_token)
|
||||
return v3.Token(v3_auth_url, auth_token)
|
||||
else:
|
||||
return v3_auth.Password(v3_auth_url, **kwargs)
|
||||
return v3.Password(v3_auth_url, **kwargs)
|
||||
|
||||
def _get_keystone_v2_auth(self, v2_auth_url, **kwargs):
|
||||
auth_token = kwargs.pop('auth_token', None)
|
||||
if auth_token:
|
||||
return v2_auth.Token(
|
||||
return v2.Token(
|
||||
v2_auth_url,
|
||||
auth_token,
|
||||
tenant_id=kwargs.pop('project_id', None),
|
||||
tenant_name=kwargs.pop('project_name', None))
|
||||
else:
|
||||
return v2_auth.Password(
|
||||
return v2.Password(
|
||||
v2_auth_url,
|
||||
username=kwargs.pop('username', None),
|
||||
password=kwargs.pop('password', None),
|
||||
@@ -344,167 +207,140 @@ class WatcherShell(object):
|
||||
# support only v2
|
||||
auth = self._get_keystone_v2_auth(v2_auth_url, **kwargs)
|
||||
else:
|
||||
raise exc.CommandError('Unable to determine the Keystone version '
|
||||
'to authenticate with using the given '
|
||||
'auth_url.')
|
||||
raise exc.CommandError(
|
||||
_('Unable to determine the Keystone version '
|
||||
'to authenticate with using the given '
|
||||
'auth_url.'))
|
||||
|
||||
return auth
|
||||
|
||||
def main(self, argv):
|
||||
# Parse args once to find version
|
||||
parser = self.get_base_parser()
|
||||
(options, args) = parser.parse_known_args(argv)
|
||||
self._setup_debugging(options.debug)
|
||||
def build_option_parser(self, description, version, argparse_kwargs=None):
|
||||
"""Introduces global arguments for the application.
|
||||
|
||||
# build available subcommands based on version
|
||||
api_version = options.watcher_api_version
|
||||
subcommand_parser = self.get_subcommand_parser(api_version)
|
||||
self.parser = subcommand_parser
|
||||
This is inherited from the framework.
|
||||
"""
|
||||
parser = super(WatcherShell, self).build_option_parser(
|
||||
description, version, argparse_kwargs)
|
||||
parser.add_argument('--no-auth', '-N', action='store_true',
|
||||
help='Do not use authentication.')
|
||||
parser.add_argument('--os-identity-api-version',
|
||||
metavar='<identity-api-version>',
|
||||
default=utils.env('OS_IDENTITY_API_VERSION'),
|
||||
help='Specify Identity API version to use. '
|
||||
'Defaults to env[OS_IDENTITY_API_VERSION]'
|
||||
' or 3.')
|
||||
parser.add_argument('--os-auth-url', '-A',
|
||||
metavar='<auth-url>',
|
||||
default=utils.env('OS_AUTH_URL'),
|
||||
help='Defaults to env[OS_AUTH_URL].')
|
||||
parser.add_argument('--os-region-name', '-R',
|
||||
metavar='<region-name>',
|
||||
default=utils.env('OS_REGION_NAME'),
|
||||
help='Defaults to env[OS_REGION_NAME].')
|
||||
parser.add_argument('--os-username', '-U',
|
||||
metavar='<auth-user-name>',
|
||||
default=utils.env('OS_USERNAME'),
|
||||
help='Defaults to env[OS_USERNAME].')
|
||||
parser.add_argument('--os-user-id',
|
||||
metavar='<auth-user-id>',
|
||||
default=utils.env('OS_USER_ID'),
|
||||
help='Defaults to env[OS_USER_ID].')
|
||||
parser.add_argument('--os-password', '-P',
|
||||
metavar='<auth-password>',
|
||||
default=utils.env('OS_PASSWORD'),
|
||||
help='Defaults to env[OS_PASSWORD].')
|
||||
parser.add_argument('--os-user-domain-id',
|
||||
metavar='<auth-user-domain-id>',
|
||||
default=utils.env('OS_USER_DOMAIN_ID'),
|
||||
help='Defaults to env[OS_USER_DOMAIN_ID].')
|
||||
parser.add_argument('--os-user-domain-name',
|
||||
metavar='<auth-user-domain-name>',
|
||||
default=utils.env('OS_USER_DOMAIN_NAME'),
|
||||
help='Defaults to env[OS_USER_DOMAIN_NAME].')
|
||||
parser.add_argument('--os-tenant-name', '-T',
|
||||
metavar='<auth-tenant-name>',
|
||||
default=utils.env('OS_TENANT_NAME'),
|
||||
help='Defaults to env[OS_TENANT_NAME].')
|
||||
parser.add_argument('--os-tenant-id', '-I',
|
||||
metavar='<tenant-id>',
|
||||
default=utils.env('OS_TENANT_ID'),
|
||||
help='Defaults to env[OS_TENANT_ID].')
|
||||
parser.add_argument('--os-project-id',
|
||||
metavar='<auth-project-id>',
|
||||
default=utils.env('OS_PROJECT_ID'),
|
||||
help='Another way to specify tenant ID. '
|
||||
'This option is mutually exclusive with '
|
||||
' --os-tenant-id. '
|
||||
'Defaults to env[OS_PROJECT_ID].')
|
||||
parser.add_argument('--os-project-name',
|
||||
metavar='<auth-project-name>',
|
||||
default=utils.env('OS_PROJECT_NAME'),
|
||||
help='Another way to specify tenant name. '
|
||||
'This option is mutually exclusive with '
|
||||
' --os-tenant-name. '
|
||||
'Defaults to env[OS_PROJECT_NAME].')
|
||||
parser.add_argument('--os-project-domain-id',
|
||||
metavar='<auth-project-domain-id>',
|
||||
default=utils.env('OS_PROJECT_DOMAIN_ID'),
|
||||
help='Defaults to env[OS_PROJECT_DOMAIN_ID].')
|
||||
parser.add_argument('--os-project-domain-name',
|
||||
metavar='<auth-project-domain-name>',
|
||||
default=utils.env('OS_PROJECT_DOMAIN_NAME'),
|
||||
help='Defaults to env[OS_PROJECT_DOMAIN_NAME].')
|
||||
parser.add_argument('--os-auth-token',
|
||||
metavar='<auth-token>',
|
||||
default=utils.env('OS_AUTH_TOKEN'),
|
||||
help='Defaults to env[OS_AUTH_TOKEN].')
|
||||
parser.add_argument('--watcher-api-version',
|
||||
metavar='<watcher-api-version>',
|
||||
default=utils.env('WATCHER_API_VERSION'),
|
||||
help='Defaults to env[WATCHER_API_VERSION].')
|
||||
parser.add_argument('--os-endpoint-type',
|
||||
default=utils.env('OS_ENDPOINT_TYPE'),
|
||||
help='Defaults to env[OS_ENDPOINT_TYPE] or '
|
||||
'"publicURL"')
|
||||
parser.epilog = ('See "watcher help COMMAND" for help '
|
||||
'on a specific command.')
|
||||
session.Session.register_cli_options(parser)
|
||||
return parser
|
||||
|
||||
# Handle top-level --help/-h before attempting to parse
|
||||
# a command off the command line
|
||||
if options.help or not argv:
|
||||
self.do_help(options)
|
||||
return 0
|
||||
def configure_logging(self):
|
||||
"""Configure logging for the app."""
|
||||
self.log_configurator = logs.LogConfigurator(self.options)
|
||||
self.dump_stack_trace = self.log_configurator.dump_trace
|
||||
|
||||
# Parse args again and call whatever callback was selected
|
||||
args = subcommand_parser.parse_args(argv)
|
||||
def prepare_to_run_command(self, cmd):
|
||||
"""Prepares to run the command
|
||||
|
||||
# Short-circuit and deal with these commands right away.
|
||||
if args.func == self.do_help:
|
||||
self.do_help(args)
|
||||
return 0
|
||||
elif args.func == self.do_bash_completion:
|
||||
self.do_bash_completion()
|
||||
return 0
|
||||
|
||||
if not (args.os_auth_token and (args.watcher_url or args.os_endpoint)):
|
||||
if not args.os_username:
|
||||
raise exc.CommandError(_("You must provide a username via "
|
||||
"either --os-username or via "
|
||||
"env[OS_USERNAME]"))
|
||||
|
||||
if not args.os_password:
|
||||
# No password, If we've got a tty, try prompting for it
|
||||
if hasattr(sys.stdin, 'isatty') and sys.stdin.isatty():
|
||||
# Check for Ctl-D
|
||||
try:
|
||||
args.os_password = getpass.getpass(
|
||||
'OpenStack Password: ')
|
||||
except EOFError:
|
||||
pass
|
||||
# No password because we didn't have a tty or the
|
||||
# user Ctl-D when prompted.
|
||||
if not args.os_password:
|
||||
raise exc.CommandError(_("You must provide a password via "
|
||||
"either --os-password, "
|
||||
"env[OS_PASSWORD], "
|
||||
"or prompted response"))
|
||||
|
||||
if not (args.os_tenant_id or args.os_tenant_name or
|
||||
args.os_project_id or args.os_project_name):
|
||||
raise exc.CommandError(_(
|
||||
"You must provide a project name or"
|
||||
" project id via --os-project-name, --os-project-id,"
|
||||
" env[OS_PROJECT_ID] or env[OS_PROJECT_NAME]. You may"
|
||||
" use os-project and os-tenant interchangeably."))
|
||||
|
||||
if not args.os_auth_url:
|
||||
raise exc.CommandError(_("You must provide an auth url via "
|
||||
"either --os-auth-url or via "
|
||||
"env[OS_AUTH_URL]"))
|
||||
|
||||
endpoint = args.watcher_url or args.os_endpoint
|
||||
service_type = args.os_service_type or 'infra-optim'
|
||||
project_id = args.os_project_id or args.os_tenant_id
|
||||
project_name = args.os_project_name or args.os_tenant_name
|
||||
|
||||
if (args.os_auth_token and (args.watcher_url or args.os_endpoint)):
|
||||
kwargs = {
|
||||
'token': args.os_auth_token,
|
||||
'insecure': args.insecure,
|
||||
'timeout': args.timeout,
|
||||
'ca_file': args.os_cacert,
|
||||
'cert_file': args.os_cert,
|
||||
'key_file': args.os_key,
|
||||
'auth_ref': None,
|
||||
}
|
||||
elif (args.os_username and
|
||||
args.os_password and
|
||||
args.os_auth_url and
|
||||
(project_id or project_name)):
|
||||
|
||||
keystone_session = kssession.Session.load_from_cli_options(args)
|
||||
|
||||
kwargs = {
|
||||
'username': args.os_username,
|
||||
'user_domain_id': args.os_user_domain_id,
|
||||
'user_domain_name': args.os_user_domain_name,
|
||||
'password': args.os_password,
|
||||
'auth_token': args.os_auth_token,
|
||||
'project_id': project_id,
|
||||
'project_name': project_name,
|
||||
'project_domain_id': args.os_project_domain_id,
|
||||
'project_domain_name': args.os_project_domain_name,
|
||||
}
|
||||
keystone_auth = self._get_keystone_auth(keystone_session,
|
||||
args.os_auth_url,
|
||||
**kwargs)
|
||||
if not endpoint:
|
||||
svc_type = args.os_service_type
|
||||
region_name = args.os_region_name
|
||||
endpoint = keystone_auth.get_endpoint(keystone_session,
|
||||
service_type=svc_type,
|
||||
region_name=region_name)
|
||||
|
||||
endpoint_type = args.os_endpoint_type or 'publicURL'
|
||||
kwargs = {
|
||||
'auth_url': args.os_auth_url,
|
||||
'session': keystone_session,
|
||||
'auth': keystone_auth,
|
||||
'service_type': service_type,
|
||||
'endpoint_type': endpoint_type,
|
||||
'region_name': args.os_region_name,
|
||||
'username': args.os_username,
|
||||
'password': args.os_password,
|
||||
}
|
||||
client = watcher_client.Client(api_version, endpoint, **kwargs)
|
||||
Checks if the minimal parameters are provided and creates the
|
||||
client interface.
|
||||
This is inherited from the framework.
|
||||
"""
|
||||
self.client_manager = namedtuple('ClientManager', 'infra_optim')
|
||||
if cmd.auth_required:
|
||||
client = self.create_client(self.options)
|
||||
setattr(self.client_manager, 'infra-optim', client)
|
||||
|
||||
def run(self, argv):
|
||||
ret_val = 1
|
||||
self.command_options = argv
|
||||
try:
|
||||
args.func(client, args)
|
||||
except exc.Unauthorized:
|
||||
raise exc.CommandError(_("Invalid OpenStack Identity credentials"))
|
||||
ret_val = super(WatcherShell, self).run(argv)
|
||||
return ret_val
|
||||
except Exception as e:
|
||||
if not logging.getLogger('').handlers:
|
||||
logging.basicConfig()
|
||||
self.log.error('Exception raised: %s', str(e))
|
||||
|
||||
@cliutils.arg('command', metavar='<subcommand>', nargs='?',
|
||||
help='Display help for <subcommand>')
|
||||
def do_help(self, args):
|
||||
"""Display help about this program or one of its subcommands."""
|
||||
if getattr(args, 'command', None):
|
||||
if args.command in self.subcommands:
|
||||
self.subcommands[args.command].print_help()
|
||||
else:
|
||||
raise exc.CommandError(_("'%s' is not a valid subcommand") %
|
||||
args.command)
|
||||
else:
|
||||
self.parser.print_help()
|
||||
return ret_val
|
||||
|
||||
finally:
|
||||
self.log.info("END return value: %s", ret_val)
|
||||
|
||||
|
||||
class HelpFormatter(argparse.HelpFormatter):
|
||||
def start_section(self, heading):
|
||||
# Title-case the headings
|
||||
heading = '%s%s' % (heading[0].upper(), heading[1:])
|
||||
super(HelpFormatter, self).start_section(heading)
|
||||
def main(argv=sys.argv[1:]):
|
||||
watcher_app = WatcherShell()
|
||||
return watcher_app.run(argv)
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
WatcherShell().main(sys.argv[1:])
|
||||
except KeyboardInterrupt:
|
||||
print("... terminating watcher client", file=sys.stderr)
|
||||
sys.exit(130)
|
||||
except Exception as e:
|
||||
print(str(e), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
if __name__ == '__main__': # pragma: no cover
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
|
||||
@@ -1,306 +0,0 @@
|
||||
# -*- 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.
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
import fixtures
|
||||
import httpretty
|
||||
from keystoneclient import exceptions as keystone_exc
|
||||
from keystoneclient.fixture import v2 as ks_v2_fixture
|
||||
from keystoneclient.fixture import v3 as ks_v3_fixture
|
||||
import mock
|
||||
import six
|
||||
import testtools
|
||||
from testtools import matchers
|
||||
|
||||
from watcherclient import exceptions as exc
|
||||
from watcherclient import shell as watcher_shell
|
||||
from watcherclient.tests import keystone_client_fixtures
|
||||
from watcherclient.tests import utils
|
||||
|
||||
FAKE_ENV = {'OS_USERNAME': 'username',
|
||||
'OS_PASSWORD': 'password',
|
||||
'OS_TENANT_NAME': 'tenant_name',
|
||||
'OS_AUTH_URL': 'http://no.where/v2.0/'}
|
||||
|
||||
FAKE_ENV_KEYSTONE_V2 = {
|
||||
'OS_USERNAME': 'username',
|
||||
'OS_PASSWORD': 'password',
|
||||
'OS_TENANT_NAME': 'tenant_name',
|
||||
'OS_AUTH_URL': keystone_client_fixtures.BASE_URL,
|
||||
}
|
||||
|
||||
FAKE_ENV_KEYSTONE_V3 = {
|
||||
'OS_USERNAME': 'username',
|
||||
'OS_PASSWORD': 'password',
|
||||
'OS_TENANT_NAME': 'tenant_name',
|
||||
'OS_AUTH_URL': keystone_client_fixtures.BASE_URL,
|
||||
'OS_USER_DOMAIN_ID': 'default',
|
||||
'OS_PROJECT_DOMAIN_ID': 'default',
|
||||
}
|
||||
|
||||
|
||||
class ShellTest(utils.BaseTestCase):
|
||||
re_options = re.DOTALL | re.MULTILINE
|
||||
|
||||
# Patch os.environ to avoid required auth info.
|
||||
def make_env(self, exclude=None):
|
||||
env = dict((k, v) for k, v in FAKE_ENV.items() if k != exclude)
|
||||
self.useFixture(fixtures.MonkeyPatch('os.environ', env))
|
||||
|
||||
def setUp(self):
|
||||
super(ShellTest, self).setUp()
|
||||
|
||||
def shell(self, argstr):
|
||||
orig = sys.stdout
|
||||
try:
|
||||
sys.stdout = six.StringIO()
|
||||
_shell = watcher_shell.WatcherShell()
|
||||
_shell.main(argstr.split())
|
||||
except SystemExit:
|
||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||
self.assertEqual(0, exc_value.code)
|
||||
finally:
|
||||
out = sys.stdout.getvalue()
|
||||
sys.stdout.close()
|
||||
sys.stdout = orig
|
||||
return out
|
||||
|
||||
def test_help_unknown_command(self):
|
||||
self.assertRaises(exc.CommandError, self.shell, 'help foofoo')
|
||||
|
||||
def test_help(self):
|
||||
required = [
|
||||
'.*?^usage: watcher',
|
||||
'.*?^ +bash-completion',
|
||||
'.*?^See "watcher help COMMAND" '
|
||||
'for help on a specific command',
|
||||
]
|
||||
for argstr in ['--help', 'help']:
|
||||
help_text = self.shell(argstr)
|
||||
for r in required:
|
||||
self.assertThat(help_text,
|
||||
matchers.MatchesRegex(r,
|
||||
self.re_options))
|
||||
|
||||
def test_help_on_subcommand(self):
|
||||
required = [
|
||||
'.*?^usage: watcher action-show',
|
||||
".*?^Show detailed information about an action",
|
||||
]
|
||||
argstrings = [
|
||||
'help action-show',
|
||||
]
|
||||
for argstr in argstrings:
|
||||
help_text = self.shell(argstr)
|
||||
for r in required:
|
||||
self.assertThat(help_text,
|
||||
matchers.MatchesRegex(r, self.re_options))
|
||||
|
||||
def test_auth_param(self):
|
||||
self.make_env(exclude='OS_USERNAME')
|
||||
self.test_help()
|
||||
|
||||
@mock.patch('sys.stdin', side_effect=mock.MagicMock)
|
||||
@mock.patch('getpass.getpass', return_value='password')
|
||||
def test_password_prompted(self, mock_getpass, mock_stdin):
|
||||
self.make_env(exclude='OS_PASSWORD')
|
||||
# We will get a Connection Refused because there is no keystone.
|
||||
self.assertRaises(keystone_exc.ConnectionRefused,
|
||||
self.shell, 'action-list')
|
||||
# Make sure we are actually prompted.
|
||||
mock_getpass.assert_called_with('OpenStack Password: ')
|
||||
|
||||
@mock.patch('sys.stdin', side_effect=mock.MagicMock)
|
||||
@mock.patch('getpass.getpass', side_effect=EOFError)
|
||||
def test_password_prompted_ctrlD(self, mock_getpass, mock_stdin):
|
||||
self.make_env(exclude='OS_PASSWORD')
|
||||
# We should get Command Error because we mock Ctl-D.
|
||||
self.assertRaises(exc.CommandError,
|
||||
self.shell, 'action-list')
|
||||
# Make sure we are actually prompted.
|
||||
mock_getpass.assert_called_with('OpenStack Password: ')
|
||||
|
||||
@mock.patch('sys.stdin')
|
||||
def test_no_password_no_tty(self, mock_stdin):
|
||||
# delete the isatty attribute so that we do not get
|
||||
# prompted when manually running the tests
|
||||
del mock_stdin.isatty
|
||||
required = ('You must provide a password'
|
||||
' via either --os-password, env[OS_PASSWORD],'
|
||||
' or prompted response',)
|
||||
self.make_env(exclude='OS_PASSWORD')
|
||||
try:
|
||||
self.shell('action-list')
|
||||
except exc.CommandError as message:
|
||||
self.assertEqual(required, message.args)
|
||||
else:
|
||||
self.fail('CommandError not raised')
|
||||
|
||||
def test_bash_completion(self):
|
||||
stdout = self.shell('bash-completion')
|
||||
# just check we have some output
|
||||
required = [
|
||||
'.*help',
|
||||
'.*audit-list',
|
||||
'.*audit-show',
|
||||
'.*audit-delete',
|
||||
'.*audit-update',
|
||||
'.*audit-template-create',
|
||||
'.*audit-template-update',
|
||||
'.*audit-template-list',
|
||||
'.*audit-template-show',
|
||||
'.*audit-template-delete',
|
||||
'.*action-list',
|
||||
'.*action-show',
|
||||
'.*action-update',
|
||||
'.*action-plan-list',
|
||||
'.*action-plan-show',
|
||||
'.*action-plan-update',
|
||||
]
|
||||
for r in required:
|
||||
self.assertThat(stdout,
|
||||
matchers.MatchesRegex(r, self.re_options))
|
||||
|
||||
|
||||
class TestCase(testtools.TestCase):
|
||||
|
||||
tokenid = keystone_client_fixtures.TOKENID
|
||||
|
||||
def set_fake_env(self, fake_env):
|
||||
client_env = ('OS_USERNAME', 'OS_PASSWORD', 'OS_TENANT_ID',
|
||||
'OS_TENANT_NAME', 'OS_AUTH_URL', 'OS_REGION_NAME',
|
||||
'OS_AUTH_TOKEN', 'OS_NO_CLIENT_AUTH', 'OS_SERVICE_TYPE',
|
||||
'OS_ENDPOINT_TYPE')
|
||||
|
||||
for key in client_env:
|
||||
self.useFixture(
|
||||
fixtures.EnvironmentVariable(key, fake_env.get(key)))
|
||||
|
||||
# required for testing with Python 2.6
|
||||
def assertRegexpMatches(self, text, expected_regexp, msg=None):
|
||||
"""Fail the test unless the text matches the regular expression."""
|
||||
if isinstance(expected_regexp, six.string_types):
|
||||
expected_regexp = re.compile(expected_regexp)
|
||||
if not expected_regexp.search(text):
|
||||
msg = msg or "Regexp didn't match"
|
||||
msg = '%s: %r not found in %r' % (
|
||||
msg, expected_regexp.pattern, text)
|
||||
raise self.failureException(msg)
|
||||
|
||||
def register_keystone_v2_token_fixture(self):
|
||||
v2_token = ks_v2_fixture.Token(token_id=self.tokenid)
|
||||
service = v2_token.add_service('baremetal')
|
||||
service.add_endpoint('http://watcher.example.com', region='RegionOne')
|
||||
httpretty.register_uri(
|
||||
httpretty.POST,
|
||||
'%s/tokens' % (keystone_client_fixtures.V2_URL),
|
||||
body=json.dumps(v2_token))
|
||||
|
||||
def register_keystone_v3_token_fixture(self):
|
||||
v3_token = ks_v3_fixture.Token()
|
||||
service = v3_token.add_service('baremetal')
|
||||
service.add_standard_endpoints(public='http://watcher.example.com')
|
||||
httpretty.register_uri(
|
||||
httpretty.POST,
|
||||
'%s/auth/tokens' % (keystone_client_fixtures.V3_URL),
|
||||
body=json.dumps(v3_token),
|
||||
adding_headers={'X-Subject-Token': self.tokenid})
|
||||
|
||||
def register_keystone_auth_fixture(self):
|
||||
self.register_keystone_v2_token_fixture()
|
||||
self.register_keystone_v3_token_fixture()
|
||||
httpretty.register_uri(
|
||||
httpretty.GET,
|
||||
keystone_client_fixtures.BASE_URL,
|
||||
body=keystone_client_fixtures.keystone_request_callback)
|
||||
|
||||
|
||||
class ShellTestNoMox(TestCase):
|
||||
def setUp(self):
|
||||
super(ShellTestNoMox, self).setUp()
|
||||
# httpretty doesn't work as expected if http proxy environment
|
||||
# variable is set.
|
||||
os.environ = dict((k, v) for (k, v) in os.environ.items()
|
||||
if k.lower() not in ('http_proxy', 'https_proxy'))
|
||||
self.set_fake_env(FAKE_ENV_KEYSTONE_V2)
|
||||
|
||||
def shell(self, argstr):
|
||||
orig = sys.stdout
|
||||
try:
|
||||
sys.stdout = six.StringIO()
|
||||
_shell = watcher_shell.WatcherShell()
|
||||
_shell.main(argstr.split())
|
||||
self.subcommands = _shell.subcommands.keys()
|
||||
except SystemExit:
|
||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||
self.assertEqual(0, exc_value.code)
|
||||
finally:
|
||||
out = sys.stdout.getvalue()
|
||||
sys.stdout.close()
|
||||
sys.stdout = orig
|
||||
|
||||
return out
|
||||
|
||||
# @httpretty.activate
|
||||
# def test_action_list(self):
|
||||
# self.register_keystone_auth_fixture()
|
||||
# resp_dict = {"dummies": [
|
||||
# {"instance_uuid": "null",
|
||||
# "uuid": "351a82d6-9f04-4c36-b79a-a38b9e98ff71",
|
||||
# "links": [{"href": "http://watcher.example.com:6385/"
|
||||
# "v1/dummies/foo",
|
||||
# "rel": "self"},
|
||||
# {"href": "http://watcher.example.com:6385/"
|
||||
# "dummies/foo",
|
||||
# "rel": "bookmark"}],
|
||||
# "maintenance": "false",
|
||||
# "provision_state": "null",
|
||||
# "power_state": "power off"},
|
||||
# {"instance_uuid": "null",
|
||||
# "uuid": "66fbba13-29e8-4b8a-9e80-c655096a40d3",
|
||||
# "links": [{"href": "http://watcher.example.com:6385/"
|
||||
# "v1/dummies/foo2",
|
||||
# "rel": "self"},
|
||||
# {"href": "http://watcher.example.com:6385/"
|
||||
# "dummies/foo2",
|
||||
# "rel": "bookmark"}],
|
||||
# "maintenance": "false",
|
||||
# "provision_state": "null",
|
||||
# "power_state": "power off"}]}
|
||||
# httpretty.register_uri(
|
||||
# httpretty.GET,
|
||||
# 'http://watcher.example.com/v1/dummies',
|
||||
# status=200,
|
||||
# content_type='application/json; charset=UTF-8',
|
||||
# body=json.dumps(resp_dict))
|
||||
|
||||
# event_list_text = self.shell('action-list')
|
||||
|
||||
# required = [
|
||||
# '351a82d6-9f04-4c36-b79a-a38b9e98ff71',
|
||||
# '66fbba13-29e8-4b8a-9e80-c655096a40d3',
|
||||
# ]
|
||||
|
||||
# for r in required:
|
||||
# self.assertRegexpMatches(event_list_text, r)
|
||||
|
||||
|
||||
class ShellTestNoMoxV3(ShellTestNoMox):
|
||||
|
||||
def _set_fake_env(self):
|
||||
self.set_fake_env(FAKE_ENV_KEYSTONE_V3)
|
||||
@@ -17,8 +17,8 @@
|
||||
|
||||
import mock
|
||||
|
||||
from watcherclient.common.apiclient import exceptions as exc
|
||||
from watcherclient.common import utils
|
||||
from watcherclient import exceptions as exc
|
||||
from watcherclient.tests import utils as test_utils
|
||||
|
||||
|
||||
|
||||
@@ -21,13 +21,13 @@ import os
|
||||
|
||||
import fixtures
|
||||
from oslo_utils import strutils
|
||||
from oslotest import base
|
||||
import six
|
||||
import testtools
|
||||
|
||||
from watcherclient.common import http
|
||||
|
||||
|
||||
class BaseTestCase(testtools.TestCase):
|
||||
class BaseTestCase(base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(BaseTestCase, self).setUp()
|
||||
|
||||
67
watcherclient/tests/v1/base.py
Normal file
67
watcherclient/tests/v1/base.py
Normal file
@@ -0,0 +1,67 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
import shlex
|
||||
|
||||
import mock
|
||||
|
||||
from watcherclient import shell
|
||||
from watcherclient.tests import utils
|
||||
from watcherclient.v1 import client
|
||||
|
||||
|
||||
class CommandTestCase(utils.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(CommandTestCase, self).setUp()
|
||||
|
||||
self.p_build_http_client = mock.patch.object(
|
||||
client.Client, 'build_http_client')
|
||||
self.m_build_http_client = self.p_build_http_client.start()
|
||||
|
||||
self.m_watcher_client = mock.Mock(side_effect=client.Client)
|
||||
self.p_create_client = mock.patch.object(
|
||||
shell.WatcherShell, 'create_client', self.m_watcher_client)
|
||||
self.p_create_client.start()
|
||||
|
||||
self.addCleanup(self.p_build_http_client.stop)
|
||||
self.addCleanup(self.p_create_client.stop)
|
||||
|
||||
def run_cmd(self, cmd, formatting='json'):
|
||||
if formatting:
|
||||
formatter_arg = " -f %s" % formatting
|
||||
formatter = json.loads
|
||||
else:
|
||||
formatter_arg = ''
|
||||
formatter = str
|
||||
formatted_cmd = "%(cmd)s%(formatter)s" % dict(
|
||||
cmd=cmd, formatter=formatter_arg)
|
||||
|
||||
exit_code = self.cmd.run(shlex.split(formatted_cmd))
|
||||
|
||||
try:
|
||||
raw_data = self.stdout.getvalue()
|
||||
formatted_output = formatter(self.stdout.getvalue())
|
||||
except Exception:
|
||||
self.fail("Formatting error (`%s` -> '%s')" %
|
||||
(raw_data, formatting))
|
||||
return exit_code, formatted_output
|
||||
|
||||
def resource_as_dict(self, resource, columns=(), column_headers=()):
|
||||
mapping = dict(zip(columns, column_headers))
|
||||
return {mapping[k]: v for k, v in resource.to_dict().items()
|
||||
if not columns or columns and k in mapping}
|
||||
@@ -30,7 +30,6 @@ ACTION1 = {
|
||||
'description': 'Action_1 description',
|
||||
'next': '239f02a5-9649-4e14-9d33-ac2bf67cb755',
|
||||
'state': 'PENDING',
|
||||
'alarm': None
|
||||
}
|
||||
|
||||
ACTION2 = {
|
||||
@@ -40,7 +39,6 @@ ACTION2 = {
|
||||
'description': 'Action_2 description',
|
||||
'next': '67653274-eb24-c7ba-70f6-a84e73d80843',
|
||||
'state': 'PENDING',
|
||||
'alarm': None
|
||||
}
|
||||
|
||||
ACTION3 = {
|
||||
@@ -50,7 +48,6 @@ ACTION3 = {
|
||||
'description': 'Action_3 description',
|
||||
'next': None,
|
||||
'state': 'PENDING',
|
||||
'alarm': None
|
||||
}
|
||||
|
||||
ACTION_PLAN1 = {
|
||||
@@ -254,7 +251,6 @@ class ActionManagerTest(testtools.TestCase):
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(ACTION1['uuid'], action.uuid)
|
||||
self.assertEqual(ACTION1['action_plan'], action.action_plan)
|
||||
self.assertEqual(ACTION1['alarm'], action.alarm)
|
||||
self.assertEqual(ACTION1['next'], action.next)
|
||||
|
||||
def test_delete(self):
|
||||
|
||||
@@ -38,7 +38,7 @@ ACTION_PLAN2 = {
|
||||
}
|
||||
|
||||
UPDATED_ACTION_PLAN = copy.deepcopy(ACTION_PLAN1)
|
||||
NEW_STATE = 'STARTING'
|
||||
NEW_STATE = 'PENDING'
|
||||
UPDATED_ACTION_PLAN['state'] = NEW_STATE
|
||||
|
||||
fake_responses = {
|
||||
|
||||
@@ -1,148 +1,259 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright (c) 2016 b<>com
|
||||
#
|
||||
# Copyright 2013 IBM Corp
|
||||
# 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
|
||||
#
|
||||
# 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
|
||||
#
|
||||
# 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.
|
||||
# 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 datetime
|
||||
|
||||
import mock
|
||||
import six
|
||||
|
||||
from watcherclient.common import utils as commonutils
|
||||
from watcherclient.openstack.common.apiclient.exceptions import ValidationError
|
||||
from watcherclient.openstack.common import cliutils
|
||||
from watcherclient.tests import utils
|
||||
import watcherclient.v1.action_plan_shell as ap_shell
|
||||
from watcherclient import exceptions
|
||||
from watcherclient import shell
|
||||
from watcherclient.tests.v1 import base
|
||||
from watcherclient import v1 as resource
|
||||
from watcherclient.v1 import resource_fields
|
||||
|
||||
ACTION_PLAN_1 = {
|
||||
'uuid': 'd9d9978e-6db5-4a05-8eab-1531795d7004',
|
||||
'audit_uuid': '770ef053-ecb3-48b0-85b5-d55a2dbc6588',
|
||||
'state': 'RECOMMENDED',
|
||||
'efficacy_indicators': [{'description': 'Indicator 1',
|
||||
'name': 'indicator1',
|
||||
'unit': '%'}],
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'global_efficacy': {
|
||||
"value": 99,
|
||||
"unit": "%",
|
||||
"name": "dummy_global_efficacy",
|
||||
"description": "Dummy Global Efficacy",
|
||||
},
|
||||
'deleted_at': None,
|
||||
}
|
||||
|
||||
ACTION_PLAN_2 = {
|
||||
'uuid': 'd6363285-5afa-4a26-96f2-89441e335765',
|
||||
'audit_uuid': '239f02a5-9649-4e14-9d33-ac2bf67cb755',
|
||||
'state': 'RECOMMENDED',
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'efficacy_indicators': [{'description': 'Indicator 2',
|
||||
'name': 'indicator2',
|
||||
'unit': '%'}],
|
||||
'updated_at': None,
|
||||
'global_efficacy': {
|
||||
"value": 87,
|
||||
"unit": "%",
|
||||
"name": "dummy_global_efficacy",
|
||||
"description": "Dummy Global Efficacy",
|
||||
},
|
||||
'deleted_at': None,
|
||||
}
|
||||
|
||||
|
||||
class ActionPlanShellTest(utils.BaseTestCase):
|
||||
def test_do_action_plan_show(self):
|
||||
actual = {}
|
||||
fake_print_dict = lambda data, *args, **kwargs: actual.update(data)
|
||||
with mock.patch.object(cliutils, 'print_dict', fake_print_dict):
|
||||
action_plan = object()
|
||||
ap_shell._print_action_plan_show(action_plan)
|
||||
exp = ['uuid', 'created_at', 'updated_at', 'deleted_at',
|
||||
'state', 'audit_uuid']
|
||||
act = actual.keys()
|
||||
self.assertEqual(sorted(exp), sorted(act))
|
||||
class ActionPlanShellTest(base.CommandTestCase):
|
||||
|
||||
SHORT_LIST_FIELDS = resource_fields.ACTION_PLAN_SHORT_LIST_FIELDS
|
||||
SHORT_LIST_FIELD_LABELS = (
|
||||
resource_fields.ACTION_PLAN_SHORT_LIST_FIELD_LABELS)
|
||||
FIELDS = resource_fields.ACTION_PLAN_FIELDS
|
||||
FIELD_LABELS = resource_fields.ACTION_PLAN_FIELD_LABELS
|
||||
|
||||
def setUp(self):
|
||||
super(self.__class__, self).setUp()
|
||||
|
||||
p_audit_manager = mock.patch.object(resource, 'AuditManager')
|
||||
p_audit_template_manager = mock.patch.object(
|
||||
resource, 'ActionPlanManager')
|
||||
p_action_plan_manager = mock.patch.object(
|
||||
resource, 'ActionPlanManager')
|
||||
|
||||
self.m_audit_mgr_cls = p_audit_manager.start()
|
||||
self.m_audit_template_mgr_cls = p_audit_template_manager.start()
|
||||
self.m_action_plan_mgr_cls = p_action_plan_manager.start()
|
||||
|
||||
self.addCleanup(p_audit_manager.stop)
|
||||
self.addCleanup(p_audit_template_manager.stop)
|
||||
self.addCleanup(p_action_plan_manager.stop)
|
||||
|
||||
self.m_audit_mgr = mock.Mock()
|
||||
self.m_audit_template_mgr = mock.Mock()
|
||||
self.m_action_plan_mgr = mock.Mock()
|
||||
|
||||
self.m_audit_mgr_cls.return_value = self.m_audit_mgr
|
||||
self.m_audit_template_mgr_cls.return_value = self.m_audit_template_mgr
|
||||
self.m_action_plan_mgr_cls.return_value = self.m_action_plan_mgr
|
||||
|
||||
self.stdout = six.StringIO()
|
||||
self.cmd = shell.WatcherShell(stdout=self.stdout)
|
||||
|
||||
def test_do_action_plan_list(self):
|
||||
action_plan1 = resource.ActionPlan(mock.Mock(), ACTION_PLAN_1)
|
||||
action_plan2 = resource.ActionPlan(mock.Mock(), ACTION_PLAN_2)
|
||||
self.m_action_plan_mgr.list.return_value = [
|
||||
action_plan1, action_plan2]
|
||||
|
||||
exit_code, results = self.run_cmd('actionplan list')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(action_plan1, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS),
|
||||
self.resource_as_dict(action_plan2, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_action_plan_mgr.list.assert_called_once_with(detail=False)
|
||||
|
||||
def test_do_action_plan_list_detail(self):
|
||||
action_plan1 = resource.ActionPlan(mock.Mock(), ACTION_PLAN_1)
|
||||
action_plan2 = resource.ActionPlan(mock.Mock(), ACTION_PLAN_2)
|
||||
self.m_action_plan_mgr.list.return_value = [
|
||||
action_plan1, action_plan2]
|
||||
|
||||
exit_code, results = self.run_cmd('actionplan list --detail')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(action_plan1, self.FIELDS,
|
||||
self.FIELD_LABELS),
|
||||
self.resource_as_dict(action_plan2, self.FIELDS,
|
||||
self.FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_action_plan_mgr.list.assert_called_once_with(detail=True)
|
||||
|
||||
def test_do_action_plan_list_filter_by_audit(self):
|
||||
action_plan1 = resource.ActionPlan(mock.Mock(), ACTION_PLAN_1)
|
||||
self.m_action_plan_mgr.list.return_value = [action_plan1]
|
||||
|
||||
exit_code, results = self.run_cmd(
|
||||
'actionplan list --audit '
|
||||
'770ef053-ecb3-48b0-85b5-d55a2dbc6588')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(action_plan1, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_action_plan_mgr.list.assert_called_once_with(
|
||||
detail=False,
|
||||
audit='770ef053-ecb3-48b0-85b5-d55a2dbc6588',
|
||||
)
|
||||
|
||||
def test_do_action_plan_show_by_uuid(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
setattr(args, 'action-plan', 'a5199d0e-0702-4613-9234-5ae2af8dafea')
|
||||
action_plan = resource.ActionPlan(mock.Mock(), ACTION_PLAN_1)
|
||||
self.m_action_plan_mgr.get.return_value = action_plan
|
||||
|
||||
ap_shell.do_action_plan_show(client_mock, args)
|
||||
client_mock.action_plan.get.assert_called_once_with(
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafea'
|
||||
)
|
||||
# assert get_by_name() wasn't called
|
||||
self.assertFalse(client_mock.action_plan.get_by_name.called)
|
||||
exit_code, result = self.run_cmd(
|
||||
'actionplan show d9d9978e-6db5-4a05-8eab-1531795d7004')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(
|
||||
action_plan, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_action_plan_mgr.get.assert_called_once_with(
|
||||
'd9d9978e-6db5-4a05-8eab-1531795d7004')
|
||||
|
||||
def test_do_action_plan_show_by_not_uuid(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
setattr(args, 'action-plan', 'not_uuid')
|
||||
self.m_action_plan_mgr.get.side_effect = exceptions.HTTPNotFound
|
||||
|
||||
self.assertRaises(ValidationError, ap_shell.do_action_plan_show,
|
||||
client_mock, args)
|
||||
exit_code, result = self.run_cmd(
|
||||
'actionplan show not_uuid', formatting=None)
|
||||
|
||||
self.assertEqual(1, exit_code)
|
||||
self.assertEqual('', result)
|
||||
|
||||
def test_do_action_plan_delete(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
delete = ['a5199d0e-0702-4613-9234-5ae2af8dafea']
|
||||
setattr(args, 'action-plan', delete)
|
||||
self.m_action_plan_mgr.delete.return_value = ''
|
||||
|
||||
ap_shell.do_action_plan_delete(client_mock, args)
|
||||
client_mock.action_plan.delete.assert_called_once_with(
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafea')
|
||||
exit_code, result = self.run_cmd(
|
||||
'actionplan delete 5869da81-4876-4687-a1ed-12cd64cf53d9',
|
||||
formatting=None)
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual('', result)
|
||||
self.m_action_plan_mgr.delete.assert_called_once_with(
|
||||
'5869da81-4876-4687-a1ed-12cd64cf53d9')
|
||||
|
||||
def test_do_action_plan_delete_not_uuid(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
setattr(args, 'action-plan', ['not_uuid'])
|
||||
exit_code, result = self.run_cmd(
|
||||
'actionplan delete not_uuid', formatting=None)
|
||||
|
||||
self.assertRaises(ValidationError, ap_shell.do_action_plan_delete,
|
||||
client_mock, args)
|
||||
self.assertEqual(1, exit_code)
|
||||
self.assertEqual('', result)
|
||||
|
||||
def test_do_action_plan_delete_multiple(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
setattr(args, 'action-plan',
|
||||
["a5199d0e-0702-4613-9234-5ae2af8dafea",
|
||||
"a5199d0e-0702-4613-9234-5ae2af8dafeb"])
|
||||
self.m_action_plan_mgr.delete.return_value = ''
|
||||
|
||||
ap_shell.do_action_plan_delete(client_mock, args)
|
||||
client_mock.action_plan.delete.assert_has_calls(
|
||||
[mock.call('a5199d0e-0702-4613-9234-5ae2af8dafea'),
|
||||
mock.call('a5199d0e-0702-4613-9234-5ae2af8dafeb')])
|
||||
exit_code, result = self.run_cmd(
|
||||
'actionplan delete 5869da81-4876-4687-a1ed-12cd64cf53d9 '
|
||||
'c20627fa-ea70-4d56-ae15-4106358f773b',
|
||||
formatting=None)
|
||||
|
||||
def test_do_action_plan_delete_multiple_not_uuid(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
setattr(args, 'action-plan',
|
||||
["a5199d0e-0702-4613-9234-5ae2af8dafea",
|
||||
"not_uuid",
|
||||
"a5199d0e-0702-4613-9234-5ae2af8dafeb"])
|
||||
|
||||
self.assertRaises(ValidationError, ap_shell.do_action_plan_delete,
|
||||
client_mock, args)
|
||||
client_mock.action_plan.delete.assert_has_calls(
|
||||
[mock.call('a5199d0e-0702-4613-9234-5ae2af8dafea')])
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual('', result)
|
||||
self.m_action_plan_mgr.delete.assert_any_call(
|
||||
'5869da81-4876-4687-a1ed-12cd64cf53d9')
|
||||
self.m_action_plan_mgr.delete.assert_any_call(
|
||||
'c20627fa-ea70-4d56-ae15-4106358f773b')
|
||||
|
||||
def test_do_action_plan_update(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
action_plan = resource.ActionPlan(mock.Mock(), ACTION_PLAN_1)
|
||||
self.m_action_plan_mgr.update.return_value = action_plan
|
||||
|
||||
setattr(args, 'action-plan', "a5199d0e-0702-4613-9234-5ae2af8dafea")
|
||||
args.op = 'add'
|
||||
args.attributes = [['arg1=val1', 'arg2=val2']]
|
||||
exit_code, result = self.run_cmd(
|
||||
'actionplan update 5869da81-4876-4687-a1ed-12cd64cf53d9 '
|
||||
'replace state=CANCELLED')
|
||||
|
||||
ap_shell.do_action_plan_update(client_mock, args)
|
||||
patch = commonutils.args_array_to_patch(
|
||||
args.op,
|
||||
args.attributes[0])
|
||||
client_mock.action_plan.update.assert_called_once_with(
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafea', patch)
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(action_plan, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_action_plan_mgr.update.assert_called_once_with(
|
||||
'5869da81-4876-4687-a1ed-12cd64cf53d9',
|
||||
[{'op': 'replace', 'path': '/state', 'value': 'CANCELLED'}])
|
||||
|
||||
def test_do_action_plan_update_not_uuid(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
exit_code, result = self.run_cmd(
|
||||
'actionplan update not_uuid '
|
||||
'replace state=CANCELLED',
|
||||
formatting=None)
|
||||
|
||||
setattr(args, 'action-plan', "not_uuid")
|
||||
args.op = 'add'
|
||||
args.attributes = [['arg1=val1', 'arg2=val2']]
|
||||
|
||||
self.assertRaises(ValidationError, ap_shell.do_action_plan_update,
|
||||
client_mock, args)
|
||||
self.assertEqual(1, exit_code)
|
||||
self.assertEqual('', result)
|
||||
|
||||
def test_do_action_plan_start(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
action_plan = resource.ActionPlan(mock.Mock(), ACTION_PLAN_1)
|
||||
self.m_action_plan_mgr.start.return_value = action_plan
|
||||
|
||||
action_plan = 'a5199d0e-0702-4613-9234-5ae2af8dafea'
|
||||
setattr(args, 'action-plan', action_plan)
|
||||
exit_code, result = self.run_cmd(
|
||||
'actionplan start 5869da81-4876-4687-a1ed-12cd64cf53d9')
|
||||
|
||||
ap_shell.do_action_plan_start(client_mock, args)
|
||||
patch = commonutils.args_array_to_patch(
|
||||
'replace', ['state=STARTING'])
|
||||
client_mock.action_plan.update.assert_called_once_with(
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafea', patch)
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(action_plan, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_action_plan_mgr.start.assert_called_once_with(
|
||||
'5869da81-4876-4687-a1ed-12cd64cf53d9')
|
||||
|
||||
def test_do_action_plan_start_not_uuid(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
exit_code, result = self.run_cmd(
|
||||
'actionplan start not_uuid',
|
||||
formatting=None)
|
||||
|
||||
action_plan = 'not_uuid'
|
||||
setattr(args, 'action-plan', action_plan)
|
||||
|
||||
self.assertRaises(ValidationError, ap_shell.do_action_plan_start,
|
||||
client_mock, args)
|
||||
self.assertEqual(1, exit_code)
|
||||
self.assertEqual('', result)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# Copyright 2013 IBM Corp
|
||||
#
|
||||
# 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
|
||||
# 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
|
||||
@@ -14,119 +14,143 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import datetime
|
||||
|
||||
import mock
|
||||
import six
|
||||
|
||||
from watcherclient.common import utils as commonutils
|
||||
from watcherclient.openstack.common.apiclient.exceptions import ValidationError
|
||||
from watcherclient.openstack.common import cliutils
|
||||
from watcherclient.tests import utils
|
||||
import watcherclient.v1.action_shell as a_shell
|
||||
from watcherclient import exceptions
|
||||
from watcherclient import shell
|
||||
from watcherclient.tests.v1 import base
|
||||
from watcherclient import v1 as resource
|
||||
from watcherclient.v1 import resource_fields
|
||||
|
||||
ACTION_1 = {
|
||||
'uuid': '770ef053-ecb3-48b0-85b5-d55a2dbc6588',
|
||||
'action_plan_uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2',
|
||||
'state': 'PENDING',
|
||||
'action_type': 'migrate',
|
||||
'next_uuid': '239f02a5-9649-4e14-9d33-ac2bf67cb755',
|
||||
'input_parameters': {"test": 1},
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
}
|
||||
|
||||
ACTION_2 = {
|
||||
'uuid': '239f02a5-9649-4e14-9d33-ac2bf67cb755',
|
||||
'action_plan_uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2',
|
||||
'state': 'PENDING',
|
||||
'action_type': 'migrate',
|
||||
'next_uuid': '67653274-eb24-c7ba-70f6-a84e73d80843',
|
||||
'input_parameters': {"test": 2},
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
}
|
||||
|
||||
ACTION_3 = {
|
||||
'uuid': '67653274-eb24-c7ba-70f6-a84e73d80843',
|
||||
'action_plan_uuid': 'a5199d0e-0702-4613-9234-5ae2af8dafea',
|
||||
'next_uuid': None,
|
||||
'state': 'PENDING',
|
||||
'action_type': 'sleep',
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
}
|
||||
|
||||
ACTION_PLAN_1 = {
|
||||
'uuid': 'a5199d0e-0702-4613-9234-5ae2af8dafea',
|
||||
'action': '770ef053-ecb3-48b0-85b5-d55a2dbc6588',
|
||||
'state': 'RECOMMENDED',
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
}
|
||||
|
||||
|
||||
class ActionShellTest(utils.BaseTestCase):
|
||||
def test_do_action_show(self):
|
||||
actual = {}
|
||||
fake_print_dict = lambda data, *args, **kwargs: actual.update(data)
|
||||
with mock.patch.object(cliutils, 'print_dict', fake_print_dict):
|
||||
action = object()
|
||||
a_shell._print_action_show(action)
|
||||
exp = ['action_type',
|
||||
'alarm',
|
||||
'applies_to',
|
||||
'created_at',
|
||||
'deleted_at',
|
||||
'description',
|
||||
'dst',
|
||||
'next_uuid',
|
||||
'parameter',
|
||||
'src',
|
||||
'state',
|
||||
'action_plan_uuid',
|
||||
'updated_at',
|
||||
'uuid']
|
||||
act = actual.keys()
|
||||
self.assertEqual(sorted(exp), sorted(act))
|
||||
class ActionShellTest(base.CommandTestCase):
|
||||
|
||||
SHORT_LIST_FIELDS = resource_fields.ACTION_SHORT_LIST_FIELDS
|
||||
SHORT_LIST_FIELD_LABELS = resource_fields.ACTION_SHORT_LIST_FIELD_LABELS
|
||||
FIELDS = resource_fields.ACTION_FIELDS
|
||||
FIELD_LABELS = resource_fields.ACTION_FIELD_LABELS
|
||||
|
||||
def setUp(self):
|
||||
super(self.__class__, self).setUp()
|
||||
|
||||
p_action_manager = mock.patch.object(resource, 'ActionManager')
|
||||
p_action_plan_manager = mock.patch.object(
|
||||
resource, 'ActionPlanManager')
|
||||
self.m_action_mgr_cls = p_action_manager.start()
|
||||
self.m_action_plan_mgr_cls = p_action_plan_manager.start()
|
||||
self.addCleanup(p_action_manager.stop)
|
||||
self.addCleanup(p_action_plan_manager.stop)
|
||||
|
||||
self.m_action_mgr = mock.Mock()
|
||||
self.m_action_plan_mgr = mock.Mock()
|
||||
|
||||
self.m_action_mgr_cls.return_value = self.m_action_mgr
|
||||
self.m_action_plan_mgr_cls.return_value = self.m_action_plan_mgr
|
||||
|
||||
self.stdout = six.StringIO()
|
||||
self.cmd = shell.WatcherShell(stdout=self.stdout)
|
||||
|
||||
def test_do_action_list(self):
|
||||
action1 = resource.Action(mock.Mock(), ACTION_1)
|
||||
action2 = resource.Action(mock.Mock(), ACTION_2)
|
||||
self.m_action_mgr.list.return_value = [action1, action2]
|
||||
|
||||
exit_code, results = self.run_cmd('action list')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(action1, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS),
|
||||
self.resource_as_dict(action2, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_action_mgr.list.assert_called_once_with(detail=False)
|
||||
|
||||
def test_do_action_list_detail(self):
|
||||
action1 = resource.Action(mock.Mock(), ACTION_1)
|
||||
action2 = resource.Action(mock.Mock(), ACTION_2)
|
||||
self.m_action_mgr.list.return_value = [action1, action2]
|
||||
|
||||
exit_code, results = self.run_cmd('action list --detail')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(action1, self.FIELDS,
|
||||
self.FIELD_LABELS),
|
||||
self.resource_as_dict(action2, self.FIELDS,
|
||||
self.FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_action_mgr.list.assert_called_once_with(detail=True)
|
||||
|
||||
def test_do_action_show_by_uuid(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.action = 'a5199d0e-0702-4613-9234-5ae2af8dafea'
|
||||
action = resource.Action(mock.Mock(), ACTION_1)
|
||||
self.m_action_mgr.get.return_value = action
|
||||
self.m_action_plan_mgr.get.return_value = action
|
||||
|
||||
a_shell.do_action_show(client_mock, args)
|
||||
client_mock.action.get.assert_called_once_with(
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafea'
|
||||
)
|
||||
# assert get_by_name() wasn't called
|
||||
self.assertFalse(client_mock.action.get_by_name.called)
|
||||
exit_code, result = self.run_cmd(
|
||||
'action show 5869da81-4876-4687-a1ed-12cd64cf53d9')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(action, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_action_mgr.get.assert_called_once_with(
|
||||
'5869da81-4876-4687-a1ed-12cd64cf53d9')
|
||||
|
||||
def test_do_action_show_by_not_uuid(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.action = 'not_uuid'
|
||||
self.m_action_mgr.get.side_effect = exceptions.HTTPNotFound
|
||||
|
||||
self.assertRaises(ValidationError, a_shell.do_action_show,
|
||||
client_mock, args)
|
||||
exit_code, result = self.run_cmd(
|
||||
'action show not_uuid', formatting=None)
|
||||
|
||||
def test_do_action_delete(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.action = ['a5199d0e-0702-4613-9234-5ae2af8dafea']
|
||||
|
||||
a_shell.do_action_delete(client_mock, args)
|
||||
client_mock.action.delete.assert_called_once_with(
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafea')
|
||||
|
||||
def test_do_action_delete_not_uuid(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.action = ['not_uuid']
|
||||
|
||||
self.assertRaises(ValidationError, a_shell.do_action_delete,
|
||||
client_mock, args)
|
||||
|
||||
def test_do_action_delete_multiple(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.action = ['a5199d0e-0702-4613-9234-5ae2af8dafea',
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafeb']
|
||||
|
||||
a_shell.do_action_delete(client_mock, args)
|
||||
client_mock.action.delete.assert_has_calls(
|
||||
[mock.call('a5199d0e-0702-4613-9234-5ae2af8dafea'),
|
||||
mock.call('a5199d0e-0702-4613-9234-5ae2af8dafeb')])
|
||||
|
||||
def test_do_action_delete_multiple_not_uuid(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.action = ['a5199d0e-0702-4613-9234-5ae2af8dafea',
|
||||
'not_uuid'
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafeb']
|
||||
|
||||
self.assertRaises(ValidationError, a_shell.do_action_delete,
|
||||
client_mock, args)
|
||||
client_mock.action.delete.assert_has_calls(
|
||||
[mock.call('a5199d0e-0702-4613-9234-5ae2af8dafea')])
|
||||
|
||||
def test_do_action_update(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.action = 'a5199d0e-0702-4613-9234-5ae2af8dafea'
|
||||
args.op = 'add'
|
||||
args.attributes = [['arg1=val1', 'arg2=val2']]
|
||||
|
||||
a_shell.do_action_update(client_mock, args)
|
||||
patch = commonutils.args_array_to_patch(
|
||||
args.op,
|
||||
args.attributes[0])
|
||||
client_mock.action.update.assert_called_once_with(
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafea', patch)
|
||||
|
||||
def test_do_action_update_not_uuid(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.action = 'not_uuid'
|
||||
args.op = 'add'
|
||||
args.attributes = [['arg1=val1', 'arg2=val2']]
|
||||
|
||||
self.assertRaises(ValidationError, a_shell.do_action_update,
|
||||
client_mock, args)
|
||||
self.assertEqual(1, exit_code)
|
||||
self.assertEqual('', result)
|
||||
|
||||
@@ -27,7 +27,7 @@ AUDIT1 = {
|
||||
'id': 1,
|
||||
'uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2',
|
||||
'deadline': None,
|
||||
'type': 'ONE_SHOT',
|
||||
'audit_type': 'ONE_SHOT',
|
||||
'audit_template_uuid': '770ef053-ecb3-48b0-85b5-d55a2dbc6588'
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ AUDIT2 = {
|
||||
'id': 2,
|
||||
'uuid': 'a5199d0e-0702-4613-9234-5ae2af8dafea',
|
||||
'deadline': None,
|
||||
'type': 'ONE_SHOT',
|
||||
'audit_type': 'ONE_SHOT',
|
||||
'audit_template_uuid': '770ef053-ecb3-48b0-85b5-d55a2dbc6588'
|
||||
}
|
||||
|
||||
|
||||
@@ -14,132 +14,320 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import datetime
|
||||
|
||||
import mock
|
||||
import six
|
||||
|
||||
from watcherclient.common import utils as commonutils
|
||||
from watcherclient.openstack.common.apiclient.exceptions import ValidationError
|
||||
from watcherclient.openstack.common import cliutils
|
||||
from watcherclient.tests import utils
|
||||
import watcherclient.v1.audit_shell as a_shell
|
||||
from watcherclient import exceptions
|
||||
from watcherclient import shell
|
||||
from watcherclient.tests.v1 import base
|
||||
from watcherclient import v1 as resource
|
||||
from watcherclient.v1 import resource_fields
|
||||
|
||||
AUDIT_TEMPLATE_1 = {
|
||||
'uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2',
|
||||
'name': 'at1',
|
||||
'description': 'Audit Template 1 description',
|
||||
'host_aggregate': 5,
|
||||
'extra': {'automatic': False},
|
||||
'goal_uuid': '7568667b-51fe-4087-9eb1-29b26891036f',
|
||||
'strategy_uuid': 'bbe6b966-f98e-439b-a01a-17b9b3b8478b',
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
}
|
||||
|
||||
AUDIT_1 = {
|
||||
'uuid': '5869da81-4876-4687-a1ed-12cd64cf53d9',
|
||||
'deadline': None,
|
||||
'audit_type': 'ONESHOT',
|
||||
'state': 'PENDING',
|
||||
'audit_template_uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2',
|
||||
'audit_template_name': 'at1',
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
'parameters': None,
|
||||
'interval': None,
|
||||
}
|
||||
|
||||
AUDIT_2 = {
|
||||
'uuid': 'a5199d0e-0702-4613-9234-5ae2af8dafea',
|
||||
'deadline': None,
|
||||
'audit_type': 'ONESHOT',
|
||||
'audit_template_uuid': '770ef053-ecb3-48b0-85b5-d55a2dbc6588',
|
||||
'audit_template_name': 'at2',
|
||||
'state': 'PENDING',
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
'parameters': None,
|
||||
'interval': None,
|
||||
}
|
||||
|
||||
AUDIT_3 = {
|
||||
'uuid': '43199d0e-0712-1213-9674-5ae2af8dhgte',
|
||||
'deadline': None,
|
||||
'audit_type': 'CONTINUOUS',
|
||||
'audit_template_uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2',
|
||||
'audit_template_name': 'at1',
|
||||
'state': 'PENDING',
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
'parameters': None,
|
||||
'interval': 3600,
|
||||
}
|
||||
|
||||
|
||||
class AuditShellTest(utils.BaseTestCase):
|
||||
def test_do_audit_show(self):
|
||||
actual = {}
|
||||
fake_print_dict = lambda data, *args, **kwargs: actual.update(data)
|
||||
with mock.patch.object(cliutils, 'print_dict', fake_print_dict):
|
||||
audit = object()
|
||||
a_shell._print_audit_show(audit)
|
||||
exp = ['created_at', 'audit_template_uuid', 'audit_template_name',
|
||||
'updated_at', 'uuid', 'deleted_at', 'state', 'type',
|
||||
'deadline']
|
||||
act = actual.keys()
|
||||
self.assertEqual(sorted(exp), sorted(act))
|
||||
class AuditShellTest(base.CommandTestCase):
|
||||
|
||||
SHORT_LIST_FIELDS = resource_fields.AUDIT_SHORT_LIST_FIELDS
|
||||
SHORT_LIST_FIELD_LABELS = resource_fields.AUDIT_SHORT_LIST_FIELD_LABELS
|
||||
FIELDS = resource_fields.AUDIT_FIELDS
|
||||
FIELD_LABELS = resource_fields.AUDIT_FIELD_LABELS
|
||||
|
||||
def setUp(self):
|
||||
super(self.__class__, self).setUp()
|
||||
|
||||
p_audit_manager = mock.patch.object(resource, 'AuditManager')
|
||||
p_audit_template_manager = mock.patch.object(
|
||||
resource, 'AuditTemplateManager')
|
||||
self.m_audit_mgr_cls = p_audit_manager.start()
|
||||
self.m_audit_template_mgr_cls = p_audit_template_manager.start()
|
||||
self.addCleanup(p_audit_manager.stop)
|
||||
self.addCleanup(p_audit_template_manager.stop)
|
||||
|
||||
self.m_audit_mgr = mock.Mock()
|
||||
self.m_audit_template_mgr = mock.Mock()
|
||||
|
||||
self.m_audit_mgr_cls.return_value = self.m_audit_mgr
|
||||
self.m_audit_template_mgr_cls.return_value = self.m_audit_template_mgr
|
||||
|
||||
self.stdout = six.StringIO()
|
||||
self.cmd = shell.WatcherShell(stdout=self.stdout)
|
||||
|
||||
def test_do_audit_list(self):
|
||||
audit1 = resource.Audit(mock.Mock(), AUDIT_1)
|
||||
audit2 = resource.Audit(mock.Mock(), AUDIT_2)
|
||||
self.m_audit_mgr.list.return_value = [
|
||||
audit1, audit2]
|
||||
|
||||
exit_code, results = self.run_cmd('audit list')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(audit1, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS),
|
||||
self.resource_as_dict(audit2, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_audit_mgr.list.assert_called_once_with(detail=False)
|
||||
|
||||
def test_do_audit_list_detail(self):
|
||||
audit1 = resource.Audit(mock.Mock(), AUDIT_1)
|
||||
audit2 = resource.Audit(mock.Mock(), AUDIT_2)
|
||||
self.m_audit_mgr.list.return_value = [
|
||||
audit1, audit2]
|
||||
|
||||
exit_code, results = self.run_cmd('audit list --detail')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(audit1, self.FIELDS,
|
||||
self.FIELD_LABELS),
|
||||
self.resource_as_dict(audit2, self.FIELDS,
|
||||
self.FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_audit_mgr.list.assert_called_once_with(detail=True)
|
||||
|
||||
def test_do_audit_show_by_uuid(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.audit = 'a5199d0e-0702-4613-9234-5ae2af8dafea'
|
||||
audit = resource.Audit(mock.Mock(), AUDIT_1)
|
||||
self.m_audit_mgr.get.return_value = audit
|
||||
self.m_audit_template_mgr.get.return_value = audit
|
||||
|
||||
a_shell.do_audit_show(client_mock, args)
|
||||
client_mock.audit.get.assert_called_once_with(
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafea'
|
||||
)
|
||||
# assert get_by_name() wasn't called
|
||||
self.assertFalse(client_mock.audit.get_by_name.called)
|
||||
exit_code, result = self.run_cmd(
|
||||
'audit show 5869da81-4876-4687-a1ed-12cd64cf53d9')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(audit, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_mgr.get.assert_called_once_with(
|
||||
'5869da81-4876-4687-a1ed-12cd64cf53d9')
|
||||
|
||||
def test_do_audit_show_by_not_uuid(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.audit = 'not_uuid'
|
||||
self.m_audit_mgr.get.side_effect = exceptions.HTTPNotFound
|
||||
|
||||
self.assertRaises(ValidationError, a_shell.do_audit_show,
|
||||
client_mock, args)
|
||||
exit_code, result = self.run_cmd(
|
||||
'audit show not_uuid', formatting=None)
|
||||
|
||||
self.assertEqual(1, exit_code)
|
||||
self.assertEqual('', result)
|
||||
|
||||
def test_do_audit_delete(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.audit = ['a5199d0e-0702-4613-9234-5ae2af8dafea']
|
||||
self.m_audit_mgr.delete.return_value = ''
|
||||
|
||||
a_shell.do_audit_delete(client_mock, args)
|
||||
client_mock.audit.delete.assert_called_once_with(
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafea')
|
||||
exit_code, result = self.run_cmd(
|
||||
'audit delete 5869da81-4876-4687-a1ed-12cd64cf53d9',
|
||||
formatting=None)
|
||||
|
||||
def test_do_audit_delete_with_not_uuid(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.audit = ['not_uuid']
|
||||
|
||||
self.assertRaises(ValidationError, a_shell.do_audit_delete,
|
||||
client_mock, args)
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual('', result)
|
||||
self.m_audit_mgr.delete.assert_called_once_with(
|
||||
'5869da81-4876-4687-a1ed-12cd64cf53d9')
|
||||
|
||||
def test_do_audit_delete_multiple(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.audit = ['a5199d0e-0702-4613-9234-5ae2af8dafea',
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafeb']
|
||||
self.m_audit_mgr.delete.return_value = ''
|
||||
|
||||
a_shell.do_audit_delete(client_mock, args)
|
||||
client_mock.audit.delete.assert_has_calls(
|
||||
[mock.call('a5199d0e-0702-4613-9234-5ae2af8dafea'),
|
||||
mock.call('a5199d0e-0702-4613-9234-5ae2af8dafeb')])
|
||||
exit_code, result = self.run_cmd(
|
||||
'audit delete 5869da81-4876-4687-a1ed-12cd64cf53d9 '
|
||||
'5b157edd-5a7e-4aaa-b511-f7b33ec86e9f',
|
||||
formatting=None)
|
||||
|
||||
def test_do_audit_delete_multiple_with_not_uuid(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.audit = ['a5199d0e-0702-4613-9234-5ae2af8dafea',
|
||||
'not_uuid',
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafeb']
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual('', result)
|
||||
self.m_audit_mgr.delete.assert_any_call(
|
||||
'5869da81-4876-4687-a1ed-12cd64cf53d9')
|
||||
self.m_audit_mgr.delete.assert_any_call(
|
||||
'5b157edd-5a7e-4aaa-b511-f7b33ec86e9f')
|
||||
|
||||
self.assertRaises(ValidationError, a_shell.do_audit_delete,
|
||||
client_mock, args)
|
||||
client_mock.audit.delete.assert_has_calls(
|
||||
[mock.call('a5199d0e-0702-4613-9234-5ae2af8dafea')])
|
||||
def test_do_audit_delete_with_not_uuid(self):
|
||||
self.m_audit_mgr.delete.return_value = ''
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'audit delete not_uuid',
|
||||
formatting=None)
|
||||
|
||||
self.assertEqual(1, exit_code)
|
||||
self.assertEqual('', result)
|
||||
|
||||
def test_do_audit_update(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.audit = 'a5199d0e-0702-4613-9234-5ae2af8dafea'
|
||||
args.op = 'add'
|
||||
args.attributes = [['arg1=val1', 'arg2=val2']]
|
||||
audit = resource.Audit(mock.Mock(), AUDIT_1)
|
||||
self.m_audit_mgr.update.return_value = audit
|
||||
|
||||
a_shell.do_audit_update(client_mock, args)
|
||||
patch = commonutils.args_array_to_patch(
|
||||
args.op,
|
||||
args.attributes[0])
|
||||
client_mock.audit.update.assert_called_once_with(
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafea', patch)
|
||||
exit_code, result = self.run_cmd(
|
||||
'audit update 5869da81-4876-4687-a1ed-12cd64cf53d9 '
|
||||
'replace state=PENDING')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(audit, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_mgr.update.assert_called_once_with(
|
||||
'5869da81-4876-4687-a1ed-12cd64cf53d9',
|
||||
[{'op': 'replace', 'path': '/state', 'value': 'PENDING'}])
|
||||
|
||||
def test_do_audit_update_with_not_uuid(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.audit = ['not_uuid']
|
||||
args.op = 'add'
|
||||
args.attributes = [['arg1=val1', 'arg2=val2']]
|
||||
self.m_audit_mgr.update.return_value = ''
|
||||
|
||||
self.assertRaises(ValidationError, a_shell.do_audit_update,
|
||||
client_mock, args)
|
||||
exit_code, result = self.run_cmd(
|
||||
'audit update not_uuid replace state=PENDING', formatting=None)
|
||||
|
||||
def test_do_audit_create(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
self.assertEqual(1, exit_code)
|
||||
self.assertEqual('', result)
|
||||
|
||||
a_shell.do_audit_create(client_mock, args)
|
||||
client_mock.audit.create.assert_called_once_with()
|
||||
def test_do_audit_create_with_audit_template_uuid(self):
|
||||
audit = resource.Audit(mock.Mock(), AUDIT_1)
|
||||
self.m_audit_mgr.create.return_value = audit
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'audit create -a f8e47706-efcf-49a4-a5c4-af604eb492f2')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(audit, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_mgr.create.assert_called_once_with(
|
||||
audit_template_uuid='f8e47706-efcf-49a4-a5c4-af604eb492f2',
|
||||
audit_type='ONESHOT')
|
||||
|
||||
def test_do_audit_create_with_audit_template_name(self):
|
||||
audit = resource.Audit(mock.Mock(), AUDIT_1)
|
||||
audit_template = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1)
|
||||
self.m_audit_template_mgr.get.return_value = audit_template
|
||||
self.m_audit_mgr.create.return_value = audit
|
||||
|
||||
exit_code, result = self.run_cmd('audit create -a at1')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(audit, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_mgr.create.assert_called_once_with(
|
||||
audit_template_uuid='f8e47706-efcf-49a4-a5c4-af604eb492f2',
|
||||
audit_type='ONESHOT')
|
||||
|
||||
def test_do_audit_create_with_deadline(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.deadline = 'deadline'
|
||||
audit = resource.Audit(mock.Mock(), AUDIT_1)
|
||||
audit_template = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1)
|
||||
self.m_audit_template_mgr.get.return_value = audit_template
|
||||
self.m_audit_mgr.create.return_value = audit
|
||||
|
||||
a_shell.do_audit_create(client_mock, args)
|
||||
client_mock.audit.create.assert_called_once_with(
|
||||
deadline='deadline')
|
||||
exit_code, result = self.run_cmd(
|
||||
'audit create -a at1 -d 2016-04-28T10:48:32.064802')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(audit, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_mgr.create.assert_called_once_with(
|
||||
audit_template_uuid='f8e47706-efcf-49a4-a5c4-af604eb492f2',
|
||||
audit_type='ONESHOT',
|
||||
deadline='2016-04-28T10:48:32.064802')
|
||||
|
||||
def test_do_audit_create_with_type(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.type = 'type'
|
||||
audit = resource.Audit(mock.Mock(), AUDIT_1)
|
||||
audit_template = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1)
|
||||
self.m_audit_template_mgr.get.return_value = audit_template
|
||||
self.m_audit_mgr.create.return_value = audit
|
||||
|
||||
a_shell.do_audit_create(client_mock, args)
|
||||
client_mock.audit.create.assert_called_once_with(type='type')
|
||||
exit_code, result = self.run_cmd(
|
||||
'audit create -a at1 -t ONESHOT')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(audit, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_mgr.create.assert_called_once_with(
|
||||
audit_template_uuid='f8e47706-efcf-49a4-a5c4-af604eb492f2',
|
||||
audit_type='ONESHOT')
|
||||
|
||||
def test_do_audit_create_with_parameter(self):
|
||||
audit = resource.Audit(mock.Mock(), AUDIT_1)
|
||||
audit_template = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1)
|
||||
self.m_audit_template_mgr.get.return_value = audit_template
|
||||
self.m_audit_mgr.create.return_value = audit
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'audit create -a at1 -p para1=10 -p para2=20')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(audit, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_mgr.create.assert_called_once_with(
|
||||
audit_template_uuid='f8e47706-efcf-49a4-a5c4-af604eb492f2',
|
||||
audit_type='ONESHOT',
|
||||
parameters={'para1': 10, 'para2': 20})
|
||||
|
||||
def test_do_audit_create_with_type_continuous(self):
|
||||
audit = resource.Audit(mock.Mock(), AUDIT_3)
|
||||
audit_template = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1)
|
||||
self.m_audit_template_mgr.get.return_value = audit_template
|
||||
self.m_audit_mgr.create.return_value = audit
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'audit create -a at1 -t CONTINUOUS -i 3600')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
self.resource_as_dict(audit, self.FIELDS, self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_mgr.create.assert_called_once_with(
|
||||
audit_template_uuid='f8e47706-efcf-49a4-a5c4-af604eb492f2',
|
||||
audit_type='CONTINUOUS',
|
||||
interval='3600')
|
||||
|
||||
@@ -18,8 +18,7 @@
|
||||
import copy
|
||||
|
||||
from six.moves.urllib import parse as urlparse
|
||||
import testtools
|
||||
from testtools.matchers import HasLength
|
||||
from testtools import matchers
|
||||
|
||||
from watcherclient.tests import utils
|
||||
import watcherclient.v1.audit_template
|
||||
@@ -31,7 +30,10 @@ AUDIT_TMPL1 = {
|
||||
'description': 'Audit Template 1 description',
|
||||
'host_aggregate': 5,
|
||||
'extra': {'automatic': False},
|
||||
'goal': 'MINIMIZE_LICENSING_COST'
|
||||
'goal_uuid': '7568667b-51fe-4087-9eb1-29b26891036f',
|
||||
'goal_name': 'SERVER_CONSOLIDATION',
|
||||
'strategy_uuid': 'bbe6b966-f98e-439b-a01a-17b9b3b8478b',
|
||||
'strategy_name': 'server_consolidation',
|
||||
}
|
||||
|
||||
AUDIT_TMPL2 = {
|
||||
@@ -41,7 +43,10 @@ AUDIT_TMPL2 = {
|
||||
'description': 'Audit Template 2 description',
|
||||
'host_aggregate': 8,
|
||||
'extra': {'automatic': True},
|
||||
'goal': 'SERVERS_CONSOLIDATION'
|
||||
'goal_uuid': 'e75ee410-b32b-465f-88b5-4397705f9473',
|
||||
'goal_name': 'DUMMY',
|
||||
'strategy_uuid': 'ae99a4a4-acbc-4c67-abe1-e37128fac45d',
|
||||
'strategy_name': 'dummy',
|
||||
}
|
||||
|
||||
AUDIT_TMPL3 = {
|
||||
@@ -51,12 +56,17 @@ AUDIT_TMPL3 = {
|
||||
'description': 'Audit Template 3 description',
|
||||
'host_aggregate': 7,
|
||||
'extra': {'automatic': True},
|
||||
'goal': 'MINIMIZE_LICENSING_COST'
|
||||
'goal_uuid': '7568667b-51fe-4087-9eb1-29b26891036f',
|
||||
'goal_name': 'SERVER_CONSOLIDATION',
|
||||
}
|
||||
|
||||
CREATE_AUDIT_TEMPLATE = copy.deepcopy(AUDIT_TMPL1)
|
||||
del CREATE_AUDIT_TEMPLATE['id']
|
||||
del CREATE_AUDIT_TEMPLATE['uuid']
|
||||
del CREATE_AUDIT_TEMPLATE['goal_name']
|
||||
del CREATE_AUDIT_TEMPLATE['strategy_name']
|
||||
CREATE_AUDIT_TEMPLATE['goal'] = CREATE_AUDIT_TEMPLATE.pop('goal_uuid')
|
||||
CREATE_AUDIT_TEMPLATE['strategy'] = CREATE_AUDIT_TEMPLATE.pop('strategy_uuid')
|
||||
|
||||
UPDATED_AUDIT_TMPL1 = copy.deepcopy(AUDIT_TMPL1)
|
||||
NEW_NAME = 'Audit Template_1 new name'
|
||||
@@ -125,14 +135,14 @@ fake_responses = {
|
||||
{"audit_templates": [AUDIT_TMPL1]},
|
||||
),
|
||||
},
|
||||
'/v1/audit_templates/detail?goal=%s' % AUDIT_TMPL1['goal']:
|
||||
'/v1/audit_templates/detail?goal=%s' % AUDIT_TMPL1['goal_uuid']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"audit_templates": [AUDIT_TMPL1, AUDIT_TMPL3]},
|
||||
),
|
||||
},
|
||||
'/v1/audit_templates/?goal=%s' % AUDIT_TMPL1['goal']:
|
||||
'/v1/audit_templates/?goal=%s' % AUDIT_TMPL1['goal_uuid']:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
@@ -176,8 +186,58 @@ fake_responses_sorting = {
|
||||
},
|
||||
}
|
||||
|
||||
fake_responses_filter_by_goal_uuid = {
|
||||
'/v1/audit_templates/?goal=e75ee410-b32b-465f-88b5-4397705f9473':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"audit_templates": [AUDIT_TMPL2]}
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
class AuditTemplateManagerTest(testtools.TestCase):
|
||||
fake_responses_filter_by_goal_name = {
|
||||
'/v1/audit_templates/?goal=DUMMY':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"audit_templates": [AUDIT_TMPL2]}
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
fake_responses_filter_by_strategy_uuid = {
|
||||
'/v1/audit_templates/?strategy=ae99a4a4-acbc-4c67-abe1-e37128fac45d':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"audit_templates": [AUDIT_TMPL2]}
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
fake_responses_filter_by_strategy_name = {
|
||||
'/v1/audit_templates/?strategy=dummy':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"audit_templates": [AUDIT_TMPL2]}
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
fake_responses_filter_by_strategy_and_goal_name = {
|
||||
'/v1/audit_templates/?goal=DUMMY&strategy=dummy':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"audit_templates": [AUDIT_TMPL2]}
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class AuditTemplateManagerTest(utils.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(AuditTemplateManagerTest, self).setUp()
|
||||
@@ -193,7 +253,7 @@ class AuditTemplateManagerTest(testtools.TestCase):
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(1, len(audit_templates))
|
||||
|
||||
def test_audit_templates_list_by_name(self):
|
||||
def test_audit_templates_list_filter_by_name(self):
|
||||
audit_templates = self.mgr.list(name=AUDIT_TMPL1['name'])
|
||||
expect = [
|
||||
('GET', '/v1/audit_templates/?name=%s' % AUDIT_TMPL1['name'],
|
||||
@@ -202,6 +262,77 @@ class AuditTemplateManagerTest(testtools.TestCase):
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(1, len(audit_templates))
|
||||
|
||||
def test_audit_templates_list_filter_by_goal_uuid(self):
|
||||
self.api = utils.FakeAPI(fake_responses_filter_by_goal_uuid)
|
||||
self.mgr = watcherclient.v1.audit_template.AuditTemplateManager(
|
||||
self.api)
|
||||
audit_templates = self.mgr.list(
|
||||
goal="e75ee410-b32b-465f-88b5-4397705f9473")
|
||||
expect = [
|
||||
('GET',
|
||||
'/v1/audit_templates/?goal=%s' % AUDIT_TMPL2['goal_uuid'],
|
||||
{}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(1, len(audit_templates))
|
||||
|
||||
def test_audit_templates_list_filter_by_goal_name(self):
|
||||
self.api = utils.FakeAPI(fake_responses_filter_by_goal_name)
|
||||
self.mgr = watcherclient.v1.audit_template.AuditTemplateManager(
|
||||
self.api)
|
||||
audit_templates = self.mgr.list(goal="DUMMY")
|
||||
expect = [
|
||||
('GET',
|
||||
'/v1/audit_templates/?goal=%s' % AUDIT_TMPL2['goal_name'],
|
||||
{}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(1, len(audit_templates))
|
||||
|
||||
def test_audit_templates_list_filter_by_strategy_uuid(self):
|
||||
self.api = utils.FakeAPI(fake_responses_filter_by_strategy_uuid)
|
||||
self.mgr = watcherclient.v1.audit_template.AuditTemplateManager(
|
||||
self.api)
|
||||
audit_templates = self.mgr.list(
|
||||
strategy="ae99a4a4-acbc-4c67-abe1-e37128fac45d")
|
||||
expect = [
|
||||
('GET',
|
||||
'/v1/audit_templates/?strategy=%s' % (
|
||||
AUDIT_TMPL2['strategy_uuid']),
|
||||
{}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(1, len(audit_templates))
|
||||
|
||||
def test_audit_templates_list_filter_by_strategy_name(self):
|
||||
self.api = utils.FakeAPI(fake_responses_filter_by_strategy_name)
|
||||
self.mgr = watcherclient.v1.audit_template.AuditTemplateManager(
|
||||
self.api)
|
||||
audit_templates = self.mgr.list(strategy="dummy")
|
||||
expect = [
|
||||
('GET',
|
||||
'/v1/audit_templates/?strategy=%s' % (
|
||||
AUDIT_TMPL2['strategy_name']),
|
||||
{}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(1, len(audit_templates))
|
||||
|
||||
def test_audit_templates_list_filter_by_goal_and_strategy_name(self):
|
||||
self.api = utils.FakeAPI(
|
||||
fake_responses_filter_by_strategy_and_goal_name)
|
||||
self.mgr = watcherclient.v1.audit_template.AuditTemplateManager(
|
||||
self.api)
|
||||
audit_templates = self.mgr.list(goal="DUMMY", strategy="dummy")
|
||||
expect = [
|
||||
('GET',
|
||||
'/v1/audit_templates/?goal=%s&strategy=%s' % (
|
||||
AUDIT_TMPL2['goal_name'], AUDIT_TMPL2['strategy_name']),
|
||||
{}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(1, len(audit_templates))
|
||||
|
||||
def test_audit_templates_list_detail(self):
|
||||
audit_templates = self.mgr.list(detail=True)
|
||||
expect = [
|
||||
@@ -230,7 +361,7 @@ class AuditTemplateManagerTest(testtools.TestCase):
|
||||
('GET', '/v1/audit_templates/?limit=1', {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(audit_templates, HasLength(1))
|
||||
self.assertThat(audit_templates, matchers.HasLength(1))
|
||||
|
||||
def test_audit_templates_list_pagination_no_limit(self):
|
||||
self.api = utils.FakeAPI(fake_responses_pagination)
|
||||
@@ -242,7 +373,7 @@ class AuditTemplateManagerTest(testtools.TestCase):
|
||||
('GET', '/v1/audit_templates/?limit=1', {}, None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(audit_templates, HasLength(2))
|
||||
self.assertThat(audit_templates, matchers.HasLength(2))
|
||||
|
||||
def test_audit_templates_list_sort_key(self):
|
||||
self.api = utils.FakeAPI(fake_responses_sorting)
|
||||
@@ -278,9 +409,10 @@ class AuditTemplateManagerTest(testtools.TestCase):
|
||||
audit_template.description)
|
||||
self.assertEqual(AUDIT_TMPL1['host_aggregate'],
|
||||
audit_template.host_aggregate)
|
||||
self.assertEqual(AUDIT_TMPL1['goal'], audit_template.goal)
|
||||
self.assertEqual(AUDIT_TMPL1['extra'],
|
||||
audit_template.extra)
|
||||
self.assertEqual(AUDIT_TMPL1['goal_uuid'], audit_template.goal_uuid)
|
||||
self.assertEqual(AUDIT_TMPL1['strategy_uuid'],
|
||||
audit_template.strategy_uuid)
|
||||
self.assertEqual(AUDIT_TMPL1['extra'], audit_template.extra)
|
||||
|
||||
def test_audit_templates_show_by_name(self):
|
||||
audit_template = self.mgr.get(urlparse.quote(AUDIT_TMPL1['name']))
|
||||
@@ -297,9 +429,10 @@ class AuditTemplateManagerTest(testtools.TestCase):
|
||||
audit_template.description)
|
||||
self.assertEqual(AUDIT_TMPL1['host_aggregate'],
|
||||
audit_template.host_aggregate)
|
||||
self.assertEqual(AUDIT_TMPL1['goal'], audit_template.goal)
|
||||
self.assertEqual(AUDIT_TMPL1['extra'],
|
||||
audit_template.extra)
|
||||
self.assertEqual(AUDIT_TMPL1['goal_uuid'], audit_template.goal_uuid)
|
||||
self.assertEqual(AUDIT_TMPL1['strategy_uuid'],
|
||||
audit_template.strategy_uuid)
|
||||
self.assertEqual(AUDIT_TMPL1['extra'], audit_template.extra)
|
||||
|
||||
def test_create(self):
|
||||
audit_template = self.mgr.create(**CREATE_AUDIT_TEMPLATE)
|
||||
|
||||
@@ -14,124 +14,363 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import datetime
|
||||
|
||||
import mock
|
||||
import six
|
||||
|
||||
from watcherclient.common import utils as commonutils
|
||||
from watcherclient.openstack.common import cliutils
|
||||
from watcherclient.tests import utils
|
||||
import watcherclient.v1.audit_template_shell as at_shell
|
||||
from watcherclient import shell
|
||||
from watcherclient.tests.v1 import base
|
||||
from watcherclient import v1 as resource
|
||||
from watcherclient.v1 import resource_fields
|
||||
|
||||
GOAL_1 = {
|
||||
'uuid': "fc087747-61be-4aad-8126-b701731ae836",
|
||||
'name': "SERVER_CONSOLIDATION",
|
||||
'display_name': 'Server Consolidation',
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
}
|
||||
|
||||
STRATEGY_1 = {
|
||||
'uuid': '2cf86250-d309-4b81-818e-1537f3dba6e5',
|
||||
'name': 'basic',
|
||||
'display_name': 'Basic consolidation',
|
||||
'goal_uuid': 'fc087747-61be-4aad-8126-b701731ae836',
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
}
|
||||
|
||||
AUDIT_TEMPLATE_1 = {
|
||||
'uuid': 'f8e47706-efcf-49a4-a5c4-af604eb492f2',
|
||||
'name': 'at1',
|
||||
'description': 'Audit Template 1 description',
|
||||
'host_aggregate': 5,
|
||||
'extra': {'automatic': False},
|
||||
'goal_uuid': 'fc087747-61be-4aad-8126-b701731ae836',
|
||||
'goal_name': 'SERVER_CONSOLIDATION',
|
||||
'strategy_uuid': '2cf86250-d309-4b81-818e-1537f3dba6e5',
|
||||
'strategy_name': 'basic',
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
}
|
||||
|
||||
AUDIT_TEMPLATE_2 = {
|
||||
'uuid': '2a60ca9b-09b0-40ff-8674-de8a36fc4bc8',
|
||||
'name': 'at2',
|
||||
'description': 'Audit Template 2',
|
||||
'host_aggregate': 3,
|
||||
'extra': {'automatic': False},
|
||||
'goal_uuid': 'fc087747-61be-4aad-8126-b701731ae836',
|
||||
'goal_name': 'SERVER_CONSOLIDATION',
|
||||
'strategy_uuid': None,
|
||||
'strategy_name': None,
|
||||
'created_at': datetime.datetime.now().isoformat(),
|
||||
'updated_at': None,
|
||||
'deleted_at': None,
|
||||
}
|
||||
|
||||
|
||||
class AuditTemplateShellTest(utils.BaseTestCase):
|
||||
def test_do_audit_template_show(self):
|
||||
actual = {}
|
||||
fake_print_dict = lambda data, *args, **kwargs: actual.update(data)
|
||||
with mock.patch.object(cliutils, 'print_dict', fake_print_dict):
|
||||
audit_template = object()
|
||||
at_shell._print_audit_template_show(audit_template)
|
||||
exp = [
|
||||
'uuid', 'created_at', 'updated_at', 'deleted_at',
|
||||
'description', 'host_aggregate', 'name',
|
||||
'extra', 'goal']
|
||||
act = actual.keys()
|
||||
self.assertEqual(sorted(exp), sorted(act))
|
||||
class AuditTemplateShellTest(base.CommandTestCase):
|
||||
|
||||
def test_do_audit_template_show_by_uuid(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
setattr(args, 'audit-template', 'a5199d0e-0702-4613-9234-5ae2af8dafea')
|
||||
SHORT_LIST_FIELDS = resource_fields.AUDIT_TEMPLATE_SHORT_LIST_FIELDS
|
||||
SHORT_LIST_FIELD_LABELS = (
|
||||
resource_fields.AUDIT_TEMPLATE_SHORT_LIST_FIELD_LABELS)
|
||||
FIELDS = resource_fields.AUDIT_TEMPLATE_FIELDS
|
||||
FIELD_LABELS = resource_fields.AUDIT_TEMPLATE_FIELD_LABELS
|
||||
|
||||
at_shell.do_audit_template_show(client_mock, args)
|
||||
client_mock.audit_template.get.assert_called_once_with(
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafea'
|
||||
def setUp(self):
|
||||
super(self.__class__, self).setUp()
|
||||
|
||||
# goal mock
|
||||
p_goal_manager = mock.patch.object(resource, 'GoalManager')
|
||||
self.m_goal_mgr_cls = p_goal_manager.start()
|
||||
self.addCleanup(p_goal_manager.stop)
|
||||
|
||||
self.m_goal_mgr = mock.Mock()
|
||||
self.m_goal_mgr_cls.return_value = self.m_goal_mgr
|
||||
|
||||
# strategy mock
|
||||
p_strategy_manager = mock.patch.object(resource, 'StrategyManager')
|
||||
self.m_strategy_mgr_cls = p_strategy_manager.start()
|
||||
self.addCleanup(p_strategy_manager.stop)
|
||||
|
||||
self.m_strategy_mgr = mock.Mock()
|
||||
self.m_strategy_mgr_cls.return_value = self.m_strategy_mgr
|
||||
|
||||
# audit template mock
|
||||
p_audit_template_manager = mock.patch.object(
|
||||
resource, 'AuditTemplateManager')
|
||||
self.m_audit_template_mgr_cls = p_audit_template_manager.start()
|
||||
self.addCleanup(p_audit_template_manager.stop)
|
||||
|
||||
self.m_audit_template_mgr = mock.Mock()
|
||||
self.m_audit_template_mgr_cls.return_value = self.m_audit_template_mgr
|
||||
|
||||
# stdout mock
|
||||
self.stdout = six.StringIO()
|
||||
self.cmd = shell.WatcherShell(stdout=self.stdout)
|
||||
|
||||
def test_do_audit_template_list(self):
|
||||
audit_template1 = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1)
|
||||
audit_template2 = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_2)
|
||||
self.m_audit_template_mgr.list.return_value = [
|
||||
audit_template1, audit_template2]
|
||||
|
||||
exit_code, results = self.run_cmd('audittemplate list')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(audit_template1, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS),
|
||||
self.resource_as_dict(audit_template2, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_audit_template_mgr.list.assert_called_once_with(detail=False)
|
||||
|
||||
def test_do_audit_template_list_detail(self):
|
||||
audit_template1 = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1)
|
||||
audit_template2 = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_2)
|
||||
self.m_audit_template_mgr.list.return_value = [
|
||||
audit_template1, audit_template2]
|
||||
|
||||
exit_code, results = self.run_cmd('audittemplate list --detail')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(audit_template1, self.FIELDS,
|
||||
self.FIELD_LABELS),
|
||||
self.resource_as_dict(audit_template2, self.FIELDS,
|
||||
self.FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_audit_template_mgr.list.assert_called_once_with(detail=True)
|
||||
|
||||
def test_do_audit_template_list_filter_by_goal_uuid(self):
|
||||
audit_template1 = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1)
|
||||
audit_template2 = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_2)
|
||||
self.m_audit_template_mgr.list.return_value = [
|
||||
audit_template1, audit_template2]
|
||||
|
||||
exit_code, results = self.run_cmd(
|
||||
'audittemplate list --goal '
|
||||
'fc087747-61be-4aad-8126-b701731ae836')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(audit_template1, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS),
|
||||
self.resource_as_dict(audit_template2, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_audit_template_mgr.list.assert_called_once_with(
|
||||
detail=False,
|
||||
goal='fc087747-61be-4aad-8126-b701731ae836',
|
||||
)
|
||||
|
||||
def test_do_audit_template_list_filter_by_goal_name(self):
|
||||
goal1 = resource.Goal(mock.Mock(), GOAL_1)
|
||||
strategy1 = resource.Strategy(mock.Mock(), STRATEGY_1)
|
||||
audit_template1 = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1)
|
||||
audit_template2 = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_2)
|
||||
self.m_goal_mgr.get.return_value = goal1
|
||||
self.m_strategy_mgr.get.return_value = strategy1
|
||||
self.m_audit_template_mgr.list.return_value = [
|
||||
audit_template1, audit_template2]
|
||||
|
||||
exit_code, results = self.run_cmd(
|
||||
'audittemplate list --goal SERVER_CONSOLIDATION')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(audit_template1, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS),
|
||||
self.resource_as_dict(audit_template2, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_audit_template_mgr.list.assert_called_once_with(
|
||||
detail=False,
|
||||
goal='SERVER_CONSOLIDATION',
|
||||
)
|
||||
|
||||
def test_do_audit_template_list_filter_by_strategy_uuid(self):
|
||||
goal1 = resource.Goal(mock.Mock(), GOAL_1)
|
||||
strategy1 = resource.Strategy(mock.Mock(), STRATEGY_1)
|
||||
audit_template1 = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1)
|
||||
self.m_goal_mgr.get.return_value = goal1
|
||||
self.m_strategy_mgr.get.return_value = strategy1
|
||||
self.m_audit_template_mgr.list.return_value = [audit_template1]
|
||||
|
||||
exit_code, results = self.run_cmd(
|
||||
'audittemplate list --strategy '
|
||||
'2cf86250-d309-4b81-818e-1537f3dba6e5')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(audit_template1, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_audit_template_mgr.list.assert_called_once_with(
|
||||
detail=False,
|
||||
strategy='2cf86250-d309-4b81-818e-1537f3dba6e5',
|
||||
)
|
||||
|
||||
def test_do_audit_template_list_filter_by_strategy_name(self):
|
||||
audit_template1 = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1)
|
||||
self.m_audit_template_mgr.list.return_value = [audit_template1]
|
||||
|
||||
exit_code, results = self.run_cmd(
|
||||
'audittemplate list --strategy '
|
||||
'basic')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(
|
||||
[self.resource_as_dict(audit_template1, self.SHORT_LIST_FIELDS,
|
||||
self.SHORT_LIST_FIELD_LABELS)],
|
||||
results)
|
||||
|
||||
self.m_audit_template_mgr.list.assert_called_once_with(
|
||||
detail=False,
|
||||
strategy='basic',
|
||||
)
|
||||
# assert get_by_name() wasn't called
|
||||
self.assertFalse(client_mock.audit_template.get_by_name.called)
|
||||
|
||||
def test_do_audit_template_show_by_name(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
setattr(args, 'audit-template', "a5199d0e-0702-4613-9234-5ae2af8dafea")
|
||||
audit_template = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1)
|
||||
self.m_audit_template_mgr.get.return_value = audit_template
|
||||
|
||||
at_shell.do_audit_template_show(client_mock, args)
|
||||
client_mock.audit_template.get.assert_called_once_with(
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafea')
|
||||
exit_code, result = self.run_cmd('audittemplate show at1')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(self.resource_as_dict(audit_template, self.FIELDS,
|
||||
self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_template_mgr.get.assert_called_once_with('at1')
|
||||
|
||||
def test_do_audit_template_show_by_uuid(self):
|
||||
audit_template = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1)
|
||||
self.m_audit_template_mgr.get.return_value = audit_template
|
||||
|
||||
exit_code, result = self.run_cmd(
|
||||
'audittemplate show f8e47706-efcf-49a4-a5c4-af604eb492f2')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(self.resource_as_dict(audit_template, self.FIELDS,
|
||||
self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_template_mgr.get.assert_called_once_with(
|
||||
'f8e47706-efcf-49a4-a5c4-af604eb492f2')
|
||||
|
||||
def test_do_audit_template_delete(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
setattr(args, 'audit-template',
|
||||
['a5199d0e-0702-4613-9234-5ae2af8dafea'])
|
||||
self.m_audit_template_mgr.delete.return_value = ''
|
||||
|
||||
at_shell.do_audit_template_delete(client_mock, args)
|
||||
client_mock.audit_template.delete.assert_called_once_with(
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafea')
|
||||
exit_code, result = self.run_cmd(
|
||||
'audittemplate delete f8e47706-efcf-49a4-a5c4-af604eb492f2',
|
||||
formatting=None)
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual('', result)
|
||||
self.m_audit_template_mgr.delete.assert_called_once_with(
|
||||
'f8e47706-efcf-49a4-a5c4-af604eb492f2')
|
||||
|
||||
def test_do_audit_template_delete_multiple(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
setattr(args, 'audit-template',
|
||||
['a5199d0e-0702-4613-9234-5ae2af8dafea',
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafeb'])
|
||||
self.m_audit_template_mgr.delete.return_value = ''
|
||||
|
||||
at_shell.do_audit_template_delete(client_mock, args)
|
||||
client_mock.audit_template.delete.assert_has_calls(
|
||||
[mock.call('a5199d0e-0702-4613-9234-5ae2af8dafea'),
|
||||
mock.call('a5199d0e-0702-4613-9234-5ae2af8dafeb')])
|
||||
exit_code, result = self.run_cmd(
|
||||
'audittemplate delete f8e47706-efcf-49a4-a5c4-af604eb492f2 '
|
||||
'92dfce2f-0a5e-473f-92b7-d92e21839e4d',
|
||||
formatting=None)
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual('', result)
|
||||
self.m_audit_template_mgr.delete.assert_any_call(
|
||||
'f8e47706-efcf-49a4-a5c4-af604eb492f2')
|
||||
self.m_audit_template_mgr.delete.assert_any_call(
|
||||
'92dfce2f-0a5e-473f-92b7-d92e21839e4d')
|
||||
|
||||
def test_do_audit_template_update(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
setattr(args, 'audit-template', "a5199d0e-0702-4613-9234-5ae2af8dafea")
|
||||
args.op = 'add'
|
||||
args.attributes = [['arg1=val1', 'arg2=val2']]
|
||||
audit_template = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1)
|
||||
self.m_audit_template_mgr.update.return_value = audit_template
|
||||
|
||||
at_shell.do_audit_template_update(client_mock, args)
|
||||
patch = commonutils.args_array_to_patch(
|
||||
args.op,
|
||||
args.attributes[0])
|
||||
client_mock.audit_template.update.assert_called_once_with(
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafea', patch)
|
||||
exit_code, result = self.run_cmd(
|
||||
'audittemplate update at1 replace host_aggregate=5')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(self.resource_as_dict(audit_template, self.FIELDS,
|
||||
self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_template_mgr.update.assert_called_once_with(
|
||||
'at1',
|
||||
[{'op': 'replace', 'path': '/host_aggregate', 'value': 5}])
|
||||
|
||||
def test_do_audit_template_create(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
audit_template = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1)
|
||||
self.m_audit_template_mgr.create.return_value = audit_template
|
||||
|
||||
at_shell.do_audit_template_create(client_mock, args)
|
||||
client_mock.audit_template.create.assert_called_once_with()
|
||||
exit_code, result = self.run_cmd(
|
||||
'audittemplate create at1 fc087747-61be-4aad-8126-b701731ae836')
|
||||
|
||||
def test_do_audit_template_create_with_name(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.name = 'my audit template'
|
||||
|
||||
at_shell.do_audit_template_create(client_mock, args)
|
||||
client_mock.audit_template.create.assert_called_once_with(
|
||||
name='my audit template')
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(self.resource_as_dict(audit_template, self.FIELDS,
|
||||
self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_template_mgr.create.assert_called_once_with(
|
||||
goal='fc087747-61be-4aad-8126-b701731ae836',
|
||||
name='at1')
|
||||
|
||||
def test_do_audit_template_create_with_description(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.description = 'description'
|
||||
audit_template = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1)
|
||||
self.m_audit_template_mgr.create.return_value = audit_template
|
||||
|
||||
at_shell.do_audit_template_create(client_mock, args)
|
||||
client_mock.audit_template.create.assert_called_once_with(
|
||||
description='description')
|
||||
exit_code, result = self.run_cmd(
|
||||
'audittemplate create at1 fc087747-61be-4aad-8126-b701731ae836 '
|
||||
'-d "Audit Template 1 description"')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(self.resource_as_dict(audit_template, self.FIELDS,
|
||||
self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_template_mgr.create.assert_called_once_with(
|
||||
goal='fc087747-61be-4aad-8126-b701731ae836',
|
||||
name='at1',
|
||||
description='Audit Template 1 description')
|
||||
|
||||
def test_do_audit_template_create_with_aggregate(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.host_aggregate = 5
|
||||
audit_template = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1)
|
||||
self.m_audit_template_mgr.create.return_value = audit_template
|
||||
|
||||
at_shell.do_audit_template_create(client_mock, args)
|
||||
client_mock.audit_template.create.assert_called_once_with(
|
||||
host_aggregate=5)
|
||||
exit_code, result = self.run_cmd(
|
||||
'audittemplate create at1 fc087747-61be-4aad-8126-b701731ae836 '
|
||||
'-a 5')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(self.resource_as_dict(audit_template, self.FIELDS,
|
||||
self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_template_mgr.create.assert_called_once_with(
|
||||
goal='fc087747-61be-4aad-8126-b701731ae836',
|
||||
name='at1',
|
||||
host_aggregate='5')
|
||||
|
||||
def test_do_audit_template_create_with_extra(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.extra = ['automatic=true']
|
||||
audit_template = resource.AuditTemplate(mock.Mock(), AUDIT_TEMPLATE_1)
|
||||
self.m_audit_template_mgr.create.return_value = audit_template
|
||||
|
||||
at_shell.do_audit_template_create(client_mock, args)
|
||||
client_mock.audit_template.create.assert_called_once_with(
|
||||
exit_code, result = self.run_cmd(
|
||||
'audittemplate create at1 fc087747-61be-4aad-8126-b701731ae836 '
|
||||
'-e automatic=true')
|
||||
|
||||
self.assertEqual(0, exit_code)
|
||||
self.assertEqual(self.resource_as_dict(audit_template, self.FIELDS,
|
||||
self.FIELD_LABELS),
|
||||
result)
|
||||
self.m_audit_template_mgr.create.assert_called_once_with(
|
||||
goal='fc087747-61be-4aad-8126-b701731ae836',
|
||||
name='at1',
|
||||
extra={'automatic': True})
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user